From f7d85562d17901ffa13060245f02e7c3c32c7274 Mon Sep 17 00:00:00 2001 From: Fabian Wolter Date: Wed, 17 Jun 2020 21:20:29 +0200 Subject: [PATCH] [lcn] Add LCN binding (#7509) * [lcn] Add LCN binding Migrates the Local Control Network Binding from OH1 to OH2. Closes #108 Signed-off-by: Fabian Wolter --- CODEOWNERS | 1 + bom/openhab-addons/pom.xml | 5 + bundles/org.openhab.binding.lcn/.classpath | 32 + bundles/org.openhab.binding.lcn/.project | 23 + bundles/org.openhab.binding.lcn/NOTICE | 13 + bundles/org.openhab.binding.lcn/README.md | 594 +++++++++++++ .../doc/LCN-PRO_output_steps.png | Bin 0 -> 200517 bytes .../org.openhab.binding.lcn/doc/dyn_text.png | Bin 0 -> 106065 bytes bundles/org.openhab.binding.lcn/doc/ir.png | Bin 0 -> 110156 bytes .../org.openhab.binding.lcn/doc/overview.jpg | Bin 0 -> 72598 bytes bundles/org.openhab.binding.lcn/pom.xml | 17 + .../src/main/feature/feature.xml | 9 + .../lcn/internal/DimmerOutputProfile.java | 119 +++ .../lcn/internal/ILcnModuleActions.java | 31 + .../lcn/internal/LcnBindingConstants.java | 43 + .../LcnChannelVariableConfiguration.java | 26 + .../lcn/internal/LcnGroupConfiguration.java | 25 + .../binding/lcn/internal/LcnGroupHandler.java | 55 ++ .../lcn/internal/LcnHandlerFactory.java | 66 ++ .../lcn/internal/LcnModuleActions.java | 202 +++++ .../lcn/internal/LcnModuleConfiguration.java | 26 + .../internal/LcnModuleDiscoveryService.java | 264 ++++++ .../lcn/internal/LcnModuleHandler.java | 363 ++++++++ .../lcn/internal/LcnProfileFactory.java | 71 ++ .../lcn/internal/PckGatewayConfiguration.java | 54 ++ .../lcn/internal/PckGatewayHandler.java | 303 +++++++ .../internal/common/DimmerOutputCommand.java | 65 ++ .../binding/lcn/internal/common/LcnAddr.java | 79 ++ .../lcn/internal/common/LcnAddrGrp.java | 102 +++ .../lcn/internal/common/LcnAddrMod.java | 102 +++ .../lcn/internal/common/LcnChannelGroup.java | 122 +++ .../binding/lcn/internal/common/LcnDefs.java | 156 ++++ .../lcn/internal/common/LcnException.java | 37 + .../lcn/internal/common/PckGenerator.java | 780 ++++++++++++++++++ .../lcn/internal/common/ReverseNumber.java | 53 ++ .../binding/lcn/internal/common/Variable.java | 278 +++++++ .../lcn/internal/common/VariableValue.java | 99 +++ .../connection/AbstractConnectionState.java | 100 +++ ...bstractConnectionStateSendCredentials.java | 48 ++ .../internal/connection/AbstractState.java | 76 ++ .../connection/AbstractStateMachine.java | 64 ++ .../lcn/internal/connection/Connection.java | 474 +++++++++++ .../connection/ConnectionCallback.java | 44 + .../connection/ConnectionSettings.java | 158 ++++ .../connection/ConnectionStateConnected.java | 58 ++ .../connection/ConnectionStateConnecting.java | 97 +++ ...ectionStateGracePeriodBeforeReconnect.java | 45 + .../connection/ConnectionStateInit.java | 37 + .../connection/ConnectionStateMachine.java | 107 +++ .../ConnectionStateSegmentScan.java | 82 ++ .../ConnectionStateSendDimMode.java | 41 + .../ConnectionStateSendPassword.java | 41 + .../ConnectionStateSendUsername.java | 41 + .../connection/ConnectionStateShutdown.java | 45 + ...ConnectionStateWaitForLcnBusConnected.java | 68 ++ ...itForLcnBusConnectedAfterDisconnected.java | 33 + .../lcn/internal/connection/ModInfo.java | 500 +++++++++++ .../lcn/internal/connection/PckQueueItem.java | 74 ++ .../internal/connection/RequestStatus.java | 195 +++++ .../lcn/internal/connection/SendData.java | 38 + .../lcn/internal/connection/SendDataPck.java | 77 ++ .../connection/SendDataPlainText.java | 61 ++ .../lcn/internal/converter/Converter.java | 118 +++ .../lcn/internal/converter/Converters.java | 62 ++ .../lcn/internal/converter/S0Converter.java | 55 ++ .../internal/pchkdiscovery/ExtService.java | 39 + .../internal/pchkdiscovery/ExtServices.java | 33 + .../LcnPchkDiscoveryService.java | 161 ++++ .../lcn/internal/pchkdiscovery/Server.java | 73 ++ .../pchkdiscovery/ServicesResponse.java | 47 ++ .../lcn/internal/pchkdiscovery/Version.java | 43 + .../AbstractLcnModuleSubHandler.java | 178 ++++ .../AbstractLcnModuleVariableSubHandler.java | 140 ++++ .../subhandler/ILcnModuleSubHandler.java | 155 ++++ .../LcnModuleBinarySensorSubHandler.java | 62 ++ .../subhandler/LcnModuleCodeSubHandler.java | 108 +++ .../LcnModuleKeyLockTableSubHandler.java | 95 +++ .../subhandler/LcnModuleLedSubHandler.java | 66 ++ .../subhandler/LcnModuleLogicSubHandler.java | 126 +++ .../LcnModuleMetaAckSubHandler.java | 90 ++ .../LcnModuleMetaFirmwareSubHandler.java | 55 ++ .../subhandler/LcnModuleOutputSubHandler.java | 182 ++++ .../subhandler/LcnModuleRelaySubHandler.java | 86 ++ ...cnModuleRollershutterOutputSubHandler.java | 82 ++ ...LcnModuleRollershutterRelaySubHandler.java | 77 ++ .../LcnModuleRvarLockSubHandler.java | 66 ++ .../LcnModuleRvarSetpointSubHandler.java | 79 ++ .../LcnModuleS0CounterSubHandler.java | 58 ++ .../LcnModuleThresholdSubHandler.java | 105 +++ .../LcnModuleVariableSubHandler.java | 88 ++ .../resources/ESH-INF/binding/binding.xml | 10 + .../main/resources/ESH-INF/config/config.xml | 102 +++ .../resources/ESH-INF/i18n/lcn_de.properties | 171 ++++ .../resources/ESH-INF/thing/thing-types.xml | 634 ++++++++++++++ .../lcn/internal/ModuleActionsTest.java | 199 +++++ .../LcnPchkDiscoveryServiceTest.java | 58 ++ .../AbstractTestLcnModuleSubHandler.java | 43 + .../LcnModuleBinarySensorSubHandlerTest.java | 77 ++ .../LcnModuleKeyLockTableSubHandlerTest.java | 173 ++++ .../LcnModuleLedSubHandlerTest.java | 84 ++ .../LcnModuleLogicSubHandlerTest.java | 106 +++ .../LcnModuleOutputSubHandlerTest.java | 198 +++++ .../LcnModuleRelaySubHandlerTest.java | 114 +++ ...duleRollershutterOutputSubHandlerTest.java | 66 ++ ...oduleRollershutterRelaySubHandlerTest.java | 89 ++ .../LcnModuleRvarLockSubHandlerTest.java | 64 ++ .../LcnModuleRvarSetpointSubHandlerTest.java | 138 ++++ .../LcnModuleS0CounterSubHandlerTest.java | 57 ++ .../LcnModuleThresholdSubHandlerTest.java | 133 +++ .../LcnModuleVariableSubHandlerTest.java | 89 ++ bundles/pom.xml | 1 + 111 files changed, 11954 insertions(+) create mode 100644 bundles/org.openhab.binding.lcn/.classpath create mode 100644 bundles/org.openhab.binding.lcn/.project create mode 100644 bundles/org.openhab.binding.lcn/NOTICE create mode 100644 bundles/org.openhab.binding.lcn/README.md create mode 100644 bundles/org.openhab.binding.lcn/doc/LCN-PRO_output_steps.png create mode 100644 bundles/org.openhab.binding.lcn/doc/dyn_text.png create mode 100644 bundles/org.openhab.binding.lcn/doc/ir.png create mode 100644 bundles/org.openhab.binding.lcn/doc/overview.jpg create mode 100644 bundles/org.openhab.binding.lcn/pom.xml create mode 100644 bundles/org.openhab.binding.lcn/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/DimmerOutputProfile.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/ILcnModuleActions.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnBindingConstants.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnChannelVariableConfiguration.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnGroupConfiguration.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnGroupHandler.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnHandlerFactory.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnModuleActions.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnModuleConfiguration.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnModuleDiscoveryService.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnModuleHandler.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnProfileFactory.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/PckGatewayConfiguration.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/PckGatewayHandler.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/DimmerOutputCommand.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/LcnAddr.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/LcnAddrGrp.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/LcnAddrMod.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/LcnChannelGroup.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/LcnDefs.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/LcnException.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/PckGenerator.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/ReverseNumber.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/Variable.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/VariableValue.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/AbstractConnectionState.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/AbstractConnectionStateSendCredentials.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/AbstractState.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/AbstractStateMachine.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/Connection.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionCallback.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionSettings.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateConnected.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateConnecting.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateGracePeriodBeforeReconnect.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateInit.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateMachine.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateSegmentScan.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateSendDimMode.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateSendPassword.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateSendUsername.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateShutdown.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateWaitForLcnBusConnected.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateWaitForLcnBusConnectedAfterDisconnected.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ModInfo.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/PckQueueItem.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/RequestStatus.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/SendData.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/SendDataPck.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/SendDataPlainText.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/Converter.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/Converters.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/S0Converter.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/pchkdiscovery/ExtService.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/pchkdiscovery/ExtServices.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/pchkdiscovery/LcnPchkDiscoveryService.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/pchkdiscovery/Server.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/pchkdiscovery/ServicesResponse.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/pchkdiscovery/Version.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/AbstractLcnModuleSubHandler.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/AbstractLcnModuleVariableSubHandler.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/ILcnModuleSubHandler.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleBinarySensorSubHandler.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleCodeSubHandler.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleKeyLockTableSubHandler.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleLedSubHandler.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleLogicSubHandler.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleMetaAckSubHandler.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleMetaFirmwareSubHandler.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleOutputSubHandler.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRelaySubHandler.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRollershutterOutputSubHandler.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRollershutterRelaySubHandler.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRvarLockSubHandler.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRvarSetpointSubHandler.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleS0CounterSubHandler.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleThresholdSubHandler.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleVariableSubHandler.java create mode 100644 bundles/org.openhab.binding.lcn/src/main/resources/ESH-INF/binding/binding.xml create mode 100644 bundles/org.openhab.binding.lcn/src/main/resources/ESH-INF/config/config.xml create mode 100644 bundles/org.openhab.binding.lcn/src/main/resources/ESH-INF/i18n/lcn_de.properties create mode 100644 bundles/org.openhab.binding.lcn/src/main/resources/ESH-INF/thing/thing-types.xml create mode 100644 bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/ModuleActionsTest.java create mode 100644 bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/pchkdiscovery/LcnPchkDiscoveryServiceTest.java create mode 100644 bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/AbstractTestLcnModuleSubHandler.java create mode 100644 bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleBinarySensorSubHandlerTest.java create mode 100644 bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleKeyLockTableSubHandlerTest.java create mode 100644 bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleLedSubHandlerTest.java create mode 100644 bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleLogicSubHandlerTest.java create mode 100644 bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleOutputSubHandlerTest.java create mode 100644 bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRelaySubHandlerTest.java create mode 100644 bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRollershutterOutputSubHandlerTest.java create mode 100644 bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRollershutterRelaySubHandlerTest.java create mode 100644 bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRvarLockSubHandlerTest.java create mode 100644 bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRvarSetpointSubHandlerTest.java create mode 100644 bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleS0CounterSubHandlerTest.java create mode 100644 bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleThresholdSubHandlerTest.java create mode 100644 bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleVariableSubHandlerTest.java diff --git a/CODEOWNERS b/CODEOWNERS index 6a373dde13819..86ceedf781144 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -96,6 +96,7 @@ /bundles/org.openhab.binding.konnected/ @volfan6415 /bundles/org.openhab.binding.kostalinverter/ @cschneider /bundles/org.openhab.binding.lametrictime/ @syphr42 +/bundles/org.openhab.binding.lcn/ @fwolter /bundles/org.openhab.binding.leapmotion/ @kaikreuzer /bundles/org.openhab.binding.lghombot/ @FluBBaOfWard /bundles/org.openhab.binding.lgtvserial/ @fa2k diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index f0045d390a790..6c2e753ce8ba6 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -476,6 +476,11 @@ org.openhab.binding.lametrictime ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.lcn + ${project.version} + org.openhab.addons.bundles org.openhab.binding.leapmotion diff --git a/bundles/org.openhab.binding.lcn/.classpath b/bundles/org.openhab.binding.lcn/.classpath new file mode 100644 index 0000000000000..a5d95095ccaaf --- /dev/null +++ b/bundles/org.openhab.binding.lcn/.classpath @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.lcn/.project b/bundles/org.openhab.binding.lcn/.project new file mode 100644 index 0000000000000..5302a4a4a785d --- /dev/null +++ b/bundles/org.openhab.binding.lcn/.project @@ -0,0 +1,23 @@ + + + org.openhab.binding.lcn + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/bundles/org.openhab.binding.lcn/NOTICE b/bundles/org.openhab.binding.lcn/NOTICE new file mode 100644 index 0000000000000..38d625e349232 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/NOTICE @@ -0,0 +1,13 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-addons diff --git a/bundles/org.openhab.binding.lcn/README.md b/bundles/org.openhab.binding.lcn/README.md new file mode 100644 index 0000000000000..6226424c7e662 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/README.md @@ -0,0 +1,594 @@ +# LCN Binding + +[Local Control Network (LCN)](http://www.lcn.eu) is a building automation system for small and very large installations. +It is capable of controlling lights, shutters, access control etc. and can process data from several sensor types. +It has been introduced in 1992. + +A broad range of glass key panels, displays, remote controls, sensors and in- and outputs exist. +The system can handle up to 30,000 bus members, called modules. +LCN modules are available for DIN rail and in-wall mounting and feature versatile interfaces. The bus modules and most of the accessories are developed, manufactured and assembled in Germany. + +Bus members are inter-connected via a free wire in the standard NYM cable. Wireless components are available, though. + +![Illustration of the LCN product family](doc/overview.jpg) + +This binding uses TCP/IP to access the LCN bus via the software LCN-PCHK (Windows/Linux) or the DIN rail device LCN-PKE. +**This means 1 unused LCN-PCHK license or a LCN-PKE is required** + +## LCN Overview + +LCN modules and their connecting peripherals are explained in the following. + +### LCN Modules + +Active LCN components connected to the LCN bus are called *LCN modules*. +LCN modules are addressed by their numeric id: Valid range is 5..254 + +In larger buildings, a second topologic layer is added: *segments*. +Valid range is 5..128 or 0 (= no segments exist) or 3 (= target all segments) + +LCN modules within the **same** segment can be grouped: Valid range is 5..254 or 3 (= target all groups) + +### LCN Firmware Versions + +Each LCN module has a feature-set based on its firmware version. +This version is written as follows: \[year since 1990\]\[month\]\[day\] + +Each component is written in hexadecimal with 2 characters. Examples: + +- 090101 = 1. january 1990 +- 0D0C01 = 1. december 2003 +- 170206 = 6. feb. 2013 + +### LCN Dimmer Outputs + +LCN modules support 2 to 4 dimmer output ports (number depends on firmware version). +If the module hardware type doesn't feature physical dimmer outputs, the outputs can still be used as virtual. + +Status values are always in percent. +Modules since 170206 have a 0.5%-steps resolution. Older modules have a 2%-steps resolution. + +The time it takes the output port to reach its setpoint is called *ramp*. + +### LCN Variables + +LCN modules support: + +- 3 or 12 (since 170206) analog variables for general purpose +- 2 regulators with configurable setpoints +- 5 or 4x4 (since 170206) thresholds (trigger levels) +- 4 S0-input counters (since 170206, LCN-BU4L must be connected) + +### LCN Regulators (additions to variables) + +LCN modules have 2 regulators. +Each one has a setpoint and uses one variable as its value source. +A regulator can be locked, so that the target actuator keeps switched off, also if the value source is in control range. + +### LCN Thresholds + +LCN modules since firmware 170206 have 4 threshold registers. Each threshold register comprises 4 thresholds. + +A threshold register uses one variable as its value source (see [LCN Variables](#lcn-variables)). +Arbitrary LCN commands can be send into the bus, when the value-source falls below a threshold or exceeds one. +A threshold can be locked, so that the configured LCN command is not fired, also if the value source passes the threshold. + +### LCN Relays + +LCN modules support 8 relays. If no hardware relays are connected, the relays can still be used as virtual. + +### LCN Binary Sensors + +LCN modules support 8 binary sensors (e.g. motion detectors; hardware periphery must be connected). + +### LCN LEDs (legacy name: *lamps*) + +12x multi-state variables can be used for logic operations or visualization (hardware periphery must be connected). + +Values: OFF, ON, BLINK, FLICKER + +### LCN Logic Operations (legacy name: *sums*) + +4x multi-state variables each representing the result of a logic operation of the associated LEDs. + +Values: NOT (all LEDs off), OR (some LEDs on), AND (all LEDs on) + +### LCN Keys + +LCN keys are data-points in the module with bound commands. +LCN modules support 3 ("A-C") or 4 ("A-D") key-tables (number depends on firmware version). + +Each key-table holds 8 keys. Examples: A1, A7, D8 + +Each key has 3 command types: HIT(press), MAKE(long press), BREAK(long press release) + +These keys can be locked. The bound (LCN-)commands cannot be executed, then. + +### LCN Access Control & Remote Controls + +LCN can interface several transponder readers and finger print sensors, used for access control. + +Remote controls can not only be used for triggering commands, but also for access control, by evaluating the transmitted serial number. + +## Supported Things + +### Thing: LCN Module + +Any LCN module that should be controlled or visualized, need to be added to openHAB as a *Thing*. + +LCN modules with firmware versions 120612 (2008) and 170602 (2013) were tested with this binding. +No known features/changes that need special handling were added until now (2020). +Modules with older and newer firmware should work, too. +The module hardware types (e.g. LCN-SH, LCN-HU, LCN-UPP, ...) are compatible to each other and can therefore be handled all in the same way. + +Thing ID: `module` + +| Name | Description | Type | Required | +|-------------|----------------------------------------------------------------|---------|----------| +| `moduleId` | The module ID, configured in LCN-PRO | Integer | Yes | +| `segmentId` | The segment ID the module is in (0 if no segments are present) | Integer | Yes | + +openHAB's discovery function can be used to add LCN modules automatically. +See [Discover LCN Modules](#discover-lcn-modules). + +### Thing: LCN PCK Gateway + +PCK is the protocol spoken over TCP/IP with a PCK gateway to communicate with the LCN bus. +Examples for PCK gateways are the *LCN-PCHK* software running on Windows or Linux and the DIN rail mounting device *LCN-PKE*. + +For each LCN bus, interfaced to openHAB, a PCK gateway needs to be added to openHAB as a *Thing*. + +Several PCK gateways can be added to openHAB to control multiple LCN busses in distinct locations. + +The minimum recommended version is LCN-PCHK 2.8 (older versions will also work, but lack some functionality). +Visit [https://www.lcn.eu](https://www.lcn.eu) for updates. + +Thing ID: `pckGateway` + +| Name | Description | Type | Required | +|-------------|------------------------------------------------------------------------------------------------------------|---------|----------| +| `hostname` | Hostname or IP address of the LCN-PCHK gateway | String | Yes | +| `port` | TCP port of the LCN-PCHK gateway (default:4114) | Integer | Yes | +| `username` | Username configured within LCN-PCHK Monitor | String | Yes | +| `password` | Password configured within LCN-PCHK Monitor | String | Yes | +| `mode` | Dimmer resolution: `native50` or `native200` See below. | String | Yes | +| `timeoutMs` | Period after which an LCN command is resent, when no acknowledge has been received (in ms) (default: 3500) | Integer | Yes | + +> **IMPORTANT:** You need to configure the dimmer output resolution. This setting is valid for the **whole** LCN bus.
+The setting is either 0-50 steps or 0-200 steps. +It **has to be the same** as in the parameterizing software **LCN-PRO** under Options/Settings/Expert Settings. +See the following screenshot. + +![LCN-PRO screenshot, showing the 50 or 200 steps for the dimmer outputs](doc/LCN-PRO_output_steps.png) + +When using a wrong dimmer output setting, dimming the outputs will result in unintended behavior. + +### Thing: LCN Group + +LCN modules can be assigned to groups with the programming software *LCN-PRO*. + +To send commands to an LCN group, the group needs to be added to openHAB as a *Thing*. + +One LCN module within the group is used to represent the status of the whole group. +For example, when a Dimmer Output is controlled via a LCN group *Thing*, openHAB will always visualize the state of the Dimmer Output of the chosen module. The states of the other modules in the group are ignored for visualization. + +Thing ID: `group` + +| Name | Description | Type | Required | +|-------------|----------------------------------------------------------------------------------------------------------------------------------------------|---------|----------| +| `groupId` | The group number, configured in LCN-PRO | Integer | Yes | +| `moduleId` | The module ID of any module in the group. The state of this module is used for visualization of the group as representative for all modules. | Integer | Yes | +| `segmentId` | The segment ID of all modules in this group (0 if no segments are present) | Integer | Yes | + +The `groupId` must match the previously configured group number in the programming software *LCN-PRO*. + +## Discovery + +### Discover LCN Modules + +Basic data of LCN modules can be read out by openHAB. +To do so, simply start openHAB's discovery. + +If not all LCN modules get listed on the first run, click on the refresh button to start another scan. + +When adding a module by discovery, the new *Thing*'s UID will be the module's serial number. + +### Discover PCK Gateways + +PCK gateways in the LAN can be found automatically by openHAB. This is done by UDP multicast messages on port 4220. +The discovery works only if the firewall of the PCK gateway is not configured too strictly. +This means on Windows PCs, that the network must be configured as 'private' and not as 'public'. +Also, some network switches may block multicast packets. +Unfortunately, *LCN-PCHK* listens only on the first network interface of the computer for discovery packets. +If your PCK gateway has multiple network interfaces, *LCN-PCHK* may listen on the wrong interface and fails to respond to the discovery request. + +Discovery has successfully been tested with LCN-PCHK 3.2.2 running on a Raspberry Pi with Raspbian and openHAB running on Windows 10. + +If discovery fails, you can add a PCK gateway manually. See [Thing: PCK Gateway](#thing-lcn-pck-gateway). + +Please be aware that you **have to configure** username, password and the dimmer output resolution also if you use discovery. +See [Thing: PCK Gateway](#thing-lcn-pck-gateway). + +When adding a PCK gateway by discovery, the new *Thing*'s UID is the MAC address of the device, running the PCK gateway. + +## Supported LCN Features and openHAB Channels + +The following table lists all features of LCN and their mappings to openHAB Channels. These Channels are available for the *Things* LCN module (`module`) and LCN group (`group`). The PCK gateway (`pckGateway`) has no Channels. + +Although, there are many **Not implemented** entries, the vast majority of LCN features can be used with openHAB:
+If a special command is needed, the [Hit Key](#hit-key) action (German: "Sende Taste") can be used to hit a module's key virtually and execute an arbitrary command. + +| LCN Feature (English) | LCN Feature (German) | Channel | IDs | Type | Description | +|---------------------------------|----------------------------------|------------------------|------|--------------------------------|-------------------------------------------------------------------------------------------------------------------------------| +| Dimmer Output Control Single | Ausgang | output | 1-4 | Dimmer, Switch | Sets the dimming value of an output with a given ramp. | +| Relay | Relais | relay | 1-8 | Switch | Controls a relay and visualizes its state. | +| Visualize Binary Sensor | Binärsensor anzeigen | binarysensor | 1-8 | Contact | Visualizes the state of a binary sensor. | +| LED Control | LED-Steuerung | led | 1-12 | Text (ON, OFF, BLINK, FLICKER) | Controls an LED and visualizes its current state. | +| Visualize Logic Operations | Logik Funktion anzeigen | logic | 1-4 | Text (NOT, OR, AND) | Visualizes the result of the logic operation. | +| Motor/Shutter on Dimmer Outputs | Motor/Rollladen an Ausgängen | rollershutteroutput | 1-4 | Rollershutter | Control roller shutters on dimmer outputs | +| Motor/Shutter on Relays | Motor/Rollladen an Relais | rollershutterrelay | 1-4 | Rollershutter | Control roller shutters on relays | +| Variables | Variable anzeigen | variable | 1-12 | Number | Sets and visualizes the value of a variable. | +| Regulator Set Setpoint | Regler Sollwert ändern | rvarsetpoint | 1-2 | Number | Sets and visualizes the setpoint of a regulator. | +| Regulator Lock | Regler sperren | rvarlock | 1-2 | Switch | Locks a regulator and visualizes its locking state. | +| Set Thresholds in Register 1 | Schwellwert in Register 1 ändern | thresholdregister1 | 1-4 | Number | Sets and visualizes a threshold in the given threshold register. | +| Set Thresholds in Register 2 | Schwellwert in Register 2 ändern | thresholdregister2 | 1-4 | Number | Sets and visualizes a threshold in the given threshold register. | +| Set Thresholds in Register 3 | Schwellwert in Register 3 ändern | thresholdregister3 | 1-4 | Number | Sets and visualizes a threshold in the given threshold register. | +| Set Thresholds in Register 4 | Schwellwert in Register 4 ändern | thresholdregister4 | 1-4 | Number | Sets and visualizes a threshold in the given threshold register. | +| Visualize S0 Counters | S0-Zähler anzeigen | s0input | 1-4 | Number | Visualizes the value of a S0 counter. | +| Lock Keys Table A | Sperre Tastentabelle A | keylocktablea | 1-8 | Switch | Locks a key on the given key table and visualizes its state. | +| Lock Keys Table B | Sperre Tastentabelle B | keylocktableb | 1-8 | Switch | Locks a key on the given key table and visualizes its state. | +| Lock Keys Table C | Sperre Tastentabelle C | keylocktablec | 1-8 | Switch | Locks a key on the given key table and visualizes its state. | +| Lock Keys Table D | Sperre Tastentabelle D | keylocktabled | 1-8 | Switch | Locks a key on the given key table and visualizes its state. | +| Dimmer Output Flicker | Ausgang: Flackern | N/A | N/A | N/A | Action "flickerOutput": Let a dimmer output flicker for a given count of flashes. | +| Dynamic Text | Dynamischer Text | N/A | N/A | N/A | Action: "sendDynamicText": Sends custom text to an LCN-GTxD display. | +| Send Keys | Sende Tasten | N/A | N/A | N/A | Action: "hitKey": Hits a key of a key table in an LCN module. Can be used to execute commands, not supported by this binding. | +| Dimmer Output Control Multiple | Mehrere Ausgänge steuern | output | 1-4 | Dimmer, Switch | Control multiple outputs simultaneously. See below. | +| Transponder | Transponder | code#transponder | | Trigger | Receive transponder messages | +| Remote Control | Fernbedienung | code#remotecontrolkey | | Trigger | Receive commands from remote control | +| Access Control | Zutrittskontrolle | code#remotecontrolcode | | Trigger | Receive serial numbers from remote control | +| Remote Control Battery Low | Fernbedienung Batterie schwach | code#remotecontrolbatterylow | | Trigger | Triggered when the sending remote control has a low battery | +| Status Message | Statusmeldungen | - | - | - | Automatically done by OpenHAB Binding | +| Audio Beep | Audio Piepen | - | - | - | Not implemented | +| Audio LCN-MRS | Audio LCN-MRS | - | - | - | Not implemented | +| Count/Compute | Zählen/Rechnen | - | - | - | Not implemented | +| DALI | DALI | - | - | - | Not implemented | +| Dimmer Output Memory Toggle | Ausgang: Memory Taster | - | - | - | Not implemented | +| Dimmer Output Ramp Stop | Ausgang: Rampe Stop | - | - | - | Not implemented | +| Dimmer Output Relative | Ausgang: Relativ | - | - | - | Not implemented | +| Dimmer Output Stairway | Ausgang: Treppenhauslicht | - | - | - | Not implemented | +| Dimmer Output Timer | Ausgang: Timer (Kurzzeit) | - | - | - | Not implemented | +| Display Set Language | Display-Sprache setzen | - | - | - | Not implemented | +| Dynamic Groups | Dynamische Gruppen | - | - | - | Not implemented | +| Free Input | Freie Eingabe | - | - | - | Not implemented | +| LED Brightness | LED-Helligkeit | - | - | - | Not implemented | +| LED Test | LED-Test | - | - | - | Not implemented | +| LED Transform | LED-Umwandlung | - | - | - | Not implemented | +| Light Scenes | Lichtszenen | - | - | - | Not implemented | +| Lock Keys by Time (Table A) | Sperre (Zeit) Tasten (Tabelle A) | - | - | - | Not implemented | +| Lock Outputs by Time | Sperre (Zeit) Ausgänge | - | - | - | Not implemented | +| Lock Relays | Sperre Relais | - | - | - | Not implemented | +| Lock Thresholds | Sperre Schwellwerte | - | - | - | Not implemented | +| Motor Position | Motor Position | - | - | - | Not implemented | +| Relay Timer | Relais-Timer | - | - | - | Not implemented | +| Send Keys Delayed | Sende Tasten verzögert | - | - | - | Not implemented | +| Set S0 Counters | S0-Zähler setzen | - | - | - | Not implemented | +| Status Command | Statuskommandos | - | - | - | Not implemented | + +**For some *Channel*s a unit should be configured for visualization.** By default the native LCN value is used. + +S0 counter Channels need to be the pulses per kWh configured. If the value is left blank, a default value of 1000 pulses/kWh is set. + +### Transponder + +LCN transponder readers can be integrated in openHAB e.g. for access control. +The transponder function must be enabled in the module's I-port properties within *LCN-PRO*. + +Example: When the transponder card with the ID "12ABCD" is seen by the reader connected to LCN module "17B308349E", the item "M10_Relay7" is switched on: + +``` +rule "My Transponder" +when + Channel "lcn:module:b827ebfea4bb:17B308349E:code#transponder" triggered "12ABCD" +then + M10_Relay7.sendCommand(ON) +end +``` + +### Remote Control + +To evaluate commands from LCN remote controls (e.g. LCN-RT or LCN-RT16), the module's I-port behavior must be configured as "IR access control" within *LCN-PRO*: + +![Screenshot, showing the I-port properties for remote controls](doc/ir.png) + +#### Remote Control Keys + +The trigger *Channel* `lcn:module:::code#remotecontrolkey` can be used to execute commands, when a specific key on a remote control is pressed: + +``` +rule "Remote Control Key 3 on Layer 1 hit" +when + Channel "lcn:module:b827ebfea4bb:17B3073D6A:code#remotecontrolkey" triggered "A3:HIT" +then + M10_Relay7.sendCommand(ON) +end +``` + +`A3` is key 3 on the first layer. `B1` is key 1 on the second layer etc.. After the colon follows the LCN "hit type" HIT, MAKE or BREAK (German: kurz, lang, los). + +#### Remote Control used as Access Control + +The serial number of a remote control can be used for access control via the channel `lcn:module:::code#remotecontrolcode`. See the following example: + +``` +rule "Remote Control Key 3 on Layer 1 hit (only executed for serial number AB1234)" +when + Channel "lcn:module:b827ebfea4bb:17B3073D6A:code#remotecontrolcode" triggered "AB1234:A3:HIT" or + Channel "lcn:module:b827ebfea4bb:17B3073D6A:code#remotecontrolcode" triggered "AB1234:A3:MAKE" +then + M10_Relay7.sendCommand(ON) +end +``` + +The command will be executed when the remote control button A3 is either pressed short or long. + +## Dimmer Outputs with Ramp and Multiple Outputs + +The *output* profile can be used to control multiple dimmer outputs of the *same* module simultaneously or control a dimmer output with a ramp (slowly dimming). + +The optional *ramp* parameter must be float or integer. +The lowest value is 0.25, which corresponds to 0.25s. The highest value is 486s. +When no *ramp* parameter is specified or no profile is configured, the ramp is 0 (behavior like a switch). +The ramp parameter is not available for Color *Item*s. + +``` +// Dim output 2 in 0.25s +Switch M10_Output2 {channel="lcn:module:b827ebfea4bb:17B4196847:output#2"[profile="lcn:output", ramp=0.25]} // with ramp of 0.25s (smallest value) +// Dim output 3 in 486s +Dimmer M10_Output3 {channel="lcn:module:b827ebfea4bb:17B4196847:output#3"[profile="lcn:output", ramp=486]} // with ramp of 486s (biggest value) +``` + +The optional parameters *controlAllOutputs* and *controlOutputs12* can be used to control multiple outputs simultaneously. +Please note that the combination of these parameters with the *ramp* parameter is limited: + +``` +// Control outputs 1+2 simultaneously. Status of Output 1 is visualized. Only ramps of 0s or 0.25s are supported. +Dimmer M10_Outputs12a {channel="lcn:module:b827ebfea4bb:17B4196847:output#1"[profile="lcn:output", controlOutputs12=true]} +Dimmer M10_Outputs12b {channel="lcn:module:b827ebfea4bb:17B4196847:output#1"[profile="lcn:output", controlOutputs12=true, ramp=0.25]} +// Control all outputs simultaneously. Status of Output 1 is visualized. +Dimmer M10_OutputAll1 {channel="lcn:module:b827ebfea4bb:17B4196847:output#1"[profile="lcn:output", controlAllOutputs=true, ramp=0]} // ramp only since firmware 180501 +Dimmer M10_OutputAll2 {channel="lcn:module:b827ebfea4bb:17B4196847:output#1"[profile="lcn:output", controlAllOutputs=true, ramp=0.25]} // ramp compatibility: all +Dimmer M10_OutputAll3 {channel="lcn:module:b827ebfea4bb:17B4196847:output#1"[profile="lcn:output", controlAllOutputs=true, ramp=0.5]} // ramp only since firmware 180501 +``` + +## Actions + +Actions are special commands that can be sent to LCN modules or LCN groups. + +### Hit Key + +This *Action* virtually hits a key of a key table in an LCN module. +Simply spoken, OpenHab acts as a push button switch connected to an LCN module. + +This *Action* can be used to execute commands which are not natively supported by this binding. +The function can be programmed via the software *LCN-PRO* onto a key in a module's key table. +Then, the programmed key can be "hit" by this *Action* and the command will be executed. + +When programming a "Hit Key" *Action*, the following parameters need to be set: + +*table* - The module's key table: A, B, C or D
+*key* - The number of the key within the key table: 1-8
+*action* - The key's action: HIT (German: "kurz"), MAKE ("lang") or BREAK ("los") + +``` +rule "Hit key C4 hourly" +when + Time cron "0 0 * * * ?" +then + val actions = getActions("lcn","lcn:module:b827ebfea4bb:17B4196847") + actions.hitKey("C", 4, "HIT") +end +``` + +### Dynamic Text + +This *Action* can be used to send custom texts to an LCN-GTxD display. +To make this function work, the row of the display has to be configured to allow dynamic text within *LCN-PRO*: + +![Screenshot of LCN-PRO, showing the dynamic text setting of an LCN-GT10D](doc/dyn_text.png) + +When programming a "Dynamic Text" *Action*, the following parameters need to be set: + +*row* - The number of the row in the display: 1-4
+*text* - The text to be displayed (UTF-8) + +The length of the text may not exceed 60 bytes of characters. +Bear in mind that unicode characters can take more than one byte (e.g. umlauts (äöü) take two bytes). + +``` +rule "Send dynamic Text to GT10D hourly" +when + Time cron "0 0 * * * ?" +then + val actions = getActions("lcn","lcn:module:b827ebfea4bb:17B3073D6A") + actions.sendDynamicText(1, "Test 123 CO₂ öäü߀") // row 1 +end +``` + +### Flicker Output + +This *Action* realizes the LCN command "Output: Flicker" (German: "Ausgang: Flackern"). +The command let a dimmer output flash a given number of times. This feature can be used e.g. for alert signals or visual door bells. + +When programming a "Flicker Output" *Action*, the following parameters need to be set: + +*output* - The dimmer output number: 1-4
+*depth* - The depth of the flickering: 0-2 (0=25% 1=50% 2=100% Example: When the output is fully on (100%), and 0 is selected, flashes will dim from 100% to 75% and back)
+*ramp* - The duration/ramp of one flash: 0-2 (0=2sec 1=1sec 2=0.5sec)
+*count* - The number of flashes: 1-15 + +This action has also effect, if the given output is off. The output will be dimmed from 0% to *depth* and back, then. + +``` +rule "Flicker output 1 when window opens" +when + Item M10_BinarySensor5 changed to OPEN +then + val actions = getActions("lcn","lcn:module:b827ebfea4bb:17B4196847") + // output=1, depth=2=100%, ramp=0=2s, count=3 + actions.flickerOutput(1, 2, 0, 3) +end +``` + +## Caveat and Limitations + +LCN segments are supported by this binding, but could not be tested, due to lack of hardware. + +LEDs do not support the *OnOffCommand* and respectively the *Switch* Item type, because they have the additional states *BLINK* and *FLICKER*. They must be configured as *String* Item. When used in rules, the parameter must be of type string. Example: `M10_LED1.sendCommand("ON")`. Note the quotation marks. + +## Full Example + +Config .items + +``` +// Dimmer Outputs +Dimmer M10_Output1 {channel="lcn:module:b827ebfea4bb:17B4196847:output#1"} +Switch M10_Output2 {channel="lcn:module:b827ebfea4bb:17B4196847:output#2"[profile="lcn:output", ramp=0.25]} // with ramp of 0.25s (smallest value) +Dimmer M10_Output3 {channel="lcn:module:b827ebfea4bb:17B4196847:output#3"[profile="lcn:output", ramp=486]} // with ramp of 486s (biggest value) + +// Dimmer Outputs: Control all simultaneously. Status of Output 1 is visualized. +Dimmer M10_OutputAll1 {channel="lcn:module:b827ebfea4bb:17B4196847:output#1"[profile="lcn:output", controlAllOutputs=true, ramp=0]} // ramp=0: only since firmware 180501 +Dimmer M10_OutputAll2 {channel="lcn:module:b827ebfea4bb:17B4196847:output#1"[profile="lcn:output", controlAllOutputs=true, ramp=0.25]} // ramp=0.25: compatibility: all firmwares +Dimmer M10_OutputAll3 {channel="lcn:module:b827ebfea4bb:17B4196847:output#1"[profile="lcn:output", controlAllOutputs=true, ramp=0.5]} // ramp>=0.5: only since firmware 180501 + +// Dimmer Outputs: Control outputs 1+2 simultaneously. Status of Output 1 is visualized. Only ramps of 0s or 0.25s are supported. +Dimmer M10_Outputs12b {channel="lcn:module:b827ebfea4bb:17B4196847:output#1"[profile="lcn:output", controlOutputs12=true, ramp=0.25]} + +// Dimmer Outputs: RGB Control +Color M10_Color {channel="lcn:module:b827ebfea4bb:17B4196847:output#color"[profile="lcn:output"]} + +// Roller Shutter on Output 1+2 +Rollershutter M10_RollershutterOutput1 {channel="lcn:module:b827ebfea4bb:17B4196847:rollershutteroutput#1"} + +// Relays +Switch M10_Relay1 {channel="lcn:module:b827ebfea4bb:17B4196847:relay#1"} + +// Roller Shutter on Relays 1+2 +Rollershutter M10_RollershutterRelay1 {channel="lcn:module:b827ebfea4bb:17B4196847:rollershutterrelay#1"} + +// LEDs +String M10_LED1 {channel="lcn:module:b827ebfea4bb:17B4196847:led#1"} +String M10_LED2 {channel="lcn:module:b827ebfea4bb:17B4196847:led#2"} + +// Logic Operations (legacy name: "Sums") +String M10_Logic1 {channel="lcn:module:b827ebfea4bb:17B4196847:logic#1"} +String M10_Logic2 {channel="lcn:module:b827ebfea4bb:17B4196847:logic#2"[profile="transform:MAP", function="alertSystem.map"]} +// conf/transform/alertSystem.map: +// NOT=All windows are closed +// OR=Some windows are open +// AND=All windows are open + +// Binary Sensors +Contact M10_BinarySensor1 {channel="lcn:module:b827ebfea4bb:17B4196847:binarysensor#1"} + +// Variables +// The units of the variables must also be set in the Channels configuration, to be visualized correctly. +Number:Temperature M10_Variable1 "[%.1f %unit%]" {channel="lcn:module:b827ebfea4bb:17B4196847:variable#1"} // Temperature in °C +Number:Temperature M10_Variable2 "[%.1f °F]" {channel="lcn:module:b827ebfea4bb:17B4196847:variable#2"} // Temperature in °F +Number M10_Variable3 "[%d ppm]" {channel="lcn:module:b827ebfea4bb:17B4196847:variable#3"} // Indoor air quality in ppm +Number M10_Variable4 "[%d lx]" {channel="lcn:module:b827ebfea4bb:17B4196847:variable#4"} // Illuminance in Lux +Number:Illuminance M10_Variable5 "[%.1f klx]" {channel="lcn:module:b827ebfea4bb:17B4196847:variable#5"} // Illuminance in kLux +Number M10_Variable6 "[%.1f mA]" {channel="lcn:module:b827ebfea4bb:17B4196847:variable#6"} // Electrical current in mA +Number M10_Variable7 "[%.1f V]" {channel="lcn:module:b827ebfea4bb:17B4196847:variable#7"} // Voltage in V +Number M10_Variable8 "[%.1f m/s]" {channel="lcn:module:b827ebfea4bb:17B4196847:variable#8"} // Wind speed in m/s +Number M10_Variable9 "[%.1f °]" {channel="lcn:module:b827ebfea4bb:17B4196847:variable#9"} // position of the sun (azimuth or elevation) in ° +Number M10_Variable10 "[%d W]" {channel="lcn:module:b827ebfea4bb:17B4196847:variable#10"} // Current power of an S0 input in W +Number:Power M10_Variable11 "[%.1f kW]" {channel="lcn:module:b827ebfea4bb:17B4196847:variable#11"} // Current power of an S0 input in kW + +// Regulators +Number:Temperature M10_R1VarSetpoint "[%.1f %unit%]" {channel="lcn:module:b827ebfea4bb:17B4196847:rvarsetpoint#1"} // Temperature in °C +Switch M10_R1VarLock {channel="lcn:module:b827ebfea4bb:17B4196847:rvarlock#1"} // Lock state of R1Var + +// Thresholds +Number:Temperature M10_ThresholdRegister1_Threshold1 "[%.1f %unit%]" {channel="lcn:module:b827ebfea4bb:17B4196847:thresholdregister1#1"} // Temperature in °C +Number:Temperature M10_ThresholdRegister4_Threshold2 "[%.1f %unit%]" {channel="lcn:module:b827ebfea4bb:17B4196847:thresholdregister4#2"} // Temperature in °C + +// S0 Counters +Number:Energy M10_S0Counter1 "[%.1f kWh]" {channel="lcn:module:b827ebfea4bb:17B4196847:s0input#1"} + +// Key Locks +Switch M10_KeyLockA1 {channel="lcn:module:b827ebfea4bb:17B4196847:keylocktablea#1"} +Switch M10_KeyLockD5 {channel="lcn:module:b827ebfea4bb:17B4196847:keylocktabled#5"} +``` + +Config .sitemap + +``` +sitemap lcn label="My home automation" { + Frame label="Demo Items" { + // Dimmer Outputs + Default item=M10_Output1 label="Output 1" + Default item=M10_Output2 label="Output 2" + Default item=M10_Output3 label="Output 3" + + // Dimmer Outputs: Control all simultaneously. Status of Output 1 is visualized. + Default item=M10_OutputAll1 label="All Outputs ramp=0 since firmware 180501" + Default item=M10_OutputAll2 label="All Outputs ramp=250ms all firmwares" + Default item=M10_OutputAll3 label="All Outputs ramp>=500ms since firmware 180501" + + // Dimmer Outputs: Control outputs 1+2 simultaneously. Status of Output 1 is visualized. Only ramps of 0s or 0.25s are supported. + Default item=M10_Outputs12a label="Outputs 1+2 Ramp=0" + Default item=M10_Outputs12b label="Outputs 1+2 Ramp=0.25s" + + // Dimmer Outputs: RGB Control + Colorpicker item=M10_Color + + // Roller Shutter on Outputs 1+2 + Default item=M10_RollershutterOutput1 label="Roller Shutter on Output 1+2" + + // Relays + Default item=M10_Relay1 label="Relay 1" + + // Roller Shutter on Relays + Default item=M10_RollershutterRelay1 label="Roller Shutter on Relay 1-2" + + // LEDs + Switch item=M10_LED1 label="LED 1" mappings=[ON=ON, OFF=OFF] // Don't display "Blink" or "Flicker" + Switch item=M10_LED2 label="LED 2" + + // Logic Operations (legacy name: "Sums") + Default item=M10_Logic1 label="Logic Operation 1" + Default item=M10_Logic2 label="Logic Operation 2" + + // Binary Sensors + Default item=M10_BinarySensor1 label="Binary Sensor 1" + + // Variables + Setpoint item=M10_Variable1 label="Variable 1" + Default item=M10_Variable2 label="Variable 2" + Default item=M10_Variable3 label="Variable 3" + Default item=M10_Variable4 label="Variable 4" + Default item=M10_Variable5 label="Variable 5" + Default item=M10_Variable6 label="Variable 6" + Default item=M10_Variable7 label="Variable 7" + Default item=M10_Variable8 label="Variable 8" + Default item=M10_Variable9 label="Variable 9" + Default item=M10_Variable10 label="Variable 10" + Default item=M10_Variable11 label="Variable 11" + + // Regulators + Setpoint item=M10_R1VarSetpoint label="R1Var Setpoint" step=1 minValue=-10.0 + Default item=M10_R1VarLock label="R1Var Lock" // Lock state of R1Var + + // Thresholds + Setpoint item=M10_ThresholdRegister1_Threshold1 label="Threshold Register 1 Threshold 1" + Setpoint item=M10_ThresholdRegister4_Threshold2 label="Threshold Register 4 Threshold 2" + + // S0 Counters + Default item=M10_S0Counter1 label="S0 Counter 1" + + // Key Locks + Default item=M10_KeyLockA1 label="Locked State Key A1" + Default item=M10_KeyLockD5 label="Locked State Key D5" + } +} +``` diff --git a/bundles/org.openhab.binding.lcn/doc/LCN-PRO_output_steps.png b/bundles/org.openhab.binding.lcn/doc/LCN-PRO_output_steps.png new file mode 100644 index 0000000000000000000000000000000000000000..d86eaabe932fa6888deb4a2d95afa3fd8253570c GIT binary patch literal 200517 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 z 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�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

  • +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

    B{N;H}+Lo@~-&)(1eCv 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 literal 0 HcmV?d00001 diff --git a/bundles/org.openhab.binding.lcn/doc/dyn_text.png b/bundles/org.openhab.binding.lcn/doc/dyn_text.png new file mode 100644 index 0000000000000000000000000000000000000000..64167c7b23ea3da5b7f42b1f3f4c75893fceb8dc GIT binary patch literal 106065 zcmXtfbyyqU^L4P|uEo7jytsRzxVyVUad!z&w73^7PH=a3DK5d?UBk=g`@FwD_Q_`D zW_Rw)IcMfhB9#=RP>~3c0002$4`~S%004UBV-rAt{x~9%koofQf#@Wy?E(NGWBs>5 z&93{u0s!QI9}=Q!o>`~s9$7e&EAW?K)%kU74+|LCaGH={*g7LVUghzlaqWxAESLTY zf~gw?w)GB60l_*TuO%Bf7Mz;%3J*wp{suBrH--f#c{%y)uOTcuA?GnQK6yK*s4gh^ zu;!xJ<^LuyMMX)_Q&qr|Stl2pCLmWHIx4o4#CIAx8^Co-AWs$$bn(7;a23##*<- zqgtZS;C*-8FsHz8T{%!&mugJ#acYo-8AUjlyrwr=(q-fk9NPDe(CwU#vQOq^lm^i4 zTZeeCn~kG%7XncGSpC&|og{4S!+u~=gpS1icXv2wD;=Ys6{U~$SIiVraEu?qe+P{# zNG#BT0RT+xl!h&I5nFi)h6g`(mlJE)lhNmyt55&C=2ZqG!G3k?V!VATq92i>cevoL zs_)86xMa+{rCY{!Ku(3ZiPu6y28Ix0HWBt?IO@ujHV_(zu1b{!enpalkkStv2h?$5 zG)8I)v#~6>%Ct}`|L}^2SwdKPMoK_Mun2ct6OKxhdOK>xs|Yq`nsn8roOo>bECO-+h0=$YRB*lnk^V~WUe zR%^|R9$>#_Bxq}Gc40p^o#GwRKKL37ESy0~H?ZGTT!cZ6YTMshi&bohuU3h~{T9DN z9hczaH0E2qikOT#|IG|{3CU%IAQn@dVrE=sWT@`LDf?9C%@fYoXtb9%wyulBuDgr( zgJbD`wYcytdOyoCrARstHl0-2zqF^YkhN>eH>l{yfG#teNM8@?YW=|25LcN@oIG|# z!N=5xu8Hs3LpxU__kz9$6n;8z#3&eRty1UBF23SyDwkYjKQ|#hD0S()zWBOW-xw5M zPzpLwUQPR^9#u4fpi$XJe9t?8{{CBneR)DPEwT~vWy!!srn<^vC7M@fJCR+;n(uNOMvgnr%1K)vldv7Vn{h~LETA#F zE7YwCVi&e)e^ispRE8MiEZ+liJeO~8U06c3*5!HpW#=A4^ zB$W;O>c5`Z{tIL`6K#!$^YuP*quIhA>uny29|2)^A%SBM)5UQQR}|cFzsy~MRl6wk z@)ft8!#&$HVWDDw901T#<~QL7)T^X0b$Q+{*pg{qkNQ-*{s@E5f0A#!g{{5*R zhTHbwE1>zUDzjw2-2G*Ja+Dzy{-%kabY=gNf0EaoAa>Pufs}va?*8cD-0sV4W|r%U z*Q!S?G^#8|R`aR?KDhC&8Lz-c{}x zIJje;|^_VEpEb$jC$I4eEB_E^260P)FhnsfnXU)gSHQ*F^ zV}~u)>+L)b);*rxh2ooz^D3FYFNZ{=7l~pssD-dWg*4%WQsE)u{dyaDFq-ReGxULU!W5l-9<47;yIEOF#|zmlV&| zF|H=?Pq!{KPygHGo#U^oCH{}6AtTWyXOO2RczCx+Y`isB2(M$idRue5@nh$~hW9hY zfFqc1e-Izuqs-@Vq}N!=E#N)6l#IE#zg+Ncv;dw|V0Rl?$lH62i^Kdi1V87w+T?Re z;i{nL35<9vjb-Os?pSr3hXEVdYq5{4WgDjk+CHy~sE-w8Cm?b`N9O5jh%DE0J90|s zn;X7|AeUvNR+UCEWv>VrrAU3z!kZzyUuM7{Lb>6MVneH4Q8ISf~96FtOpqLqXjk! zHrh$eFgBGrD8SyI*xJt2-#$lIjiPK+Ayaf&4X#cF`0cP?fZclTo3eeEB3j><4h#rh zwxZiy(|!&XH9^Otf;;X``QdYY55}rH+^zzLuPpW9H+yU+-@k!&%6AOkj_*L}+@#G9 zbKzEQPxU_)RTim)9$#n1SUPNW(LxkD?mNpBo?xQnpV}GnyA$ISJFjP4NPQsli(p7s zUwD?=fIYyW^=-T70Q@%Tb7A;U;wOlT9sQqN5SI76dhf?pwCj4i_c?E|#DFF0%(9mK znXnTpZ2XWw4C=Q{c-($J28;9bRTrGuzb!%B}JUvo4wItpn~BJNcN6!YBq+O80JR zj#ex6<7rqx`+8b~GngqO!w~7!;RU?zKU737^c1@8UhCB|)A>KLyYJ=63wVcYu@LdF z1Z&Lk-!D4`EV|`-9n%A-KICNX+kF6J*;1oQs`j4o&>Vz<4T}h<;GpExU(uQpewbk{ zxqzK8Lky-HV3gPz-$4EjZe2XL1cM(!(T>aj@%A1UGkwTuX6AV{Pt1MHd`IaQA=qu& z|2&5LxL3WVo{O-P4#HmY_a;Y+LJSv@{wlcUa8?U7nY%%Equ5wAnJacnxm04JLTz3} zK-N1S6!ro&^fE5XZxZ?&(S`5EV@a4$W#I`ifZxYfG+_dr*L~CC5OZ101hxUAjace? z2vj8MNs&OwVD05X!x1nM2OZIFPjX5PJ2k7K)Ud1I^gPy( zLm{XlE`EsqN5tOi%SFQgKSsRr^76jxWMQsf;^_RnZtFiy4H@O7XiWA$c3kxzru$+a zM=~39Shx+9NxZfekOR1ng!Q^uJNHDV-tT(xbN)6TZjVXnsmiKXWHDRny>IT5f5EFm z2)u+j7vBD5>d}1gJaDHR%5JKjwvPdg(z+Xjud-<1x_iI6Y)-HL>+bn2*W*wb5s)X% zVNsn#TC5*$S1Zpu&+PyP?X6N> zr+x>d??+N#;6~p?*9$bjQa|8e;0wh1#hxruv(zlp{=EI2kV1kH%N-St{3Xuk?51Sy zet)1zFW}$)l_%qH-+9YH!xwozmm%Ys9Q}-M15^rakR*dMh}R99qdY@2aC<{Dm%k8t z+Gfw$<-f4MD(Dr;FzyT2_q`e*a*lGO8}}miIAzmy>`*^RVWswdosklHyRAzZblZ6B z^H_K5FD(f-pH4Egt8iregj6BfUePK*0|bUV56Ipi*_W>_HNt7M=uu!PhBv5x76GLp zs%d`%MFpQ_{D=qtYi_0|Qf#P{US{IPz1BXWCbn&X$Gzq?mZEB8OcvNP>S>ppQCp2l zB7yMdS`V8cK^0WA^Cj2(;hej!i_+TrHejVK)@b93?8TIwX3D8ag?xO#GAxOeUG}x1 zq>zfN<8l{K|J`Y>l>d#@QnZ9Qo9Po(MK<0)7QJ}&NHmhKOi&vBRfk`&u2Sr|8Fe zl0@siwmp1Ln*bD&RN4{gJEZE+4@bG5F<{(R*N~@M;U-|FQ6;Fk&P$2F7A0lNc*qn% z(cuDTkJ*6(nwSd%+mYtz<_nB`uID%@RKLXlI4tGBRQ05s5ETrQ0TkpBiLW4EY)a5$ zEsTkVj+*PK3e=&K4m-eU#tmw@#Z>AmE)$i-f{t4Kg$n4jbakY^7ARw@&~$=vE}Vdt zi)LME-rg?TD23ghPzg+s!tA0VIG$rho>vcoZV5aG*ur%N~sx$wt zd2R0{4+soR8fTiwZ|V0g$Nch756xd46EPvFc5h3CS$C-@$zKa{KE{cKm|xTDGqyRm z08gjEp6|IAf=H%D$RtZGn;90Ls0~w-CCzaRFq-IPrqKWbMJ9ao22}Bbb#lDM6v8l= zq&^SIj2Q_2f6~^~LUyA%_e(xw7@|Ar*I@%_?~!WQXy2R(w?b9$)LAsdFbo;mZY+Mi zsJjl)Ij3TRGU}3bSdv`=q!|8T5y-VHKF)ldwEq$W+7nGXr~_;ipfMVSD0wTv;Y5EcXa z3JKdS)yxz~p@04+LEj*zgjLneYdu@Y()IccFFHN1pY<0QfkNVs62Uh!>37Im-yWbS z>Gu~!|NCXjN?%ob=2QlpYj0f1Rn$H$OEH8>H&s3iWs=SM;Np3+(M2}(YwvNHPLkhq6!B=>eccPQ&$bvd4B%JVMEzz`eD0% zUAa_y8M9}J16U|7A7ovKyEMHosGF}}prw3L#}uoWQ`bJJR{P+vk!7&wUc=sozad1qh zo)$U*!DCvFig#^`PcSn%SO4hfZQKSGrQK3(Zg^`D$>e7T@ zaIk)j5~THV2G7tpVO2)AZh3(^o4^?~trxL?3oxy9k$1Z*RY< zH4pE;H|AaG%6RO{>Ob3-s}ReBxe9WT^eCtBYh1MnwEZbSYFf0_aw*_RYFS$g@;|B% zZ%vnv&HN)*Fw7NYS1``445E9@-@^?vtcOlbf?f(v%%3-wMppSCA=nu@6XpJVpaf*Uo8!z@kMa$NO!;MJ1QCoR|Fl0GT2Qc9dFG-}bs9lv z0SWG|M_E(&kjv5<6S4rFKR)jyQs*8Ls^TH_g-Mj-{T07WKnQssRU&PkT0-MjWjee? zH~f#XTWpa!?=(SGXcGv1kkx;+FsZY;RvR~Rm~|h)hkSHYB~|}^^l(PWrfH0&nut?o zVZr2y!Fi#A2LSlQ5%M{hJnR>byj{Sn_zAbpxdC;Q^?@~APHj!h>iec|}mEe(t% z60j48b1|_S*f)41+=C}cn+l0M!9}52-*F4XyKj7TL}~&F>ATRO@7Tc(eXKg5g$A;f z55fpl`I;;j{qwBo&ff94;1#@Z@ZP-*>sk?&Y z3!mw=C42F&IBKkT*dfXxjr3MhPMCtT7j!1Z_4X3nfG6z?c8|G{xr&vdl!N7^iS390-jiDo^IF@GhUuC^q(w=0>+v1$fw!qpzev@lHx5XKlsBUJe^ z`#Q*!e%pyL+zPf7$L;7>M%BLtWSiDJO~vCi#4Cct9~70r#j)8HJt1GU;d1?PeiUOF z`m>cg%mg2EUBzu=5?-XNx~#r-<>s<`obQXSr&nwaOPW*C1XqcBU*A6Ynam-} z|CFl!S#vf@pknePZ2mg7Yq)M|o8PhT$nqqX&0O4qKpuZ`_tkc z?U3jDBiBtYoLKaj0TB`AlKn}hfQz}ZYOvy3?p+B}VK}iR(q8aVN(R9A=?azxVSH>* z0we53N5)wGF`$UrwkL>MBgY*EuMy5#R3;G?D_$jt0D7GIhmOfIZx@Zz455xFds484 zRT%lEh<1t@1qZo(bl9$wMkPidLID{9B1Wj$mJ*MFgsms(RCdHg=g?AW^o(?qMAm8& zZqso}4P7b|#AlRp3JLl;I4jp);+I>oGABAkypEoR!L-{Ks@pU1g7)Isj~um+>-Je-#xY{``5a#!(@MT2%5&vr3(f$ zLA&U|@cblLfIpzJXVV08fD#D~Cz=i?VeF^r@8l__XNRCOyu?nMj5fNB`hSz0J?QlZ zh*L367$UXiJ+%$-8IeU8)PP_NCt?Xo3}~G(^6s^j(_fODSlQb%zQ5L(gBCfR_2J3Vsy>7Ke{Nu;Wqxeu~>TgDgi39XPWkf(Nl6y3+55Rcgis+{0>UNcmPEg<{$P8m`E)B+s+w{H+Tk?b*~-sW0S!!&cWb509|7Df;G#Ox8be(}7iCr2lG+v% z@!v!sR)(VhSzg24PetVtZIi%*UPR8Ks&v*fEC_IxMGl9G&9--(VCEji2M6EGxg~3o zW8&Fc6yG6=o5S~I@AbTWT89I@?tuE{t`SLs`NB(x7mb+c8}araxe3J<0%ik@#cbB7 z_49WllTcIl;`ZYg1x~Q*VJ%0>;Y^Vwn%MV(OFvd|AlYZ?p$dC`P7j5}2XKt0#wVfQ zI3RDf(_dtb{MHJMlRq@^x*j!O937PXR2*weq9pE;=$Fh>4IdqQS^dSwUA_7q-N>QY zE$d>M)@ln>da^w_nI--`=yyjAn5Y(?z(a7`Z%E~HgTg-{1XC{iwrL;PxnW7z^ zWY<0}y1ZEmTVrkdZq}^t!B}WO3(gKDWiAeH0DYa>UkrXAzz6LOoEmgMJ8_- z2?n2ydNKPRagB?x#M-fAI-&Z5Y^?8qMbGW;*2`_VpYHB(BFV8ty?6mu9d`bmD&G)> zP4}c@bsSc|mN@b)b5phsn9Hja(&R(QDw-p=gnL!{(spGqpc9O)+{{b^WK$A96BdRd zsm<&KN4t~oiKTFJ;l6e=;6;at&7i;P30>eudri6crD}(y#Hi_8>wKC-Mw%%VUV8Ux0ukUGUk?lhldjc0&oRS9z44KGzT*8YPkj=Y%8ou_ z_dMp1%dRR>C`6SEbS9HQ7a>$Hw2Zr#l3+ROFSv+JPwj#6U0`bSj? zgCbW-%h~>^f|VV6T8VC4r?sBxkCKvlX`Ua^dqe8u4f={7_Ym*q^dMDiI=fGT$IF@h zNMym5C{EZJHq7(gF zq33{y!Y+V6KxrqXk+i6(ek?RnR$1N&;w$tihQGBp%Pt@n0d)@y8d+~l?3=~E_@+F( zp0EcrEvC1f`+0ILQd3UWnvw@f3y6gEzR#;{+GPrtXd>e?9i@)+h(7yza`4@|$yTHa@d;W@)VZvg z>Hm5Gp2G69E|GYrCh(6_6f)^crut%mlEe&|Te!}!5=t`o(ODB^gU{WX-q)Ir%;`;+ zYqHh6r_+64|AS)S~~*5Oy6fZ^S-j^B}Wz@K!9a#ClhMqy19~9Ii&H<#&v3^lmRYrqQ`4Pk}>K8u-Zp4dgdIS-%7MTn;+* z)sC@V$N5B^&vdchlg&jDOv246lA!}>WFKb&ZUS<8X#K2r9ZYr+ZcmU!hxh*ANVq78 z1qNRK+)tN3$O3bCyB@PNm6VkyS=83YYOT;0j3yy*XeTbKNqvy^Xy`}Inh>fkzRkA8 z)tOs46x)Zbh;K1j`*kHA1CAs#c@P$1Tw)i)S{Q*6&Ay8SbscZjLH_MIuT~{wq;}wT zWVxkkue|C?PuCLB(nh<*0EB_#V({k>FUr68Ds$jE{VL{#*m)lmAXHsd<&ex!X;tKx zyPiLaj}&GC@*Bz^6@!-CUT@YL+TtR(b*PoOnGKaZ zFV5_^+GclEej(BID!{fC{g^MAa`LJ13>mXZ-+<6HpR-tQr<%6xg|sl+Jx?4h|Ey6| z`?E8zXOTd}!Ov7Q$WS#u%Pr8rNmSLXh4e*j4di(tjB~%-b@{q86FU!S|1_3Ji}T@? zk((yCj7JLqMl!UBq^Z-U& z-`!(6{$r7*RW!wI1PrSQPuW)}Gos8jlk@VC9(+E}YxT}2i!2WVhkjCCXg>Errl!r; zn+{LA$djJNGZRpn?h_{m%1+6*EugWTi1Byo^uXEIo}t%(xXVn(f>J~f!EEX5bi9+( z-;;c$fA;0SuMV4p$GuBIRoj~(5gP)n9}BRXt!jpbt(iE+NTSl^@e={;4e?X=!)9eJ zUfjROxXqE|wYh&Y?IKm-U-pLGC5nsLFXqHdwHd+vA7W@1O%lh?(k{3)5;2(P@0|yWV5dF$GfjeHLU4(%Otvd% z*~EC1qvw|daf*7zL#BeLEo7G+a)^CK5fDj~+R~y?`d^6qZ|1(<`_SRU%jM@2%cMeM7R67A%iNw( zeUHVU^1ndas94NZB;<8u27BvHxsEd6(GzRVFbXI>kkz~8%ELoV%b`HqR%+VX^+%(|Xw$5x7TZ(&qlD#b1A!bT5K_7ys^HtF8? zWJZLYacp$Y*)rR0!8}!8!_d$%80({~H4Z<*>TkSNk*avDjuOfHG~?Q7m~SgRu6)jw|!e>xtz|5-l|{Cu5dNaG^0ik0N}$YDTQbN`NB z=sQ%wq66aSi4nx1T**lz$K5Q|WzbHyGZ6?BI<0Z_3#DpbS;rPWM40J+B8(<%&O_Ba z?VEg@S75FmR65G&Xnbta{y&5uF{mmjeSXtj2iIx;dhGx$u4}J<+#(3j!Jb(_35?5Q zMgI!TWJRrH&Ro%k?R|i8OM|cGGRqCS;$V5e?J@T%-y}4^YC`A5b_F?b@H@sIM@Kd% z0K}8H>x%3B;4|b=6$eS0+DLmkgAx;8+PrSh103NYj1j<>bHAENnUDv^87pqDp8sp{e1eWhMqmE>oq%dLFFqh^A8yMt=3_)nWm=Z zOX8nTuQ}~~Jw~R;@3nsIz;xOyy&=7En{i5aY5)goSocWl5M>7Ef z9~g6U4lDA)(1S|n$Jmec_!x6-*%Zd7`&CDIo{!g-_smckkDyU05UkI<>mZAa-%0vL z`SCi4`ux##iuj9qqJ7~h`!fJqjAevg&Imzt$y7eKJVE!8Kda=aL-meZt-$XXXakW$ zGtY(8iflhByN@G?D(n>>%+SO)&6 z$+#S57#vgxavA-s{zBNX1cTn=$ilL?!yg^8R4jmk*p348VBIbwgQMh2nQ7 z(o$X9Qp1LdsA2=2cYaQ3(Tf1jZH%|jU|5Y*B3&B>|ZpimNj_A}#t6ze?a?1p4UQ z_ojh$wy=_5!PdWb*59QZx6V9H z)j8Q|;RS!_ku>?}&#YH6HYh?LK(=zWyBaS`c^z*^UxW%tVYv=;C< z08C1v=2p;sncX^~3rrL>Q{wOS+X?A5rmz5m6SbdMmjqfrP>hB4~A#8G#Kv=?g2WSQ)VPK;1?x|9i`iF2f5O6?BV;R9iP^TdE7 zIX-GstG_3A4`UPfy)~R$#OoN6HV5g##a`Mg1O0c4YeyYZb|? zc%MllBfJt)v%w>Uz5vqx%$!lS0END~-wgMK-0-X_6JOmho!?axEh!nmPq^ z-WK&~7>j}L@aK|sz8;1+q@o$$@~5XKVU_&G6nC)tnh!qK)oiP`%%8GELw|ikKBO5f zdc5Vs2pj+Lsp>OJPPq@r6{cjQnbpui*J!B5s6}YZxS@QXf`aJ# zFal*CtC)n@2M=puLla!z}tjL7W>7t3(;zuHpLU>b8?%2#dP_muRhn& z?t4Sg$JdA{Jf6Mrx8Tmj@NI~`?QGTOw}TI{`%G8YFmEXhi=p@7=P%&fA{iR|PXCD+ zT|57O^NvhEic&h=2DZfSd2Xso9Yz;Ae~m3WzVDS04G9PmIi>ZL7*zx?VU8F&d{QpO z%2=S2Jxmya+>*kur5?&b3(Dl{C+7;GL`u{ z`R}Ukz?W<1?WoRT!t*S;DY)#|p1Gs1F{>{$_ z`9x(_g&jbNXjz9#e@ruYQbfSPnK~RqJ1z!%e)j&4m27^9!0=1$929*$X+V4b{3WA2 z82bc|i>v8XP6tk*)_3*lwG7_FhH*0UaewZe;9=yS*2|Nx>w%IBmPchTi_TjY~Cbo2FbLEz9f~WUs9p zL>>qnwn8xw%3N_>JTKcX7?SFEyUQZ=G4!5<5>0KY>Uhy_dyn-YYjty@z()`ruc1VXfAt;xG6_K(2&n+8ROPoZ@ zY}mhKn3K4V7KSy0O3&|1g!Jkx-cEse4Y4USvz#y=4T(wekL7I+-Zln{TRwKR_3p2x z)r$BgD<^TW)s<&mW8GYIt9D>{de4*0ru~PDroK*ivK4dpopJOMAk)KAmEk`o8zfbV z3fvIhS5NkFldoN@lvbbWn(t+9&m+a4#6+yAe?OnUc+Q&5nVS;W4o-kI!;Klgpa?a( z=Lg6Y6E)O|AW8ow&>W6&C@Z*|k(vwsET*I41cKsl+6ApP>fSCCn06|V`42F`qZ;x& zAC@(SNGVqG?rx7g9Pd~}>BFBJ%yr;zuRl9*=gF2F%b!MB)hiAehxmMtB2{%6Lct7y z{V|p(miDKDC=i1VXQs2G`PdmF=<|4u{lA5e@?7w6e`q+!q8KH^xI&D;UN56cCeg}_ z2>@TOR*p=V;^b0OD-;i?HE2s%6zq&{+!>KmZ8Ry5zVN@3voA4cYh4DFy!mxe5k(tt zXe%qH$66Jw%Pq&NI(nS}Pw0{r%)dRp{f(eCkN~;L9KhI2x+m5wjY&Lb-z;h>h}H`v zUM*qm+dazGQDq%qu_i`=qEKkfTwOFJKak7ieX!i3Tp^E7Xxu1A7KlP)gfOTvZ^s^-OWR86sA z3Dxk&fXzUht{MGW&Hx)M0*5qnef(c-*z@FD#jrxtkrEUMtg$H8PU@Er&GPg3$8${6 zQ`-e*3^afp{DA(vu0V^&_KTA>n=>4g&$_}xrOqshVnzPrHX-xfwixQ9A+xw;l)wJs z$v;n&+-b9|>VO?PuR)V>|K=%w_3O%J_XCn7%d}iS-(}R+1%pFdJq@^@k_ZD+0R61& zD2rVNSE~L)=8^gJR(Hml^(IlU{hDW6{_tXjwAUrhj3o-JDQD}i)qwS!(ucG~OPj)m zeNi$A*nAEA8E0J0e`D)G3r-^)Ya}rmIxGy*Cmyu*<`ln+1U-iC#qWH`$o0Pd;uOC9 zyX2=C3I{>mX)-=ryucAl`>23fHrr41t*##z@#DF7X%e1 zOZQG$-J${kqwtMr$)VO5w@u!!{ z>S{yvVthcioJ5P>CU3EX+QwaCW_8OM85w`X zBR)E8=KURFa^-DF;)cLJ7ad}M0C~m577--w!+XxQUZ_otNn(64!57(H%t776O9~N3 zbvBI~;j`ATK}WT0kS6O&?8yF?i>}=)E{u2 z4qXRZYS`>A#X)F~C?OZSi;vY=BZfd}vY)c(D;P(ffHja$+R5JP))x#{W53j>)g0gX z7tnL8dyAXmVVi^S-}G3^5`KUL6hx$KXp zvs?1Tng%D{MFaH~Nmz{nG4k(hPhKE|&$hi6bLw6A67|>e#)UhrYU|

    -DZ!fI-O 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

    *11(UQ%p?#Wx0Gz&4{CLV%U5fRiKB8 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 literal 0 HcmV?d00001 diff --git a/bundles/org.openhab.binding.lcn/doc/ir.png b/bundles/org.openhab.binding.lcn/doc/ir.png new file mode 100644 index 0000000000000000000000000000000000000000..c287edb0eee96f4fb5902f7a3e393a8dc2ff0501 GIT binary patch literal 110156 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 #0R0L8;j;mzP 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 literal 0 HcmV?d00001 diff --git a/bundles/org.openhab.binding.lcn/pom.xml b/bundles/org.openhab.binding.lcn/pom.xml new file mode 100644 index 0000000000000..eccef96b93fe3 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/pom.xml @@ -0,0 +1,17 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 2.5.6-SNAPSHOT + + + org.openhab.binding.lcn + + openHAB Add-ons :: Bundles :: LCN Binding + + diff --git a/bundles/org.openhab.binding.lcn/src/main/feature/feature.xml b/bundles/org.openhab.binding.lcn/src/main/feature/feature.xml new file mode 100644 index 0000000000000..4b803e077eff1 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/feature/feature.xml @@ -0,0 +1,9 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + mvn:org.openhab.addons.bundles/org.openhab.binding.lcn/${project.version} + + diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/DimmerOutputProfile.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/DimmerOutputProfile.java new file mode 100644 index 0000000000000..5c7feb50d2c78 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/DimmerOutputProfile.java @@ -0,0 +1,119 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal; + +import java.math.BigDecimal; +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.DecimalType; +import org.eclipse.smarthome.core.library.types.OnOffType; +import org.eclipse.smarthome.core.thing.profiles.ProfileCallback; +import org.eclipse.smarthome.core.thing.profiles.ProfileContext; +import org.eclipse.smarthome.core.thing.profiles.ProfileTypeUID; +import org.eclipse.smarthome.core.thing.profiles.StateProfile; +import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.core.types.State; +import org.openhab.binding.lcn.internal.common.DimmerOutputCommand; +import org.openhab.binding.lcn.internal.common.LcnDefs; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A profile to control multiple dimmer outputs simultaneously with ramp. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class DimmerOutputProfile implements StateProfile { + private final Logger logger = LoggerFactory.getLogger(DimmerOutputProfile.class); + /** The Profile's UID */ + static final ProfileTypeUID UID = new ProfileTypeUID(LcnBindingConstants.BINDING_ID, "output"); + private final ProfileCallback callback; + private int rampMs; + private boolean controlAllOutputs; + private boolean controlOutputs12; + + public DimmerOutputProfile(ProfileCallback callback, ProfileContext profileContext) { + this.callback = callback; + + Optional ramp = getConfig(profileContext, "ramp"); + Optional allOutputs = getConfig(profileContext, "controlAllOutputs"); + Optional outputs12 = getConfig(profileContext, "controlOutputs12"); + + ramp.ifPresent(b -> { + if (b instanceof BigDecimal) { + rampMs = (int) (((BigDecimal) b).doubleValue() * 1000); + } else { + logger.warn("Could not parse 'ramp', unexpected type, should be float: {}", ramp); + } + }); + + allOutputs.ifPresent(b -> { + if (b instanceof Boolean) { + controlAllOutputs = true; + } else { + logger.warn("Could not parse 'controlAllOutputs', unexpected type, should be true/false: {}", b); + } + }); + + outputs12.ifPresent(b -> { + if (b instanceof Boolean) { + controlOutputs12 = true; + } else { + logger.warn("Could not parse 'controlOutputs12', unexpected type, should be true/false: {}", b); + } + }); + } + + private Optional getConfig(ProfileContext profileContext, String key) { + return Optional.ofNullable(profileContext.getConfiguration().get(key)); + } + + @Override + public void onCommandFromItem(Command command) { + if (rampMs != 0 && rampMs != LcnDefs.FIXED_RAMP_MS && controlOutputs12) { + logger.warn("Unsupported 'ramp' setting. Will be forced to 250ms: {}", rampMs); + } + BigDecimal value; + if (command instanceof DecimalType) { + value = ((DecimalType) command).toBigDecimal(); + } else if (command instanceof OnOffType) { + value = ((OnOffType) command) == OnOffType.ON ? BigDecimal.valueOf(100) : BigDecimal.ZERO; + } else { + logger.warn("Unsupported type: {}", command.toFullString()); + return; + } + callback.handleCommand(new DimmerOutputCommand(value, controlAllOutputs, controlOutputs12, rampMs)); + } + + @Override + public void onStateUpdateFromHandler(State state) { + callback.sendUpdate(state); + } + + @Override + public ProfileTypeUID getProfileTypeUID() { + return UID; + } + + @Override + public void onCommandFromHandler(Command command) { + // nothing + } + + @Override + public void onStateUpdateFromItem(State state) { + // nothing + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/ILcnModuleActions.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/ILcnModuleActions.java new file mode 100644 index 0000000000000..a6f2871e44321 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/ILcnModuleActions.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link ILcnModuleActions} defines the interface for all thing actions supported by the binding. + * These methods, parameters, and return types are explained in {@link LcnModuleActions}. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public interface ILcnModuleActions { + void hitKey(@Nullable String table, int key, @Nullable String action); + + void flickerOutput(int output, int depth, int ramp, int count); + + void sendDynamicText(int row, @Nullable String textInput); +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnBindingConstants.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnBindingConstants.java new file mode 100644 index 0000000000000..39c7d2b4ba62a --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnBindingConstants.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.thing.ThingTypeUID; + +/** + * The {@link LcnBindingConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnBindingConstants { + /** The scope name of this binding */ + public static final String BINDING_ID = "lcn"; + /** + * Firmware version of the measurement processing since 2013. It has more variables and thresholds and event-based + * variable updates. + */ + public static final int FIRMWARE_2013 = 0x170206; + /** Firmware version which supports controlling all 4 outputs simultaneously */ + public static final int FIRMWARE_2014 = 0x180501; + /** List of all Thing Type UIDs */ + public static final ThingTypeUID THING_TYPE_PCK_GATEWAY = new ThingTypeUID(BINDING_ID, "pckGateway"); + public static final ThingTypeUID THING_TYPE_MODULE = new ThingTypeUID(BINDING_ID, "module"); + public static final ThingTypeUID THING_TYPE_GROUP = new ThingTypeUID(BINDING_ID, "group"); + /** Regex for address in PCK protocol */ + public static final String ADDRESS_REGEX = "[:=%]M(?\\d{3})(?\\d{3})"; + /** LCN coding for ACK */ + public static final int CODE_ACK = -1; +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnChannelVariableConfiguration.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnChannelVariableConfiguration.java new file mode 100644 index 0000000000000..6c3713cbcfe21 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnChannelVariableConfiguration.java @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LcnChannelVariableConfiguration} class contains configuration field mapping for Channels of type + * 'variable'. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnChannelVariableConfiguration { + public String unit = "native"; +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnGroupConfiguration.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnGroupConfiguration.java new file mode 100644 index 0000000000000..165966cdc85a0 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnGroupConfiguration.java @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LcnModuleConfiguration} class contains fields mapping thing configuration parameters. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnGroupConfiguration extends LcnModuleConfiguration { + public int groupId; +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnGroupHandler.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnGroupHandler.java new file mode 100644 index 0000000000000..5ce3f6bdfeac3 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnGroupHandler.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.thing.Thing; +import org.openhab.binding.lcn.internal.common.LcnAddr; +import org.openhab.binding.lcn.internal.common.LcnAddrGrp; +import org.openhab.binding.lcn.internal.common.LcnException; + +/** + * The {@link LcnGroupHandler} is responsible for handling commands, which are + * addressed to an LCN group. + * + * The module in the field moduleAddress is used for state updates of the group as representative for all modules in + * the group. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnGroupHandler extends LcnModuleHandler { + private @Nullable LcnAddrGrp groupAddress; + + public LcnGroupHandler(Thing thing) { + super(thing); + } + + @Override + public void initialize() { + LcnGroupConfiguration localConfig = getConfigAs(LcnGroupConfiguration.class); + groupAddress = new LcnAddrGrp(localConfig.segmentId, localConfig.groupId); + + super.initialize(); + } + + @Override + protected LcnAddr getCommandAddress() throws LcnException { + LcnAddrGrp localAddress = groupAddress; + if (localAddress == null) { + throw new LcnException("LCN group address not set"); + } + return localAddress; + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnHandlerFactory.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnHandlerFactory.java new file mode 100644 index 0000000000000..b66e61f9d7ddd --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnHandlerFactory.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal; + +import static org.openhab.binding.lcn.internal.LcnBindingConstants.*; + +import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.thing.Bridge; +import org.eclipse.smarthome.core.thing.Thing; +import org.eclipse.smarthome.core.thing.ThingTypeUID; +import org.eclipse.smarthome.core.thing.binding.BaseThingHandlerFactory; +import org.eclipse.smarthome.core.thing.binding.ThingHandler; +import org.eclipse.smarthome.core.thing.binding.ThingHandlerFactory; +import org.osgi.service.component.annotations.Component; + +/** + * The {@link LcnHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +@Component(configurationPid = "binding.lcn", service = ThingHandlerFactory.class) +public class LcnHandlerFactory extends BaseThingHandlerFactory { + private static final Set SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet( + Stream.of(THING_TYPE_PCK_GATEWAY, THING_TYPE_MODULE, THING_TYPE_GROUP).collect(Collectors.toSet())); + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + @Override + protected @Nullable ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (THING_TYPE_GROUP.equals(thingTypeUID)) { + return new LcnGroupHandler(thing); + } + + if (THING_TYPE_MODULE.equals(thingTypeUID)) { + return new LcnModuleHandler(thing); + } + + if (THING_TYPE_PCK_GATEWAY.equals(thingTypeUID)) { + return new PckGatewayHandler((Bridge) thing); + } + return null; + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnModuleActions.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnModuleActions.java new file mode 100644 index 0000000000000..2dd7524a44f8f --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnModuleActions.java @@ -0,0 +1,202 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal; + +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.nio.ByteBuffer; +import java.util.Arrays; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.thing.binding.ThingActions; +import org.eclipse.smarthome.core.thing.binding.ThingActionsScope; +import org.eclipse.smarthome.core.thing.binding.ThingHandler; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnDefs; +import org.openhab.binding.lcn.internal.common.LcnDefs.KeyTable; +import org.openhab.binding.lcn.internal.common.LcnDefs.SendKeyCommand; +import org.openhab.binding.lcn.internal.common.LcnException; +import org.openhab.binding.lcn.internal.common.PckGenerator; +import org.openhab.core.automation.annotation.ActionInput; +import org.openhab.core.automation.annotation.RuleAction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Handles actions requested to be sent to an LCN module. + * + * @author Fabian Wolter - Initial contribution + */ +@ThingActionsScope(name = "lcn") +@NonNullByDefault +public class LcnModuleActions implements ThingActions, ILcnModuleActions { + private final Logger logger = LoggerFactory.getLogger(LcnModuleActions.class); + private static final int DYN_TEXT_CHUNK_COUNT = 5; + private static final int DYN_TEXT_HEADER_LENGTH = 6; + private static final int DYN_TEXT_CHUNK_LENGTH = 12; + private @Nullable LcnModuleHandler moduleHandler; + + @Override + public void setThingHandler(@Nullable ThingHandler handler) { + this.moduleHandler = (LcnModuleHandler) handler; + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return moduleHandler; + } + + @Override + @RuleAction(label = "LCN Hit Key", description = "Sends a \"hit key\" command to an LCN module") + public void hitKey( + @ActionInput(name = "table", required = true, type = "java.lang.String", label = "Table", description = "The key table (A-D)") @Nullable String table, + @ActionInput(name = "key", required = true, type = "java.lang.Integer", label = "Key", description = "The key number (1-8)") int key, + @ActionInput(name = "action", required = true, type = "java.lang.String", label = "Action", description = "The action (HIT, MAKE, BREAK)") @Nullable String action) { + try { + if (table == null) { + throw new LcnException("Table is not set"); + } + + if (action == null) { + throw new LcnException("Action is not set"); + } + + KeyTable keyTable; + try { + keyTable = LcnDefs.KeyTable.valueOf(table.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new LcnException("Unknown key table: " + table); + } + + SendKeyCommand sendKeyCommand; + try { + sendKeyCommand = SendKeyCommand.valueOf(action.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new LcnException("Unknown action: " + action); + } + + if (!LcnChannelGroup.KEYLOCKTABLEA.isValidId(key - 1)) { + throw new LcnException("Key number is out of range: " + key); + } + + SendKeyCommand[] cmds = new SendKeyCommand[LcnDefs.KEY_TABLE_COUNT]; + Arrays.fill(cmds, SendKeyCommand.DONTSEND); + boolean[] keys = new boolean[LcnChannelGroup.KEYLOCKTABLEA.getCount()]; + + int keyTableNumber = keyTable.name().charAt(0) - LcnDefs.KeyTable.A.name().charAt(0); + cmds[keyTableNumber] = sendKeyCommand; + keys[key - 1] = true; + + getHandler().sendPck(PckGenerator.sendKeys(cmds, keys)); + } catch (LcnException e) { + logger.warn("Could not execute hit key command: {}", e.getMessage()); + } + } + + @Override + @RuleAction(label = "LCN Flicker Output", description = "Let a dimmer output flicker for a given count of flashes") + public void flickerOutput( + @ActionInput(name = "output", type = "java.lang.Integer", required = true, label = "Output", description = "The output number (1-4)") int output, + @ActionInput(name = "depth", type = "java.lang.Integer", label = "Depth", description = "0=25% 1=50% 2=100%") int depth, + @ActionInput(name = "ramp", type = "java.lang.Integer", label = "Ramp", description = "0=2sec 1=1sec 2=0.5sec") int ramp, + @ActionInput(name = "count", type = "java.lang.Integer", label = "Count", description = "Number of flashes (1-15)") int count) { + try { + getHandler().sendPck(PckGenerator.flickerOutput(output - 1, depth, ramp, count)); + } catch (LcnException e) { + logger.warn("Could not send output flicker command: {}", e.getMessage()); + } + } + + @Override + @RuleAction(label = "LCN Dynamic Text", description = "Send custom text to an LCN-GTxD display") + public void sendDynamicText( + @ActionInput(name = "row", type = "java.lang.Integer", required = true, label = "Row", description = "Display the text on the LCN-GTxD in the given row number (1-4)") int row, + @ActionInput(name = "text", type = "java.lang.String", label = "Text", description = "The text to display (max. 60 chars/bytes)") @Nullable String textInput) { + try { + String text = textInput; + + if (text == null) { + text = new String(); + } + + // convert String to bytes to split the data every 12 bytes, because a unicode character can take more than + // one byte + ByteBuffer bb = ByteBuffer.wrap(text.getBytes(LcnDefs.LCN_ENCODING)); + + if (bb.capacity() > DYN_TEXT_CHUNK_LENGTH * DYN_TEXT_CHUNK_COUNT) { + logger.warn("Dynamic text truncated. Has {} bytes: '{}'", bb.capacity(), text); + } + + bb.limit(Math.min(DYN_TEXT_CHUNK_LENGTH * DYN_TEXT_CHUNK_COUNT, bb.capacity())); + + int part = 0; + while (bb.hasRemaining()) { + byte[] chunk = new byte[DYN_TEXT_CHUNK_LENGTH]; + bb.get(chunk, 0, Math.min(bb.remaining(), DYN_TEXT_CHUNK_LENGTH)); + + ByteBuffer command = ByteBuffer.allocate(DYN_TEXT_HEADER_LENGTH + DYN_TEXT_CHUNK_LENGTH); + command.put(PckGenerator.dynTextHeader(row - 1, part++).getBytes(LcnDefs.LCN_ENCODING)); + command.put(chunk); + + getHandler().sendPck(command.array()); + } + } catch (IllegalArgumentException | LcnException e) { + logger.warn("Could not send dynamic text: {}", e.getMessage()); + } + } + + private static ILcnModuleActions invokeMethodOf(@Nullable ThingActions actions) { + if (actions == null) { + throw new IllegalArgumentException("actions cannot be null"); + } + if (actions.getClass().getName().equals(LcnModuleActions.class.getName())) { + if (actions instanceof LcnModuleActions) { + return (ILcnModuleActions) actions; + } else { + return (ILcnModuleActions) Proxy.newProxyInstance(ILcnModuleActions.class.getClassLoader(), + new Class[] { ILcnModuleActions.class }, (Object proxy, Method method, Object[] args) -> { + Method m = actions.getClass().getDeclaredMethod(method.getName(), + method.getParameterTypes()); + return m.invoke(actions, args); + }); + } + } + throw new IllegalArgumentException("Actions is not an instance of EcobeeActions"); + } + + /** Static alias to support the old DSL rules engine and make the action available there. */ + public static void hitKey(@Nullable ThingActions actions, @Nullable String table, int key, + @Nullable String action) { + invokeMethodOf(actions).hitKey(table, key, action); + } + + /** Static alias to support the old DSL rules engine and make the action available there. */ + public static void flickerOutput(@Nullable ThingActions actions, int output, int depth, int ramp, int count) { + invokeMethodOf(actions).flickerOutput(output, depth, ramp, count); + } + + /** Static alias to support the old DSL rules engine and make the action available there. */ + public static void sendDynamicText(@Nullable ThingActions actions, int row, @Nullable String text) { + invokeMethodOf(actions).sendDynamicText(row, text); + } + + private LcnModuleHandler getHandler() throws LcnException { + LcnModuleHandler localModuleHandler = moduleHandler; + if (localModuleHandler != null) { + return localModuleHandler; + } else { + throw new LcnException("Handler not set"); + } + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnModuleConfiguration.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnModuleConfiguration.java new file mode 100644 index 0000000000000..aa845f77027ab --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnModuleConfiguration.java @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LcnModuleConfiguration} class contains fields mapping thing configuration parameters. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleConfiguration { + public int segmentId; + public int moduleId; +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnModuleDiscoveryService.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnModuleDiscoveryService.java new file mode 100644 index 0000000000000..8ab89ce32297b --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnModuleDiscoveryService.java @@ -0,0 +1,264 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal; + +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.config.discovery.AbstractDiscoveryService; +import org.eclipse.smarthome.config.discovery.DiscoveryResultBuilder; +import org.eclipse.smarthome.config.discovery.DiscoveryService; +import org.eclipse.smarthome.core.thing.ThingTypeUID; +import org.eclipse.smarthome.core.thing.ThingUID; +import org.eclipse.smarthome.core.thing.binding.ThingHandler; +import org.eclipse.smarthome.core.thing.binding.ThingHandlerService; +import org.openhab.binding.lcn.internal.common.LcnAddrMod; +import org.openhab.binding.lcn.internal.connection.Connection; +import org.openhab.binding.lcn.internal.subhandler.LcnModuleMetaAckSubHandler; +import org.openhab.binding.lcn.internal.subhandler.LcnModuleMetaFirmwareSubHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Scans all LCN segments for LCN modules. + * + * Scan approach: + * 1. Send "Leerkomando" to the broadcast address with request for Ack set + * 2. For every received Ack, send the following requests to the module: + * - serial number request (SN) + * - module's name first part request (NM1) + * - module's name second part request (NM2) + * 3. When all three messages have been received, fire thingDiscovered() + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class LcnModuleDiscoveryService extends AbstractDiscoveryService + implements DiscoveryService, ThingHandlerService { + private final Logger logger = LoggerFactory.getLogger(LcnModuleDiscoveryService.class); + private static final Pattern NAME_PATTERN = Pattern + .compile("=M(?\\d{3})(?\\d{3}).N(?[1-2]{1})(?.*)"); + private static final String SEGMENT_ID = "segmentId"; + private static final String MODULE_ID = "moduleId"; + private static final String SERIAL_NUMBER = "serialNumber"; + private static final int MODULE_NAME_PART_COUNT = 2; + private static final int DISCOVERY_TIMEOUT_SEC = 90; + private static final int ACK_TIMEOUT_MS = 1000; + private static final Set SUPPORTED_THING_TYPES_UIDS = Collections + .unmodifiableSet(Stream.of(LcnBindingConstants.THING_TYPE_MODULE).collect(Collectors.toSet())); + private @Nullable PckGatewayHandler bridgeHandler; + private final Map> moduleNames = new HashMap<>(); + private final Map discoveryResultBuilders = new ConcurrentHashMap<>(); + private final List successfullyDiscovered = new LinkedList<>(); + private final Queue<@Nullable LcnAddrMod> serialNumberRequestQueue = new ConcurrentLinkedQueue<>(); + private final Queue<@Nullable LcnAddrMod> moduleNameRequestQueue = new ConcurrentLinkedQueue<>(); + private @Nullable volatile ScheduledFuture queueProcessor; + private @Nullable ScheduledFuture builderTask; + + public LcnModuleDiscoveryService() { + super(SUPPORTED_THING_TYPES_UIDS, DISCOVERY_TIMEOUT_SEC, false); + } + + @Override + public void setThingHandler(@Nullable ThingHandler handler) { + if (handler instanceof PckGatewayHandler) { + this.bridgeHandler = (PckGatewayHandler) handler; + } + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return bridgeHandler; + } + + @Override + public void deactivate() { + stopScan(); + super.deactivate(); + } + + @Override + protected void startScan() { + synchronized (this) { + PckGatewayHandler localBridgeHandler = bridgeHandler; + if (localBridgeHandler == null) { + logger.warn("Bridge handler not set"); + return; + } + + ScheduledFuture localBuilderTask = builderTask; + if (localBridgeHandler.getConnection() == null && localBuilderTask != null) { + localBuilderTask.cancel(true); + } + + localBridgeHandler.registerPckListener(data -> { + Matcher matcher; + + if ((matcher = LcnModuleMetaAckSubHandler.PATTERN_POS.matcher(data)).matches() + || (matcher = LcnModuleMetaFirmwareSubHandler.PATTERN.matcher(data)).matches() + || (matcher = NAME_PATTERN.matcher(data)).matches()) { + synchronized (LcnModuleDiscoveryService.this) { + Connection connection = localBridgeHandler.getConnection(); + + if (connection == null) { + return; + } + + LcnAddrMod addr = new LcnAddrMod( + localBridgeHandler.toLogicalSegmentId(Integer.parseInt(matcher.group("segId"))), + Integer.parseInt(matcher.group("modId"))); + + if (matcher.pattern() == LcnModuleMetaAckSubHandler.PATTERN_POS) { + // Received an ACK frame + + // The module could send an Ack with a response to another command. So, ignore the Ack, when + // we received our data already. + if (!discoveryResultBuilders.containsKey(addr)) { + serialNumberRequestQueue.add(addr); + rescheduleQueueProcessor(); // delay request of serial until all modules finished ACKing + } + + Map localNameParts = moduleNames.get(addr); + if (localNameParts == null || localNameParts.size() != MODULE_NAME_PART_COUNT) { + moduleNameRequestQueue.add(addr); + rescheduleQueueProcessor(); // delay request of names until all modules finished ACKing + } + } else if (matcher.pattern() == LcnModuleMetaFirmwareSubHandler.PATTERN) { + // Received a firmware version info frame + + ThingUID bridgeUid = localBridgeHandler.getThing().getUID(); + String serialNumber = matcher.group("sn"); + ThingUID thingUid = new ThingUID(LcnBindingConstants.THING_TYPE_MODULE, bridgeUid, + serialNumber); + + Map properties = new HashMap<>(3); + properties.put(SEGMENT_ID, addr.getSegmentId()); + properties.put(MODULE_ID, addr.getModuleId()); + properties.put(SERIAL_NUMBER, serialNumber); + + DiscoveryResultBuilder discoveryResult = DiscoveryResultBuilder.create(thingUid) + .withProperties(properties).withRepresentationProperty(SERIAL_NUMBER) + .withBridge(bridgeUid); + + discoveryResultBuilders.put(addr, discoveryResult); + } else if (matcher.pattern() == NAME_PATTERN) { + // Received part of a module's name frame + + final int part = Integer.parseInt(matcher.group("part")) - 1; + final String name = matcher.group("name"); + + moduleNames.compute(addr, (partNumber, namePart) -> { + Map namePartMapping = namePart; + if (namePartMapping == null) { + namePartMapping = new HashMap<>(); + } + + namePartMapping.put(part, name); + + return namePartMapping; + }); + } + } + } + }); + + builderTask = scheduler.scheduleWithFixedDelay(() -> { + synchronized (LcnModuleDiscoveryService.this) { + discoveryResultBuilders.entrySet().stream().filter(e -> { + Map localNameParts = moduleNames.get(e.getKey()); + return localNameParts != null && localNameParts.size() == MODULE_NAME_PART_COUNT; + }).filter(e -> !successfullyDiscovered.contains(e.getKey())).forEach(e -> { + StringBuilder thingName = new StringBuilder(); + if (e.getKey().getSegmentId() != 0) { + thingName.append("Segment " + e.getKey().getSegmentId() + " "); + } + + thingName.append("Module " + e.getKey().getModuleId() + ": "); + Map localNameParts = moduleNames.get(e.getKey()); + if (localNameParts != null) { + thingName.append(localNameParts.get(0)); + thingName.append(localNameParts.get(1)); + + thingDiscovered(e.getValue().withLabel(thingName.toString()).build()); + successfullyDiscovered.add(e.getKey()); + } + }); + } + }, 500, 500, TimeUnit.MILLISECONDS); + + localBridgeHandler.sendModuleDiscoveryCommand(); + } + } + + private synchronized void rescheduleQueueProcessor() { + // delay serial number and module name requests to not clog the bus + ScheduledFuture localQueueProcessor = queueProcessor; + if (localQueueProcessor != null) { + localQueueProcessor.cancel(true); + } + queueProcessor = scheduler.scheduleWithFixedDelay(() -> { + PckGatewayHandler localBridgeHandler = bridgeHandler; + if (localBridgeHandler != null) { + LcnAddrMod serial = serialNumberRequestQueue.poll(); + if (serial != null) { + localBridgeHandler.sendSerialNumberRequest(serial); + } + + LcnAddrMod name = moduleNameRequestQueue.poll(); + if (name != null) { + localBridgeHandler.sendModuleNameRequest(name); + } + + // stop scan when all LCN modules have been requested + if (serial == null && name == null) { + scheduler.schedule(this::stopScan, ACK_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } + } + }, ACK_TIMEOUT_MS, ACK_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } + + @Override + public synchronized void stopScan() { + ScheduledFuture localBuilderTask = builderTask; + if (localBuilderTask != null) { + localBuilderTask.cancel(true); + } + ScheduledFuture localQueueProcessor = queueProcessor; + if (localQueueProcessor != null) { + localQueueProcessor.cancel(true); + } + PckGatewayHandler localBridgeHandler = bridgeHandler; + if (localBridgeHandler != null) { + localBridgeHandler.removeAllPckListeners(); + } + successfullyDiscovered.clear(); + moduleNames.clear(); + + super.stopScan(); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnModuleHandler.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnModuleHandler.java new file mode 100644 index 0000000000000..32ea98c7e35b3 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnModuleHandler.java @@ -0,0 +1,363 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.library.types.DecimalType; +import org.eclipse.smarthome.core.library.types.HSBType; +import org.eclipse.smarthome.core.library.types.OnOffType; +import org.eclipse.smarthome.core.library.types.PercentType; +import org.eclipse.smarthome.core.library.types.QuantityType; +import org.eclipse.smarthome.core.library.types.StopMoveType; +import org.eclipse.smarthome.core.library.types.StringType; +import org.eclipse.smarthome.core.library.types.UpDownType; +import org.eclipse.smarthome.core.thing.Bridge; +import org.eclipse.smarthome.core.thing.Channel; +import org.eclipse.smarthome.core.thing.ChannelUID; +import org.eclipse.smarthome.core.thing.Thing; +import org.eclipse.smarthome.core.thing.ThingStatus; +import org.eclipse.smarthome.core.thing.ThingStatusDetail; +import org.eclipse.smarthome.core.thing.binding.BaseThingHandler; +import org.eclipse.smarthome.core.thing.binding.ThingHandlerService; +import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.core.types.RefreshType; +import org.eclipse.smarthome.core.types.State; +import org.openhab.binding.lcn.internal.common.DimmerOutputCommand; +import org.openhab.binding.lcn.internal.common.LcnAddr; +import org.openhab.binding.lcn.internal.common.LcnAddrMod; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnException; +import org.openhab.binding.lcn.internal.connection.Connection; +import org.openhab.binding.lcn.internal.connection.ModInfo; +import org.openhab.binding.lcn.internal.converter.Converter; +import org.openhab.binding.lcn.internal.converter.Converters; +import org.openhab.binding.lcn.internal.converter.S0Converter; +import org.openhab.binding.lcn.internal.subhandler.AbstractLcnModuleSubHandler; +import org.openhab.binding.lcn.internal.subhandler.LcnModuleMetaAckSubHandler; +import org.openhab.binding.lcn.internal.subhandler.LcnModuleMetaFirmwareSubHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link LcnModuleHandler} is responsible for handling commands, which are + * sent to or received from one of the channels. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleHandler extends BaseThingHandler { + private final Logger logger = LoggerFactory.getLogger(LcnModuleHandler.class); + private static final Map CONVERTERS = new HashMap<>(); + private @Nullable LcnAddrMod moduleAddress; + private final Map subHandlers = new HashMap<>(); + private final List metadataSubHandlers = new ArrayList<>(); + private final Map converters = new HashMap<>(); + + static { + CONVERTERS.put("temperature", Converters.TEMPERATURE); + CONVERTERS.put("light", Converters.LIGHT); + CONVERTERS.put("co2", Converters.CO2); + CONVERTERS.put("current", Converters.CURRENT); + CONVERTERS.put("voltage", Converters.VOLTAGE); + CONVERTERS.put("angle", Converters.ANGLE); + CONVERTERS.put("windspeed", Converters.WINDSPEED); + } + + public LcnModuleHandler(Thing thing) { + super(thing); + } + + @Override + public void initialize() { + LcnModuleConfiguration localConfig = getConfigAs(LcnModuleConfiguration.class); + LcnAddrMod localModuleAddress = moduleAddress = new LcnAddrMod(localConfig.segmentId, localConfig.moduleId); + + try { + // create sub handlers + ModInfo info = getPckGatewayHandler().getModInfo(localModuleAddress); + for (LcnChannelGroup type : LcnChannelGroup.values()) { + subHandlers.put(type, type.createSubHandler(this, info)); + } + + // meta sub handlers, which are not assigned to a channel group + metadataSubHandlers.add(new LcnModuleMetaAckSubHandler(this, info)); + metadataSubHandlers.add(new LcnModuleMetaFirmwareSubHandler(this, info)); + + // initialize variable value converters + for (Channel channel : thing.getChannels()) { + Object unitObject = channel.getConfiguration().get("unit"); + Object parameterObject = channel.getConfiguration().get("parameter"); + + if (unitObject instanceof String) { + switch ((String) unitObject) { + case "power": + case "energy": + converters.put(channel.getUID(), new S0Converter(parameterObject)); + break; + default: + if (CONVERTERS.containsKey(unitObject)) { + converters.put(channel.getUID(), CONVERTERS.get(unitObject)); + } + break; + } + } + } + + // module is assumed as online, when the corresponding Bridge (PckGatewayHandler) is online. + updateStatus(ThingStatus.ONLINE); + } catch (LcnException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage()); + } + } + + @Override + public void handleCommand(ChannelUID channelUid, Command command) { + try { + String groupId = channelUid.getGroupId(); + + if (!channelUid.isInGroup()) { + return; + } + + if (groupId == null) { + throw new LcnException("Group ID is null"); + } + + LcnChannelGroup channelGroup = LcnChannelGroup.valueOf(groupId.toUpperCase()); + AbstractLcnModuleSubHandler subHandler = subHandlers.get(channelGroup); + + if (subHandler == null) { + throw new LcnException("Sub Handler not found for: " + channelGroup); + } + + Optional number = channelUidToChannelNumber(channelUid, channelGroup); + + if (command instanceof RefreshType) { + number.ifPresent(n -> subHandler.handleRefresh(channelGroup, n)); + subHandler.handleRefresh(channelUid.getIdWithoutGroup()); + } else if (command instanceof OnOffType) { + subHandler.handleCommandOnOff((OnOffType) command, channelGroup, number.get()); + } else if (command instanceof DimmerOutputCommand) { + subHandler.handleCommandDimmerOutput((DimmerOutputCommand) command, number.get()); + } else if (command instanceof PercentType && number.isPresent()) { + subHandler.handleCommandPercent((PercentType) command, channelGroup, number.get()); + } else if (command instanceof HSBType) { + subHandler.handleCommandHsb((HSBType) command, channelUid.getIdWithoutGroup()); + } else if (command instanceof PercentType) { + subHandler.handleCommandPercent((PercentType) command, channelGroup, channelUid.getIdWithoutGroup()); + } else if (command instanceof StringType) { + subHandler.handleCommandString((StringType) command, number.get()); + } else if (command instanceof DecimalType) { + DecimalType decimalType = (DecimalType) command; + DecimalType nativeValue = getConverter(channelUid).onCommandFromItem(decimalType.doubleValue()); + subHandler.handleCommandDecimal(nativeValue, channelGroup, number.get()); + } else if (command instanceof QuantityType) { + QuantityType quantityType = (QuantityType) command; + DecimalType nativeValue = getConverter(channelUid).onCommandFromItem(quantityType); + subHandler.handleCommandDecimal(nativeValue, channelGroup, number.get()); + } else if (command instanceof UpDownType) { + subHandler.handleCommandUpDown((UpDownType) command, channelGroup, number.get()); + } else if (command instanceof StopMoveType) { + subHandler.handleCommandStopMove((StopMoveType) command, channelGroup, number.get()); + } else { + throw new LcnException("Unsupported command type"); + } + } catch (IllegalArgumentException | NoSuchElementException | LcnException e) { + logger.warn("{}: Failed to handle command {}: {}", channelUid, command.getClass().getSimpleName(), + e.getMessage()); + } + } + + @NonNullByDefault({}) // getOrDefault() + private Converter getConverter(ChannelUID channelUid) { + return converters.getOrDefault(channelUid, Converters.IDENTITY); + } + + /** + * Invoked when a PCK messages arrives from the PCK gateway + * + * @param pck the message without line termination + */ + @SuppressWarnings("null") + public void handleStatusMessage(String pck) { + for (AbstractLcnModuleSubHandler handler : subHandlers.values()) { + if (handler.tryParse(pck)) { + break; + } + } + + metadataSubHandlers.forEach(h -> h.tryParse(pck)); + } + + private Optional channelUidToChannelNumber(ChannelUID channelUid, LcnChannelGroup channelGroup) + throws LcnException { + try { + int number = Integer.parseInt(channelUid.getIdWithoutGroup()) - 1; + + if (!channelGroup.isValidId(number)) { + throw new LcnException("Out of range: " + number); + } + return Optional.of(number); + } catch (NumberFormatException e) { + return Optional.empty(); + } + } + + private PckGatewayHandler getPckGatewayHandler() throws LcnException { + Bridge bridge = getBridge(); + if (bridge == null) { + throw new LcnException("No LCN-PCK gateway configured for this module"); + } + + PckGatewayHandler handler = (PckGatewayHandler) bridge.getHandler(); + if (handler == null) { + throw new LcnException("Could not get PckGatewayHandler"); + } + return handler; + } + + /** + * Queues a PCK string for sending. + * + * @param command without the address part + * @throws LcnException when the module address is unknown + */ + public void sendPck(String command) throws LcnException { + getPckGatewayHandler().queue(getCommandAddress(), true, command); + } + + /** + * Queues a PCK byte buffer for sending. + * + * @param command without the address part + * @throws LcnException when the module address is unknown + */ + public void sendPck(byte[] command) throws LcnException { + getPckGatewayHandler().queue(getCommandAddress(), true, command); + } + + /** + * Gets the address, which shall be used when sending commands into the LCN bus. This can also be a group address. + * + * @return the address to send to + * @throws LcnException when the address is unknown + */ + protected LcnAddr getCommandAddress() throws LcnException, LcnException { + LcnAddr localAddress = moduleAddress; + if (localAddress == null) { + throw new LcnException("Module address not set"); + } + return localAddress; + } + + /** + * Invoked when an update for this LCN module should be fired to openHAB. + * + * @param channelGroup the Channel to update + * @param channelId the ID within the Channel to update + * @param state the new state + */ + public void updateChannel(LcnChannelGroup channelGroup, String channelId, State state) { + ChannelUID channelUid = createChannelUid(channelGroup, channelId); + Converter converter = converters.get(channelUid); + + State convertedState = state; + if (converter != null) { + convertedState = converter.onStateUpdateFromHandler(state); + } + updateState(channelUid, convertedState); + } + + /** + * Invoked when an trigger for this LCN module should be fired to openHAB. + * + * @param channelGroup the Channel to update + * @param channelId the ID within the Channel to update + * @param event the event used to trigger + */ + public void triggerChannel(LcnChannelGroup channelGroup, String channelId, String event) { + triggerChannel(createChannelUid(channelGroup, channelId), event); + } + + private ChannelUID createChannelUid(LcnChannelGroup channelGroup, String channelId) { + return new ChannelUID(thing.getUID(), channelGroup.name().toLowerCase() + "#" + channelId); + } + + /** + * Checks the LCN module address against the own. + * + * @param physicalSegmentId which is 0 if it is the local segment + * @param moduleId + * @return true, if the given address matches the own address + */ + public boolean isMyAddress(String physicalSegmentId, String moduleId) { + try { + return new LcnAddrMod(getPckGatewayHandler().toLogicalSegmentId(Integer.parseInt(physicalSegmentId)), + Integer.parseInt(moduleId)).equals(getStatusMessageAddress()); + } catch (LcnException e) { + return false; + } + } + + @Override + public Collection> getServices() { + return Collections.singleton(LcnModuleActions.class); + } + + /** + * Invoked when an Ack from this module has been received. + */ + public void onAckRceived() { + try { + Connection connection = getPckGatewayHandler().getConnection(); + LcnAddrMod localModuleAddress = moduleAddress; + if (connection != null && localModuleAddress != null) { + getPckGatewayHandler().getModInfo(localModuleAddress).onAck(LcnBindingConstants.CODE_ACK, connection, + getPckGatewayHandler().getTimeoutMs(), System.nanoTime()); + } + } catch (LcnException e) { + logger.warn("Connection or module address not set"); + } + } + + /** + * Gets the address the handler shall react to, when a status message from this address is processed. + * + * @return the address for status messages + */ + public LcnAddrMod getStatusMessageAddress() { + LcnAddrMod localmoduleAddress = moduleAddress; + if (localmoduleAddress != null) { + return localmoduleAddress; + } else { + return new LcnAddrMod(0, 0); + } + } + + @Override + public void dispose() { + metadataSubHandlers.clear(); + subHandlers.clear(); + converters.clear(); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnProfileFactory.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnProfileFactory.java new file mode 100644 index 0000000000000..a63c73e0c74cf --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/LcnProfileFactory.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal; + +import java.util.Collection; +import java.util.Collections; +import java.util.Locale; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.library.CoreItemFactory; +import org.eclipse.smarthome.core.thing.profiles.Profile; +import org.eclipse.smarthome.core.thing.profiles.ProfileCallback; +import org.eclipse.smarthome.core.thing.profiles.ProfileContext; +import org.eclipse.smarthome.core.thing.profiles.ProfileFactory; +import org.eclipse.smarthome.core.thing.profiles.ProfileType; +import org.eclipse.smarthome.core.thing.profiles.ProfileTypeBuilder; +import org.eclipse.smarthome.core.thing.profiles.ProfileTypeProvider; +import org.eclipse.smarthome.core.thing.profiles.ProfileTypeUID; +import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Factory to create Profile instances. Also provides the available ProfileTypes and gives advise which profile to use + * by a given link. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +@Component(service = { ProfileFactory.class, ProfileTypeProvider.class }) +public class LcnProfileFactory implements ProfileFactory, ProfileTypeProvider { + private final Logger logger = LoggerFactory.getLogger(LcnProfileFactory.class); + + @Override + public Collection getSupportedProfileTypeUIDs() { + return Collections.singleton(DimmerOutputProfile.UID); + } + + @Override + public Collection getProfileTypes(@Nullable Locale locale) { + return Collections.singleton(ProfileTypeBuilder.newState(DimmerOutputProfile.UID, "Dimmer Output (%)") + .withSupportedItemTypes(CoreItemFactory.DIMMER, CoreItemFactory.COLOR) + .withSupportedChannelTypeUIDs( + new ChannelTypeUID(LcnBindingConstants.BINDING_ID, LcnChannelGroup.OUTPUT.name().toLowerCase())) + .build()); + } + + @Override + public @Nullable Profile createProfile(ProfileTypeUID profileTypeUID, ProfileCallback callback, + ProfileContext profileContext) { + if (profileTypeUID.equals(DimmerOutputProfile.UID)) { + return new DimmerOutputProfile(callback, profileContext); + } else { + logger.warn("Could not create {}", profileTypeUID); + return null; + } + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/PckGatewayConfiguration.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/PckGatewayConfiguration.java new file mode 100644 index 0000000000000..593396a463c2a --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/PckGatewayConfiguration.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PckGatewayConfiguration} class contains fields mapping thing configuration parameters. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class PckGatewayConfiguration { + private @NonNullByDefault({}) String hostname; + private int port; + private @NonNullByDefault({}) String username; + private @NonNullByDefault({}) String password; + private @NonNullByDefault({}) String mode; + private @NonNullByDefault({}) int timeoutMs; + + public String getHostname() { + return hostname; + } + + public int getPort() { + return port; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public String getMode() { + return mode; + } + + public int getTimeoutMs() { + return timeoutMs; + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/PckGatewayHandler.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/PckGatewayHandler.java new file mode 100644 index 0000000000000..9d239c5936a02 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/PckGatewayHandler.java @@ -0,0 +1,303 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal; + +import java.util.Collection; +import java.util.Collections; +import java.util.Optional; +import java.util.function.Consumer; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.thing.Bridge; +import org.eclipse.smarthome.core.thing.ChannelUID; +import org.eclipse.smarthome.core.thing.Thing; +import org.eclipse.smarthome.core.thing.ThingStatus; +import org.eclipse.smarthome.core.thing.ThingStatusDetail; +import org.eclipse.smarthome.core.thing.binding.BaseBridgeHandler; +import org.eclipse.smarthome.core.thing.binding.ThingHandler; +import org.eclipse.smarthome.core.thing.binding.ThingHandlerService; +import org.eclipse.smarthome.core.types.Command; +import org.openhab.binding.lcn.internal.common.LcnAddr; +import org.openhab.binding.lcn.internal.common.LcnAddrMod; +import org.openhab.binding.lcn.internal.common.LcnDefs; +import org.openhab.binding.lcn.internal.common.LcnDefs.OutputPortDimMode; +import org.openhab.binding.lcn.internal.common.LcnException; +import org.openhab.binding.lcn.internal.connection.Connection; +import org.openhab.binding.lcn.internal.connection.ConnectionCallback; +import org.openhab.binding.lcn.internal.connection.ConnectionSettings; +import org.openhab.binding.lcn.internal.connection.ModInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PckGatewayHandler} is responsible for the communication via a PCK gateway. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class PckGatewayHandler extends BaseBridgeHandler { + private final Logger logger = LoggerFactory.getLogger(PckGatewayHandler.class); + private @Nullable Connection connection; + private Optional> pckListener = Optional.empty(); + private @Nullable PckGatewayConfiguration config; + + public PckGatewayHandler(Bridge bridge) { + super(bridge); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + // nothing + } + + @Override + public synchronized void initialize() { + PckGatewayConfiguration localConfig = config = getConfigAs(PckGatewayConfiguration.class); + + String errorMessage = "Could not connect to LCN-PCHK/PKE: " + localConfig.getHostname() + ": "; + + try { + OutputPortDimMode dimMode; + String mode = localConfig.getMode(); + if (LcnDefs.OutputPortDimMode.NATIVE50.name().equalsIgnoreCase(mode)) { + dimMode = LcnDefs.OutputPortDimMode.NATIVE50; + } else if (LcnDefs.OutputPortDimMode.NATIVE200.name().equalsIgnoreCase(mode)) { + dimMode = LcnDefs.OutputPortDimMode.NATIVE200; + } else { + throw new LcnException("DimMode " + mode + " is not supported"); + } + + ConnectionSettings settings = new ConnectionSettings("0", localConfig.getHostname(), localConfig.getPort(), + localConfig.getUsername(), localConfig.getPassword(), dimMode, LcnDefs.OutputPortStatusMode.PERCENT, + localConfig.getTimeoutMs()); + + connection = new Connection(settings, scheduler, new ConnectionCallback() { + @Override + public void onOnline() { + updateStatus(ThingStatus.ONLINE); + } + + @Override + public void onOffline(@Nullable String errorMessage) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, errorMessage + "."); + } + + @Override + public void onPckMessageReceived(String message) { + pckListener.ifPresent(l -> l.accept(message)); + getThing().getThings().stream().filter(t -> t.getStatus() == ThingStatus.ONLINE).map(t -> { + LcnModuleHandler handler = (LcnModuleHandler) t.getHandler(); + if (handler == null) { + logger.warn("Failed to process PCK message: Handler not set"); + } + return handler; + }).filter(h -> h != null).forEach(h -> h.handleStatusMessage(message)); + } + }); + + updateStatus(ThingStatus.UNKNOWN); + } catch (LcnException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, errorMessage + e.getMessage()); + } + } + + @Override + public Collection> getServices() { + return Collections.singleton(LcnModuleDiscoveryService.class); + } + + @Override + public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) { + if (childThing.getThingTypeUID().equals(LcnBindingConstants.THING_TYPE_MODULE) + || childThing.getThingTypeUID().equals(LcnBindingConstants.THING_TYPE_GROUP)) { + try { + LcnAddr addr = getLcnAddrFromThing(childThing); + Connection localConnection = connection; + if (localConnection != null) { + localConnection.removeLcnModule(addr); + } + } catch (LcnException e) { + logger.warn("Failed to read configuration: {}", e.getMessage()); + } + } + } + + private LcnAddr getLcnAddrFromThing(Thing childThing) throws LcnException { + LcnModuleHandler lcnModuleHandler = (LcnModuleHandler) childThing.getHandler(); + if (lcnModuleHandler != null) { + return lcnModuleHandler.getCommandAddress(); + } else { + throw new LcnException("Could not get module handler"); + } + } + + /** + * Enqueues a PCK (String) command to be sent to an LCN module. + * + * @param addr the modules address + * @param wantsAck true, if the module shall send an ACK upon successful processing + * @param pck the command to send + */ + public void queue(LcnAddr addr, boolean wantsAck, String pck) { + Connection localConnection = connection; + if (localConnection != null) { + localConnection.queue(addr, wantsAck, pck); + } else { + logger.warn("Dropped PCK command: {}", pck); + } + } + + /** + * Enqueues a PCK (ByteBuffer) command to be sent to an LCN module. + * + * @param addr the modules address + * @param wantsAck true, if the module shall send an ACK upon successful processing + * @param pck the command to send + */ + public void queue(LcnAddr addr, boolean wantsAck, byte[] pck) { + Connection localConnection = connection; + if (localConnection != null) { + localConnection.queue(addr, wantsAck, pck); + } else { + logger.warn("Dropped PCK command of length: {}", pck.length); + } + } + + /** + * Sends a broadcast message to all LCN modules: All LCN modules are requested to answer with an Ack. + */ + void sendModuleDiscoveryCommand() { + Connection localConnection = connection; + if (localConnection != null) { + localConnection.sendModuleDiscoveryCommand(); + } + } + + /** + * Send a request to an LCN module to respond with its serial number and firmware version. + * + * @param addr the module's address + */ + void sendSerialNumberRequest(LcnAddrMod addr) { + Connection localConnection = connection; + if (localConnection != null) { + localConnection.sendSerialNumberRequest(addr); + } + } + + /** + * Send a request to an LCN module to respond with its configured name. + * + * @param addr the module's address + */ + void sendModuleNameRequest(LcnAddrMod addr) { + Connection localConnection = connection; + if (localConnection != null) { + localConnection.sendModuleNameRequest(addr); + } + } + + /** + * Returns the ModInfo to a given module. Will be created if it doesn't exist,yet. + * + * @param addr the module's address + * @return the ModInfo + * @throws LcnException when this handler is not initialized, yet + */ + ModInfo getModInfo(LcnAddrMod addr) throws LcnException { + Connection localConnection = connection; + if (localConnection != null) { + return localConnection.updateModuleData(addr); + } else { + throw new LcnException("Connection is null"); + } + } + + /** + * Registers a listener to receive all PCK messages from this PCK gateway. + * + * @param listener the listener to add + */ + void registerPckListener(Consumer listener) { + this.pckListener = Optional.of(listener); + } + + /** + * Removes all listeners for PCK messages from this PCK gateway. + */ + void removeAllPckListeners() { + this.pckListener = Optional.empty(); + } + + /** + * Gets the Connection for this handler. + * + * @return the Connection + */ + @Nullable + public Connection getConnection() { + return connection; + } + + /** + * Gets the local segment ID. When no segments are used, the value is 0. + * + * @return the local segment ID + */ + public int getLocalSegmentId() { + Connection localConnection = connection; + if (localConnection != null) { + return localConnection.getLocalSegId(); + } else { + return 0; + } + } + + /** + * Translates the given physical segment ID (0 or 4 if local segment) to the logical segment ID (local segment ID). + * + * @param physicalSegmentId the segment ID to convert + * @return the converted segment ID + */ + public int toLogicalSegmentId(int physicalSegmentId) { + int localSegmentId = getLocalSegmentId(); + if ((physicalSegmentId == 0 || physicalSegmentId == 4) && localSegmentId != -1) { + // PCK message came from local segment + // physicalSegmentId == 0 => Module is programmed to send status messages to local segment only + // physicalSegmentId == 4 => Module is programmed to send status messages globally (to all segments) + // or segment coupler scan did not finish, yet (-1). Assume local segment, then. + return localSegmentId; + } else { + return physicalSegmentId; + } + } + + @Override + public void dispose() { + Connection localConnection = connection; + if (localConnection != null) { + localConnection.shutdown(); + } + } + + /** + * Gets the configured connection timeout for the PCK gateway. + * + * @return the timeout in ms + */ + public long getTimeoutMs() { + PckGatewayConfiguration localConfig = config; + return localConfig != null ? localConfig.getTimeoutMs() : 3500; + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/DimmerOutputCommand.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/DimmerOutputCommand.java new file mode 100644 index 0000000000000..b54dd12367647 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/DimmerOutputCommand.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.common; + +import java.math.BigDecimal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.PercentType; + +/** + * Holds the information to control dimmer outputs of an LCN module. Used when the user configured an "output" profile. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class DimmerOutputCommand extends PercentType { + private static final long serialVersionUID = 8147502412107723798L; + private final boolean controlAllOutputs; + private final boolean controlOutputs12; + private final int rampMs; + + public DimmerOutputCommand(BigDecimal value, boolean controlAllOutputs, boolean controlOutputs12, int rampMs) { + super(value); + this.controlAllOutputs = controlAllOutputs; + this.controlOutputs12 = controlOutputs12; + this.rampMs = rampMs; + } + + /** + * Gets the ramp. + * + * @return ramp in milliseconds + */ + public int getRampMs() { + return rampMs; + } + + /** + * Returns if all dimmer outputs shall be controlled. + * + * @return true, if all dimmer outputs shall be controlled + */ + public boolean isControlAllOutputs() { + return controlAllOutputs; + } + + /** + * Returns if dimmer outputs 1+2 shall be controlled. + * + * @return true, if dimmer outputs 1+2 shall be controlled + */ + public boolean isControlOutputs12() { + return controlOutputs12; + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/LcnAddr.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/LcnAddr.java new file mode 100644 index 0000000000000..c5630ecc69d9c --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/LcnAddr.java @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.common; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Represents an LCN address (module or group). + * + * @author Tobias Jüttner - Initial Contribution + */ +@NonNullByDefault +public abstract class LcnAddr { + /** + * The logical segment ID. When no segments are used, the ID is always 0. When segments are used and the module is + * in the local segment, the ID is the local's segment ID. + */ + protected final int segmentId; + + /** + * Constructs an address with a (logical) segment id. + * + * @param segId the segment id + */ + public LcnAddr(int segId) { + this.segmentId = segId; + } + + /** + * Gets the (logical) segment id. + * + * @return the segment id + */ + public int getSegmentId() { + return this.segmentId; + } + + /** + * Gets the physical segment id ("local" segment replaced with 0). + * Can be used to send data into the LCN bus. + * + * @param localSegegmentId the segment id of the local segment (managed by {@link Connection}) + * @return the physical segment id + */ + public int getPhysicalSegmentId(int localSegegmentId) { + return this.segmentId == localSegegmentId ? 0 : this.segmentId; + } + + /** + * Checks the address against the LCN specification for valid addresses. + * + * @return true if address is valid + */ + public abstract boolean isValid(); + + /** + * Queries the concrete address type. + * + * @return true if address is a group address (module address otherwise) + */ + public abstract boolean isGroup(); + + /** + * Gets the address' module or group id (discarding the concrete type). + * + * @return the module or group id + */ + public abstract int getId(); +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/LcnAddrGrp.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/LcnAddrGrp.java new file mode 100644 index 0000000000000..0873dfd0edd85 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/LcnAddrGrp.java @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.common; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Represents an LCN group address. + * Can be used as a key in maps. + * Hash codes are guaranteed to be unique as long as {@link #isValid()} is true. + * + * @author Tobias Jüttner - Initial Contribution + */ +@NonNullByDefault +public class LcnAddrGrp extends LcnAddr implements Comparable { + private final Logger logger = LoggerFactory.getLogger(LcnAddrGrp.class); + private final int groupId; + + /** + * Constructs a group address with (logical) segment id and group id. + * + * @param segId the segment id + * @param grpId the group id + */ + public LcnAddrGrp(int segId, int grpId) { + super(segId); + this.groupId = grpId; + } + + /** + * Gets the group id. + * + * @return the group id + */ + public int getGroupId() { + return this.groupId; + } + + @Override + public boolean isValid() { + // segId: + // 0 = Local, 1..2 = Not allowed (but "seen in the wild") + // 3 = Broadcast, 4 = Status messages, 5..127, 128 = Segment-bus disabled (valid value) + // grpId: + // 3 = Broadcast, 4 = Status messages, 5..254 + return this.segmentId >= 0 && this.segmentId <= 128 && this.groupId >= 3 && this.groupId <= 254; + } + + @Override + public boolean isGroup() { + return true; + } + + @Override + public int getId() { + return this.groupId; + } + + @Override + public int hashCode() { + // Reversing the bits helps to generate better balanced trees as ids tend to be "user-sorted" + try { + if (this.isValid()) { + return ReverseNumber.reverseUInt8(this.groupId) << 8 + ReverseNumber.reverseUInt8(this.segmentId); + } + } catch (LcnException ex) { + logger.warn("Could not calculate hash code"); + } + return -1; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof LcnAddrGrp)) { + return false; + } + return this.segmentId == ((LcnAddrGrp) obj).segmentId && this.groupId == ((LcnAddrGrp) obj).groupId; + } + + @Override + public int compareTo(LcnAddrMod other) { + return this.hashCode() - other.hashCode(); + } + + @Override + public String toString() { + return this.isValid() ? String.format("S%03dG%03d", this.segmentId, this.groupId) : "Invalid"; + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/LcnAddrMod.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/LcnAddrMod.java new file mode 100644 index 0000000000000..81fe842230c38 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/LcnAddrMod.java @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.common; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Represents an LCN module address. + * Can be used as a key in maps. + * Hash codes are guaranteed to be unique as long as {@link #isValid()} is true. + * + * @author Tobias Jüttner - Initial Contribution + */ +@NonNullByDefault +public class LcnAddrMod extends LcnAddr implements Comparable { + private final Logger logger = LoggerFactory.getLogger(LcnAddrMod.class); + private final int moduleId; + + /** + * Constructs a module address with (logical) segment id and module id. + * + * @param segId the segment id + * @param modId the module id + */ + public LcnAddrMod(int segId, int modId) { + super(segId); + this.moduleId = modId; + } + + /** + * Gets the module id. + * + * @return the module id + */ + public int getModuleId() { + return this.moduleId; + } + + @Override + public boolean isValid() { + // segId: + // 0 = Local, 1..2 = Not allowed (but "seen in the wild") + // 3 = Broadcast, 4 = Status messages, 5..127, 128 = Segment-bus disabled (valid value) + // modId: + // 1 = LCN-PRO, 2 = LCN-GVS/LCN-W, 4 = PCHK, 5..254, 255 = Unprog. (valid, but irrelevant here) + return this.segmentId >= 0 && this.segmentId <= 128 && this.moduleId >= 1 && this.moduleId <= 254; + } + + @Override + public boolean isGroup() { + return false; + } + + @Override + public int getId() { + return this.moduleId; + } + + @Override + public int hashCode() { + // Reversing the bits helps to generate better balanced trees as ids tend to be "user-sorted" + try { + if (this.isValid()) { + return ReverseNumber.reverseUInt8(this.moduleId) << 8 + ReverseNumber.reverseUInt8(this.segmentId); + } + } catch (LcnException ex) { + logger.warn("Could not calculate hash code"); + } + return -1; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof LcnAddrMod)) { + return false; + } + return this.segmentId == ((LcnAddrMod) obj).segmentId && this.moduleId == ((LcnAddrMod) obj).moduleId; + } + + @Override + public int compareTo(LcnAddrMod other) { + return this.hashCode() - other.hashCode(); + } + + @Override + public String toString() { + return this.isValid() ? String.format("S%03dM%03d", this.segmentId, this.moduleId) : "Invalid"; + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/LcnChannelGroup.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/LcnChannelGroup.java new file mode 100644 index 0000000000000..d9d6a6da4f38c --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/LcnChannelGroup.java @@ -0,0 +1,122 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.common; + +import java.util.function.BiFunction; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lcn.internal.LcnModuleHandler; +import org.openhab.binding.lcn.internal.connection.ModInfo; +import org.openhab.binding.lcn.internal.subhandler.AbstractLcnModuleSubHandler; +import org.openhab.binding.lcn.internal.subhandler.LcnModuleBinarySensorSubHandler; +import org.openhab.binding.lcn.internal.subhandler.LcnModuleCodeSubHandler; +import org.openhab.binding.lcn.internal.subhandler.LcnModuleKeyLockTableSubHandler; +import org.openhab.binding.lcn.internal.subhandler.LcnModuleLedSubHandler; +import org.openhab.binding.lcn.internal.subhandler.LcnModuleLogicSubHandler; +import org.openhab.binding.lcn.internal.subhandler.LcnModuleOutputSubHandler; +import org.openhab.binding.lcn.internal.subhandler.LcnModuleRelaySubHandler; +import org.openhab.binding.lcn.internal.subhandler.LcnModuleRollershutterOutputSubHandler; +import org.openhab.binding.lcn.internal.subhandler.LcnModuleRollershutterRelaySubHandler; +import org.openhab.binding.lcn.internal.subhandler.LcnModuleRvarLockSubHandler; +import org.openhab.binding.lcn.internal.subhandler.LcnModuleRvarSetpointSubHandler; +import org.openhab.binding.lcn.internal.subhandler.LcnModuleS0CounterSubHandler; +import org.openhab.binding.lcn.internal.subhandler.LcnModuleThresholdSubHandler; +import org.openhab.binding.lcn.internal.subhandler.LcnModuleVariableSubHandler; + +/** + * Defines the supported channels of an LCN module handler. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public enum LcnChannelGroup { + OUTPUT(4, LcnModuleOutputSubHandler::new), + ROLLERSHUTTEROUTPUT(1, LcnModuleRollershutterOutputSubHandler::new), + RELAY(8, LcnModuleRelaySubHandler::new), + ROLLERSHUTTERRELAY(4, LcnModuleRollershutterRelaySubHandler::new), + LED(12, LcnModuleLedSubHandler::new), + LOGIC(4, LcnModuleLogicSubHandler::new), + BINARYSENSOR(8, LcnModuleBinarySensorSubHandler::new), + VARIABLE(12, LcnModuleVariableSubHandler::new), + RVARSETPOINT(2, LcnModuleRvarSetpointSubHandler::new), + RVARLOCK(2, LcnModuleRvarLockSubHandler::new), + THRESHOLDREGISTER1(5, LcnModuleThresholdSubHandler::new), + THRESHOLDREGISTER2(4, LcnModuleThresholdSubHandler::new), + THRESHOLDREGISTER3(4, LcnModuleThresholdSubHandler::new), + THRESHOLDREGISTER4(4, LcnModuleThresholdSubHandler::new), + S0INPUT(4, LcnModuleS0CounterSubHandler::new), + KEYLOCKTABLEA(8, LcnModuleKeyLockTableSubHandler::new), + KEYLOCKTABLEB(8, LcnModuleKeyLockTableSubHandler::new), + KEYLOCKTABLEC(8, LcnModuleKeyLockTableSubHandler::new), + KEYLOCKTABLED(8, LcnModuleKeyLockTableSubHandler::new), + CODE(0, LcnModuleCodeSubHandler::new); + + private int count; + private BiFunction handlerFactory; + + private LcnChannelGroup(int count, + BiFunction handlerFactory) { + this.count = count; + this.handlerFactory = handlerFactory; + } + + /** + * Gets the number of Channels within the channel group. + * + * @return the Channel count + */ + public int getCount() { + return count; + } + + /** + * Checks the given Channel id against the max. Channel count in this Channel group. + * + * @param number the number to check + * @return true, if the number is in the range + */ + public boolean isValidId(int number) { + return number >= 0 && number < count; + } + + /** + * Gets the sub handler class to handle this Channel group. + * + * @return the sub handler class + */ + public AbstractLcnModuleSubHandler createSubHandler(LcnModuleHandler handler, ModInfo info) { + return handlerFactory.apply(handler, info); + } + + /** + * Converts a given table ID into the corresponding Channel group. + * + * @param tableId to convert + * @return the channel group + * @throws LcnException when the ID is out of range + */ + public static LcnChannelGroup fromTableId(int tableId) throws LcnException { + switch (tableId) { + case 0: + return KEYLOCKTABLEA; + case 1: + return KEYLOCKTABLEB; + case 2: + return KEYLOCKTABLEC; + case 3: + return KEYLOCKTABLED; + default: + throw new LcnException("Unknown key table ID: " + tableId); + } + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/LcnDefs.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/LcnDefs.java new file mode 100644 index 0000000000000..6ad123bc8affe --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/LcnDefs.java @@ -0,0 +1,156 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.common; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Common definitions and helpers for the PCK protocol. + * + * @author Tobias Jüttner - Initial Contribution + * @author Fabian Wolter - Migration to OH2 + */ +@NonNullByDefault +public final class LcnDefs { + /** Text encoding used by LCN-PCHK. */ + public static final Charset LCN_ENCODING = StandardCharsets.UTF_8; + /** Number of thresholds registers of an LCN module */ + public static final int THRESHOLD_REGISTER_COUNT = 4; + /** Number of key tables of an LCN module. */ + public static final int KEY_TABLE_COUNT = 4; + /** Number of thresholds before LCN module firmware version 2013 */ + public static final int THRESHOLD_COUNT_BEFORE_2013 = 5; + /** + * Default dimmer output ramp when used with roller shutters. Results in a switching delay of 600ms. Value copied + * from the LCN-PRO motor/shutter command dialog. + */ + public static final int ROLLER_SHUTTER_RAMP_MS = 4000; + /** Max. value of a variable, threshold or regulator setpoint */ + public static final int MAX_VARIABLE_VALUE = 32768; + /** The fixed ramp when output 1+2 are controlled */ + public static final int FIXED_RAMP_MS = 250; + /** Authentication at LCN-PCHK: Request user name. */ + public static final String AUTH_USERNAME = "Username:"; + /** Authentication at LCN-PCHK: Request password. */ + public static final String AUTH_PASSWORD = "Password:"; + /** LCN-PK/PKU is connected. */ + public static final String LCNCONNSTATE_CONNECTED = "$io:#LCN:connected"; + /** LCN-PK/PKU is disconnected. */ + public static final String LCNCONNSTATE_DISCONNECTED = "$io:#LCN:disconnected"; + /** LCN-PCHK/PKE has not enough licenses to handle this connection. */ + public static final String INSUFFICIENT_LICENSES = "$err:(license?)"; + + /** + * LCN dimming mode. + * If solely modules with firmware 170206 or newer are present, LCN-PRO automatically programs {@link #NATIVE200}. + * Otherwise the default is {@link #NATIVE50}. + * Since LCN-PCHK doesn't know the current mode, it must explicitly be set. + */ + public enum OutputPortDimMode { + NATIVE50, // 0..50 dimming steps (all LCN module generations) + NATIVE200 // 0..200 dimming steps (since 170206) + } + + /** + * Tells LCN-PCHK how to format output-port status-messages. + * {@link #NATIVE} allows to show the status in half-percent steps (e.g. "10.5"). + * {@link #NATIVE} is completely backward compatible and there are no restrictions + * concerning the LCN module generations. It requires LCN-PCHK 2.3 or higher though. + */ + public enum OutputPortStatusMode { + PERCENT, // Default (compatible with all versions of LCN-PCHK) + NATIVE // 0..200 steps (since LCN-PCHK 2.3) + } + + /** Possible states for LCN LEDs. */ + public enum LedStatus { + OFF, + ON, + BLINK, + FLICKER; + } + + /** Possible states for LCN logic-operations. */ + public enum LogicOpStatus { + NOT, + OR, // Note: Actually not correct since AND won't be OR also + AND; + } + + /** Time units used for several LCN commands. */ + public enum TimeUnit { + SECONDS, + MINUTES, + HOURS, + DAYS; + } + + /** Relay-state modifiers used in LCN commands. */ + public enum RelayStateModifier { + ON, + OFF, + TOGGLE, + NOCHANGE + } + + /** Value-reference for relative LCN variable commands. */ + public enum RelVarRef { + CURRENT, + PROG // Programmed value (LCN-PRO). Relevant for set-points and thresholds. + } + + /** Command types used when sending LCN keys. */ + public enum SendKeyCommand { + HIT, + MAKE, + BREAK, + DONTSEND + } + + /** Key-lock modifiers used in LCN commands. */ + public enum KeyLockStateModifier { + ON, + OFF, + TOGGLE, + NOCHANGE + } + + /** List of key tables of an LCN module */ + public enum KeyTable { + A, + B, + C, + D + } + + /** + * Generates an array of booleans from an input integer (actually a byte). + * + * @param input the input byte (0..255) + * @return the array of 8 booleans + * @throws IllegalArgumentException if input is out of range (not a byte) + */ + public static boolean[] getBooleanValue(int inputByte) throws IllegalArgumentException { + if (inputByte < 0 || inputByte > 255) { + throw new IllegalArgumentException(); + } + boolean[] result = new boolean[8]; + for (int i = 0; i < 8; ++i) { + result[i] = (inputByte & (1 << i)) != 0; + } + return result; + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/LcnException.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/LcnException.java new file mode 100644 index 0000000000000..3731bf000b6a3 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/LcnException.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.common; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Default checked exception. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnException extends Exception { + private static final long serialVersionUID = -4341882774124288028L; + + public LcnException() { + super(); + } + + public LcnException(String message) { + super(message); + } + + public LcnException(Exception e) { + super(e); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/PckGenerator.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/PckGenerator.java new file mode 100644 index 0000000000000..5c76e562639e3 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/PckGenerator.java @@ -0,0 +1,780 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.common; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lcn.internal.LcnBindingConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Helpers to generate LCN-PCK commands. + *

    + * LCN-PCK is the command-syntax used by LCN-PCHK to send and receive LCN commands. + * + * @author Tobias Jüttner - Initial Contribution + * @author Fabian Wolter - Migration to OH2 + */ +@NonNullByDefault +public final class PckGenerator { + private static final Logger LOGGER = LoggerFactory.getLogger(PckGenerator.class); + /** Termination character after a PCK message */ + public static final String TERMINATION = "\n"; + + /** + * Generates a keep-alive. + * LCN-PCHK will close the connection if it does not receive any commands from + * an open {@link Connection} for a specific period (10 minutes by default). + * + * @param counter the current ping's id (optional, but "best practice"). Should start with 1 + * @return the PCK command as text + */ + public static String ping(int counter) { + return String.format("^ping%d", counter); + } + + /** + * Generates a PCK command that will set the LCN-PCHK connection's operation mode. + * This influences how output-port commands and status are interpreted and must be + * in sync with the LCN bus. + * + * @param dimMode see {@link LcnDefs.OutputPortDimMode} + * @param statusMode see {@link LcnDefs.OutputPortStatusMode} + * @return the PCK command as text + */ + public static String setOperationMode(LcnDefs.OutputPortDimMode dimMode, LcnDefs.OutputPortStatusMode statusMode) { + return "!OM" + (dimMode == LcnDefs.OutputPortDimMode.NATIVE200 ? "1" : "0") + + (statusMode == LcnDefs.OutputPortStatusMode.PERCENT ? "P" : "N"); + } + + /** + * Generates a PCK address header. + * Used for commands to LCN modules and groups. + * + * @param addr the target's address (module or group) + * @param localSegId the local segment id where the physical bus connection is located + * @param wantsAck true to claim an acknowledge / receipt from the target + * @return the PCK address header as text + */ + public static String generateAddressHeader(LcnAddr addr, int localSegId, boolean wantsAck) { + return String.format(">%s%03d%03d%s", addr.isGroup() ? "G" : "M", addr.getPhysicalSegmentId(localSegId), + addr.getId(), wantsAck ? "!" : "."); + } + + /** + * Generates a scan-command for LCN segment-couplers. + * Used to detect the local segment (where the physical bus connection is located). + * + * @return the PCK command (without address header) as text + */ + public static String segmentCouplerScan() { + return "SK"; + } + + /** + * Generates a firmware/serial-number request. + * + * @return the PCK command (without address header) as text + */ + public static String requestSn() { + return "SN"; + } + + /** + * Generates a command to request a part of a name of a module. + * + * @param partNumber 0..1 + * @return the PCK command (without address header) as text + */ + public static String requestModuleName(int partNumber) { + return "NMN" + (partNumber + 1); + } + + /** + * Generates an output-port status request. + * + * @param outputId 0..3 + * @return the PCK command (without address header) as text + * @throws LcnException if out of range + */ + public static String requestOutputStatus(int outputId) throws LcnException { + if (outputId < 0 || outputId > 3) { + throw new LcnException(); + } + return String.format("SMA%d", outputId + 1); + } + + /** + * Generates a dim command for a single output-port. + * + * @param outputId 0..3 + * @param percent 0..100 + * @param rampMs ramp in milliseconds + * @return the PCK command (without address header) as text + * @throws LcnException if out of range + */ + public static String dimOutput(int outputId, double percent, int rampMs) throws LcnException { + if (outputId < 0 || outputId > 3) { + throw new LcnException(); + } + int rampNative = PckGenerator.timeToRampValue(rampMs); + int n = (int) Math.round(percent * 2); + if ((n % 2) == 0) { // Use the percent command (supported by all LCN-PCHK versions) + return String.format("A%dDI%03d%03d", outputId + 1, n / 2, rampNative); + } else { // We have a ".5" value. Use the native command (supported since LCN-PCHK 2.3) + return String.format("O%dDI%03d%03d", outputId + 1, n, rampNative); + } + } + + /** + * Generates a dim command for all output-ports. + * + * Attention: This command is supported since module firmware version 180501 AND LCN-PCHK 2.61 + * + * @param firstPercent dimmer value of the first output 0..100 + * @param secondPercent dimmer value of the first output 0..100 + * @param thirdPercent dimmer value of the first output 0..100 + * @param fourthPercent dimmer value of the first output 0..100 + * @param rampMs ramp in milliseconds + * @return the PCK command (without address header) as text + */ + public static String dimAllOutputs(double firstPercent, double secondPercent, double thirdPercent, + double fourthPercent, int rampMs) { + long n1 = Math.round(firstPercent * 2); + long n2 = Math.round(secondPercent * 2); + long n3 = Math.round(thirdPercent * 2); + long n4 = Math.round(fourthPercent * 2); + + return String.format("OY%03d%03d%03d%03d%03d", n1, n2, n3, n4, timeToRampValue(rampMs)); + } + + /** + * Generates a control command for switching all outputs ON or OFF with a fixed ramp of 0.5s. + * + * @param percent 0..100 + * @returnthe PCK command (without address header) as text + */ + public static String controlAllOutputs(double percent) { + return String.format("AH%03d", Math.round(percent)); + } + + /** + * Generates a control command for switching dimmer output 1 and 2 both ON or OFF with a fixed ramp of 0.5s or + * without ramp. + * + * @param on true, if outputs shall be switched on + * @param ramp true, if the ramp shall be 0.5s, else 0s + * @return the PCK command (without address header) as text + */ + public static String controlOutputs12(boolean on, boolean ramp) { + int commandByte; + if (on) { + commandByte = ramp ? 0xC8 : 0xFD; + } else { + commandByte = ramp ? 0x00 : 0xFC; + } + return String.format("X2%03d%03d%03d", 1, commandByte, commandByte); + } + + /** + * Generates a dim command for setting the brightness of dimmer output 1 and 2 with a fixed ramp of 0.5s. + * + * @param percent brightness of both outputs 0..100 + * @return the PCK command (without address header) as text + */ + public static String dimOutputs12(double percent) { + long localPercent = Math.round(percent); + return String.format("AY%03d%03d", localPercent, localPercent); + } + + /** + * Let an output flicker. + * + * @param outputId output id 0..3 + * @param depth flicker depth, the higher the deeper 0..2 + * @param ramp the flicker speed 0..2 + * @param count number of flashes 1..15 + * @return the PCK command (without address header) as text + * @throws LcnException when the input values are out of range + */ + public static String flickerOutput(int outputId, int depth, int ramp, int count) throws LcnException { + if (outputId < 0 || outputId > 3) { + throw new LcnException("Output number out of range"); + } + if (count < 1 || count > 15) { + throw new LcnException("Number of flashes out of range"); + } + String depthString; + switch (depth) { + case 0: + depthString = "G"; + break; + case 1: + depthString = "M"; + break; + case 2: + depthString = "S"; + break; + default: + throw new LcnException("Depth out of range"); + } + String rampString; + switch (ramp) { + case 0: + rampString = "L"; + break; + case 1: + rampString = "M"; + break; + case 2: + rampString = "S"; + break; + default: + throw new LcnException("Ramp out of range"); + } + return String.format("A%dFL%s%s%02d", outputId + 1, depthString, rampString, count); + } + + /** + * Generates a command to change the value of an output-port. + * + * @param outputId 0..3 + * @param percent -100..100 + * @return the PCK command (without address header) as text + * @throws LcnException if out of range + */ + public static String relOutput(int outputId, double percent) throws LcnException { + if (outputId < 0 || outputId > 3) { + throw new LcnException(); + } + int n = (int) Math.round(percent * 2); + if ((n % 2) == 0) { // Use the percent command (supported by all LCN-PCHK versions) + return String.format("A%d%s%03d", outputId + 1, percent >= 0 ? "AD" : "SB", Math.abs(n / 2)); + } else { // We have a ".5" value. Use the native command (supported since LCN-PCHK 2.3) + return String.format("O%d%s%03d", outputId + 1, percent >= 0 ? "AD" : "SB", Math.abs(n)); + } + } + + /** + * Generates a command that toggles a single output-port (on->off, off->on). + * + * @param outputId 0..3 + * @param ramp see {@link PckGenerator#timeToRampValue(int)} + * @return the PCK command (without address header) as text + * @throws LcnException if out of range + */ + public static String toggleOutput(int outputId, int ramp) throws LcnException { + if (outputId < 0 || outputId > 3) { + throw new LcnException(); + } + return String.format("A%dTA%03d", outputId + 1, ramp); + } + + /** + * Generates a command that toggles all output-ports (on->off, off->on). + * + * @param ramp see {@link PckGenerator#timeToRampValue(int)} + * @return the PCK command (without address header) as text + */ + public static String toggleAllOutputs(int ramp) { + return String.format("AU%03d", ramp); + } + + /** + * Generates a relays-status request. + * + * @return the PCK command (without address header) as text + */ + public static String requestRelaysStatus() { + return "SMR"; + } + + /** + * Generates a command to control relays. + * + * @param states the 8 modifiers for the relay states + * @return the PCK command (without address header) as text + * @throws LcnException if out of range + */ + public static String controlRelays(LcnDefs.RelayStateModifier[] states) throws LcnException { + if (states.length != 8) { + throw new LcnException(); + } + StringBuilder ret = new StringBuilder("R8"); + for (int i = 0; i < 8; ++i) { + switch (states[i]) { + case ON: + ret.append("1"); + break; + case OFF: + ret.append("0"); + break; + case TOGGLE: + ret.append("U"); + break; + case NOCHANGE: + ret.append("-"); + break; + default: + throw new LcnException(); + } + } + return ret.toString(); + } + + /** + * Generates a binary-sensors status request. + * + * @return the PCK command (without address header) as text + */ + public static String requestBinSensorsStatus() { + return "SMB"; + } + + /** + * Generates a command that sets a variable absolute. + * + * @param number regulator number 0..1 + * @param value the absolute value to set + * @return the PCK command (without address header) as text + * @throws LcnException + */ + public static String setSetpointAbsolute(int number, int value) { + int internalValue = value; + // Set absolute (not in PCK yet) + int b1 = number << 6; // 01000000 + b1 |= 0x20; // xx10xxxx (set absolute) + if (value < 1000) { + internalValue = 1000 - internalValue; + b1 |= 8; + } else { + internalValue -= 1000; + } + b1 |= (internalValue >> 8) & 0x0f; // xxxx1111 + int b2 = internalValue & 0xff; + return String.format("X2%03d%03d%03d", 30, b1, b2); + } + + /** + * Generates a command to change the value of a variable. + * + * @param variable the target variable to change + * @param type the reference-point + * @param value the native LCN value to add/subtract (can be negative) + * @return the PCK command (without address header) as text + * @throws LcnException if command is not supported + */ + public static String setVariableRelative(Variable variable, LcnDefs.RelVarRef type, int value) { + if (variable.getNumber() == 0) { + // Old command for variable 1 / T-var (compatible with all modules) + return String.format("Z%s%d", value >= 0 ? "A" : "S", Math.abs(value)); + } else { // New command for variable 1-12 (compatible with all modules, since LCN-PCHK 2.8) + return String.format("Z%s%03d%d", value >= 0 ? "+" : "-", variable.getNumber() + 1, Math.abs(value)); + } + } + + /** + * Generates a command the change the value of a regulator setpoint relative. + * + * @param number 0..1 + * @param type relative to the current or to the programmed value + * @param value the relative value -4000..+4000 + * @return the PCK command (without address header) as text + */ + public static String setSetpointRelative(int number, LcnDefs.RelVarRef type, int value) { + return String.format("RE%sS%s%s%d", number == 0 ? "A" : "B", type == LcnDefs.RelVarRef.CURRENT ? "A" : "P", + value >= 0 ? "+" : "-", Math.abs(value)); + } + + /** + * Generates a command the change the value of a threshold relative. + * + * @param variable the threshold to change + * @param type relative to the current or to the programmed value + * @param value the relative value -4000..+4000 + * @param is2013 true, if the LCN module's firmware is equal to or newer than 2013 + * @return the PCK command (without address header) as text + */ + public static String setThresholdRelative(Variable variable, LcnDefs.RelVarRef type, int value, boolean is2013) + throws LcnException { + if (is2013) { // New command for registers 1-4 (since 170206, LCN-PCHK 2.8) + return String.format("SS%s%04d%sR%d%d", type == LcnDefs.RelVarRef.CURRENT ? "R" : "E", Math.abs(value), + value >= 0 ? "A" : "S", variable.getNumber() + 1, variable.getThresholdNumber().get() + 1); + } else if (variable.getNumber() == 0) { // Old command for register 1 (before 170206) + return String.format("SS%s%04d%s%s%s%s%s%s", type == LcnDefs.RelVarRef.CURRENT ? "R" : "E", Math.abs(value), + value >= 0 ? "A" : "S", variable.getThresholdNumber().get() == 0 ? "1" : "0", + variable.getThresholdNumber().get() == 1 ? "1" : "0", + variable.getThresholdNumber().get() == 2 ? "1" : "0", + variable.getThresholdNumber().get() == 3 ? "1" : "0", + variable.getThresholdNumber().get() == 4 ? "1" : "0"); + } else { + throw new LcnException( + "Module does not have threshold register " + (variable.getThresholdNumber().get() + 1)); + } + } + + /** + * Generates a variable value request. + * + * @param variable the variable to request + * @param firmwareVersion the target module's firmware version + * @return the PCK command (without address header) as text + * @throws LcnException if command is not supported + */ + public static String requestVarStatus(Variable variable, int firmwareVersion) throws LcnException { + if (firmwareVersion >= LcnBindingConstants.FIRMWARE_2013) { + int id = variable.getNumber(); + switch (variable.getType()) { + case UNKNOWN: + throw new LcnException("Variable unknown"); + case VARIABLE: + return String.format("MWT%03d", id + 1); + case REGULATOR: + return String.format("MWS%03d", id + 1); + case THRESHOLD: + return String.format("SE%03d", id + 1); // Whole register + case S0INPUT: + return String.format("MWC%03d", id + 1); + } + throw new LcnException("Unsupported variable type: " + variable); + } else { + switch (variable) { + case VARIABLE1: + return "MWV"; + case VARIABLE2: + return "MWTA"; + case VARIABLE3: + return "MWTB"; + case RVARSETPOINT1: + return "MWSA"; + case RVARSETPOINT2: + return "MWSB"; + case THRESHOLDREGISTER11: + case THRESHOLDREGISTER12: + case THRESHOLDREGISTER13: + case THRESHOLDREGISTER14: + case THRESHOLDREGISTER15: + return "SL1"; // Whole register + default: + throw new LcnException("Unsupported variable type: " + variable); + } + } + } + + /** + * Generates a request for LED and logic-operations states. + * + * @return the PCK command (without address header) as text + */ + public static String requestLedsAndLogicOpsStatus() { + return "SMT"; + } + + /** + * Generates a command to the set the state of a single LED. + * + * @param ledId 0..11 + * @param state the state to set + * @return the PCK command (without address header) as text + * @throws LcnException if out of range + */ + public static String controlLed(int ledId, LcnDefs.LedStatus state) throws LcnException { + if (ledId < 0 || ledId > 11) { + throw new LcnException(); + } + return String.format("LA%03d%s", ledId + 1, state == LcnDefs.LedStatus.OFF ? "A" + : state == LcnDefs.LedStatus.ON ? "E" : state == LcnDefs.LedStatus.BLINK ? "B" : "F"); + } + + /** + * Generates a command to send LCN keys. + * + * @param cmds the 4 concrete commands to send for the tables (A-D) + * @param keys the tables' 8 key-states (true means "send") + * @return the PCK command (without address header) as text + * @throws LcnException if out of range + */ + public static String sendKeys(LcnDefs.SendKeyCommand[] cmds, boolean[] keys) throws LcnException { + if (cmds.length != 4 || keys.length != 8) { + throw new LcnException(); + } + StringBuilder ret = new StringBuilder("TS"); + for (int i = 0; i < 4; ++i) { + switch (cmds[i]) { + case HIT: + ret.append("K"); + break; + case MAKE: + ret.append("L"); + break; + case BREAK: + ret.append("O"); + break; + case DONTSEND: + // By skipping table D (if it is not used), we use the old command + // for table A-C which is compatible with older LCN modules + if (i < 3) { + ret.append("-"); + } + break; + default: + throw new LcnException(); + } + } + for (int i = 0; i < 8; ++i) { + ret.append(keys[i] ? "1" : "0"); + } + return ret.toString(); + } + + /** + * Generates a command to send LCN keys deferred / delayed. + * + * @param tableId 0(A)..3(D) + * @param time the delay time + * @param timeUnit the time unit + * @param keys the key-states (true means "send") + * @return the PCK command (without address header) as text + * @throws LcnException if out of range + */ + public static String sendKeysHitDefered(int tableId, int time, LcnDefs.TimeUnit timeUnit, boolean[] keys) + throws LcnException { + if (tableId < 0 || tableId > 3 || keys.length != 8) { + throw new LcnException(); + } + StringBuilder ret = new StringBuilder("TV"); + switch (tableId) { + case 0: + ret.append("A"); + break; + case 1: + ret.append("B"); + break; + case 2: + ret.append("C"); + break; + case 3: + ret.append("D"); + break; + default: + throw new LcnException(); + } + ret.append(String.format("%03d", time)); + switch (timeUnit) { + case SECONDS: + if (time < 1 || time > 60) { + throw new LcnException(); + } + ret.append("S"); + break; + case MINUTES: + if (time < 1 || time > 90) { + throw new LcnException(); + } + ret.append("M"); + break; + case HOURS: + if (time < 1 || time > 50) { + throw new LcnException(); + } + ret.append("H"); + break; + case DAYS: + if (time < 1 || time > 45) { + throw new LcnException(); + } + ret.append("D"); + break; + default: + throw new LcnException(); + } + for (int i = 0; i < 8; ++i) { + ret.append(keys[i] ? "1" : "0"); + } + return ret.toString(); + } + + /** + * Generates a request for key-lock states. + * Always requests table A-D. Supported since LCN-PCHK 2.8. + * + * @return the PCK command (without address header) as text + */ + public static String requestKeyLocksStatus() { + return "STX"; + } + + /** + * Generates a command to lock keys. + * + * @param tableId 0(A)..3(D) + * @param states the 8 key-lock modifiers + * @return the PCK command (without address header) as text + * @throws LcnException if out of range + */ + public static String lockKeys(int tableId, LcnDefs.KeyLockStateModifier[] states) throws LcnException { + if (tableId < 0 || tableId > 3 || states.length != 8) { + throw new LcnException(); + } + StringBuilder ret = new StringBuilder( + String.format("TX%s", tableId == 0 ? "A" : tableId == 1 ? "B" : tableId == 2 ? "C" : "D")); + for (int i = 0; i < 8; ++i) { + switch (states[i]) { + case ON: + ret.append("1"); + break; + case OFF: + ret.append("0"); + break; + case TOGGLE: + ret.append("U"); + break; + case NOCHANGE: + ret.append("-"); + break; + default: + throw new LcnException(); + } + } + return ret.toString(); + } + + /** + * Generates a command to lock keys for table A temporary. + * There is no hardware-support for locking tables B-D. + * + * @param time the lock time + * @param timeUnit the time unit + * @param keys the 8 key-lock states (true means lock) + * @return the PCK command (without address header) as text + * @throws LcnException if out of range + */ + public static String lockKeyTabATemporary(int time, LcnDefs.TimeUnit timeUnit, boolean[] keys) throws LcnException { + if (keys.length != 8) { + throw new LcnException(); + } + StringBuilder ret = new StringBuilder(String.format("TXZA%03d", time)); + switch (timeUnit) { + case SECONDS: + if (time < 1 || time > 60) { + throw new LcnException(); + } + ret.append("S"); + break; + case MINUTES: + if (time < 1 || time > 90) { + throw new LcnException(); + } + ret.append("M"); + break; + case HOURS: + if (time < 1 || time > 50) { + throw new LcnException(); + } + ret.append("H"); + break; + case DAYS: + if (time < 1 || time > 45) { + throw new LcnException(); + } + ret.append("D"); + break; + default: + throw new LcnException(); + } + for (int i = 0; i < 8; ++i) { + ret.append(keys[i] ? "1" : "0"); + } + return ret.toString(); + } + + /** + * Generates the command header / start for sending dynamic texts. + * Used by LCN-GTxD periphery (supports 4 text rows). + * To complete the command, the text to send must be appended (UTF-8 encoding). + * Texts are split up into up to 5 parts with 12 "UTF-8 bytes" each. + * + * @param row 0..3 + * @param part 0..4 + * @return the PCK command (without address header) as text + * @throws LcnException if out of range + */ + public static String dynTextHeader(int row, int part) throws LcnException { + if (row < 0 || row > 3 || part < 0 || part > 4) { + throw new LcnException("Row number is out of range: " + (row + 1)); + } + return String.format("GTDT%d%d", row + 1, part + 1); + } + + /** + * Generates a command to lock a regulator. + * + * @param regId 0..1 + * @param state the lock state + * @return the PCK command (without address header) as text + * @throws LcnException if out of range + */ + public static String lockRegulator(int regId, boolean state) throws LcnException { + if (regId < 0 || regId > 1) { + throw new LcnException(); + } + return String.format("RE%sX%s", regId == 0 ? "A" : "B", state ? "S" : "A"); + } + + /** + * Generates a null command, used for broadcast messages. + * + * @return the PCK command (without address header) as text + */ + public static String nullCommand() { + return "LEER"; + } + + /** + * Converts the given time into an LCN ramp value. + * + * @param timeMSec the time in milliseconds + * @return the (LCN-internal) ramp value (0..250) + */ + private static int timeToRampValue(int timeMSec) { + int ret; + if (timeMSec < 250) { + ret = 0; + } else if (timeMSec < 500) { + ret = 1; + } else if (timeMSec < 660) { + ret = 2; + } else if (timeMSec < 1000) { + ret = 3; + } else if (timeMSec < 1400) { + ret = 4; + } else if (timeMSec < 2000) { + ret = 5; + } else if (timeMSec < 3000) { + ret = 6; + } else if (timeMSec < 4000) { + ret = 7; + } else if (timeMSec < 5000) { + ret = 8; + } else if (timeMSec < 6000) { + ret = 9; + } else { + ret = (timeMSec / 1000 - 6) / 2 + 10; + if (ret > 250) { + ret = 250; + LOGGER.warn("Ramp value is too high. Limiting value to 486s."); + } + } + return ret; + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/ReverseNumber.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/ReverseNumber.java new file mode 100644 index 0000000000000..70a6c1fc82bd8 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/ReverseNumber.java @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.common; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Helper to bitwise reverse numbers. + * + * @author Tobias Jüttner - Initial Contribution + */ +@NonNullByDefault +final class ReverseNumber { + /** Cache with all reversed 8 bit values. */ + private static final int[] REVERSED_UINT8 = new int[256]; + + /** Initializes static data once this class is first used. */ + static { + for (int i = 0; i < 256; ++i) { + int reversed = 0; + for (int j = 0; j < 8; ++j) { + if ((i & (1 << j)) != 0) { + reversed |= (0x80 >> j); + } + } + REVERSED_UINT8[i] = reversed; + } + } + + /** + * Reverses the given 8 bit value bitwise. + * + * @param value the value to reverse bitwise (treated as unsigned 8 bit value) + * @return the reversed value + * @throws LcnException if value is out of range (not unsigned 8 bit) + */ + static int reverseUInt8(int value) throws LcnException { + if (value < 0 || value > 255) { + throw new LcnException(); + } + return REVERSED_UINT8[value]; + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/Variable.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/Variable.java new file mode 100644 index 0000000000000..c32573988d5ee --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/Variable.java @@ -0,0 +1,278 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.common; + +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lcn.internal.LcnBindingConstants; + +/** + * LCN variable types. + * + * @author Tobias Jüttner - Initial Contribution + * @author Fabian Wolter - Migration to OH2 + */ +@NonNullByDefault +public enum Variable { + UNKNOWN(0, Type.UNKNOWN, LcnChannelGroup.VARIABLE), // Used if the real type is not known (yet) + VARIABLE1(0, Type.VARIABLE, LcnChannelGroup.VARIABLE), // or TVar + VARIABLE2(1, Type.VARIABLE, LcnChannelGroup.VARIABLE), + VARIABLE3(2, Type.VARIABLE, LcnChannelGroup.VARIABLE), + VARIABLE4(3, Type.VARIABLE, LcnChannelGroup.VARIABLE), + VARIABLE5(4, Type.VARIABLE, LcnChannelGroup.VARIABLE), + VARIABLE6(5, Type.VARIABLE, LcnChannelGroup.VARIABLE), + VARIABLE7(6, Type.VARIABLE, LcnChannelGroup.VARIABLE), + VARIABLE8(7, Type.VARIABLE, LcnChannelGroup.VARIABLE), + VARIABLE9(8, Type.VARIABLE, LcnChannelGroup.VARIABLE), + VARIABLE10(9, Type.VARIABLE, LcnChannelGroup.VARIABLE), + VARIABLE11(10, Type.VARIABLE, LcnChannelGroup.VARIABLE), + VARIABLE12(11, Type.VARIABLE, LcnChannelGroup.VARIABLE), // Since 170206 + RVARSETPOINT1(0, Type.REGULATOR, LcnChannelGroup.RVARSETPOINT), + RVARSETPOINT2(1, Type.REGULATOR, LcnChannelGroup.RVARSETPOINT), // Set-points for regulators + THRESHOLDREGISTER11(0, 0, Type.THRESHOLD, LcnChannelGroup.THRESHOLDREGISTER1), + THRESHOLDREGISTER12(0, 1, Type.THRESHOLD, LcnChannelGroup.THRESHOLDREGISTER1), + THRESHOLDREGISTER13(0, 2, Type.THRESHOLD, LcnChannelGroup.THRESHOLDREGISTER1), + THRESHOLDREGISTER14(0, 3, Type.THRESHOLD, LcnChannelGroup.THRESHOLDREGISTER1), + // Register 1 (THRESHOLDREGISTER15 only before 170206) + THRESHOLDREGISTER15(0, 4, Type.THRESHOLD, LcnChannelGroup.THRESHOLDREGISTER1), + THRESHOLDREGISTER21(1, 0, Type.THRESHOLD, LcnChannelGroup.THRESHOLDREGISTER2), + THRESHOLDREGISTER22(1, 1, Type.THRESHOLD, LcnChannelGroup.THRESHOLDREGISTER2), + THRESHOLDREGISTER23(1, 2, Type.THRESHOLD, LcnChannelGroup.THRESHOLDREGISTER2), + THRESHOLDREGISTER24(1, 3, Type.THRESHOLD, LcnChannelGroup.THRESHOLDREGISTER2), // Register 2 (since 2012) + THRESHOLDREGISTER31(2, 0, Type.THRESHOLD, LcnChannelGroup.THRESHOLDREGISTER3), + THRESHOLDREGISTER32(2, 1, Type.THRESHOLD, LcnChannelGroup.THRESHOLDREGISTER3), + THRESHOLDREGISTER33(2, 2, Type.THRESHOLD, LcnChannelGroup.THRESHOLDREGISTER3), + THRESHOLDREGISTER34(2, 3, Type.THRESHOLD, LcnChannelGroup.THRESHOLDREGISTER3), // Register 3 (since 2012) + THRESHOLDREGISTER41(3, 0, Type.THRESHOLD, LcnChannelGroup.THRESHOLDREGISTER4), + THRESHOLDREGISTER42(3, 1, Type.THRESHOLD, LcnChannelGroup.THRESHOLDREGISTER4), + THRESHOLDREGISTER43(3, 2, Type.THRESHOLD, LcnChannelGroup.THRESHOLDREGISTER4), + THRESHOLDREGISTER44(3, 3, Type.THRESHOLD, LcnChannelGroup.THRESHOLDREGISTER4), // Register 4 (since 2012) + S0INPUT1(0, Type.S0INPUT, LcnChannelGroup.S0INPUT), + S0INPUT2(1, Type.S0INPUT, LcnChannelGroup.S0INPUT), + S0INPUT3(2, Type.S0INPUT, LcnChannelGroup.S0INPUT), + S0INPUT4(3, Type.S0INPUT, LcnChannelGroup.S0INPUT); // LCN-BU4L + + private final int number; + private final Optional thresholdNumber; + private final Type type; + private final LcnChannelGroup channelGroup; + + /** + * Defines the origin of an LCN variable. + */ + public enum Type { + UNKNOWN, + VARIABLE, + REGULATOR, + THRESHOLD, + S0INPUT + } + + Variable(int number, Type type, LcnChannelGroup channelGroup) { + this(number, Optional.empty(), type, channelGroup); + } + + Variable(int number, int thresholdNumber, Type type, LcnChannelGroup channelGroup) { + this(number, Optional.of(thresholdNumber), type, channelGroup); + } + + Variable(int number, Optional thresholdNumber, Type type, LcnChannelGroup channelGroup) { + this.number = number; + this.type = type; + this.channelGroup = channelGroup; + this.thresholdNumber = thresholdNumber; + } + + /** + * Gets the type of the variable's origin. + * + * @return the type + */ + public Type getType() { + return type; + } + + /** + * Gets the channel type of the variable. + * + * @return the channel type + */ + public LcnChannelGroup getChannelType() { + return channelGroup; + } + + /** + * Gets the threshold number within a threshold register. + * + * @return the threshold number + */ + public Optional getThresholdNumber() { + return thresholdNumber; + } + + /** + * Gets the threshold register number. + * + * @return the threshold register number + */ + public int getNumber() { + return number; + } + + /** + * Translates a given id into a variable type. + * + * @param number 0..11 + * @return the translated {@link Variable} + * @throws LcnException if out of range + */ + public static Variable varIdToVar(int number) throws LcnException { + if (number < 0 || number >= LcnChannelGroup.VARIABLE.getCount()) { + throw new LcnException("Invalid variable number: " + (number + 1)); + } + return getVariableFromNumberAndType(number, Type.VARIABLE, v -> true); + } + + /** + * Translates a given id into a LCN set-point variable type. + * + * @param number 0..1 + * @return the translated {@link Variable} + * @throws LcnException if out of range + */ + public static Variable setPointIdToVar(int number) throws LcnException { + if (number < 0 || number >= LcnChannelGroup.RVARSETPOINT.getCount()) { + throw new LcnException(); + } + + return getVariableFromNumberAndType(number, Type.REGULATOR, v -> true); + } + + /** + * Translates given ids into a LCN threshold variable type. + * + * @param registerNumber 0..3 + * @param thresholdNumber 0..4 for register 0, 0..3 for registers 1..3 + * @return the translated {@link Variable} + * @throws LcnException if out of range + */ + public static Variable thrsIdToVar(int registerNumber, int thresholdNumber) throws LcnException { + if (registerNumber < 0 || registerNumber >= LcnDefs.THRESHOLD_REGISTER_COUNT) { + throw new LcnException("Threshold register number out of range: " + (registerNumber + 1)); + } + if (thresholdNumber < 0 || thresholdNumber >= (registerNumber == 0 ? 5 : 4)) { + throw new LcnException("Threshold number out of range: " + (thresholdNumber + 1)); + } + return getVariableFromNumberAndType(registerNumber, Type.THRESHOLD, + v -> v.thresholdNumber.get() == thresholdNumber); + } + + /** + * Translates a given id into a LCN S0-input variable type. + * + * @param number 0..3 + * @return the translated {@link Variable} + * @throws LcnException if out of range + */ + public static Variable s0IdToVar(int number) throws LcnException { + if (number < 0 || number >= LcnChannelGroup.S0INPUT.getCount()) { + throw new LcnException(); + } + return getVariableFromNumberAndType(number, Type.S0INPUT, v -> true); + } + + private static Variable getVariableFromNumberAndType(int varId, Type type, Predicate filter) + throws LcnException { + return Stream.of(values()).filter(v -> v.type == type).filter(v -> v.number == varId).filter(filter).findAny() + .orElseThrow(LcnException::new); + } + + /** + * Checks if this variable type uses special values. + * Examples for special values: "No value yet", "sensor defective" etc. + * + * @return true if special values are in use + */ + public boolean useLcnSpecialValues() { + return type != Type.S0INPUT; + } + + /** + * Module-generation check. + * Checks if the given variable type would receive a typed response if + * its status was requested. + * + * @param firmwareVersion the target LCN-modules firmware version + * @return true if a response would contain the variable's type + */ + public boolean hasTypeInResponse(int firmwareVersion) { + return (firmwareVersion >= LcnBindingConstants.FIRMWARE_2013 + || (type != Type.VARIABLE && type != Type.REGULATOR)); + } + + /** + * Module-generation check. + * Checks if the given variable type automatically sends status-updates on + * value-change. It must be polled otherwise. + * + * @param firmwareVersion the target LCN-module's firmware version + * @return true if the LCN module supports automatic status-messages for this {@link Variable} + */ + public boolean isEventBased(int firmwareVersion) { + return type == Type.REGULATOR || type == Type.S0INPUT || firmwareVersion >= LcnBindingConstants.FIRMWARE_2013; + } + + /** + * Module-generation check. + * Checks if the target LCN module would automatically send status-updates if + * the given variable type was changed by command. + * + * @param variable the variable type to check + * @param is2013 the target module's-generation + * @return true if a poll is required to get the new status-value + */ + public boolean shouldPollStatusAfterCommand(int firmwareVersion) { + // Regulator set-points will send status-messages on every change (all firmware versions) + if (type == Type.REGULATOR) { + return false; + } + // Thresholds since 170206 will send status-messages on every change + if (firmwareVersion >= LcnBindingConstants.FIRMWARE_2013 && type == Type.THRESHOLD) { + return false; + } + // Others: + // - Variables before 170206 will never send any status-messages + // - Variables since 170206 only send status-messages on "big" changes + // - Thresholds before 170206 will never send any status-messages + // - S0-inputs only send status-messages on "big" changes + // (all "big changes" cases force us to poll the status to get faster updates) + return true; + } + + /** + * Module-generation check. + * Checks if the target LCN module would automatically send status-updates if + * the given regulator's lock-state was changed by command. + * + * @param firmwareVersion the target LCN-module's firmware version + * @param lockState the lock-state sent via command + * @return true if a poll is required to get the new status-value + */ + public boolean shouldPollStatusAfterRegulatorLock(int firmwareVersion, boolean lockState) { + // LCN modules before 170206 will send an automatic status-message for "lock", but not for "unlock" + return !lockState && firmwareVersion < LcnBindingConstants.FIRMWARE_2013; + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/VariableValue.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/VariableValue.java new file mode 100644 index 0000000000000..90c6bf83c8cb6 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/common/VariableValue.java @@ -0,0 +1,99 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.common; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.DecimalType; +import org.eclipse.smarthome.core.library.types.StringType; +import org.eclipse.smarthome.core.types.State; + +/** + * A value of an LCN variable. + *

    + * It internally stores the native LCN value and allows to convert from/into other units. + * Some conversions allow to specify whether the source value is absolute or relative. + * Relative values are used to create {@link VariableValue}s that can be added/subtracted from + * other (absolute) {@link VariableValue}s. + * + * @author Tobias Jüttner - Initial Contribution + * @author Fabian Wolter - Migration to OH2 + */ +@NonNullByDefault +public class VariableValue { + private static final String SENSOR_DEFECTIVE_STATE = "DEFECTIVE"; + + /** The absolute, native LCN value. */ + private final long nativeValue; + + /** + * Constructor with native LCN value. + * + * @param nativeValue the native value + */ + public VariableValue(long nativeValue) { + this.nativeValue = nativeValue; + } + + /** + * Converts to native value. Mask locked bit. + * + * @return the converted value + */ + public long toNative(boolean useSpecialValues) { + if (useSpecialValues) { + return nativeValue & 0x7fff; + } else { + return nativeValue; + } + } + + /** + * Returns the lock state if value comes from a regulator set-point. + * If the variable type is not a regulator, the result is undefined. + * + * @return true if the regulator is locked + */ + public boolean isRegulatorLocked() { + return (this.nativeValue & 0x8000) != 0; + } + + /** + * Returns the defective state of the originating sensor for this variable. + * + * @return true if the sensor is defective + */ + public boolean isSensorDefective() { + return nativeValue == 0x7f00; + } + + /** + * Returns the configuration state of the variable. + * + * @return true if the variable is configured via LCN-PRO + */ + public boolean isConfigured() { + return this.nativeValue != 0xFFFF; + } + + public State getState(Variable variable) { + State stateValue; + if (variable.useLcnSpecialValues() && isSensorDefective()) { + stateValue = new StringType(SENSOR_DEFECTIVE_STATE); + } else if (variable.useLcnSpecialValues() && !isConfigured()) { + stateValue = new StringType("Not configured in LCN-PRO"); + } else { + stateValue = new DecimalType(toNative(variable.useLcnSpecialValues())); + } + return stateValue; + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/AbstractConnectionState.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/AbstractConnectionState.java new file mode 100644 index 0000000000000..13d001868090d --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/AbstractConnectionState.java @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.connection; + +import java.io.IOException; +import java.nio.channels.Channel; +import java.util.concurrent.ScheduledExecutorService; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lcn.internal.common.LcnAddr; +import org.openhab.binding.lcn.internal.common.LcnDefs; + +/** + * Base class representing LCN-PCK gateway connection states. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public abstract class AbstractConnectionState extends AbstractState { + /** The PCK gateway's Connection */ + protected final Connection connection; + + public AbstractConnectionState(ConnectionStateMachine context) { + super(context); + this.connection = context.getConnection(); + } + + /** + * Callback method when a PCK message has been received. + * + * @param data the received PCK message without line termination character + */ + public abstract void onPckMessageReceived(String data); + + /** + * Gets the framework's scheduler. + * + * @return the scheduler + */ + public ScheduledExecutorService getScheduler() { + return context.getScheduler(); + } + + /** + * Enqueues a PCK message to be sent. When the connection is offline, the message will be buffered and sent when the + * connection is established. When the enqueued PCK message is too old, it will be discarded before a new connection + * is established. + * + * @param addr the module's address to which is message shall be sent + * @param wantsAck true, if the module shall respond with an Ack upon successful processing + * @param data the PCK message to be sent + */ + public void queue(LcnAddr addr, boolean wantsAck, byte[] data) { + connection.queueOffline(addr, wantsAck, data); + } + + /** + * Shuts the Connection down finally. A shut-down connection cannot re-used. + */ + public void shutdownFinally() { + nextState(ConnectionStateShutdown::new); + } + + /** + * Checks if the given PCK message is an LCN bus disconnect message. If so, openHAB will be informed and the + * Connection's State Machine waits for a re-connect. + * + * @param pck the PCK message to check + */ + protected void parseLcnBusDiconnectMessage(String pck) { + if (pck.equals(LcnDefs.LCNCONNSTATE_DISCONNECTED)) { + connection.getCallback().onOffline("LCN bus not connected to LCN-PCHK/PKE"); + nextState(ConnectionStateWaitForLcnBusConnectedAfterDisconnected::new); + } + } + + /** + * Closes the Connection SocketChannel. + */ + protected void closeSocketChannel() { + try { + Channel socketChannel = connection.getSocketChannel(); + if (socketChannel != null) { + socketChannel.close(); + } + } catch (IOException e) { + // ignore + } + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/AbstractConnectionStateSendCredentials.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/AbstractConnectionStateSendCredentials.java new file mode 100644 index 0000000000000..4037bf47e29ca --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/AbstractConnectionStateSendCredentials.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.connection; + +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lcn.internal.common.LcnException; + +/** + * Base class for sending username or password. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public abstract class AbstractConnectionStateSendCredentials extends AbstractConnectionState { + private static final int AUTH_TIMEOUT_SEC = 10; + + public AbstractConnectionStateSendCredentials(ConnectionStateMachine context) { + super(context); + } + + @Override + public void startWorking() { + addTimer(getScheduler().schedule(() -> nextState(ConnectionStateConnecting::new), AUTH_TIMEOUT_SEC, + TimeUnit.SECONDS)); + } + + /** + * Starts a timeout when the PCK gateway does not answer to the credentials. + */ + protected void startTimeoutTimer() { + addTimer(getScheduler().schedule( + () -> context.handleConnectionFailed( + new LcnException("Network timeout in state " + getClass().getSimpleName())), + connection.getSettings().getTimeout(), TimeUnit.MILLISECONDS)); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/AbstractState.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/AbstractState.java new file mode 100644 index 0000000000000..0b8bbd5a5205b --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/AbstractState.java @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.connection; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ScheduledFuture; +import java.util.function.Function; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Base class for all states used with {@link AbstractStateMachine}. + * + * @param type of the state machine implementation + * @param type of the state implementation + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public abstract class AbstractState, U extends AbstractState> { + private final List> usedTimers = Collections.synchronizedList(new ArrayList<>()); + protected final T context; + + public AbstractState(T context) { + this.context = context; + } + + /** + * Invoked when the State shall start its operation. + */ + protected abstract void startWorking(); + + /** + * Stops all timers, the State has been started. + */ + protected void cancelAllTimers() { + synchronized (usedTimers) { + usedTimers.forEach(t -> t.cancel(true)); + } + } + + /** + * When a state starts a timer, its ScheduledFuture must be registered by this method. All timers added by this + * method, are canceled when the StateMachine leaves this State. + * + * @param timer the new timer + */ + protected void addTimer(ScheduledFuture timer) { + usedTimers.add(timer); + } + + /** + * Sets a new State. The current state is torn down gracefully. + * + * @param newStateFactory the lambda returning the new State + */ + protected void nextState(Function newStateFactory) { + synchronized (context) { + if (context.isStateActive(this)) { + context.setState(newStateFactory); + } + } + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/AbstractStateMachine.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/AbstractStateMachine.java new file mode 100644 index 0000000000000..fbf44830c440e --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/AbstractStateMachine.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.connection; + +import java.util.function.Function; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Base class for state machines. + * + * @param type of the state machine implementation + * @param type of the state implementation + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public abstract class AbstractStateMachine, U extends AbstractState> { + private final Logger logger = LoggerFactory.getLogger(AbstractStateMachine.class); + /** The StateMachine's current state */ + protected @Nullable volatile U state; + + /** + * Sets the current state. + * + * @param newStateFactory the new state's factory + */ + protected synchronized void setState(Function newStateFactory) { + @Nullable + U localState = state; + if (localState != null) { + localState.cancelAllTimers(); + } + + @SuppressWarnings("unchecked") + U newState = newStateFactory.apply((T) this); + + if (localState != null) { + logger.debug("Changing state {} -> {}", localState.getClass().getSimpleName(), + newState.getClass().getSimpleName()); + } + + state = newState; + + state.startWorking(); + } + + protected boolean isStateActive(AbstractState otherState) { + return state == otherState; // compare by identity + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/Connection.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/Connection.java new file mode 100644 index 0000000000000..f89a7051c1e8c --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/Connection.java @@ -0,0 +1,474 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.connection; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousSocketChannel; +import java.nio.channels.Channel; +import java.nio.channels.CompletionHandler; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ScheduledExecutorService; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lcn.internal.common.LcnAddr; +import org.openhab.binding.lcn.internal.common.LcnAddrGrp; +import org.openhab.binding.lcn.internal.common.LcnAddrMod; +import org.openhab.binding.lcn.internal.common.LcnDefs; +import org.openhab.binding.lcn.internal.common.LcnException; +import org.openhab.binding.lcn.internal.common.PckGenerator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class represents a configured connection to one LCN-PCHK. + * It uses a {@link AsynchronousSocketChannel} to connect to LCN-PCHK. + * Included logic: + *

      + *
    • Reconnection on connection loss + *
    • Segment scan (to detect the local segment ID) + *
    • Acknowledge handling + *
    • Periodic value requests + *
    • Caching of runtime data about the underlying LCN bus + *
    + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class Connection { + private final Logger logger = LoggerFactory.getLogger(Connection.class); + private static final int BROADCAST_MODULE_ID = 3; + private static final int BROADCAST_SEGMENT_ID = 3; + private final ConnectionSettings settings; + private final ConnectionCallback callback; + @Nullable + private AsynchronousSocketChannel channel; + /** The local segment id. -1 means "unknown". */ + private int localSegId; + private final ByteBuffer readBuffer = ByteBuffer.allocate(1024); + private final ByteArrayOutputStream sendBuffer = new ByteArrayOutputStream(); + private final Queue<@Nullable SendData> sendQueue = new LinkedBlockingQueue<>(); + private final BlockingQueue offlineSendQueue = new LinkedBlockingQueue<>(); + private final Map modData = Collections.synchronizedMap(new HashMap<>()); + private volatile boolean writeInProgress; + private final ScheduledExecutorService scheduler; + private final ConnectionStateMachine connectionStateMachine; + + /** + * Constructs a clean (disconnected) connection with the given settings. + * This does not start the actual connection process. + * + * @param sets the settings to use for the new connection + * @param callback the callback to the owner + * @throws IOException + */ + public Connection(ConnectionSettings sets, ScheduledExecutorService scheduler, ConnectionCallback callback) { + this.settings = sets; + this.callback = callback; + this.scheduler = scheduler; + this.clearRuntimeData(); + + connectionStateMachine = new ConnectionStateMachine(this, scheduler); + } + + /** Clears all runtime data. */ + void clearRuntimeData() { + this.channel = null; + this.localSegId = -1; + this.readBuffer.clear(); + this.sendQueue.clear(); + this.sendBuffer.reset(); + } + + /** + * Retrieves the settings for this connection (never changed). + * + * @return the settings + */ + public ConnectionSettings getSettings() { + return this.settings; + } + + private boolean isSocketConnected() { + try { + AsynchronousSocketChannel localChannel = channel; + return localChannel != null && localChannel.getRemoteAddress() != null; + } catch (IOException e) { + return false; + } + } + + /** + * Sets the local segment id. + * + * @param localSegId the new local segment id + */ + public void setLocalSegId(int localSegId) { + this.localSegId = localSegId; + } + + /** + * Called whenever an acknowledge is received. + * + * @param addr the source LCN module + * @param code the LCN internal code (-1 = "positive") + */ + public void onAck(LcnAddrMod addr, int code) { + synchronized (modData) { + if (modData.containsKey(addr)) { + modData.get(addr).onAck(code, this, this.settings.getTimeout(), System.nanoTime()); + } + } + } + + /** + * Creates and/or returns cached data for the given LCN module. + * + * @param addr the module's address + * @return the data + */ + public ModInfo updateModuleData(LcnAddrMod addr) { + return modData.computeIfAbsent(addr, ModInfo::new); + } + + /** + * Reads and processes input from the underlying channel. + * Fragmented input is kept in {@link #readBuffer} and will be processed with the next call. + * + * @throws IOException if connection was closed or a generic channel error occurred + */ + void readAndProcess() { + AsynchronousSocketChannel localChannel = channel; + if (localChannel != null && isSocketConnected()) { + localChannel.read(readBuffer, null, new CompletionHandler<@Nullable Integer, @Nullable Void>() { + @Override + public void completed(@Nullable Integer transmittedByteCount, @Nullable Void attachment) { + synchronized (Connection.this) { + if (transmittedByteCount == null || transmittedByteCount == -1) { + String msg = "Connection was closed by foreign host."; + connectionStateMachine.handleConnectionFailed(new LcnException(msg)); + } else { + // read data chunks from socket and separate frames + readBuffer.flip(); + int aPos = readBuffer.position(); // 0 + String s = new String(readBuffer.array(), aPos, transmittedByteCount, LcnDefs.LCN_ENCODING); + int pos1 = 0, pos2 = s.indexOf(PckGenerator.TERMINATION, pos1); + while (pos2 != -1) { + String data = s.substring(pos1, pos2); + if (logger.isTraceEnabled()) { + logger.trace("Received: '{}'", data); + } + scheduler.submit(() -> { + connectionStateMachine.onInputReceived(data); + callback.onPckMessageReceived(data); + }); + // Seek position in input array + aPos += s.substring(pos1, pos2 + 1).getBytes(LcnDefs.LCN_ENCODING).length; + // Next input + pos1 = pos2 + 1; + pos2 = s.indexOf(PckGenerator.TERMINATION, pos1); + } + readBuffer.limit(readBuffer.capacity()); + readBuffer.position(transmittedByteCount - aPos); // Keeps fragments for the next call + + if (isSocketConnected()) { + readAndProcess(); + } + } + } + } + + @Override + public void failed(@Nullable Throwable e, @Nullable Void attachment) { + logger.debug("Lost connection"); + connectionStateMachine.handleConnectionFailed(e); + } + }); + } else { + connectionStateMachine.handleConnectionFailed(new LcnException("Socket not open")); + } + } + + /** + * Writes all queued data. + * Will try to write all data at once to reduce overhead. + */ + public synchronized void triggerWriteToSocket() { + AsynchronousSocketChannel localChannel = channel; + if (localChannel == null || !isSocketConnected() || writeInProgress) { + return; + } + sendBuffer.reset(); + SendData item = sendQueue.poll(); + + if (item != null) { + try { + if (!item.write(sendBuffer, localSegId)) { + logger.warn("Data loss: Could not write packet into send buffer"); + } + + writeInProgress = true; + byte[] data = sendBuffer.toByteArray(); + localChannel.write(ByteBuffer.wrap(data), null, + new CompletionHandler<@Nullable Integer, @Nullable Void>() { + @Override + public void completed(@Nullable Integer result, @Nullable Void attachment) { + synchronized (Connection.this) { + if (result != data.length) { + logger.warn("Data loss while writing to channel: {}", settings.getAddress()); + } else { + if (logger.isTraceEnabled()) { + logger.trace("Sent: {}", new String(data, 0, data.length)); + } + } + + writeInProgress = false; + + if (sendQueue.size() > 0) { + /** + * This could lead to stack overflows, since the CompletionHandler may run in + * the same Thread as triggerWriteToSocket() is invoked (see + * {@link AsynchronousChannelGroup}/Threading), but we do not expect as much + * data in one chunk here, that the stack can be filled in a critical way. + */ + triggerWriteToSocket(); + } + } + } + + @Override + public void failed(@Nullable Throwable exc, @Nullable Void attachment) { + synchronized (Connection.this) { + if (exc != null) { + logger.warn("Writing to channel \"{}\" failed: {}", settings.getAddress(), + exc.getMessage()); + } + writeInProgress = false; + connectionStateMachine.handleConnectionFailed(new LcnException("write() failed")); + } + } + }); + } catch (BufferOverflowException | IOException e) { + logger.warn("Sending failed: {}: {}: {}", item, e.getClass().getSimpleName(), e.getMessage()); + } + } + } + + /** + * Queues plain text to be sent to LCN-PCHK. + * Sending will be done the next time {@link #triggerWriteToSocket()} is called. + * + * @param plainText the text + */ + public void queueDirectlyPlainText(String plainText) { + this.queueAndSend(new SendDataPlainText(plainText)); + } + + /** + * Queues a PCK command to be sent. + * + * @param addr the target LCN address + * @param wantsAck true to wait for acknowledge on receipt (should be false for group addresses) + * @param pck the pure PCK command (without address header) + */ + void queueDirectly(LcnAddr addr, boolean wantsAck, String pck) { + this.queueDirectly(addr, wantsAck, pck.getBytes(LcnDefs.LCN_ENCODING)); + } + + /** + * Queues a PCK command for immediate sending, regardless of the Connection state. The PCK command is automatically + * re-sent if the destination is not a group, an Ack is requested and the module did not answer within the expected + * time. + * + * @param addr the target LCN address + * @param wantsAck true to wait for acknowledge on receipt (should be false for group addresses) + * @param data the pure PCK command (without address header) + */ + void queueDirectly(LcnAddr addr, boolean wantsAck, byte[] data) { + if (!addr.isGroup() && wantsAck) { + this.updateModuleData((LcnAddrMod) addr).queuePckCommandWithAck(data, this, this.settings.getTimeout(), + System.nanoTime()); + } else { + this.queueAndSend(new SendDataPck(addr, false, data)); + } + } + + /** + * Enqueues a raw PCK command and triggers the socket to start sending, if it does not already. Does not take care + * of any Acks. + * + * @param data raw PCK command + */ + synchronized void queueAndSend(SendData data) { + this.sendQueue.add(data); + + triggerWriteToSocket(); + } + + /** + * Enqueues a PCK command to the offline queue. Data will be sent when the Connection state will enter + * {@link ConnectionStateConnected}. + * + * @param addr LCN module address + * @param wantsAck true, if the LCN module shall respond with an Ack on successful processing + * @param data the pure PCK command (without address header) + */ + void queueOffline(LcnAddr addr, boolean wantsAck, byte[] data) { + offlineSendQueue.add(new PckQueueItem(addr, wantsAck, data)); + } + + /** + * Enqueues a PCK command for sending. Takes care of the Connection state and buffers the command for a specific + * time if the Connection is not ready. If an Ack is requested, the PCK command is automatically + * re-sent, if the module did not answer in the expected time. + * + * @param addr LCN module address + * @param wantsAck true, if the LCN module shall respond with an Ack on successful processing + * @param pck the pure PCK command (without address header) + */ + public void queue(LcnAddr addr, boolean wantsAck, String pck) { + this.queue(addr, wantsAck, pck.getBytes(LcnDefs.LCN_ENCODING)); + } + + /** + * Enqueues a PCK command for sending. Takes care of the Connection state and buffers the command for a specific + * time if the Connection is not ready. If an Ack is requested, the PCK command is automatically + * re-sent, if the module did not answer in the expected time. + * + * @param addr LCN module address + * @param wantsAck true, if the LCN module shall respond with an Ack on successful processing + * @param pck the pure PCK command (without address header) + */ + public void queue(LcnAddr addr, boolean wantsAck, byte[] pck) { + connectionStateMachine.queue(addr, wantsAck, pck); + } + + /** + * Process the offline PCK command queue. Does only send recently enqueued PCK commands, the rest is discarded. + */ + void sendOfflineQueue() { + List allItems = new ArrayList<>(offlineSendQueue.size()); + offlineSendQueue.drainTo(allItems); + + allItems.forEach(item -> { + // only send messages that were enqueued recently, discard older messages + long timeout = settings.getTimeout(); + if (item.getEnqueued().isAfter(Instant.now().minus(timeout * 4, ChronoUnit.MILLIS))) { + queueDirectly(item.getAddr(), item.isWantsAck(), item.getData()); + } + }); + } + + /** + * Gets the Connection's callback. + * + * @return the callback + */ + public ConnectionCallback getCallback() { + return callback; + } + + /** + * Sets the SocketChannel of this Connection + * + * @param channel the new Channel + */ + public void setSocketChannel(AsynchronousSocketChannel channel) { + this.channel = channel; + } + + /** + * Gets the SocketChannel of the Connection. + * + * @returnthe socket channel + */ + @Nullable + public Channel getSocketChannel() { + return channel; + } + + /** + * Gets the local segment ID. When no segments are used, the local segment ID is 0. + * + * @return the local segment ID + */ + public int getLocalSegId() { + return localSegId; + } + + /** + * Runs the periodic updates on all ModInfos. + */ + public void updateModInfos() { + synchronized (modData) { + modData.values().forEach(i -> i.update(this, settings.getTimeout(), System.nanoTime())); + } + } + + /** + * Removes an LCN module from the ModData list. + * + * @param addr the module's address to be removed + */ + public void removeLcnModule(LcnAddr addr) { + modData.remove(addr); + } + + /** + * Invoked when this Connection shall be shut-down finally. + */ + public void shutdown() { + connectionStateMachine.shutdownFinally(); + } + + /** + * Sends a broadcast to all LCN modules with a reuqest to respond with an Ack. + */ + public void sendModuleDiscoveryCommand() { + queueAndSend(new SendDataPck(new LcnAddrGrp(BROADCAST_SEGMENT_ID, BROADCAST_MODULE_ID), true, + PckGenerator.nullCommand().getBytes(LcnDefs.LCN_ENCODING))); + queueAndSend(new SendDataPck(new LcnAddrGrp(0, BROADCAST_MODULE_ID), true, + PckGenerator.nullCommand().getBytes(LcnDefs.LCN_ENCODING))); + } + + /** + * Requests the serial number and the firmware version of the given LCN module. + * + * @param addr module's address + */ + public void sendSerialNumberRequest(LcnAddrMod addr) { + queueDirectly(addr, false, PckGenerator.requestSn()); + } + + /** + * Requests theprogrammed name of the given LCN module. + * + * @param addr module's address + */ + public void sendModuleNameRequest(LcnAddrMod addr) { + queueDirectly(addr, false, PckGenerator.requestModuleName(0)); + queueDirectly(addr, false, PckGenerator.requestModuleName(1)); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionCallback.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionCallback.java new file mode 100644 index 0000000000000..0a2b116250453 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionCallback.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.connection; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Handles events from the connection to the LCN-PCK gateway. + * + * @author Tobias Jüttner - Initial Contribution + * @author Fabian Wolter - Migration to OH2 + */ +@NonNullByDefault +public interface ConnectionCallback { + /** + * Invoked when the Connection to the PCK gateway is established and the LCN bus is connected to the PCK gateway. + */ + void onOnline(); + + /** + * Invoked when the Connection to the PCK gateway has been closed or when the LCN bus is disconnected from the PCK + * gateway. + * + * @param errorMessage the reason + */ + void onOffline(String errorMessage); + + /** + * Invoked when a PCK message has been reived from the PCK gateway. + * + * @param message the received message + */ + void onPckMessageReceived(String message); +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionSettings.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionSettings.java new file mode 100644 index 0000000000000..dae04ef589fe6 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionSettings.java @@ -0,0 +1,158 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.connection; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lcn.internal.common.LcnDefs; + +/** + * Settings for a connection to LCN-PCHK. + * + * @author Tobias Jüttner - Initial Contribution + */ +@NonNullByDefault +public class ConnectionSettings { + + /** Unique identifier for this connection. */ + private final String id; + + /** The user name for authentication. */ + private final String username; + + /** The password for authentication. */ + private final String password; + + /** The TCP/IP address or IP of the connection. */ + private final String address; + + /** The TCP/IP port of the connection. */ + private final int port; + + /** The dimming mode to use. */ + private final LcnDefs.OutputPortDimMode dimMode; + + /** The status-messages mode to use. */ + private final LcnDefs.OutputPortStatusMode statusMode; + + /** Timeout for requests. */ + private final long timeoutMSec; + + /** + * Constructor. + * + * @param id the connnection's unique identifier + * @param address the connection's TCP/IP address or IP + * @param port the connection's TCP/IP port + * @param username the user name for authentication + * @param password the password for authentication + * @param dimMode the dimming mode + * @param statusMode the status-messages mode + * @param timeout the request timeout + */ + public ConnectionSettings(String id, String address, int port, String username, String password, + LcnDefs.OutputPortDimMode dimMode, LcnDefs.OutputPortStatusMode statusMode, int timeout) { + this.id = id; + this.address = address; + this.port = port; + this.username = username; + this.password = password; + this.dimMode = dimMode; + this.statusMode = statusMode; + this.timeoutMSec = timeout; + } + + /** + * Gets the unique identifier for the connection. + * + * @return the unique identifier + */ + public String getId() { + return this.id; + } + + /** + * Gets the user name used for authentication. + * + * @return the user name + */ + public String getUsername() { + return this.username; + } + + /** + * Gets the password used for authentication. + * + * @return the password + */ + public String getPassword() { + return this.password; + } + + /** + * Gets the TCP/IP address or IP of the connection. + * + * @return the address or IP + */ + public String getAddress() { + return this.address; + } + + /** + * Gets the TCP/IP port of the connection. + * + * @return the port + */ + public int getPort() { + return this.port; + } + + /** + * Gets the dimming mode to use for the connection. + * + * @return the dimming mode + */ + public LcnDefs.OutputPortDimMode getDimMode() { + return this.dimMode; + } + + /** + * Gets the status-messages mode to use for the connection. + * + * @return the status-messages mode + */ + public LcnDefs.OutputPortStatusMode getStatusMode() { + return this.statusMode; + } + + /** + * Gets the request timeout. + * + * @return the timeout in milliseconds + */ + public long getTimeout() { + return this.timeoutMSec; + } + + @Override + public boolean equals(@Nullable Object o) { + if (!(o instanceof ConnectionSettings)) { + return false; + } + ConnectionSettings other = (ConnectionSettings) o; + return this.id.equals(other.id) && this.address.equals(other.address) && this.port == other.port + && this.username.equals(other.username) && this.password.equals(other.password) + && this.dimMode == other.dimMode && this.statusMode == other.statusMode + && this.timeoutMSec == other.timeoutMSec; + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateConnected.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateConnected.java new file mode 100644 index 0000000000000..55acca37974b0 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateConnected.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.connection; + +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lcn.internal.common.LcnAddr; +import org.openhab.binding.lcn.internal.common.PckGenerator; + +/** + * This state is active when the connection to the LCN bus has been established successfully and data can be sent and + * retrieved. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class ConnectionStateConnected extends AbstractConnectionState { + private static final int PING_INTERVAL_SEC = 60; + private int pingCounter; + + public ConnectionStateConnected(ConnectionStateMachine context) { + super(context); + } + + @Override + public void startWorking() { + // send periodic keep-alives to keep the connection open + addTimer(getScheduler().scheduleWithFixedDelay( + () -> connection.queueDirectlyPlainText(PckGenerator.ping(++pingCounter)), PING_INTERVAL_SEC, + PING_INTERVAL_SEC, TimeUnit.SECONDS)); + + // run ModInfo.update() for every LCN module + addTimer(getScheduler().scheduleWithFixedDelay(connection::updateModInfos, 0, 1, TimeUnit.SECONDS)); + + connection.sendOfflineQueue(); + } + + @Override + public void queue(LcnAddr addr, boolean wantsAck, byte[] data) { + connection.queueDirectly(addr, wantsAck, data); + } + + @Override + public void onPckMessageReceived(String data) { + parseLcnBusDiconnectMessage(data); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateConnecting.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateConnecting.java new file mode 100644 index 0000000000000..35f54d8b22795 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateConnecting.java @@ -0,0 +1,97 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.connection; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.StandardSocketOptions; +import java.nio.channels.AsynchronousSocketChannel; +import java.nio.channels.CompletionHandler; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lcn.internal.common.LcnException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This state is active during the socket creation, host name resolving and waiting for the TCP connection to become + * established. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class ConnectionStateConnecting extends AbstractConnectionState { + private final Logger logger = LoggerFactory.getLogger(ConnectionStateConnecting.class); + + public ConnectionStateConnecting(ConnectionStateMachine context) { + super(context); + } + + @Override + public void startWorking() { + connection.clearRuntimeData(); + + logger.debug("Connecting to {}:{} ...", connection.getSettings().getAddress(), + connection.getSettings().getPort()); + + try { + // Open Channel by using the system-wide default AynchronousChannelGroup. + // So, Threads are used or re-used on demand by the JVM. + AsynchronousSocketChannel channel = AsynchronousSocketChannel.open(); + // Do not wait until some buffer is filled, send PCK commands immediately + channel.setOption(StandardSocketOptions.TCP_NODELAY, true); + connection.setSocketChannel(channel); + + InetSocketAddress address = new InetSocketAddress(connection.getSettings().getAddress(), + connection.getSettings().getPort()); + + if (address.isUnresolved()) { + throw new LcnException("Could not resolve hostname"); + } + + channel.connect(address, null, new CompletionHandler<@Nullable Void, @Nullable Void>() { + @Override + public void completed(@Nullable Void result, @Nullable Void attachment) { + connection.readAndProcess(); + nextState(ConnectionStateSendUsername::new); + } + + @Override + public void failed(@Nullable Throwable e, @Nullable Void attachment) { + handleConnectionFailure(e); + } + }); + } catch (IOException | LcnException e) { + handleConnectionFailure(e); + } + } + + private void handleConnectionFailure(@Nullable Throwable e) { + String message; + if (e != null) { + logger.warn("Could not connect to {}:{}: {}", connection.getSettings().getAddress(), + connection.getSettings().getPort(), e.getMessage()); + message = e.getMessage(); + } else { + message = ""; + } + connection.getCallback().onOffline(message); + context.handleConnectionFailed(e); + } + + @Override + public void onPckMessageReceived(String data) { + // nothing + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateGracePeriodBeforeReconnect.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateGracePeriodBeforeReconnect.java new file mode 100644 index 0000000000000..e6d05cba3ff49 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateGracePeriodBeforeReconnect.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.connection; + +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * This state is active when the connection failed. A grace period is enforced to prevent fast cycling through the + * states. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class ConnectionStateGracePeriodBeforeReconnect extends AbstractConnectionState { + private static final int RECONNECT_GRACE_PERIOD_SEC = 5; + + public ConnectionStateGracePeriodBeforeReconnect(ConnectionStateMachine context) { + super(context); + } + + @Override + public void startWorking() { + closeSocketChannel(); + + addTimer(getScheduler().schedule(() -> nextState(ConnectionStateConnecting::new), RECONNECT_GRACE_PERIOD_SEC, + TimeUnit.SECONDS)); + } + + @Override + public void onPckMessageReceived(String data) { + // nothing + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateInit.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateInit.java new file mode 100644 index 0000000000000..a4c3790ee96a2 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateInit.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.connection; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * This is the initial state of the {@link ConnectionStateMachine}. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class ConnectionStateInit extends AbstractConnectionState { + public ConnectionStateInit(ConnectionStateMachine context) { + super(context); + } + + @Override + public void startWorking() { + nextState(ConnectionStateConnecting::new); + } + + @Override + public void onPckMessageReceived(String data) { + // nothing + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateMachine.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateMachine.java new file mode 100644 index 0000000000000..9e83f7ffeaa3b --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateMachine.java @@ -0,0 +1,107 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.connection; + +import java.util.concurrent.ScheduledExecutorService; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lcn.internal.common.LcnAddr; + +/** + * Implements a state machine for managing the connection to the LCN-PCK gateway. Setting states is thread-safe. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class ConnectionStateMachine extends AbstractStateMachine { + private final Connection connection; + final ScheduledExecutorService scheduler; + + public ConnectionStateMachine(Connection connection, ScheduledExecutorService scheduler) { + this.connection = connection; + this.scheduler = scheduler; + + setState(ConnectionStateInit::new); + } + + /** + * Gets the framework's scheduler. + * + * @return the scheduler + */ + protected ScheduledExecutorService getScheduler() { + return scheduler; + } + + /** + * Gets the PCHK Connection object. + * + * @return the connection + */ + public Connection getConnection() { + return connection; + } + + /** + * Enqueues a PCK command. Implementation is state dependent. + * + * @param addr the destination address + * @param wantsAck true, if the module shall respond with an Ack + * @param data the data + */ + public void queue(LcnAddr addr, boolean wantsAck, byte[] data) { + AbstractConnectionState localState = state; + if (localState != null) { + localState.queue(addr, wantsAck, data); + } + } + + /** + * Invoked by any state, if the connection fails. + * + * @param e the cause + */ + public void handleConnectionFailed(@Nullable Throwable e) { + if (!(state instanceof ConnectionStateShutdown)) { + if (e != null) { + connection.getCallback().onOffline(e.getMessage()); + } else { + connection.getCallback().onOffline(""); + } + setState(ConnectionStateGracePeriodBeforeReconnect::new); + } + } + + /** + * Processes a received PCK message by passing it to the current State. + * + * @param data the PCK message + */ + public void onInputReceived(String data) { + AbstractConnectionState localState = state; + if (localState != null) { + localState.onPckMessageReceived(data); + } + } + + /** + * Shuts the StateMachine down finally. A shut-down StateMachine cannot be re-used. + */ + public void shutdownFinally() { + AbstractConnectionState localState = state; + if (localState != null) { + localState.shutdownFinally(); + } + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateSegmentScan.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateSegmentScan.java new file mode 100644 index 0000000000000..0942f777d85b9 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateSegmentScan.java @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.connection; + +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lcn.internal.common.LcnAddrGrp; +import org.openhab.binding.lcn.internal.common.LcnException; +import org.openhab.binding.lcn.internal.common.PckGenerator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This state discovers the LCN segment couplers. + * + * After the authorization against the LCN-PCK gateway was successful, the LCN segment couplers are discovery, to + * retrieve the segment ID of the local segment. When no segment couplers were found, a timeout sets the local segment + * ID to 0. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class ConnectionStateSegmentScan extends AbstractConnectionState { + private final Logger logger = LoggerFactory.getLogger(ConnectionStateSegmentScan.class); + public static final Pattern PATTERN_SK_RESPONSE = Pattern + .compile("=M(?\\d{3})(?\\d{3})\\.SK(?\\d+)"); + private final RequestStatus statusSegmentScan = new RequestStatus(-1, 3, "Segment Scan"); + + public ConnectionStateSegmentScan(ConnectionStateMachine context) { + super(context); + } + + @Override + public void startWorking() { + statusSegmentScan.refresh(); + addTimer(getScheduler().scheduleWithFixedDelay(this::update, 0, 500, TimeUnit.MILLISECONDS)); + } + + private void update() { + long currTime = System.nanoTime(); + try { + if (statusSegmentScan.shouldSendNextRequest(connection.getSettings().getTimeout(), currTime)) { + connection.queueDirectly(new LcnAddrGrp(3, 3), false, PckGenerator.segmentCouplerScan()); + statusSegmentScan.onRequestSent(currTime); + } + } catch (LcnException e) { + // Give up. Probably no segments available. + connection.setLocalSegId(0); + logger.debug("No segment couplers detected"); + nextState(ConnectionStateConnected::new); + } + } + + @Override + public void onPckMessageReceived(String data) { + Matcher matcher = PATTERN_SK_RESPONSE.matcher(data); + + if (matcher.matches()) { + // any segment coupler answered + if (Integer.parseInt(matcher.group("segId")) == 0) { + // local segment coupler answered + connection.setLocalSegId(Integer.parseInt(matcher.group("id"))); + logger.debug("Local segment ID is {}", connection.getLocalSegId()); + nextState(ConnectionStateConnected::new); + } + } + parseLcnBusDiconnectMessage(data); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateSendDimMode.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateSendDimMode.java new file mode 100644 index 0000000000000..bfcf89f37a766 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateSendDimMode.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.connection; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lcn.internal.common.PckGenerator; + +/** + * Sets the dimming mode range (0-50 or 0-200) in the LCN-PCK for this connection, as configured by the user. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class ConnectionStateSendDimMode extends AbstractConnectionState { + public ConnectionStateSendDimMode(ConnectionStateMachine context) { + super(context); + } + + @Override + public void startWorking() { + connection.queueDirectlyPlainText(PckGenerator.setOperationMode(connection.getSettings().getDimMode(), + connection.getSettings().getStatusMode())); + + nextState(ConnectionStateSegmentScan::new); + } + + @Override + public void onPckMessageReceived(String data) { + // nothing + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateSendPassword.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateSendPassword.java new file mode 100644 index 0000000000000..01ae13bd3fe72 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateSendPassword.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.connection; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lcn.internal.common.LcnDefs; + +/** + * This state sends the password during the authentication with the LCN-PCK gateway. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class ConnectionStateSendPassword extends AbstractConnectionStateSendCredentials { + public ConnectionStateSendPassword(ConnectionStateMachine context) { + super(context); + } + + @Override + public void startWorking() { + startTimeoutTimer(); + } + + @Override + public void onPckMessageReceived(String data) { + if (data.equals(LcnDefs.AUTH_PASSWORD)) { + connection.queueDirectlyPlainText(connection.getSettings().getPassword()); + nextState(ConnectionStateWaitForLcnBusConnected::new); + } + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateSendUsername.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateSendUsername.java new file mode 100644 index 0000000000000..a04f1d341a3c3 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateSendUsername.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.connection; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lcn.internal.common.LcnDefs; + +/** + * This state sends the username during the authentication with the LCN-PCK gateway. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class ConnectionStateSendUsername extends AbstractConnectionStateSendCredentials { + public ConnectionStateSendUsername(ConnectionStateMachine context) { + super(context); + } + + @Override + public void startWorking() { + startTimeoutTimer(); + } + + @Override + public void onPckMessageReceived(String data) { + if (data.equals(LcnDefs.AUTH_USERNAME)) { + connection.queueDirectlyPlainText(connection.getSettings().getUsername()); + nextState(ConnectionStateSendPassword::new); + } + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateShutdown.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateShutdown.java new file mode 100644 index 0000000000000..67c7ff2100e25 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateShutdown.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.connection; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lcn.internal.common.LcnAddr; + +/** + * This state is entered when the connection shall be shut-down finally. This happens when Thing.dispose() is called. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class ConnectionStateShutdown extends AbstractConnectionState { + public ConnectionStateShutdown(ConnectionStateMachine context) { + super(context); + } + + @Override + public void startWorking() { + closeSocketChannel(); + + // end state + } + + @Override + public void queue(LcnAddr addr, boolean wantsAck, byte[] data) { + // nothing + } + + @Override + public void onPckMessageReceived(String data) { + // nothing + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateWaitForLcnBusConnected.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateWaitForLcnBusConnected.java new file mode 100644 index 0000000000000..8882042cebed4 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateWaitForLcnBusConnected.java @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.connection; + +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lcn.internal.common.LcnDefs; +import org.openhab.binding.lcn.internal.common.LcnException; + +/** + * This state waits for the status answer of the LCN-PCK gateway after connection establishment, rather the LCN bus is + * connected. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class ConnectionStateWaitForLcnBusConnected extends AbstractConnectionState { + private @Nullable ScheduledFuture legacyTimer; + + public ConnectionStateWaitForLcnBusConnected(ConnectionStateMachine context) { + super(context); + } + + @Override + public void startWorking() { + // Legacy support for LCN-PCHK 2.2 and earlier: + // There was no explicit "LCN connected" notification after successful authentication. + // Only "LCN disconnected" would be reported immediately. That means "LCN connected" used to be the default. + ScheduledFuture localLegacyTimer = legacyTimer = getScheduler().schedule(() -> { + connection.getCallback().onOnline(); + nextState(ConnectionStateSendDimMode::new); + }, connection.getSettings().getTimeout(), TimeUnit.MILLISECONDS); + addTimer(localLegacyTimer); + } + + @Override + public void onPckMessageReceived(String data) { + ScheduledFuture localLegacyTimer = legacyTimer; + if (data.equals(LcnDefs.LCNCONNSTATE_DISCONNECTED)) { + if (localLegacyTimer != null) { + localLegacyTimer.cancel(true); + } + connection.getCallback().onOffline("LCN bus not connected to LCN-PCHK/PKE"); + } else if (data.equals(LcnDefs.LCNCONNSTATE_CONNECTED)) { + if (localLegacyTimer != null) { + localLegacyTimer.cancel(true); + } + connection.getCallback().onOnline(); + nextState(ConnectionStateSendDimMode::new); + } else if (data.equals(LcnDefs.INSUFFICIENT_LICENSES)) { + context.handleConnectionFailed( + new LcnException("LCN-PCHK/PKE has not enough licenses to handle this connection")); + } + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateWaitForLcnBusConnectedAfterDisconnected.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateWaitForLcnBusConnectedAfterDisconnected.java new file mode 100644 index 0000000000000..8174ada6eecb7 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ConnectionStateWaitForLcnBusConnectedAfterDisconnected.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.connection; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * This state is entered when the LCN-PCK gateway sent a message, that the connection to the LCN bus was lost. This can + * happen if the user plugs the USB cable to the PC coupler. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class ConnectionStateWaitForLcnBusConnectedAfterDisconnected extends ConnectionStateWaitForLcnBusConnected { + public ConnectionStateWaitForLcnBusConnectedAfterDisconnected(ConnectionStateMachine context) { + super(context); + } + + @Override + public void startWorking() { + // nothing, don't start legacy timer + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ModInfo.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ModInfo.java new file mode 100644 index 0000000000000..a7799240c2e3f --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/ModInfo.java @@ -0,0 +1,500 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.connection; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lcn.internal.LcnBindingConstants; +import org.openhab.binding.lcn.internal.common.LcnAddr; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnDefs; +import org.openhab.binding.lcn.internal.common.LcnException; +import org.openhab.binding.lcn.internal.common.PckGenerator; +import org.openhab.binding.lcn.internal.common.Variable; +import org.openhab.binding.lcn.internal.common.VariableValue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Holds data of an LCN module. + *
      + *
    • Stores the module's firmware version (if requested) + *
    • Manages the scheduling of status-requests + *
    • Manages the scheduling of acknowledged commands + *
    + * + * @author Tobias Jüttner - Initial Contribution + * @author Fabian Wolter - Migration to OH2 + */ +@NonNullByDefault +public class ModInfo { + private final Logger logger = LoggerFactory.getLogger(ModInfo.class); + /** Total number of request to sent before going into failed-state. */ + private static final int NUM_TRIES = 3; + + /** Poll interval for status values that automatically send their values on change. */ + private static final int MAX_STATUS_EVENTBASED_VALUEAGE_MSEC = 600000; + + /** Poll interval for status values that do not send their values on change (always polled). */ + private static final int MAX_STATUS_POLLED_VALUEAGE_MSEC = 30000; + + /** Status request delay after a command has been send which potentially changed that status. */ + private static final int STATUS_REQUEST_DELAY_AFTER_COMMAND_MSEC = 2000; + + /** The LCN module's address. */ + private final LcnAddr addr; + + /** Firmware date of the LCN module. -1 means "unknown". */ + private int firmwareVersion = -1; + + /** Firmware version request status. */ + private final RequestStatus requestFirmwareVersion = new RequestStatus(-1, NUM_TRIES, "Firmware Version"); + + /** Output-port request status (0..3). */ + private final RequestStatus[] requestStatusOutputs = new RequestStatus[LcnChannelGroup.OUTPUT.getCount()]; + + /** Relays request status (all 8). */ + private final RequestStatus requestStatusRelays = new RequestStatus(MAX_STATUS_EVENTBASED_VALUEAGE_MSEC, NUM_TRIES, + "Relays"); + + /** Binary-sensors request status (all 8). */ + private final RequestStatus requestStatusBinSensors = new RequestStatus(MAX_STATUS_EVENTBASED_VALUEAGE_MSEC, + NUM_TRIES, "Binary Sensors"); + + /** + * Variables request status. + * Lazy initialization: Will be filled once the firmware version is known. + */ + private final Map requestStatusVars = new HashMap<>(); + + /** + * Caches the values of the variables, needed for changing the values. + */ + private final Map variableValue = new HashMap<>(); + + /** LEDs and logic-operations request status (all 12+4). */ + private final RequestStatus requestStatusLedsAndLogicOps = new RequestStatus(MAX_STATUS_POLLED_VALUEAGE_MSEC, + NUM_TRIES, "LEDs and Logic"); + + /** Key lock-states request status (all tables, A-D). */ + private final RequestStatus requestStatusLockedKeys = new RequestStatus(MAX_STATUS_POLLED_VALUEAGE_MSEC, NUM_TRIES, + "Key Locks"); + + /** + * Holds the last LCN variable requested whose response will not contain the variable's type. + * {@link Variable#UNKNOWN} means there is currently no such request. + */ + private Variable lastRequestedVarWithoutTypeInResponse = Variable.UNKNOWN; + + /** + * List of queued PCK commands to be acknowledged by the LCN module. + * Commands are always without address header. + * Note that the first one might currently be "in progress". + */ + private final Queue pckCommandsWithAck = new ConcurrentLinkedQueue<>(); + + /** Status data for the currently processed {@link PckCommandWithAck}. */ + private final RequestStatus requestCurrentPckCommandWithAck = new RequestStatus(-1, NUM_TRIES, "Commands with Ack"); + + /** + * Constructor. + * + * @param addr the module's address + */ + public ModInfo(LcnAddr addr) { + this.addr = addr; + for (int i = 0; i < LcnChannelGroup.OUTPUT.getCount(); ++i) { + requestStatusOutputs[i] = new RequestStatus(MAX_STATUS_EVENTBASED_VALUEAGE_MSEC, NUM_TRIES, + "Output " + (i + 1)); + } + + for (Variable var : Variable.values()) { + if (var != Variable.UNKNOWN) { + this.requestStatusVars.put(var, new RequestStatus(MAX_STATUS_POLLED_VALUEAGE_MSEC, NUM_TRIES, + var.getType() + " " + (var.getNumber() + 1))); + } + } + } + + /** + * Gets the last requested variable whose response will not contain the variables type. + * + * @return the "typeless" variable + */ + public Variable getLastRequestedVarWithoutTypeInResponse() { + return this.lastRequestedVarWithoutTypeInResponse; + } + + /** + * Sets the last requested variable whose response will not contain the variables type. + * + * @param var the "typeless" variable + */ + public void setLastRequestedVarWithoutTypeInResponse(Variable var) { + this.lastRequestedVarWithoutTypeInResponse = var; + } + + /** + * Queues a PCK command to be sent. + * It will request an acknowledge from the LCN module on receipt. + * If there is no response within the request timeout, the command is retried. + * + * @param data the PCK command to send (without address header) + * @param timeoutMSec the time to wait for a response before retrying a request + * @param currTime the current time stamp + */ + public void queuePckCommandWithAck(byte[] data, Connection conn, long timeoutMSec, long currTime) { + this.pckCommandsWithAck.add(data); + // Try to process the new acknowledged command. Will do nothing if another one is still in progress. + this.tryProcessNextCommandWithAck(conn, timeoutMSec, currTime); + } + + /** + * Called whenever an acknowledge is received from the LCN module. + * + * @param code the LCN internal code. -1 means "positive" acknowledge + * @param timeoutMSec the time to wait for a response before retrying a request + * @param currTime the current time stamp + */ + public void onAck(int code, Connection conn, long timeoutMSec, long currTime) { + if (this.requestCurrentPckCommandWithAck.isActive()) { // Check if we wait for an ack. + this.pckCommandsWithAck.poll(); + this.requestCurrentPckCommandWithAck.reset(); + // Try to process next acknowledged command + this.tryProcessNextCommandWithAck(conn, timeoutMSec, currTime); + } + } + + /** + * Sends the next acknowledged command from the queue. + * + * @param conn the {@link Connection} belonging to this {@link ModInfo} + * @param timeoutMSec the time to wait for a response before retrying a request + * @param currTime the current time stamp + * @return true if a new command was sent + * @throws LcnException when a command response timed out + */ + private boolean tryProcessNextCommandWithAck(Connection conn, long timeoutMSec, long currTime) { + // Use the chance to remove a failed command first + if (this.requestCurrentPckCommandWithAck.isFailed(timeoutMSec, currTime)) { + byte[] failedCommand = this.pckCommandsWithAck.poll(); + this.requestCurrentPckCommandWithAck.reset(); + + if (failedCommand != null) { + logger.warn("{}: Module did not respond to command: {}", addr, + new String(failedCommand, LcnDefs.LCN_ENCODING)); + } + } + // Peek new command + if (!this.pckCommandsWithAck.isEmpty() && !this.requestCurrentPckCommandWithAck.isActive()) { + this.requestCurrentPckCommandWithAck.nextRequestIn(0, currTime); + } + byte[] command = this.pckCommandsWithAck.peek(); + if (command == null) { + return false; + } + try { + if (requestCurrentPckCommandWithAck.shouldSendNextRequest(timeoutMSec, currTime)) { + conn.queueAndSend(new SendDataPck(addr, true, command)); + this.requestCurrentPckCommandWithAck.onRequestSent(currTime); + } + } catch (LcnException e) { + logger.warn("{}: Could not send command: {}: {}", addr, new String(command, LcnDefs.LCN_ENCODING), + e.getMessage()); + } + return true; + } + + /** + * Triggers a request to retrieve the firmware version of the LCN module, if it is not known, yet. + */ + public void requestFirmwareVersion() { + if (firmwareVersion == -1) { + requestFirmwareVersion.refresh(); + } + } + + /** + * Used to check if the module has the measurement processing firmware (since Feb. 2013). + * + * @return if the module has at least 4 threshold registers and 12 variables + */ + public boolean hasExtendedMeasurementProcessing() { + if (firmwareVersion == -1) { + logger.warn("LCN module firmware version unknown"); + return false; + } + return firmwareVersion >= LcnBindingConstants.FIRMWARE_2013; + } + + private boolean update(Connection conn, long timeoutMSec, long currTime, RequestStatus requestStatus, String pck) + throws LcnException { + if (requestStatus.shouldSendNextRequest(timeoutMSec, currTime)) { + conn.queue(this.addr, false, pck); + requestStatus.onRequestSent(currTime); + return true; + } + return false; + } + + /** + * Keeps the request logic active. + * Must be called periodically. + * + * @param conn the {@link Connection} belonging to this {@link ModInfo} + * @param timeoutMSec the time to wait for a response before retrying a request + * @param currTime the current time stamp + */ + void update(Connection conn, long timeoutMSec, long currTime) { + try { + if (update(conn, timeoutMSec, currTime, requestFirmwareVersion, PckGenerator.requestSn())) { + return; + } + + for (int i = 0; i < LcnChannelGroup.OUTPUT.getCount(); ++i) { + if (update(conn, timeoutMSec, currTime, requestStatusOutputs[i], PckGenerator.requestOutputStatus(i))) { + return; + } + } + + if (update(conn, timeoutMSec, currTime, requestStatusRelays, PckGenerator.requestRelaysStatus())) { + return; + } + if (update(conn, timeoutMSec, currTime, requestStatusBinSensors, PckGenerator.requestBinSensorsStatus())) { + return; + } + + // Variable requests + if (this.firmwareVersion != -1) { // Firmware version is required + // Use the chance to remove a failed "typeless variable" request + if (lastRequestedVarWithoutTypeInResponse != Variable.UNKNOWN) { + RequestStatus requestStatus = requestStatusVars.get(lastRequestedVarWithoutTypeInResponse); + if (requestStatus != null && requestStatus.isTimeout(timeoutMSec, currTime)) { + lastRequestedVarWithoutTypeInResponse = Variable.UNKNOWN; + } + } + // Variables + for (Map.Entry kv : this.requestStatusVars.entrySet()) { + RequestStatus requestStatus = kv.getValue(); + if (requestStatus != null && requestStatus.shouldSendNextRequest(timeoutMSec, currTime)) { + // Detect if we can send immediately or if we have to wait for a "typeless" request first + boolean hasTypeInResponse = kv.getKey().hasTypeInResponse(this.firmwareVersion); + if (hasTypeInResponse || this.lastRequestedVarWithoutTypeInResponse == Variable.UNKNOWN) { + try { + conn.queue(this.addr, false, + PckGenerator.requestVarStatus(kv.getKey(), this.firmwareVersion)); + requestStatus.onRequestSent(currTime); + if (!hasTypeInResponse) { + this.lastRequestedVarWithoutTypeInResponse = kv.getKey(); + } + return; + } catch (LcnException ex) { + requestStatus.reset(); + } + } + } + } + } + + if (update(conn, timeoutMSec, currTime, requestStatusLedsAndLogicOps, + PckGenerator.requestLedsAndLogicOpsStatus())) { + return; + } + + if (update(conn, timeoutMSec, currTime, requestStatusLockedKeys, PckGenerator.requestKeyLocksStatus())) { + return; + } + + // Try to send next acknowledged command. Will also detect failed ones. + this.tryProcessNextCommandWithAck(conn, timeoutMSec, currTime); + } catch (LcnException e) { + logger.warn("{}: Failed to receive status message: {}", addr, e.getMessage()); + } + } + + /** + * Gets the LCN module's firmware date. + * + * @return the date + */ + public int getFirmwareVersion() { + return this.firmwareVersion; + } + + /** + * Sets the LCN module's firmware date. + * + * @param firmwareVersion the date + */ + public void setFirmwareVersion(int firmwareVersion) { + this.firmwareVersion = firmwareVersion; + + requestFirmwareVersion.onResponseReceived(); + + // increase poll interval, if the LCN module sends status updates of a variable event-based + requestStatusVars.entrySet().stream().filter(e -> e.getKey().isEventBased(firmwareVersion)).forEach(e -> { + RequestStatus value = e.getValue(); + if (value != null) { + value.setMaxAgeMSec(MAX_STATUS_EVENTBASED_VALUEAGE_MSEC); + } + }); + } + + /** + * Updates the variable value cache. + * + * @param variable the variable to update + * @param value the new value + */ + public void updateVariableValue(Variable variable, VariableValue value) { + variableValue.put(variable, value); + } + + /** + * Gets the current value of a variable from the cache. + * + * @param variable the variable to retrieve the value for + * @return the value of the variable + * @throws LcnException when the variable is not in the cache + */ + public long getVariableValue(Variable variable) throws LcnException { + return Optional.ofNullable(variableValue.get(variable)).map(v -> v.toNative(variable.useLcnSpecialValues())) + .orElseThrow(() -> new LcnException("Current variable value unknown")); + } + + /** + * Requests the current value of all dimmer outputs. + */ + public void refreshAllOutputs() { + Arrays.stream(requestStatusOutputs).forEach(RequestStatus::refresh); + } + + /** + * Requests the current value of the given dimmer output. + * + * @param number 0..3 + */ + public void refreshOutput(int number) { + requestStatusOutputs[number].refresh(); + } + + /** + * Requests the current value of all relays. + */ + public void refreshRelays() { + requestStatusRelays.refresh(); + } + + /** + * Requests the current value of all binary sensor. + */ + public void refreshBinarySensors() { + requestStatusBinSensors.refresh(); + } + + /** + * Requests the current value of the given variable. + * + * @param variable the variable to request + */ + public void refreshVariable(Variable variable) { + RequestStatus requestStatus = requestStatusVars.get(variable); + if (requestStatus != null) { + requestStatus.refresh(); + } + } + + /** + * Requests the current value of all LEDs and logic operations. + */ + public void refreshLedsAndLogic() { + requestStatusLedsAndLogicOps.refresh(); + } + + /** + * Requests the current value of all LEDs and logic operations, after a LED has been changed by openHAB. + */ + public void refreshStatusLedsAnLogicAfterChange() { + requestStatusLedsAndLogicOps.nextRequestIn(STATUS_REQUEST_DELAY_AFTER_COMMAND_MSEC, System.nanoTime()); + } + + /** + * Requests the current locking states of all keys. + */ + public void refreshStatusLockedKeys() { + requestStatusLockedKeys.refresh(); + } + + /** + * Requests the current locking states of all keys, after a lock state has been changed by openHAB. + */ + public void refreshStatusStatusLockedKeysAfterChange() { + requestStatusLockedKeys.nextRequestIn(STATUS_REQUEST_DELAY_AFTER_COMMAND_MSEC, System.nanoTime()); + } + + /** + * Resets the value request logic, when a requested value has been received from the LCN module: Dimmer Output + * + * @param outputId 0..3 + */ + public void onOutputResponseReceived(int outputId) { + requestStatusOutputs[outputId].onResponseReceived(); + } + + /** + * Resets the value request logic, when a requested value has been received from the LCN module: Relay + */ + public void onRelayResponseReceived() { + requestStatusRelays.onResponseReceived(); + } + + /** + * Resets the value request logic, when a requested value has been received from the LCN module: Binary Sensor + */ + public void onBinarySensorsResponseReceived() { + requestStatusBinSensors.onResponseReceived(); + } + + /** + * Resets the value request logic, when a requested value has been received from the LCN module: Variable + * + * @param variable the received variable type + */ + public void onVariableResponseReceived(Variable variable) { + RequestStatus requestStatus = requestStatusVars.get(variable); + if (requestStatus != null) { + requestStatus.onResponseReceived(); + } + } + + /** + * Resets the value request logic, when a requested value has been received from the LCN module: LEDs and logic + */ + public void onLedsAndLogicResponseReceived() { + requestStatusLedsAndLogicOps.onResponseReceived(); + } + + /** + * Resets the value request logic, when a requested value has been received from the LCN module: Keys lock state + */ + public void onLockedKeysResponseReceived() { + requestStatusLockedKeys.onResponseReceived(); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/PckQueueItem.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/PckQueueItem.java new file mode 100644 index 0000000000000..f65ac8fa9ce9e --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/PckQueueItem.java @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.connection; + +import java.time.Instant; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lcn.internal.common.LcnAddr; + +/** + * Holds data of one PCK command with the target address and the date when the item has been enqueued. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class PckQueueItem { + private final Instant enqueued; + private final LcnAddr addr; + private final boolean wantsAck; + private final byte[] data; + + public PckQueueItem(LcnAddr addr, boolean wantsAck, byte[] data) { + this.enqueued = Instant.now(); + this.addr = addr; + this.wantsAck = wantsAck; + this.data = data; + } + + /** + * Gets the time when this message has been enqueued. + * + * @return the Instant + */ + public Instant getEnqueued() { + return enqueued; + } + + /** + * Gets the address of the destination LCN module. + * + * @return the address + */ + public LcnAddr getAddr() { + return addr; + } + + /** + * Checks whether an Ack is requested. + * + * @return true, if an Ack is requested + */ + public boolean isWantsAck() { + return wantsAck; + } + + /** + * Gets the raw PCK message to be sent. + * + * @return message as ByteBuffer + */ + public byte[] getData() { + return data; + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/RequestStatus.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/RequestStatus.java new file mode 100644 index 0000000000000..e5b95e876bda8 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/RequestStatus.java @@ -0,0 +1,195 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.connection; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lcn.internal.common.LcnException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Manages timeout and retry logic for an LCN request. + * + * @author Tobias Jüttner - Initial Contribution + * @author Fabian Wolter - Migration to OH2 + */ +@NonNullByDefault +public class RequestStatus { + private final Logger logger = LoggerFactory.getLogger(RequestStatus.class); + /** Interval for forced updates. -1 if not used. */ + private volatile long maxAgeMSec; + + /** Tells how often a request will be sent if no response was received. */ + private final int numTries; + + /** true if request logic is activated. */ + private volatile boolean isActive; + + /** The time the current request was sent out or 0. */ + private volatile long currRequestTimeStamp; + + /** The time stamp of the next scheduled request or 0. */ + private volatile long nextRequestTimeStamp; + + /** Number of retries left until the request is marked as failed. */ + private volatile int numRetriesLeft; + private final String label; + + /** + * Constructor. + * + * @param maxAgeMSec the forced-updates interval (-1 if not used) + * @param numTries the maximum number of tries until the request is marked as failed + */ + RequestStatus(long maxAgeMSec, int numTries, String label) { + this.maxAgeMSec = maxAgeMSec; + this.numTries = numTries; + this.label = label; + this.reset(); + } + + /** Resets the runtime data to the initial states. */ + public synchronized void reset() { + this.isActive = false; + this.currRequestTimeStamp = 0; + this.nextRequestTimeStamp = 0; + this.numRetriesLeft = 0; + } + + /** + * Checks whether the request logic is active. + * + * @return true if active + */ + public boolean isActive() { + return this.isActive; + } + + /** + * Checks whether a request is waiting for a response. + * + * @return true if waiting for a response + */ + boolean isPending() { + return this.currRequestTimeStamp != 0; + } + + /** + * Checks whether the request is active and ran into timeout while waiting for a response. + * + * @param timeoutMSec the timeout in milliseconds + * @param currTime the current time stamp + * @return true if request timed out + */ + synchronized boolean isTimeout(long timeoutMSec, long currTime) { + return this.isPending() && currTime - this.currRequestTimeStamp >= timeoutMSec * 1000000L; + } + + /** + * Checks for failed requests (active and out of retries). + * + * @param timeoutMSec the timeout in milliseconds + * @param currTime the current time stamp + * @return true if no response was received and no retries are left + */ + synchronized boolean isFailed(long timeoutMSec, long currTime) { + return this.isTimeout(timeoutMSec, currTime) && this.numRetriesLeft == 0; + } + + /** + * Schedules the next request. + * + * @param delayMSec the delay in milliseconds + * @param currTime the current time stamp + */ + public synchronized void nextRequestIn(long delayMSec, long currTime) { + this.isActive = true; + this.nextRequestTimeStamp = currTime + delayMSec * 1000000L; + } + + /** + * Schedules a request to retrieve the current value. + */ + public synchronized void refresh() { + nextRequestIn(0, System.nanoTime()); + this.numRetriesLeft = this.numTries; + } + + /** + * Checks whether sending a new request is required (should be called periodically). + * + * @param timeoutMSec the time to wait for a response before retrying the request + * @param currTime the current time stamp + * @return true to indicate a new request should be sent + * @throws LcnException when a status request timed out + */ + synchronized boolean shouldSendNextRequest(long timeoutMSec, long currTime) throws LcnException { + if (this.isActive) { + if (this.nextRequestTimeStamp != 0 && currTime >= this.nextRequestTimeStamp) { + return true; + } + // Retry of current request (after no response was received) + if (this.isTimeout(timeoutMSec, currTime)) { + if (this.numRetriesLeft > 0) { + return true; + } else if (isPending()) { + currRequestTimeStamp = 0; + throw new LcnException(label + ": Failed finally after " + numTries + " tries"); + } + } + } + return false; + } + + /** + * Must be called right after a new request has been sent. + * Must be activated first. + * + * @param currTime the current time stamp + */ + public synchronized void onRequestSent(long currTime) { + if (!this.isActive) { + logger.warn("Tried to send a request which is not active"); + } + // Updates retry counter + if (this.currRequestTimeStamp == 0) { + this.numRetriesLeft = this.numTries - 1; + } else if (this.numRetriesLeft > 0) { // Should not happen if used correctly + --this.numRetriesLeft; + } + // Mark request as pending + this.currRequestTimeStamp = currTime; + // Schedule next request + if (this.maxAgeMSec != -1) { + this.nextRequestIn(this.maxAgeMSec, currTime); + } else { + this.nextRequestTimeStamp = 0; + } + } + + /** Must be called when a response (requested or not) has been received. */ + public synchronized void onResponseReceived() { + if (this.isActive) { + this.currRequestTimeStamp = 0; // Mark request (if any) as successful + } + } + + /** + * Sets the timeout of this RequestStatus. + * + * @param maxAgeMSec the timeout in ms + */ + public void setMaxAgeMSec(long maxAgeMSec) { + this.maxAgeMSec = maxAgeMSec; + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/SendData.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/SendData.java new file mode 100644 index 0000000000000..a271f6a7902c8 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/SendData.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.connection; + +import java.io.IOException; +import java.io.OutputStream; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Base class for a packet to be send to LCN-PCHK. + * + * @author Tobias Jüttner - Initial Contribution + * @author Fabian Wolter - Migration to OH2 + */ +@NonNullByDefault +public abstract class SendData { + /** + * Writes the packet's data into the given buffer. + * Called right before the packet is actually sent to LCN-PCHK. + * + * @param buffer the target buffer + * @param localSegId the local segment id + * @return true if everything was set-up correctly and data was written + * @throws IOException if an I/O error occurs + */ + abstract boolean write(OutputStream buffer, int localSegId) throws IOException; +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/SendDataPck.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/SendDataPck.java new file mode 100644 index 0000000000000..f04b9688d8489 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/SendDataPck.java @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.connection; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.BufferOverflowException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lcn.internal.common.LcnAddr; +import org.openhab.binding.lcn.internal.common.LcnDefs; +import org.openhab.binding.lcn.internal.common.PckGenerator; + +/** + * A PCK command to be send to LCN-PCHK. + * It is already encoded as bytes to allow different text-encodings (ANSI, UTF-8). + * + * @author Tobias Jüttner - Initial Contribution + * @author Fabian Wolter - Migration to OH2 + */ +@NonNullByDefault +class SendDataPck extends SendData { + /** The target LCN address. */ + private final LcnAddr addr; + + /** true to acknowledge the command on receipt. */ + private final boolean wantsAck; + + /** PCK command (without address header) encoded as bytes. */ + private final byte[] data; + + /** + * Constructor. + * + * @param addr the target LCN address + * @param wantsAck true to claim receipt + * @param data the PCK command encoded as bytes + */ + SendDataPck(LcnAddr addr, boolean wantsAck, byte[] data) { + this.addr = addr; + this.wantsAck = wantsAck; + this.data = data; + } + + /** + * Gets the PCK command. + * + * @return the PCK command encoded as bytes + */ + byte[] getData() { + return this.data; + } + + @Override + boolean write(OutputStream buffer, int localSegId) throws BufferOverflowException, IOException { + buffer.write(PckGenerator.generateAddressHeader(this.addr, localSegId == -1 ? 0 : localSegId, this.wantsAck) + .getBytes(LcnDefs.LCN_ENCODING)); + buffer.write(this.data); + buffer.write(PckGenerator.TERMINATION.getBytes(LcnDefs.LCN_ENCODING)); + return true; + } + + @Override + public String toString() { + return "Addr: " + addr + ": " + new String(data, 0, data.length, LcnDefs.LCN_ENCODING); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/SendDataPlainText.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/SendDataPlainText.java new file mode 100644 index 0000000000000..65e31e8f2d903 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/connection/SendDataPlainText.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.connection; + +import java.io.IOException; +import java.io.OutputStream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lcn.internal.common.LcnDefs; +import org.openhab.binding.lcn.internal.common.PckGenerator; + +/** + * A plain text to be send to LCN-PCHK. + * + * @author Tobias Jüttner - Initial Contribution + * @author Fabian Wolter - Migration to OH2 + */ +@NonNullByDefault +class SendDataPlainText extends SendData { + /** The text. */ + private final String text; + + /** + * Constructor. + * + * @param text the text + */ + SendDataPlainText(String text) { + this.text = text; + } + + /** + * Gets the text. + * + * @return the text + */ + String getText() { + return this.text; + } + + @Override + boolean write(OutputStream buffer, int localSegId) throws IOException { + buffer.write((this.text + PckGenerator.TERMINATION).getBytes(LcnDefs.LCN_ENCODING)); + return true; + } + + @Override + public String toString() { + return text; + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/Converter.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/Converter.java new file mode 100644 index 0000000000000..456bbe847da48 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/Converter.java @@ -0,0 +1,118 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.converter; + +import java.util.function.Function; + +import javax.measure.Unit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.library.types.DecimalType; +import org.eclipse.smarthome.core.library.types.QuantityType; +import org.eclipse.smarthome.core.types.State; +import org.openhab.binding.lcn.internal.common.LcnException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Base class for all LCN variable value converters. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class Converter { + private final Logger logger = LoggerFactory.getLogger(Converter.class); + private @Nullable final Unit unit; + private final Function toHuman; + private final Function toNative; + + public Converter(@Nullable Unit unit, Function toHuman, Function toNative) { + this.unit = unit; + this.toHuman = toHuman; + this.toNative = toNative; + } + + /** + * Converts the given human readable value into the native LCN value. + * + * @param humanReadableValue the value to convert + * @return the native value + */ + protected long toNative(double humanReadableValue) { + return toNative.apply(humanReadableValue); + } + + /** + * Converts the given native LCN value into a human readable value. + * + * @param nativeValue the value to convert + * @return the human readable value + */ + protected double toHumanReadable(long nativeValue) { + return toHuman.apply(nativeValue); + } + + /** + * Converts a human readable value into LCN native value. + * + * @param humanReadable value to convert + * @return the native LCN value + */ + public DecimalType onCommandFromItem(double humanReadable) { + return new DecimalType(toNative(humanReadable)); + } + + /** + * Converts a human readable value into LCN native value. + * + * @param humanReadable value to convert + * @return the native LCN value + * @throws LcnException when the value could not be converted to the base unit + */ + public DecimalType onCommandFromItem(QuantityType quantityType) throws LcnException { + Unit localUnit = unit; + if (localUnit == null) { + return onCommandFromItem(quantityType.doubleValue()); + } + + QuantityType quantityInBaseUnit = quantityType.toUnit(localUnit); + + if (quantityInBaseUnit != null) { + return onCommandFromItem(quantityInBaseUnit.doubleValue()); + } else { + throw new LcnException(quantityType + ": Incompatible Channel unit configured: " + localUnit); + } + } + + /** + * Converts a state update from the Thing into a human readable unit. + * + * @param state from the Thing + * @return human readable State + */ + public State onStateUpdateFromHandler(State state) { + State result = state; + + if (state instanceof DecimalType) { + Unit localUnit = unit; + if (localUnit != null) { + result = QuantityType.valueOf(toHumanReadable(((DecimalType) state).longValue()), localUnit); + } + } else { + logger.warn("Unexpected state type: {}", state.getClass().getSimpleName()); + } + + return result; + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/Converters.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/Converters.java new file mode 100644 index 0000000000000..3a8c5fff7aa04 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/Converters.java @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.converter; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.unit.SIUnits; +import org.eclipse.smarthome.core.library.unit.SmartHomeUnits; + +/** + * Holds all Converter objects. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class Converters { + public static final Converter TEMPERATURE; + public static final Converter LIGHT; + public static final Converter CO2; + public static final Converter CURRENT; + public static final Converter VOLTAGE; + public static final Converter ANGLE; + public static final Converter WINDSPEED; + public static final Converter IDENTITY; + + static { + TEMPERATURE = new Converter(SIUnits.CELSIUS, n -> (n - 1000) / 10d, h -> Math.round(h * 10) + 1000); + LIGHT = new Converter(SmartHomeUnits.LUX, Converters::lightToHumanReadable, Converters::lightToNative); + CO2 = new Converter(SmartHomeUnits.PARTS_PER_MILLION, n -> (double) n, Math::round); + CURRENT = new Converter(SmartHomeUnits.AMPERE, n -> n / 100d, h -> Math.round(h * 100)); + VOLTAGE = new Converter(SmartHomeUnits.VOLT, n -> n / 400d, h -> Math.round(h * 400)); + ANGLE = new Converter(SmartHomeUnits.DEGREE_ANGLE, n -> (n - 1000) / 10d, Converters::angleToNative); + WINDSPEED = new Converter(SmartHomeUnits.METRE_PER_SECOND, n -> n / 10d, h -> Math.round(h * 10)); + IDENTITY = new Converter(null, n -> (double) n, Math::round); + } + + private static long lightToNative(double value) { + return Math.round(Math.log(value) * 100); + } + + private static double lightToHumanReadable(long value) { + // Max. value hardware can deliver is 100klx. Apply hard limit, because higher native values lead to very big + // lux values. + if (value > lightToNative(100e3)) { + return Double.NaN; + } + return Math.exp(value / 100d); + } + + private static long angleToNative(double h) { + return (Math.round(h * 10) + 1000); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/S0Converter.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/S0Converter.java new file mode 100644 index 0000000000000..b2b4989a668c8 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/converter/S0Converter.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.converter; + +import java.math.BigDecimal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.library.unit.SmartHomeUnits; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Base class for S0 counter value converters. + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +public class S0Converter extends Converter { + private final Logger logger = LoggerFactory.getLogger(S0Converter.class); + protected double pulsesPerKwh; + + public S0Converter(@Nullable Object parameter) { + super(SmartHomeUnits.WATT, n -> 0d, h -> 0L); + + if (parameter == null) { + pulsesPerKwh = 1000; + logger.debug("Pulses per kWh not set. Assuming 1000 imp./kWh."); + } else if (parameter instanceof BigDecimal) { + pulsesPerKwh = ((BigDecimal) parameter).doubleValue(); + } else { + logger.warn("Could not parse 'pulses', unexpected type, should be float or integer: {}", parameter); + } + } + + @Override + public long toNative(double value) { + return Math.round(value * pulsesPerKwh / 1000); + } + + @Override + public double toHumanReadable(long value) { + return value / pulsesPerKwh * 1000; + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/pchkdiscovery/ExtService.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/pchkdiscovery/ExtService.java new file mode 100644 index 0000000000000..1cf6f92bd24f0 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/pchkdiscovery/ExtService.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.pchkdiscovery; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.thoughtworks.xstream.annotations.XStreamConverter; +import com.thoughtworks.xstream.converters.extended.ToAttributedValueConverter; + +/** + * Used for deserializing the XML response of the LCN-PCHK discovery protocol. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +@XStreamConverter(value = ToAttributedValueConverter.class, strings = { "content" }) +public class ExtService { + private final int localPort; + @SuppressWarnings("unused") + private final String content = ""; + + public ExtService(int localPort) { + this.localPort = localPort; + } + + public int getLocalPort() { + return localPort; + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/pchkdiscovery/ExtServices.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/pchkdiscovery/ExtServices.java new file mode 100644 index 0000000000000..da2ec561faa8c --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/pchkdiscovery/ExtServices.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.pchkdiscovery; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Used for deserializing the XML response of the LCN-PCHK discovery protocol. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class ExtServices { + private final ExtService ExtService; + + public ExtServices(ExtService extService) { + ExtService = extService; + } + + public ExtService getExtService() { + return ExtService; + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/pchkdiscovery/LcnPchkDiscoveryService.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/pchkdiscovery/LcnPchkDiscoveryService.java new file mode 100644 index 0000000000000..be8bb3fbc0925 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/pchkdiscovery/LcnPchkDiscoveryService.java @@ -0,0 +1,161 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.pchkdiscovery; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.InetAddress; +import java.net.MulticastSocket; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.config.discovery.AbstractDiscoveryService; +import org.eclipse.smarthome.config.discovery.DiscoveryResultBuilder; +import org.eclipse.smarthome.config.discovery.DiscoveryService; +import org.eclipse.smarthome.core.thing.ThingTypeUID; +import org.eclipse.smarthome.core.thing.ThingUID; +import org.openhab.binding.lcn.internal.LcnBindingConstants; +import org.openhab.binding.lcn.internal.common.LcnDefs; +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.StaxDriver; + +/** + * Discovers LCN-PCK gateways, such as LCN-PCHK. + * + * Scan approach: + * 1. Determines all local network interfaces + * 2. Send a multicast message on each interface to the PCHK multicast address 234.5.6.7 (not configurable by user). + * 3. Evaluate multicast responses of PCK gateways in the network + * + * @author Fabian Wolter - Initial Contribution + */ +@NonNullByDefault +@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.lcn") +public class LcnPchkDiscoveryService extends AbstractDiscoveryService { + private final Logger logger = LoggerFactory.getLogger(LcnPchkDiscoveryService.class); + private static final String HOSTNAME = "hostname"; + private static final String PORT = "port"; + private static final String MAC_ADDRESS = "macAddress"; + private static final String PCHK_DISCOVERY_MULTICAST_ADDRESS = "234.5.6.7"; + private static final int PCHK_DISCOVERY_PORT = 4220; + private static final int INTERFACE_TIMEOUT_SEC = 2; + private static final Set SUPPORTED_THING_TYPES_UIDS = Collections + .unmodifiableSet(Stream.of(LcnBindingConstants.THING_TYPE_PCK_GATEWAY).collect(Collectors.toSet())); + private static final String DISCOVER_REQUEST = "openHAB"; + + public LcnPchkDiscoveryService() throws IllegalArgumentException { + super(SUPPORTED_THING_TYPES_UIDS, 0, false); + } + + private List getLocalAddresses() { + List result = new LinkedList<>(); + try { + for (NetworkInterface networkInterface : Collections.list(NetworkInterface.getNetworkInterfaces())) { + try { + if (networkInterface.isUp() && !networkInterface.isLoopback() + && !networkInterface.isPointToPoint()) { + result.addAll(Collections.list(networkInterface.getInetAddresses())); + } + } catch (SocketException exception) { + // ignore + } + } + } catch (SocketException exception) { + return Collections.emptyList(); + } + return result; + } + + @Override + protected void startScan() { + try { + InetAddress multicastAddress = InetAddress.getByName(PCHK_DISCOVERY_MULTICAST_ADDRESS); + + getLocalAddresses().forEach(localInterfaceAddress -> { + logger.debug("Searching on {} ...", localInterfaceAddress.getHostAddress()); + try (MulticastSocket socket = new MulticastSocket(PCHK_DISCOVERY_PORT)) { + socket.setInterface(localInterfaceAddress); + socket.setReuseAddress(true); + socket.setSoTimeout(INTERFACE_TIMEOUT_SEC * 1000); + socket.joinGroup(multicastAddress); + + byte[] requestData = DISCOVER_REQUEST.getBytes(LcnDefs.LCN_ENCODING); + DatagramPacket request = new DatagramPacket(requestData, requestData.length, multicastAddress, + PCHK_DISCOVERY_PORT); + socket.send(request); + + do { + byte[] rxbuf = new byte[8192]; + DatagramPacket packet = new DatagramPacket(rxbuf, rxbuf.length); + socket.receive(packet); + + InetAddress addr = packet.getAddress(); + String response = new String(packet.getData(), LcnDefs.LCN_ENCODING); + + if (response.contains("ServicesRequest")) { + continue; + } + + ServicesResponse deserialized = xmlToServiceResponse(response); + + String macAddress = deserialized.getServer().getMachineId().replace(":", ""); + ThingUID thingUid = new ThingUID(LcnBindingConstants.THING_TYPE_PCK_GATEWAY, macAddress); + + Map properties = new HashMap<>(3); + properties.put(HOSTNAME, addr.getHostAddress()); + properties.put(PORT, deserialized.getExtServices().getExtService().getLocalPort()); + properties.put(MAC_ADDRESS, macAddress); + + DiscoveryResultBuilder discoveryResult = DiscoveryResultBuilder.create(thingUid) + .withProperties(properties).withRepresentationProperty(MAC_ADDRESS) + .withLabel(deserialized.getServer().getContent() + " (" + + deserialized.getServer().getMachineName() + ")"); + + thingDiscovered(discoveryResult.build()); + } while (true); // left by SocketTimeoutException + } catch (IOException e) { + logger.debug("Discovery failed for {}: {}", localInterfaceAddress, e.getMessage()); + } + }); + } catch (UnknownHostException e) { + logger.warn("Discovery failed: {}", e.getMessage()); + } + } + + ServicesResponse xmlToServiceResponse(String response) { + XStream xstream = new XStream(new StaxDriver()); + xstream.setClassLoader(getClass().getClassLoader()); + xstream.autodetectAnnotations(true); + xstream.alias("ServicesResponse", ServicesResponse.class); + xstream.alias("Server", Server.class); + xstream.alias("Version", Server.class); + xstream.alias("ExtServices", ExtServices.class); + xstream.alias("ExtService", ExtService.class); + + return (ServicesResponse) xstream.fromXML(response); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/pchkdiscovery/Server.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/pchkdiscovery/Server.java new file mode 100644 index 0000000000000..ee39fa6ab7254 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/pchkdiscovery/Server.java @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.pchkdiscovery; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.thoughtworks.xstream.annotations.XStreamAsAttribute; +import com.thoughtworks.xstream.annotations.XStreamConverter; +import com.thoughtworks.xstream.converters.extended.ToAttributedValueConverter; + +/** + * Used for deserializing the XML response of the LCN-PCHK discovery protocol. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +@XStreamConverter(value = ToAttributedValueConverter.class, strings = { "content" }) +public class Server { + @XStreamAsAttribute + private final int requestId; + @XStreamAsAttribute + private final String machineId; + @XStreamAsAttribute + private final String machineName; + @XStreamAsAttribute + private final String osShort; + @XStreamAsAttribute + private final String osLong; + private final String content; + + public Server(int requestId, String machineId, String machineName, String osShort, String osLong, String content) { + this.requestId = requestId; + this.machineId = machineId; + this.machineName = machineName; + this.osShort = osShort; + this.osLong = osLong; + this.content = content; + } + + public int getRequestId() { + return requestId; + } + + public String getMachineId() { + return machineId; + } + + public String getOsShort() { + return osShort; + } + + public String getOsLong() { + return osLong; + } + + public String getContent() { + return content; + } + + public Object getMachineName() { + return machineName; + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/pchkdiscovery/ServicesResponse.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/pchkdiscovery/ServicesResponse.java new file mode 100644 index 0000000000000..e2a29e2434405 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/pchkdiscovery/ServicesResponse.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.pchkdiscovery; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Used for deserializing the XML response of the LCN-PCHK discovery protocol. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class ServicesResponse { + private final Version Version; + private final Server Server; + private final ExtServices ExtServices; + @SuppressWarnings("unused") + private final Object Services = new Object(); + + public ServicesResponse(Version version, Server server, ExtServices extServices) { + this.Version = version; + this.Server = server; + this.ExtServices = extServices; + } + + public Server getServer() { + return Server; + } + + public Version getVersion() { + return Version; + } + + public ExtServices getExtServices() { + return ExtServices; + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/pchkdiscovery/Version.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/pchkdiscovery/Version.java new file mode 100644 index 0000000000000..6c406662474ae --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/pchkdiscovery/Version.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.pchkdiscovery; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.thoughtworks.xstream.annotations.XStreamAsAttribute; + +/** + * Used for deserializing the XML response of the LCN-PCHK discovery protocol. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class Version { + @XStreamAsAttribute + private final int major; + @XStreamAsAttribute + private final int minor; + + public Version(int major, int minor) { + this.major = major; + this.minor = minor; + } + + public int getMajor() { + return major; + } + + public int getMinor() { + return minor; + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/AbstractLcnModuleSubHandler.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/AbstractLcnModuleSubHandler.java new file mode 100644 index 0000000000000..3988283d50305 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/AbstractLcnModuleSubHandler.java @@ -0,0 +1,178 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Matcher; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.DecimalType; +import org.eclipse.smarthome.core.library.types.HSBType; +import org.eclipse.smarthome.core.library.types.OnOffType; +import org.eclipse.smarthome.core.library.types.PercentType; +import org.eclipse.smarthome.core.library.types.StopMoveType; +import org.eclipse.smarthome.core.library.types.StringType; +import org.eclipse.smarthome.core.library.types.UpDownType; +import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.core.types.State; +import org.openhab.binding.lcn.internal.LcnModuleHandler; +import org.openhab.binding.lcn.internal.common.DimmerOutputCommand; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnDefs; +import org.openhab.binding.lcn.internal.common.LcnDefs.RelayStateModifier; +import org.openhab.binding.lcn.internal.common.LcnException; +import org.openhab.binding.lcn.internal.common.Variable; +import org.openhab.binding.lcn.internal.common.VariableValue; +import org.openhab.binding.lcn.internal.connection.ModInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Base class for LCN module Thing sub handlers. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public abstract class AbstractLcnModuleSubHandler implements ILcnModuleSubHandler { + private final Logger logger = LoggerFactory.getLogger(AbstractLcnModuleSubHandler.class); + protected final LcnModuleHandler handler; + protected final ModInfo info; + + public AbstractLcnModuleSubHandler(LcnModuleHandler handler, ModInfo info) { + this.handler = handler; + this.info = info; + } + + @Override + public void handleRefresh(String groupId) { + // can be overwritten by subclasses. + } + + @Override + public void handleCommandOnOff(OnOffType command, LcnChannelGroup channelGroup, int number) throws LcnException { + unsupportedCommand(command); + } + + @Override + public void handleCommandPercent(PercentType command, LcnChannelGroup channelGroup, int number) + throws LcnException { + unsupportedCommand(command); + } + + @Override + public void handleCommandPercent(PercentType command, LcnChannelGroup channelGroup, String idWithoutGroup) + throws LcnException { + unsupportedCommand(command); + } + + @Override + public void handleCommandDecimal(DecimalType command, LcnChannelGroup channelGroup, int number) + throws LcnException { + unsupportedCommand(command); + } + + @Override + public void handleCommandDimmerOutput(DimmerOutputCommand command, int number) throws LcnException { + unsupportedCommand(command); + } + + @Override + public void handleCommandString(StringType command, int number) throws LcnException { + unsupportedCommand(command); + } + + @Override + public void handleCommandUpDown(UpDownType command, LcnChannelGroup channelGroup, int number) throws LcnException { + unsupportedCommand(command); + } + + @Override + public void handleCommandStopMove(StopMoveType command, LcnChannelGroup channelGroup, int number) + throws LcnException { + unsupportedCommand(command); + } + + @Override + public void handleCommandHsb(HSBType command, String groupId) throws LcnException { + unsupportedCommand(command); + } + + private void unsupportedCommand(Command command) { + logger.warn("Unsupported command: {}: {}", getClass().getSimpleName(), command.getClass().getSimpleName()); + } + + /** + * Tries to parses the given PCK message. Fails silently to let another sub handler give the chance to process the + * message. + * + * @param pck the message to process + * @return true, if the message could be processed successfully + */ + public boolean tryParse(String pck) { + Optional firstSuccessfulMatcher = getPckStatusMessagePatterns().stream().map(p -> p.matcher(pck)) + .filter(Matcher::matches).filter(m -> handler.isMyAddress(m.group("segId"), m.group("modId"))) + .findAny(); + + firstSuccessfulMatcher.ifPresent(matcher -> { + try { + handleStatusMessage(matcher); + } catch (LcnException e) { + logger.warn("Parse error: {}", e.getMessage()); + } + }); + + return firstSuccessfulMatcher.isPresent(); + } + + /** + * Creates a RelayStateModifier array with all elements set to NOCHANGE. + * + * @return the created array + */ + protected RelayStateModifier[] createRelayStateModifierArray() { + RelayStateModifier[] ret = new LcnDefs.RelayStateModifier[LcnChannelGroup.RELAY.getCount()]; + Arrays.fill(ret, LcnDefs.RelayStateModifier.NOCHANGE); + return ret; + } + + /** + * Updates the state of the LCN module. + * + * @param type the channel type which shall be updated + * @param number the Channel's number within the channel type, zero-based + * @param state the new state + */ + protected void fireUpdate(LcnChannelGroup type, int number, State state) { + handler.updateChannel(type, (number + 1) + "", state); + } + + /** + * Fires the current state of a Variable to openHAB. Resets running value request logic. + * + * @param matcher the pre-matched matcher + * @param channelId the Channel's ID to update + * @param variable the Variable to update + * @return the new variable's value + */ + protected VariableValue fireUpdateAndReset(Matcher matcher, String channelId, Variable variable) { + VariableValue value = new VariableValue(Long.parseLong(matcher.group("value" + channelId))); + + info.updateVariableValue(variable, value); + info.onVariableResponseReceived(variable); + + fireUpdate(variable.getChannelType(), variable.getThresholdNumber().orElse(variable.getNumber()), + value.getState(variable)); + return value; + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/AbstractLcnModuleVariableSubHandler.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/AbstractLcnModuleVariableSubHandler.java new file mode 100644 index 0000000000000..5589d1d258197 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/AbstractLcnModuleVariableSubHandler.java @@ -0,0 +1,140 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.DecimalType; +import org.openhab.binding.lcn.internal.LcnModuleHandler; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnException; +import org.openhab.binding.lcn.internal.common.Variable; +import org.openhab.binding.lcn.internal.connection.ModInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Base class for LCN module Thing sub handlers processing variables. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public abstract class AbstractLcnModuleVariableSubHandler extends AbstractLcnModuleSubHandler { + private final Logger logger = LoggerFactory.getLogger(AbstractLcnModuleVariableSubHandler.class); + + public AbstractLcnModuleVariableSubHandler(LcnModuleHandler handler, ModInfo info) { + super(handler, info); + } + + @Override + public void handleRefresh(LcnChannelGroup channelGroup, int number) { + requestVariable(info, channelGroup, number); + info.requestFirmwareVersion(); + } + + /** + * Requests the current state of the given Channel. + * + * @param info the modules ModInfo cache + * @param channelGroup the Channel group + * @param number the Channel's number within the Channel group + */ + protected void requestVariable(ModInfo info, LcnChannelGroup channelGroup, int number) { + try { + Variable var = getVariable(channelGroup, number); + info.refreshVariable(var); + } catch (IllegalArgumentException e) { + logger.warn("Could not parse variable name: {}{}", channelGroup, (number + 1)); + } + } + + /** + * Gets a Variable from the given parameters. + * + * @param channelGroup the Channel group the Variable is in + * @param number the number of the Variable's Channel + * @return the Variable + * @throws IllegalArgumentException when the Channel group and number do not exist + */ + protected Variable getVariable(LcnChannelGroup channelGroup, int number) throws IllegalArgumentException { + return Variable.valueOf(channelGroup.name() + (number + 1)); + } + + /** + * Calculates the relative change between the current and the demanded value of a Variable. + * + * @param command the requested value + * @param variable the Variable type + * @return the difference + * @throws LcnException when the difference is too big + */ + protected int getRelativeChange(DecimalType command, Variable variable) throws LcnException { + // LCN doesn't support setting thresholds or variables with absolute values. So, calculate the relative change. + int relativeVariableChange = (int) (command.longValue() - info.getVariableValue(variable)); + + int result; + if (relativeVariableChange > 0) { + result = Math.min(relativeVariableChange, getMaxAbsChange(variable)); + } else { + result = Math.max(relativeVariableChange, -getMaxAbsChange(variable)); + } + if (result != relativeVariableChange) { + logger.warn("Relative change of {} too big, limiting: {}", variable, relativeVariableChange); + } + return result; + } + + private int getMaxAbsChange(Variable variable) { + switch (variable) { + case RVARSETPOINT1: + case RVARSETPOINT2: + case THRESHOLDREGISTER11: + case THRESHOLDREGISTER12: + case THRESHOLDREGISTER13: + case THRESHOLDREGISTER14: + case THRESHOLDREGISTER15: + case THRESHOLDREGISTER21: + case THRESHOLDREGISTER22: + case THRESHOLDREGISTER23: + case THRESHOLDREGISTER24: + case THRESHOLDREGISTER31: + case THRESHOLDREGISTER32: + case THRESHOLDREGISTER33: + case THRESHOLDREGISTER34: + case THRESHOLDREGISTER41: + case THRESHOLDREGISTER42: + case THRESHOLDREGISTER43: + case THRESHOLDREGISTER44: + return 1000; + case VARIABLE1: + case VARIABLE2: + case VARIABLE3: + case VARIABLE4: + case VARIABLE5: + case VARIABLE6: + case VARIABLE7: + case VARIABLE8: + case VARIABLE9: + case VARIABLE10: + case VARIABLE11: + case VARIABLE12: + return 4000; + case UNKNOWN: + case S0INPUT1: + case S0INPUT2: + case S0INPUT3: + case S0INPUT4: + default: + return 0; + } + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/ILcnModuleSubHandler.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/ILcnModuleSubHandler.java new file mode 100644 index 0000000000000..aff33cc1f0ebd --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/ILcnModuleSubHandler.java @@ -0,0 +1,155 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import java.util.Collection; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.DecimalType; +import org.eclipse.smarthome.core.library.types.HSBType; +import org.eclipse.smarthome.core.library.types.OnOffType; +import org.eclipse.smarthome.core.library.types.PercentType; +import org.eclipse.smarthome.core.library.types.StopMoveType; +import org.eclipse.smarthome.core.library.types.StringType; +import org.eclipse.smarthome.core.library.types.UpDownType; +import org.openhab.binding.lcn.internal.common.DimmerOutputCommand; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnException; + +/** + * Interface for LCN module Thing sub handlers processing variables. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public interface ILcnModuleSubHandler { + /** + * Gets the Patterns, the sub handler is capable to process. + * + * @return the Patterns + */ + Collection getPckStatusMessagePatterns(); + + /** + * Processes the payload of a pre-matched PCK message. + * + * @param matcher the pre-matched matcher. + * @throws LcnException when the message cannot be processed + */ + void handleStatusMessage(Matcher matcher) throws LcnException; + + /** + * Processes a refresh request from openHAB. + * + * @param channelGroup the Channel group that shall be refreshed + * @param number the Channel number within the Channel group + */ + void handleRefresh(LcnChannelGroup channelGroup, int number); + + /** + * Processes a refresh request from openHAB. + * + * @param groupId the Channel ID that shall be refreshed + */ + void handleRefresh(String groupId); + + /** + * Handles a Command from openHAB. + * + * @param command the command to handle + * @param channelGroup the addressed Channel group + * @param number the Channel's number within the Channel group + * @throws LcnException when the command could not processed + */ + void handleCommandOnOff(OnOffType command, LcnChannelGroup channelGroup, int number) throws LcnException; + + /** + * Handles a Command from openHAB. + * + * @param command the command to handle + * @param channelGroup the addressed Channel group + * @param number the Channel's number within the Channel group + * @throws LcnException when the command could not processed + */ + void handleCommandPercent(PercentType command, LcnChannelGroup channelGroup, int number) throws LcnException; + + /** + * Handles a Command from openHAB. + * + * @param command the command to handle + * @param channelGroup the addressed Channel group + * @param idWithoutGroup the Channel's name within the Channel group + * @throws LcnException when the command could not processed + */ + void handleCommandPercent(PercentType command, LcnChannelGroup channelGroup, String idWithoutGroup) + throws LcnException; + + /** + * Handles a Command from openHAB. + * + * @param command the command to handle + * @param channelGroup the addressed Channel group + * @param number the Channel's number within the Channel group + * @throws LcnException when the command could not processed + */ + void handleCommandDecimal(DecimalType command, LcnChannelGroup channelGroup, int number) throws LcnException; + + /** + * Handles a Command from openHAB. + * + * @param command the command to handle + * @param number the Channel's number within the Channel group + * @throws LcnException when the command could not processed + */ + void handleCommandDimmerOutput(DimmerOutputCommand command, int number) throws LcnException; + + /** + * Handles a Command from openHAB. + * + * @param command the command to handle + * @param number the Channel's number within the Channel group + * @throws LcnException when the command could not processed + */ + void handleCommandString(StringType command, int number) throws LcnException; + + /** + * Handles a Command from openHAB. + * + * @param command the command to handle + * @param channelGroup the addressed Channel group + * @param number the Channel's number within the Channel group + * @throws LcnException when the command could not processed + */ + void handleCommandUpDown(UpDownType command, LcnChannelGroup channelGroup, int number) throws LcnException; + + /** + * Handles a Command from openHAB. + * + * @param command the command to handle + * @param channelGroup the addressed Channel group + * @param number the Channel's number within the Channel group + * @throws LcnException when the command could not processed + */ + void handleCommandStopMove(StopMoveType command, LcnChannelGroup channelGroup, int number) throws LcnException; + + /** + * Handles a Command from openHAB. + * + * @param command the command to handle + * @param groupId the Channel's name within the Channel group + * @throws LcnException when the command could not processed + */ + void handleCommandHsb(HSBType command, String groupId) throws LcnException; +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleBinarySensorSubHandler.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleBinarySensorSubHandler.java new file mode 100644 index 0000000000000..a911662d25cb0 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleBinarySensorSubHandler.java @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import java.util.Collection; +import java.util.Collections; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.IntStream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.OpenClosedType; +import org.openhab.binding.lcn.internal.LcnBindingConstants; +import org.openhab.binding.lcn.internal.LcnModuleHandler; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnDefs; +import org.openhab.binding.lcn.internal.connection.ModInfo; + +/** + * Handles State changes of binary sensors of an LCN module. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleBinarySensorSubHandler extends AbstractLcnModuleSubHandler { + private static final Pattern PATTERN = Pattern.compile(LcnBindingConstants.ADDRESS_REGEX + "Bx(?\\d+)"); + + public LcnModuleBinarySensorSubHandler(LcnModuleHandler handler, ModInfo info) { + super(handler, info); + } + + @Override + public void handleRefresh(LcnChannelGroup channelGroup, int number) { + info.refreshBinarySensors(); + } + + @Override + public void handleStatusMessage(Matcher matcher) { + info.onBinarySensorsResponseReceived(); + + boolean[] states = LcnDefs.getBooleanValue(Integer.parseInt(matcher.group("byteValue"))); + + IntStream.range(0, LcnChannelGroup.BINARYSENSOR.getCount()) + .forEach(i -> fireUpdate(LcnChannelGroup.BINARYSENSOR, i, + states[i] ? OpenClosedType.OPEN : OpenClosedType.CLOSED)); + } + + @Override + public Collection getPckStatusMessagePatterns() { + return Collections.singleton(PATTERN); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleCodeSubHandler.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleCodeSubHandler.java new file mode 100644 index 0000000000000..69a9102013a56 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleCodeSubHandler.java @@ -0,0 +1,108 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import java.util.Arrays; +import java.util.Collection; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lcn.internal.LcnBindingConstants; +import org.openhab.binding.lcn.internal.LcnModuleHandler; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnDefs; +import org.openhab.binding.lcn.internal.connection.ModInfo; + +/** + * Handles State changes of transponders and remote controls of an LCN module. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleCodeSubHandler extends AbstractLcnModuleSubHandler { + private static final Pattern TRANSPONDER_PATTERN = Pattern + .compile(LcnBindingConstants.ADDRESS_REGEX + "\\.ZT(?\\d{3})(?\\d{3})(?\\d{3})"); + private static final Pattern REMOTE_CONTROL_PATTERN = Pattern.compile(LcnBindingConstants.ADDRESS_REGEX + + "\\.ZI(?\\d{3})(?\\d{3})(?\\d{3})(?\\d{3})(?\\d{3})"); + + public LcnModuleCodeSubHandler(LcnModuleHandler handler, ModInfo info) { + super(handler, info); + } + + @Override + public void handleRefresh(LcnChannelGroup channelGroup, int number) { + // nothing + } + + @Override + public void handleStatusMessage(Matcher matcher) { + String code = String.format("%02X%02X%02X", Integer.parseInt(matcher.group("byte0")), + Integer.parseInt(matcher.group("byte1")), Integer.parseInt(matcher.group("byte2"))); + + if (matcher.pattern() == TRANSPONDER_PATTERN) { + handler.triggerChannel(LcnChannelGroup.CODE, "transponder", code); + } else if (matcher.pattern() == REMOTE_CONTROL_PATTERN) { + int keyNumber = Integer.parseInt(matcher.group("key")); + String keyLayer; + + if (keyNumber > 30) { + keyLayer = "D"; + keyNumber -= 30; + } else if (keyNumber > 20) { + keyLayer = "C"; + keyNumber -= 20; + } else if (keyNumber > 10) { + keyLayer = "B"; + keyNumber -= 10; + } else if (keyNumber > 0) { + keyLayer = "A"; + } else { + return; + } + + int action = Integer.parseInt(matcher.group("action")); + + if (action > 10) { + handler.triggerChannel(LcnChannelGroup.CODE, "remotecontrolbatterylow", code); + action -= 10; + } + + LcnDefs.SendKeyCommand actionType; + switch (action) { + case 1: + actionType = LcnDefs.SendKeyCommand.HIT; + break; + case 2: + actionType = LcnDefs.SendKeyCommand.MAKE; + break; + case 3: + actionType = LcnDefs.SendKeyCommand.BREAK; + break; + default: + return; + } + + handler.triggerChannel(LcnChannelGroup.CODE, "remotecontrolkey", + keyLayer + keyNumber + ":" + actionType.name()); + + handler.triggerChannel(LcnChannelGroup.CODE, "remotecontrolcode", + code + ":" + keyLayer + keyNumber + ":" + actionType.name()); + } + } + + @Override + public Collection getPckStatusMessagePatterns() { + return Arrays.asList(TRANSPONDER_PATTERN, REMOTE_CONTROL_PATTERN); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleKeyLockTableSubHandler.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleKeyLockTableSubHandler.java new file mode 100644 index 0000000000000..b05116476e3cd --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleKeyLockTableSubHandler.java @@ -0,0 +1,95 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.IntStream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.OnOffType; +import org.openhab.binding.lcn.internal.LcnBindingConstants; +import org.openhab.binding.lcn.internal.LcnModuleHandler; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnDefs; +import org.openhab.binding.lcn.internal.common.LcnDefs.KeyLockStateModifier; +import org.openhab.binding.lcn.internal.common.LcnException; +import org.openhab.binding.lcn.internal.common.PckGenerator; +import org.openhab.binding.lcn.internal.connection.ModInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Handles Commands and State changes of key table locks of an LCN module. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleKeyLockTableSubHandler extends AbstractLcnModuleSubHandler { + private final Logger logger = LoggerFactory.getLogger(LcnModuleKeyLockTableSubHandler.class); + private static final Pattern PATTERN = Pattern.compile(LcnBindingConstants.ADDRESS_REGEX + + "\\.TX(?\\d{3})(?\\d{3})(?\\d{3})((?\\d{3}))?"); + + public LcnModuleKeyLockTableSubHandler(LcnModuleHandler handler, ModInfo info) { + super(handler, info); + } + + @Override + public void handleRefresh(LcnChannelGroup channelGroup, int number) { + info.refreshStatusLockedKeys(); + } + + @Override + public void handleRefresh(String groupId) { + // nothing + } + + @Override + public void handleCommandOnOff(OnOffType command, LcnChannelGroup channelGroup, int number) throws LcnException { + KeyLockStateModifier[] keyLockStateModifiers = new LcnDefs.KeyLockStateModifier[channelGroup.getCount()]; + Arrays.fill(keyLockStateModifiers, LcnDefs.KeyLockStateModifier.NOCHANGE); + keyLockStateModifiers[number] = command == OnOffType.ON ? LcnDefs.KeyLockStateModifier.ON + : LcnDefs.KeyLockStateModifier.OFF; + int tableId = channelGroup.ordinal() - LcnChannelGroup.KEYLOCKTABLEA.ordinal(); + handler.sendPck(PckGenerator.lockKeys(tableId, keyLockStateModifiers)); + info.refreshStatusStatusLockedKeysAfterChange(); + } + + @Override + public void handleStatusMessage(Matcher matcher) { + info.onLockedKeysResponseReceived(); + + IntStream.range(0, LcnDefs.KEY_TABLE_COUNT).forEach(tableId -> { + String stateString = matcher.group(String.format("table%d", tableId)); + if (stateString != null) { + boolean[] states = LcnDefs.getBooleanValue(Integer.parseInt(stateString)); + try { + LcnChannelGroup channelGroup = LcnChannelGroup.fromTableId(tableId); + for (int i = 0; i < states.length; i++) { + fireUpdate(channelGroup, i, states[i] ? OnOffType.ON : OnOffType.OFF); + } + } catch (LcnException e) { + logger.warn("Failed to set key table lock state: {}", e.getMessage()); + } + } + }); + } + + @Override + public Collection getPckStatusMessagePatterns() { + return Collections.singleton(PATTERN); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleLedSubHandler.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleLedSubHandler.java new file mode 100644 index 0000000000000..6fd5d95cefa9f --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleLedSubHandler.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import java.util.Collection; +import java.util.Collections; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.OnOffType; +import org.eclipse.smarthome.core.library.types.StringType; +import org.openhab.binding.lcn.internal.LcnModuleHandler; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnDefs; +import org.openhab.binding.lcn.internal.common.LcnException; +import org.openhab.binding.lcn.internal.common.PckGenerator; +import org.openhab.binding.lcn.internal.connection.ModInfo; + +/** + * Handles Commands and State changes of LEDs of an LCN module. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleLedSubHandler extends AbstractLcnModuleSubHandler { + public LcnModuleLedSubHandler(LcnModuleHandler handler, ModInfo info) { + super(handler, info); + } + + @Override + public void handleRefresh(LcnChannelGroup channelGroup, int number) { + info.refreshLedsAndLogic(); + } + + @Override + public void handleCommandOnOff(OnOffType command, LcnChannelGroup channelGroup, int number) throws LcnException { + handleCommandString(new StringType(command.toString()), number); + } + + @Override + public void handleCommandString(StringType command, int number) throws LcnException { + handler.sendPck(PckGenerator.controlLed(number, LcnDefs.LedStatus.valueOf(command.toString()))); + info.refreshStatusLedsAnLogicAfterChange(); + } + + @Override + public void handleStatusMessage(Matcher matcher) { + /** Status messages are handled in {@link LcnModuleLogicSubHandler}. */ + } + + @Override + public Collection getPckStatusMessagePatterns() { + return Collections.emptyList(); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleLogicSubHandler.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleLogicSubHandler.java new file mode 100644 index 0000000000000..21b5403ae16b1 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleLogicSubHandler.java @@ -0,0 +1,126 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import java.util.Arrays; +import java.util.Collection; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.IntStream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.StringType; +import org.openhab.binding.lcn.internal.LcnBindingConstants; +import org.openhab.binding.lcn.internal.LcnModuleHandler; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnDefs; +import org.openhab.binding.lcn.internal.common.LcnDefs.LogicOpStatus; +import org.openhab.binding.lcn.internal.connection.ModInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Handles State changes of logic operations of an LCN module. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleLogicSubHandler extends AbstractLcnModuleSubHandler { + private final Logger logger = LoggerFactory.getLogger(LcnModuleLogicSubHandler.class); + private static final Pattern PATTERN_SINGLE_LOGIC = Pattern + .compile(LcnBindingConstants.ADDRESS_REGEX + "S(?\\d{1})(?\\d{3})"); + private static final Pattern PATTERN_ALL = Pattern + .compile(LcnBindingConstants.ADDRESS_REGEX + "\\.TL(?[AEBF]{12})(?[NTV]{4})"); + + public LcnModuleLogicSubHandler(LcnModuleHandler handler, ModInfo info) { + super(handler, info); + } + + @Override + public void handleRefresh(LcnChannelGroup channelGroup, int number) { + info.refreshLedsAndLogic(); + } + + @Override + public void handleStatusMessage(Matcher matcher) { + info.onLedsAndLogicResponseReceived(); + + if (matcher.pattern() == PATTERN_ALL) { + IntStream.range(0, LcnChannelGroup.LED.getCount()).forEach(i -> { + switch (matcher.group("ledStates").toUpperCase().charAt(i)) { + case 'A': + fireLed(i, LcnDefs.LedStatus.OFF); + break; + case 'E': + fireLed(i, LcnDefs.LedStatus.ON); + break; + case 'B': + fireLed(i, LcnDefs.LedStatus.BLINK); + break; + case 'F': + fireLed(i, LcnDefs.LedStatus.FLICKER); + break; + default: + logger.warn("Failed to parse LED state: {}", matcher.group("ledStates")); + } + }); + IntStream.range(0, LcnChannelGroup.LOGIC.getCount()).forEach(i -> { + switch (matcher.group("logicOpStates").toUpperCase().charAt(i)) { + case 'N': + fireLogic(i, LcnDefs.LogicOpStatus.NOT); + break; + case 'T': + fireLogic(i, LcnDefs.LogicOpStatus.OR); + break; + case 'V': + fireLogic(i, LcnDefs.LogicOpStatus.AND); + break; + default: + logger.warn("Failed to parse logic state: {}", matcher.group("logicOpStates")); + } + }); + } else if (matcher.pattern() == PATTERN_SINGLE_LOGIC) { + String rawState = matcher.group("logicOpState"); + + LogicOpStatus state; + switch (rawState) { + case "000": + state = LcnDefs.LogicOpStatus.NOT; + break; + case "025": + state = LcnDefs.LogicOpStatus.OR; + break; + case "050": + state = LcnDefs.LogicOpStatus.AND; + break; + default: + logger.warn("Failed to parse logic state: {}", rawState); + return; + } + fireLogic(Integer.parseInt(matcher.group("id")) - 1, state); + } + } + + private void fireLed(int number, LcnDefs.LedStatus status) { + fireUpdate(LcnChannelGroup.LED, number, new StringType(status.toString())); + } + + private void fireLogic(int number, LcnDefs.LogicOpStatus status) { + fireUpdate(LcnChannelGroup.LOGIC, number, new StringType(status.toString())); + } + + @Override + public Collection getPckStatusMessagePatterns() { + return Arrays.asList(PATTERN_ALL, PATTERN_SINGLE_LOGIC); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleMetaAckSubHandler.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleMetaAckSubHandler.java new file mode 100644 index 0000000000000..6ced1c8c35699 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleMetaAckSubHandler.java @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import java.util.Arrays; +import java.util.Collection; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lcn.internal.LcnBindingConstants; +import org.openhab.binding.lcn.internal.LcnModuleHandler; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.connection.ModInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Handle Acks received from an LCN module. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleMetaAckSubHandler extends AbstractLcnModuleSubHandler { + private final Logger logger = LoggerFactory.getLogger(LcnModuleMetaAckSubHandler.class); + /** The pattern for the Ack PCK message */ + public static final Pattern PATTERN_POS = Pattern.compile("-M(?\\d{3})(?\\d{3})!"); + private static final Pattern PATTERN_NEG = Pattern.compile("-M(?\\d{3})(?\\d{3})(?\\d+)"); + + public LcnModuleMetaAckSubHandler(LcnModuleHandler handler, ModInfo info) { + super(handler, info); + } + + @Override + public void handleRefresh(LcnChannelGroup channelGroup, int number) { + // nothing + } + + @Override + public void handleStatusMessage(Matcher matcher) { + if (matcher.pattern() == PATTERN_POS) { + handler.onAckRceived(); + } else if (matcher.pattern() == PATTERN_NEG) { + logger.warn("{}: NACK received: {}", handler.getStatusMessageAddress(), + codeToString(Integer.parseInt(matcher.group("code")))); + } + } + + private String codeToString(int code) { + switch (code) { + case LcnBindingConstants.CODE_ACK: + return "ACK"; + case 5: + return "Unknown command"; + case 6: + return "Invalid parameter count"; + case 7: + return "Invalid parameter"; + case 8: + return "Command not allowed (e.g. output locked)"; + case 9: + return "Command not allowed by module's configuration"; + case 10: + return "Module not capable"; + case 11: + return "Periphery missing"; + case 12: + return "Programming mode necessary"; + case 14: + return "Mains fuse blown"; + default: + return "Unknown"; + } + } + + @Override + public Collection getPckStatusMessagePatterns() { + return Arrays.asList(PATTERN_POS, PATTERN_NEG); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleMetaFirmwareSubHandler.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleMetaFirmwareSubHandler.java new file mode 100644 index 0000000000000..59621e8e1261f --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleMetaFirmwareSubHandler.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import java.util.Collection; +import java.util.Collections; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lcn.internal.LcnBindingConstants; +import org.openhab.binding.lcn.internal.LcnModuleHandler; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.connection.ModInfo; + +/** + * Handles serial number and firmware versions received from an LCN module. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleMetaFirmwareSubHandler extends AbstractLcnModuleSubHandler { + /** The pattern for the serial number and firmware PCK message */ + public static final Pattern PATTERN = Pattern.compile(LcnBindingConstants.ADDRESS_REGEX + + "\\.SN(?[0-9|A-F]{10})(?[0-9|A-F]{2})FW(?[0-9|A-F]{6})HW(?\\d+)"); + + public LcnModuleMetaFirmwareSubHandler(LcnModuleHandler handler, ModInfo info) { + super(handler, info); + } + + @Override + public void handleRefresh(LcnChannelGroup channelGroup, int number) { + // nothing + } + + @Override + public void handleStatusMessage(Matcher matcher) { + info.setFirmwareVersion(Integer.parseInt(matcher.group("firmwareVersion"), 16)); + } + + @Override + public Collection getPckStatusMessagePatterns() { + return Collections.singleton(PATTERN); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleOutputSubHandler.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleOutputSubHandler.java new file mode 100644 index 0000000000000..d7f5d1fcb179c --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleOutputSubHandler.java @@ -0,0 +1,182 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import java.util.Arrays; +import java.util.Collection; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.HSBType; +import org.eclipse.smarthome.core.library.types.OnOffType; +import org.eclipse.smarthome.core.library.types.PercentType; +import org.eclipse.smarthome.core.library.types.UpDownType; +import org.openhab.binding.lcn.internal.LcnBindingConstants; +import org.openhab.binding.lcn.internal.LcnModuleHandler; +import org.openhab.binding.lcn.internal.common.DimmerOutputCommand; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnDefs; +import org.openhab.binding.lcn.internal.common.LcnException; +import org.openhab.binding.lcn.internal.common.PckGenerator; +import org.openhab.binding.lcn.internal.connection.ModInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Handles Commands and State changes of dimmer outputs of an LCN module. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleOutputSubHandler extends AbstractLcnModuleSubHandler { + private final Logger logger = LoggerFactory.getLogger(LcnModuleOutputSubHandler.class); + private static final int COLOR_RAMP_MS = 1000; + private static final String OUTPUT_COLOR = "color"; + private static final Pattern PERCENT_PATTERN; + private static final Pattern NATIVE_PATTERN; + private volatile HSBType currentColor = new HSBType(); + private volatile PercentType output4 = new PercentType(); + + public LcnModuleOutputSubHandler(LcnModuleHandler handler, ModInfo info) { + super(handler, info); + } + + static { + PERCENT_PATTERN = Pattern.compile(LcnBindingConstants.ADDRESS_REGEX + "A(?\\d)(?\\d+)"); + NATIVE_PATTERN = Pattern.compile(LcnBindingConstants.ADDRESS_REGEX + "O(?\\d)(?\\d+)"); + } + + @Override + public Collection getPckStatusMessagePatterns() { + return Arrays.asList(NATIVE_PATTERN, PERCENT_PATTERN); + } + + @Override + public void handleRefresh(LcnChannelGroup channelGroup, int number) { + info.refreshOutput(number); + } + + @Override + public void handleRefresh(String groupId) { + if (OUTPUT_COLOR.equals(groupId)) { + info.refreshAllOutputs(); + } + } + + @Override + public void handleCommandOnOff(OnOffType command, LcnChannelGroup channelGroup, int number) throws LcnException { + // don't use OnOffType.as() here, because it returns @Nullable + handler.sendPck(PckGenerator.dimOutput(number, command == OnOffType.ON ? 100 : 0, 0)); + } + + @Override + public void handleCommandPercent(PercentType command, LcnChannelGroup channelGroup, int number) + throws LcnException { + handler.sendPck(PckGenerator.dimOutput(number, command.doubleValue(), 0)); + } + + @Override + public void handleCommandPercent(PercentType command, LcnChannelGroup channelGroup, String idWithoutGroup) + throws LcnException { + if (!OUTPUT_COLOR.equals(idWithoutGroup)) { + throw new LcnException("Unknown group ID: " + idWithoutGroup); + } + updateAndSendColor(new HSBType(currentColor.getHue(), currentColor.getSaturation(), command)); + } + + @Override + public void handleCommandHsb(HSBType command, String groupId) throws LcnException { + if (!OUTPUT_COLOR.equals(groupId)) { + throw new LcnException("Unknown group ID: " + groupId); + } + updateAndSendColor(command); + } + + private synchronized void updateAndSendColor(HSBType hsbType) throws LcnException { + currentColor = hsbType; + handler.updateChannel(LcnChannelGroup.OUTPUT, OUTPUT_COLOR, currentColor); + + if (info.getFirmwareVersion() >= LcnBindingConstants.FIRMWARE_2014) { + handler.sendPck(PckGenerator.dimAllOutputs(currentColor.getRed().doubleValue(), + currentColor.getGreen().doubleValue(), currentColor.getBlue().doubleValue(), output4.doubleValue(), + COLOR_RAMP_MS)); + } else { + handler.sendPck(PckGenerator.dimOutput(0, currentColor.getRed().doubleValue(), COLOR_RAMP_MS)); + handler.sendPck(PckGenerator.dimOutput(1, currentColor.getGreen().doubleValue(), COLOR_RAMP_MS)); + handler.sendPck(PckGenerator.dimOutput(2, currentColor.getBlue().doubleValue(), COLOR_RAMP_MS)); + } + } + + @Override + public void handleCommandDimmerOutput(DimmerOutputCommand command, int number) throws LcnException { + int rampMs = command.getRampMs(); + if (command.isControlAllOutputs()) { // control all dimmer outputs + if (rampMs == LcnDefs.FIXED_RAMP_MS) { + // compatibility command + handler.sendPck(PckGenerator.controlAllOutputs(command.intValue())); + } else { + // command since firmware 180501 + handler.sendPck(PckGenerator.dimAllOutputs(command.doubleValue(), command.doubleValue(), + command.doubleValue(), command.doubleValue(), rampMs)); + } + } else if (command.isControlOutputs12()) { // control dimmer outputs 1+2 + if (command.intValue() == 0 || command.intValue() == 100) { + handler.sendPck(PckGenerator.controlOutputs12(command.intValue() > 0, rampMs >= LcnDefs.FIXED_RAMP_MS)); + } else { + // ignore ramp when dimming + handler.sendPck(PckGenerator.dimOutputs12(command.doubleValue())); + } + } else { + handler.sendPck(PckGenerator.dimOutput(number, command.doubleValue(), rampMs)); + } + } + + @Override + public void handleStatusMessage(Matcher matcher) { + int outputId = Integer.parseInt(matcher.group("outputId")) - 1; + + if (!LcnChannelGroup.OUTPUT.isValidId(outputId)) { + logger.warn("outputId out of range: {}", outputId); + return; + } + double percent; + if (matcher.pattern() == PERCENT_PATTERN) { + percent = Integer.parseInt(matcher.group("percent")); + } else if (matcher.pattern() == NATIVE_PATTERN) { + percent = (double) Integer.parseInt(matcher.group("value")) / 2; + } else { + logger.warn("Unexpected pattern: {}", matcher.pattern()); + return; + } + + info.onOutputResponseReceived(outputId); + + percent = Math.min(100, Math.max(0, percent)); + + PercentType percentType = new PercentType((int) Math.round(percent)); + fireUpdate(LcnChannelGroup.OUTPUT, outputId, percentType); + + if (outputId == 3) { + output4 = percentType; + } + + if (percent > 0) { + if (outputId == 0) { + fireUpdate(LcnChannelGroup.ROLLERSHUTTEROUTPUT, 0, UpDownType.UP); + } else if (outputId == 1) { + fireUpdate(LcnChannelGroup.ROLLERSHUTTEROUTPUT, 0, UpDownType.DOWN); + } + } + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRelaySubHandler.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRelaySubHandler.java new file mode 100644 index 0000000000000..d2b166cb67adc --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRelaySubHandler.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import java.util.Collection; +import java.util.Collections; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.IntStream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.OnOffType; +import org.eclipse.smarthome.core.library.types.PercentType; +import org.eclipse.smarthome.core.library.types.UpDownType; +import org.openhab.binding.lcn.internal.LcnBindingConstants; +import org.openhab.binding.lcn.internal.LcnModuleHandler; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnDefs; +import org.openhab.binding.lcn.internal.common.LcnDefs.RelayStateModifier; +import org.openhab.binding.lcn.internal.common.LcnException; +import org.openhab.binding.lcn.internal.common.PckGenerator; +import org.openhab.binding.lcn.internal.connection.ModInfo; + +/** + * Handles Commands and State changes of Relays of an LCN module. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleRelaySubHandler extends AbstractLcnModuleSubHandler { + private static final Pattern PATTERN = Pattern.compile(LcnBindingConstants.ADDRESS_REGEX + "Rx(?\\d+)"); + + public LcnModuleRelaySubHandler(LcnModuleHandler handler, ModInfo info) { + super(handler, info); + } + + @Override + public void handleRefresh(LcnChannelGroup channelGroup, int number) { + info.refreshRelays(); + } + + @Override + public void handleCommandOnOff(OnOffType command, LcnChannelGroup channelGroup, int number) throws LcnException { + RelayStateModifier[] relayStateModifiers = createRelayStateModifierArray(); + relayStateModifiers[number] = command == OnOffType.ON ? LcnDefs.RelayStateModifier.ON + : LcnDefs.RelayStateModifier.OFF; + handler.sendPck(PckGenerator.controlRelays(relayStateModifiers)); + } + + @Override + public void handleCommandPercent(PercentType command, LcnChannelGroup channelGroup, int number) + throws LcnException { + // don't use OnOffType.as(), because it returns @Nullable + handleCommandOnOff(command.intValue() > 0 ? OnOffType.ON : OnOffType.OFF, channelGroup, number); + } + + @Override + public void handleStatusMessage(Matcher matcher) { + info.onRelayResponseReceived(); + + boolean[] states = LcnDefs.getBooleanValue(Integer.parseInt(matcher.group("byteValue"))); + + IntStream.range(0, LcnChannelGroup.RELAY.getCount()) + .forEach(i -> fireUpdate(LcnChannelGroup.RELAY, i, OnOffType.from(states[i]))); + + IntStream.range(0, LcnChannelGroup.ROLLERSHUTTERRELAY.getCount()).forEach(i -> { + UpDownType state = states[i * 2 + 1] ? UpDownType.DOWN : UpDownType.UP; + fireUpdate(LcnChannelGroup.ROLLERSHUTTERRELAY, i, state); + }); + } + + @Override + public Collection getPckStatusMessagePatterns() { + return Collections.singleton(PATTERN); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRollershutterOutputSubHandler.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRollershutterOutputSubHandler.java new file mode 100644 index 0000000000000..71b7521ebcd93 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRollershutterOutputSubHandler.java @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import java.util.Collection; +import java.util.Collections; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.StopMoveType; +import org.eclipse.smarthome.core.library.types.UpDownType; +import org.openhab.binding.lcn.internal.LcnModuleHandler; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnDefs; +import org.openhab.binding.lcn.internal.common.LcnException; +import org.openhab.binding.lcn.internal.common.PckGenerator; +import org.openhab.binding.lcn.internal.connection.ModInfo; + +/** + * Handles Commands and State changes of roller shutters connected to dimmer outputs of an LCN module. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleRollershutterOutputSubHandler extends AbstractLcnModuleSubHandler { + public LcnModuleRollershutterOutputSubHandler(LcnModuleHandler handler, ModInfo info) { + super(handler, info); + } + + @Override + public void handleRefresh(LcnChannelGroup channelGroup, int number) { + info.refreshOutput(number); + } + + @Override + public void handleCommandUpDown(UpDownType command, LcnChannelGroup channelGroup, int number) throws LcnException { + // When configured as shutter in LCN-PRO, an output gets switched off, when the other is + // switched on and vice versa. + if (command == UpDownType.UP) { + // first output: 100% + handler.sendPck(PckGenerator.dimOutput(0, 100, LcnDefs.ROLLER_SHUTTER_RAMP_MS)); + } else { + // second output: 100% + handler.sendPck(PckGenerator.dimOutput(1, 100, LcnDefs.ROLLER_SHUTTER_RAMP_MS)); + } + } + + @Override + public void handleCommandStopMove(StopMoveType command, LcnChannelGroup channelGroup, int number) + throws LcnException { + if (command == StopMoveType.STOP) { + // both outputs off + handler.sendPck(PckGenerator.dimOutput(0, 0, 0)); + handler.sendPck(PckGenerator.dimOutput(1, 0, 0)); + } else { + // roller shutters on outputs are stateless, assume always down when MOVE is sent + // second output: 100% + handler.sendPck(PckGenerator.dimOutput(1, 100, LcnDefs.ROLLER_SHUTTER_RAMP_MS)); + } + } + + @Override + public void handleStatusMessage(Matcher matcher) { + // status messages of roller shutters on dimmer outputs are handled in the dimmer output sub handler + } + + @Override + public Collection getPckStatusMessagePatterns() { + return Collections.emptyList(); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRollershutterRelaySubHandler.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRollershutterRelaySubHandler.java new file mode 100644 index 0000000000000..ef46add8a5f01 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRollershutterRelaySubHandler.java @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import java.util.Collection; +import java.util.Collections; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.StopMoveType; +import org.eclipse.smarthome.core.library.types.UpDownType; +import org.openhab.binding.lcn.internal.LcnModuleHandler; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnDefs; +import org.openhab.binding.lcn.internal.common.LcnDefs.RelayStateModifier; +import org.openhab.binding.lcn.internal.common.LcnException; +import org.openhab.binding.lcn.internal.common.PckGenerator; +import org.openhab.binding.lcn.internal.connection.ModInfo; + +/** + * Handles Commands and State changes of roller shutters connected to relays of an LCN module. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleRollershutterRelaySubHandler extends AbstractLcnModuleSubHandler { + public LcnModuleRollershutterRelaySubHandler(LcnModuleHandler handler, ModInfo info) { + super(handler, info); + } + + @Override + public void handleRefresh(LcnChannelGroup channelGroup, int number) { + info.refreshRelays(); + } + + @Override + public void handleCommandUpDown(UpDownType command, LcnChannelGroup channelGroup, int number) throws LcnException { + RelayStateModifier[] relayStateModifiers = createRelayStateModifierArray(); + // direction relay + relayStateModifiers[number * 2 + 1] = command == UpDownType.DOWN ? LcnDefs.RelayStateModifier.ON + : LcnDefs.RelayStateModifier.OFF; + // power relay + relayStateModifiers[number * 2] = LcnDefs.RelayStateModifier.ON; + handler.sendPck(PckGenerator.controlRelays(relayStateModifiers)); + } + + @Override + public void handleCommandStopMove(StopMoveType command, LcnChannelGroup channelGroup, int number) + throws LcnException { + RelayStateModifier[] relayStateModifiers = createRelayStateModifierArray(); + // power relay + relayStateModifiers[number * 2] = command == StopMoveType.MOVE ? LcnDefs.RelayStateModifier.ON + : LcnDefs.RelayStateModifier.OFF; + handler.sendPck(PckGenerator.controlRelays(relayStateModifiers)); + } + + @Override + public void handleStatusMessage(Matcher matcher) { + // status messages of roller shutters on relays are handled in the relay sub handler + } + + @Override + public Collection getPckStatusMessagePatterns() { + return Collections.emptyList(); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRvarLockSubHandler.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRvarLockSubHandler.java new file mode 100644 index 0000000000000..a2611cc38213c --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRvarLockSubHandler.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import java.util.Collection; +import java.util.Collections; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.OnOffType; +import org.openhab.binding.lcn.internal.LcnModuleHandler; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnException; +import org.openhab.binding.lcn.internal.common.PckGenerator; +import org.openhab.binding.lcn.internal.common.Variable; +import org.openhab.binding.lcn.internal.connection.ModInfo; + +/** + * Handles Commands and State changes of regulator locks of an LCN module. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleRvarLockSubHandler extends AbstractLcnModuleVariableSubHandler { + public LcnModuleRvarLockSubHandler(LcnModuleHandler handler, ModInfo info) { + super(handler, info); + } + + @Override + public void handleRefresh(LcnChannelGroup channelGroup, int number) { + super.handleRefresh(LcnChannelGroup.RVARSETPOINT, number); + } + + @Override + public void handleCommandOnOff(OnOffType command, LcnChannelGroup channelGroup, int number) throws LcnException { + boolean locked = command == OnOffType.ON; + handler.sendPck(PckGenerator.lockRegulator(number, locked)); + + // request new lock state, if the module doesn't send it on itself + Variable variable = getVariable(LcnChannelGroup.RVARSETPOINT, number); + if (variable.shouldPollStatusAfterRegulatorLock(info.getFirmwareVersion(), locked)) { + info.refreshVariable(variable); + } + } + + @Override + public void handleStatusMessage(Matcher matcher) { + // status messages are handled in the RVar setpoint sub handler + } + + @Override + public Collection getPckStatusMessagePatterns() { + return Collections.emptyList(); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRvarSetpointSubHandler.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRvarSetpointSubHandler.java new file mode 100644 index 0000000000000..5bf2d0c29c79d --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRvarSetpointSubHandler.java @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import java.util.Collection; +import java.util.Collections; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.DecimalType; +import org.eclipse.smarthome.core.library.types.OnOffType; +import org.openhab.binding.lcn.internal.LcnBindingConstants; +import org.openhab.binding.lcn.internal.LcnModuleHandler; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnDefs; +import org.openhab.binding.lcn.internal.common.LcnException; +import org.openhab.binding.lcn.internal.common.PckGenerator; +import org.openhab.binding.lcn.internal.common.Variable; +import org.openhab.binding.lcn.internal.common.VariableValue; +import org.openhab.binding.lcn.internal.connection.ModInfo; + +/** + * Handles Commands and State changes of regulator setpoints of an LCN module. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleRvarSetpointSubHandler extends AbstractLcnModuleVariableSubHandler { + private static final Pattern PATTERN = Pattern + .compile(LcnBindingConstants.ADDRESS_REGEX + "\\.S(?\\d)(?\\d+)"); + + public LcnModuleRvarSetpointSubHandler(LcnModuleHandler handler, ModInfo info) { + super(handler, info); + } + + @Override + public void handleCommandDecimal(DecimalType command, LcnChannelGroup channelGroup, int number) + throws LcnException { + Variable variable = getVariable(channelGroup, number); + + if (info.hasExtendedMeasurementProcessing()) { + handler.sendPck(PckGenerator.setSetpointAbsolute(number, command.intValue())); + } else { + try { + int relativeVariableChange = getRelativeChange(command, variable); + handler.sendPck( + PckGenerator.setSetpointRelative(number, LcnDefs.RelVarRef.CURRENT, relativeVariableChange)); + } catch (LcnException e) { + // current value unknown for some reason, refresh it in case we come again here + info.refreshVariable(variable); + throw e; + } + } + } + + @Override + public void handleStatusMessage(Matcher matcher) throws LcnException { + Variable variable = Variable.setPointIdToVar(Integer.parseInt(matcher.group("id")) - 1); + VariableValue value = fireUpdateAndReset(matcher, "", variable); + + fireUpdate(LcnChannelGroup.RVARLOCK, variable.getNumber(), OnOffType.from(value.isRegulatorLocked())); + } + + @Override + public Collection getPckStatusMessagePatterns() { + return Collections.singleton(PATTERN); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleS0CounterSubHandler.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleS0CounterSubHandler.java new file mode 100644 index 0000000000000..428b8352f822c --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleS0CounterSubHandler.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import java.util.Collection; +import java.util.Collections; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.DecimalType; +import org.openhab.binding.lcn.internal.LcnBindingConstants; +import org.openhab.binding.lcn.internal.LcnModuleHandler; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnException; +import org.openhab.binding.lcn.internal.common.Variable; +import org.openhab.binding.lcn.internal.connection.ModInfo; + +/** + * Handles Commands and State changes of S0 counter inputs of an LCN module. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleS0CounterSubHandler extends AbstractLcnModuleVariableSubHandler { + private static final Pattern PATTERN = Pattern + .compile(LcnBindingConstants.ADDRESS_REGEX + "\\.C(?\\d)(?\\d+)"); + + public LcnModuleS0CounterSubHandler(LcnModuleHandler handler, ModInfo info) { + super(handler, info); + } + + @Override + public void handleCommandDecimal(DecimalType command, LcnChannelGroup channelGroup, int number) + throws LcnException { + throw new LcnException("Setting S0 counters is not supported"); + } + + @Override + public void handleStatusMessage(Matcher matcher) throws LcnException { + fireUpdateAndReset(matcher, "", Variable.s0IdToVar(Integer.parseInt(matcher.group("id")) - 1)); + } + + @Override + public Collection getPckStatusMessagePatterns() { + return Collections.singleton(PATTERN); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleThresholdSubHandler.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleThresholdSubHandler.java new file mode 100644 index 0000000000000..744b61db88f9f --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleThresholdSubHandler.java @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.IntStream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.DecimalType; +import org.openhab.binding.lcn.internal.LcnBindingConstants; +import org.openhab.binding.lcn.internal.LcnModuleHandler; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnDefs; +import org.openhab.binding.lcn.internal.common.LcnException; +import org.openhab.binding.lcn.internal.common.PckGenerator; +import org.openhab.binding.lcn.internal.common.Variable; +import org.openhab.binding.lcn.internal.connection.ModInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Handles Commands and State changes of thresholds of an LCN module. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleThresholdSubHandler extends AbstractLcnModuleVariableSubHandler { + private final Logger logger = LoggerFactory.getLogger(LcnModuleThresholdSubHandler.class); + private static final Pattern PATTERN = Pattern + .compile(LcnBindingConstants.ADDRESS_REGEX + "\\.T(?\\d)(?\\d)(?\\d+)"); + private static final Pattern PATTERN_BEFORE_2013 = Pattern.compile(LcnBindingConstants.ADDRESS_REGEX + + "\\.S1(?\\d{5})(?\\d{5})(?\\d{5})(?\\d{5})(?\\d{5})(?\\d{5})"); + + public LcnModuleThresholdSubHandler(LcnModuleHandler handler, ModInfo info) { + super(handler, info); + } + + @Override + public void handleCommandDecimal(DecimalType command, LcnChannelGroup channelGroup, int number) + throws LcnException { + Variable variable = getVariable(channelGroup, number); + try { + int relativeChange = getRelativeChange(command, variable); + handler.sendPck(PckGenerator.setThresholdRelative(variable, LcnDefs.RelVarRef.CURRENT, relativeChange, + info.hasExtendedMeasurementProcessing())); + + // request new value, if the module doesn't send it on itself + if (variable.shouldPollStatusAfterCommand(info.getFirmwareVersion())) { + info.refreshVariable(variable); + } + } catch (LcnException e) { + // current value unknown for some reason, refresh it in case we come again here + info.refreshVariable(variable); + throw e; + } + } + + @Override + public void handleStatusMessage(Matcher matcher) { + IntStream stream; + Optional groupSuffix; + int registerNumber; + if (matcher.pattern() == PATTERN) { + int thresholdId = Integer.parseInt(matcher.group("thresholdId")) - 1; + registerNumber = Integer.parseInt(matcher.group("registerId")) - 1; + stream = IntStream.rangeClosed(thresholdId, thresholdId); + groupSuffix = Optional.of(""); + } else if (matcher.pattern() == PATTERN_BEFORE_2013) { + stream = IntStream.range(0, LcnDefs.THRESHOLD_COUNT_BEFORE_2013); + groupSuffix = Optional.empty(); + registerNumber = 0; + } else { + logger.warn("Unexpected pattern: {}", matcher.pattern()); + return; + } + + stream.forEach(i -> { + try { + fireUpdateAndReset(matcher, groupSuffix.orElse(String.valueOf(i)), + Variable.thrsIdToVar(registerNumber, i)); + } catch (LcnException e) { + logger.warn("Parse error: {}", e.getMessage()); + } + }); + } + + @Override + public Collection getPckStatusMessagePatterns() { + return Arrays.asList(PATTERN, PATTERN_BEFORE_2013); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleVariableSubHandler.java b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleVariableSubHandler.java new file mode 100644 index 0000000000000..ac1ce6f3cc945 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleVariableSubHandler.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import java.util.Arrays; +import java.util.Collection; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.DecimalType; +import org.openhab.binding.lcn.internal.LcnBindingConstants; +import org.openhab.binding.lcn.internal.LcnModuleHandler; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnDefs; +import org.openhab.binding.lcn.internal.common.LcnException; +import org.openhab.binding.lcn.internal.common.PckGenerator; +import org.openhab.binding.lcn.internal.common.Variable; +import org.openhab.binding.lcn.internal.connection.ModInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Handles Commands and State changes of variables of an LCN module. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleVariableSubHandler extends AbstractLcnModuleVariableSubHandler { + private final Logger logger = LoggerFactory.getLogger(LcnModuleVariableSubHandler.class); + private static final Pattern PATTERN = Pattern + .compile(LcnBindingConstants.ADDRESS_REGEX + "\\.A(?\\d{3})(?\\d+)"); + private static final Pattern PATTERN_LEGACY = Pattern + .compile(LcnBindingConstants.ADDRESS_REGEX + "\\.(?\\d+)"); + + public LcnModuleVariableSubHandler(LcnModuleHandler handler, ModInfo info) { + super(handler, info); + } + + @Override + public void handleCommandDecimal(DecimalType command, LcnChannelGroup channelGroup, int number) + throws LcnException { + Variable variable = getVariable(channelGroup, number); + try { + int relativeChange = getRelativeChange(command, variable); + handler.sendPck(PckGenerator.setVariableRelative(variable, LcnDefs.RelVarRef.CURRENT, relativeChange)); + + // request new value, if the module doesn't send it on itself + if (variable.shouldPollStatusAfterCommand(info.getFirmwareVersion())) { + info.refreshVariable(variable); + } + } catch (LcnException e) { + // current value unknown for some reason, refresh it in case we come again here + info.refreshVariable(variable); + throw e; + } + } + + @Override + public void handleStatusMessage(Matcher matcher) throws LcnException { + Variable variable; + if (matcher.pattern() == PATTERN) { + variable = Variable.varIdToVar(Integer.parseInt(matcher.group("id")) - 1); + } else if (matcher.pattern() == PATTERN_LEGACY) { + variable = info.getLastRequestedVarWithoutTypeInResponse(); + info.setLastRequestedVarWithoutTypeInResponse(Variable.UNKNOWN); // Reset + } else { + logger.warn("Unexpected pattern: {}", matcher.pattern()); + return; + } + fireUpdateAndReset(matcher, "", variable); + } + + @Override + public Collection getPckStatusMessagePatterns() { + return Arrays.asList(PATTERN, PATTERN_LEGACY); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/main/resources/ESH-INF/binding/binding.xml b/bundles/org.openhab.binding.lcn/src/main/resources/ESH-INF/binding/binding.xml new file mode 100644 index 0000000000000..c494e66acc295 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/resources/ESH-INF/binding/binding.xml @@ -0,0 +1,10 @@ + + + + LCN Binding + This is the binding for Local Control Network (LCN) + Fabian Wolter + + diff --git a/bundles/org.openhab.binding.lcn/src/main/resources/ESH-INF/config/config.xml b/bundles/org.openhab.binding.lcn/src/main/resources/ESH-INF/config/config.xml new file mode 100644 index 0000000000000..439256829226b --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/resources/ESH-INF/config/config.xml @@ -0,0 +1,102 @@ + + + + + + + The hostname or the IP address of the PCK gateway + network-address + true + + + + The IP port of the PCK gateway + 4114 + true + + + + The login username of the PCK gateway + true + + + + The login password of the PCK gateway + password + + + + IMPORTANT: Dimming range of all modules. Must be the same value as configured in LCN-PRO (Options/Settings/Expert Settings). If you only have modules with firmware newer than Feb. 2013, you probably want to choose 0 - 200.]]> + native200 + + + + + true + + + + Period after which an LCN command is resent, when no acknowledge has been received (in ms). + 3500 + true + + + + + + + The module ID, configured in LCN-PRO + + + + The segment ID the module is in (0 if no segments are present) + + + + + + + The group number, configured in LCN-PRO + + + + The module ID of any module in the group. The state of this module is used for visualization of the + group as representative for all modules. + + + + The segment ID of all modules in this group (0 if no segments are present) + 0 + + + + + + + Unit of the sensor + native + + + + + + + + + + + + + true + + + + Only for S0 counters (power or energy) + 1000 + + + diff --git a/bundles/org.openhab.binding.lcn/src/main/resources/ESH-INF/i18n/lcn_de.properties b/bundles/org.openhab.binding.lcn/src/main/resources/ESH-INF/i18n/lcn_de.properties new file mode 100644 index 0000000000000..29ca7960c205e --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/resources/ESH-INF/i18n/lcn_de.properties @@ -0,0 +1,171 @@ +# binding +binding.lcn.name = LCN Binding +binding.lcn.description = Binding f�r Local Control Network (LCN) + +# thing types +thing-type.lcn.pckGateway.label = LCN-PCK-Koppler +thing-type.lcn.pckGateway.description = z.B. die LCN-PCHK-Software oder das Hutschienenmodul LCN-PKE +thing-type.lcn.module.label = LCN-Modul +thing-type.lcn.module.description = z.B. LCN-UPP, LCN-SH, LCN-HU +thing-type.lcn.group.label = LCN-Gruppe +thing-type.lcn.group.description = Eine Gruppe mit mehreren Modulen, wie in LCN-PRO parametriert + +# thing type config description +thing-type.config.lcn.pckGateway.hostname.description = Hostname oder die IP-Adresse des PCK-Kopplers +thing-type.config.lcn.pckGateway.port.description = Netzwerk-Port auf dem der PCK-Koppler l�uft +thing-type.config.lcn.pckGateway.username.description = Benutzername vom PCK-Koppler +thing-type.config.lcn.pckGateway.password.description = Login-Passwort vom PCK-Koppler +thing-type.config.lcn.pckGateway.mode.description = WICHTIG: Der Dimmbereich von allen LCN-Modulen. Muss der gleiche Wert, wie in LCN-PRO sein (Optionen/Einstellungen/Experteneinstellungen). Wenn nur Module �lter als 2013 im Bus vorhanden sind, muss hier wahrscheinlich 0 - 200 ausgew�hlt werden. +thing-type.config.lcn.pckGateway.timeoutMs.description = Zeit nach der eine PCK-Nachricht erneut gesendet wird, wenn vom Modul keine positive Quittung empfangen wurde. + +thing-type.config.lcn.module.moduleId.label = Modul-ID +thing-type.config.lcn.module.moduleId.description = Modul-ID, wie in LCN-PRO parametriert +thing-type.config.lcn.module.segmentId.label = Segment-ID +thing-type.config.lcn.module.segmentId.description = ID des Segments, in dem sich das Modul befindet (0 wenn keine Segmente vorhanden sind) + +thing-type.config.lcn.group.groupId.label = Gruppennummer +thing-type.config.lcn.group.groupId.description = Nummer der Gruppe, wie in LCN-PRO parametriert +thing-type.config.lcn.group.moduleId.label = Modul-ID eines Moduls aus der Gruppe +thing-type.config.lcn.group.moduleId.description = Der Zustand dieses Moduls wird zur Visualisierung der Gruppe, stellvertretend f�r alle Module, genutzt +thing-type.config.lcn.group.segmentId.label = Segment-ID +thing-type.config.lcn.group.segmentId.description = Segment-ID in dem sich die Module der Gruppe befinden (0 wenn keine Segmente vorhanden sind) + +# channel type config description +channel-type.config.lcn.variable.unit.label = Einheit +channel-type.config.lcn.variable.unit.description = Einheit des Sensors +channel-type.config.lcn.variable.unit.option.native = LCN-Wert +channel-type.config.lcn.variable.unit.option.temperature = Temperatur (�C) +channel-type.config.lcn.variable.unit.option.light = Licht (Lux) +channel-type.config.lcn.variable.unit.option.co2 = CO\u2082 (ppm) +channel-type.config.lcn.variable.unit.option.power = Leistung (W) +channel-type.config.lcn.variable.unit.option.energy = Z�hlerstand (kWh) +channel-type.config.lcn.variable.unit.option.current = Strom (mA) +channel-type.config.lcn.variable.unit.option.voltage = Spannung (V) +channel-type.config.lcn.variable.unit.option.angle = Winkel (�) +channel-type.config.lcn.variable.unit.option.windspeed = Windgeschwindigkeit (m/s) +channel-type.config.lcn.variable.parameter.label = Impulse pro kWh +channel-type.config.lcn.variable.parameter.description = Nur f�r S0-Z�hler + +# channel types +channel-group-type.lcn.outputs.label = Ausg�nge +channel-group-type.lcn.outputs.channel.1.label = Ausgang 1 +channel-group-type.lcn.outputs.channel.2.label = Ausgang 2 +channel-group-type.lcn.outputs.channel.3.label = Ausgang 3 +channel-group-type.lcn.outputs.channel.4.label = Ausgang 4 +channel-group-type.lcn.outputs.channel.color.label = RGB-Steuerung f�r Ausg�nge 1-3 +channel-group-type.lcn.rollershutteroutputs.label = Rolll�den an Ausg�ngen +channel-group-type.lcn.rollershutteroutputs.channel.1.label = Rolll�den an Ausg�ngen 1+2 +channel-group-type.lcn.relays.label = Relais +channel-group-type.lcn.relays.channel.1.label = Relais 1 +channel-group-type.lcn.relays.channel.2.label = Relais 2 +channel-group-type.lcn.relays.channel.3.label = Relais 3 +channel-group-type.lcn.relays.channel.4.label = Relais 4 +channel-group-type.lcn.relays.channel.5.label = Relais 5 +channel-group-type.lcn.relays.channel.6.label = Relais 6 +channel-group-type.lcn.relays.channel.7.label = Relais 7 +channel-group-type.lcn.relays.channel.8.label = Relais 8 +channel-group-type.lcn.rollershutterrelays.label = Rolll�den an Relais +channel-group-type.lcn.rollershutterrelays.channel.1.label = Rolll�den an Relais 1+2 +channel-group-type.lcn.rollershutterrelays.channel.2.label = Rolll�den an Relais 3+4 +channel-group-type.lcn.rollershutterrelays.channel.3.label = Rolll�den an Relais 5+6 +channel-group-type.lcn.rollershutterrelays.channel.4.label = Rolll�den an Relais 7+8 +channel-group-type.lcn.logics.label = Logik-Funktionen +channel-group-type.lcn.logics.channel.1.label = Logik-Funktion 1 +channel-group-type.lcn.logics.channel.2.label = Logik-Funktion 2 +channel-group-type.lcn.logics.channel.3.label = Logik-Funktion 3 +channel-group-type.lcn.logics.channel.4.label = Logik-Funktion 4 +channel-group-type.lcn.binarysensors.label = Bin�rsensoren +channel-group-type.lcn.binarysensors.channel.1.label = Bin�rsensor 1 +channel-group-type.lcn.binarysensors.channel.2.label = Bin�rsensor 2 +channel-group-type.lcn.binarysensors.channel.3.label = Bin�rsensor 3 +channel-group-type.lcn.binarysensors.channel.4.label = Bin�rsensor 4 +channel-group-type.lcn.binarysensors.channel.5.label = Bin�rsensor 5 +channel-group-type.lcn.binarysensors.channel.6.label = Bin�rsensor 6 +channel-group-type.lcn.binarysensors.channel.7.label = Bin�rsensor 7 +channel-group-type.lcn.binarysensors.channel.8.label = Bin�rsensor 8 +channel-group-type.lcn.variables.label = Variablen +channel-group-type.lcn.variables.channel.1.label = Variable 1 +channel-group-type.lcn.variables.channel.2.label = Variable 2 +channel-group-type.lcn.variables.channel.3.label = Variable 3 +channel-group-type.lcn.variables.channel.4.label = Variable 4 +channel-group-type.lcn.variables.channel.5.label = Variable 5 +channel-group-type.lcn.variables.channel.6.label = Variable 6 +channel-group-type.lcn.variables.channel.7.label = Variable 7 +channel-group-type.lcn.variables.channel.8.label = Variable 8 +channel-group-type.lcn.variables.channel.9.label = Variable 9 +channel-group-type.lcn.variables.channel.10.label = Variable 10 +channel-group-type.lcn.variables.channel.11.label = Variable 11 +channel-group-type.lcn.variables.channel.12.label = Variable 12 +channel-group-type.lcn.rvarsetpoints.label = Regler +channel-group-type.lcn.rvarsetpoints.channel.1.label = Regler 1 Sollwert +channel-group-type.lcn.rvarsetpoints.channel.2.label = Regler 2 Sollwert +channel-group-type.lcn.rvarlocks.label = Regler Sperren +channel-group-type.lcn.rvarlocks.channel.1.label = Regler 1 Sperre +channel-group-type.lcn.rvarlocks.channel.2.label = Regler 2 Sperre +channel-group-type.lcn.thresholdregisters1.label = Schwellwertregister 1 +channel-group-type.lcn.thresholdregisters1.channel.1.label = Schwellwert 1 +channel-group-type.lcn.thresholdregisters1.channel.2.label = Schwellwert 2 +channel-group-type.lcn.thresholdregisters1.channel.3.label = Schwellwert 3 +channel-group-type.lcn.thresholdregisters1.channel.4.label = Schwellwert 4 +channel-group-type.lcn.thresholdregisters1.channel.5.label = Schwellwert 5 +channel-group-type.lcn.thresholdregisters2.label = Schwellwertregister 2 +channel-group-type.lcn.thresholdregisters2.channel.1.label = Schwellwert 1 +channel-group-type.lcn.thresholdregisters2.channel.2.label = Schwellwert 2 +channel-group-type.lcn.thresholdregisters2.channel.3.label = Schwellwert 3 +channel-group-type.lcn.thresholdregisters2.channel.4.label = Schwellwert 4 +channel-group-type.lcn.thresholdregisters3.label = Schwellwertregister 3 +channel-group-type.lcn.thresholdregisters3.channel.1.label = Schwellwert 1 +channel-group-type.lcn.thresholdregisters3.channel.2.label = Schwellwert 2 +channel-group-type.lcn.thresholdregisters3.channel.3.label = Schwellwert 3 +channel-group-type.lcn.thresholdregisters3.channel.4.label = Schwellwert 4 +channel-group-type.lcn.thresholdregisters4.label = Schwellwertregister 4 +channel-group-type.lcn.thresholdregisters4.channel.1.label = Schwellwert 1 +channel-group-type.lcn.thresholdregisters4.channel.2.label = Schwellwert 2 +channel-group-type.lcn.thresholdregisters4.channel.3.label = Schwellwert 3 +channel-group-type.lcn.thresholdregisters4.channel.4.label = Schwellwert 4 +channel-group-type.lcn.s0inputs.label = S0-Z�hler +channel-group-type.lcn.s0inputs.channel.1.label = S0-Z�hler 1 +channel-group-type.lcn.s0inputs.channel.2.label = S0-Z�hler 2 +channel-group-type.lcn.s0inputs.channel.3.label = S0-Z�hler 3 +channel-group-type.lcn.s0inputs.channel.4.label = S0-Z�hler 4 +channel-group-type.lcn.keyslocktablea.label = Tastensperren Tabelle A +channel-group-type.lcn.keyslocktablea.channel.1.label = A1 Sperre +channel-group-type.lcn.keyslocktablea.channel.2.label = A2 Sperre +channel-group-type.lcn.keyslocktablea.channel.3.label = A3 Sperre +channel-group-type.lcn.keyslocktablea.channel.4.label = A4 Sperre +channel-group-type.lcn.keyslocktablea.channel.5.label = A5 Sperre +channel-group-type.lcn.keyslocktablea.channel.6.label = A6 Sperre +channel-group-type.lcn.keyslocktablea.channel.7.label = A7 Sperre +channel-group-type.lcn.keyslocktablea.channel.8.label = A8 Sperre +channel-group-type.lcn.keyslocktableb.label = Tastensperren Tabelle B +channel-group-type.lcn.keyslocktableb.channel.1.label = B1 Sperre +channel-group-type.lcn.keyslocktableb.channel.2.label = B2 Sperre +channel-group-type.lcn.keyslocktableb.channel.3.label = B3 Sperre +channel-group-type.lcn.keyslocktableb.channel.4.label = B4 Sperre +channel-group-type.lcn.keyslocktableb.channel.5.label = B5 Sperre +channel-group-type.lcn.keyslocktableb.channel.6.label = B6 Sperre +channel-group-type.lcn.keyslocktableb.channel.7.label = B7 Sperre +channel-group-type.lcn.keyslocktableb.channel.8.label = B8 Sperre +channel-group-type.lcn.keyslocktablec.label = Tastensperren Tabelle C +channel-group-type.lcn.keyslocktablec.channel.1.label = C1 Sperre +channel-group-type.lcn.keyslocktablec.channel.2.label = C2 Sperre +channel-group-type.lcn.keyslocktablec.channel.3.label = C3 Sperre +channel-group-type.lcn.keyslocktablec.channel.4.label = C4 Sperre +channel-group-type.lcn.keyslocktablec.channel.5.label = C5 Sperre +channel-group-type.lcn.keyslocktablec.channel.6.label = C6 Sperre +channel-group-type.lcn.keyslocktablec.channel.7.label = C7 Sperre +channel-group-type.lcn.keyslocktablec.channel.8.label = C8 Sperre +channel-group-type.lcn.keyslocktabled.label = Tastensperren Tabelle D +channel-group-type.lcn.keyslocktabled.channel.1.label = D1 Sperre +channel-group-type.lcn.keyslocktabled.channel.2.label = D2 Sperre +channel-group-type.lcn.keyslocktabled.channel.3.label = D3 Sperre +channel-group-type.lcn.keyslocktabled.channel.4.label = D4 Sperre +channel-group-type.lcn.keyslocktabled.channel.5.label = D5 Sperre +channel-group-type.lcn.keyslocktabled.channel.6.label = D6 Sperre +channel-group-type.lcn.keyslocktabled.channel.7.label = D7 Sperre +channel-group-type.lcn.keyslocktabled.channel.8.label = D8 Sperre +channel-group-type.lcn.codes.label = Transponder & Fernbedienungen +channel-group-type.lcn.codes.channel.transponder.label = Transponder-Code +channel-group-type.lcn.codes.channel.remotecontrolkey.label = Fernbedienung Tasten +channel-group-type.lcn.codes.channel.remotecontrolcode.label = Fernbedienung Tasten mit Zutrittscode +channel-group-type.lcn.codes.channel.remotecontrolbatterylow.label = Fernbedienung Batterie leer diff --git a/bundles/org.openhab.binding.lcn/src/main/resources/ESH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.lcn/src/main/resources/ESH-INF/thing/thing-types.xml new file mode 100644 index 0000000000000..7921cd5534715 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/main/resources/ESH-INF/thing/thing-types.xml @@ -0,0 +1,634 @@ + + + + + + An LCN gateway speaking the PCK language. E.g. LCN-PCHK software or the DIN rail device LCN-PKE. + + + + + + + + + + + An LCN bus module, e.g. LCN-UPP, LCN-SH, LCN-HU + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + An LCN group with multiple modules, configured in LCN-PRO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Dimmer + + veto + + + + Color + + veto + + + + + + + + + + + + + + + + + + + + + + + + + Switch + + veto + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rollershutter + + veto + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + String + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + String + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Contact + + veto + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Number + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Switch + + veto + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Only before Feb. 2013 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Switch + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + trigger + + + + + + trigger + + + + + + trigger + + + + + + trigger + + + + diff --git a/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/ModuleActionsTest.java b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/ModuleActionsTest.java new file mode 100644 index 0000000000000..2867607cc6d90 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/ModuleActionsTest.java @@ -0,0 +1,199 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertThat; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +import java.nio.ByteBuffer; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.MockitoAnnotations; +import org.openhab.binding.lcn.internal.common.LcnDefs; +import org.openhab.binding.lcn.internal.common.LcnException; + +/** + * Test class for {@link LcnModuleActions}. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class ModuleActionsTest { + private LcnModuleActions a = new LcnModuleActions(); + private final LcnModuleHandler handler = mock(LcnModuleHandler.class); + @Captor + private @NonNullByDefault({}) ArgumentCaptor byteBufferCaptor; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + a = new LcnModuleActions(); + a.setThingHandler(handler); + } + + private byte[] stringToByteBuffer(String string) { + return string.getBytes(LcnDefs.LCN_ENCODING); + } + + @Test + public void testSendDynamicText1CharRow1() throws LcnException { + a.sendDynamicText(1, "a"); + + verify(handler).sendPck(stringToByteBuffer("GTDT11a\0\0\0\0\0\0\0\0\0\0\0")); + } + + @Test + public void testSendDynamicText1ChunkRow1() throws LcnException { + a.sendDynamicText(1, "abcdfghijklm"); + + verify(handler).sendPck(stringToByteBuffer("GTDT11abcdfghijklm")); + } + + @Test + public void testSendDynamicText1Chunk1CharRow1() throws LcnException { + a.sendDynamicText(1, "abcdfghijklmn"); + + verify(handler, times(2)).sendPck(byteBufferCaptor.capture()); + + assertThat(byteBufferCaptor.getAllValues(), contains(stringToByteBuffer("GTDT11abcdfghijklm"), + stringToByteBuffer("GTDT12n\0\0\0\0\0\0\0\0\0\0\0"))); + } + + @Test + public void testSendDynamicText5ChunksRow1() throws LcnException { + a.sendDynamicText(1, "abcdfghijklmnopqrstuvwxyzabcdfghijklmnopqrstuvwxyzabcdfghijk"); + + verify(handler, times(5)).sendPck(byteBufferCaptor.capture()); + + assertThat(byteBufferCaptor.getAllValues(), + containsInAnyOrder(stringToByteBuffer("GTDT11abcdfghijklm"), stringToByteBuffer("GTDT12nopqrstuvwxy"), + stringToByteBuffer("GTDT13zabcdfghijkl"), stringToByteBuffer("GTDT14mnopqrstuvwx"), + stringToByteBuffer("GTDT15yzabcdfghijk"))); + } + + @Test + public void testSendDynamicText5Chunks1CharRow1Truncated() throws LcnException { + a.sendDynamicText(1, "abcdfghijklmnopqrstuvwxyzabcdfghijklmnopqrstuvwxyzabcdfghijkl"); + + verify(handler, times(5)).sendPck(byteBufferCaptor.capture()); + + assertThat(byteBufferCaptor.getAllValues(), + containsInAnyOrder(stringToByteBuffer("GTDT11abcdfghijklm"), stringToByteBuffer("GTDT12nopqrstuvwxy"), + stringToByteBuffer("GTDT13zabcdfghijkl"), stringToByteBuffer("GTDT14mnopqrstuvwx"), + stringToByteBuffer("GTDT15yzabcdfghijk"))); + } + + @Test + public void testSendDynamicText5Chunks1UmlautRow1Truncated() throws LcnException { + a.sendDynamicText(1, "äcdfghijklmnopqrstuvwxyzabcdfghijklmnopqrstuvwxyzabcdfghijkl"); + + verify(handler, times(5)).sendPck(byteBufferCaptor.capture()); + + assertThat(byteBufferCaptor.getAllValues(), + containsInAnyOrder(stringToByteBuffer("GTDT11äcdfghijklm"), stringToByteBuffer("GTDT12nopqrstuvwxy"), + stringToByteBuffer("GTDT13zabcdfghijkl"), stringToByteBuffer("GTDT14mnopqrstuvwx"), + stringToByteBuffer("GTDT15yzabcdfghijk"))); + } + + @Test + public void testSendDynamicTextRow4() throws LcnException { + a.sendDynamicText(4, "abcdfghijklmn"); + + verify(handler, times(2)).sendPck(byteBufferCaptor.capture()); + + assertThat(byteBufferCaptor.getAllValues(), contains(stringToByteBuffer("GTDT41abcdfghijklm"), + stringToByteBuffer("GTDT42n\0\0\0\0\0\0\0\0\0\0\0"))); + } + + @Test + public void testSendDynamicTextSplitInCharacter() throws LcnException { + a.sendDynamicText(4, "Test 123 öäüß"); + + verify(handler, times(2)).sendPck(byteBufferCaptor.capture()); + + String string1 = "GTDT41Test 123 ö"; + ByteBuffer chunk1 = ByteBuffer.allocate(stringToByteBuffer(string1).length + 1); + chunk1.put(stringToByteBuffer(string1)); + chunk1.put((byte) -61); // first byte of ä + + ByteBuffer chunk2 = ByteBuffer.allocate(18); + chunk2.put(stringToByteBuffer("GTDT42")); + chunk2.put((byte) -92); // second byte of ä + chunk2.put(stringToByteBuffer("üß\0\0\0\0\0\0")); + + assertThat(byteBufferCaptor.getAllValues(), contains(chunk1.array(), chunk2.array())); + } + + @Test + public void testSendKeysInvalidTable() throws LcnException { + a.hitKey("E", 3, "MAKE"); + verify(handler, times(0)).sendPck(anyString()); + } + + @Test + public void testSendKeysNullTable() throws LcnException { + a.hitKey(null, 3, "MAKE"); + verify(handler, times(0)).sendPck(anyString()); + } + + @Test + public void testSendKeysNullAction() throws LcnException { + a.hitKey("D", 3, null); + verify(handler, times(0)).sendPck(anyString()); + } + + @Test + public void testSendKeysInvalidKey0() throws LcnException { + a.hitKey("D", 0, "MAKE"); + verify(handler, times(0)).sendPck(anyString()); + } + + @Test + public void testSendKeysInvalidKey9() throws LcnException { + a.hitKey("D", 9, "MAKE"); + verify(handler, times(0)).sendPck(anyString()); + } + + @Test + public void testSendKeysInvalidAction() throws LcnException { + a.hitKey("D", 8, "invalid"); + verify(handler, times(0)).sendPck(anyString()); + } + + @Test + public void testSendKeysA1Hit() throws LcnException { + a.hitKey("a", 1, "HIT"); + + verify(handler).sendPck("TSK--10000000"); + } + + @Test + public void testSendKeysC8Hit() throws LcnException { + a.hitKey("C", 8, "break"); + + verify(handler).sendPck("TS--O00000001"); + } + + @Test + public void testSendKeysD3Make() throws LcnException { + a.hitKey("D", 3, "MAKE"); + + verify(handler).sendPck("TS---L00100000"); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/pchkdiscovery/LcnPchkDiscoveryServiceTest.java b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/pchkdiscovery/LcnPchkDiscoveryServiceTest.java new file mode 100644 index 0000000000000..38feefcc26362 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/pchkdiscovery/LcnPchkDiscoveryServiceTest.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.pchkdiscovery; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.Before; +import org.junit.Test; + +/** + * Test class for {@link LcnPchkDiscoveryService}. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnPchkDiscoveryServiceTest { + private LcnPchkDiscoveryService s = new LcnPchkDiscoveryService(); + private ServicesResponse r = s.xmlToServiceResponse(RESPONSE); + private static final String RESPONSE = "LCN-PCHK 3.2.2 running on Unix/LinuxPCHK 3.2.2 bus"; + + @Before + public void setUp() { + s = new LcnPchkDiscoveryService(); + r = s.xmlToServiceResponse(RESPONSE); + } + + @Test + public void testXmlMachineId() { + assertThat(r.getServer().getMachineId(), is("b8:27:eb:fe:a4:bb")); + } + + @Test + public void testXmlMachineName() { + assertThat(r.getServer().getMachineName(), is("raspberrypi")); + } + + @Test + public void testXmlServerContent() { + assertThat(r.getServer().getContent(), is("LCN-PCHK 3.2.2 running on Unix/Linux")); + } + + @Test + public void testXmlPort() { + assertThat(r.getExtServices().getExtService().getLocalPort(), is(4114)); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/AbstractTestLcnModuleSubHandler.java b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/AbstractTestLcnModuleSubHandler.java new file mode 100644 index 0000000000000..7cee0058d48b5 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/AbstractTestLcnModuleSubHandler.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import static org.mockito.Mockito.when; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.openhab.binding.lcn.internal.LcnModuleHandler; +import org.openhab.binding.lcn.internal.connection.ModInfo; + +/** + * Test class. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class AbstractTestLcnModuleSubHandler { + @Mock + protected @NonNullByDefault({}) LcnModuleHandler handler; + @Mock + protected @NonNullByDefault({}) ModInfo info; + + public AbstractTestLcnModuleSubHandler() { + setUp(); + } + + public void setUp() { + MockitoAnnotations.initMocks(this); + when(handler.isMyAddress("000", "005")).thenReturn(true); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleBinarySensorSubHandlerTest.java b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleBinarySensorSubHandlerTest.java new file mode 100644 index 0000000000000..ba6f4a2581cf8 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleBinarySensorSubHandlerTest.java @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import static org.mockito.Mockito.verify; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.OpenClosedType; +import org.junit.Before; +import org.junit.Test; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; + +/** + * Test class. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleBinarySensorSubHandlerTest extends AbstractTestLcnModuleSubHandler { + private @NonNullByDefault({}) LcnModuleBinarySensorSubHandler l; + + @Override + @Before + public void setUp() { + super.setUp(); + + l = new LcnModuleBinarySensorSubHandler(handler, info); + } + + @Test + public void testStatusAllClosed() { + l.tryParse("=M000005Bx000"); + verify(handler).updateChannel(LcnChannelGroup.BINARYSENSOR, "1", OpenClosedType.CLOSED); + verify(handler).updateChannel(LcnChannelGroup.BINARYSENSOR, "2", OpenClosedType.CLOSED); + verify(handler).updateChannel(LcnChannelGroup.BINARYSENSOR, "3", OpenClosedType.CLOSED); + verify(handler).updateChannel(LcnChannelGroup.BINARYSENSOR, "4", OpenClosedType.CLOSED); + verify(handler).updateChannel(LcnChannelGroup.BINARYSENSOR, "5", OpenClosedType.CLOSED); + verify(handler).updateChannel(LcnChannelGroup.BINARYSENSOR, "6", OpenClosedType.CLOSED); + verify(handler).updateChannel(LcnChannelGroup.BINARYSENSOR, "7", OpenClosedType.CLOSED); + verify(handler).updateChannel(LcnChannelGroup.BINARYSENSOR, "8", OpenClosedType.CLOSED); + } + + @Test + public void testStatusAllOpen() { + l.tryParse("=M000005Bx255"); + verify(handler).updateChannel(LcnChannelGroup.BINARYSENSOR, "1", OpenClosedType.OPEN); + verify(handler).updateChannel(LcnChannelGroup.BINARYSENSOR, "2", OpenClosedType.OPEN); + verify(handler).updateChannel(LcnChannelGroup.BINARYSENSOR, "3", OpenClosedType.OPEN); + verify(handler).updateChannel(LcnChannelGroup.BINARYSENSOR, "5", OpenClosedType.OPEN); + verify(handler).updateChannel(LcnChannelGroup.BINARYSENSOR, "6", OpenClosedType.OPEN); + verify(handler).updateChannel(LcnChannelGroup.BINARYSENSOR, "7", OpenClosedType.OPEN); + verify(handler).updateChannel(LcnChannelGroup.BINARYSENSOR, "8", OpenClosedType.OPEN); + } + + @Test + public void testStatus1And7Closed() { + l.tryParse("=M000005Bx065"); + verify(handler).updateChannel(LcnChannelGroup.BINARYSENSOR, "1", OpenClosedType.OPEN); + verify(handler).updateChannel(LcnChannelGroup.BINARYSENSOR, "2", OpenClosedType.CLOSED); + verify(handler).updateChannel(LcnChannelGroup.BINARYSENSOR, "3", OpenClosedType.CLOSED); + verify(handler).updateChannel(LcnChannelGroup.BINARYSENSOR, "4", OpenClosedType.CLOSED); + verify(handler).updateChannel(LcnChannelGroup.BINARYSENSOR, "5", OpenClosedType.CLOSED); + verify(handler).updateChannel(LcnChannelGroup.BINARYSENSOR, "6", OpenClosedType.CLOSED); + verify(handler).updateChannel(LcnChannelGroup.BINARYSENSOR, "7", OpenClosedType.OPEN); + verify(handler).updateChannel(LcnChannelGroup.BINARYSENSOR, "8", OpenClosedType.CLOSED); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleKeyLockTableSubHandlerTest.java b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleKeyLockTableSubHandlerTest.java new file mode 100644 index 0000000000000..011bc56801970 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleKeyLockTableSubHandlerTest.java @@ -0,0 +1,173 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import static org.mockito.Mockito.verify; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.OnOffType; +import org.junit.Before; +import org.junit.Test; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnException; + +/** + * Test class. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleKeyLockTableSubHandlerTest extends AbstractTestLcnModuleSubHandler { + private @NonNullByDefault({}) LcnModuleKeyLockTableSubHandler l; + + @Override + @Before + public void setUp() { + super.setUp(); + + l = new LcnModuleKeyLockTableSubHandler(handler, info); + } + + @Test + public void testStatus() { + l.tryParse("=M000005.TX098036000255"); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLEA, "1", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLEA, "2", OnOffType.ON); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLEA, "3", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLEA, "4", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLEA, "5", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLEA, "6", OnOffType.ON); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLEA, "7", OnOffType.ON); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLEA, "8", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLEB, "1", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLEB, "2", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLEB, "3", OnOffType.ON); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLEB, "4", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLEB, "5", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLEB, "6", OnOffType.ON); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLEB, "7", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLEB, "8", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLEC, "1", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLEC, "2", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLEC, "3", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLEC, "4", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLEC, "5", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLEC, "6", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLEC, "7", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLEC, "8", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLED, "1", OnOffType.ON); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLED, "2", OnOffType.ON); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLED, "3", OnOffType.ON); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLED, "4", OnOffType.ON); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLED, "5", OnOffType.ON); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLED, "6", OnOffType.ON); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLED, "7", OnOffType.ON); + verify(handler).updateChannel(LcnChannelGroup.KEYLOCKTABLED, "8", OnOffType.ON); + } + + @Test + public void testHandleCommandA1Off() throws LcnException { + l.handleCommandOnOff(OnOffType.OFF, LcnChannelGroup.KEYLOCKTABLEA, 0); + verify(handler).sendPck("TXA0-------"); + } + + @Test + public void testHandleCommandA1On() throws LcnException { + l.handleCommandOnOff(OnOffType.ON, LcnChannelGroup.KEYLOCKTABLEA, 0); + verify(handler).sendPck("TXA1-------"); + } + + @Test + public void testHandleCommandA8Off() throws LcnException { + l.handleCommandOnOff(OnOffType.OFF, LcnChannelGroup.KEYLOCKTABLEA, 7); + verify(handler).sendPck("TXA-------0"); + } + + @Test + public void testHandleCommandA8On() throws LcnException { + l.handleCommandOnOff(OnOffType.ON, LcnChannelGroup.KEYLOCKTABLEA, 7); + verify(handler).sendPck("TXA-------1"); + } + + @Test + public void testHandleCommandB1Off() throws LcnException { + l.handleCommandOnOff(OnOffType.OFF, LcnChannelGroup.KEYLOCKTABLEB, 0); + verify(handler).sendPck("TXB0-------"); + } + + @Test + public void testHandleCommandB1On() throws LcnException { + l.handleCommandOnOff(OnOffType.ON, LcnChannelGroup.KEYLOCKTABLEB, 0); + verify(handler).sendPck("TXB1-------"); + } + + @Test + public void testHandleCommandB8Off() throws LcnException { + l.handleCommandOnOff(OnOffType.OFF, LcnChannelGroup.KEYLOCKTABLEB, 7); + verify(handler).sendPck("TXB-------0"); + } + + @Test + public void testHandleCommandB8On() throws LcnException { + l.handleCommandOnOff(OnOffType.ON, LcnChannelGroup.KEYLOCKTABLEB, 7); + verify(handler).sendPck("TXB-------1"); + } + + @Test + public void testHandleCommandC1Off() throws LcnException { + l.handleCommandOnOff(OnOffType.OFF, LcnChannelGroup.KEYLOCKTABLEC, 0); + verify(handler).sendPck("TXC0-------"); + } + + @Test + public void testHandleCommandC1On() throws LcnException { + l.handleCommandOnOff(OnOffType.ON, LcnChannelGroup.KEYLOCKTABLEC, 0); + verify(handler).sendPck("TXC1-------"); + } + + @Test + public void testHandleCommandC8Off() throws LcnException { + l.handleCommandOnOff(OnOffType.OFF, LcnChannelGroup.KEYLOCKTABLEC, 7); + verify(handler).sendPck("TXC-------0"); + } + + @Test + public void testHandleCommandC8On() throws LcnException { + l.handleCommandOnOff(OnOffType.ON, LcnChannelGroup.KEYLOCKTABLEC, 7); + verify(handler).sendPck("TXC-------1"); + } + + @Test + public void testHandleCommandD1Off() throws LcnException { + l.handleCommandOnOff(OnOffType.OFF, LcnChannelGroup.KEYLOCKTABLED, 0); + verify(handler).sendPck("TXD0-------"); + } + + @Test + public void testHandleCommandD1On() throws LcnException { + l.handleCommandOnOff(OnOffType.ON, LcnChannelGroup.KEYLOCKTABLED, 0); + verify(handler).sendPck("TXD1-------"); + } + + @Test + public void testHandleCommandD8Off() throws LcnException { + l.handleCommandOnOff(OnOffType.OFF, LcnChannelGroup.KEYLOCKTABLED, 7); + verify(handler).sendPck("TXD-------0"); + } + + @Test + public void testHandleCommandD8On() throws LcnException { + l.handleCommandOnOff(OnOffType.ON, LcnChannelGroup.KEYLOCKTABLED, 7); + verify(handler).sendPck("TXD-------1"); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleLedSubHandlerTest.java b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleLedSubHandlerTest.java new file mode 100644 index 0000000000000..aea07c1f19923 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleLedSubHandlerTest.java @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import static org.mockito.Mockito.verify; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.OnOffType; +import org.eclipse.smarthome.core.library.types.StringType; +import org.junit.Before; +import org.junit.Test; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnDefs; +import org.openhab.binding.lcn.internal.common.LcnException; + +/** + * Test class. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleLedSubHandlerTest extends AbstractTestLcnModuleSubHandler { + private @NonNullByDefault({}) LcnModuleLedSubHandler l; + + @Override + @Before + public void setUp() { + super.setUp(); + + l = new LcnModuleLedSubHandler(handler, info); + } + + @Test + public void testHandleCommandLed1Off() throws LcnException { + l.handleCommandString(new StringType(LcnDefs.LedStatus.OFF.name()), 0); + verify(handler).sendPck("LA001A"); + } + + @Test + public void testHandleCommandLed1On() throws LcnException { + l.handleCommandString(new StringType(LcnDefs.LedStatus.ON.name()), 0); + verify(handler).sendPck("LA001E"); + } + + @Test + public void testHandleCommandLed1Blink() throws LcnException { + l.handleCommandString(new StringType(LcnDefs.LedStatus.BLINK.name()), 0); + verify(handler).sendPck("LA001B"); + } + + @Test + public void testHandleCommandLed1Flicker() throws LcnException { + l.handleCommandString(new StringType(LcnDefs.LedStatus.FLICKER.name()), 0); + verify(handler).sendPck("LA001F"); + } + + @Test + public void testHandleCommandLed12On() throws LcnException { + l.handleCommandString(new StringType(LcnDefs.LedStatus.ON.name()), 11); + verify(handler).sendPck("LA012E"); + } + + @Test + public void testHandleOnOffCommandLed1Off() throws LcnException { + l.handleCommandOnOff(OnOffType.OFF, LcnChannelGroup.LED, 0); + verify(handler).sendPck("LA001A"); + } + + @Test + public void testHandleOnOffCommandLed1On() throws LcnException { + l.handleCommandOnOff(OnOffType.ON, LcnChannelGroup.LED, 0); + verify(handler).sendPck("LA001E"); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleLogicSubHandlerTest.java b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleLogicSubHandlerTest.java new file mode 100644 index 0000000000000..106fd18f6d45d --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleLogicSubHandlerTest.java @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import static org.mockito.Mockito.verify; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.StringType; +import org.junit.Before; +import org.junit.Test; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; + +/** + * Test class. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleLogicSubHandlerTest extends AbstractTestLcnModuleSubHandler { + private static final StringType ON = new StringType("ON"); + private static final StringType OFF = new StringType("OFF"); + private static final StringType BLINK = new StringType("BLINK"); + private static final StringType FLICKER = new StringType("FLICKER"); + private static final StringType NOT = new StringType("NOT"); + private static final StringType OR = new StringType("OR"); + private static final StringType AND = new StringType("AND"); + private @NonNullByDefault({}) LcnModuleLogicSubHandler l; + + @Override + @Before + public void setUp() { + super.setUp(); + + l = new LcnModuleLogicSubHandler(handler, info); + } + + @Test + public void testStatusLedOffLogicNot() { + l.tryParse("=M000005.TLAAAAAAAAAAAANNNN"); + verify(handler).updateChannel(LcnChannelGroup.LED, "1", OFF); + verify(handler).updateChannel(LcnChannelGroup.LED, "2", OFF); + verify(handler).updateChannel(LcnChannelGroup.LED, "3", OFF); + verify(handler).updateChannel(LcnChannelGroup.LED, "4", OFF); + verify(handler).updateChannel(LcnChannelGroup.LED, "5", OFF); + verify(handler).updateChannel(LcnChannelGroup.LED, "6", OFF); + verify(handler).updateChannel(LcnChannelGroup.LED, "7", OFF); + verify(handler).updateChannel(LcnChannelGroup.LED, "8", OFF); + verify(handler).updateChannel(LcnChannelGroup.LED, "9", OFF); + verify(handler).updateChannel(LcnChannelGroup.LED, "10", OFF); + verify(handler).updateChannel(LcnChannelGroup.LED, "11", OFF); + verify(handler).updateChannel(LcnChannelGroup.LED, "12", OFF); + verify(handler).updateChannel(LcnChannelGroup.LOGIC, "1", NOT); + verify(handler).updateChannel(LcnChannelGroup.LOGIC, "2", NOT); + verify(handler).updateChannel(LcnChannelGroup.LOGIC, "3", NOT); + verify(handler).updateChannel(LcnChannelGroup.LOGIC, "4", NOT); + } + + @Test + public void testStatusMixed() { + l.tryParse("=M000005.TLAEBFAAAAAAAFNVNT"); + verify(handler).updateChannel(LcnChannelGroup.LED, "1", OFF); + verify(handler).updateChannel(LcnChannelGroup.LED, "2", ON); + verify(handler).updateChannel(LcnChannelGroup.LED, "3", BLINK); + verify(handler).updateChannel(LcnChannelGroup.LED, "4", FLICKER); + verify(handler).updateChannel(LcnChannelGroup.LED, "5", OFF); + verify(handler).updateChannel(LcnChannelGroup.LED, "6", OFF); + verify(handler).updateChannel(LcnChannelGroup.LED, "7", OFF); + verify(handler).updateChannel(LcnChannelGroup.LED, "8", OFF); + verify(handler).updateChannel(LcnChannelGroup.LED, "9", OFF); + verify(handler).updateChannel(LcnChannelGroup.LED, "10", OFF); + verify(handler).updateChannel(LcnChannelGroup.LED, "11", OFF); + verify(handler).updateChannel(LcnChannelGroup.LED, "12", FLICKER); + verify(handler).updateChannel(LcnChannelGroup.LOGIC, "1", NOT); + verify(handler).updateChannel(LcnChannelGroup.LOGIC, "2", AND); + verify(handler).updateChannel(LcnChannelGroup.LOGIC, "3", NOT); + verify(handler).updateChannel(LcnChannelGroup.LOGIC, "4", OR); + } + + @Test + public void testStatusSingleLogic1Not() { + l.tryParse("=M000005S1000"); + verify(handler).updateChannel(LcnChannelGroup.LOGIC, "1", NOT); + } + + @Test + public void testStatusSingleLogic4Or() { + l.tryParse("=M000005S4025"); + verify(handler).updateChannel(LcnChannelGroup.LOGIC, "4", OR); + } + + @Test + public void testStatusSingleLogic3And() { + l.tryParse("=M000005S3050"); + verify(handler).updateChannel(LcnChannelGroup.LOGIC, "3", AND); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleOutputSubHandlerTest.java b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleOutputSubHandlerTest.java new file mode 100644 index 0000000000000..e18eebff250cc --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleOutputSubHandlerTest.java @@ -0,0 +1,198 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import static org.mockito.Mockito.verify; + +import java.math.BigDecimal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.OnOffType; +import org.eclipse.smarthome.core.library.types.PercentType; +import org.junit.Before; +import org.junit.Test; +import org.openhab.binding.lcn.internal.common.DimmerOutputCommand; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnDefs; +import org.openhab.binding.lcn.internal.common.LcnException; + +/** + * Test class. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleOutputSubHandlerTest extends AbstractTestLcnModuleSubHandler { + private @NonNullByDefault({}) LcnModuleOutputSubHandler l; + + @Override + @Before + public void setUp() { + super.setUp(); + + l = new LcnModuleOutputSubHandler(handler, info); + } + + @Test + public void testStatusOutput1OffPercent() { + l.tryParse("=M000005A1000"); + verify(handler).updateChannel(LcnChannelGroup.OUTPUT, "1", new PercentType(0)); + } + + @Test + public void testStatusOutput2OffPercent() { + l.tryParse("=M000005A2000"); + verify(handler).updateChannel(LcnChannelGroup.OUTPUT, "2", new PercentType(0)); + } + + @Test + public void testStatusOutput1OffNative() { + l.tryParse("=M000005O1000"); + verify(handler).updateChannel(LcnChannelGroup.OUTPUT, "1", new PercentType(0)); + } + + @Test + public void testStatusOutput2OffNative() { + l.tryParse("=M000005O2000"); + verify(handler).updateChannel(LcnChannelGroup.OUTPUT, "2", new PercentType(0)); + } + + @Test + public void testStatusOutput1OnPercent() { + l.tryParse("=M000005A1100"); + verify(handler).updateChannel(LcnChannelGroup.OUTPUT, "1", new PercentType(100)); + } + + @Test + public void testStatusOutput2OnPercent() { + l.tryParse("=M000005A2100"); + verify(handler).updateChannel(LcnChannelGroup.OUTPUT, "2", new PercentType(100)); + } + + @Test + public void testStatusOutput1OnNative() { + l.tryParse("=M000005O1200"); + verify(handler).updateChannel(LcnChannelGroup.OUTPUT, "1", new PercentType(100)); + } + + @Test + public void testStatusOutput2OnNative() { + l.tryParse("=M000005O2200"); + verify(handler).updateChannel(LcnChannelGroup.OUTPUT, "2", new PercentType(100)); + } + + @Test + public void testStatusOutput2On50Percent() { + l.tryParse("=M000005A2050"); + verify(handler).updateChannel(LcnChannelGroup.OUTPUT, "2", new PercentType(50)); + } + + @Test + public void testStatusOutput1On50Native() { + l.tryParse("=M000005O1100"); + verify(handler).updateChannel(LcnChannelGroup.OUTPUT, "1", new PercentType(50)); + } + + @Test + public void testHandleCommandOutput1On() throws LcnException { + l.handleCommandOnOff(OnOffType.ON, LcnChannelGroup.OUTPUT, 0); + verify(handler).sendPck("A1DI100000"); + } + + @Test + public void testHandleCommandOutput2On() throws LcnException { + l.handleCommandOnOff(OnOffType.ON, LcnChannelGroup.OUTPUT, 1); + verify(handler).sendPck("A2DI100000"); + } + + @Test + public void testHandleCommandOutput1Off() throws LcnException { + l.handleCommandOnOff(OnOffType.OFF, LcnChannelGroup.OUTPUT, 0); + verify(handler).sendPck("A1DI000000"); + } + + @Test + public void testHandleCommandOutput2Off() throws LcnException { + l.handleCommandOnOff(OnOffType.OFF, LcnChannelGroup.OUTPUT, 1); + verify(handler).sendPck("A2DI000000"); + } + + @Test + public void testHandleCommandOutput1Percent10() throws LcnException { + l.handleCommandPercent(new PercentType(99), LcnChannelGroup.OUTPUT, 0); + verify(handler).sendPck("A1DI099000"); + } + + @Test + public void testHandleCommandOutput2Percent1() throws LcnException { + l.handleCommandPercent(new PercentType(1), LcnChannelGroup.OUTPUT, 1); + verify(handler).sendPck("A2DI001000"); + } + + @Test + public void testHandleCommandOutput1Percent995() throws LcnException { + l.handleCommandPercent(new PercentType(BigDecimal.valueOf(99.5)), LcnChannelGroup.OUTPUT, 0); + verify(handler).sendPck("O1DI199000"); + } + + @Test + public void testHandleCommandOutput2Percent05() throws LcnException { + l.handleCommandPercent(new PercentType(BigDecimal.valueOf(0.5)), LcnChannelGroup.OUTPUT, 1); + verify(handler).sendPck("O2DI001000"); + } + + @Test + public void testHandleCommandDimmerOutputAll60FixedRamp() throws LcnException { + l.handleCommandDimmerOutput(new DimmerOutputCommand(BigDecimal.valueOf(60), true, false, LcnDefs.FIXED_RAMP_MS), + 0); + verify(handler).sendPck("AH060"); + } + + @Test + public void testHandleCommandDimmerOutputAll40CustomRamp() throws LcnException { + l.handleCommandDimmerOutput(new DimmerOutputCommand(BigDecimal.valueOf(40), true, false, 1000), 0); + verify(handler).sendPck("OY080080080080004"); + } + + @Test + public void testHandleCommandDimmerOutput12Value100FixedRamp() throws LcnException { + l.handleCommandDimmerOutput( + new DimmerOutputCommand(BigDecimal.valueOf(100), false, true, LcnDefs.FIXED_RAMP_MS), 0); + verify(handler).sendPck("X2001200200"); + } + + @Test + public void testHandleCommandDimmerOutput12Value0FixedRamp() throws LcnException { + l.handleCommandDimmerOutput(new DimmerOutputCommand(BigDecimal.valueOf(0), false, true, LcnDefs.FIXED_RAMP_MS), + 0); + verify(handler).sendPck("X2001000000"); + } + + @Test + public void testHandleCommandDimmerOutput12Value100NoRamp() throws LcnException { + l.handleCommandDimmerOutput(new DimmerOutputCommand(BigDecimal.valueOf(100), false, true, 0), 0); + verify(handler).sendPck("X2001253253"); + } + + @Test + public void testHandleCommandDimmerOutput12Value0NoRamp() throws LcnException { + l.handleCommandDimmerOutput(new DimmerOutputCommand(BigDecimal.valueOf(0), false, true, 0), 0); + verify(handler).sendPck("X2001252252"); + } + + @Test + public void testHandleCommandDimmerOutput12Value40() throws LcnException { + l.handleCommandDimmerOutput(new DimmerOutputCommand(BigDecimal.valueOf(40), false, true, 0), 0); + verify(handler).sendPck("AY040040"); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRelaySubHandlerTest.java b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRelaySubHandlerTest.java new file mode 100644 index 0000000000000..25c181309b02e --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRelaySubHandlerTest.java @@ -0,0 +1,114 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import static org.mockito.Mockito.verify; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.OnOffType; +import org.eclipse.smarthome.core.library.types.PercentType; +import org.junit.Before; +import org.junit.Test; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnException; + +/** + * Test class. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleRelaySubHandlerTest extends AbstractTestLcnModuleSubHandler { + private @NonNullByDefault({}) LcnModuleRelaySubHandler l; + + @Override + @Before + public void setUp() { + super.setUp(); + + l = new LcnModuleRelaySubHandler(handler, info); + } + + @Test + public void testStatusAllOff() { + l.tryParse("=M000005Rx000"); + verify(handler).updateChannel(LcnChannelGroup.RELAY, "1", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.RELAY, "2", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.RELAY, "3", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.RELAY, "4", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.RELAY, "5", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.RELAY, "6", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.RELAY, "7", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.RELAY, "8", OnOffType.OFF); + } + + @Test + public void testStatusAllOn() { + l.tryParse("=M000005Rx255"); + verify(handler).updateChannel(LcnChannelGroup.RELAY, "1", OnOffType.ON); + verify(handler).updateChannel(LcnChannelGroup.RELAY, "2", OnOffType.ON); + verify(handler).updateChannel(LcnChannelGroup.RELAY, "3", OnOffType.ON); + verify(handler).updateChannel(LcnChannelGroup.RELAY, "5", OnOffType.ON); + verify(handler).updateChannel(LcnChannelGroup.RELAY, "6", OnOffType.ON); + verify(handler).updateChannel(LcnChannelGroup.RELAY, "7", OnOffType.ON); + verify(handler).updateChannel(LcnChannelGroup.RELAY, "8", OnOffType.ON); + } + + @Test + public void testStatusRelay1Relay7On() { + l.tryParse("=M000005Rx065"); + verify(handler).updateChannel(LcnChannelGroup.RELAY, "1", OnOffType.ON); + verify(handler).updateChannel(LcnChannelGroup.RELAY, "2", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.RELAY, "3", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.RELAY, "4", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.RELAY, "5", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.RELAY, "6", OnOffType.OFF); + verify(handler).updateChannel(LcnChannelGroup.RELAY, "7", OnOffType.ON); + verify(handler).updateChannel(LcnChannelGroup.RELAY, "8", OnOffType.OFF); + } + + @Test + public void testHandleCommandRelay1On() throws LcnException { + l.handleCommandOnOff(OnOffType.ON, LcnChannelGroup.RELAY, 0); + verify(handler).sendPck("R81-------"); + } + + @Test + public void testHandleCommandRelay8On() throws LcnException { + l.handleCommandOnOff(OnOffType.ON, LcnChannelGroup.RELAY, 7); + verify(handler).sendPck("R8-------1"); + } + + @Test + public void testHandleCommandRelay1Off() throws LcnException { + l.handleCommandOnOff(OnOffType.OFF, LcnChannelGroup.RELAY, 0); + verify(handler).sendPck("R80-------"); + } + + @Test + public void testHandleCommandRelay8Off() throws LcnException { + l.handleCommandOnOff(OnOffType.OFF, LcnChannelGroup.RELAY, 7); + verify(handler).sendPck("R8-------0"); + } + + @Test + public void testHandleCommandRelay8Percent1() throws LcnException { + l.handleCommandPercent(new PercentType(1), LcnChannelGroup.RELAY, 7); + verify(handler).sendPck("R8-------1"); + } + + @Test + public void testHandleCommandRelay1Percent0() throws LcnException { + l.handleCommandPercent(PercentType.ZERO, LcnChannelGroup.RELAY, 0); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRollershutterOutputSubHandlerTest.java b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRollershutterOutputSubHandlerTest.java new file mode 100644 index 0000000000000..3809f0a099788 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRollershutterOutputSubHandlerTest.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import static org.mockito.Mockito.verify; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.StopMoveType; +import org.eclipse.smarthome.core.library.types.UpDownType; +import org.junit.Before; +import org.junit.Test; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnException; + +/** + * Test class. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleRollershutterOutputSubHandlerTest extends AbstractTestLcnModuleSubHandler { + private @NonNullByDefault({}) LcnModuleRollershutterOutputSubHandler l; + + @Override + @Before + public void setUp() { + super.setUp(); + + l = new LcnModuleRollershutterOutputSubHandler(handler, info); + } + + @Test + public void testUp() throws LcnException { + l.handleCommandUpDown(UpDownType.UP, LcnChannelGroup.ROLLERSHUTTEROUTPUT, 0); + verify(handler).sendPck("A1DI100008"); + } + + @Test + public void testDown() throws LcnException { + l.handleCommandUpDown(UpDownType.DOWN, LcnChannelGroup.ROLLERSHUTTEROUTPUT, 0); + verify(handler).sendPck("A2DI100008"); + } + + @Test + public void testStop() throws LcnException { + l.handleCommandStopMove(StopMoveType.STOP, LcnChannelGroup.ROLLERSHUTTEROUTPUT, 0); + verify(handler).sendPck("A1DI000000"); + verify(handler).sendPck("A2DI000000"); + } + + @Test + public void testMove() throws LcnException { + l.handleCommandStopMove(StopMoveType.MOVE, LcnChannelGroup.ROLLERSHUTTEROUTPUT, 0); + verify(handler).sendPck("A2DI100008"); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRollershutterRelaySubHandlerTest.java b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRollershutterRelaySubHandlerTest.java new file mode 100644 index 0000000000000..99816dc13d055 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRollershutterRelaySubHandlerTest.java @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import static org.mockito.Mockito.verify; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.StopMoveType; +import org.eclipse.smarthome.core.library.types.UpDownType; +import org.junit.Before; +import org.junit.Test; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnException; + +/** + * Test class. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleRollershutterRelaySubHandlerTest extends AbstractTestLcnModuleSubHandler { + private @NonNullByDefault({}) LcnModuleRollershutterRelaySubHandler l; + + @Override + @Before + public void setUp() { + super.setUp(); + + l = new LcnModuleRollershutterRelaySubHandler(handler, info); + } + + @Test + public void testUp1() throws LcnException { + l.handleCommandUpDown(UpDownType.UP, LcnChannelGroup.ROLLERSHUTTERRELAY, 0); + verify(handler).sendPck("R810------"); + } + + @Test + public void testUp4() throws LcnException { + l.handleCommandUpDown(UpDownType.UP, LcnChannelGroup.ROLLERSHUTTERRELAY, 3); + verify(handler).sendPck("R8------10"); + } + + @Test + public void testDown1() throws LcnException { + l.handleCommandUpDown(UpDownType.DOWN, LcnChannelGroup.ROLLERSHUTTERRELAY, 0); + verify(handler).sendPck("R811------"); + } + + @Test + public void testDown4() throws LcnException { + l.handleCommandUpDown(UpDownType.DOWN, LcnChannelGroup.ROLLERSHUTTERRELAY, 3); + verify(handler).sendPck("R8------11"); + } + + @Test + public void testStop1() throws LcnException { + l.handleCommandStopMove(StopMoveType.STOP, LcnChannelGroup.ROLLERSHUTTERRELAY, 0); + verify(handler).sendPck("R80-------"); + } + + @Test + public void testStop4() throws LcnException { + l.handleCommandStopMove(StopMoveType.STOP, LcnChannelGroup.ROLLERSHUTTERRELAY, 3); + verify(handler).sendPck("R8------0-"); + } + + @Test + public void testMove1() throws LcnException { + l.handleCommandStopMove(StopMoveType.MOVE, LcnChannelGroup.ROLLERSHUTTERRELAY, 0); + verify(handler).sendPck("R81-------"); + } + + @Test + public void testMove4() throws LcnException { + l.handleCommandStopMove(StopMoveType.MOVE, LcnChannelGroup.ROLLERSHUTTERRELAY, 3); + verify(handler).sendPck("R8------1-"); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRvarLockSubHandlerTest.java b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRvarLockSubHandlerTest.java new file mode 100644 index 0000000000000..8acf7e33e4c63 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRvarLockSubHandlerTest.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import static org.mockito.Mockito.verify; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.OnOffType; +import org.junit.Before; +import org.junit.Test; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnException; + +/** + * Test class. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleRvarLockSubHandlerTest extends AbstractTestLcnModuleSubHandler { + private @NonNullByDefault({}) LcnModuleRvarLockSubHandler l; + + @Override + @Before + public void setUp() { + super.setUp(); + + l = new LcnModuleRvarLockSubHandler(handler, info); + } + + @Test + public void testLock1() throws LcnException { + l.handleCommandOnOff(OnOffType.ON, LcnChannelGroup.RVARLOCK, 0); + verify(handler).sendPck("REAXS"); + } + + @Test + public void testLock2() throws LcnException { + l.handleCommandOnOff(OnOffType.ON, LcnChannelGroup.RVARLOCK, 1); + verify(handler).sendPck("REBXS"); + } + + @Test + public void testUnlock1() throws LcnException { + l.handleCommandOnOff(OnOffType.OFF, LcnChannelGroup.RVARLOCK, 0); + verify(handler).sendPck("REAXA"); + } + + @Test + public void testUnlock2() throws LcnException { + l.handleCommandOnOff(OnOffType.OFF, LcnChannelGroup.RVARLOCK, 1); + verify(handler).sendPck("REBXA"); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRvarSetpointSubHandlerTest.java b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRvarSetpointSubHandlerTest.java new file mode 100644 index 0000000000000..be0d3df53395b --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleRvarSetpointSubHandlerTest.java @@ -0,0 +1,138 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import static org.mockito.Mockito.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.DecimalType; +import org.eclipse.smarthome.core.library.types.OnOffType; +import org.eclipse.smarthome.core.library.types.StringType; +import org.junit.Before; +import org.junit.Test; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnException; +import org.openhab.binding.lcn.internal.common.Variable; + +/** + * Test class. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleRvarSetpointSubHandlerTest extends AbstractTestLcnModuleSubHandler { + private @NonNullByDefault({}) LcnModuleRvarSetpointSubHandler l; + + @Override + @Before + public void setUp() { + super.setUp(); + + l = new LcnModuleRvarSetpointSubHandler(handler, info); + } + + @Test + public void testhandleCommandRvar1Positive() throws LcnException { + when(info.hasExtendedMeasurementProcessing()).thenReturn(true); + l.handleCommandDecimal(new DecimalType(1000), LcnChannelGroup.RVARSETPOINT, 0); + verify(handler).sendPck("X2030032000"); + } + + @Test + public void testhandleCommandRvar2Positive() throws LcnException { + when(info.hasExtendedMeasurementProcessing()).thenReturn(true); + l.handleCommandDecimal(new DecimalType(1100), LcnChannelGroup.RVARSETPOINT, 1); + verify(handler).sendPck("X2030096100"); + } + + @Test + public void testhandleCommandRvar1Negative() throws LcnException { + when(info.hasExtendedMeasurementProcessing()).thenReturn(true); + l.handleCommandDecimal(new DecimalType(0), LcnChannelGroup.RVARSETPOINT, 0); + verify(handler).sendPck("X2030043232"); + } + + @Test + public void testhandleCommandRvar2Negative() throws LcnException { + when(info.hasExtendedMeasurementProcessing()).thenReturn(true); + l.handleCommandDecimal(new DecimalType(999), LcnChannelGroup.RVARSETPOINT, 1); + verify(handler).sendPck("X2030104001"); + } + + @Test + public void testhandleCommandRvar1PositiveLegacy() throws LcnException { + when(info.getVariableValue(Variable.RVARSETPOINT1)).thenReturn(1000L); + when(info.hasExtendedMeasurementProcessing()).thenReturn(false); + l.handleCommandDecimal(new DecimalType(1100), LcnChannelGroup.RVARSETPOINT, 0); + verify(handler).sendPck("REASA+100"); + } + + @Test + public void testhandleCommandRvar2PositiveLegacy() throws LcnException { + when(info.getVariableValue(Variable.RVARSETPOINT2)).thenReturn(1000L); + when(info.hasExtendedMeasurementProcessing()).thenReturn(false); + l.handleCommandDecimal(new DecimalType(1100), LcnChannelGroup.RVARSETPOINT, 1); + verify(handler).sendPck("REBSA+100"); + } + + @Test + public void testhandleCommandRvar1NegativeLegacy() throws LcnException { + when(info.getVariableValue(Variable.RVARSETPOINT1)).thenReturn(1000L); + when(info.hasExtendedMeasurementProcessing()).thenReturn(false); + l.handleCommandDecimal(new DecimalType(900), LcnChannelGroup.RVARSETPOINT, 0); + verify(handler).sendPck("REASA-100"); + } + + @Test + public void testhandleCommandRvar2NegativeLegacy() throws LcnException { + when(info.getVariableValue(Variable.RVARSETPOINT2)).thenReturn(1000L); + when(info.hasExtendedMeasurementProcessing()).thenReturn(false); + l.handleCommandDecimal(new DecimalType(900), LcnChannelGroup.RVARSETPOINT, 1); + verify(handler).sendPck("REBSA-100"); + } + + @Test + public void testRvar1() { + l.tryParse("=M000005.S11234"); + verify(handler).updateChannel(LcnChannelGroup.RVARSETPOINT, "1", new DecimalType(1234)); + verify(handler).updateChannel(LcnChannelGroup.RVARLOCK, "1", OnOffType.OFF); + } + + @Test + public void testRvar2() { + l.tryParse("=M000005.S21234"); + verify(handler).updateChannel(LcnChannelGroup.RVARSETPOINT, "2", new DecimalType(1234)); + verify(handler).updateChannel(LcnChannelGroup.RVARLOCK, "2", OnOffType.OFF); + } + + @Test + public void testRvar1SensorDefective() { + l.tryParse("=M000005.S132512"); + verify(handler).updateChannel(LcnChannelGroup.RVARSETPOINT, "1", new StringType("DEFECTIVE")); + verify(handler).updateChannel(LcnChannelGroup.RVARLOCK, "1", OnOffType.OFF); + } + + @Test + public void testRvar1Locked() { + l.tryParse("=M000005.S134002"); + verify(handler).updateChannel(LcnChannelGroup.RVARSETPOINT, "1", new DecimalType(1234)); + verify(handler).updateChannel(LcnChannelGroup.RVARLOCK, "1", OnOffType.ON); + } + + @Test + public void testRvar2Locked() { + l.tryParse("=M000005.S234002"); + verify(handler).updateChannel(LcnChannelGroup.RVARSETPOINT, "2", new DecimalType(1234)); + verify(handler).updateChannel(LcnChannelGroup.RVARLOCK, "2", OnOffType.ON); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleS0CounterSubHandlerTest.java b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleS0CounterSubHandlerTest.java new file mode 100644 index 0000000000000..4f5f900c17e35 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleS0CounterSubHandlerTest.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import static org.mockito.Mockito.verify; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.DecimalType; +import org.junit.Before; +import org.junit.Test; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; + +/** + * Test class. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleS0CounterSubHandlerTest extends AbstractTestLcnModuleSubHandler { + private @NonNullByDefault({}) LcnModuleS0CounterSubHandler l; + + @Override + @Before + public void setUp() { + super.setUp(); + + l = new LcnModuleS0CounterSubHandler(handler, info); + } + + @Test + public void testZero() { + l.tryParse("=M000005.C10"); + verify(handler).updateChannel(LcnChannelGroup.S0INPUT, "1", new DecimalType(0)); + } + + @Test + public void testMaxValue() { + l.tryParse("=M000005.C14294967295"); + verify(handler).updateChannel(LcnChannelGroup.S0INPUT, "1", new DecimalType(4294967295L)); + } + + @Test + public void test4() { + l.tryParse("=M000005.C412345"); + verify(handler).updateChannel(LcnChannelGroup.S0INPUT, "4", new DecimalType(12345)); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleThresholdSubHandlerTest.java b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleThresholdSubHandlerTest.java new file mode 100644 index 0000000000000..ad3ee3bb85ab5 --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleThresholdSubHandlerTest.java @@ -0,0 +1,133 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import static org.mockito.Mockito.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.DecimalType; +import org.junit.Before; +import org.junit.Test; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnException; +import org.openhab.binding.lcn.internal.common.Variable; + +/** + * Test class. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleThresholdSubHandlerTest extends AbstractTestLcnModuleSubHandler { + private @NonNullByDefault({}) LcnModuleThresholdSubHandler l; + + @Override + @Before + public void setUp() { + super.setUp(); + + l = new LcnModuleThresholdSubHandler(handler, info); + } + + @Test + public void testThreshold11() { + l.tryParse("=M000005.T1112345"); + verify(handler).updateChannel(LcnChannelGroup.THRESHOLDREGISTER1, "1", new DecimalType(12345)); + } + + @Test + public void testThreshold14() { + l.tryParse("=M000005.T140"); + verify(handler).updateChannel(LcnChannelGroup.THRESHOLDREGISTER1, "4", new DecimalType(0)); + } + + @Test + public void testThreshold41() { + l.tryParse("=M000005.T4112345"); + verify(handler).updateChannel(LcnChannelGroup.THRESHOLDREGISTER4, "1", new DecimalType(12345)); + } + + @Test + public void testThresholdLegacy() { + l.tryParse("=M000005.S1123451123411123000000000112345"); + verify(handler).updateChannel(LcnChannelGroup.THRESHOLDREGISTER1, "1", new DecimalType(12345)); + verify(handler).updateChannel(LcnChannelGroup.THRESHOLDREGISTER1, "2", new DecimalType(11234)); + verify(handler).updateChannel(LcnChannelGroup.THRESHOLDREGISTER1, "3", new DecimalType(11123)); + verify(handler).updateChannel(LcnChannelGroup.THRESHOLDREGISTER1, "4", new DecimalType(0)); + verify(handler).updateChannel(LcnChannelGroup.THRESHOLDREGISTER1, "5", new DecimalType(1)); + } + + @Test + public void testhandleCommandThreshold11Positive() throws LcnException { + when(info.getVariableValue(Variable.THRESHOLDREGISTER11)).thenReturn(1000L); + when(info.hasExtendedMeasurementProcessing()).thenReturn(true); + l.handleCommandDecimal(new DecimalType(1100), LcnChannelGroup.THRESHOLDREGISTER1, 0); + verify(handler).sendPck("SSR0100AR11"); + } + + @Test + public void testhandleCommandThreshold11Negative() throws LcnException { + when(info.getVariableValue(Variable.THRESHOLDREGISTER11)).thenReturn(1000L); + when(info.hasExtendedMeasurementProcessing()).thenReturn(true); + l.handleCommandDecimal(new DecimalType(900), LcnChannelGroup.THRESHOLDREGISTER1, 0); + verify(handler).sendPck("SSR0100SR11"); + } + + @Test + public void testhandleCommandThreshold44Positive() throws LcnException { + when(info.getVariableValue(Variable.THRESHOLDREGISTER44)).thenReturn(1000L); + when(info.hasExtendedMeasurementProcessing()).thenReturn(true); + l.handleCommandDecimal(new DecimalType(1100), LcnChannelGroup.THRESHOLDREGISTER4, 3); + verify(handler).sendPck("SSR0100AR44"); + } + + @Test + public void testhandleCommandThreshold44Negative() throws LcnException { + when(info.getVariableValue(Variable.THRESHOLDREGISTER44)).thenReturn(1000L); + when(info.hasExtendedMeasurementProcessing()).thenReturn(true); + l.handleCommandDecimal(new DecimalType(900), LcnChannelGroup.THRESHOLDREGISTER4, 3); + verify(handler).sendPck("SSR0100SR44"); + } + + @Test + public void testhandleCommandThreshold11LegacyPositive() throws LcnException { + when(info.getVariableValue(Variable.THRESHOLDREGISTER11)).thenReturn(1000L); + when(info.hasExtendedMeasurementProcessing()).thenReturn(false); + l.handleCommandDecimal(new DecimalType(1100), LcnChannelGroup.THRESHOLDREGISTER1, 0); + verify(handler).sendPck("SSR0100A10000"); + } + + @Test + public void testhandleCommandThreshold11LegacyNegative() throws LcnException { + when(info.getVariableValue(Variable.THRESHOLDREGISTER11)).thenReturn(1000L); + when(info.hasExtendedMeasurementProcessing()).thenReturn(false); + l.handleCommandDecimal(new DecimalType(900), LcnChannelGroup.THRESHOLDREGISTER1, 0); + verify(handler).sendPck("SSR0100S10000"); + } + + @Test + public void testhandleCommandThreshold14Legacy() throws LcnException { + when(info.getVariableValue(Variable.THRESHOLDREGISTER14)).thenReturn(1000L); + when(info.hasExtendedMeasurementProcessing()).thenReturn(false); + l.handleCommandDecimal(new DecimalType(1100), LcnChannelGroup.THRESHOLDREGISTER1, 3); + verify(handler).sendPck("SSR0100A00010"); + } + + @Test + public void testhandleCommandThreshold15Legacy() throws LcnException { + when(info.getVariableValue(Variable.THRESHOLDREGISTER15)).thenReturn(1000L); + when(info.hasExtendedMeasurementProcessing()).thenReturn(false); + l.handleCommandDecimal(new DecimalType(1100), LcnChannelGroup.THRESHOLDREGISTER1, 4); + verify(handler).sendPck("SSR0100A00001"); + } +} diff --git a/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleVariableSubHandlerTest.java b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleVariableSubHandlerTest.java new file mode 100644 index 0000000000000..de976ee4acb8d --- /dev/null +++ b/bundles/org.openhab.binding.lcn/src/test/java/org/openhab/binding/lcn/internal/subhandler/LcnModuleVariableSubHandlerTest.java @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.lcn.internal.subhandler; + +import static org.mockito.Mockito.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.types.DecimalType; +import org.eclipse.smarthome.core.library.types.StringType; +import org.junit.Before; +import org.junit.Test; +import org.openhab.binding.lcn.internal.common.LcnChannelGroup; +import org.openhab.binding.lcn.internal.common.LcnException; +import org.openhab.binding.lcn.internal.common.Variable; + +/** + * Test class. + * + * @author Fabian Wolter - Initial contribution + */ +@NonNullByDefault +public class LcnModuleVariableSubHandlerTest extends AbstractTestLcnModuleSubHandler { + private @NonNullByDefault({}) LcnModuleVariableSubHandler l; + + @Override + @Before + public void setUp() { + super.setUp(); + + l = new LcnModuleVariableSubHandler(handler, info); + } + + @Test + public void testStatusVariable1() { + l.tryParse("=M000005.A00112345"); + verify(handler).updateChannel(LcnChannelGroup.VARIABLE, "1", new DecimalType(12345)); + } + + @Test + public void testStatusVariable12() { + l.tryParse("=M000005.A01212345"); + verify(handler).updateChannel(LcnChannelGroup.VARIABLE, "12", new DecimalType(12345)); + } + + @Test + public void testStatusLegacyVariable3() { + when(info.getLastRequestedVarWithoutTypeInResponse()).thenReturn(Variable.VARIABLE3); + l.tryParse("=M000005.12345"); + verify(handler).updateChannel(LcnChannelGroup.VARIABLE, "3", new DecimalType(12345)); + } + + @Test + public void testHandleCommandLegacyTvarPositive() throws LcnException { + when(info.hasExtendedMeasurementProcessing()).thenReturn(false); + when(info.getVariableValue(Variable.VARIABLE1)).thenReturn(1000L); + l.handleCommandDecimal(new DecimalType(1234), LcnChannelGroup.VARIABLE, 0); + verify(handler).sendPck("ZA234"); + } + + @Test + public void testHandleCommandLegacyTvarNegative() throws LcnException { + when(info.hasExtendedMeasurementProcessing()).thenReturn(false); + when(info.getVariableValue(Variable.VARIABLE1)).thenReturn(2000L); + l.handleCommandDecimal(new DecimalType(1100), LcnChannelGroup.VARIABLE, 0); + verify(handler).sendPck("ZS900"); + } + + @Test + public void testStatusVariable10SensorDefective() { + l.tryParse("=M000005.A01032512"); + verify(handler).updateChannel(LcnChannelGroup.VARIABLE, "10", new StringType("DEFECTIVE")); + } + + @Test + public void testStatusVariable8NotConfigured() { + l.tryParse("=M000005.A00865535"); + verify(handler).updateChannel(LcnChannelGroup.VARIABLE, "8", new StringType("Not configured in LCN-PRO")); + } +} diff --git a/bundles/pom.xml b/bundles/pom.xml index 83c0d00f65d47..dec0ca57a7b4e 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -132,6 +132,7 @@ org.openhab.binding.konnected org.openhab.binding.kostalinverter org.openhab.binding.lametrictime + org.openhab.binding.lcn org.openhab.binding.leapmotion org.openhab.binding.lghombot org.openhab.binding.lgtvserial