@@ -0,0 +1,243 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace {
|
||||
class IPSModule
|
||||
{
|
||||
protected int $InstanceID;
|
||||
private array $properties = [];
|
||||
|
||||
public function __construct(int $instanceID = 1)
|
||||
{
|
||||
$this->InstanceID = $instanceID;
|
||||
$GLOBALS['ipsObjects'][$instanceID] = ['parent' => 0, 'ident' => '', 'name' => 'Modul'];
|
||||
$this->Create();
|
||||
}
|
||||
|
||||
public function Create()
|
||||
{
|
||||
}
|
||||
|
||||
protected function RegisterPropertyBoolean(string $name, bool $value): void
|
||||
{
|
||||
$this->properties[$name] = $value;
|
||||
}
|
||||
|
||||
protected function RegisterPropertyString(string $name, string $value): void
|
||||
{
|
||||
$this->properties[$name] = $value;
|
||||
}
|
||||
|
||||
protected function ReadPropertyBoolean(string $name): bool
|
||||
{
|
||||
return (bool) $this->properties[$name];
|
||||
}
|
||||
|
||||
protected function ReadPropertyString(string $name): string
|
||||
{
|
||||
return (string) $this->properties[$name];
|
||||
}
|
||||
|
||||
protected function ConnectParent(string $moduleID): void
|
||||
{
|
||||
}
|
||||
|
||||
protected function SendDataToParent(string $packet): void
|
||||
{
|
||||
$GLOBALS['sentPackets'][] = $packet;
|
||||
}
|
||||
|
||||
protected function SendDebug(string $title, string $message, int $format): void
|
||||
{
|
||||
}
|
||||
|
||||
public function setTestProperty(string $name, $value): void
|
||||
{
|
||||
$this->properties[$name] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
function IPS_GetObjectIDByIdent(string $ident, int $parentID)
|
||||
{
|
||||
foreach ($GLOBALS['ipsObjects'] as $id => $object) {
|
||||
if ($object['parent'] === $parentID && $object['ident'] === $ident) {
|
||||
return $id;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function IPS_CreateCategory(): int
|
||||
{
|
||||
return ipsCreateObject('category');
|
||||
}
|
||||
|
||||
function IPS_CreateVariable(int $type): int
|
||||
{
|
||||
$id = ipsCreateObject('variable');
|
||||
$GLOBALS['ipsVariables'][$id] = ['VariableType' => $type];
|
||||
return $id;
|
||||
}
|
||||
|
||||
function IPS_CreateScript(int $type): int
|
||||
{
|
||||
return ipsCreateObject('script');
|
||||
}
|
||||
|
||||
function ipsCreateObject(string $type): int
|
||||
{
|
||||
$id = ++$GLOBALS['ipsNextID'];
|
||||
$GLOBALS['ipsObjects'][$id] = [
|
||||
'parent' => 0,
|
||||
'ident' => '',
|
||||
'name' => '',
|
||||
'type' => $type,
|
||||
'profile' => '',
|
||||
];
|
||||
return $id;
|
||||
}
|
||||
|
||||
function IPS_SetParent(int $id, int $parentID): void
|
||||
{
|
||||
$GLOBALS['ipsObjects'][$id]['parent'] = $parentID;
|
||||
}
|
||||
|
||||
function IPS_SetIdent(int $id, string $ident): void
|
||||
{
|
||||
$GLOBALS['ipsObjects'][$id]['ident'] = $ident;
|
||||
}
|
||||
|
||||
function IPS_SetName(int $id, string $name): void
|
||||
{
|
||||
$GLOBALS['ipsObjects'][$id]['name'] = $name;
|
||||
}
|
||||
|
||||
function IPS_SetInfo(int $id, string $info): void
|
||||
{
|
||||
}
|
||||
|
||||
function IPS_SetPosition(int $id, int $position): void
|
||||
{
|
||||
}
|
||||
|
||||
function IPS_VariableProfileExists(string $profile): bool
|
||||
{
|
||||
return in_array($profile, ['~Switch', '~Temperature', '~Humidity'], true);
|
||||
}
|
||||
|
||||
function IPS_SetVariableCustomProfile(int $id, string $profile): void
|
||||
{
|
||||
$GLOBALS['ipsObjects'][$id]['profile'] = $profile;
|
||||
}
|
||||
|
||||
function IPS_SetVariableCustomAction(int $id, int $scriptID): void
|
||||
{
|
||||
}
|
||||
|
||||
function IPS_SetHidden(int $id, bool $hidden): void
|
||||
{
|
||||
}
|
||||
|
||||
function IPS_SetScriptContent(int $id, string $content): void
|
||||
{
|
||||
}
|
||||
|
||||
function IPS_VariableExists(int $id): bool
|
||||
{
|
||||
return isset($GLOBALS['ipsVariables'][$id]);
|
||||
}
|
||||
|
||||
function IPS_ScriptExists(int $id): bool
|
||||
{
|
||||
return ($GLOBALS['ipsObjects'][$id]['type'] ?? '') === 'script';
|
||||
}
|
||||
|
||||
function IPS_GetVariable(int $id): array
|
||||
{
|
||||
return $GLOBALS['ipsVariables'][$id];
|
||||
}
|
||||
|
||||
function SetValue(int $id, $value): void
|
||||
{
|
||||
$GLOBALS['ipsValues'][$id] = $value;
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../ShellyModul/module.php';
|
||||
}
|
||||
|
||||
namespace Belevo\EnelixUtils\Tests {
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class ShellyModulTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
$GLOBALS['ipsNextID'] = 100;
|
||||
$GLOBALS['ipsObjects'] = [];
|
||||
$GLOBALS['ipsVariables'] = [];
|
||||
$GLOBALS['ipsValues'] = [];
|
||||
$GLOBALS['sentPackets'] = [];
|
||||
}
|
||||
|
||||
public function testCustomNestedPrefixAndUnknownSourceAreAccepted(): void
|
||||
{
|
||||
$module = new \ShellyModul(1);
|
||||
$module->ReceiveData(json_encode([
|
||||
'PacketType' => 3,
|
||||
'Topic' => 'gebaeude/wohnung/symcon/events/rpc',
|
||||
'Payload' => json_encode([
|
||||
'src' => 'custom-client-name',
|
||||
'method' => 'NotifyStatus',
|
||||
'params' => [
|
||||
'switch:0' => ['output' => true, 'apower' => 12.5],
|
||||
'cover:0' => ['current_pos' => 37],
|
||||
'future:0' => ['new_value' => 99],
|
||||
],
|
||||
], JSON_THROW_ON_ERROR),
|
||||
], JSON_THROW_ON_ERROR));
|
||||
|
||||
$names = array_column($GLOBALS['ipsObjects'], 'name');
|
||||
self::assertContains('gebaeude/wohnung/symcon', $names);
|
||||
self::assertContains('Output 0', $names);
|
||||
self::assertContains('Switch 0 - Wirkleistung', $names);
|
||||
self::assertContains('Cover 0 - Position', $names);
|
||||
self::assertNotContains('Future 0 - New value', $names);
|
||||
self::assertContains(true, $GLOBALS['ipsValues']);
|
||||
self::assertContains(12.5, $GLOBALS['ipsValues']);
|
||||
}
|
||||
|
||||
public function testComponentStatusUsesEnvironmentProfile(): void
|
||||
{
|
||||
$module = new \ShellyModul(1);
|
||||
$module->ReceiveData(json_encode([
|
||||
'PacketType' => 3,
|
||||
'Topic' => 'symcon/status/humidity:0',
|
||||
'Payload' => '{"id":0,"rh":54.3}',
|
||||
], JSON_THROW_ON_ERROR));
|
||||
|
||||
$humidity = array_filter(
|
||||
$GLOBALS['ipsObjects'],
|
||||
static fn (array $object): bool => $object['name'] === 'Humidity 0 - Luftfeuchtigkeit'
|
||||
);
|
||||
self::assertCount(1, $humidity);
|
||||
self::assertSame('~Humidity', array_values($humidity)[0]['profile']);
|
||||
self::assertContains(54.3, $GLOBALS['ipsValues']);
|
||||
}
|
||||
|
||||
public function testOptionalTopicFilterCanRejectOtherPrefixes(): void
|
||||
{
|
||||
$module = new \ShellyModul(1);
|
||||
$module->setTestProperty('UseDeviceTopicFilter', true);
|
||||
$module->setTestProperty('DeviceTopicPrefix', 'shelly');
|
||||
$module->ReceiveData(json_encode([
|
||||
'PacketType' => 3,
|
||||
'Topic' => 'symcon/online',
|
||||
'Payload' => 'true',
|
||||
], JSON_THROW_ON_ERROR));
|
||||
|
||||
self::assertCount(1, $GLOBALS['ipsObjects']);
|
||||
self::assertSame([], $GLOBALS['ipsValues']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Belevo\EnelixUtils\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
require_once __DIR__ . '/../ShellyModul/libs/ShellyParser.php';
|
||||
|
||||
final class ShellyParserTest extends TestCase
|
||||
{
|
||||
public function testCustomAndNestedTopicPrefixesAreRecognized(): void
|
||||
{
|
||||
self::assertSame(
|
||||
['deviceTopic' => 'symcon', 'kind' => 'rpc', 'component' => null],
|
||||
\ShellyParser::parseTopic('symcon/events/rpc')
|
||||
);
|
||||
self::assertSame(
|
||||
['deviceTopic' => 'building/floor/device-1', 'kind' => 'component_status', 'component' => 'pm1:0'],
|
||||
\ShellyParser::parseTopic('building/floor/device-1/status/pm1:0')
|
||||
);
|
||||
}
|
||||
|
||||
public function testNotifyStatusIsFlattenedWithoutKnowingTheDeviceModel(): void
|
||||
{
|
||||
$points = \ShellyParser::pointsFromRpc([
|
||||
'src' => 'custom-source',
|
||||
'method' => 'NotifyStatus',
|
||||
'params' => [
|
||||
'switch:0' => [
|
||||
'output' => true,
|
||||
'apower' => 12.4,
|
||||
'aenergy' => ['total' => 42.5, 'by_minute' => [1, 2, 3]],
|
||||
],
|
||||
'humidity:0' => ['rh' => 56.2],
|
||||
],
|
||||
]);
|
||||
|
||||
self::assertContains(['component' => 'switch:0', 'path' => 'output', 'value' => true], $points);
|
||||
self::assertContains(['component' => 'switch:0', 'path' => 'apower', 'value' => 12.4], $points);
|
||||
self::assertContains(['component' => 'switch:0', 'path' => 'aenergy.total', 'value' => 42.5], $points);
|
||||
self::assertContains(['component' => 'switch:0', 'path' => 'aenergy.by_minute', 'value' => '[1,2,3]'], $points);
|
||||
self::assertContains(['component' => 'humidity:0', 'path' => 'rh', 'value' => 56.2], $points);
|
||||
}
|
||||
|
||||
public function testGroupsSeparateOutputsMetersAndSensors(): void
|
||||
{
|
||||
self::assertSame('switches', \ShellyParser::groupForPoint('switch:0', 'output'));
|
||||
self::assertSame('power', \ShellyParser::groupForPoint('switch:0', 'apower'));
|
||||
self::assertSame('energy', \ShellyParser::groupForPoint('emdata:0', 'a_total_act_energy'));
|
||||
self::assertSame('humidity', \ShellyParser::groupForPoint('humidity:0', 'rh'));
|
||||
self::assertSame('covers', \ShellyParser::groupForPoint('cover:0', 'current_pos'));
|
||||
self::assertSame('other', \ShellyParser::groupForPoint('future:7', 'new_value'));
|
||||
}
|
||||
|
||||
public function testNotifyEventsAreMappedToTheirComponent(): void
|
||||
{
|
||||
$points = \ShellyParser::pointsFromRpc([
|
||||
'method' => 'NotifyEvent',
|
||||
'params' => [
|
||||
'events' => [[
|
||||
'component' => 'input:0',
|
||||
'event' => 'single_push',
|
||||
'ts' => 123.5,
|
||||
]],
|
||||
],
|
||||
]);
|
||||
|
||||
self::assertContains(
|
||||
['component' => 'input:0', 'path' => 'event.event', 'value' => 'single_push'],
|
||||
$points
|
||||
);
|
||||
}
|
||||
|
||||
public function testNumericMeasurementsUseFloatVariables(): void
|
||||
{
|
||||
self::assertSame(2, \ShellyParser::variableType(0, 'apower'));
|
||||
self::assertSame(1, \ShellyParser::variableType(123, 'uptime'));
|
||||
self::assertSame(1, \ShellyParser::variableType(123.5, 'ts'));
|
||||
self::assertSame(0, \ShellyParser::variableType(true, 'output'));
|
||||
self::assertSame(3, \ShellyParser::variableType('open', 'state'));
|
||||
}
|
||||
|
||||
public function testConfigurationOffersAllDataPointGroups(): void
|
||||
{
|
||||
$json = file_get_contents(__DIR__ . '/../ShellyModul/form.json');
|
||||
self::assertNotFalse($json);
|
||||
$form = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
|
||||
$encoded = json_encode($form, JSON_THROW_ON_ERROR);
|
||||
|
||||
foreach ([
|
||||
'CreateOnline',
|
||||
'CreateDeviceInfo',
|
||||
'CreateInputs',
|
||||
'CreateSwitches',
|
||||
'CreateCovers',
|
||||
'CreateLights',
|
||||
'CreatePower',
|
||||
'CreateVoltage',
|
||||
'CreateCurrent',
|
||||
'CreateEnergy',
|
||||
'CreateFrequency',
|
||||
'CreatePowerFactor',
|
||||
'CreateMeterDetails',
|
||||
'CreateTemperature',
|
||||
'CreateHumidity',
|
||||
'CreateIlluminance',
|
||||
'CreateBattery',
|
||||
'CreateEnvironment',
|
||||
'CreateSystem',
|
||||
'CreateOther',
|
||||
] as $property) {
|
||||
self::assertStringContainsString($property, $encoded);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user