diff --git a/.gitignore b/.gitignore index 0b96f60..04675b4 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,8 @@ test-spatial_image test-continuity test-receiver_stream test-source_bridge + +test-atomic_file +test-json_protocol +test-receiver_output +test-service_lifecycle diff --git a/HyperDRM.manifest.json b/HyperDRM.manifest.json index c4480df..c7e58bb 100644 --- a/HyperDRM.manifest.json +++ b/HyperDRM.manifest.json @@ -1,15 +1,15 @@ { "id": "org.webosbrew.histogramhyperion", - "version": "0.12.0", + "version": "0.13.0", "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.12.0_arm.ipk", + "ipkUrl": "https://gitea.adnau.de/matze19999/HyperDRM/raw/branch/main/HyperDRM_0.13.0_arm.ipk", "ipkHash": { - "sha256": "b4fcc609d763b8e1a72d84484a5d454627416dde0ef51ef65ab705173e3c0810" + "sha256": "a7ab184db8c948ceb4657f093c2cf10c13df3565ff9ed4d8482dcf2d59896a0a" }, - "ipkSize": 1204332 + "ipkSize": 1207634 } diff --git a/HyperDRM_0.12.0_arm.ipk b/HyperDRM_0.13.0_arm.ipk similarity index 88% rename from HyperDRM_0.12.0_arm.ipk rename to HyperDRM_0.13.0_arm.ipk index 4c7cbc0..8c21f83 100644 Binary files a/HyperDRM_0.12.0_arm.ipk and b/HyperDRM_0.13.0_arm.ipk differ diff --git a/README.md b/README.md index 53e5fe1..527b125 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Only the own service is modified. Optional Auto PicCap can configure its receive ## Installation -The current package is [HyperDRM_0.12.0_arm.ipk](HyperDRM_0.12.0_arm.ipk). Install it using the TV's webOS/Homebrew package installation workflow. +The current package is [HyperDRM_0.13.0_arm.ipk](HyperDRM_0.13.0_arm.ipk). Install it using the TV's webOS/Homebrew package installation workflow. Elevate the installed own service through Homebrew Channel: @@ -209,3 +209,27 @@ An existing tag or a branch other than main is rejected. Attach the exact verifi IPK to the matching Gitea release using an authorized session, then compare the downloaded attachment's size and SHA-256. Git access does not authorize web uploads. See [runtime architecture](docs/architecture.md) for module boundaries. + +### Receiver resilience and updates + +The main receiver socket stays nonblocking during connect, authentication, layout +loading, color output and acknowledgements. Only one command is in flight and the +newest unsent frame replaces older unsent frames. Timed-out or invalid replies +close the connection and use the existing retry policy. Start returns a Connecting +state; Stop cancels startup and sends a bounded asynchronous clear when needed. +LG capture initialization itself remains dependent on the vendor library. + +Receiver, profile and PicCap preferences share an atomic writer with private +permissions, interrupted/partial-write handling, file sync, rename and directory +sync. Failures before rename preserve the previous preference. A directory-sync +failure after rename is reported as committed with uncertain crash durability; +it is not represented as a rollback. + +The UI compares its generated version against the running service. Mismatches +block settings and startup actions while keeping Stop and status available. After +installing the matching package, Reload service explicitly restarts only HyperDRM +and briefly interrupts ambient lighting; it never runs automatically. + +`python release.py --sanitizers` additionally checks protocol, atomic-file, +receiver-output and simulated service-lifecycle tests with ASan/UBSan. These host +tests do not replace real LG capture and remote-control validation on a TV. diff --git a/VERSION b/VERSION index ac454c6..54d1a4f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.12.0 +0.13.0 diff --git a/app/app.js b/app/app.js index 16353ea..4d6ee40 100644 --- a/app/app.js +++ b/app/app.js @@ -1,6 +1,26 @@ 'use strict'; var pending = []; var actionError = ''; +var versionCompatible = typeof HYPERDRM_APP_VERSION === 'undefined'; + +function checkServiceVersion(result) { + if (typeof HYPERDRM_APP_VERSION === 'undefined' || result.returnValue === false) return; + versionCompatible = result.version === HYPERDRM_APP_VERSION; + var warning = document.getElementById('versionWarning'); + warning.style.display = versionCompatible ? 'none' : 'block'; + document.getElementById('reloadService').style.display = versionCompatible ? 'none' : 'inline-block'; + warning.textContent = versionCompatible ? '' : 'App ' + HYPERDRM_APP_VERSION + ' / service ' + (result.version || 'unknown') + '. The installed service does not match this app. Finish the update, then select Reload service before changing settings. Reloading briefly stops ambient lighting.'; + ['start', 'autostart', 'handover', 'saveTarget', 'testConnection', 'calibrate', 'validateCalibration', 'saveProfile'].forEach(function(id) { + var button = document.getElementById(id); + if (!versionCompatible) { + button.versionBlocked = true; + button.disabled = true; + } else if (button.versionBlocked) { + button.versionBlocked = false; + button.disabled = id === 'handover' ? !result.piccapInstalled : id === 'testConnection' && result.connectionTest && ['Connecting', 'Sending request', 'Checking authentication', 'Reading LED layout'].indexOf(result.connectionTest.state) >= 0; + } + }); +} function showActionError(message) { actionError = message || ''; @@ -32,6 +52,13 @@ function showProfile() { } function call(method, done, data) { + if (!versionCompatible && ['start', 'autostart', 'handover', 'settings', 'testConnection', 'calibration', 'calibrationSession', 'calibrationDraft'].indexOf(method) >= 0 && !(method === 'calibrationSession' && data && data.enabled === false)) { + done({ + returnValue: false, + errorText: 'App and service versions do not match. Finish the update first.' + }); + return; + } if (['start', 'stop', 'autostart', 'handover', 'settings', 'testConnection', 'calibration'].indexOf(method) >= 0) clearActionError(); callUri('luna://org.webosbrew.histogramhyperion.service/' + method, function(result) { if (method !== 'status' && result.returnValue === false) showActionError(result.errorText || 'The action failed.'); @@ -117,10 +144,11 @@ function display(result) { toggle.textContent = 'Autostart: ' + (result.autostart ? 'On' : 'Off'); } document.getElementById('error').textContent = actionError || result.errorText || result.error || ''; - document.getElementById('status').textContent = result.returnValue === false ? 'Service unavailable' : (result.running ? 'Running' : 'Stopped') + ' · ' + (result.frames || 0) + ' acknowledged color updates'; + document.getElementById('status').textContent = result.returnValue === false ? 'Service unavailable' : (result.receiverConnecting ? 'Connecting to receiver' : result.running ? 'Running' : 'Stopped') + ' · ' + (result.frames || 0) + ' acknowledged color updates'; if (result.activeSource === 'PicCap') { document.getElementById('status').textContent = 'PicCap · ' + (result.running ? 'Running' : 'Switching capture source') + ' · ' + (result.piccapConnected ? 'Receiver connected' : 'Waiting for receiver'); document.getElementById('regions').textContent = ''; + checkServiceVersion(result); return; } if (result.piccapHandover) document.getElementById('status').textContent += ' · HyperDRM · ' + result.playbackProtection; @@ -143,7 +171,25 @@ function display(result) { e.title = 'Histogram ' + index; box.appendChild(e); }); + checkServiceVersion(result); } +document.getElementById('reloadService').onclick = function() { + var button = this; + button.disabled = true; + clearActionError(); + call('restartService', function(result) { + if (result.returnValue === false) { + button.disabled = false; + showActionError(result.errorText || 'Reinstall the current package, then restart its service.'); + return; + } + setTimeout(function() { + button.disabled = false; + call('status', display); + }, 1500); + }); +}; + document.getElementById('start').onclick = function() { call('start', function(r) { if (r.returnValue === false) display(r); @@ -347,6 +393,6 @@ document.getElementById('editProfile').onclick = function() { showProfile(); if (advancedProfile) document.getElementById('customProfileField').focus(); }; -setupRemoteNavigation(['overviewTab', 'receiverTab', 'calibrationTab', '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', 'tokenField', 'clearToken', 'saveTarget', 'testConnection', 'calibrate', 'validateCalibration', 'profile', 'saveProfile', 'editProfile', 'customProfileField', 'cancelCalibration'].map(function(id) { return document.getElementById(id); }), document, window); diff --git a/app/appinfo.json b/app/appinfo.json index da045a5..6cae3d6 100644 --- a/app/appinfo.json +++ b/app/appinfo.json @@ -1,6 +1,6 @@ { "id": "org.webosbrew.histogramhyperion", - "version": "0.12.0", + "version": "0.13.0", "vendor": "Local development", "type": "web", "main": "index.html", diff --git a/app/index.html b/app/index.html index 904a1fc..049d5b8 100644 --- a/app/index.html +++ b/app/index.html @@ -21,6 +21,7 @@ nav{border-bottom:1px solid #456185;padding-bottom:10px;margin-bottom:18px}nav b
HyperDRMChecking service …
+