Private
Public Access
Release 0.13.4
This commit is contained in:
1 parent
ef03258e22
commit
fcdfc4b431
21 files changed
+71
-27
No files matched your search
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"id": "org.webosbrew.histogramhyperion",
|
||||
"version": "0.13.3",
|
||||
"version": "0.13.4",
|
||||
"type": "web",
|
||||
"title": "HyperDRM",
|
||||
"appDescription": "Ambient lighting for webOS, Hyperion and HyperHDR",
|
||||
"iconUri": "https://gitea.adnau.de/matze19999/HyperDRM/raw/branch/main/app/icon.png",
|
||||
"sourceUrl": "https://gitea.adnau.de/matze19999/HyperDRM",
|
||||
"rootRequired": true,
|
||||
"ipkUrl": "https://gitea.adnau.de/matze19999/HyperDRM/raw/branch/main/HyperDRM_0.13.3_arm.ipk",
|
||||
"ipkUrl": "https://gitea.adnau.de/matze19999/HyperDRM/raw/branch/main/HyperDRM_0.13.4_arm.ipk",
|
||||
"ipkHash": {
|
||||
"sha256": "acb0978100796e8765e586a91183599ef4eb783631d8b68205d716621790e232"
|
||||
"sha256": "3028ea164e05ff6b9c601aa7f3ff0f05b17781ba603eaebe3395abae503f7bb7"
|
||||
},
|
||||
"ipkSize": 1208120
|
||||
"ipkSize": 1208554
|
||||
}
|
||||
Binary file not shown.
@@ -27,7 +27,7 @@ Only the own service is modified. Optional Auto PicCap can configure its receive
|
||||
|
||||
## Installation
|
||||
|
||||
The current package is [HyperDRM_0.13.3_arm.ipk](HyperDRM_0.13.3_arm.ipk). Install it using the TV's webOS/Homebrew package installation workflow.
|
||||
The current package is [HyperDRM_0.13.4_arm.ipk](HyperDRM_0.13.4_arm.ipk). Install it using the TV's webOS/Homebrew package installation workflow.
|
||||
|
||||
Elevate the installed own service through Homebrew Channel:
|
||||
|
||||
@@ -237,3 +237,5 @@ tests do not replace real LG capture and remote-control validation on a TV.
|
||||
Histogram near-black handling suppresses colors with a maximum RGB channel of 16 or less, gradually restores brightness between 16 and 32, and preserves brighter colors. This applies only to histogram output; PicCap, receiver calibration and raw calibration samples are unchanged.
|
||||
|
||||
Thin letterbox detection compares both edges against lit interior histogram samples and requires a consistently dark upper edge for at least two seconds. Histogram probes cannot determine the exact bar height or guarantee detection on every scene.
|
||||
|
||||
Histogram capture defaults to 50 ms. The Receiver panel provides a saved Capture interval (ms) setting from 25 to 1000 in steps of 25. Existing settings without this field use 50 ms. PicCap capture timing is unchanged.
|
||||
+9
-2
@@ -125,6 +125,7 @@ function display(result) {
|
||||
document.getElementById('host').value = result.host;
|
||||
document.getElementById('port').value = result.port;
|
||||
document.getElementById('piccapPort').value = result.piccapPort || 19400;
|
||||
document.getElementById('captureInterval').value = result.captureIntervalMs || 50;
|
||||
receiver = result.receiver;
|
||||
document.getElementById('receiver').textContent = 'Receiver: ' + receiver;
|
||||
}
|
||||
@@ -298,7 +299,7 @@ document.getElementById('handover').onclick = function() {
|
||||
enabled: button.getAttribute('aria-checked') !== 'true'
|
||||
});
|
||||
};
|
||||
['host', 'port', 'piccapPort', 'token', 'clearToken'].forEach(function(id) {
|
||||
['host', 'port', 'piccapPort', 'captureInterval', 'token', 'clearToken'].forEach(function(id) {
|
||||
document.getElementById(id).addEventListener('input', function() {
|
||||
targetDirty = true;
|
||||
});
|
||||
@@ -320,7 +321,13 @@ document.getElementById('saveTarget').onclick = function() {
|
||||
showActionError('PicCap FlatBuffer port must be an integer from 1 to 65535.');
|
||||
return;
|
||||
}
|
||||
var interval = Number(document.getElementById('captureInterval').value);
|
||||
if (!Number.isInteger(interval) || interval < 25 || interval > 1000 || interval % 25) {
|
||||
showActionError('Capture interval must be 25–1000 ms in steps of 25.');
|
||||
return;
|
||||
}
|
||||
var settings = {
|
||||
captureIntervalMs: interval,
|
||||
piccapPort: flatPort,
|
||||
host: document.getElementById('host').value.trim(),
|
||||
port: port,
|
||||
@@ -393,6 +400,6 @@ document.getElementById('editProfile').onclick = function() {
|
||||
showProfile();
|
||||
if (advancedProfile) document.getElementById('customProfileField').focus();
|
||||
};
|
||||
setupRemoteNavigation(['overviewTab', 'receiverTab', 'calibrationTab', 'reloadService', 'grantRoot', 'start', 'stop', 'refresh', 'autostart', 'handover', 'calibrationShortcut', 'receiver', 'hostField', 'portField', 'piccapPortField', 'tokenField', 'clearToken', 'saveTarget', 'testConnection', 'calibrate', 'validateCalibration', 'profile', 'saveProfile', 'editProfile', 'customProfileField', 'cancelCalibration'].map(function(id) {
|
||||
setupRemoteNavigation(['overviewTab', 'receiverTab', 'calibrationTab', 'reloadService', 'grantRoot', 'start', 'stop', 'refresh', 'autostart', 'handover', 'calibrationShortcut', 'receiver', 'hostField', 'portField', 'piccapPortField', 'captureIntervalField', 'tokenField', 'clearToken', 'saveTarget', 'testConnection', 'calibrate', 'validateCalibration', 'profile', 'saveProfile', 'editProfile', 'customProfileField', 'cancelCalibration'].map(function(id) {
|
||||
return document.getElementById(id);
|
||||
}), document, window);
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"id": "org.webosbrew.histogramhyperion",
|
||||
"version": "0.13.3",
|
||||
"version": "0.13.4",
|
||||
"vendor": "Local development",
|
||||
"type": "web",
|
||||
"main": "index.html",
|
||||
|
||||
+2
-1
@@ -38,11 +38,12 @@ nav{border-bottom:1px solid #456185;padding-bottom:10px;margin-bottom:18px}nav b
|
||||
<div id="hostField" class="field" tabindex="0" role="button" aria-label="IP address, press OK to edit"><label>IP address<input id="host" tabindex="-1" readonly type="text" maxlength="45" value="127.0.0.1" spellcheck="false"></label></div>
|
||||
<div id="portField" class="field narrow" tabindex="0" role="button" aria-label="JSON port, press OK to edit"><label>JSON port<input id="port" tabindex="-1" readonly type="number" min="1" max="65535" value="19444"></label></div>
|
||||
<div id="piccapPortField" class="field narrow" tabindex="0" role="button" aria-label="FlatBuffer port, press OK to edit"><label>PicCap FlatBuffer port<input id="piccapPort" tabindex="-1" readonly type="number" min="1" max="65535" value="19400"></label></div>
|
||||
<div id="captureIntervalField" class="field narrow" tabindex="0" role="button" aria-label="Capture interval in milliseconds, press OK to edit"><label>Capture interval (ms)<input id="captureInterval" tabindex="-1" readonly type="number" min="25" max="1000" step="25" value="50"></label></div>
|
||||
<div id="tokenField" class="field" tabindex="0" role="button" aria-label="API token, press OK to edit"><label>API token (optional)<input id="token" tabindex="-1" readonly type="password" maxlength="256" autocomplete="off"></label></div>
|
||||
</div>
|
||||
<label><input id="clearToken" type="checkbox"> Remove saved token</label>
|
||||
<div><button id="saveTarget" class="primary">Save receiver</button><button id="testConnection">Test connection</button></div><p id="testResult" role="status">Connection test: Not tested</p>
|
||||
<p><small>The JSON TCP port is usually 19444. Leave the token blank to keep the saved token. Saving updates the current session.</small></p>
|
||||
<p><small>The JSON TCP port is usually 19444. Leave the token blank to keep the saved token. Saving updates the current session. Histogram capture defaults to 50 ms; choose 25–1000 ms in steps of 25. Lower values reduce latency and increase TV load. PicCap uses its own capture rate.</small></p>
|
||||
<p id="piccapTargetHint"><small>Auto PicCap uses the same IP and its FlatBuffer port, usually 19400. Enable the receiver's FlatBuffer server. Remote IPv6 uses HyperDRM.</small></p></fieldset>
|
||||
</section>
|
||||
<section id="calibrationPanel" aria-label="TV calibration" style="display:none">
|
||||
|
||||
+1
-1
@@ -64,4 +64,4 @@ function setupFormEditors(ids, doc, win) {
|
||||
}
|
||||
});
|
||||
}
|
||||
setupFormEditors(['host', 'port', 'piccapPort', 'token', 'customProfile'], document, window);
|
||||
setupFormEditors(['host', 'port', 'piccapPort', 'captureInterval', 'token', 'customProfile'], document, window);
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
'use strict';
|
||||
var HYPERDRM_APP_VERSION = '0.13.3';
|
||||
var HYPERDRM_APP_VERSION = '0.13.4';
|
||||
@@ -0,0 +1,3 @@
|
||||
# HyperDRM 0.13.4
|
||||
|
||||
Default histogram capture to 50 ms and expose a persisted Capture interval (ms) in the Receiver panel (25–1000, steps of 25). Remote highlighting leaves the keyboard closed; OK opens editing. A 25 ms scheduler keeps network processing responsive independently of slower capture settings. Missing legacy values default to 50 ms. PicCap timing is unchanged. Native tests check persistence, legacy defaults and invalid values.
|
||||
@@ -83,6 +83,8 @@ static int tick(void *unused) {
|
||||
capture_ctx.settling = 2;
|
||||
return 1;
|
||||
}
|
||||
if (capture_ctx.sample_at && now - capture_ctx.sample_at < target_capture_interval(&target))
|
||||
return 1;
|
||||
Histogram hist[16] = {0};
|
||||
int rc = vt_get(capture_ctx.handle, hist);
|
||||
bool populated = !rc && histogram_populated(hist);
|
||||
|
||||
+1
-1
@@ -270,7 +270,7 @@ int main(void) {
|
||||
service_handle = handle;
|
||||
if (!boot_enabled())
|
||||
retry_pause(&retries);
|
||||
timer(100, tick, NULL);
|
||||
timer(25, tick, NULL);
|
||||
while (!quitting) {
|
||||
iterate(NULL, 0);
|
||||
usleep(10000);
|
||||
|
||||
@@ -61,10 +61,10 @@ static bool status(void *h, void *m, void *ctx) {
|
||||
CALIBRATION_ACTIVE ? "true" : "false");
|
||||
used +=
|
||||
snprintf(json + used, sizeof(json) - used,
|
||||
"\"returnValue\":true,\"piccapPort\":%u,\"connectionTest\":{\"state\":\"%s\","
|
||||
"\"returnValue\":true,\"captureIntervalMs\":%u,\"piccapPort\":%u,\"connectionTest\":{\"state\":\"%s\","
|
||||
"\"ledCount\":%u,\"elapsedMs\":%llu,\"error\":\"%s\"},\"lastOutputAgeMs\":%"
|
||||
"lld,\"retryInMs\":%llu,\"captureState\":\"%s\",\"piccapError\":\"%s\",",
|
||||
target.piccap_port, test_stage_name(), connection_test.leds,
|
||||
target_capture_interval(&target), target.piccap_port, test_stage_name(), connection_test.leds,
|
||||
(unsigned long long)connection_test.elapsed, connection_test.error,
|
||||
capture_ctx.last_output_at ? (long long)(now - capture_ctx.last_output_at) : -1,
|
||||
(unsigned long long)(retries.due > now ? retries.due - now : 0),
|
||||
|
||||
+25
-4
@@ -15,10 +15,14 @@
|
||||
"/media/developer/apps/usr/palm/services/org.webosbrew.histogramhyperion.service/target.json"
|
||||
typedef struct {
|
||||
char host[46], receiver[16], token[257];
|
||||
unsigned port, piccap_port;
|
||||
unsigned port, piccap_port, capture_interval;
|
||||
} TargetConfig;
|
||||
static TargetConfig target = {
|
||||
.host = "127.0.0.1", .receiver = "Hyperion", .port = 19444, .piccap_port = 19400};
|
||||
.host = "127.0.0.1", .receiver = "Hyperion", .port = 19444, .piccap_port = 19400,
|
||||
.capture_interval = 50};
|
||||
static inline unsigned target_capture_interval(const TargetConfig *t) {
|
||||
return t->capture_interval ? t->capture_interval : 50;
|
||||
}
|
||||
static inline bool target_piccap_local(const TargetConfig *t) {
|
||||
return t->port == 19444 && (!strcmp(t->host, "127.0.0.1") || !strcmp(t->host, "::1"));
|
||||
}
|
||||
@@ -31,6 +35,9 @@ static inline const char *target_piccap_host(const TargetConfig *t) {
|
||||
}
|
||||
static bool target_valid(const TargetConfig *t) {
|
||||
unsigned char address[16];
|
||||
unsigned interval = target_capture_interval(t);
|
||||
if (interval < 25 || interval > 1000 || interval % 25)
|
||||
return false;
|
||||
if (inet_pton(AF_INET, t->host, address) != 1 && inet_pton(AF_INET6, t->host, address) != 1)
|
||||
return false;
|
||||
if (!t->piccap_port || t->piccap_port > 65535)
|
||||
@@ -97,6 +104,20 @@ static bool target_parse(PlaybackJson *j, const char *text, TargetConfig *out) {
|
||||
next.piccap_port = (unsigned)n;
|
||||
}
|
||||
}
|
||||
void *interval = NULL;
|
||||
if (j->type(root) == 4 && j->get(root, "captureIntervalMs", &interval)) {
|
||||
if (j->type(interval) != 3)
|
||||
valid = false;
|
||||
else {
|
||||
char *end = NULL;
|
||||
errno = 0;
|
||||
long n = strtol(j->string(interval), &end, 10);
|
||||
if (errno || !end || *end || n < 25 || n > 1000 || n % 25)
|
||||
valid = false;
|
||||
else
|
||||
next.capture_interval = (unsigned)n;
|
||||
}
|
||||
}
|
||||
void *clear = playback_field(j, root, "clearToken");
|
||||
if (clear) {
|
||||
if (j->type(clear) != 1)
|
||||
@@ -116,8 +137,8 @@ static bool target_save(const char *path, const TargetConfig *t) {
|
||||
char data[512];
|
||||
int n = snprintf(
|
||||
data, sizeof(data),
|
||||
"{\"host\":\"%s\",\"port\":%u,\"piccapPort\":%u,\"receiver\":\"%s\",\"token\":\"%s\"}\n",
|
||||
t->host, t->port, t->piccap_port, t->receiver, t->token);
|
||||
"{\"host\":\"%s\",\"port\":%u,\"piccapPort\":%u,\"receiver\":\"%s\",\"token\":\"%s\",\"captureIntervalMs\":%u}\n",
|
||||
t->host, t->port, t->piccap_port, t->receiver, t->token, target_capture_interval(t));
|
||||
return n > 0 && (size_t)n < sizeof(data) &&
|
||||
atomic_replace(path, data, (size_t)n) != SAVE_UNCHANGED;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ int main(void){
|
||||
assert(letterbox_candidate(thin_black,thin_color)==-1);
|
||||
memset(thin_color,100,sizeof(thin_color));
|
||||
assert(letterbox_candidate(thin_black,thin_color)!=1);
|
||||
memset(thin_black,15,sizeof(thin_black));
|
||||
for(unsigned i=0;i<16;i++)thin_black[i]=15;
|
||||
assert(letterbox_candidate(thin_black,thin_color)!=1);
|
||||
puts("PASS: measured wide/thin bars, full frame, one-sided/isolated edges, dark frame and low contrast");
|
||||
}
|
||||
@@ -24,8 +24,16 @@ int main(void){
|
||||
assert(!target_parse(&j,"{\"host\":\"127.0.0.1\",\"port\":19444,\"piccapPort\":null,\"receiver\":\"HyperHDR\"}",&t));
|
||||
TargetConfig v6=t;strcpy(v6.host,"2001:db8::1");assert(!target_piccap_compatible(&v6));strcpy(v6.host,"::1");assert(target_piccap_compatible(&v6) && !strcmp(target_piccap_host(&v6),"127.0.0.1"));
|
||||
const char *file="/tmp/hyperdrm-target-test.json";assert(access(file,F_OK)!=0);assert(access("/tmp/hyperdrm-target-test.json.tmp",F_OK)!=0);
|
||||
assert(target_capture_interval(&t)==50);
|
||||
assert(target_parse(&j,"{\"host\":\"::1\",\"port\":65535,\"receiver\":\"HyperHDR\",\"captureIntervalMs\":100}",&t));
|
||||
assert(t.capture_interval==100);
|
||||
const char *invalid_intervals[]={"0","24","26","1001","50.5","null","\"50\""};
|
||||
for(unsigned i=0;i<sizeof(invalid_intervals)/sizeof(*invalid_intervals);i++){
|
||||
char request[256];snprintf(request,sizeof(request),"{\"host\":\"::1\",\"port\":65535,\"receiver\":\"HyperHDR\",\"captureIntervalMs\":%s}",invalid_intervals[i]);
|
||||
TargetConfig before=t;assert(!target_parse(&j,request,&t));assert(!memcmp(&before,&t,sizeof(t)));
|
||||
}
|
||||
assert(target_save(file,&t));struct stat st;assert(stat(file,&st)==0 && (st.st_mode&0777)==0600);
|
||||
TargetConfig restored=target;assert(target_load(&j,file,&restored));assert(!strcmp(restored.host,"::1") && restored.port==65535 && !strcmp(restored.token,"test-token"));
|
||||
TargetConfig restored=target;assert(target_load(&j,file,&restored));assert(restored.capture_interval==100);assert(!strcmp(restored.host,"::1") && restored.port==65535 && !strcmp(restored.token,"test-token"));
|
||||
assert(target_parse(&j,"{\"host\":\"127.0.0.1\",\"port\":19444,\"receiver\":\"HyperHDR\",\"clearToken\":true}",&t));assert(!t.token[0]);assert(target_piccap_local(&t));unlink(file);
|
||||
int server=socket(AF_INET,SOCK_STREAM,0);assert(server>=0);struct sockaddr_in a={.sin_family=AF_INET,.sin_addr={htonl(INADDR_LOOPBACK)}};
|
||||
assert(bind(server,(void*)&a,sizeof(a))==0 && listen(server,1)==0);socklen_t length=sizeof(a);assert(getsockname(server,(void*)&a,&length)==0);t.port=ntohs(a.sin_port);
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#ifndef HYPERDRM_VERSION_H
|
||||
#define HYPERDRM_VERSION_H
|
||||
#define HYPERDRM_VERSION "0.13.3"
|
||||
#define HYPERDRM_VERSION "0.13.4"
|
||||
#endif
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"id": "org.webosbrew.histogramhyperion",
|
||||
"version": "0.13.3",
|
||||
"version": "0.13.4",
|
||||
"vendor": "Local development",
|
||||
"app": "org.webosbrew.histogramhyperion",
|
||||
"services": [
|
||||
|
||||
Binary file not shown.
@@ -7,7 +7,7 @@ function element(id,input){return {id,tagName:input?'INPUT':'DIV',type:'text',re
|
||||
focus(){if(doc.activeElement && doc.activeElement!==this)doc.activeElement.blur();doc.activeElement=this;if(input)inputFocuses++;},
|
||||
blur(){if(this.handlers.blur)this.handlers.blur();if(doc.activeElement===this)doc.activeElement=null;}
|
||||
};}
|
||||
['host','port','piccapPort','token','customProfile'].forEach(id=>{elements[id]=element(id,true);elements[id+'Field']=element(id+'Field',false);});
|
||||
['host','port','piccapPort','captureInterval','token','customProfile'].forEach(id=>{elements[id]=element(id,true);elements[id+'Field']=element(id+'Field',false);});
|
||||
['overview','receiver','calibration'].forEach(key=>{elements[key+'Panel']=element(key+'Panel');elements[key+'Tab']=element(key+'Tab');});
|
||||
['start','receiver','calibrate','calibrationShortcut'].forEach(id=>elements[id]=element(id));
|
||||
const win={addEventListener(k,f){windowEvents[k]=f;}};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
const assert=require('assert'),fs=require('fs'),vm=require('vm');
|
||||
const ids=['reloadService','versionWarning','host','port','piccapPort','receiver','token','clearToken','endpoint','handover','autostart','error','status','regions','start','stop','refresh','saveTarget','testConnection','testResult','diagnostics','profile','customEditor','customProfile','saveProfile','profileStatus','calibrate','validateCalibration','cancelCalibration','editProfile','customProfileField','overviewTab','receiverTab','calibrationTab','calibrationShortcut','hostField','portField','piccapPortField','tokenField','grantRoot','rootHint'];
|
||||
const ids=['reloadService','versionWarning','host','port','piccapPort','captureInterval','receiver','token','clearToken','endpoint','handover','autostart','error','status','regions','start','stop','refresh','saveTarget','testConnection','testResult','diagnostics','profile','customEditor','customProfile','saveProfile','profileStatus','calibrate','validateCalibration','cancelCalibration','editProfile','customProfileField','overviewTab','receiverTab','calibrationTab','calibrationShortcut','hostField','portField','piccapPortField','captureIntervalField','tokenField','grantRoot','rootHint'];
|
||||
const elements={};
|
||||
ids.forEach(id=>elements[id]={value:'',textContent:'',disabled:false,checked:false,attributes:{},style:{},addEventListener(){},focus(){},appendChild(){},setAttribute(k,v){this.attributes[k]=v;},getAttribute(k){return this.attributes[k];}});
|
||||
const requests=[];
|
||||
@@ -17,8 +17,8 @@ assert(elements.diagnostics.textContent.includes('Root service'));
|
||||
assert(controls.includes(elements.piccapPortField)&&controls.includes(elements.testConnection));
|
||||
context.targetDirty=true;elements.testConnection.onclick();assert(elements.error.textContent.includes('Save receiver'));
|
||||
elements.piccapPort.value='0';elements.saveTarget.onclick();assert(elements.error.textContent.includes('FlatBuffer port'));
|
||||
elements.piccapPort.value='19446';elements.host.value='10.1.0.100';elements.token.value='';elements.saveTarget.onclick();
|
||||
const saved=requests.find(r=>r.uri.endsWith('/settings'));assert.strictEqual(saved.data.piccapPort,19446);assert.strictEqual(saved.data.host,'10.1.0.100');assert(!('token' in saved.data));
|
||||
elements.piccapPort.value='19446';elements.captureInterval.value='26';elements.saveTarget.onclick();assert(elements.error.textContent.includes('Capture interval'));elements.captureInterval.value='100';elements.piccapPort.value='19446';elements.host.value='10.1.0.100';elements.token.value='';elements.saveTarget.onclick();
|
||||
const saved=requests.find(r=>r.uri.endsWith('/settings'));assert.strictEqual(saved.data.piccapPort,19446);assert.strictEqual(saved.data.captureIntervalMs,100);assert.strictEqual(saved.data.host,'10.1.0.100');assert(!('token' in saved.data));
|
||||
elements.testConnection.onclick();assert(requests.some(r=>r.uri.endsWith('/testConnection')));
|
||||
assert.strictEqual(elements.token.value,'');assert(!elements.diagnostics.textContent.includes('fixture-token'));
|
||||
elements.profile.onclick();assert(elements.profile.textContent.includes('LG G4'));elements.saveProfile.onclick();assert(requests.some(r=>r.uri.endsWith('/calibration')&&r.data.profile==='g4'));
|
||||
|
||||
Reference in new issue
Block a user