diff --git a/Awtrix App/module.new.php b/Awtrix App/module.new.php new file mode 100644 index 0000000..34fd8ce --- /dev/null +++ b/Awtrix App/module.new.php @@ -0,0 +1,545 @@ +RegisterAttributeString('LastPayload', ''); + $this->RegisterAttributeString('LastAppName', ''); + + $this->RegisterPropertyString('AwtrixIp', '172.78.88.67'); + $this->RegisterPropertyString('AppName', ''); + $this->RegisterPropertyString('Prefix', ''); + $this->RegisterPropertyString('Suffix', ''); + + $this->RegisterVariables(); + + if ($this->ReadAttributeString('LastPayload') === '') { + $this->WriteAttributeString('LastPayload', $this->NormalizeForCompare($this->BuildAppPayload())); + } + + if ($this->ReadAttributeString('LastAppName') === '') { + $this->WriteAttributeString('LastAppName', $this->GetAppName()); + } + } + + public function ApplyChanges() + { + parent::ApplyChanges(); + + $payload = $this->BuildAppPayload(); + $payloadChanged = $this->NormalizeForCompare($payload) !== $this->ReadAttributeString('LastPayload'); + $appNameChanged = $this->GetAppName() !== $this->ReadAttributeString('LastAppName'); + + if (!$payloadChanged && !$appNameChanged) { + $this->SetStatus(102); + return; + } + + $this->SendApp(); + } + + public function RequestAction($Ident, $Value) + { + SetValue($this->GetIDForIdent($Ident), $Value); + $this->SendApp(); + } + + public function SendApp() + { + $appName = $this->GetAppName(); + $payload = $this->BuildAppPayload(); + $previousAppName = $this->ReadAttributeString('LastAppName'); + + if ($previousAppName !== '' && $previousAppName !== $appName) { + $this->DeleteAppByName($previousAppName); + } + + if (!$this->SendJsonRequest('/api/custom?name=' . rawurlencode($appName), $payload)) { + return false; + } + + $this->WriteAttributeString('LastPayload', $this->NormalizeForCompare($payload)); + $this->WriteAttributeString('LastAppName', $appName); + return true; + } + + public function DeleteApp() + { + $deleted = $this->DeleteAppByName($this->GetAppName()); + if ($deleted) { + $this->WriteAttributeString('LastPayload', ''); + $this->WriteAttributeString('LastAppName', ''); + } + + return $deleted; + } + + private function RegisterVariables() + { + $p = 0; + + $this->RegisterStringVariableWithDefault('text', 'Text', $this->StringInputPresentation(), $p += 10, ''); + $this->RegisterIntegerVariableWithDefault('textCase', 'Text Case', [ + 'PRESENTATION' => VARIABLE_PRESENTATION_SLIDER, + 'MIN' => 0, + 'MAX' => 2, + 'STEP_SIZE' => 1, + 'USAGE_TYPE' => 5 + ], $p += 10, 0); + $this->RegisterBooleanVariableWithDefault('topText', 'Top Text', [ + 'PRESENTATION' => VARIABLE_PRESENTATION_SWITCH + ], $p += 10, false); + $this->RegisterIntegerVariableWithDefault('textOffset', 'Text Offset', [ + 'PRESENTATION' => VARIABLE_PRESENTATION_SLIDER, + 'MIN' => 0, + 'MAX' => 32, + 'STEP_SIZE' => 1, + 'USAGE_TYPE' => 5 + ], $p += 10, 0); + $this->RegisterBooleanVariableWithDefault('center', 'Center Short Text', [ + 'PRESENTATION' => VARIABLE_PRESENTATION_SWITCH + ], $p += 10, true); + $this->RegisterIntegerVariableWithDefault('color', 'Text/Chart Color', [ + 'PRESENTATION' => VARIABLE_PRESENTATION_COLOR, + 'ENCODING' => 0, + 'COLOR_SPACE' => 0, + 'COLOR_CURVE' => 0 + ], $p += 10, 16777215); + $this->RegisterBooleanVariableWithDefault('gradientEnabled', 'Gradient Enabled', [ + 'PRESENTATION' => VARIABLE_PRESENTATION_SWITCH + ], $p += 10, false); + $this->RegisterIntegerVariableWithDefault('gradient01', 'Gradient Color 1', [ + 'PRESENTATION' => VARIABLE_PRESENTATION_COLOR, + 'ENCODING' => 0, + 'COLOR_SPACE' => 0, + 'COLOR_CURVE' => 0 + ], $p += 10, 16777215); + $this->RegisterIntegerVariableWithDefault('gradient02', 'Gradient Color 2', [ + 'PRESENTATION' => VARIABLE_PRESENTATION_COLOR, + 'ENCODING' => 0, + 'COLOR_SPACE' => 0, + 'COLOR_CURVE' => 0 + ], $p += 10, 16777215); + $this->RegisterIntegerVariableWithDefault('blinkText', 'Blink Text (ms)', [ + 'PRESENTATION' => VARIABLE_PRESENTATION_SLIDER, + 'MIN' => 0, + 'MAX' => 10000, + 'STEP_SIZE' => 100, + 'USAGE_TYPE' => 5, + 'SUFFIX' => ' ms' + ], $p += 10, 0); + $this->RegisterIntegerVariableWithDefault('fadeText', 'Fade Text (ms)', [ + 'PRESENTATION' => VARIABLE_PRESENTATION_SLIDER, + 'MIN' => 0, + 'MAX' => 10000, + 'STEP_SIZE' => 100, + 'USAGE_TYPE' => 5, + 'SUFFIX' => ' ms' + ], $p += 10, 0); + $this->RegisterIntegerVariableWithDefault('background', 'Background Color', [ + 'PRESENTATION' => VARIABLE_PRESENTATION_COLOR, + 'ENCODING' => 0, + 'COLOR_SPACE' => 0, + 'COLOR_CURVE' => 0 + ], $p += 10, 0); + $this->RegisterBooleanVariableWithDefault('rainbow', 'Rainbow Text', [ + 'PRESENTATION' => VARIABLE_PRESENTATION_SWITCH + ], $p += 10, false); + $this->RegisterStringVariableWithDefault('icon', 'Icon (ID/Filename/Base64)', $this->StringInputPresentation(), $p += 10, ''); + $this->RegisterIntegerVariableWithDefault('pushIcon', 'Push Icon', [ + 'PRESENTATION' => VARIABLE_PRESENTATION_SLIDER, + 'MIN' => 0, + 'MAX' => 2, + 'STEP_SIZE' => 1, + 'USAGE_TYPE' => 5 + ], $p += 10, 0); + $this->RegisterIntegerVariableWithDefault('repeat', 'Repeat (-1 Forever)', $this->NumericInputPresentation(), $p += 10, -1); + $this->RegisterIntegerVariableWithDefault('duration', 'Duration (s)', [ + 'PRESENTATION' => VARIABLE_PRESENTATION_SLIDER, + 'MIN' => 1, + 'MAX' => 300, + 'STEP_SIZE' => 1, + 'USAGE_TYPE' => 5, + 'SUFFIX' => ' s' + ], $p += 10, 5); + $this->RegisterStringVariableWithDefault('bar', 'Bar Values (JSON/CSV)', $this->StringInputPresentation(), $p += 10, ''); + $this->RegisterStringVariableWithDefault('line', 'Line Values (JSON/CSV)', $this->StringInputPresentation(), $p += 10, ''); + $this->RegisterBooleanVariableWithDefault('autoscale', 'Autoscale Bar/Line', [ + 'PRESENTATION' => VARIABLE_PRESENTATION_SWITCH + ], $p += 10, true); + $this->RegisterIntegerVariableWithDefault('barBC', 'Bar Background Color', [ + 'PRESENTATION' => VARIABLE_PRESENTATION_COLOR, + 'ENCODING' => 0, + 'COLOR_SPACE' => 0, + 'COLOR_CURVE' => 0 + ], $p += 10, 0); + $this->RegisterIntegerVariableWithDefault('progress', 'Progress (-1 Off, 0-100)', [ + 'PRESENTATION' => VARIABLE_PRESENTATION_SLIDER, + 'MIN' => -1, + 'MAX' => 100, + 'STEP_SIZE' => 1, + 'USAGE_TYPE' => 5 + ], $p += 10, -1); + $this->RegisterIntegerVariableWithDefault('progressC', 'Progress Color', [ + 'PRESENTATION' => VARIABLE_PRESENTATION_COLOR, + 'ENCODING' => 0, + 'COLOR_SPACE' => 0, + 'COLOR_CURVE' => 0 + ], $p += 10, 16777215); + $this->RegisterIntegerVariableWithDefault('progressBC', 'Progress Background Color', [ + 'PRESENTATION' => VARIABLE_PRESENTATION_COLOR, + 'ENCODING' => 0, + 'COLOR_SPACE' => 0, + 'COLOR_CURVE' => 0 + ], $p += 10, 0); + $this->RegisterIntegerVariableWithDefault('pos', 'Loop Position', $this->NumericInputPresentation(), $p += 10, 0); + $this->RegisterStringVariableWithDefault('draw', 'Draw Instructions (JSON)', $this->StringInputPresentation(), $p += 10, ''); + $this->RegisterIntegerVariableWithDefault('lifetime', 'Lifetime (s)', [ + 'PRESENTATION' => VARIABLE_PRESENTATION_SLIDER, + 'MIN' => 0, + 'MAX' => 86400, + 'STEP_SIZE' => 10, + 'USAGE_TYPE' => 5, + 'SUFFIX' => ' s' + ], $p += 10, 0); + $this->RegisterIntegerVariableWithDefault('lifetimeMode', 'Lifetime Mode', [ + 'PRESENTATION' => VARIABLE_PRESENTATION_SLIDER, + 'MIN' => 0, + 'MAX' => 1, + 'STEP_SIZE' => 1, + 'USAGE_TYPE' => 5 + ], $p += 10, 0); + $this->RegisterBooleanVariableWithDefault('noScroll', 'Disable Scrolling', [ + 'PRESENTATION' => VARIABLE_PRESENTATION_SWITCH + ], $p += 10, false); + $this->RegisterIntegerVariableWithDefault('scrollSpeed', 'Scroll Speed (%)', [ + 'PRESENTATION' => VARIABLE_PRESENTATION_SLIDER, + 'MIN' => 10, + 'MAX' => 200, + 'STEP_SIZE' => 5, + 'USAGE_TYPE' => 5, + 'SUFFIX' => ' %' + ], $p += 10, 100); + $this->RegisterStringVariableWithDefault('effect', 'Background Effect', $this->StringInputPresentation(), $p += 10, ''); + $this->RegisterStringVariableWithDefault('effectSettings', 'Effect Settings (JSON Map)', $this->StringInputPresentation(), $p += 10, ''); + $this->RegisterBooleanVariableWithDefault('save', 'Save App To Flash', [ + 'PRESENTATION' => VARIABLE_PRESENTATION_SWITCH + ], $p += 10, false); + $this->RegisterStringVariableWithDefault('overlay', 'Overlay', $this->StringInputPresentation(), $p += 10, 'clear'); + $this->RegisterVariableString('LastPayloadPreview', 'Last Payload', $this->StringInputPresentation(), $p += 10); + } + + private function RegisterIntegerVariableWithDefault($ident, $name, array $presentation, $position, $defaultValue) + { + $exists = $this->HasIdent($ident); + $this->RegisterVariableInteger($ident, $name, $presentation, $position); + $this->EnableAction($ident); + if (!$exists) { + SetValueInteger($this->GetIDForIdent($ident), $defaultValue); + } + } + + private function RegisterBooleanVariableWithDefault($ident, $name, array $presentation, $position, $defaultValue) + { + $exists = $this->HasIdent($ident); + $this->RegisterVariableBoolean($ident, $name, $presentation, $position); + $this->EnableAction($ident); + if (!$exists) { + SetValueBoolean($this->GetIDForIdent($ident), $defaultValue); + } + } + + private function RegisterStringVariableWithDefault($ident, $name, array $presentation, $position, $defaultValue) + { + $exists = $this->HasIdent($ident); + $this->RegisterVariableString($ident, $name, $presentation, $position); + $this->EnableAction($ident); + if (!$exists) { + SetValueString($this->GetIDForIdent($ident), $defaultValue); + } + } + + private function HasIdent($ident) + { + return (@$this->GetIDForIdent($ident) > 0); + } + + private function BuildAppPayload() + { + $payload = [ + 'textCase' => GetValueInteger($this->GetIDForIdent('textCase')), + 'topText' => GetValueBoolean($this->GetIDForIdent('topText')), + 'textOffset' => GetValueInteger($this->GetIDForIdent('textOffset')), + 'center' => GetValueBoolean($this->GetIDForIdent('center')), + 'color' => $this->ColorToApi(GetValueInteger($this->GetIDForIdent('color'))), + 'blinkText' => GetValueInteger($this->GetIDForIdent('blinkText')), + 'fadeText' => GetValueInteger($this->GetIDForIdent('fadeText')), + 'background' => $this->ColorToApi(GetValueInteger($this->GetIDForIdent('background'))), + 'rainbow' => GetValueBoolean($this->GetIDForIdent('rainbow')), + 'pushIcon' => GetValueInteger($this->GetIDForIdent('pushIcon')), + 'repeat' => GetValueInteger($this->GetIDForIdent('repeat')), + 'duration' => GetValueInteger($this->GetIDForIdent('duration')), + 'autoscale' => GetValueBoolean($this->GetIDForIdent('autoscale')), + 'noScroll' => GetValueBoolean($this->GetIDForIdent('noScroll')), + 'scrollSpeed' => GetValueInteger($this->GetIDForIdent('scrollSpeed')) + ]; + + $text = $this->ReadPropertyString('Prefix') . GetValueString($this->GetIDForIdent('text')) . $this->ReadPropertyString('Suffix'); + if ($text !== '') { + $payload['text'] = $text; + } + + $icon = trim(GetValueString($this->GetIDForIdent('icon'))); + if ($icon !== '') { + $payload['icon'] = $icon; + } + + if (GetValueBoolean($this->GetIDForIdent('gradientEnabled'))) { + $payload['gradient'] = [ + $this->ColorToApi(GetValueInteger($this->GetIDForIdent('gradient01'))), + $this->ColorToApi(GetValueInteger($this->GetIDForIdent('gradient02'))) + ]; + } + + $bar = $this->ParseIntListVariable('bar'); + if ($bar !== null) { + $payload['bar'] = $bar; + $payload['barBC'] = $this->ColorToApi(GetValueInteger($this->GetIDForIdent('barBC'))); + } + + $line = $this->ParseIntListVariable('line'); + if ($line !== null) { + $payload['line'] = $line; + } + + $progress = GetValueInteger($this->GetIDForIdent('progress')); + if ($progress >= 0) { + $payload['progress'] = $progress; + $payload['progressC'] = $this->ColorToApi(GetValueInteger($this->GetIDForIdent('progressC'))); + $payload['progressBC'] = $this->ColorToApi(GetValueInteger($this->GetIDForIdent('progressBC'))); + } + + $draw = $this->ParseJsonVariable('draw', true); + if ($draw !== null) { + $payload['draw'] = $draw; + } + + $pos = GetValueInteger($this->GetIDForIdent('pos')); + if ($pos >= 0) { + $payload['pos'] = $pos; + } + + $lifetime = GetValueInteger($this->GetIDForIdent('lifetime')); + if ($lifetime > 0) { + $payload['lifetime'] = $lifetime; + $payload['lifetimeMode'] = GetValueInteger($this->GetIDForIdent('lifetimeMode')); + } + + $effect = trim(GetValueString($this->GetIDForIdent('effect'))); + if ($effect !== '') { + $payload['effect'] = $effect; + } + + $effectSettings = $this->ParseJsonVariable('effectSettings', false); + if ($effectSettings !== null) { + $payload['effectSettings'] = $effectSettings; + } + + if (GetValueBoolean($this->GetIDForIdent('save'))) { + $payload['save'] = true; + } + + $overlay = trim(GetValueString($this->GetIDForIdent('overlay'))); + if ($overlay !== '') { + $payload['overlay'] = $overlay; + } + + return $payload; + } + + private function ParseIntListVariable($ident) + { + $value = trim(GetValueString($this->GetIDForIdent($ident))); + if ($value === '') { + return null; + } + + $json = json_decode($value, true); + if (is_array($json)) { + return array_map('intval', $json); + } + + $parts = preg_split('/[\s,;]+/', $value, -1, PREG_SPLIT_NO_EMPTY); + if (!is_array($parts) || count($parts) === 0) { + return null; + } + + return array_map('intval', $parts); + } + + private function ParseJsonVariable($ident, $expectList) + { + $value = trim(GetValueString($this->GetIDForIdent($ident))); + if ($value === '') { + return null; + } + + $decoded = json_decode($value, true); + if (json_last_error() !== JSON_ERROR_NONE || !is_array($decoded)) { + IPS_LogMessage('AWTRIX', 'Invalid JSON in ' . $ident . ': ' . json_last_error_msg()); + return null; + } + + if ($expectList && array_keys($decoded) !== range(0, count($decoded) - 1)) { + IPS_LogMessage('AWTRIX', 'Expected JSON array in ' . $ident); + return null; + } + + if (!$expectList && array_keys($decoded) === range(0, count($decoded) - 1)) { + IPS_LogMessage('AWTRIX', 'Expected JSON object/map in ' . $ident); + return null; + } + + return $decoded; + } + + private function GetAppName() + { + $appName = trim($this->ReadPropertyString('AppName')); + if ($appName === '') { + $appName = IPS_GetName($this->InstanceID); + } + + $appName = preg_replace('/\s+/', '_', $appName); + $appName = preg_replace('/[^A-Za-z0-9_-]/', '', $appName); + + if ($appName === '' || $appName === null) { + $appName = 'app_' . $this->InstanceID; + } + + return $appName; + } + + private function DeleteAppByName($appName) + { + if ($appName === '') { + return true; + } + + return $this->SendRawPost('/api/custom?name=' . rawurlencode($appName), ''); + } + + private function SendJsonRequest($path, array $payload) + { + $response = $this->RequestAwtrix('POST', $path, json_encode($payload), ['Content-Type: application/json']); + if ($response['success']) { + SetValueString($this->GetIDForIdent('LastPayloadPreview'), json_encode($payload)); + } + + return $response['success']; + } + + private function SendRawPost($path, $body) + { + return $this->RequestAwtrix('POST', $path, $body, [])['success']; + } + + private function RequestAwtrix($method, $path, $body = null, array $headers = []) + { + $awtrixIp = trim($this->ReadPropertyString('AwtrixIp')); + $url = 'http://' . $awtrixIp . $path; + + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, 2000); + curl_setopt($ch, CURLOPT_TIMEOUT_MS, 5000); + + if ($method === 'POST') { + curl_setopt($ch, CURLOPT_POST, true); + if (count($headers) > 0) { + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + } + if ($body !== null) { + curl_setopt($ch, CURLOPT_POSTFIELDS, $body); + } + } else { + curl_setopt($ch, CURLOPT_HTTPGET, true); + } + + $response = curl_exec($ch); + $error = curl_error($ch); + $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($response === false || $error !== '') { + $this->SetStatus(104); + IPS_LogMessage('AWTRIX', 'CURL error on ' . $path . ': ' . $error); + return ['success' => false, 'raw' => $response]; + } + + if ($httpCode < 200 || $httpCode >= 300) { + $this->SetStatus(104); + IPS_LogMessage('AWTRIX', 'HTTP ' . $httpCode . ' on ' . $path . ': ' . $response); + return ['success' => false, 'raw' => $response]; + } + + $this->SetStatus(102); + return ['success' => true, 'raw' => $response]; + } + + private function ColorToApi($value) + { + $value = max(0, min((int) $value, 16777215)); + return '#' . strtoupper(str_pad(dechex($value), 6, '0', STR_PAD_LEFT)); + } + + private function NormalizeForCompare(array $value) + { + return json_encode($this->NormalizeValue($value)); + } + + private function NormalizeValue($value) + { + if (!is_array($value)) { + return $value; + } + + if (array_keys($value) === range(0, count($value) - 1)) { + return array_map([$this, 'NormalizeValue'], $value); + } + + ksort($value); + foreach ($value as $key => $item) { + $value[$key] = $this->NormalizeValue($item); + } + + return $value; + } + + private function StringInputPresentation() + { + if (defined('VARIABLE_PRESENTATION_INPUT')) { + return ['PRESENTATION' => VARIABLE_PRESENTATION_INPUT]; + } + + return []; + } + + private function NumericInputPresentation() + { + if (defined('VARIABLE_PRESENTATION_VALUE_INPUT')) { + return ['PRESENTATION' => VARIABLE_PRESENTATION_VALUE_INPUT]; + } + + if (defined('VARIABLE_PRESENTATION_INPUT')) { + return ['PRESENTATION' => VARIABLE_PRESENTATION_INPUT]; + } + + return []; + } +} + +?> diff --git a/Awtrix Settings/form.json b/Awtrix Settings/form.json index fbf59a7..51f6850 100644 --- a/Awtrix Settings/form.json +++ b/Awtrix Settings/form.json @@ -7,6 +7,7 @@ { "type": "CheckBox", "name": "HUM", "caption": "Enable Humidity App" }, { "type": "CheckBox", "name": "TEMP", "caption": "Enable Temp App" }, { "type": "CheckBox", "name": "BAT", "caption": "Enable Battery App" }, + { "type": "Label", "caption": "Changes to app enablement are sent on Apply and trigger an automatic reboot after a successful update." }, { "type": "Select", "name": "TFORMAT", "caption": "Time-Format", "options": [ { "caption": "13:30:45", "value": "%H:%M:%S" }, @@ -35,7 +36,8 @@ { "type": "CheckBox", "name": "SOM", "caption": "Start the week on Monday" }, { "type": "CheckBox", "name": "CEL", "caption": "Shows the temperature in Celsius (Fahrenheit when false)." }, { "type": "SelectColor", "name": "CCORRECTION", "caption": "Color Correction","allowTransparent": false }, - { "type": "SelectColor", "name": "CTEMP", "caption": "Color Temperatur","allowTransparent": false } + { "type": "SelectColor", "name": "CTEMP", "caption": "Color Temperatur","allowTransparent": false }, + { "type": "Label", "caption": "Use Apply to send changed configuration values. 'Send Settings' resends the currently saved instance configuration." } ], "actions": [ @@ -46,4 +48,4 @@ [ ] -} \ No newline at end of file +} diff --git a/Awtrix Settings/module.php b/Awtrix Settings/module.php index 7d53c3b..ce7bae5 100644 --- a/Awtrix Settings/module.php +++ b/Awtrix Settings/module.php @@ -1,560 +1,661 @@ -RegisterAttributeString('Settings',''); - $this->RegisterAttributeInteger('State',0); + $this->RegisterAttributeString('Settings', ''); + $this->RegisterAttributeString('LastSettings', ''); + $this->RegisterAttributeInteger('SyncInProgress', 0); + $this->RegisterAttributeInteger('State', 0); - $this->RegisterPropertyString('AwtrixIp','172.78.88.67'); - $this->RegisterPropertyBoolean('TIM',true); - $this->RegisterPropertyBoolean('DAT',true); - $this->RegisterPropertyBoolean('HUM',false); - $this->RegisterPropertyBoolean('TEMP',false); - $this->RegisterPropertyBoolean('BAT',false); + $this->RegisterPropertyString('AwtrixIp', '172.78.88.67'); + $this->RegisterPropertyBoolean('TIM', true); + $this->RegisterPropertyBoolean('DAT', true); + $this->RegisterPropertyBoolean('HUM', false); + $this->RegisterPropertyBoolean('TEMP', false); + $this->RegisterPropertyBoolean('BAT', false); $this->RegisterPropertyString('TFORMAT', '%H:%M:%S'); $this->RegisterPropertyString('DFORMAT', '%d.%m'); - $this->RegisterPropertyBoolean('SOM',true); - $this->RegisterPropertyBoolean('CEL',true); - $this->RegisterPropertyInteger('CCORRECTION',16777215); - $this->RegisterPropertyInteger('CTEMP',16777215); + $this->RegisterPropertyBoolean('SOM', true); + $this->RegisterPropertyBoolean('CEL', true); + $this->RegisterPropertyInteger('CCORRECTION', 16777215); + $this->RegisterPropertyInteger('CTEMP', 16777215); - - - $this->RegisterVariableInteger ('ATIME', 'App duration', [ + $this->RegisterIntegerVariableWithDefault('ATIME', 'App duration', [ 'PRESENTATION' => VARIABLE_PRESENTATION_SLIDER, 'MIN' => 0, 'MAX' => 60, 'STEP_SIZE' => 1, - 'USAGE_TYPE' => 5, ///5 = none - 'SUFFIX' => ' sec', - ], 0); - $this->EnableAction("ATIME"); - SetValueInteger($this->GetIDForIdent("ATIME"),7); + 'USAGE_TYPE' => 5, + 'SUFFIX' => ' sec' + ], 0, 7); - $this->RegisterVariableInteger ('TEFF', 'App Transisioneffect', [ + $this->RegisterIntegerVariableWithDefault('TEFF', 'App Transisioneffect', [ 'PRESENTATION' => VARIABLE_PRESENTATION_SLIDER, 'MIN' => 0, 'MAX' => 10, 'STEP_SIZE' => 1, - 'USAGE_TYPE' => 5, ///5 = none + 'USAGE_TYPE' => 5, 'SUFFIX' => '' - ], 10); - $this->EnableAction("TEFF"); - SetValueInteger($this->GetIDForIdent("TEFF"),1); + ], 10, 1); - $this->RegisterVariableInteger ('TSPEED', 'Transisiontime', [ + $this->RegisterIntegerVariableWithDefault('TSPEED', 'Transisiontime', [ 'PRESENTATION' => VARIABLE_PRESENTATION_SLIDER, 'MIN' => 0, 'MAX' => 2000, 'STEP_SIZE' => 50, - 'USAGE_TYPE' => 5, ///5 = none + 'USAGE_TYPE' => 5, 'SUFFIX' => ' ms' - ], 20); - $this->EnableAction("TSPEED"); - SetValueInteger($this->GetIDForIdent("TSPEED"),500); + ], 20, 500); - $this->RegisterVariableInteger ('TCOL', 'Global Text Color',[ + $this->RegisterIntegerVariableWithDefault('TCOL', 'Global Text Color', [ 'PRESENTATION' => VARIABLE_PRESENTATION_COLOR, 'ENCODING' => 0, 'COLOR_SPACE' => 0, 'COLOR_CURVE' => 0 - ], 30); - $this->EnableAction("TCOL"); - SetValueInteger($this->GetIDForIdent("TCOL"),16777215); ///is white...Frank White + ], 30, 16777215); - $this->RegisterVariableInteger ('TMODE', 'Time App Style',[ + $this->RegisterIntegerVariableWithDefault('TMODE', 'Time App Style', [ 'PRESENTATION' => VARIABLE_PRESENTATION_SLIDER, 'MIN' => 0, 'MAX' => 6, 'STEP_SIZE' => 1, - 'USAGE_TYPE' => 5, ///5 = none + 'USAGE_TYPE' => 5, 'SUFFIX' => '' - ], 40); - $this->EnableAction("TMODE"); - SetValueInteger($this->GetIDForIdent("TMODE"),1); + ], 40, 1); - $this->RegisterVariableInteger ('CHCOL', 'Calendar Header Color', [ + $this->RegisterIntegerVariableWithDefault('CHCOL', 'Calendar Header Color', [ 'PRESENTATION' => VARIABLE_PRESENTATION_COLOR, 'ENCODING' => 0, 'COLOR_SPACE' => 0, 'COLOR_CURVE' => 0 - ], 50); - $this->EnableAction("CHCOL"); - SetValueInteger($this->GetIDForIdent("CHCOL"),16711680); //Red like Reddington + ], 50, 16711680); - - $this->RegisterVariableInteger ('CBCOL', 'Calendar Body Color', [ + $this->RegisterIntegerVariableWithDefault('CBCOL', 'Calendar Body Color', [ 'PRESENTATION' => VARIABLE_PRESENTATION_COLOR, 'ENCODING' => 0, 'COLOR_SPACE' => 0, 'COLOR_CURVE' => 0 - ], 60); - $this->EnableAction("CBCOL"); - SetValueInteger($this->GetIDForIdent("CBCOL"),16777215); //white + ], 60, 16777215); - - $this->RegisterVariableInteger ('CTCOL', 'Calendar Text Color', [ + $this->RegisterIntegerVariableWithDefault('CTCOL', 'Calendar Text Color', [ 'PRESENTATION' => VARIABLE_PRESENTATION_COLOR, 'ENCODING' => 0, 'COLOR_SPACE' => 0, 'COLOR_CURVE' => 0 - ], 70); - $this->EnableAction("CTCOL"); - SetValueInteger($this->GetIDForIdent("CTCOL"),0); //i see black for you + ], 70, 0); - $this->RegisterVariableBoolean ('WD', 'show Weekday', [ + $this->RegisterBooleanVariableWithDefault('WD', 'show Weekday', [ 'PRESENTATION' => VARIABLE_PRESENTATION_SWITCH - ], 80); - $this->EnableAction("WD"); - SetValueBoolean($this->GetIDForIdent("WD"),0); + ], 80, false); - $this->RegisterVariableInteger ('WDCA', 'Active Weekday Color', [ + $this->RegisterIntegerVariableWithDefault('WDCA', 'Active Weekday Color', [ 'PRESENTATION' => VARIABLE_PRESENTATION_COLOR, 'ENCODING' => 0, 'COLOR_SPACE' => 0, 'COLOR_CURVE' => 0 - ], 90); - $this->EnableAction("WDCA"); - SetValueInteger($this->GetIDForIdent("WDCA"),65535); + ], 90, 65535); - $this->RegisterVariableInteger ('WDCI', 'Inactive Weekday Color', [ + $this->RegisterIntegerVariableWithDefault('WDCI', 'Inactive Weekday Color', [ 'PRESENTATION' => VARIABLE_PRESENTATION_COLOR, 'ENCODING' => 0, 'COLOR_SPACE' => 0, 'COLOR_CURVE' => 0 - ], 100); - $this->EnableAction("WDCI"); - SetValueInteger($this->GetIDForIdent("WDCI"),35071); + ], 100, 35071); - $this->RegisterVariableInteger ('BRI', 'Brightness', [ + $this->RegisterIntegerVariableWithDefault('BRI', 'Brightness', [ 'PRESENTATION' => VARIABLE_PRESENTATION_SLIDER, 'MIN' => 0, 'MAX' => 255, 'PERCENTAGE' => true, - 'USAGE_TYPE' => 2, ///5 = none + 'USAGE_TYPE' => 2, 'SUFFIX' => ' %' - ], 110); - $this->EnableAction("BRI"); - SetValueInteger($this->GetIDForIdent("BRI"),128); + ], 110, 128); - $this->RegisterVariableBoolean ('ABRI', 'Automatic Brightness', [ + $this->RegisterBooleanVariableWithDefault('ABRI', 'Automatic Brightness', [ 'PRESENTATION' => VARIABLE_PRESENTATION_SWITCH - ], 120); - $this->EnableAction("ABRI"); - SetValueBoolean($this->GetIDForIdent("ABRI"),0); + ], 120, false); - $this->RegisterVariableBoolean ('ATRANS', 'Automatic Transsions to next App', [ + $this->RegisterBooleanVariableWithDefault('ATRANS', 'Automatic Transsions to next App', [ 'PRESENTATION' => VARIABLE_PRESENTATION_SWITCH - ], 130); - $this->EnableAction("ATRANS"); - SetValueBoolean($this->GetIDForIdent("ATRANS"),1); + ], 130, true); - $this->RegisterVariableBoolean('BLOCKN', 'Local Key Enable', [ + $this->RegisterBooleanVariableWithDefault('BLOCKN', 'Local Key Enable', [ 'PRESENTATION' => VARIABLE_PRESENTATION_SWITCH - ], 160); - $this->EnableAction("BLOCKN"); - SetValueBoolean($this->GetIDForIdent("BLOCKN"),1); + ], 160, true); - $this->RegisterVariableBoolean('UPPERCASE', 'Display Uppercase', [ + $this->RegisterBooleanVariableWithDefault('UPPERCASE', 'Display Uppercase', [ 'PRESENTATION' => VARIABLE_PRESENTATION_SWITCH - ], 170); - $this->EnableAction("UPPERCASE"); - SetValueBoolean($this->GetIDForIdent("UPPERCASE"),0); + ], 170, false); - $this->RegisterVariableInteger ('TIME_COL', 'Time Color', [ + $this->RegisterIntegerVariableWithDefault('TIME_COL', 'Time Color', [ 'PRESENTATION' => VARIABLE_PRESENTATION_COLOR, 'ENCODING' => 0, 'COLOR_SPACE' => 0, 'COLOR_CURVE' => 0 - ], 180); - $this->EnableAction("TIME_COL"); - SetValueInteger($this->GetIDForIdent("TIME_COL"),16777215); + ], 180, 16777215); - $this->RegisterVariableInteger ('DATE_COL', 'Date Color', [ + $this->RegisterIntegerVariableWithDefault('DATE_COL', 'Date Color', [ 'PRESENTATION' => VARIABLE_PRESENTATION_COLOR, 'ENCODING' => 0, 'COLOR_SPACE' => 0, 'COLOR_CURVE' => 0 - ], 190); - $this->EnableAction("DATE_COL"); - SetValueInteger($this->GetIDForIdent("DATE_COL"),16777215); + ], 190, 16777215); - $this->RegisterVariableInteger ('TEMP_COL', 'Temperature Color',[ + $this->RegisterIntegerVariableWithDefault('TEMP_COL', 'Temperature Color', [ 'PRESENTATION' => VARIABLE_PRESENTATION_COLOR, 'ENCODING' => 0, 'COLOR_SPACE' => 0, 'COLOR_CURVE' => 0 - ], 200); - $this->EnableAction("TEMP_COL"); - SetValueInteger($this->GetIDForIdent("TEMP_COL"),16777215); + ], 200, 16777215); - $this->RegisterVariableInteger ('HUM_COL', 'Humidity Color', [ + $this->RegisterIntegerVariableWithDefault('HUM_COL', 'Humidity Color', [ 'PRESENTATION' => VARIABLE_PRESENTATION_COLOR, 'ENCODING' => 0, 'COLOR_SPACE' => 0, 'COLOR_CURVE' => 0 - ], 210); - $this->EnableAction("HUM_COL"); - SetValueInteger($this->GetIDForIdent("HUM_COL"),16777215); + ], 210, 16777215); - $this->RegisterVariableInteger ('BAT_COL', 'Battery Color', [ + $this->RegisterIntegerVariableWithDefault('BAT_COL', 'Battery Color', [ 'PRESENTATION' => VARIABLE_PRESENTATION_COLOR, 'ENCODING' => 0, 'COLOR_SPACE' => 0, 'COLOR_CURVE' => 0 - ], 220); - $this->EnableAction("BAT_COL"); - SetValueInteger($this->GetIDForIdent("BAT_COL"),16777215); + ], 220, 16777215); - $this->RegisterVariableInteger ('SSPEED', 'Scrollspeed',[ + $this->RegisterIntegerVariableWithDefault('SSPEED', 'Scrollspeed', [ 'PRESENTATION' => VARIABLE_PRESENTATION_SLIDER, 'MIN' => 0, 'MAX' => 100, 'PERCENTAGE' => true, - 'USAGE_TYPE' => 2, ///5 = none + 'USAGE_TYPE' => 2, 'SUFFIX' => ' %' - ], 230); - $this->EnableAction("SSPEED"); - SetValueInteger($this->GetIDForIdent("SSPEED"),100); + ], 230, 100); - $this->RegisterVariableBoolean('MATP', 'Powerswitch', [ + $this->RegisterBooleanVariableWithDefault('MATP', 'Powerswitch', [ 'PRESENTATION' => VARIABLE_PRESENTATION_SWITCH - ], 240); - $this->EnableAction("MATP"); - SetValueBoolean($this->GetIDForIdent("MATP"),1); + ], 240, true); - $this->RegisterVariableString ('OVERLAY', 'Effectsoverlay', [ - 'PRESENTATION' => VARIABLE_PRESENTATION_SLIDER, - 'MIN' => 0, - 'MAX' => 2000, - 'STEP_SIZE' => 50, - 'USAGE_TYPE' => 5, ///5 = none - 'SUFFIX' => ' ms' - ], 250); - /*Overlay effects: + $this->RegisterStringVariableWithDefault('OVERLAY', 'Effect overlay', [ + ], 250, 'clear'); - "clear" - "snow" - "rain" - "drizzle" - "storm" - "thunder" - "frost"*/ - - // Diese Zeile nicht löschen. - - $this->UpdateConfig(); - - - $this->RegisterTimer("Update", 0, 'AWSET_UpdateConfig('.$this->InstanceID.');'); - $this->SetTimerInterval("Update", 10 * 1000); - - parent::Create(); + $this->RegisterTimer('Update', 0, 'AWSET_UpdateConfig(' . $this->InstanceID . ');'); + if ($this->ReadAttributeString('LastSettings') === '') { + $this->WriteLastSettings($this->BuildSettingsPayload()); + } } - // Überschreibt die intere IPS_ApplyChanges($id) Funktion - public function ApplyChanges() { - - $this->SendSetting(); - $this->SendReboot(); - - // Diese Zeile nicht löschen + public function ApplyChanges() + { parent::ApplyChanges(); - } - /** - * Die folgenden Funktionen stehen automatisch zur Verfügung, wenn das Modul über die "Module Control" eingefügt wurden. - * Die Funktionen werden, mit dem selbst eingerichteten Prefix, in PHP und JSON-RPC wiefolgt zur Verfügung gestellt: - * - * DWM_SendMessage($id); - * - */ - public function RequestAction($Ident, $Value) { - $varid = $this->GetIDForIdent($Ident); - SetValue($varid, $Value); - - $this->SendSetting(); - } - - public function SendSetting() { - if ($this->CheckIP()) { - $awtrixIp = $this->ReadPropertyString("AwtrixIp"); - $url="http://{$awtrixIp}/api/settings"; - - $atime = GetValue($this->GetIDForIdent('ATIME')); - $teff = GetValue($this->GetIDForIdent('TEFF')); - $tspeed = GetValue($this->GetIDForIdent('TSPEED')); - $tcol = GetValue($this->GetIDForIdent('TCOL')); - $tmode = GetValue($this->GetIDForIdent('TMODE')); - - $chcol = GetValue($this->GetIDForIdent('CHCOL')); - $cbcol = GetValue($this->GetIDForIdent('CBCOL')); - $ctcol = GetValue($this->GetIDForIdent('CTCOL')); - $wd = GetValueBoolean($this->GetIDForIdent('WD')); - $wdca = GetValue($this->GetIDForIdent('WDCA')); - $wdci = GetValue($this->GetIDForIdent('WDCI')); - $bri = GetValue($this->GetIDForIdent('BRI')); - $abri = GetValueBoolean($this->GetIDForIdent('ABRI')); - $atrans = GetValueBoolean($this->GetIDForIdent('ATRANS')); - - $ccorrection = $this->ReadPropertyInteger("CCORRECTION"); - $ctemp = $this->ReadPropertyInteger("CTEMP"); - - $tformat = $this->ReadPropertyString("TFORMAT"); - $dformat = $this->ReadPropertyString("DFORMAT"); - - $som = $this->ReadPropertyBoolean("SOM"); - $cel = $this->ReadPropertyBoolean("CEL"); - - $blockn = GetValueBoolean($this->GetIDForIdent('BLOCKN')); - $uppercase = GetValueBoolean($this->GetIDForIdent('UPPERCASE')); - - $time_col = GetValue($this->GetIDForIdent('TIME_COL')); - $date_col = GetValue($this->GetIDForIdent('DATE_COL')); - $temp_col = GetValue($this->GetIDForIdent('TEMP_COL')); - $hum_col = GetValue($this->GetIDForIdent('HUM_COL')); - $bat_col = GetValue($this->GetIDForIdent('BAT_COL')); - - $sspeed = GetValue($this->GetIDForIdent('SSPEED')); - - $tim = $this->ReadPropertyBoolean("TIM"); - $dat= $this->ReadPropertyBoolean("DAT"); - $hum = $this->ReadPropertyBoolean("HUM"); - $temp = $this->ReadPropertyBoolean("TEMP"); - $bat = $this->ReadPropertyBoolean("BAT"); - - $matp = GetValueBoolean($this->GetIDForIdent('MATP')); - - - $settings = [ - - // Anzeige - "ATIME" => $atime, - "TEFF" => $teff, - "TSPEED" => $tspeed, - "TMODE" => $tmode, - - // Farben - "TCOL" => dechex($tcol), - "TIME_COL" => dechex($time_col), - "DATE_COL" => dechex($date_col), - "TEMP_COL" => dechex($temp_col), - "HUM_COL" => dechex($hum_col), - "BAT_COL" => dechex($bat_col), - - "CHCOL" => dechex($chcol), - "CBCOL" => dechex($cbcol), - "CTCOL" => dechex($ctcol), - - "WDCA" => dechex($wdca), - "WDCI" => dechex($wdci), - - // Time / Date - "TFORMAT" => $tformat, - "DFORMAT" => $dformat, - - // Verhalten - "WD" => $wd, - "SOM" => $som, - "CEL" => $cel, - - // Matrix - "BRI" => $bri, - "ABRI" => $abri, - "ATRANS" => $atrans, - "CCORRECTION" => $ccorrection, - "CTEMP" => $ctemp, - "BLOCKN" => $blockn, - "UPPERCASE" => $uppercase, - "MATP" => $matp, - - // Apps aktivieren - "TIM" => $tim, - "DAT" => $dat, - "TEMP" => $temp, - "HUM" => $hum, - "BAT" => $bat, - - // Overlay Effekt - "OVERLAY" => "clear", - - // Scroll Speed - "SSPEED" => $sspeed, - - // Sound - "VOL" => 10 - ]; - - // ============================= - // CURL Request - // ============================= - $ch = curl_init($url); - - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_HTTPHEADER, [ - "Content-Type: application/json" - ]); - - curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($settings)); - - $response = curl_exec($ch); - $error = curl_error($ch); - - curl_close($ch); - - //$this->WriteAttributeString('Settings',$settings); + if ($this->ReadAttributeInteger('SyncInProgress') === 1) { + $this->WriteAttributeInteger('SyncInProgress', 0); + $this->SetStatus(102); + return; } - + + $currentSettings = $this->BuildSettingsPayload(); + $lastSettings = $this->ReadLastSettings(); + + if ($this->SettingsAreEqual($currentSettings, $lastSettings)) { + $this->SetStatus(102); + return; + } + + if (!$this->SendSettingsPayload($currentSettings)) { + return; + } + + if ($this->AppStatesChanged($currentSettings, $lastSettings)) { + $this->SendReboot(); + } + + $this->WriteLastSettings($currentSettings); } - public function UpdateConfig() { - if ($this->CheckIP()) { - $awtrixIp = $this->ReadPropertyString("AwtrixIp"); - $url="http://{$awtrixIp}/api/settings"; - - $ch = curl_init($url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_HTTPGET, true); + public function RequestAction($Ident, $Value) + { + SetValue($this->GetIDForIdent($Ident), $Value); + $this->SendSetting(); + } - $response = curl_exec($ch); - $error = curl_error($ch); + public function SendSetting() + { + $settings = $this->BuildSettingsPayload(); + $lastSettings = $this->ReadLastSettings(); - curl_close($ch); + if (!$this->SendSettingsPayload($settings)) { + return false; + } - if ($error) { - echo "CURL Error: " . $error; - exit; + if ($this->AppStatesChanged($settings, $lastSettings)) { + $this->SendReboot(); + } + + $this->WriteLastSettings($settings); + return true; + } + + public function UpdateConfig() + { + $result = $this->RequestAwtrix('GET', '/api/settings'); + if (!$result['success']) { + return false; + } + + if (!is_array($result['data'])) { + $this->SetErrorState('Invalid response from /api/settings'); + return false; + } + + $data = $result['data']; + $this->WriteAttributeString('Settings', json_encode($data)); + + $this->UpdateRuntimeVariablesFromApi($data); + $this->UpdateFormFieldsFromApi($data); + + $normalized = $this->NormalizeSettingsFromApi($data); + $this->WriteLastSettings($normalized); + + if ($this->UpdateStoredPropertiesFromApi($data)) { + $this->WriteAttributeInteger('SyncInProgress', 1); + IPS_ApplyChanges($this->InstanceID); + } else { + $this->SetStatus(102); + } + + return true; + } + + public function SendReboot() + { + $result = $this->RequestAwtrix('POST', '/api/reboot', []); + return $result['success']; + } + + public function CheckIP() + { + $result = $this->RequestAwtrix('GET', '/api/stats'); + return $result['success']; + } + + private function RegisterIntegerVariableWithDefault($ident, $name, array $presentation, $position, $defaultValue) + { + $exists = $this->HasIdent($ident); + $this->RegisterVariableInteger($ident, $name, $presentation, $position); + $this->EnableAction($ident); + + if (!$exists) { + SetValueInteger($this->GetIDForIdent($ident), $defaultValue); + } + } + + private function RegisterBooleanVariableWithDefault($ident, $name, array $presentation, $position, $defaultValue) + { + $exists = $this->HasIdent($ident); + $this->RegisterVariableBoolean($ident, $name, $presentation, $position); + $this->EnableAction($ident); + + if (!$exists) { + SetValueBoolean($this->GetIDForIdent($ident), $defaultValue); + } + } + + private function RegisterStringVariableWithDefault($ident, $name, array $presentation, $position, $defaultValue) + { + $exists = $this->HasIdent($ident); + $this->RegisterVariableString($ident, $name, $presentation, $position); + $this->EnableAction($ident); + + if (!$exists) { + SetValueString($this->GetIDForIdent($ident), $defaultValue); + } + } + + private function HasIdent($ident) + { + return (@$this->GetIDForIdent($ident) > 0); + } + + private function BuildSettingsPayload() + { + $settings = [ + 'ATIME' => GetValueInteger($this->GetIDForIdent('ATIME')), + 'TEFF' => GetValueInteger($this->GetIDForIdent('TEFF')), + 'TSPEED' => GetValueInteger($this->GetIDForIdent('TSPEED')), + 'TMODE' => GetValueInteger($this->GetIDForIdent('TMODE')), + 'TCOL' => $this->ColorToApi(GetValueInteger($this->GetIDForIdent('TCOL'))), + 'TIME_COL' => $this->ColorToApi(GetValueInteger($this->GetIDForIdent('TIME_COL'))), + 'DATE_COL' => $this->ColorToApi(GetValueInteger($this->GetIDForIdent('DATE_COL'))), + 'TEMP_COL' => $this->ColorToApi(GetValueInteger($this->GetIDForIdent('TEMP_COL'))), + 'HUM_COL' => $this->ColorToApi(GetValueInteger($this->GetIDForIdent('HUM_COL'))), + 'BAT_COL' => $this->ColorToApi(GetValueInteger($this->GetIDForIdent('BAT_COL'))), + 'CHCOL' => $this->ColorToApi(GetValueInteger($this->GetIDForIdent('CHCOL'))), + 'CBCOL' => $this->ColorToApi(GetValueInteger($this->GetIDForIdent('CBCOL'))), + 'CTCOL' => $this->ColorToApi(GetValueInteger($this->GetIDForIdent('CTCOL'))), + 'WDCA' => $this->ColorToApi(GetValueInteger($this->GetIDForIdent('WDCA'))), + 'WDCI' => $this->ColorToApi(GetValueInteger($this->GetIDForIdent('WDCI'))), + 'TFORMAT' => $this->ReadPropertyString('TFORMAT'), + 'DFORMAT' => $this->ReadPropertyString('DFORMAT'), + 'WD' => GetValueBoolean($this->GetIDForIdent('WD')), + 'SOM' => $this->ReadPropertyBoolean('SOM'), + 'CEL' => $this->ReadPropertyBoolean('CEL'), + 'BRI' => GetValueInteger($this->GetIDForIdent('BRI')), + 'ABRI' => GetValueBoolean($this->GetIDForIdent('ABRI')), + 'ATRANS' => GetValueBoolean($this->GetIDForIdent('ATRANS')), + 'CCORRECTION' => $this->ColorToApi($this->ReadPropertyInteger('CCORRECTION')), + 'CTEMP' => $this->ColorToApi($this->ReadPropertyInteger('CTEMP')), + 'BLOCKN' => GetValueBoolean($this->GetIDForIdent('BLOCKN')), + 'UPPERCASE' => GetValueBoolean($this->GetIDForIdent('UPPERCASE')), + 'MATP' => GetValueBoolean($this->GetIDForIdent('MATP')), + 'TIM' => $this->ReadPropertyBoolean('TIM'), + 'DAT' => $this->ReadPropertyBoolean('DAT'), + 'TEMP' => $this->ReadPropertyBoolean('TEMP'), + 'HUM' => $this->ReadPropertyBoolean('HUM'), + 'BAT' => $this->ReadPropertyBoolean('BAT'), + 'SSPEED' => GetValueInteger($this->GetIDForIdent('SSPEED')), + 'OVERLAY' => GetValueString($this->GetIDForIdent('OVERLAY')) + ]; + + return $settings; + } + + private function SendSettingsPayload(array $settings) + { + $result = $this->RequestAwtrix('POST', '/api/settings', $settings); + if ($result['success']) { + $this->WriteAttributeString('Settings', json_encode($settings)); + } + + return $result['success']; + } + + private function RequestAwtrix($method, $path, array $payload = null) + { + $awtrixIp = trim($this->ReadPropertyString('AwtrixIp')); + $url = 'http://' . $awtrixIp . $path; + + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, 2000); + curl_setopt($ch, CURLOPT_TIMEOUT_MS, 5000); + + if ($method === 'POST') { + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); + if ($payload !== null) { + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); } + } else { + curl_setopt($ch, CURLOPT_HTTPGET, true); + } + $response = curl_exec($ch); + $error = curl_error($ch); + $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($response === false || $error !== '') { + $this->SetErrorState('CURL error on ' . $path . ': ' . $error); + return ['success' => false, 'data' => null, 'raw' => $response]; + } + + if ($httpCode < 200 || $httpCode >= 300) { + $this->SetErrorState('HTTP ' . $httpCode . ' on ' . $path); + return ['success' => false, 'data' => null, 'raw' => $response]; + } + + $data = null; + if ($response !== '' && $response !== null) { $data = json_decode($response, true); - - // $persData = $this->ReadAttributeString('Settings'); - - - Setvalue($this->GetIDForIdent('ATIME'),$data['ATIME']); - Setvalue($this->GetIDForIdent('TEFF'),$data['TEFF']); - Setvalue($this->GetIDForIdent('TSPEED'),$data['TSPEED']); - Setvalue($this->GetIDForIdent('TCOL'),$data['TCOL']); - Setvalue($this->GetIDForIdent('TMODE'),$data['TMODE']); - Setvalue($this->GetIDForIdent('CHCOL'),$data['CHCOL']); - Setvalue($this->GetIDForIdent('CBCOL'),$data['CBCOL']); - Setvalue($this->GetIDForIdent('CTCOL'),$data['CTCOL']); - SetValueBoolean($this->GetIDForIdent('WD'),$data['WD'] ?? false); - Setvalue($this->GetIDForIdent('WDCA'),$data['WDCA']); - Setvalue($this->GetIDForIdent('WDCI'),$data['WDCI']); - Setvalue($this->GetIDForIdent('BRI'),$data['BRI']); - SetValueBoolean($this->GetIDForIdent('ABRI'),$data['ABRI'] ?? false); - SetValueBoolean($this->GetIDForIdent('ATRANS'),$data['ATRANS'] ?? false); - - $this->UpdateFormField("CCORRECTION",'value',hexdec($data['CCORRECTION']) ?? 0); - $this->UpdateFormField("CTEMP",'value',hexdec($data['CTEMP']) ?? 0); - - $this->UpdateFormField("TFORMAT",'value',$data['TFORMAT']); - $this->UpdateFormField("DFORMAT",'value',$data['DFORMAT']); - - $this->UpdateFormField("SOM",'value',$data['SOM'] ?? true); - $this->UpdateFormField("CEL",'value',$data['CEL'] ?? true); - - SetValueBoolean($this->GetIDForIdent('BLOCKN'), $data['BLOCKN'] ?? false); - SetValueBoolean($this->GetIDForIdent('UPPERCASE'), $data['UPPERCASE'] ?? false); - - Setvalue($this->GetIDForIdent('TIME_COL'),hexdec($data['TIME_COL'])); - Setvalue($this->GetIDForIdent('DATE_COL'),hexdec($data['DATE_COL'])); - Setvalue($this->GetIDForIdent('TEMP_COL'),hexdec($data['TEMP_COL'])); - Setvalue($this->GetIDForIdent('HUM_COL'),hexdec($data['HUM_COL'])); - Setvalue($this->GetIDForIdent('BAT_COL'),hexdec($data['BAT_COL'])); - - Setvalue($this->GetIDForIdent('SSPEED'),$data['SSPEED']); - - $this->UpdateFormField("TIM",'value',$data['TIM']); - $this->UpdateFormField("DAT",'value',$data['DAT']); - $this->UpdateFormField("HUM",'value',$data['HUM']); - $this->UpdateFormField("TEMP",'value',$data['TEMP']); - $this->UpdateFormField("BAT",'value',$data['BAT']); - - SetValueBoolean($this->GetIDForIdent('MATP'),$data['MATP'] ?? false); - - //$this->WriteAttributeString('Settings',$data); - - - } - - } - - public function SendReboot() { - - if (!$this->CheckIP()) { - // Discord webhook URL - $awtrixIp = $this->ReadPropertyString("AwtrixIp"); - $url="http://{$awtrixIp}/api/reboot"; - - $settings = [ - - // Anzeige - - ]; - - // ============================= - // CURL Request - // ============================= - $ch = curl_init($url); - - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_HTTPHEADER, [ - "Content-Type: application/json" - ]); - - curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($settings)); - - $response = curl_exec($ch); - $error = curl_error($ch); - - curl_close($ch); - } - - } - - public function CheckIP() { - - // Discord webhook URL - $awtrixIp = $this->ReadPropertyString("AwtrixIp"); - $url="http://{$awtrixIp}/api/stats"; - - $ch = curl_init($url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_HTTPGET, true); - curl_setopt($ch, CURLOPT_TIMEOUT_MS, 2500); - - $response = curl_exec($ch); - $error = curl_error($ch); - - curl_close($ch); - if (!curl_errno($ch)) { - $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); - if ($http_code == 200) { - return true; - $this->SetStatus(102); - IPS_LogMessage("AWTRIX","Status 102"); - } else { - return false; - $this->SetStatus(104); - IPS_LogMessage("AWTRIX","Status 104"); - } - } else { - return false; - $this->SetStatus(104); - IPS_LogMessage("AWTRIX","Status 104 END"); + if ($data === null && json_last_error() !== JSON_ERROR_NONE) { + $data = null; } + } + $this->SetStatus(102); + return ['success' => true, 'data' => $data, 'raw' => $response]; + } + + private function UpdateRuntimeVariablesFromApi(array $data) + { + $this->SetIntegerVariableIfPresent('ATIME', $data, 'ATIME'); + $this->SetIntegerVariableIfPresent('TEFF', $data, 'TEFF'); + $this->SetIntegerVariableIfPresent('TSPEED', $data, 'TSPEED'); + $this->SetColorVariableIfPresent('TCOL', $data, 'TCOL'); + $this->SetIntegerVariableIfPresent('TMODE', $data, 'TMODE'); + $this->SetColorVariableIfPresent('CHCOL', $data, 'CHCOL'); + $this->SetColorVariableIfPresent('CBCOL', $data, 'CBCOL'); + $this->SetColorVariableIfPresent('CTCOL', $data, 'CTCOL'); + $this->SetBooleanVariableIfPresent('WD', $data, 'WD'); + $this->SetColorVariableIfPresent('WDCA', $data, 'WDCA'); + $this->SetColorVariableIfPresent('WDCI', $data, 'WDCI'); + $this->SetIntegerVariableIfPresent('BRI', $data, 'BRI'); + $this->SetBooleanVariableIfPresent('ABRI', $data, 'ABRI'); + $this->SetBooleanVariableIfPresent('ATRANS', $data, 'ATRANS'); + $this->SetBooleanVariableIfPresent('BLOCKN', $data, 'BLOCKN'); + $this->SetBooleanVariableIfPresent('UPPERCASE', $data, 'UPPERCASE'); + $this->SetColorVariableIfPresent('TIME_COL', $data, 'TIME_COL'); + $this->SetColorVariableIfPresent('DATE_COL', $data, 'DATE_COL'); + $this->SetColorVariableIfPresent('TEMP_COL', $data, 'TEMP_COL'); + $this->SetColorVariableIfPresent('HUM_COL', $data, 'HUM_COL'); + $this->SetColorVariableIfPresent('BAT_COL', $data, 'BAT_COL'); + $this->SetIntegerVariableIfPresent('SSPEED', $data, 'SSPEED'); + $this->SetBooleanVariableIfPresent('MATP', $data, 'MATP'); + $this->SetStringVariableIfPresent('OVERLAY', $data, 'OVERLAY'); + } + + private function UpdateFormFieldsFromApi(array $data) + { + if (array_key_exists('TIM', $data)) { + $this->UpdateFormField('TIM', 'value', (bool) $data['TIM']); + } + if (array_key_exists('DAT', $data)) { + $this->UpdateFormField('DAT', 'value', (bool) $data['DAT']); + } + if (array_key_exists('HUM', $data)) { + $this->UpdateFormField('HUM', 'value', (bool) $data['HUM']); + } + if (array_key_exists('TEMP', $data)) { + $this->UpdateFormField('TEMP', 'value', (bool) $data['TEMP']); + } + if (array_key_exists('BAT', $data)) { + $this->UpdateFormField('BAT', 'value', (bool) $data['BAT']); + } + if (array_key_exists('TFORMAT', $data)) { + $this->UpdateFormField('TFORMAT', 'value', (string) $data['TFORMAT']); + } + if (array_key_exists('DFORMAT', $data)) { + $this->UpdateFormField('DFORMAT', 'value', (string) $data['DFORMAT']); + } + if (array_key_exists('SOM', $data)) { + $this->UpdateFormField('SOM', 'value', (bool) $data['SOM']); + } + if (array_key_exists('CEL', $data)) { + $this->UpdateFormField('CEL', 'value', (bool) $data['CEL']); + } + if (array_key_exists('CCORRECTION', $data)) { + $this->UpdateFormField('CCORRECTION', 'value', $this->ApiColorToInt($data['CCORRECTION'])); + } + if (array_key_exists('CTEMP', $data)) { + $this->UpdateFormField('CTEMP', 'value', $this->ApiColorToInt($data['CTEMP'])); + } + } + + private function UpdateStoredPropertiesFromApi(array $data) + { + $changed = false; + + $changed = $this->UpdatePropertyIfChanged('TIM', array_key_exists('TIM', $data) ? (bool) $data['TIM'] : null) || $changed; + $changed = $this->UpdatePropertyIfChanged('DAT', array_key_exists('DAT', $data) ? (bool) $data['DAT'] : null) || $changed; + $changed = $this->UpdatePropertyIfChanged('HUM', array_key_exists('HUM', $data) ? (bool) $data['HUM'] : null) || $changed; + $changed = $this->UpdatePropertyIfChanged('TEMP', array_key_exists('TEMP', $data) ? (bool) $data['TEMP'] : null) || $changed; + $changed = $this->UpdatePropertyIfChanged('BAT', array_key_exists('BAT', $data) ? (bool) $data['BAT'] : null) || $changed; + $changed = $this->UpdatePropertyIfChanged('TFORMAT', array_key_exists('TFORMAT', $data) ? (string) $data['TFORMAT'] : null) || $changed; + $changed = $this->UpdatePropertyIfChanged('DFORMAT', array_key_exists('DFORMAT', $data) ? (string) $data['DFORMAT'] : null) || $changed; + $changed = $this->UpdatePropertyIfChanged('SOM', array_key_exists('SOM', $data) ? (bool) $data['SOM'] : null) || $changed; + $changed = $this->UpdatePropertyIfChanged('CEL', array_key_exists('CEL', $data) ? (bool) $data['CEL'] : null) || $changed; + $changed = $this->UpdatePropertyIfChanged('CCORRECTION', array_key_exists('CCORRECTION', $data) ? $this->ApiColorToInt($data['CCORRECTION']) : null) || $changed; + $changed = $this->UpdatePropertyIfChanged('CTEMP', array_key_exists('CTEMP', $data) ? $this->ApiColorToInt($data['CTEMP']) : null) || $changed; + + return $changed; + } + + private function UpdatePropertyIfChanged($propertyName, $newValue) + { + if ($newValue === null) { + return false; + } + + $currentValue = IPS_GetProperty($this->InstanceID, $propertyName); + if ($currentValue === $newValue) { + return false; + } + + IPS_SetProperty($this->InstanceID, $propertyName, $newValue); + return true; + } + + private function NormalizeSettingsFromApi(array $data) + { + $currentSettings = $this->BuildSettingsPayload(); + + foreach (['ATIME', 'TEFF', 'TSPEED', 'TMODE', 'BRI', 'SSPEED'] as $key) { + if (array_key_exists($key, $data)) { + $currentSettings[$key] = (int) $data[$key]; + } + } + + foreach (['WD', 'ABRI', 'ATRANS', 'BLOCKN', 'UPPERCASE', 'MATP', 'SOM', 'CEL', 'TIM', 'DAT', 'HUM', 'TEMP', 'BAT'] as $key) { + if (array_key_exists($key, $data)) { + $currentSettings[$key] = (bool) $data[$key]; + } + } + + foreach (['TFORMAT', 'DFORMAT', 'OVERLAY'] as $key) { + if (array_key_exists($key, $data)) { + $currentSettings[$key] = (string) $data[$key]; + } + } + + foreach (['TCOL', 'TIME_COL', 'DATE_COL', 'TEMP_COL', 'HUM_COL', 'BAT_COL', 'CHCOL', 'CBCOL', 'CTCOL', 'WDCA', 'WDCI', 'CCORRECTION', 'CTEMP'] as $key) { + if (array_key_exists($key, $data)) { + $currentSettings[$key] = $this->ColorToApi($this->ApiColorToInt($data[$key])); + } + } + + return $currentSettings; + } + + private function SetIntegerVariableIfPresent($ident, array $data, $key) + { + if (array_key_exists($key, $data)) { + SetValueInteger($this->GetIDForIdent($ident), (int) $data[$key]); + } + } + + private function SetBooleanVariableIfPresent($ident, array $data, $key) + { + if (array_key_exists($key, $data)) { + SetValueBoolean($this->GetIDForIdent($ident), (bool) $data[$key]); + } + } + + private function SetStringVariableIfPresent($ident, array $data, $key) + { + if (array_key_exists($key, $data)) { + SetValueString($this->GetIDForIdent($ident), (string) $data[$key]); + } + } + + private function SetColorVariableIfPresent($ident, array $data, $key) + { + if (array_key_exists($key, $data)) { + SetValueInteger($this->GetIDForIdent($ident), $this->ApiColorToInt($data[$key])); + } + } + + private function ColorToApi($value) + { + $value = max(0, min((int) $value, 16777215)); + return strtolower(str_pad(dechex($value), 6, '0', STR_PAD_LEFT)); + } + + private function ApiColorToInt($value) + { + if (is_int($value)) { + return $value; + } + + $value = strtolower(trim((string) $value)); + $value = ltrim($value, '#'); + if ($value === '') { + return 0; + } + + if (ctype_xdigit($value)) { + return (int) hexdec($value); + } + + if (is_numeric($value)) { + return (int) $value; + } + + return (int) hexdec($value); + } + + private function AppStatesChanged(array $currentSettings, array $lastSettings) + { + foreach (self::APP_KEYS as $key) { + $currentValue = array_key_exists($key, $currentSettings) ? (bool) $currentSettings[$key] : false; + $lastValue = array_key_exists($key, $lastSettings) ? (bool) $lastSettings[$key] : false; + if ($currentValue !== $lastValue) { + return true; + } + } + + return false; + } + + private function SettingsAreEqual(array $left, array $right) + { + return $this->NormalizeForCompare($left) === $this->NormalizeForCompare($right); + } + + private function NormalizeForCompare(array $settings) + { + ksort($settings); + return json_encode($settings); + } + + private function ReadLastSettings() + { + $json = $this->ReadAttributeString('LastSettings'); + if ($json === '') { + return []; + } + + $data = json_decode($json, true); + return is_array($data) ? $data : []; + } + + private function WriteLastSettings(array $settings) + { + $this->WriteAttributeString('LastSettings', $this->NormalizeForCompare($settings)); + } + + private function SetErrorState($message) + { + $this->SetStatus(104); + IPS_LogMessage('AWTRIX', $message); } } -?> \ No newline at end of file +?> diff --git a/library.json b/library.json index 980173a..1de2c7c 100644 --- a/library.json +++ b/library.json @@ -4,10 +4,9 @@ "name": "Awtrix Tools", "url": "https://jh-artworks.com", "compatibility": { - "version": "6.0", - "date": 1491343200 + "version": "9.0" }, - "version": "1.2", + "version": "1.3", "build": 3, "date": 0 } \ No newline at end of file