From ffab78281a67cd5037e7314b8a62df52f440caef Mon Sep 17 00:00:00 2001 From: jhartworks Date: Thu, 16 Apr 2026 16:06:53 +0200 Subject: [PATCH] Refactor CounterClient to CounterServer with improved payload handling and measurement storage - Renamed class from CounterClient to CounterServer for clarity. - Updated properties to include Connector and SourceJSON. - Removed MQTT-related properties and methods. - Implemented checkTable method to handle JSON input and database operations. - Added logic for inserting/updating meter devices and measurements in the database. - Simplified unit handling and removed unused methods. - Enhanced logging for better debugging and error tracking. --- CounterClient/module.php | 462 ++++++++++-------------------- CounterServer/module.php | 601 ++++++++------------------------------- 2 files changed, 270 insertions(+), 793 deletions(-) diff --git a/CounterClient/module.php b/CounterClient/module.php index 5990ca4..cd1f436 100644 --- a/CounterClient/module.php +++ b/CounterClient/module.php @@ -50,8 +50,6 @@ class CounterClient extends IPSModule if ($mqttClientId > 0 && IPS_InstanceExists($mqttClientId) && $mqttTopic !== '') { $this->MqttPublish($mqttClientId, $mqttTopic, $json, true); - } else { - IPS_LogMessage('CounterClient', 'MQTT ist aktiviert, aber ungültige MQTT Client ID oder Topic.'); } } } @@ -64,82 +62,90 @@ class CounterClient extends IPSModule return ''; } - $counterIds = IPS_GetChildrenIDs($counterCategoryId); - $payload = [ 'timestamp' => date('Y-m-d H:i:s'), 'counters' => [] ]; - foreach ($counterIds as $counterObjectId) { - if (!IPS_ObjectExists($counterObjectId)) { + $counterFolders = IPS_GetChildrenIDs($counterCategoryId); + + foreach ($counterFolders as $counterFolderId) { + if (!IPS_ObjectExists($counterFolderId)) { continue; } - $obj = IPS_GetObject($counterObjectId); - + $obj = IPS_GetObject($counterFolderId); if ((int)$obj['ObjectType'] !== 0) { continue; } - $counterName = IPS_GetName($counterObjectId); + $counterName = IPS_GetName($counterFolderId); $counterId = $this->makeSlug($counterName); - $entries = $this->CollectEntriesRecursive($counterObjectId, [$counterName], []); - if (count($entries) === 0) { - IPS_LogMessage('CounterClient', 'Keine Variablen gefunden in Counter: ' . $counterName); - continue; - } - $counterData = [ 'id' => $counterId, 'name' => $counterName ]; - $tempSlots = []; - $pressureSlots = []; + $found = 0; + $children = IPS_GetChildrenIDs($counterFolderId); - foreach ($entries as $entry) { - $classified = $this->ClassifyEntry($entry); - if ($classified === null) { + foreach ($children as $childId) { + if (!IPS_ObjectExists($childId)) { continue; } - $field = $classified['field']; - $value = $classified['value']; - $unit = $classified['unit']; + if (IPS_LinkExists($childId)) { + $link = IPS_GetLink($childId); + $targetId = (int)$link['TargetID']; + $linkName = IPS_GetName($childId); + + if ($targetId <= 0 || !IPS_ObjectExists($targetId)) { + continue; + } + + if (IPS_VariableExists($targetId)) { + if ($this->addVariableToCounter($counterData, $targetId, $counterName . ' ' . $linkName)) { + $found++; + } + } else { + $subChildren = IPS_GetChildrenIDs($targetId); + foreach ($subChildren as $subChildId) { + if (IPS_VariableExists($subChildId)) { + if ($this->addVariableToCounter($counterData, $subChildId, $counterName . ' ' . $linkName . ' ' . IPS_GetName($targetId))) { + $found++; + } + } + } + } - if ($field === 'temperature_auto') { - $tempSlots[] = $classified; continue; } - if ($field === 'pressure_auto') { - $pressureSlots[] = $classified; + if (IPS_VariableExists($childId)) { + if ($this->addVariableToCounter($counterData, $childId, $counterName)) { + $found++; + } continue; } - $counterData[$field] = $value; - - if ($field === 'total_value' && !isset($counterData['unit']) && $unit !== '') { - $counterData['unit'] = $unit; + $subChildren = IPS_GetChildrenIDs($childId); + foreach ($subChildren as $subChildId) { + if (IPS_VariableExists($subChildId)) { + if ($this->addVariableToCounter($counterData, $subChildId, $counterName . ' ' . IPS_GetName($childId))) { + $found++; + } + } } } - $this->assignInOutValues($counterData, $tempSlots, 'temperature_in_value', 'temperature_out_value'); - $this->assignInOutValues($counterData, $pressureSlots, 'pressure_in_value', 'pressure_out_value'); - - $contextString = mb_strtolower($counterName . ' ' . implode(' ', array_map(function ($e) { - return $e['context']; - }, $entries))); - - $counterData['type'] = $this->DetectCounterType($counterData, $contextString); + $counterData['type'] = $this->DetectCounterType($counterData, $counterName); if (!isset($counterData['unit'])) { $counterData['unit'] = $this->inferDefaultUnit($counterData); } - if ($this->hasUsableMeasurement($counterData)) { + if ($found > 0) { $payload['counters'][] = $counterData; } else { IPS_LogMessage('CounterClient', 'Counter ohne erkannte Messwerte übersprungen: ' . $counterName); @@ -149,273 +155,164 @@ class CounterClient extends IPSModule return json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); } - private function CollectEntriesRecursive(int $objectId, array $pathParts, array $visited): array + private function addVariableToCounter(array &$counterData, int $varId, string $contextPrefix): bool { - $result = []; - - if (isset($visited[$objectId])) { - return $result; - } - $visited[$objectId] = true; - - if (!IPS_ObjectExists($objectId)) { - return $result; - } - - if (IPS_LinkExists($objectId)) { - $link = IPS_GetLink($objectId); - $targetId = (int)$link['TargetID']; - $linkName = IPS_GetName($objectId); - - if ($targetId > 0 && IPS_ObjectExists($targetId)) { - $newPath = $pathParts; - if ($linkName !== '') { - $newPath[] = $linkName; - } - - return $this->CollectEntriesRecursive($targetId, $newPath, $visited); - } - - return $result; - } - - if (IPS_VariableExists($objectId)) { - $result[] = [ - 'varId' => $objectId, - 'context' => $this->buildContextString($pathParts, IPS_GetName($objectId)), - 'path' => $pathParts, - 'varName' => IPS_GetName($objectId) - ]; - return $result; - } - - $children = IPS_GetChildrenIDs($objectId); - foreach ($children as $childId) { - if (!IPS_ObjectExists($childId)) { - continue; - } - - $childName = IPS_GetName($childId); - $newPath = $pathParts; - - if ($childName !== '') { - $newPath[] = $childName; - } - - $sub = $this->CollectEntriesRecursive($childId, $newPath, $visited); - foreach ($sub as $entry) { - $result[] = $entry; - } - } - - return $result; - } - - private function ClassifyEntry(array $entry): ?array - { - $varId = (int)$entry['varId']; - if (!IPS_VariableExists($varId)) { - return null; + return false; } $var = IPS_GetVariable($varId); $type = (int)$var['VariableType']; if ($type !== 1 && $type !== 2) { - return null; - } - - $profileName = ''; - if ($var['VariableCustomProfile'] !== '') { - $profileName = $var['VariableCustomProfile']; - } elseif ($var['VariableProfile'] !== '') { - $profileName = $var['VariableProfile']; - } - - if ($profileName === '' || !IPS_VariableProfileExists($profileName)) { - IPS_LogMessage('CounterClient', 'Variable ohne gültiges Profil ignoriert: ' . $entry['context']); - return null; - } - - $profile = IPS_GetVariableProfile($profileName); - $suffix = trim((string)$profile['Suffix']); - $prefix = trim((string)$profile['Prefix']); - $unit = trim($prefix . ' ' . $suffix); - - if ($unit === '') { - IPS_LogMessage('CounterClient', 'Variable ohne Einheit ignoriert: ' . $entry['context']); - return null; + return false; } $value = GetValue($varId); if (!is_numeric($value)) { - return null; + return false; } $value = round((float)$value, 3); - $context = mb_strtolower($entry['context']); + + $varName = IPS_GetName($varId); + $context = mb_strtolower(trim($contextPrefix . ' ' . $varName)); + $unit = $this->getVariableUnit($varId, $var, $value); $unitNorm = $this->normalizeUnit($unit); + if ($unitNorm === '') { + IPS_LogMessage('CounterClient', 'Keine Einheit erkannt: ' . $context); + return false; + } + if ($this->isTotalUnit($unitNorm, $context)) { - return [ - 'field' => 'total_value', - 'value' => $value, - 'unit' => $this->formatOutputUnit($suffix, $unitNorm), - 'name' => $context, - 'context' => $context - ]; + $counterData['total_value'] = $value; + if (!isset($counterData['unit'])) { + $counterData['unit'] = $this->formatOutputUnit($unitNorm); + } + return true; } if ($this->isPowerUnit($unitNorm, $context)) { - return [ - 'field' => 'power_value', - 'value' => $value, - 'unit' => $unitNorm, - 'name' => $context, - 'context' => $context - ]; + $counterData['power_value'] = $value; + return true; } if ($this->isFlowUnit($unitNorm, $context)) { - return [ - 'field' => 'flow_value', - 'value' => $value, - 'unit' => $unitNorm, - 'name' => $context, - 'context' => $context - ]; + $counterData['flow_value'] = $value; + return true; } if ($unitNorm === 'a') { - return [ - 'field' => 'current_value', - 'value' => $value, - 'unit' => $unitNorm, - 'name' => $context, - 'context' => $context - ]; + $counterData['current_value'] = $value; + return true; } if ($unitNorm === 'v') { - return [ - 'field' => 'voltage_value', - 'value' => $value, - 'unit' => $unitNorm, - 'name' => $context, - 'context' => $context - ]; + $counterData['voltage_value'] = $value; + return true; } if ($unitNorm === 'hz') { - return [ - 'field' => 'frequency_value', - 'value' => $value, - 'unit' => $unitNorm, - 'name' => $context, - 'context' => $context - ]; + $counterData['frequency_value'] = $value; + return true; } if (in_array($unitNorm, ['bar', 'mbar', 'pa'], true)) { - if ($this->looksLikeIn($context)) { - return [ - 'field' => 'pressure_in_value', - 'value' => $value, - 'unit' => $unitNorm, - 'name' => $context, - 'context' => $context - ]; - } - if ($this->looksLikeOut($context)) { - return [ - 'field' => 'pressure_out_value', - 'value' => $value, - 'unit' => $unitNorm, - 'name' => $context, - 'context' => $context - ]; + $counterData['pressure_out_value'] = $value; + } else { + $counterData['pressure_in_value'] = $value; } - - return [ - 'field' => 'pressure_auto', - 'value' => $value, - 'unit' => $unitNorm, - 'name' => $context, - 'context' => $context - ]; + return true; } if (in_array($unitNorm, ['°c', 'c', 'k'], true)) { - if ($this->looksLikeIn($context)) { - return [ - 'field' => 'temperature_in_value', - 'value' => $value, - 'unit' => $unitNorm, - 'name' => $context, - 'context' => $context - ]; - } - if ($this->looksLikeOut($context)) { - return [ - 'field' => 'temperature_out_value', - 'value' => $value, - 'unit' => $unitNorm, - 'name' => $context, - 'context' => $context - ]; + $counterData['temperature_out_value'] = $value; + } else { + $counterData['temperature_in_value'] = $value; } - - return [ - 'field' => 'temperature_auto', - 'value' => $value, - 'unit' => $unitNorm, - 'name' => $context, - 'context' => $context - ]; + return true; } if ($unitNorm === 'cos' || $unitNorm === 'cosphi' || $unitNorm === 'pf') { - return [ - 'field' => 'power_factor', - 'value' => $value, - 'unit' => $unitNorm, - 'name' => $context, - 'context' => $context - ]; + $counterData['power_factor'] = $value; + return true; } - IPS_LogMessage('CounterClient', 'Nicht erkannt: ' . $context . ' | Unit: ' . $unitNorm . ' | Profile: ' . $profileName); - return null; + IPS_LogMessage('CounterClient', 'Nicht zugeordnet: ' . $context . ' | Einheit: ' . $unitNorm . ' | Wert: ' . $value); + return false; } - private function assignInOutValues(array &$counterData, array $items, string $fieldIn, string $fieldOut): void + private function getVariableUnit(int $varId, array $var, float $value): string { - foreach ($items as $item) { - if ($this->looksLikeIn($item['name']) && !isset($counterData[$fieldIn])) { - $counterData[$fieldIn] = $item['value']; - continue; - } - - if ($this->looksLikeOut($item['name']) && !isset($counterData[$fieldOut])) { - $counterData[$fieldOut] = $item['value']; - continue; + if (function_exists('IPS_GetPresentation')) { + try { + $presentation = @IPS_GetPresentation($varId); + if (is_array($presentation)) { + if (isset($presentation['Suffix']) && trim((string)$presentation['Suffix']) !== '') { + return trim((string)$presentation['Suffix']); + } + if (isset($presentation['Prefix']) && trim((string)$presentation['Prefix']) !== '') { + return trim((string)$presentation['Prefix']); + } + if (isset($presentation['Unit']) && trim((string)$presentation['Unit']) !== '') { + return trim((string)$presentation['Unit']); + } + } + } catch (\Throwable $e) { } } - foreach ($items as $item) { - if (!isset($counterData[$fieldIn])) { - $counterData[$fieldIn] = $item['value']; - continue; - } + $profileName = ''; + if (isset($var['VariableCustomProfile']) && $var['VariableCustomProfile'] !== '') { + $profileName = $var['VariableCustomProfile']; + } elseif (isset($var['VariableProfile']) && $var['VariableProfile'] !== '') { + $profileName = $var['VariableProfile']; + } - if (!isset($counterData[$fieldOut])) { - $counterData[$fieldOut] = $item['value']; - continue; + if ($profileName !== '' && IPS_VariableProfileExists($profileName)) { + $profile = IPS_GetVariableProfile($profileName); + $suffix = trim((string)$profile['Suffix']); + $prefix = trim((string)$profile['Prefix']); + $unit = trim($prefix . ' ' . $suffix); + if ($unit !== '') { + return $unit; } } + + $formatted = GetValueFormatted($varId); + return $this->extractUnitFromFormattedValue($formatted, $value); + } + + private function extractUnitFromFormattedValue(string $formatted, float $value): string + { + $formatted = trim($formatted); + if ($formatted === '') { + return ''; + } + + $search = [ + number_format($value, 0, ',', '.'), + number_format($value, 1, ',', '.'), + number_format($value, 2, ',', '.'), + number_format($value, 3, ',', '.'), + str_replace('.', ',', (string)$value), + str_replace(',', '.', (string)$value), + (string)$value + ]; + + $unit = $formatted; + foreach ($search as $needle) { + if ($needle !== '') { + $unit = str_replace($needle, '', $unit); + } + } + + $unit = trim($unit); + $unit = preg_replace('/^[\-\+\d\.,\s]+/u', '', $unit); + return trim($unit); } private function DetectCounterType(array $counterData, string $context): string @@ -459,7 +356,7 @@ class CounterClient extends IPSModule { if (isset($counterData['total_value'])) { if ($counterData['type'] === 'water' || $counterData['type'] === 'gas') { - return 'm3'; + return 'm³'; } return 'kWh'; } @@ -467,39 +364,9 @@ class CounterClient extends IPSModule return ''; } - private function hasUsableMeasurement(array $counterData): bool - { - $fields = [ - 'total_value', - 'consumption_value', - 'power_value', - 'flow_value', - 'current_value', - 'voltage_value', - 'temperature_in_value', - 'temperature_out_value', - 'pressure_in_value', - 'pressure_out_value', - 'frequency_value', - 'power_factor' - ]; - - foreach ($fields as $field) { - if (array_key_exists($field, $counterData)) { - return true; - } - } - - return false; - } - private function isTotalUnit(string $unitNorm, string $context): bool { - if (in_array($unitNorm, ['kwh', 'wh', 'mwh', 'm3', 'l'], true)) { - return true; - } - - if ($unitNorm === 'kw/h' || $unitNorm === 'w/h' || $unitNorm === 'mw/h') { + if (in_array($unitNorm, ['kwh', 'wh', 'mwh', 'm3', 'l', 'kw/h', 'w/h', 'mw/h'], true)) { return true; } @@ -539,25 +406,17 @@ class CounterClient extends IPSModule return false; } - private function formatOutputUnit(string $suffix, string $unitNorm): string + private function formatOutputUnit(string $unitNorm): string { - $suffix = trim($suffix); - if ($suffix !== '') { - return $suffix; - } - if ($unitNorm === 'm3') { return 'm³'; } - if ($unitNorm === 'kwh') { return 'kWh'; } - if ($unitNorm === 'kw/h') { return 'kW/h'; } - return $unitNorm; } @@ -591,9 +450,6 @@ class CounterClient extends IPSModule $unit = mb_strtolower(trim($unit)); $unit = str_replace(["\xc2\xa0", ' '], '', $unit); $unit = str_replace(['m³', '㎥'], 'm3', $unit); - $unit = str_replace(['°'], '°', $unit); - $unit = str_replace(['kw/hh'], 'kw/h', $unit); - $unit = str_replace(['㎾/h'], 'kw/h', $unit); return $unit; } @@ -611,20 +467,6 @@ class CounterClient extends IPSModule return $text; } - private function buildContextString(array $pathParts, string $varName): string - { - $parts = $pathParts; - if ($varName !== '') { - $parts[] = $varName; - } - - $parts = array_values(array_filter($parts, function ($v) { - return trim((string)$v) !== ''; - })); - - return implode(' ', $parts); - } - public function MqttPublish($server_id, $topic, $payload, $retain) { if (!IPS_InstanceExists($server_id)) { @@ -644,7 +486,6 @@ class CounterClient extends IPSModule if ($id === false) { $id = @IPS_CreateInstance($module_id); if ($id === false) { - IPS_SemaphoreLeave($ident); return false; } IPS_SetParent($id, $this->InstanceID); @@ -652,7 +493,6 @@ class CounterClient extends IPSModule } if (!IPS_IsInstanceCompatible($id, $server_id)) { - IPS_SemaphoreLeave($ident); return false; } @@ -660,7 +500,6 @@ class CounterClient extends IPSModule if ((int)$inst_config['ConnectionID'] !== (int)$server_id) { IPS_DisconnectInstance($id); if (!@IPS_ConnectInstance($id, $server_id)) { - IPS_SemaphoreLeave($ident); return false; } } @@ -679,7 +518,6 @@ class CounterClient extends IPSModule $var_id = @IPS_GetObjectIDByIdent('Value', $id); if ($var_id === false) { - IPS_SemaphoreLeave($ident); return false; } diff --git a/CounterServer/module.php b/CounterServer/module.php index cd1f436..a5ef0ef 100644 --- a/CounterServer/module.php +++ b/CounterServer/module.php @@ -1,21 +1,17 @@ RegisterPropertyInteger('CounterCategoryID', 0); - $this->RegisterPropertyInteger('JsonOutputVariableID', 0); + $this->RegisterPropertyInteger('Connector', 0); + $this->RegisterPropertyInteger('SourceJSON', 0); $this->RegisterPropertyInteger('UpdateTime', 60); - $this->RegisterPropertyBoolean('EnableMQTT', false); - $this->RegisterPropertyInteger('MqttClientID', 0); - $this->RegisterPropertyString('Projectname', ''); - $this->RegisterPropertyInteger('Projectyear', 2026); - $this->RegisterPropertyInteger('Projectnumber', 0); - - $this->RegisterTimer('Update', 0, 'SECC_BuildAndStorePayload(' . $this->InstanceID . ');'); + $this->RegisterPropertyString('Projectname', 60); + $this->RegisterPropertyInteger('ProjectIdPortal', -1); + $this->RegisterTimer('Update', 0, 'SECS_checkTable('.$this->InstanceID.');'); } public function ApplyChanges() @@ -28,505 +24,148 @@ class CounterClient extends IPSModule } $this->SetTimerInterval('Update', $updateTime * 1000); + $this->checkTable(); } - public function BuildAndStorePayload() + public function checkTable() { - $json = $this->BuildPayload(); - if ($json === '') { + $sqlId = $this->ReadPropertyInteger('Connector'); + $jsonVar = $this->ReadPropertyInteger('SourceJSON'); + + if ($sqlId <= 0 || $jsonVar <= 0) { + IPS_LogMessage('CounterTable', 'Config fehlt.'); return; } - $targetVarId = $this->ReadPropertyInteger('JsonOutputVariableID'); - if ($targetVarId > 0 && IPS_VariableExists($targetVarId)) { - SetValueString($targetVarId, $json); + $jsonString = GetValueString($jsonVar); + if ($jsonString === '') { + return; } - IPS_LogMessage('CounterClient', $json); - - if ($this->ReadPropertyBoolean('EnableMQTT')) { - $mqttClientId = $this->ReadPropertyInteger('MqttClientID'); - $mqttTopic = 'Projekte' . $this->ReadPropertyInteger('Projectyear') . '/P' . $this->ReadPropertyInteger('Projectnumber') . '/Counters'; - - if ($mqttClientId > 0 && IPS_InstanceExists($mqttClientId) && $mqttTopic !== '') { - $this->MqttPublish($mqttClientId, $mqttTopic, $json, true); - } - } - } - - public function BuildPayload() - { - $counterCategoryId = $this->ReadPropertyInteger('CounterCategoryID'); - if ($counterCategoryId <= 0 || !IPS_ObjectExists($counterCategoryId)) { - IPS_LogMessage('CounterClient', 'Ungültige CounterCategoryID'); - return ''; + $payload = json_decode($jsonString, true); + if (!is_array($payload)) { + IPS_LogMessage('CounterTable', 'JSON Fehler'); + return; } - $payload = [ - 'timestamp' => date('Y-m-d H:i:s'), - 'counters' => [] - ]; + $clientId = (string)$this->InstanceID; + $clientName = IPS_GetName($this->InstanceID); + $projectName = $this->ReadPropertyString('Projectname'); + $timestamp = $payload['timestamp'] ?? date('Y-m-d H:i:s'); + $counters = $payload['counters'] ?? []; + $projectId = $this->ReadPropertyInteger('ProjectIdPortal'); - $counterFolders = IPS_GetChildrenIDs($counterCategoryId); + if ($projectId !== -1) { + foreach ($counters as $c) { - foreach ($counterFolders as $counterFolderId) { - if (!IPS_ObjectExists($counterFolderId)) { + $counterId = $c['id'] ?? ''; + if ($counterId === '') continue; + + $counterName = $c['name'] ?? $counterId; + $type = $c['type'] ?? 'other'; + $unit = $c['unit'] ?? 'kWh'; + + + // ---------- UPSERT ---------- + $sql = " + INSERT INTO meter_devices + (project_id, counter_id, meter_uuid, external_id, external_name, meter_type, billing_unit, is_active) + VALUES + ( + '" . $this->esc($projectId) . "', + '" . $this->esc($counterId) . "', + '" . hash('sha256', $projectId.$clientId . '_' . $counterId) . "', + '" . $this->esc($clientId) . "', + '" . $this->esc($projectName.'_'. $counterId) . "', + '" . $this->esc($type) . "', + '" . $this->esc($unit) . "', + 1 + ) + ON DUPLICATE KEY UPDATE + meter_name = VALUES(meter_name), + meter_type = VALUES(meter_type), + billing_unit = VALUES(billing_unit), + is_active = 1 + "; + + + MySQL_ExecuteSimple($sqlId, $sql); + + + // ---------- GET meter_id ---------- + $sqlGet = " + SELECT id FROM meter_devices + WHERE meter_uuid = '" . hash('sha256', $projectId.$clientId . '_' . $counterId) . "' + LIMIT 1 + "; + + $res = MySQL_ExecuteSimple($sqlId, $sqlGet); + + if (!is_array($res) || count($res) === 0) { + IPS_LogMessage('CounterTable', 'Kein meter_id gefunden'); continue; } - $obj = IPS_GetObject($counterFolderId); - if ((int)$obj['ObjectType'] !== 0) { - continue; - } + $meterId = $res[0]->id; - $counterName = IPS_GetName($counterFolderId); - $counterId = $this->makeSlug($counterName); + // ---------- INSERT measurement ---------- + $sqlInsert = " + INSERT INTO meter_measurements + ( + meter_id, measured_at, + total_value, consumption_value, + power_value, flow_value, + current_value, voltage_value, + temperature_in_value, temperature_out_value, + pressure_in_value, pressure_out_value, + frequency_value, power_factor + ) + VALUES + ( + " . $meterId . ", + '" . $this->esc($timestamp) . "', + " . $this->f($c, 'total_value') . ", + " . $this->f($c, 'consumption_value') . ", + " . $this->f($c, 'power_value') . ", + " . $this->f($c, 'flow_value') . ", + " . $this->f($c, 'current_value') . ", + " . $this->f($c, 'voltage_value') . ", + " . $this->f($c, 'temperature_in_value') . ", + " . $this->f($c, 'temperature_out_value') . ", + " . $this->f($c, 'pressure_in_value') . ", + " . $this->f($c, 'pressure_out_value') . ", + " . $this->f($c, 'frequency_value') . ", + " . $this->f($c, 'power_factor') . " + ) + "; - $counterData = [ - 'id' => $counterId, - 'name' => $counterName - ]; - - $found = 0; - $children = IPS_GetChildrenIDs($counterFolderId); - - foreach ($children as $childId) { - if (!IPS_ObjectExists($childId)) { - continue; - } - - if (IPS_LinkExists($childId)) { - $link = IPS_GetLink($childId); - $targetId = (int)$link['TargetID']; - $linkName = IPS_GetName($childId); - - if ($targetId <= 0 || !IPS_ObjectExists($targetId)) { - continue; - } - - if (IPS_VariableExists($targetId)) { - if ($this->addVariableToCounter($counterData, $targetId, $counterName . ' ' . $linkName)) { - $found++; - } - } else { - $subChildren = IPS_GetChildrenIDs($targetId); - foreach ($subChildren as $subChildId) { - if (IPS_VariableExists($subChildId)) { - if ($this->addVariableToCounter($counterData, $subChildId, $counterName . ' ' . $linkName . ' ' . IPS_GetName($targetId))) { - $found++; - } - } - } - } - - continue; - } - - if (IPS_VariableExists($childId)) { - if ($this->addVariableToCounter($counterData, $childId, $counterName)) { - $found++; - } - continue; - } - - $subChildren = IPS_GetChildrenIDs($childId); - foreach ($subChildren as $subChildId) { - if (IPS_VariableExists($subChildId)) { - if ($this->addVariableToCounter($counterData, $subChildId, $counterName . ' ' . IPS_GetName($childId))) { - $found++; - } - } - } - } - - $counterData['type'] = $this->DetectCounterType($counterData, $counterName); - - if (!isset($counterData['unit'])) { - $counterData['unit'] = $this->inferDefaultUnit($counterData); - } - - if ($found > 0) { - $payload['counters'][] = $counterData; - } else { - IPS_LogMessage('CounterClient', 'Counter ohne erkannte Messwerte übersprungen: ' . $counterName); - } + MySQL_ExecuteSimple($sqlId, $sqlInsert); + } + } else { + IPS_LogMessage('CounterTable', 'Keine Project-ID für Portal angegeben.'); } - - return json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); } - private function addVariableToCounter(array &$counterData, int $varId, string $contextPrefix): bool + private function f($arr, $key) { - if (!IPS_VariableExists($varId)) { - return false; + if (!isset($arr[$key]) || $arr[$key] === '' || $arr[$key] === null) { + return 'NULL'; } - $var = IPS_GetVariable($varId); - $type = (int)$var['VariableType']; - - if ($type !== 1 && $type !== 2) { - return false; + if (is_numeric($arr[$key])) { + return str_replace(',', '.', (string)$arr[$key]); } - $value = GetValue($varId); - if (!is_numeric($value)) { - return false; - } - - $value = round((float)$value, 3); - - $varName = IPS_GetName($varId); - $context = mb_strtolower(trim($contextPrefix . ' ' . $varName)); - $unit = $this->getVariableUnit($varId, $var, $value); - $unitNorm = $this->normalizeUnit($unit); - - if ($unitNorm === '') { - IPS_LogMessage('CounterClient', 'Keine Einheit erkannt: ' . $context); - return false; - } - - if ($this->isTotalUnit($unitNorm, $context)) { - $counterData['total_value'] = $value; - if (!isset($counterData['unit'])) { - $counterData['unit'] = $this->formatOutputUnit($unitNorm); - } - return true; - } - - if ($this->isPowerUnit($unitNorm, $context)) { - $counterData['power_value'] = $value; - return true; - } - - if ($this->isFlowUnit($unitNorm, $context)) { - $counterData['flow_value'] = $value; - return true; - } - - if ($unitNorm === 'a') { - $counterData['current_value'] = $value; - return true; - } - - if ($unitNorm === 'v') { - $counterData['voltage_value'] = $value; - return true; - } - - if ($unitNorm === 'hz') { - $counterData['frequency_value'] = $value; - return true; - } - - if (in_array($unitNorm, ['bar', 'mbar', 'pa'], true)) { - if ($this->looksLikeOut($context)) { - $counterData['pressure_out_value'] = $value; - } else { - $counterData['pressure_in_value'] = $value; - } - return true; - } - - if (in_array($unitNorm, ['°c', 'c', 'k'], true)) { - if ($this->looksLikeOut($context)) { - $counterData['temperature_out_value'] = $value; - } else { - $counterData['temperature_in_value'] = $value; - } - return true; - } - - if ($unitNorm === 'cos' || $unitNorm === 'cosphi' || $unitNorm === 'pf') { - $counterData['power_factor'] = $value; - return true; - } - - IPS_LogMessage('CounterClient', 'Nicht zugeordnet: ' . $context . ' | Einheit: ' . $unitNorm . ' | Wert: ' . $value); - return false; + return 'NULL'; } - private function getVariableUnit(int $varId, array $var, float $value): string + private function esc($v) { - if (function_exists('IPS_GetPresentation')) { - try { - $presentation = @IPS_GetPresentation($varId); - if (is_array($presentation)) { - if (isset($presentation['Suffix']) && trim((string)$presentation['Suffix']) !== '') { - return trim((string)$presentation['Suffix']); - } - if (isset($presentation['Prefix']) && trim((string)$presentation['Prefix']) !== '') { - return trim((string)$presentation['Prefix']); - } - if (isset($presentation['Unit']) && trim((string)$presentation['Unit']) !== '') { - return trim((string)$presentation['Unit']); - } - } - } catch (\Throwable $e) { - } - } - - $profileName = ''; - if (isset($var['VariableCustomProfile']) && $var['VariableCustomProfile'] !== '') { - $profileName = $var['VariableCustomProfile']; - } elseif (isset($var['VariableProfile']) && $var['VariableProfile'] !== '') { - $profileName = $var['VariableProfile']; - } - - if ($profileName !== '' && IPS_VariableProfileExists($profileName)) { - $profile = IPS_GetVariableProfile($profileName); - $suffix = trim((string)$profile['Suffix']); - $prefix = trim((string)$profile['Prefix']); - $unit = trim($prefix . ' ' . $suffix); - if ($unit !== '') { - return $unit; - } - } - - $formatted = GetValueFormatted($varId); - return $this->extractUnitFromFormattedValue($formatted, $value); - } - - private function extractUnitFromFormattedValue(string $formatted, float $value): string - { - $formatted = trim($formatted); - if ($formatted === '') { - return ''; - } - - $search = [ - number_format($value, 0, ',', '.'), - number_format($value, 1, ',', '.'), - number_format($value, 2, ',', '.'), - number_format($value, 3, ',', '.'), - str_replace('.', ',', (string)$value), - str_replace(',', '.', (string)$value), - (string)$value - ]; - - $unit = $formatted; - foreach ($search as $needle) { - if ($needle !== '') { - $unit = str_replace($needle, '', $unit); - } - } - - $unit = trim($unit); - $unit = preg_replace('/^[\-\+\d\.,\s]+/u', '', $unit); - return trim($unit); - } - - private function DetectCounterType(array $counterData, string $context): string - { - $unit = isset($counterData['unit']) ? $this->normalizeUnit((string)$counterData['unit']) : ''; - $ctx = mb_strtolower($context); - - if (isset($counterData['voltage_value']) || isset($counterData['current_value']) || isset($counterData['frequency_value']) || isset($counterData['power_factor'])) { - return 'electricity'; - } - - if (isset($counterData['temperature_in_value']) || isset($counterData['temperature_out_value'])) { - if (strpos($ctx, 'kalt') !== false || strpos($ctx, 'cool') !== false || strpos($ctx, 'kaelte') !== false || strpos($ctx, 'kälte') !== false) { - return 'cooling'; - } - return 'heat'; - } - - if ($unit === 'm3' || $unit === 'l') { - if (strpos($ctx, 'gas') !== false) { - return 'gas'; - } - return 'water'; - } - - if ($unit === 'kwh' || $unit === 'kw/h' || $unit === 'mwh' || $unit === 'wh') { - if (isset($counterData['flow_value']) || strpos($ctx, 'waerme') !== false || strpos($ctx, 'wärme') !== false || strpos($ctx, 'warmwasser') !== false || strpos($ctx, 'heizung') !== false) { - return 'heat'; - } - return 'electricity'; - } - - if (strpos($ctx, 'gas') !== false) { - return 'gas'; - } - - return 'other'; - } - - private function inferDefaultUnit(array $counterData): string - { - if (isset($counterData['total_value'])) { - if ($counterData['type'] === 'water' || $counterData['type'] === 'gas') { - return 'm³'; - } - return 'kWh'; - } - - return ''; - } - - private function isTotalUnit(string $unitNorm, string $context): bool - { - if (in_array($unitNorm, ['kwh', 'wh', 'mwh', 'm3', 'l', 'kw/h', 'w/h', 'mw/h'], true)) { - return true; - } - - if (strpos($context, 'energy') !== false && in_array($unitNorm, ['kw', 'w', 'mw'], true)) { - return true; - } - - if (strpos($context, 'volume') !== false && in_array($unitNorm, ['m3', 'l'], true)) { - return true; - } - - return false; - } - - private function isPowerUnit(string $unitNorm, string $context): bool - { - if (in_array($unitNorm, ['kw', 'w', 'mw'], true)) { - if (strpos($context, 'energy') !== false) { - return false; - } - return true; - } - - return false; - } - - private function isFlowUnit(string $unitNorm, string $context): bool - { - if (in_array($unitNorm, ['m3/h', 'l/h', 'l/min'], true)) { - return true; - } - - if (strpos($context, 'flow') !== false && in_array($unitNorm, ['m3', 'l'], true)) { - return true; - } - - return false; - } - - private function formatOutputUnit(string $unitNorm): string - { - if ($unitNorm === 'm3') { - return 'm³'; - } - if ($unitNorm === 'kwh') { - return 'kWh'; - } - if ($unitNorm === 'kw/h') { - return 'kW/h'; - } - return $unitNorm; - } - - private function looksLikeIn(string $name): bool - { - $name = mb_strtolower($name); - - return strpos($name, 'vorlauf') !== false - || strpos($name, 'vl') !== false - || strpos($name, 'flow') !== false - || strpos($name, 'ein') !== false - || strpos($name, 'iv') !== false - || strpos($name, 'inlet') !== false; - } - - private function looksLikeOut(string $name): bool - { - $name = mb_strtolower($name); - - return strpos($name, 'ruecklauf') !== false - || strpos($name, 'rücklauf') !== false - || strpos($name, 'rl') !== false - || strpos($name, 'return') !== false - || strpos($name, 'aus') !== false - || strpos($name, 'ri') !== false - || strpos($name, 'outlet') !== false; - } - - private function normalizeUnit(string $unit): string - { - $unit = mb_strtolower(trim($unit)); - $unit = str_replace(["\xc2\xa0", ' '], '', $unit); - $unit = str_replace(['m³', '㎥'], 'm3', $unit); - return $unit; - } - - private function makeSlug(string $text): string - { - $text = mb_strtolower($text); - $text = str_replace(['ä', 'ö', 'ü', 'ß'], ['ae', 'oe', 'ue', 'ss'], $text); - $text = preg_replace('/[^a-z0-9]+/u', '_', $text); - $text = trim($text, '_'); - - if ($text === '') { - $text = 'counter_' . time(); - } - - return $text; - } - - public function MqttPublish($server_id, $topic, $payload, $retain) - { - if (!IPS_InstanceExists($server_id)) { - return false; - } - - $ips_var_type = 3; - $module_id = '{01C00ADD-D04E-452E-B66A-D253278743FE}'; - $ident = 'TempMQTTDevice_' . $this->InstanceID; - - if (!IPS_SemaphoreEnter($ident, 100)) { - return false; - } - - try { - $id = @IPS_GetObjectIDByIdent($ident, $this->InstanceID); - if ($id === false) { - $id = @IPS_CreateInstance($module_id); - if ($id === false) { - return false; - } - IPS_SetParent($id, $this->InstanceID); - IPS_SetIdent($id, $ident); - } - - if (!IPS_IsInstanceCompatible($id, $server_id)) { - return false; - } - - $inst_config = IPS_GetInstance($id); - if ((int)$inst_config['ConnectionID'] !== (int)$server_id) { - IPS_DisconnectInstance($id); - if (!@IPS_ConnectInstance($id, $server_id)) { - return false; - } - } - - IPS_SetName($id, 'Temporary MQTT Device for: ' . $topic); - - $config_arr = [ - 'Retain' => $retain, - 'Topic' => $topic, - 'Type' => $ips_var_type - ]; - - IPS_SetConfiguration($id, json_encode($config_arr)); - IPS_SetHidden($id, true); - IPS_ApplyChanges($id); - - $var_id = @IPS_GetObjectIDByIdent('Value', $id); - if ($var_id === false) { - return false; - } - - RequestAction($var_id, $payload); - } finally { - IPS_SemaphoreLeave($ident); - } - - return true; + return str_replace( + ['\\', "'", '"'], + ['\\\\', "\\'", '\\"'], + $v + ); } } ?> \ No newline at end of file