From ab80718065d94ebd0af961cb4c734a8c0c3675aa Mon Sep 17 00:00:00 2001 From: Michael Weger Date: Sat, 6 Mar 2021 10:02:59 +0100 Subject: [PATCH 1/8] - OH3 migration (search'n'replace) - replaced jmbus lib by source - tookover jrxtx from smartmeter binding open: - org.openhab.binding.wmbus.tools - migration of tests --- .../lib/jmbus-3.1.1.sp2.jar | Bin 95171 -> 0 bytes org.openhab.binding.wmbus/pom.xml | 157 +- .../src/main/feature/feature.xml | 9 + .../src/main/history/dependencies.xml | 9 + .../binding/wmbus/BindingConfiguration.java | 47 +- .../org/openhab/binding/wmbus/RecordType.java | 206 +- .../openhab/binding/wmbus/UnitRegistry.java | 91 +- .../binding/wmbus/WMBusBindingConstants.java | 253 +- .../wmbus/WMBusCompanyIdentifiers.java | 107 +- .../openhab/binding/wmbus/WMBusDevice.java | 234 +- .../binding/wmbus/config/DateFieldMode.java | 50 +- .../binding/wmbus/config/StickModel.java | 52 +- .../wmbus/config/WMBusBridgeConfig.java | 67 +- .../wmbus/config/WMBusSerialBridgeConfig.java | 51 +- .../AbstractWMBusDiscoveryParticipant.java | 110 +- .../openhab/binding/wmbus/device/Meter.java | 92 +- .../binding/wmbus/device/UnknownMeter.java | 151 +- .../generic/DynamicWMBusThingHandler.java | 297 +- .../generic/GenericWMBusThingHandler.java | 151 +- .../device/itron/ItronBindingConstants.java | 157 +- .../itron/ItronConfigStatusDataParser.java | 146 +- .../itron/ItronDiscoveryParticipant.java | 217 +- .../device/itron/ItronHandlerFactory.java | 158 +- .../itron/ItronManufacturerDataParser.java | 149 +- .../itron/ItronSmokeDetectorHandler.java | 336 +- .../binding/wmbus/device/techem/Record.java | 126 +- .../device/techem/TechemBindingConstants.java | 424 +-- .../wmbus/device/techem/TechemDevice.java | 122 +- .../techem/TechemDiscoveryParticipant.java | 301 +- .../device/techem/TechemHandlerFactory.java | 201 +- .../techem/TechemHeatCostAllocator.java | 60 +- .../wmbus/device/techem/TechemHeatMeter.java | 61 +- .../device/techem/TechemSmokeDetector.java | 59 +- .../device/techem/TechemUnknownDevice.java | 60 +- .../wmbus/device/techem/TechemWaterMeter.java | 62 +- .../binding/wmbus/device/techem/Variant.java | 137 +- .../decoder/AbstractTechemFrameDecoder.java | 231 +- .../wmbus/device/techem/decoder/Buffer.java | 333 +- .../decoder/CompositeTechemFrameDecoder.java | 160 +- .../device/techem/decoder/DebugBuffer.java | 224 +- .../techem/decoder/TechemFrameDecoder.java | 43 +- .../techem/decoder/TechemHKVFrameDecoder.java | 126 +- .../TechemHKVRoomTempFrameDecoder.java | 153 +- .../decoder/TechemHeatMeterFrameDecoder.java | 164 +- .../TechemSmokeDetectorFrameDecoder.java | 105 +- .../TechemVariantFrameDecoderSelector.java | 138 +- .../TechemVersionFrameDecoderSelector.java | 78 +- .../decoder/TechemWaterMeterFrameDecoder.java | 139 +- .../techem/handler/TechemMeterHandler.java | 303 +- .../discovery/CompositeMessageListener.java | 117 +- .../wmbus/discovery/DebugMessageListener.java | 132 +- .../discovery/WMBusDiscoveryParticipant.java | 113 +- .../handler/VirtualWMBusBridgeHandler.java | 173 +- .../binding/wmbus/handler/WMBusAdapter.java | 67 +- .../wmbus/handler/WMBusBridgeHandler.java | 419 ++- .../wmbus/handler/WMBusBridgeHandlerBase.java | 554 ++-- .../wmbus/handler/WMBusDeviceHandler.java | 624 ++-- .../wmbus/handler/WMBusMessageListener.java | 75 +- .../internal/DynamicBindingConfiguration.java | 187 +- .../binding/wmbus/internal/HexConverter.java | 64 +- .../internal/WMBusChannelTypeProvider.java | 488 +-- .../wmbus/internal/WMBusException.java | 51 +- .../wmbus/internal/WMBusHandlerFactory.java | 289 +- .../binding/wmbus/internal/WMBusReceiver.java | 195 +- .../discovery/WMBusDiscoveryService.java | 305 +- .../discovery/WMBusDiscoveryService2.java | 408 +-- .../internal/units/CompositeUnitRegistry.java | 197 +- ...eUnitsRegistry.java => UnitsRegistry.java} | 719 ++--- .../mbus/wireless/FilteredKeyStorage.java | 129 +- .../transport/mbus/wireless/KeyStorage.java | 63 +- .../mbus/wireless/MapKeyStorage.java | 99 +- .../main/java/org/openmuc/jmbus/AesCrypt.java | 68 + .../src/main/java/org/openmuc/jmbus/Bcd.java | 91 + .../main/java/org/openmuc/jmbus/CRC16.java | 56 + .../java/org/openmuc/jmbus/DataRecord.java | 2759 +++++++++-------- .../org/openmuc/jmbus/DecodingException.java | 26 + .../java/org/openmuc/jmbus/DeviceType.java | 122 + .../main/java/org/openmuc/jmbus/DlmsUnit.java | 146 + .../org/openmuc/jmbus/EncryptionMode.java | 99 + .../org/openmuc/jmbus/MBusConnection.java | 522 ++++ .../java/org/openmuc/jmbus/MBusMessage.java | 131 + .../openmuc/jmbus/ScanSecondaryAddress.java | 218 ++ .../org/openmuc/jmbus/SecondaryAddress.java | 224 ++ .../jmbus/SecondaryAddressListener.java | 30 + .../openmuc/jmbus/VariableDataStructure.java | 449 +++ .../org/openmuc/jmbus/VerboseMessage.java | 48 + .../openmuc/jmbus/VerboseMessageListener.java | 20 + .../java/org/openmuc/jmbus/package-info.java | 11 + .../openmuc/jmbus/transportlayer/Builder.java | 64 + .../jmbus/transportlayer/SerialBuilder.java | 109 + .../jmbus/transportlayer/SerialLayer.java | 73 + .../jmbus/transportlayer/TcpBuilder.java | 73 + .../jmbus/transportlayer/TcpLayer.java | 98 + .../jmbus/transportlayer/TransportLayer.java | 72 + .../jmbus/transportlayer/package-info.java | 9 + .../wireless/AbstractWMBusConnection.java | 132 + .../jmbus/wireless/HciMessageException.java | 16 + .../jmbus/wireless/MessageReceiver.java | 52 + .../wireless/VirtualWMBusMessageHelper.java | 61 +- .../jmbus/wireless/WMBusConnection.java | 172 + .../jmbus/wireless/WMBusConnectionAmber.java | 222 ++ .../jmbus/wireless/WMBusConnectionImst.java | 369 +++ .../wireless/WMBusConnectionRadioCrafts.java | 229 ++ .../openmuc/jmbus/wireless/WMBusListener.java | 39 + .../openmuc/jmbus/wireless/WMBusMessage.java | 129 + .../org/openmuc/jmbus/wireless/WMBusMode.java | 24 + .../openmuc/jmbus/wireless/package-info.java | 12 + .../main/java/org/openmuc/jrxtx/DataBits.java | 36 + .../java/org/openmuc/jrxtx/FlowControl.java | 29 + .../java/org/openmuc/jrxtx/JRxTxPort.java | 340 ++ .../main/java/org/openmuc/jrxtx/Parity.java | 54 + .../openmuc/jrxtx/PortNotFoundException.java | 20 + .../java/org/openmuc/jrxtx/SerialPort.java | 172 + .../org/openmuc/jrxtx/SerialPortBuilder.java | 142 + .../openmuc/jrxtx/SerialPortException.java | 23 + .../jrxtx/SerialPortTimeoutException.java | 25 + .../main/java/org/openmuc/jrxtx/StopBits.java | 32 + .../resources/ESH-INF/binding/binding.xml | 22 - .../main/resources/ESH-INF/thing/bridge.xml | 103 - .../resources/ESH-INF/thing/channel-types.xml | 157 - .../main/resources/ESH-INF/thing/itron.xml | 417 --- .../main/resources/ESH-INF/thing/techem.xml | 230 -- .../resources/ESH-INF/thing/thing-types.xml | 244 -- .../main/resources/OH-INF/binding/binding.xml | 26 + .../{ESH-INF => OH-INF}/i18n/wmbus.properties | 0 .../i18n/wmbus_xx_XX.properties | 4 + .../main/resources/OH-INF/thing/bridge.xml | 113 + .../resources/OH-INF/thing/channel-types.xml | 159 + .../src/main/resources/OH-INF/thing/itron.xml | 429 +++ .../main/resources/OH-INF/thing/techem.xml | 231 ++ .../resources/OH-INF/thing/thing-types.xml | 248 ++ 131 files changed, 14035 insertions(+), 8771 deletions(-) delete mode 100644 org.openhab.binding.wmbus/lib/jmbus-3.1.1.sp2.jar create mode 100644 org.openhab.binding.wmbus/src/main/feature/feature.xml create mode 100644 org.openhab.binding.wmbus/src/main/history/dependencies.xml rename org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/units/{SmartHomeUnitsRegistry.java => UnitsRegistry.java} (82%) create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/AesCrypt.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/Bcd.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/CRC16.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DecodingException.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DeviceType.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DlmsUnit.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/EncryptionMode.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/MBusConnection.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/MBusMessage.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/ScanSecondaryAddress.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/SecondaryAddress.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/SecondaryAddressListener.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/VariableDataStructure.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/VerboseMessage.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/VerboseMessageListener.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/package-info.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/Builder.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/SerialBuilder.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/SerialLayer.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/TcpBuilder.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/TcpLayer.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/TransportLayer.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/package-info.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/AbstractWMBusConnection.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/HciMessageException.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/MessageReceiver.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnection.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionAmber.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionImst.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionRadioCrafts.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusListener.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusMessage.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusMode.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/package-info.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/DataBits.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/FlowControl.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/JRxTxPort.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/Parity.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/PortNotFoundException.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPort.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPortBuilder.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPortException.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPortTimeoutException.java create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/StopBits.java delete mode 100644 org.openhab.binding.wmbus/src/main/resources/ESH-INF/binding/binding.xml delete mode 100644 org.openhab.binding.wmbus/src/main/resources/ESH-INF/thing/bridge.xml delete mode 100644 org.openhab.binding.wmbus/src/main/resources/ESH-INF/thing/channel-types.xml delete mode 100644 org.openhab.binding.wmbus/src/main/resources/ESH-INF/thing/itron.xml delete mode 100644 org.openhab.binding.wmbus/src/main/resources/ESH-INF/thing/techem.xml delete mode 100644 org.openhab.binding.wmbus/src/main/resources/ESH-INF/thing/thing-types.xml create mode 100644 org.openhab.binding.wmbus/src/main/resources/OH-INF/binding/binding.xml rename org.openhab.binding.wmbus/src/main/resources/{ESH-INF => OH-INF}/i18n/wmbus.properties (100%) rename org.openhab.binding.wmbus/src/main/resources/{ESH-INF => OH-INF}/i18n/wmbus_xx_XX.properties (73%) create mode 100644 org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/bridge.xml create mode 100644 org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/channel-types.xml create mode 100644 org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/itron.xml create mode 100644 org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/techem.xml create mode 100644 org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/thing-types.xml diff --git a/org.openhab.binding.wmbus/lib/jmbus-3.1.1.sp2.jar b/org.openhab.binding.wmbus/lib/jmbus-3.1.1.sp2.jar deleted file mode 100644 index a53dcf1150edbbdd28ea6710296e48afc30a5ad0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 95171 zcmbq(W00m@kY(AnZQHhO+qSFAwrzIlE!(!yWwXob;`TQ)u{%34h>gAR{CFbn&HE?w zoXm4Gm1IG|(14(zpnwh?Smc5J-9iHb1Cke46Q-9|kYJP-R*;quS5;$>m-w0l0t!o( zR~!^V3crzmBg`g{%bAxK>{o%pgo2E#L_Qn|*=PmT-txG-#@OHGh0-Xh+*q2OW_{>w z;w>T9{js=%Dm5a;-2ifsGul4ggIoP5cVss}H0Hz@J=9+C>dYzAP{`nluygf;(`~)f z!5SFUmK}GkI%=j(oQ7-anVUF6Oic;HT|z_rt)$KxGYKf6y5YAN$V#H`;co@gf>*`? zb0cogWDC5SsKm5HHPYkkQMVAx-`h)9X$*7nIIA$rMrLg&VzN5b_%l`3TDpnPPCc5e zje;xdd4j}b3cy#c;og=gT$Q_J%~)5iJk{j0Y<1D5H&cxia(-+4rp9ec?!RZF>(0Z? zTy(9S-3G!A>BbQTz5JHw;&@M6vq zb99a-Jyc!#ox@9Aby{62LkTo^e6x`7AEeA9ng~Lt6z-1PyaFr9LPAw%xb7JN0|AYJ z00AleA1?*`-(CXy>oG?cOGePYXCR^eao^wL|FZsT8@Rvw|JXP>nLF6KoBlu9qWlM2 z8+#LX*Z=;8{}5M1|LG0F=B}bH-cD`|rgp}zu4!tvny8v++nk|rp}=HN(JHECHd6hm zCbqyaNCX_{pzNtK=eC)86Gkj4>{&lPQon)&?AK@xl3T7`z6t^^ZMtsEpu`FjT9V(n zUih9jPILeKo?CMSiZ#_0Er)kihMI0OCXS}niY+EY+@$`aCaN)Hi9;ZcJfeb@kfeHV z-cn=r+6}K*d?sPAt;z0(EY7&Pd@KvEn3m(-;#X!YIH!NX6%G!O@u4`RE^4n!b&M|}2 zcI7b%L4MFxK^Vr3QM+||Win?`pPfTCbvPYwiK-U6uIxI9sq=O)6@AX-s#-`BX~s3= zN78$`z4sCbXuy$=1NqR7%qjm`V_SLn8W`9%*^bP6QFEDX>is|uve^DZG=?esmn+5s z4=eX#Sz-YU-F(&y+`}K!k^{>@j|Ha6ecV+wp_EqbIU&;9tA^^D6P&Jxa7(72$<1xu zBuSRqwu{~d_@Y`aS>gMcn(wY}T3;=(Rdq!#h}xdY<@iJAPw{gD4o8q2$@?l>(rMNAoxXB! zb8wT@hC~!l>PsA4l||S6=CA0?YI;Rxb%Se6^@WeT<2?cm`ZL?sL;fu>s5Y;heg0v6 z!B8#5a+4y^=8s`tsJ0G?GyKP#y%f|^<;?i*?9h8;{wfu3;fsuVs8FFjClg&%T*i=gm)z^UCc@YE@TLbefj^u@|a>G z%r_-qf%nMFyZ?xJKYP#m8j9VHYc+C|j7eaZ1|#w`ZY)<_oWb*rJf=b3IkL?@vxs!W zE(s9ujH-{|C}|hPB`F(y53ryOh#wTJ!V~?nh9hKJq5d;Rl=0f*`^ z6HaNAIqbbT7HNYrwS~lCs{lkuI z<{TXBHvoMHOP252*Lq6cw&Kb6y&irCIVctSR-UteOTWyh@tInkk<`2{px^pd5bC1? z4jOk6CVH+r^Up+XBM3?Nh9NfJ#c}z@At5ZJ>zr9iK6*MJkb7v<>UXs0B zFCSK5JbXCXTREDh+-2-B^KpQJQA1SJ8?xK9F4jB}({%S=f41Pvig7ApEqs&B0V3y6uqeE*D5apD%p?imXbi8p}=u@Y2&Vd8%%Ty6UcAAui}ItgcgJKu23>!J)@1yH5?Tez>}dWrejGxv~p zBF8Oyh&nlDi7S$oOXLb`_Q2Hd2TS@b_Ut~a-?%VCTZDs=0lfZK%9@A($z!I*i~3N4 zUUI-{is((mGhBGN^VSVMRta}~uH_f#Kd8;0IYF`q1p>MQ|L;(X|DU2(R7I4T<6m*> zt}3s%tc1qz_{L6uycu0mND=@&4_UP)=y3Ikx4ERKn`5nm<=`sl7&`{tgZ=u)j&W|Bv;FkmF$l{0$F}N zCLj+ylEL;^x+?XjSx}nq`xvx7NrQP0%B`Vwx5kJ1&^aS#f}6Z%H~p0r44WX!a^Xl^ z3Z%mYjq#*JLaCnp^FyU}QEzrmARpheV&grf68n9iN6EveYzIK@IKt{QGs;B)hi0cY z29L`?VJCM`9>=lHF>9h!cq7;kqK`sm_;eBTM>4IfUlGhlRg*vVUK~c%P9TtAIJN($ z2DQcDJ!QBo>2aVP$;@@1Q<)D6-J~2Zf<~|xH`Vo24NFe-C7-lI(T4$^@6Sa=YpJ!P zq$$b$X#^o_;5%R%&3=o%pVqrgs&8khNvzLXH3!-8=VpQj0kcli!M5&)c|UnBFks?W zNmpCv_pL52RWIvW;Z?KiPPF1DfixF?S6<+1ZaaMXIY$3p#WcmlwxF>Ez3fu*Ec`8G zNv}VFEo013@snFL?V%#o6mksP&E@K(8d4i?hzL(_)Y;%pU{v9N;Z1oIh+wI&zdgd$ zD~z~%#M|z+dg`j;$`?uXj3mDMYianxeC{d}TcLWKg4wq!qWR6rT;&psKI{gyVV; zK5sCg5--tQ@R1Z=#*cr|4+XfMZ72U|hrg-rzfN;x|0!U^jNOb?%uO9#%*dGkts+m+ zcSI3J3*X(h4Pt0CX(?Nc#?Y-yYgv7kFtrQ9At5Y}@mj!*Fb9d5V$d!eH9Ts`#xj-b zd0~7tjI~^)u*F$gH#_A&<$iFcIGz4_eR=}x=kQ3;8hipfX~-qI2_HrW=OjNW-AZ=R zovcpn4(5ef#rC)#)wQ)j!12rV>q5PQ80Au9G%9)(;B>1=bw90vzstSEVYyMrZR^l4@aj74$uMHW(@w)L~g}e&OU^y zw|kfnlf!BzlKshxc9LrRr<)XuV|K==e7cGik2hW5cm;N(%o7j+vq59fPqaB|n`t&QKL2ELpS*C>E)ozq$`^;XtO-+Be1~W) zQ`EEe@dTR+A4w$JDM2a<;BK5hcm%Zo+_Amm{zKLL8}>m|e6 zozdbW<&*44DTn>9*ABtz^Sq?yWE@qG6yN7*|LKkl?0~PgH_(1nv+cx&>!Nyi><5-s zzRZ3OPF@%a{-dpZfCxVr}5A1;c1+ptAdB2ZX=`%z^Y|(51DTd$8uBS zo;c`33>RrUwd2JW`}CqT$-E15k6!w1HqN#7IU4lH_;#3qb=oVJvgpW3}h$_wRd}!STvSl5!K2B^r<|u#T*Kis2c$ za%_n=Un3`tOZk{bIk95SH(E7?SeqQSh-;LA25;BG10hO>ckg#j`O?+1aJl)G6jk6l zOd_~PruCsLf^Z+MDQC9vF|jO^t@Z>+BQ2s

aY*%q!OLy1iGi*nMK{jN4jeKdc0w zJ6tT3p9Fnq?;xk>WCViCnBSQ-KKNncVxa{Pavs93_>U@(fU)Rg7`%)z#q7G=2To=Ekj&Obv}rtTl%I*>5?U_)?I|+r87TRGxqIhLBvTMDoeCXgpYy*0s=yjIx(GNW@KTInT(_~h6Dm38cYnR^SA$5*V(GB zslQp*WqVj$xZd!x<9pN7X21J#JW6J{&3`$;{cRXj;b-HzUmS3!;m zEP2Wuk~fI=K{|$=9YLE*5^oF>I8l-^)IQ=A_A`?dt;%X3%{a0JFwAMr~&ruem;(Tj&JpT09sxEe*q)nS$Z4}C%sMwu0Zo^QgS?3LZ2wvTP0w6ASZ zHt0H?IQTH#FbFHBxi4-Zwoh)6HaInH6Q-G|9HyByg=S#Npy`!|t?Ctmt?8ALrQ{WJ zPTf6IqwE#YA+oP;p|a0!A+zsqp|cNaA+axNQ9CF(%@dZ8MT;(Ayh`ggsaJN3?b76Zd;2hbMbe2meS?S2yREC_Q`*C#Ss!6aDIHZ*Psgs_NzF>}VrA?wXqe z11zT-8(9v{=C3t{ivRZ3}+l}6Zr&V~L+8|v(>zc;i1 zmW*XEz^b!%Z6H{xfdv~r${aO6B*>ZZYP4}#kR0Ako(Vg)i>=;nQr@z}m`4p?F8nf> z?N7c&k`>F&%RCTg)?>0-wh?-oP8ds7Gv;YBO(!m=_T{3~&|5mcF1FlK*jd*5Pf=kS z&&4usN!x9^7agpvE;oP`->)Yq4U6B0G_O?={D}pnIaRa6CB~hOHn`HAv@B}BeFBD8 z2GG*%xitBaJ%-#|)q1Rk(?eiz;37I6)zU&UX3JUF*$r4`P#L2kQL~^h*(SoOoJF_d z7%(Tsmu*4-aPWBrMZ7cN+fr-;JrLlwmDz4_`S0fpvK%MwhAmWBFlA@e8%X?7cA8;) zooNI9@ekwOY*v3(yrpsc7 zdvhJAju%M|$(y&z&Jp1xe->No)Gvt1hZBTV!&wYUYZrrWS9Hti%)%GRta?SE(R*`Z z)ZFY?HVeN7C6t4w7NabYThm*jW_gjzbEpwUJ8Pc1U0sx568L!C=0(>@gUwnTSx zKi?1<>ZE;Iq8VR2DILk#T}hsm;P>JCsRoni(#AE#jW8M8AF$=2+4 z@9Qriea?7+1=Uh(UyP4o>tHYA=AgHOFpqKlF_! z6kvf zO=riVW9i;wikI%DxqIr~Vak{8rlouCK41!xjCdR zddiZnpv7CFJe#(<>x5aPn-hT>_PO$l7 z{N8j*kxsDXWBT58ijl6b`D60lcFK{iujOO*-gkhN1cX(U`1bU zg(ew!0uye)8?Z~cJmXIMm+qJ33m#=g#r2VN4zQAfKpEStH|`d~r(y?m(Ayy1w?}2| zIQj%^BplMj4JC_GX<^?u5po_Wi&m*p6dW>kb=NQSDEKD$h?ux}eR|30xWx{Wl96cl z{B2Bz#;lu5o;Ts~Q=2wfFr zOxf_FPqdaqjfNunPgLD1+2M;3jdK;iiiz)qwTW-k@7jtO^O5I;u4wki+6C5OoRQUw zEE9Q?OSi0bAAtW89l!zr@6);-U_$T135YYP^@-QYS*PIFv*BWOL$r(<2*BygD-5HrHC;0%x*VOW9&OpSb%@&g1fvvtQm z!v*9oK_Ng@o<89q$}iq=;bc}$vEBl!4Y1*)S8RFI#0pJ^pxvCvh}VqRf(&X+p+MnA zQSRYKQ6X^T$k4FlDBxH!W#%EuLI)t0pu-?op+w;(p+w;=P@~~akixO2N#Qs%6|syP z5}6hYbD1WMkd2!MP(%R2iA# zq0|uAgK8l>B2mKpMaxF)#@Pb~q2~~f2~9gIg+WY1g-J|Hg;7ct4f#w{g;`9SMuLM5 z5V+8;aKE5j;h!m|S=Z&(w+mC4cK3t^K|(^HVc`+TuZ*3rinjKkM8XD?L&YJ+kT7uZ zWVKA3_>8LtqCyJ?q(fywRH2ANRH2PSmZ0kfutOsUv_ok^T%lGVPEm>>PEpgb^@Ow> zoREnvoS2CnoT!P7owx^BUtMizbLqDOdGnw&=5JcCB6(s5W#q*9KY#0m5owSH@t<2V z+V$^nL22Vf_&ZVHI6<$@a~hHQXVjKCmQlO~XiM&*U7p{3^gc=gHmHXoEb*RQEs95~ z`Xwx}b@I}zTvv@#mV!R>N*1nj!)Ip0&YbGRDw;qcxnA~#PO97@Z;!xt2A5 z82*WN&UC~Lj9esJ1($aT$keS1EwuNMTWAtns_A&xDpg^`4ECpF5Y(XO4w zM6BwR#=1%-=d72Jc+^g8oPB7DtcX?WqP`jE+n{Y6ndUckrd6lfDEFQyOwAjAZolc% zW#5X8L0h+`wvAZ6bPxYjxpKRC0ozluCO7nia0;313U_dQa%rIN+@|;YGv<2P#<%6` zoNwSKew^|7j8huvSm$q7aT^|1v%lj91le$R(Z`wUeJe~o{yoM^QCQ1czGF&qn(>dO z`YrAiRW|r@F4-N^PWmY5YxBQBx0}4~0w4z#bSV-(Rr4q&b>mm4naCKe4QBC_JWhMW zQ6<@bo}c0GmdF&bAxe1|!Nr(EaEZi^w#knY zw!q(GlUzdulE$1;?8qX~BLyRL{l9o5f>pVR&H9M^5P}{gAzs@iX!tR%l^_W!*prXZ zHIRx4i7YTJ=8(8MNc4yO8SoK7W<{l>Qhr1KkqWuB{KgIS5rNAS<`HLeG9z}90F{J> zRG@PjB?&^s>`TGCSlq6G)sMA#a>8V;B{(Ok%L~r?&=y89Zq$t|5|j0n-u~Gf3<5vD zlq$^853UD0TBeJL9%XXD`A=k{%7Fm+{gvSN|Lr>{$^Rh1|5bLk=t63xjx7~zY`@?@ z;*D29(J*6x!E&HP#krhOkKobUpa^jzi4&ofPzal{^>{9km1h#LN0(zQ`7-ys?8JS) zDk>jxHr;GH6H)KJ@D(UJ>}>2#Ur*1@UeEeU=D$A#MF1P+#ya&YuUkbh-jPwr%Vpz> z>15iPH>OTZ$fYxDU-zSX!MIj5?pVwH%B(SKj0Ue@)wYiyyoEbi<#M9WpO|UF^Ww-K zI?|&!W%lUh6U*ni1?irT$J`&8)*n1lfiwuuVC@(T(mYVF@oND-=@W&(W8o&f1>@+O zjb78b(}9fbm%ud`o4d@~>No2FRRL^AbMhGBIqdB_H15x6bdLnJ9Y{bpHXZy%7-tIe zT;J#IPN8(dboFHIh?wX>*b2?5Z65*N@G3C>wfDn+D|)kLeSp7pb02qWVCxg%V0H5j z*e`KHRL+CNJ5&GHh)+VpX#$IP?8Kp5zR^8dN`=<3HN~9MgZ4?j={;WxX3c9cyrGK_ zckAY@dAJ9wci6;FyHoZP1IugplO3~b`jZ`tdxR8EY~O^Am#p_66A@bqCww5UUL_0Nj2TuhJ#NfD#?%z`swNt8xOAzhy*I*B)9$(%sS z!J3>i=F?$u9mdJ!v^Hv?#Ha3q)gH$d0RPd5)~nxumUAbI;q)L5?g z6FKWQjO zBV}#s^!?E7X!+`-`AJ1eRnb`1*xXpRV%XiikU;d)=9DzjTYH$neANJ5O(=3kSFNMj z#ZBMbf97oPPs|6bPJ9t^b!R*iNqte3g_~$eUx4&w@Ip-bw4*)2jD+78IJ~@iNZ0z| z^~%1$r|Am=>)!PYJ-mDx`&aNP`NO9?6c_5iqRNJvuD(1xV{l7{dw{LKJ#0%f6`6V- zv9Q!1NKIb%36+~wi$K-*zRuPi!!%R@885YHoot&+TPa=-X(s2ItD!nxW@j0`_u)CYf#A%1|?{g>w1dsf_-g0x|8CMnhr%VyrG`?I7O><5) z<&TnwaZy&hk+g@)$3}L#${O0TLML9J>h!Rd87R;%4zQ!RvS==XwQN4dsZ0FpW4tQ8 zcq6(OO^&#t)8ufUXNk74#r_Ymh9v1gfAl72iyu0=HkT5XkV-h(3{_=kRblY8@u6h^lM`5_(^xAeM8E7-Rf-hfY5V6kF!)XLMte#)1%tf2Le z7pG5x$2`#Dy6btf4ie~4&NYGuH;N~o{r$5RThE4NS#+2R6ijo~hb zU#)RAp9>YywKA1eolUiTnD9ha!`5CutuHG5Ku4h6eeGlNm(TW?MJvClNYn*3Yj{E~ z(Qa81MtR!mMn{S~EG}i7k!J<>Tq6b#7)V|95nVo%O1$JfkNL{#@@tL@u9Z4u^4ZN$ za4nknb=lK<76r?t&WHSyo0SK^)Yh6QM2P<+$GI>>f{?N4k_L0yiYY16BJYl8Q{JCO zYoet;04AR%o`k0rwhm zVaksI8k973yi+vSVLi$ZFyCCQ5H<;91t$}5SMxL1!8x)YJ-37vnUL?fx3E`D(T8~l zV9v#=sjel1)HEZ06BSH}s>Cc0=@RZ9GuEi5B>6YdD6ALnUXY#)pKvqFe72l6~4Pl=AZ)yi7VP8p~~oVMn)8zp=m1GQzy*j7%Us*^!Cv- z8&{#554`YF4zs3>SrR7nNDgQSe%q|_rsrI?d&8&L?jsErDrk1QyMJX*c21XPuadhR zgwpFeI4`+GJFCcwCU;mT)1;)|O7G3c-!nd~QMSzY=ru7_wUSr0a#uA9RI#*IC4U6z zry5eSbMRnkuI9RyArs!gCj&IpdZ_gef zIP6Sp=}&LZFw>m2=Zyz-MgV-X>`WT7o?4kY5^{92Hb?a!T{p9?_D(*h`TMs*rum1r z6eti`H%5+-DgLjFn8`PDgiSHW;vGNnAZK87Pm+?6**$H>nWCB5J$b~=Z%9%95L=ye z{|x_pEKlJG*t4g&Ve-zH@Ri%Kya)d2H@+qLx8t=%DJNilkDW4?6J*=Gd2jfAjE+Is5o*){q`dmpm(mef^{xh-O1Zfk35D=_mttixaKYwAdo zqL2AcURfYhTR{itG``tAWr}F1w9z}wSWcR8+F7cIOm&O|SsIEErOV#mYqygLv;H+reZ!9(Zxh5oUm{aCLf^|AU7AbGM8M8K=VgN@oc`WsO3xeN z8n&LRGrIVp$GOJC5Ab@=^X#p!vcmJ9{7MIZDP3PDxh}t+R19 zZ+8SIl_WcB(B#faPfZ0cD|f}1&2F-)R&tu8bE6uE%AnGHteimiEq^x#!fHV?baGE1 zrN;{!UTwZ`(P>3l<@)LIQ6zNN)rIZ{J6_L@{usjEm500N5(>iDFhO{M=c}z+!btaW zy$gqTEL03@T{(pi$}to<{9gs+d&I&*O)-)C|d8i+khhb48L|`N5sV0 z9jJ#9t_G9lU)=i7>H*|y<&A)SGX!-7gqS>XaRNi{c*-^1y&{XgzPbwTn*6Ilki}#+ z)X5j@FmeH#BeU6k^r^2d^cXrSItE6XEdH)}&1nx4R$2bu>GNi(V{Y=^X%k0d;E;_G z@~mdelh7dLjx22f$5w|lOf(hgbMCrKj%dn?t(-$@s^=4kP_(`2T)cvc$en}S4)PIl z0Yu66(EItk0&S(_sgifAM)4;8jjb4!qV*KPoR9fVHTn}Dzx-f3g)Qy;Zk2pfJlqF# zvy-%ls$^B}{BwbX3ck%0%_Dl^kk$=5ttm4N3h|LyN^x%hXF(G7Mpmf^4uK){oIqzY zQ$5+PYJQHUmWBexc*<2Wouh|$Z$%~_G4C;0j1F~8d5&@z&M`YPu&IY}Rz$dpdDh60>GJeoGM{2*RAhs- zQe~&F4UIM*IKi&lBoc1|3J^R$8uSdZRb|7hS>mY#GOR+31uJh#e!dC*4-*}4nW~RL zJVgWUW91|R|Fu{-Lj`S$dLO9Cr?>kqksjC>%UhzbjuCxu<_&W3k+45xQ?zgtsq~Ra zT&%oZk?HMxIW_4I0-1BF{-;egRJAMA?)+KYx;6NdOGTSeU)j$Z3rO8AcxIH7Fid+ zlPB>kuC9i{NSB?I3~tKyU#a*41QVwdjHO#;Y_!kli_mF8%B4%OB*MI_ss>8Q1>(w+ z`6+p0zLkasCx)=5G2O2G#Ksun>3j*$tv%q)`OV@J;Yo_C7W0#HcemH)_^}#RP3cPk z#qvi+hEtZ9Dw@%Ox2aTX0O_CGTNGQ2#MS$P=!*9 zvy`*R6S^T}N!_|J)KKJCWHV%4WPN0F%@`mhAj~w3_X>=ALwv!rgZG15_bdq!6gUG3 zLQ>GC1krfqSErT@}_1> z>pppSCwGaLck?(KveA9jHpH@Gq#-Ufnl0vdYHA-~=E8BgvU$;pTUFC%~2Khs&;Y1i4tb~u6gK}!M)u|a(W zM{^DAN}it@ye0&EfxJIH^xggp_=4!WJSA)}6h!P7{`^q=cA|TYLHa~1&+P+WQUs-c zvtCi$bDTbNCac<95M~S3VYSA)#?NTPXA*Y+{YTp6xI0oVXzd}ZMhO6UtV1i`GD)Bu zu)Uk0N!Y?N#1;{-)g6j9s4zBM$;fF^S2?uc!xbOuE!dw+L~pJ`q@!);ubwp4Xm!EY z*e#3>Gc3`Q>6AF>!PVH5DDt9}PjA4q<KWHAYWq6&Q7eFa4?s#evOGpNE3e1wGlq zxD-(b*x8eBXqgVgML4iqMs4;dC1;;$@DiEea*1Lfwv_3bch1K@ zrCe{}SC!ZsT!kW!e|(uD2P74TIqc&v2xr_CM`~cm)oY+O>a$vK!X!u=e6=o!`_x~H zznO2Hg<57>Nw)W+Iqo8{)=`I7qTMWG-P<;m_dPU9C9i)7U@PA#UrTw0R=aGLrQ)MS zy;dSba?(F7rR%5;u2RLiZ=}~=T&HCmNr+oEvwW31dM&lssoRM-HODc1?Rs9G1o>qj zkgN@K{>Zka36roq>gRxw5!Z?&32tbzJtGNuC$F|WE8a$4b21Pc*1FVFzm5z;zaEG2 zbQAB40mVioJoM-&p-NO}3kj|g#a?1hWjWrRqwU^!X|RUzSnmWyX%qTJz?ugJ^%PPG z_m5MdzI#UCIta#R8dnok`RPdEuDjm3{Jo10ePW6SXK*f~6X28Kaeu#bXhh|otcX~5 z9Q-Y)s&5+1`0=fMv8ZslD)7dn*tm|$lcKV?B9Yq7rZwHMAlA9)Z?OBHuz9ONCA#qO6IX#vTZh*5uOlPuuobchCMd@s z&M9`c#7y&xEl!DR(;XM{|&~`;mi7*WFrB ztsv#5Bh7N|l70nu#>7iaXck5WvqlEuk?}zzc;WaAup%MOiLme?lJ0|J!7>hjd11R4 zbLIlo3&D27oDSJKQFlXY4s1H%cf;cDi>|{E4tczyc??*xO`hID_z5r8OWni!!S5~} zf8%dg?JlK#qDl-9T)_(-8m-3+?P20U_Z@J(!dDF5WP(FF^L#`-oORZh{0xBn9hf23N>%EY$2xXgZS2WDFbt`9q6A6V{{- zM)0$^K?=Sfr3FTIg_}#!Kjhd3QKRyE&#wggXB@hgX>NGSsCN4}7v^Q`lyj6bItLb~ zNr^Kn2lk6m698K`F2bn_0Gol!xK}lL>6Lv)P{-Cgq(17{{>x;DmCixW7CYEdXYtl< zYQZbs946l}D<(si=2m8kqJf zsWW1#F&(;w2Ok(~1Ew>AaYz-Gq+aM5sP!4eEH%UclFrm85_PyR%JV=k5;^G}UF>Nu z3&O7k!k4s=LS)qTdbuzh{zg@cerKN}sVw-gOeww8#{o8emu5Y zvxrY;p!K*m>OjbQuhCv2%P4qfQqiry%^P62ezI1JZlL;oj$P7Dg=)6wb`j$`0aXQ7Z>3XB0AV2P|h8AMNWwvjezHBW}zHRLh{+bSOFE zS(r`hFcoXabtf=*Z)~`4R0^Vl0In~hKS84nWTXmD#TIJ2pGrBMgf2^05rrsv8hF+t z%In93N@=;mr_$|Ez`OLLW;b!y3mD_+4jmoL+5IZQhj^CbH&W)oTpw0<_Bz_`~_Ea6*1)XSzmN>M) zig_6j+k#1H0Bv;UJv#VIOhD|n^MjJP-EZ(t3qMiE$=0+sGVr)yjXy2R;0K|D9RHE` zr7Ve;fC_@zli3&^fm6U0Hf5Og%TRNGmuSk21}mO!R!30? zP5}Jw6;9hX{`nHt_&Of3gID0_`c=pPHv5^W2QKp5ORc|ap>#+G;=PgDY<7^S7x{VB z3@luNuXAjcd7C$I-Sjjnb3ycU{W`Dj9Pq%*RTVMoyv_SPZ+bexMLzEheeSH|QfNvT zbcHY;1&YW%tj%lmW{&mxQ_%$!#vm^v<=0~Hj^!6$5 z%0oJy)uAy>R-&D`9kQ$1`R-pS+2=ZsdO;_WSL^Bdo6amD&-Hwff2;Ac&LOXs-y=V0 zhk$3Z(=+}I@F17FB5LW_#vOA;z82?>M(O6Fn^3f zqNEv&?GPQ^(vK5w0&w$VyMo81fyd&nZEK-pKcoP{%I9NES z25_XdxzP+*-A`Qt5`TN+8i!#hUZ;PL*{+~Q@d~?+5KlyP<~-<)r5!^7opUhjfP>3$3s^JSchAS!Q0vG6O8h#!VO(59j4+0M zfWY1}C&$5nDl}gR>V`>_OM9$R(EEBVsHs`I`t`k^Ua3|wq-A);Ero75d88=6W&0?m z*)KhQhY6pQp#Y;P{TGA{X{mA(!+a|4K2$BL=Hx<&HXqFc+&4@!Ka{VC69Zz#4CspoEKlXt> zm!cn7Mx^4N%^kHJl41YvgZGAp9b}kGd}HI55O=MQ{s843?$WUc5grz#MGse4v#cb1 zscX)&@%9>f23sPII3Js-&habY4QU;Z?hSt+V0qy*BeR?q2??rudj2{&!2=!NL^p^< zNN2Qhezfr(x78022S9?tn_#C0?DMZX!9c zp-tv+dSs^iHXE?3Qv${Vwus>M{B&L#ouer6SW+|}l97R~YV`2A1eBh<{AM6p<|IA) z@S$AZ!+iq$3UIY4Ff~gyu6ZtOiE^Kf~rbeZ?K!@f$a!HZ{aNN)^$7ob(s@Ck~ z3pPhWA&>nqS+Etdj#_LQ5yb!=IvkD$xCX6v_$ygtYQ(Vb1R=3s(~)xLay@B(hxcR@ ztLXI1U=-GZsq>`!-WsS2iqugeb(j3tTl9;Qz!^*)sByy3;!FqRZ=OKSk>N7T6Dblx zN{;cylPQBWHTvzep&yR?Zl`Q2Q`j5S7($nh4jQDI!$=lZTTrlfJe3HeMZq<)(<0rA zu_2#~K)wTz7wVSw={AESG6mD7J*Go#>(E4>n9`H}BMYL>gLE$KJXEN}$^Ge0g`ej8p@K2mYKiSo4 zihwDa6-2XeaI8eutgKSQljY8I;K{Adpiny%L^x6T+b(IXdc486E6F`=CvF*%;dNZH(n~Wj92PM&tf^MidNxts zwDru?uRh#H?o0sQzZA4}9&UYAsK52R=^UEAnpGTMU##si2xoIhrk|GGO@+%XJUn}_ zNCKWcLX^^~awPW4gY~VcCC!fBnmx_a>X;3lp1p_${%LJ~g45&VXcb)8bNbAtENqTWu+o1eSqB_odxaQE?rG%5TGR}KMt>n2`D0eNVz*CXrrK5g$ zF;2m&Tey-9rSQE{jXA1~=|}~mB6eKGTe;`FR6;qR0?)h} zkOx@eYZk!HpOy<;1hc5?P=J-cTD)*cuDVt%KmWzt@F1WB4^AtzuVCl1E06_>(QWXv zuiN|jy87xPcnN0!4iA_Al{T_4iuinfvDnA(YMf<7zsr^1l+yToBcz8|ymL>dQ z9Q(1l-x2(lCH?6>{HgjW{0aEEp=qpZq}shtoy3OxIrqQet7xd-ta^4yC*DZ7Z9UX; zH!W0ch^QNowIRIzm7>N$3~w>koSy^loOrW%uNY98%O$P}D1H)TE}@)Eq9Hz-lnXV_ zzE%t1fVa%fFS%)a3?mf8Ih|iP#gaqI*?*b6dPB1B1hy3^86ng-sJ5Y3Tc8HFY+g3m z$1x5+P2wscRt=`o$}@}vk@~@zJxDpVCp~T5e9cRpzx-*t)2HNu;a5BouPz9vgAu$NjYHaNC~dl2+9kThxO!fcV^eX04)yu!hvh=m5xpuM z)bbUXm?pYbUnrk0_LPZ4&CDOWNP(g3APw{h9T>we9RXvcYI2#ej1GzuiF!lC5WAVy zd~sbcAiaIk?cwXqqdhQHfb;cdA%00SMEhJwB`tZb=xvh2zCU`s7VKU%429wrLk-cX z)Hy!`Yi=a4>$CWBl~Pd#m9GHZtbjpW!eUdH3fwyfMXw&m*H-RRQijqw3SFxJ9G3yq z@>2M!E&N25ASaf@?LsVqp%=;R(m7z4%adnoRQj>comMNB2L{i%+!V6mY0IH^t(`Hv zifa657nN_bH6T2fnCo=4BSSP|=gv(ot8Mzf&sDHWu7~2!ZLmtOhhdtb8kbBaRX6I? zoOhutIG4+^-4Z+yQ_D;3i#F%4JV}7c5M|v*0zJ{(7%6(B$S=8E<>k8XGg`UQFMzI+hZ&kx)X^?vwcKt7Rf4O-84w27pr25K|Vnb95xn-Xd6MtUyqjG zS0R+%w2ng9h_2t4C)DP$#Uj*UCJA?q8rx?il>F3l8>Sths^9sW8e(7N1Q2d$l_x=p zZod~Z%)!#fqFsoVU|&n4b&LjcXsH)V!EnA8D%4TIARQYPNwlKRFwU%(s$ouCDGwJ=0D@_(Qe$^fY`?!aTM|)@A!MO$E z7?;=EW@spiRYv(HHt70*P<9&+EW^2F>UV~@lv8PYv>f3ChjxAZ}L zCh9^I&7_Kw!n&#OlW!D#I#J8(g_+=NPu>eMT-hsQFt3Xm{lWJ1@} zFRzDPM(5n+I`C05vKaTj7FpDcY}gJ z@3arQ`z^zyGkP|X$+qBz8OyvqdJqL)xBmJkGdk8T`cC~scHB({4|>!9W7z{{!(KinX~c<7xvEARtr8 z|4EBU{@*Ls{<(II%xz4C-3*QYH@{ePe5VZXKl|>FzP~akit*7t_lVG3TX6Bnh-ARp zs^8-vLqBt!;B$csPn4{lvB4h5KRNXW-g$a(**U<;`V_$Axr7A+1A27P4 zxF8wt02z8Ifx>jJG7~X&0%{R%^=)@x&nTCY@)82)6m<2;*{0bI0%B;P1+3%ne6@D@ zs#QEnQ7S{23B2mj@fUTJ@pAc<3C>R0S#W)@4y7nP%kE~}v~Bnj62gW8*%%wN-v9Jv zyF?CVg#P0?E&tOb{@>lP{}(-?v5UFk|K^8{PIc!~63rRyU4nJ`MU4y@;rBai9N!@V zFN|d?+R3UT`x=~RtY|?1f*z8J#m0n%laupebLBe5b6QHqQTLq*hn}8<{v|t&b8TyF z>M}lijggbFlQHq~Min`pg={bOrj%Ft>0G&6^=9cHJr;)zEKgo5I|kV+3m-2Cgx48T z6WoXF5{q1kP=S4(AxQAAR6GrX8Y8h!62DUeRa{$4INAD}5x2>ix}64RTA5vRf1bqyUae6N#juBW!5y4qVXk9XoL~@R zoi1om7@a5kJKH+2H(}u2 zUo?_^Yv42fT;BDAXuAWf+MSD-ATJwTBfSzT??_6n2hPHci-<`v6SjTlr^&Q)*Ij0GBJ>=?hqk6fP?4v&iQ* zBzCseCG@F+vke!caQiV+qlRK>cK4o{n;-Zl(h(9k$Tm>3^CNf*c)-^$e=k4)>!N7j z>F*2#(Thx4xt?#}BWg4>pouB=2wG;n5&F!DvIuvJcA_=sq4iH~ChXuqrI;p&X zdz-u9cBXO~N!^z1IMgJ|h1x%cguKJ>7#gj(ssM}5%?P&+&zN2Nn&E0zLEC21qz0hkuFFl@5{eJJO zC#*BmHg|O6#L=_7FeGGVzU;1%$pVqg+yT5hpoE~a7z5gaN$K;#n z%mte|ipJIYZK@IMjgw)t_tw1Nm{pTi`;zjO*)+W~#O#|m{g&BwV!NN-J7${3wB^aZ zAk@dgAe2kTv#u->;$eXF_wfBW}Vjf$Qb1MO6*#yWR-ZgthJ z+Lj6dH%7Ka+e`I44-Wh2T^ggL^s3?Qxvr_Zey;o#$A3Oz+#R#DZ8UVBD>%O1P0!*b z=GBO(#V|<%t0Q$+85kC&F$7CSDkciHRe5^Fw#L_sj%6J!6Kn5UDwo`_e#^e*L&=xv z{5ifpUMRG0pIzqULHT)ZFFc!&sXFPmb@||3Q0#S3cR|9nFv-Shj*yiV-8AFN6sLfx zmzBj!yC7;1Qhk5r8TeWK{3!X>!*jnpBN+!&c7x6G-!3Pok55(_LM|R=cJv#--kWJ| zrPc&D`yG!r#6hWyeR*5d_7K{|R5<+5k<4f3YZsDlV6XH3%`H-BTR>zXoPDw4&=4Hy z_F!x|?Dt?e?p^JFIP2Q{4@6=z*=&y4`s|@ctFc<`H>d3(MzgV8?Kh|F;YYi%UhJ_B z*@pD(v5wgW?0HA!V+^rI*=6jw`s@=9_+vuZWbC;n{|V#~Vno?-4%i0o*hZ1CWF5GM z@0dmzv1S~&M(?;rp|NNkx`yr;Mk%ps9kTwvhP&Zd^BInpMVHa%%c9_ZwC5F>Pr~o9 z@Sl-T8G16ax8y00EK$*e!Fu_X!`sE{SG57pDLpR&dIla5%lA)|Ep}!G4&>Y-SVJ=l z11BT8l(5uSE=-9m0-BnAbeD*1gP@xi8jGZ^OS3EZn3&kPfbq(*tY)G>PM2W_q^8s5 zDW9Ot!g_Ac^<$5qM9<;_)TyV_yMdXvMb1df4~i4F)Vai@$DTsdP#kH0Wt3c;v#h{Y zStUH%VR=j^Y^4}nTLbNXqKj-E@y@zddZ4>yr}5#*hDakjIK zJMY_Ql!_c*Hi!Hm=+19 zs`^ngGV@LmKNF@2Zz&m|?l6(rZhIduer_+$8aZl;>*aLKjY;Df;x3vtV)gs=jm#%x z#OF$yj*3bwCYD<0sHHV?TD(F9HwdgFix*Y$tivS~7yb#b(a1wHY_zMq%M&a*z8J11 z{z*PobPBqajLTrI77VrW8I((&9j+*o71^6>=X*4$<)E8X&1#j@DI`6UpDmKB8=7n9 zbP7*pKQykIDMXLVY8P~B9h7UH!LOn%@-@o3#XmTbOYfUlEp=<}m3y91uc|HLHHy3g zpIMsKsF$~EybCGhz}7jNY!xII5w9|vJC*V&7Prf{sw0%*U1D8oDO85nJDY6fC6^Sg zw${BZ(ltuH5U(_s9GbN{bvyF%Il%Z(G_z&iXUMbmCF`8#a|Z6l@`sh7CY7FO4-l!R|ge3OP0B_ z-<%cv%x1ZZJ)+DNB3DpXiWTC_<~x;Nu1g%vG9S$Ay?GhRjnA|eKitha)hj;L&pWEr z6~Z5>O+Wq?%GK+=RbTK+;LZFWs!c!f7K+yEFXlfL7OGe4y(M247KQ6&UzqE?qqA}~-VA!VVeAfT$1;Tfry5g2Kz3Cf`^BRWtmA~;Yp z{Xx|*BREtyBRSMG!>4Mf^RHVK;LPt6fY1*Qo&xnSc0r7wa)TU3?SPb|c0x)~Zwsp1 z49Jx;z~>`tu8Wp4!531~#}^=LofTCx*XPtv4wi(fq}GxttFr^kpC3pLqJZXvTtW?j zw4~A!E~~FgtXm$S5vUH97sw8#7w8U#5(o?y7bpw{%^w@k8Q_3k`IABI6<@bK&>Vyc ztxM3P@yehd7+m{jo$8XXN%Iw;Ul?rrhl9#1vTkg^6*AMj5E3_pE&K)+Yw!hL5N8zE zwwhzfW=BoS35la30u#8YcM)qicNSIA5!lzFls0BQ@wLBC{IXQ=o9bmIcAvKGo9z3Z z?E4)3`+d)w#eQQn7CU3jTL{Ctu&dJuA7Z!(O<5mb!wmQQALOhF0#={?N*Zi6lz3IP zbM6=NoT&=E_c>MZOtkSMd0ke^Bw~DkLVPHLd<;qf)X_j-GC_c{0c52xFp2>##juZ3 zI7k_)Xq`wFe>7WU^S<|D1iZYdB!4$GJZy^t(|X3N(Su$#b%4pnpXCiiv-&H+N^4%0 zRk*w<7tpH~##J-UNVtuURilqTnOEd?YY(&Lw4oc@ps`yZwcG(TAAed_kxpA~0%v+~ zQ&-{jD!(8#2!R*2-z|65dJP6?yxf{|)Hysv*D3i`%?RYD@1u zx*;~6aHMe2af`o)es%t-?k&h(G2tQf3EZ*pdHng0s#d1qPgU7JhoK_if2w=%{`;!d z%Gy!c#{7TLVgFS9oqld}uvI}p zghyeb%{WUc7uwaW)uK!5>wq0IMCtB*%jV{#b&Cu64U6vB!9N?SX`twrRB`YP~uX9ty-!>!u zN?1eq%2=qky8fC9e|@x*MM1kr?UdH&f89W1sk&79!Gq$IUfu!oAiD)Lkp6xZ+htAS z7VjGcmQ{Jq1m;0-i)bkQ{i?KUKAuCf%jmzO@C@_I6D+6vvK8zS=@r{}1`!f?M&-E^ zSpJM17}o!U!ZU|2bp8R11579HG8kA}f)+82=+<*whiaDk7SXtuRt~aDX>9_O1iDLW zjS{p3vP)sD613!Zm&TeUXa=xv-X%O(2~?NL+68C{beGN=Ca4V3E1|J1G8x!*(d85{ zH?VKjB{eX$Kd=0ADKI1GcE#lksLAio_J&rdEE>DahF0iFL^H}x?OhmuS2EAizI0$$ zvQ9<4mOkzAZDKmn-L3I!)HKRlwDE01I@#Us@oiE%-QBNo9z460KJIZIvO2z9!Eqj{ zI=NlKaUKf0mOfpIkF-7l|5(ZoN`FUCp8U&1;FI5<%?+|pS){kDI~#dmYikJ|^rcOqi?=RfO=Rh6nOE&ez_YZYgZAip z0lq+*-0?+?d=m>^rhIHvGk|26&Ww_Sm1=JXk|X|&?BtL{iF`O0_S7#YX_#4?UV;JV zB%*1yUk)W${|msUP)WQ05`0>|SU!5Lu`-XNFcaWB&zGIuW0n-asUa|Dqsyo)M=2T_ zA+Dm_k)EYg8=oh#P9hE3@s-q5+#YjZ0dyXFpyc|_-U0Bk=E(>zEDi_HmFoSlnSyKl)nP-f0nrHY>_;^J1)9rhA}5>_vrxap_GESHlN! zERsC?;4kHK)Mgdr3JxFQ7Vrc!yqkabh)@*a)V<<-i7lR*S6xjl8UU_ZQ(E*uto5?{ ztZc-sSdxWNyyL}Oz45T1M9fhZ5H+2CE=z?qKz!h2iBySJ(N(C;o(7}OyV0!u#qz2J zyP?Uc<=4TtuJ-VXUFJpb=UL|6CFWxui~sJ)UtN6_!^(f|Th9vQpGN|B+K@s zsYS9L{Uq#tUzJ`5V4}yy5+Ea|VilAx@NQzN2HxOCgpNY64v7}j!-5EHW)&W(1ql!9 zX1R>1nl=jza@YIEtcvwYlrVCH8UzxFGk-0b!3vBay9f4@zn|x=)<=Iant$lod_G|At+0ov+~U`hiYsm{k?q^lkLja<*sZBulSujJOuu_nf474U0XZmhTDkfBY|b&C-PJ( zYrD-3AzvE^S`7^;)o7a*j9F!$6Gf|+hM!^0@yD70R&10J67)%%ALuS`HhDKn*7?CfHz0Y^md>v{$>tg#tsqf_1$ zEV=55;f-zT3*jWFWT{GL)YqG81_t>QP!#3<^6oALEomWA#RNExqd1u(T%p~uUs}(Z zA7=D+IIalv_B@$SBs6J%%Yn%y?}XM;$x`hG-APjJ^A-om4{SJDJ>2Ed?$4_wk;mI- zxwz!S5Ky#EB^TY?k*n(k0K){&7hM+GlHbUA-pwd}XbubkM7GmCXR^BQQ^l3rr15F4oKrWpn-~zqwO#{P*%-`%Sec zLZt6-oL#81y(Zrw3#|G_(iek`Su+Y4I| zvu@#lf-D=C&VKW~=PbW8`{X?dR^ys~sa=q2@GiZlFL!Ia5G!RDR+TNyp3j~yp3i3# zEmbRUVvuijuADd6Da7bGE zFAG#RHlvzn7dqE0yK9_SYUeepS%xnwR2yrcSx~QNf;UX%st`FB@y5 zT59Jz7cVPoR5mZ0X;&jtIX5@XU&&+?(zx+ek=p9<9hc33-P?$%Kp-zw6)GUivgG$7 zW7JOr2kyUK$&WT^n^dn=JYQlK~YX9*qh1yLF1d?R`M?oKwi*8aKnp|b7F4~lf z-gNX^o%6|xkV%8X_bIKL36&Y3dWOB*^OU!T!)o3}lZ{>v!dVK53NiDvNFI=QXLmW@ zk9lT}VL@#rHoXQ$?#f^B>v2-RI5uDdoKdNSfrDh~O)Jh0$7tY<4vFUQQav2T!lun>O-(pg9$yEp4>kq+D67yQP>#PS;|1hgb_~S zo5Eis%U_8GB*YBrC2GY|<7re$;(tkce&07z2=47K>_REHd1QOPZ)TOMOb68;49b0ZReW zn8u~pw=4`0jT&TRQq`9|NbpIf7$m3b2Bb97n#M64N}SOXSx6Ar6-_kdNv@snAun4D7*Uac{fAN{J1N8s zi|=rWjcHlblE&#z+9W4*?5Zha!OQMa)20soT4WQ|(CJ!3hn@{uDr#tq?LlKN#=(yo zsSNE=V@LCpXzHqT?FnPb4)Ik@H6{+QiMW3y+E?H46D`xVNYN%JE<8wB>E7bC2vRjz z8QR0grWzz@8hs`YQAv2?C662~N~LU!?-|>rYU+HZ4_Qby4jtO(JF>&4j5@XOEvq=w zHph%Ywa8U9Inyr<98$H2SJYe?*QO598Z>H}uk{^h6F-citD3J(?|~Az9hx82;Thb+ z#s(dtt?F>}@5vH%$D46prFthx3P+8qA)YNB*$Q>Vb0MD1A3+OAjk6(Wnti5D$whkO zec-iBzEb8EqH^M&aN0KCu?tNxzI4F3?1G=L3QsV;_*vX`B`#v4bo3rc*G~N_t9Thb z;^!nA#z0gxef1w13VX-%AZqHrM@~`NuWVeoG?`qq#E-xgx2dqh71L% z!;J%Ti3UT!6QnQ$h!(6N}$b71Kcyf8h) zP1a^b74!y45(F^8#Z5Nqf{Xr!$S1I~SP`dN4nh$^JX8BnY!;2`ZQmHWNi!xA_}|!?MOJt=U13 zDPY|PBLihOfWI&%uI!*d5_Iq`qqwYq_ zMRDeX;sz>s{TjceRtx*c+q~rqribVnQBX5t5-6Sni;SMC8)Cz#h=SHDx5uVpY#DSo z&{xyvf}o6E(NDgEQsSJ?8<7X9#7?do*$ZusaVc|t^q;W@USyyC)q4OabKM00H3~Aj z>jno;}TN*Y==( z>d*r&%Es$YsC{OaWXXbcRT1Fi3TtJ@%D3swhIO$G<9*#YrVErw($f>BdaoKE*c&n5 zU%#@oUxHZk8P~%M`=z`Zt!o(lh5H{}hB7uUDfB-;K!P~`QKsKYWq2t{1D~3r{D4N73GtcA82gz0dXpH|U6AJRk z)ENkVEGdBsC?nhFqGXE=E3kY6`Zducv^-yI`F1ILOZvJS2)*_3NVA^QW`_Ih+T-}V zW_sh;u`iIJ;1H}V<~;WC1z>_pd7RFHUQ zjG`S?TLwwrQ&Et4$Bd$huB$pX_!<&HcaTZZ!q8J(ka}kgcH=hZhmCJt}mg-hyq6}CgWL*1eJ}8AqfLI=u++c)ofj2eIsGeB++jkCjth1g=xMl zQY0HY8(7Db2_5Ip?bUB#flhD%?Z7A4ro|jz^KL(ub|$sl#?`bz78&DytQkGJ&}sy` ztLwlwa3E>9Xki3O8_YAQapco!ZR?yo zsKFSB0hjft%<_^CT}?{e%?AJH9&Y?`o3oyDt)`Q*_`hj}z!Q}frcM1@;(w3STGnzg zFc=g&qw_(h$Qb6I1A#9pONwyCOQ|@wmADgEi@nFv;?e08P(z*eJFE?d4x2p<6rQJ2 ztcx|l#_zE(;8YU5HRLl$0QBpRP zF-6g<2p&x7v`-Cw>sXR(;dA^>R)a~G0!^LnCb4JHM2zKWYu0z%lhe*>QwYqL`!k|K zgAlKBdG|i@%$LKWfae24Snt)6eB2EbuYrYJidU(@lh*(ar-)ZXC`KEib;Y~MF!NHu zQG*f~5muhB9!>@ogH#o`cJ`dG4-b;e3z# zo6z`unzL+cqhlM#jH=&b%0n?;aC{h;iqcl5`Sv0A2pLq6MjnlA>i10L{7A%RPzi12 z+2PRQqh;qtmE!}p*ixqmqY|HC4zP)mf@YkLIm_J4`DM!+6{%m{aK8!!E9dI5US}XS zoD0Y5k>1~a8;Dl6)qh^G{%W{qhJvKbh`Owg-@u$^T7KCc?5*U{+++sy^W#AE#6h1Y zQvy+iyDVC0;ZR&e-0oAd-<}B0pO|E*Gaiy#%qwzknAedE-%3BBV-`p{tD&NY98VbP zF|#0OTPW^}iz-^dr6zmuax`1fd!@>9#W?p5^1)b^IMwz;{OY)|Z33x^YlTjTPsxe8 zesQuiRqen>ALiv(SqU2XNHvi3Fr1mP3I<+EbkNP8fQXtwO(2T;+GkcSH>93ZX|uUX zGMcB@!XhuMcQzAk;WOE0F9;fUx$-J&UZ;O+im(y#Hc2cYIcy6J>7_VycJb&@_HjXx zhs>QImxUO>ILU+~h;255I5jvnjyfXPQ|VP3+-+Koj1#!!)X3Jn#>TmE#!$sn^2rjY zlP>v{lda;7k~>6mZ?&o35Vhw>yHX{3q*R}vevT4qXCog8##AMAianic50?^FPI3XM3#btPQJ{T@ z&c_V+i7Jfh=@6A0)sVx_nVQr5j#Ch44;=yus8kT+Dk`D8)E5!W)E5zuO%d>JO%aI5 z#tf_v8HS{pW*2HYVV(fXFUy-2F3n0*Y@y2;5zJpip&j;Cc*2X?qJ_}C_rcN?`h?~F zE2Lod9ei4^FDH_&%Jx9U3Yb3g{9veg;K^o0uug{TPy4?ScPWbGLl@Md8H>^%%0^KX zCTLgvc>g}gS`F#mMh2Q86q+HG&eBX52x^twIls%WHI%&NEhbBzM|hmY`KuciHem+_GOw+D^1fU~2Zt&NTGf3oua*NTPU zDE{9Abbo|@%$S>hqjvEjtbpHvqk^Fg2a}2!mQh<1NFVHJxdFiwm$W$?d6|U3-~6n6 z+=12&C;8J$LC&qUsZ)Yjef74qiV+!AP+eu(Y$ZJ} zrGjj){Pd6Pcz<^Ulw^JVb&(04$-`}{x!r(+2j<70k(#-|+!kva{L277sDq5oPV-bhMv#fWmIwuNB;jm>0fjB&jxO<&P~7ci8y?RDj1r5k zQob6TB4f)JkDPM0?wuMMFKfU%y&J;g8i-Imx`!KND@E;Q+Y|G%Hj<*W^F*+NNNc^; zvhO4zNFyi;4j3Ha>WTSX!O&K7K%aA}HVhbC7&fM7Lfycg$G&O3!hpyYXZ(xl`VUH1 zLe7ZcV(J5(`nn)4jRG68k{tYS%xX-^qXlTCLI6F=zOoLfR(Gax)+9+j9;_EP<$`sm z#>dBm3b8b5K847eO{-X%>El_*1kTR69|a! z)EWt!02Zf+?*J+-QWPKI`{L4r$jTyfByPLLGqT zm4EF{@}pzRX`|~nQo{Wk#+vRNJclHyWRPyK6|0k}dKOQM-JZ+0cg0b92I<9j-T0;c z;2qQg!9W`yQQ~M^i`Jw>R1rns&R1=0v=+vZD!epLK+Ui*vIVXaM1;ZW?eFQQ|F5nnRQY0mF#C8l^iHcC1Y!XRTURb&> zR>_KAF@BUephZtMXdO6o06Wywie2O4)(c1rmUR0~(h@qlj?wWub+}Xv7HlKvgiNAily~;@>6VD zLb*3`U&uV$um#s5HrlNIxDgAoLcB<<$iEh_subAW zmjCl0`LBOxwK}wy(or(6sg5f{yT3S|f1HrW5j_!K7@38i7$UGxqMtY@qQ;mJ0~APN zIvYGra}PyNHOiHtxwUtb&80?b00V!1nX@HsOUIT==f;)GMsst=RmYaAt2+4y?@qU) zDFY1RJLPBFj%%-H&qMafPnPZL^o=eTztF^+K0Ny=wfpCQ;Kf-gpj$gARd?(F+r5i2 z)|aA0cR>^IB{JfhGV0mzNz3q2J^8JS^K+ch zAblNG7^_!mAZF&G(zM>tNE;6U9^-Q*Lyv?f&OoC6NN~7s+H=!mS_DW6CsOYj35b2% zh3T`D8k*|4=J8-v>PALmq73F3aUr8?Lq*V$QIN_vi8?}cL1iIh{~mf_hKC&%go>NF z2pbkXH#E#YHKS1Lb#$UjF1r~k9GH&S%74|PLO36?)FWC%kRcM3pDSqT`E+9 zzzHK6F}O1l)yuuKA*s0r*WaSWi|&c_t1X(8&Q} zXu+$XX4tld-Bl+51kwS?fL7o#W}Qeo*`NYJ?E2HN zX7~Kw?rv{QNniYS$MoGnuPgQa#2+-$#brn&Fz5Hq!W{!${q7!&O705FHm^UV~J(jx|qY|rFQ zqqcMmjkT$^VQqm+(oNns9u72JJR97mocQr+HFk#W*iT~=r=`*&#^M@nsX#ax7~}i7$-Sh>>w5Z2mHxSSvRwa~36Q zql^bk4gF%flX;}Bm*RJZppG!VlcPi4#%|cZ68LEktRC8AGD^|k&;vmgl?G)lp2JgV zm2ltKWx!%;r?KeL@c=$Bwr0zrS3YRR_t5#SN#4+a;WRJApnlAs)uYJl5B@!GR1%ON zs7{~q^Gl&m*WZn+4*nW5W>im8lL5R@NYK;aBhMM^wiS;+h9>QS*EkS*`lR;;&4u`?T*6`8i@1{ z%mDHOAVvS+Qy}1|8Si(ohe5d;T@wwJ9D^Z4zXE-xjHg+x3<4l|REK1z{q_7&zeA_= z>V8$c(+QC0XJKuEY-Z%t0d?sP(t5!p#-Ck#Z0Q6jh?C(YtFjNB&fW};!e;p~>yj_k zbWfaK8GqlbvfLnypB2LksG-1Ucm)|A*6gtxK#L~!CU{XG8`$E&E+XXUma5HrJlh;x zG4E18q3%jHSMljmt9LSPO@Hm-wpS9{sj+kVi-!4}?%ukXN^aV7og=+bt9gMbMeI#j zZ&WCI!gHNE%971HguDFav0d{3G{_Xl0WsWwCYwKB=+iuWP~T?z_j4aw)Zc@FUUo8! zKm+c~$xml?PDG%x;-Wx--fJvvhEG2iYzYsi1R?u7tEb|wZkn(QL|H&obfXxAeJ)Sg zSI&NxeL(Dmy}S_z2z+fp8TJlrWQ1T(j}HvcvH3Zppn$jR_}DcqA7jo{@&Jpq!rm_) zt;lhcLhFg%-x$lA098X)AW63d~}i%K#=!tK z$neYa{&`EY;Hj$__(tUuO3ZiTVh#}+@Bht&ww6+ijqGnkm2=mnQ0OYk)iNrVzt?qv z_8uB9$M8}RRrW;to@e>UWc9b5m2mX_^nqsStt}t|Tc#;GhWMo%6Bn=x2Iir-*?<3` zSn`St^d0MC=@^PcA@L7;jaE^S37eRfl1zeP>@mNQ;OMOS;PBUezknR6aEG21K{WZb&LLSE)NiRp1AGoDC13@1z(aq2|^%g)-+cVAz@Ail#6z>p?|Gw zP-W=#2dmVvU`3)}c_r`uqLx|bcD@nv6Hm|e&SlpoYul~a&8^M#jpb;!G%O)RL6u{b zS4Tst#JM=d4FoZ#;OB8z!itcwX9jCnazS}PiBw$bI<;}^Fd;kn1kPQnb~CiH-;|7O z3F_PEs&;DgbK4uxdmA0gEv$vjX>0Z*$&YRHfnC$OrypxFRDNZq$4>TqUo|UMOmoll z?FPyMjg9oqvBQY^ARQ($DlCHfxbd9o$-3HqrU!=SK%(_)8uzd^_hz){OtQf?@ghGv z@V%5L3pOYdf}P97Kr51toy`575$bbWG<|M2*{Jkqb1t+K1vWXcB4fex`jWu{>^77V zWDUwYtDCF69Wy@@@c133C$d>F6ULGD)AX#Yc1HGW=t6vK2C*FYyZ7NctHa5^6u*e! zjt7fP?pqs~UfoqW*h>m_oh=!)#o#n#rMi`b9Lr+g>32kgSl<66i$lb^r!(DI2tn8O z0 zl6-tO?SnR-mzJ7m5N>hMQt}3f!nzjnTrH8=TFY4C%d*Y2mz{cX4SH zN``Cu$TYc9!a|c$58YztjTmEg3S#D9B@)>erko4%uR$8N+_9w7(tvw&fJNC(;A6gQe!%M>iogf>L*gp8S%mEHzt| zO0->(f?Jf{th|j$YS!Y>`Vyl^j@n|3iq+k0UU8|FRl*)xelFf|i|+m&Bk#Nex|F0H zSJHk@EZxJT&Bl+`9n>0Gi9^ge;%=T1+jf=!YYd$#6{p)X?TN}jvU`w81e%PpEKi>E zGRTY@YL8pg-Zn&?7ixy#9YIqnJ4g?Ku#+p;#TD@Q$+iDVZ}+Nr`^wN3H5e?*e^Q$K zWRZV2MG;(H6c_$}Xp;;DJ>% zlM%23KidaKj<3L0P>54qMXG^}%#h}Z>L z%n{y5#iNsF<(`4{XJpDHs6CKkO&G0FL~_+o7)++FNY`Q{U~;I1h0m=$VXel9h9L99 zJ0lf=0~1y^PgJ^oCDTlv%B;R^nTKDpAuj(u!!$9Ga%|AiL9cK%AzuvzX_|AF9y*BC z5E;r84^|&Any&#dU1pq>qO8*%g%&pgo&QNdv8O?C(1BHy84ptEV&J;%9=m`9Zd6~@ zaa+B!-Si5kF;3Kf2fQRnG?l!@P?B1nKc}vE79>`a!KBSF%jEN3daD}yvJOyZ=jq%JatxM1C zz}X|gL!$(+Z6H*W4wiOnTI_am2m|Px2fNk;y0*wM+ZKau=7PLHQx~IVNF=BKGM^xs z3t&9B!;oz2oQgOIwX+26f(~;|4>Kx(Q+%Gs>WTmKVgx={>BzDzH0j}n0Meks);Lw?L!tSH{edF~Qe%JuDoNDlKi5wR zSu>@0!ph((G#3*i9#K1S#&vad+Y&vyA9NDVs7b1lRy52|Szl;6H`dTLeId}wXj$a8 z><`j^SW-L_!awr$(C zZQHi7%eHOX-eudiZR|qVJ-2U1N5_9oM`WygSx;-t%=yhR2SQbM`#Xz=)o?TxR-avA z z>y!@6TrHXUEz4ZC%U*iuvVwbEQ#U)+Y~2T6_+{&L!>DjgwjB5TV{JaZ9W(0qg@Kc~ zO}vt|yrxNC1!^T56j=A>0Rwvd3 z#Vk=PrA06Py=r=iZJ%*1y%3qhG9^=7sK8T_4pDSrXYV8{KX7Z5Z|0r04ioaSrk~oO zql5c({#n&jn8Crkg2>j@R%!Cpd-dUhec8HF=JdY*Re#85ikV;lWZ;5o2T8q;QbXdN z6Zf4?wy#Z$Te=F)z+in!M3u7Gk}seQ$Y#EKVd^m&iCz^UBmx7Y=X3FFKDs zyy4^iQ)rz1k)pGM8#j*%Hm~JrUwPjNrbbX1f8i>mc`7iL%$+SvT}&cZ=bXyDxWp!> z3RShj*(-M0e@|h1#wm3TVTA#Qzh1@M+u`;a^Cz6l0MI>!+{2VgX>#U1{UW znRrW|OwT>%?gPwBeiMzJ?ouC&iRhS&Yb^T_yhJP$Bq8rxslRio*M1}_?^|m9uT2OV5Qm=t zf3uxGSiE=ad};4SC1fXC)FPeX`0a^7W7s#=H9*CBoC*}`{O;UtW_K#IceLnec^CY0 zEqPqZbn0S0>U>;CN*aZbzd4fzv{eFz`aEPKZe}-j@6>?h4eJy6MJRn!Us+z_^ku1k zeqM%Y;Z8RZHz6FlnML>^NR~*hwSVo|4J&|3ZoVS-u zDVD>sc6Zt1pZ`jcUq~x(z(oFyCjM2=|M#m4(*G71mNj*DHZ(W=KP#IQC0YAL0R-Qy zcE`j{77(dLAN35CcH(Of_XqTn0A55;ID zQpCGL;9=@2>%KcV%7GLZpMR$(qz8pOhulV*7p&T&F)IAi;BD8q0Ry9qOFuD2KF_mEAvYk|jT(Tbzw z&~X@6m?Z~Jbnz%CkZUFG#lvxsP8I2AhiCMcL{b&JjhVDL<10BTur^Rf_>*vl)*+cAsU87 z(R{5qu;%@*8e6@q-<~*(7&nZl2{=!zs0{8`$rip$a2kV+&P4xQclSt$v~G6*R2h$i z>T-_QWBcPQi+qJ_+qUnvWzL$tn|GI6-?FjCn?6Wqbg180kWF3lnb0gR2d9tfdSRPN zaoaEztLx^4hzBCrI;jxy(K!f9Z&1z`jlbwk)W^eazU9BX{GMrpz&#-_eqwQ+)rLsW zC5s5xMo%5WP@xfH;|!PBVj&{|@3KQGoRYIXE3_`L_kd8;g*M5`qs0QH$fp@~hu7ha z|3YWS*}*J!e%1O&|6`Ul{r_*l{NIK1|BA9!-B!m|MflNmW5-TS2~CE743us8##Z5YX@2SW5&sF}i*U9#hF~kh znlZfD$>`|I*qz$SLihi=hxt3oz_mXB8#6@o6KSa-q!S`9DkRLG+R;8tOHGw%=Tmy% z3`<$eT5Tj76uM~#b9bokQ0B@W`kuL;35%KqlPlx&tVY#o!Hm;nQ_Ihb@<;tk?XXu5u4)soJf152z< z5JN?<>V|0t@{9BV6Du)X^+=Q+XuRy`)YlN~s8HIYw8D%YtUUuX+M>wXc>`y^O=-8P zY8qfzAaZBG*v;Qh_(nmvn^p5}maE{#Vj!g69BAGhCk{q6F6s5^4xfZJk?QSftT$4_ z+?r5{eRnlGDwr3L!abe{4pXfsN1Gizx;W8b#W1o8+l5JwTPuJPv4FF_N6 zaes-lQ*S@*qVnO)d7!GGYi^Go= zExMCYjx!HfclbJ~H%t@z)Ev~t+SmVTMka@YV*z2#F-{JAbqWPJ^l3VI-i5}=PI>|j zE_T~haCfyinRION#O?v6S~@XLi;(R6s|eAOyh`>~Uxxtv z8IYqTA4gOp5In&vh)6JQ7w+Bti^tB;ISv(hS{SYyJk;^J%+I92#Vjw{01Jr+v&qPUw69wpy{4K>MNHCUUv@|yyAC}U!v>I`ZnGvWzA_; zcV>-Fuss>X669yxLZ^o#44$Hk;t@T0M-Zky74rk0I_Q@`V8e5BDgCnDT^w)!m0u>Z zd)G($n_VUa@jtaJ;{U%l(EnZzrKoS}Z=PWKnP5+H5HXU?A>||1G$Dlf@!f4wcSiIf^7>%bK<_BojRhP3#aV?{SMoX{n-D7H4Z-dA{mrtk?M@) z>Gg!2`tAe`@&7!w`2$ECMp1k)tVR;IlhQpNIB`cVwl7AqHxS72hby&G(BW6U6-45T zA`T};;#V;gazu7Oqz>E5={y`5iOq-psi*i2CjA>(2pyvKp@j-}E2a>cUp)|8_{R$6 zbx%YAIn@s28DfQ~9lt*aVny{+BrvtXb`g^vvn5kg`Cb`R+-yo~nj+KrQdPPCe*Fqu zx{WNgR#q<~JzF<3x7*S6CKHIXDBrf*XjNy>?KDPd8;0qap=L5>#k`bSj#0HD)2$;{ zx+XR4vSn#Xqvs?{f+ZM>6pc8~og$-gN=Z-fG;?2L#A!U6L5KK~>p5H7{p`sFx$JRT z<($c9zLEB|@eemJJs|1h0HWM1HAIa1L$IsOM&mU0^F!nL6r?EV{khZ(#WmK)Kzx_c zGD@}&9}Xa|{YmlR(BA`%Qq2YM=S`oJGTE-P+Bv@tJ&N?$KYtG}Zl!wLX)m!lx|qdO z4-ALb-JuGT;GN8tTQn3`c-o9kZsgRj4w8+NWi{al+Q7c$OT)ZQP`NB}o9WqEzIi+&A+?P+`5M8sgkr?Yh*~x2nh~xw5A;wyBOq&0V6gS(V$U zYxSzst58Q-A3H;5?>gE)b$-cky%2ZMj_-8KCB#*U6I(V8{70zT^kZ!Eu%nL_$Fe|% zKbNUBX>0gif-Dk!q54p&iqSoW zS;k^=>(cQ88uw4sT-=A3dORPdL!U;P#Yt@amFD<76l_IKKhM8xT>QCQ(|oCm8_N5x z-FWLG=eP0ZM3K+Dbs3P32Ry!2vXAwA_B#?5Md2 zGZPeC6(~i_s$=xZUv*DYZ&z?!YlNa>5$t{=l3cXE?GFGS7vS&X{djAI-v^m7Krl&i z2ZSM{d%>S|Yydi@ZzJ$0_L+bcnttr2zNZ`7JshW{7-yD8ka*z}WO!de4(?6O-Pvz} zrtR*Po`$vL~=y+L`ndDATa{BYtpDIGatEc(EaN>Lu-nSur8mj{ULI*%5Rh#DsQl zrIDDUoIVpw0t-5fGgcg8`X%mW^?`U+u% z1MEXj!YE2r$CLt-=O6dC?s1%@F}k0aUxrm=8%{rUJE074^0%su5QBuDNZg9~1Kg+isuY~r=FT$QN^@4KzLgaj8d3k#%e5B5Kh})-E zdof@bmiS@-O%IvJ-4PogZ~hoRMu=?lh$OqW-~V+SH)6;o>;7fkb;JHo&5-6lHbZ4& zLp$YPdo()}Lnlu`6BDQ3S?+&##U2ey1C?de?;9FV<|J_pAVDBNFp+c{kr-J*L4vB{ z91s~I#QuTFDQS%Ksi6#Zgr$`jtD5p=3i%T?75gjiS?uvmaZ<)@es%AD^G5 z^FeNFzoWfUai{{`RddJ!_H{aU1HXxPN;5r_+e(S?+q(YCtMsjh-W#MV=YzhRRP?WS z>C5RU;B%eU|3#wXb}|pYJ^rsAIas~?g@;1r4m~3ui9cSd9M8cYY;@@M%*t7v+bVm$ z8rzTn3zT#PY{*dA;;~B^mfo{-h}i3->7V)`sy0Q`r%J44T6hj(L!C*1U%j? z+}AYQi<|J#iA<{l#!ARbKVl3>^>;Q)?dHaKv>PgG*1~=Vb=6SbnofQsIE#7Psh z26T$L(T$)2ff_Veh$-x_Q5BZo)R4fUQ&6W)aS#=Qn}#5|e7h1){%Lm0hH0QhBV!Sb z{G-LAAVv*eAnW~If{8M?I2yl>2Pt&mysXtkkQ+8nnpEVmgLuw zdjZ_A8=+DX?ZisU@YSYKg={1Ov{8hdH2^NI#TF#9h9w*3r%Vs?vT-=+u+lwBP*teh zQtqUaNhe?YKLO9I1rKtT0INM<}&P9YUJL?<`j(c;msHO z0Yj>|F;<%(josGPP^P$hbq7hyXKzMfWONR+kC-Nv9BhI` z+lYvXYbjV1GlBLccc%uk`>3R$ueC7C!gW+|ms-6WNxP7dVZewhY~#2!LLKQ&Gfi2X&$>UiwD;6+`Gv!yU0PJJM@V2Q3iO{ zHh$z#_-sLlP&cvUADKtE2CF5(EN&w&(&69J-BCdUSTZLulrzTS;W7M*6)1^B{tjTb(9?ID3_k}Hlw4Q^sys0Ce zgKo;+`|Tm?3fkCN^2Qrh>MCt2tF6mz46Jz*=_Waz<5&;;^vZ3<`Heze?UQR3JIQNm zji1;p7A*-j%q>8mI~xt&L_DAIE{{obk>_tPRBsudn!TF!5Okh1&R2=-fPBZJcSzY% z<0UA&!8El3*?ss%(|Q`2HK=UXAw3=dYP)`G9H#Vg1k~#L@<4ETEkxAxlh-$c)!Poo zrl_a)%dLcgHf`PjR+}$0^Zaje4PDHEklyi4*qnBwH4YD+&^UI(*-rP<8>!}986iyb zFAQ7O)!8_Iivf>X;S8Ki)g=8^07^;7YkkL53g&E`(M1Y_P#F@4}A)m}U095a-@<1ej8~JDblh z`-lTICDADx_azvMfkxAI zW_h-wKwb!UefWYf>kToTpPA(LZ)G?bu-B&?;ZBU0Vn>0p1Cki%CHDI94h8j`r|h=FfN;gQNqQ>d=Uf$_+}a}WM8AN4yozYwJ^>e1FYx_UrA zd!%jf3lQ@Soq7^mqM=ozFxN{>^`93)&>9hHiGt1`sA|xr8pV_kYV8kl&4)G(d)XiW zx`fM~At8c@qGS!B*`S?1G`45s*^Yt`!-WMU#?>Xaek~l}qisv{jiEtXmW;P@$xU@&Y9n}5C z0H*F}rV68`YFeh&fGyP!325$I8`^eEW~1GXk=c@&ICBC)JZB2a8=JIiObz;)l~6*A z%vc{}bJRkuUb5c|wBXoCA#^JGrSqs7^FS-qLxV;40XdjhETNr_qo|8=)8ZaxO*HUy~ry`^!kJ@LH7C6OQOPmmIV?dsidcNt93uK z^{9R8`XXphYdMar_gbXq3MkM8T_O^0Z>aeRH=Q$7;%6^QDQ;Vh)mI(Mn{*zy4PjSK zX0YZ8%J&MSjak^eesy>J+3%n?%Wo^FN#h#3K8mv=&T+^1tC;ik`I8q zEp$B<1v?hJ_gSy1kr$DDw(STL)gy1^g)>CGfZ&yPi%8xNS-wF|_AOgS#UT&YT?|mz zWFe)M3>7c(o0~jGIHi;b!CMC#DQ(a|*TFkZ67{bsyV>UqBieXegC)jjMv^`ly4uSH z@Z!kH6?SGEt(v!u&hdSsM6Wr3T)B*ay9aV#s0Qz`3i{yaN1^Ygh5su=u7vnL?%sMVH#}q-!e4!r@JB!9cK>D=v{P_PbtxkcGru7yy z0KgRb|1>rHXRXeEo)}^@Aa!t#QGejQry5~EB*F>f+Z%qZOaYvig5ojI!ZmgCVL>7- zu|u~3*=_>6niv~uc$;79t$3}fT4FXfG_b9CG!zyP;=ES1YRs2QYqYL?cUiyunFoQg zNKepTxOcsGxxa6HcaQGfZ@2*rT8>ihS=mXp2o5jlddX?jwUm zb!};LaBHTs{D^upW4fi88NR~cWZwIX7Yr&SQd!zRFk>0 zh7C&6rW`1`DL-@@kkq;s8v|=KYU74m4P1yw#JrS`ho@^t%>+K}<#P;VVBpADb_Rp4 zy!TvxyHH`|C>dIH_->>PsLC!~N!WjUjs6D9BeA)fNEkPfNU-K{{VZ$9rsyr$x3v6%{I?&JogfbL}a zSWpsKTB9+f?D)K7##7EHWoq0XmE?q=v1@vj2ADmrfJCRRIh;c(1CK~^)7OYuYKORo z`sDy`w#4_U&21n7Z<-5lZxm}If7Cm88d4x)D<)~V3k^u_7~dx-ln@Mub7T?Shxg`U z66Sl#$-=LOl%-{!iXy?T8-N@TTjp38;#T@=+YI zhVly)hVqNm4?jtR(i4{uM7W09s8{UISS#M6$*v&?fQ90^_uE0~5V$KNRvb`=@CyPp zC7UZ3FB0$1dP9vGfQs|8^H@qY6sepiK3s+R zHPEAyFpv-gXB&IV`GfeWCu79)oxd0Nmh^}JklyIH4yiIOl)m6=7e(jSPe&(umg21o z;Q=1>XQ&V5Q(J@0f8*|E-{imt5EAsK-@mNYjJz#)co8OR(UBKvu`%nQ{Rc|=hx`y1 zGz?LhWDY?x5z1t|RKjh%KtfJZjU-KlqPYN&+)gE;yk1=1qugoj(BASnSXH;?wYl+m zh^PW(P*HyDSQZu3@@g-M5dBK#!^dKFJg1`gHlxwaO{z(uYM#PgJ107-J1~>;VMi=3 zYSOsMpDt2#hqDc*GGn_N(@P{aZcZdMw$sN8ufe3>#TM>MMV14~*L5YkdlRprrOD8o zW`hACA`DxE0mSTGkGR}*KPwTj@hq!3=UXECoc6+U1+eL|88fFDFM*X>TcmMbCY>U%=hH-d0;%+&aG_ND_f3al(Tm{itQf z=btm-GAY!g15pKZRo8>EA*TM%<(qb#5X}hY+FwWJ-CNK*=N%^Fok%CecuqQW*7Y;Z zgqYmp-&;#e@1T}>1KWp?4?q@WMN|{>UOr2{1&~r+-2`9gZ?*y8woMKR5Wh`py{<)K z!6*WiSWhTd$lIRIfQ@Hx&I{eZhXdSeaD5rY)Ohc-yTDG&0p!}pL`AM~qZ`hQSl6vU z$(KN1&d&(pKPCs&jdc}sh4$b+n5`JN0@0E|Hq)eOg+XxkO#;!jqGUKt33Hk)z;TK1H`YSPPWKH+w0(TV{0Y%rFC;v9{NYQU`tGjevr3^Rd#hb9#$ zm2Ysw#N-$PV@{gD-h@;8UErb}GZ*YE3Nk>!1r2Eo;RQTXQgqHF^p6ayaVB&#rgA5y zJF(7>3~5J9{w(y)C=EPQk1r<-`q^n6$oD)`B^^_FlatwFTvuXBQ9Vs$3M=fP4-fB{ zGzc@s>UxH;QOp_|e%YM4Y5j!{QqDR=E9hB=g^ntY$%4<#*Hs>L4w!1xKc3`n47Q@P@j?QT8X&iAfey^aY<@=uoE% zwY|yzVxQGT><+@kAp^}ky{UbU9!|@<+(mE{>%b`4vxxlYL(HqmY6@bHP#x}L?4hQ; z8y6Q!=*r*0iYV_>65Zw~(mv8+id=!mzDAZXx3M*YyTdm2U4&^qA9^__l#8)3{FqK6x@VTulB5Bw30fsu$hNH1sps8v9< z0Lckn3Cp5nxA;c9;^rU^IQYBgABEPdWn?&XhOFx?$3x?{cEBXzbqic3sce&IubA?v z`4E~)Heie%1-M6pvS|6Sw_=j9bP6yh2Rs2ev*-toMx;|`LZ}_@uyzy7na;D8(>sMM! zU4+}0n5X(&ms{MdS7sZ8qyMdPdSJROZHKT%``GI~FJopeXpc@he0*1(no`5LFdjkQ z@lf3Z#dsxv^#m*5Gokv2yslTVoU$(9Gr>G%OklF-IgD$i)=pM<(U#~Mf%L{S)*AFT z$*Kl}Hpk`3t?1IR{SpeDl$ZU~yX-~5%4+MhjC3wteUHmx_MgTfSzc-|1~)8K(XrKU zFO%=V#O)iox0)};(^@m->{J(zwD-MH+-Ktsa_iVn3 z_CKA?8U8b(|NlYrWh|XtOzr-Uh&&{oQ5skfA!K(beX*QSRw8ACBt%<73o`TK&#>Uf z^p3-oV@68PHu6tq2>)MvQahwS2F8pqKHaY;jQYFyc>p(#NrvhBRD-OBB%f*+wAOao zx{QWQv)0>|%N7+Xh}|g{h7FuhdF@T1>U2_)?K!72>JvSUSA~ktcG%jg^^QtbKi?Xu ze4=>Og9D;@KCP1PO?`=5t$`t5+ut;X@BU}wf*3z)zUwPCK!#odjB8@N?lk)(cnAnpPWM%4Pgi8$*eq3e_!IbB(a?GkQMrMkxqEI-hPj*B%fGm~G61~7%YCesONaUD zB)9DWSaf!p_Va_(DA`bN!6&wvA)BGJK(<8z`Vy@pqICKZPpqdDhTBxl>SYOe65(@; zzY5+&i5kP}%ArnW-eY@DP=^-`YEJz6(IuY4qmbLxuq}JzgzOE&=4sT5Bg5De>IPo$ zLFngO5FO~+|3>J4(0L9O&72aB#dh)@k>xngL?h&kS#ig0O3CT8=x*ND23q5|c<=!? z_->5C!fhnd9av#sU6Z5M^=OhLcQI^zT;|Nql!o^;hYb}S-)4{c>4Nwbq%!pe7F9}N zVs?+f4?9pW2WL3fF|_=>zt_(%AmyF1%D2z)V1G`3(!JiQ@o`^sUt_^p5^5er+Bam3 zhLen}PoWApCyD`c=ncwtyX7f-o2x!CW1a8t zW+s8uAl0DW07(xz{y>d>~;Cd{^Av3pr?#jU&xov%tdC$OBJ+f}>{Y*0?qt-F^*>yk>> zp`BsH)IHCY_~eY_&Alga>E}teJ(Pj;%R}3Bm2?Q6Vwe0xhuI%wm2`;m*{7Vay>Asg znui?BNkZKbkhH?IlApzhg8!^ytPWcTs`^1!jb3|EUSj3YeFm58qJUUjr-o zKup-tE-n*Y-D=DMtSr!?lCKqjG(^P~U(+aFV>WubsF1e3i^3O$<|a}UwDD0<1euwd z>2?bVZyGx-B}tm7mX_gGz=EjsX0vG4tZ5m~Y0HjrL12&=?XvH-MsVp9vBHESpVG>Q z=O}BdZ)s5E1X-=gqe``E%IX*q*(k>HV+S^9zZbB0qdP+2Ty0+^Egu_DPs3{Rw<#sb_J2e?Zm9R|8FXY&jjnb=N*`g4{0hDTG zYvXyfO95$e*vSoL?$FlE@%|OQ0-h|GrZsQER{9I`$UTzmNrY&6Tm${XMm(;u0g$3j z!O|nDJQ6$`6tYWs)4&d)sA~u)0Q&J}DtbG76ZJ)K*xXRlKp~A=h^QXvHu7nT=ytYa zt^ySzk_1HY^rt&A^zgTzA(4zetP ze}SyJ63wg>V?34({JALf;8|*EWL}?#Tn~tKbC@Hvn_sqhmk^;y4`?B`ve&9E zL{sY|k|eY_0-#(rUq@k?N@q^zOxEwhe4(%hXn8pf1e=#_B?1OaSgq z2RSS}xy(Xma}nLFDfySc23=d(K%-~`-0^eEAq!WV2ex6xV@(GQF^-q2Dy}a65ayzw zNCsJ+na;vzY>#}c=-x0W@2CuBLpYE1Y|^Dn5MKH*oF#67FX%LO#vbgc^Fd3l?@$ zS9>qN&1WnY;vbQYx^^BnlK3s*EuOU=3o`dhXjTOE4}w%{R(&5OC;P{Yn;)$rfc$aP z*l5{=DQESm8>~sZ+{qEhbUbT$Fa5y&BeieP&jtd#@{zV|IGwL^1yyG{)lAeS-~iN zW&8IpF@!I;;5@qrcWxLFjtwTkxqQc?o_~gyf7=!WC+cqf$}}<@I|iNKmvXRv7WVY- z>tH+8QEvJAzx^yb&#l3D0k)4;-`KH&x>rSYza{*IR_+vc#`B3kxr6-SS&L@pPhr1- zf9Ci2@15^7!G1D+3h(t{zp;O|_t@X?!TQGS``w>kgTVTxe}aRU-}Ij{`+rV;jH&)f zjN07!iO>c zzy*2czrki3>#q7tBc{7~`Ze)r9~=!rL!eGzY> za7dC;LKc$P&r{>k#-7Qp!GfoQR%WTw6Cwf)p$k=$`*F=Owo_!S(c7Y*(u$NeCW?uDvt9S zzW3MZ2rMr5GyPgXnZH0eMi11>wYZo_Rc(j4bg6nsRAERG)U78yQt6QDFa=g7K$fOf znynaTDwYC)ok9LAF>;gWRr zZd|D4Q<{YpR?rD2zNtDgoU8og?!nS^DI|j$MTHek7)>+ZW`8TNzRSE&U0yUJoQDY8 z7|5{=H(S21hgqvbq7ch*D7Q#Z%4e7qdbfN4Dn*wKC7KVWGS}HW7??%6OjSmS-0>Tv zK2+1ig(Xd}S<@e!1h-K;T)T|bg#L59ej@K$w1X%uQvGD6;mO~vL>=AgXXm(-KN=d% zq$a#o8TN~GsJMPzj<8r4k7*-W0xfJFAbLVLNQZQ;WNJ_&zrRe1UxbX3L(2&Y>oKLu zffBN-?wUXIxBtbpiz0O}{!#*&tDJsaq&!GcwUAR3< zoWQP94m6+LD|QG=T0g`nYrIqz&wcfWZ}nJj;*G?gUpINB@Rs@WU$~2l)xLyZJ_ldk zh~46_^q&7+?%`d4`T!8$CMG$})xqKrzI9Pvoh;yHn5%U|#&8@pyM zp97l1A4?rVf_PKVD{Kgh83g||jR&CL*_ z$CMIb$rD?UoXHd(f&PcIpXkO&5R23h;lIdcGij_BsHwumoR=*@DnOP`Myl(Q zMs5dy8T@T&bCwCB*Jwf)C-wQ49yB(u*o`B_^tsq*R9$b&$pF-EyUsc8kuQ`OU2=ES zr-h6jmRiG{$q$TOhj_9j+|r0kGW`69q8-U+QelhR?c}(dzv`5u*yU;Clyt5Fwwd&ryyw1F9%S zmR&IEi#X~^LD(16Vi({H8gdSohSL^NeNeBJpg5DZH1ec(i_69XagU#91>~?MF3&N` z35~_+e6xCqrAE~^BfVkzm??fZXiu{nv*9fc(;1WG0`OvTebYkeSI#95;EfxVYS7eQYpTjJ< zY}`WK&FFJTxxFma=yPZX&YSk<1(MFJG#*%45^F=I^}xNqKy(FGm81D;){NFQ#KAi3 zoYZ0|FU6Oj<3mN4Hq%pu*)=d`oj@aXrP_i@q)H}fOzBWyxkZ|A4PH4_?ehL>&}9GX+xaif&NBFFUx8cN!B0FuioaLcjgCyg$x|@dy zaoH?m?t!9b@Obz%MZK;7p6APS zeWY)+(_?UWWd8j^qMs?Y^@-W7Ky%)7<2qDB-^g0glNK6`6S&cxP^vJ_13R!abGBMx{cYAU}fm0Bd7V>)(ob1#oDP8pbOyBT0_q)9MByW$fmnM94y79L-{4q+4g(9 z5H(6Ua;5{Y%Rbv!34P0Y(hI%!!nbMMwLZ?BVjcCQl_<|Au9+@}DT|TjL|=d@{(|j+ z{?IVT(f01iEAkp?#9SoGWc(yV)yEzv8*td1c2_elFU#^b9o3d8BRK?p_fafOa%@N+ z@tC@ZN&vPv;3U9Z)xcNOd7O%{zZwb{4a@>ct%Pzrk?g)x)_`#;6ApPY3!djn+xeQ| zq9PJS1|z+~!3Kx1pQYll9@E5@x1_Uc(&W1WRs&E9=r=6(cdYQvm_Xss%nYcPZU-j1 z(>(tmEoZ$G#20A%qT6~b@{zF~g~umqn5M^+>KYP`O@vf3SmoU&CZDYMQ$5|{^&|#_ z=_3#)@bvjlqt*a+#ouvi1}dwjE|=7*HC3(F`E4I~(Lcj<1kWe6g#$e(5M%0!^R)yuhNOP@9gUUKAR zcEoeFLqMjXkFO|vu%MU08sio}!h0#3Zgnj}HFMVpbS)BkwB>wpC&YBu3f(c56mB>= zIaP_-=b)$+hJ`Q`S%8fVLXDukMvd8*aa_3=R3l{9HlAOVJl+%={Trln48Ad-oGNOa zICjP@?$oQaf9hz~(^|)nxaI!IasiAaG+ZLo8Yb!H6@i5z(nGdj6_!tx`fdaZtF2jR zmwL7A&DOKJDuJf8$Pr+267j`d=b&!mQ2!gnHuHeu9qEktT=n2Xr>{p}V?TuVgkEZ5 z`38gUu(3z;B9I%y*EY)`g3diwhFiqS_k|G?e=gl=>8V;P|9bqgFk+W!9XCCsL5DEG ztCd2JJz`>qp{5!D3u+ZqJL2;$ak{Ol^*Y}86~B9HsOT>awIj(N)?SS#iGlt|L02Sg z2)zhF~L&9qO|ofqC=4CJ5xi?eTx zuB^?rj#aT;v29mu+jhmab7DJ{if!ArZQE8=zP#Oi@92KV+x^{p&lqQ%obzM<$ezzy zb3b!Eb9#JleT=nI+QRTkz9n_PXDbn&p<+BN5&k{g@u;j`h@WRlUBW`0Ukp#RB|4hv z&BnuqNW=S?D6l&SR1Z&~#0EDh1rB=pwBG$?xbI5|BI|~{uAqq*UDOeFso!13gmb;W znj%V;99fm%#gZSk9o)t+29?RR7FZ$PWCc}<8FmcKBPppo^{HiW@qy?~TI*(2Z(LXU zs>EE`Bj}j>a0Br}4<)2UU3U1YSHv?c37Qzh5e9h4`!2OIg|U(C4DGQnqeoYkG9aA?AX5tPGdYMsoHcAkU{ALTRd|&cOT!=4oDjtlHtXqo+?^uzWRX(Fq!Jwpc~ax6q0$te}$7nl79`#>;&RqS+tTtGoc}5V2Id-+RO7 zw!A2c=CU34fP`i%ca%wXjqC)fj7R8`)cAc18iX9)h* zw9$LVrc;ek6A_U+eN4i0R`p%M+s`_OwX|WdtmQtQ&!-=SMn4!9XU9r)LO=0~xPKAX zyUHQ#VJfk+(TU2heB1xg!$(~LMApC7y?J5Rz-(PYbj3 z9WkFTO*RibAg!t>Yn3K_-O1mOg?N9-4wzs}6cB#~Hc^jR@f9-d%4nGIW61A9rgF&t z7R)XX@3&(>q8nasAsd=WXw_*?fTe3jN;yVgNP5y;?Sl}@sGo`!@TcC8> z`n1)X4e^v>+gw&BQV=y_%@bzMHk{u_qSzzsGihGT^H=B;SJn(Qy!8;?#E0xao#Eep zEF%4EEa~pG`|W;jO(K#z&Th<8q%Gd^xd5cr8y!yKucfZ+Gy4tAJhb8H6mGIZ9bN%* zzxCLlgl$KP>avhvCBh(CLzPBe&i$tt8n%nMo9PeYnl$lSx^IWQr~E8TaOfIGU8fO6 zu?O^mYQ3>4-E0Bw8P*A$L1fD=+HoHGO8IAs=#j@CfZp4078<5|%jRitRccjDPBA>b z<4H%+?@@P>ox(Onufsw#qbk4FXX8C7l$j8|UZP^H(JRvM{Czg8}MrzGv1fIvtqn9hmaZIfiO2-(4|Kar^0 z`WaD}+7ngx3aD0;&d zeg&c`ghT@1=Q{WV6<+6wzdsSS_(r5iPpcsB-A~cm@*(&ipLkxfJ$m5t*CDjWIUv}Y>`Mf=LUdX*T?n8$P zqch}|#&tL^qu|eoDA2`^4qFBQ(;$+peJo-L9=fP&=OUmY$^2o%{}1BY6_o_#2Xv03I1yhpVwZ$sM`*9J)r$`o zLwv3vmcT0zXIGmu;Ce)F)?^)dzUE@+oIudY)l`^kK2&{xJ~leu;TkqhN!VK`&dZ4O zKB;^=vG7Vf-LSwD#^tuGB~{Ka2EIf*6ug#B{_=BXw@v*HCmTx?zDD$w{107r+aG#| z?^Ba)_&nAB?;-lvj?b$90?~gUQqfwfS02^pGNaP!F}FiQ10AU~7YP|mL|7Ej+?xVo zrf7nWZHBgS<%&u?fbSmkRbi&Zu_F!f#o#F8r)Or$&yA&~BMcx?8gitebq3?{i0W*D zB7KD(m(OZTE+ zykigU!cD7EZDZNO!mzUoghGZ>F6a7L21N`&8w&Wcu!{Ztz(Twg(FlG+&gW$H~rkihZ7wzKHB_+%3E{MqUWoXFHQjZ8WOw4K-FL zzi@V~FkDHaQSPh25|Ad1ZQ=x|=a}oKRZ3=D#st+f z2&ia2XXECP2P;e;mbB(W&tT1EcGo|^lUU~kE(<~7As9HIG7GavbW=*plWzsjNI*bQ z6ZZ8=LP;pg&04967ST-Zt`ZwQW$lMmr+tB5xa1ceW{67T(=WkjUxcJFV2jj%RY0#q z-q+(C?);n85VIlqZ2YJCuJRw9W9a?|h3HRXEisJn0sNma5d^j2QL5;RWXsLL1r4g0 zUr=6yrJ=yk`#?4)dO_VN_6!9DOc=ihigYjFa|6D+{U%%~z{|g|X z&t>zs*FRm*=3g)P*Zt~{T-;mPu0PtoR);+#K9Ev zLx~nn!=n=54Ty%Ky#(%mex{j3^ant0yQ>pVa>ypK(8LEHjc1HKWpQS1`TG6_u>)K4 z0}PQCyBSX3Di8U?M+nIE$A(z+cCW$?v_c8b>QksMFhew;WjX>HqM@FDI4vKr0#&nrmmLQ@J6*EYD-UN@yFr|8T#9zQ1 zy~~c60edvGkle6dSU0T@U?DyX<8GN^F4NH|NSZAG(@iXCX}Bu9XX%+ zc7+9Bk#wBS3Sr}1F!nRw*%-TThN=36=+mM>j+akT7_*F_5E2dIyM_qh zp-hrxbevSM$eRf-s8uXRD-UBTTbyteZA7SbpYixU`8~4S;sdCTeL2fkzH_?8@2q~4 zQzW(}%q77J#785(tL}UlGO}aM8je z)oQ!fucb!$7K4$_A;ZF``<4?q^;UVlZ1R)+s>Iv(mb-STzE?KSsEVFF;I98x4~XVh(afcgn`qRQmi6F8AF1MHsq}cK^0{x z!Odyof_OvyT#9rP8J26#gaK;}mi|bqHYS7LMM zv$dMrEiFk;gbOb(foznd;loep#eR>s#D+cZuiIOtx8Did zsiF<_6{c?%7qLGe5-D(8PA6W(EpPnMdcNwu#*wkRA^cb)Yko-! zBXe_?L-A^FfA1weq* z#Sk>SeOV)i-Z#1v#3s`Sqc{6Dczf3!e75LqUCmCY+q-_)E&W~gXKvb^<33nj-qTap z>WwP`Rs+|rz~o`B21DE_9^D}~;sn}mi{3g;7uHP^%A zmunF(fKV0rbB9oMlfMx?$=4VZQYzjlHdFwzqYLM z#$0=Vw{-S&lDk3O#EOODt|nZOc?y{Q(XsUg`3m{|sU|9A zE19j@x)k~j+y2a~YlHp~s+v0Dx%t$7l<|~tq@C>h``4ZvP|=MenAo)_W=Ku96k7l; zI&m|A{l$$p2*;~=B&6y2(pqLJYS9VE^Chdk>-EB|ZFybT^^Cm8`p1;+71Wqm0nR$Fa8CZ~IebjD?U zX3E%-v0`@J5UeIO?EP_FXX4>6wpU7P4Gcu<`gYw4jc-k7h`5Lu4C-S1QjrKCE2~F~ zf*OnAVNy+Zk_(E?QFX};`|qda+RmG(HKLOXSCsXYxrQ&_rMiDFU(ZohC7^m0h^owy zY^jG-ywuvn`w}i>9P0GgzZQbVC6%FhGbHcw?>2yF&r7$BVp4!t4z!dS4xeAU7|&}O z=N;Dxn3gybX>73Fj+!I^Z|;qaQoK>@kdTq^#9)xLhbka7k*p84h9Z!91;vnNbVI#p zr^~Ds2HATN8DQE&XAu$az|&{l+;TDzb;e$kb|O!IkW`b9+^}{%M+N0^kY!&%Ge|Ut z+G<>LLk}V44L<7IdB|(ABzdf}ptrY0W;WbW)9kcG@>2V-pjb}kAD(JI?s!6f+YS#J zgPo6QrrIGl28%o|1ciFJu*}e+qI4gX+x4a0w-lQapMocqnP_fHMygIJHTJwV*@saP zwXHD6J4=L6p^ml%{dfj)nqav@t{JL;{pK8wza~JeqNdTD*>)*&@VQI{|gn5ob5>n0&zn~;_`#9YSWI2!00DeH99QB)1QLEDEcWtPeI<^ZFv_ zWe)29JPe_EiBbk`W_}?Wy9ip3FN_&7EkV~VoY7Z_tvjgEy;8#+P{%EX0C5d-QNU#u z$x->G3fUGHB@;AeT9Ugve8^X5j4Yrl&l%L(>(fTu0?vx4-k;&3w^F#D^nxxP&Hn;c+Le0*o#B?j4kEH)#1 zrV##$IpCAg9&HeB9?w6CG(HAaDaoG#WtotVl}0h3L{vVx7l6`nV89~L#Q+lXyj57j zpqpnD3kGqL($7)>D;7XO-4MUD(Z%If^VJ&uF41Ufpnjo_YFfFYKG30F$L$rDe%}z?1HT zcL*Wq1Ay5cp+DBqYO!<_!RLs_=cvqdp;VsLt5OxcC=pkOW#TFHgF`$m^FTVLIn5M> zaMYBxPJszz7TzGanfYEE`z^L)sd3-fZm?v+ykj&^H>nPV2!xqvHYzQH6vf7`s2xa% z&MM@R^}whHL#sIRGjPQ6_uq=QKOVBO&SwD9*yl-re^2ch|0l)!&tYAkS#ol7f6J1? z`9Y1xFzpWl!#Cs1uh0!CD()w&Q(HWO9ZKI;5glKl^Qym}q$h!bk1P{e^-F%x@d_f) z>I;tTWyewd(FZp|$FH}mBU_-d<=7m48G`6Y>4?D;8g`5q3(9B{n`8TOySKDRu*{*F zQ3kK=*B+OGU5(Bu23-!8u2hc+r&15bYpbGkqfj<&dVPI&fbxnnZW-=!rO~qU7~II$ zwR$}6Wkd0@pEEDx_|WIAXb}eW`rM0e)w;DjeKZR+(T-!L)>HRZHcc5b+ueoA$uN~_ z3mbrg`Mz2ob^72=e7C~AJ1fh>xPueXmKv1RhVTQ0oVlR;iGl+3Ah5E8+tRQ?+#5}uMV@$#U*Oax6jRK>zi?Pl{PbVs=SR? zFHb42ZUtyJ1VnHqAZ{#|*rYJYEirM)eFF*h!ABBrXW^1)XEl@y^M+E8^aZ)6jC)9W zM_MhaXJ(*nU~mDE7|SH8t(wRnYW)y0-^gK=iRaBUr{vGz z_z{LuB-JcsR&bC;f;OFYC@SZM{#GwlPu_5X0&7te`b*&b4O|l)K}C{)*zV+T45K;U zuUabB_zM#?OqU$rt0I^F3d;H^uom&M2ZDNCqi(hPE$_K*+B|5ZO!ZIv>Uq{AksGKO ztv(jS?~TtG$XWG9RF|o(?ljG}Sb(+lLr+`qI$re2bKJyvL*5>x8v6IY#VXRG0SpR0 zpE9yP{jL7}8u;te9e;Yf{age8#9pFQG(1sMFn!3S$kSw-5(U*l<;|04t0{wtpb7@e zR|85Gswonj(q$z~dyRXEvYY8Yy7juFFdp5l#)xA)j)Ff%4&G$REU*GAO#gMX8PAW5 zxQrmg1e{A1)rqJocC{2XFliC>eg_^0-56EFkP6dpARd`|6Rz9TJ}srKor8&r=~BX6 zuVHj@;J`$SK7}+$@%t(-t-|!U7tzHsAT29?n_8ohSU^%T7B8<@3(ezfM5D0e2)B<3 z<9ltRcF(j-XL&25GkIL2gR4_WyBp2zH}wvN@_v(v8FVVyl3jUR9qL+u05&K$OT1~G z;2LKiOU51YD>40^G+Dw7DG0YU?HO{bBA{eSEnH(mcfJ<+2}NksD>P!GK#pOZ>4?`C%5~Cg>+}oj`;nSDLV&0?Ttm?qzNgc#eVP5hrG<^OpCYeg)Zgn^@rV`Q)X*gqU z)h;l;qT|Kza`buKgIIrC2^*DRXqj8k5LKpr=ImlF9kOWQhA#V^!4S+huE+iJygj*I zYU~~D%-HFUjmR%6tcr;@HQ{pC%7aBze0@i_zrUa{r{+Vf&`wZQZc8n`OAc^EOD;D_ z1*m*79crIELl9`Ry7Q;;NNX*Z)Oad8&0fb;5(q88ST9Svq1M3l?)}2`Tr{+U_5P^T^y`Ap8clm(1nCp+=9`Q4r8NFJmxF4QPIjG^5 zX>A3Y;k;`%359t_Sj}E?)XMk4Xz5axoAXlGN)48rub||NVwDZ;eBW0@qP|5?U+zeM zv0+!7m|#Y;en7HGN`V;JEw$b_Lg+G16$u!dH= z<{5&gW(UsA$h#Bn*^n6Ly6VeXLU|yYX2^CQgF9Bv$G*>b5AZ7nz&_kYR)u#*r5z#^ zg*dT4jM1Y%Y{q>c?B@0OHw)QG^vt^izE{1O(YKyET-z)zS15~-kLnpt^eq4c(*|j1*D1wzjFp|m_I_A^^eL~Wg=Aqb0j>Vl$ z^4lBBv@NVdYq`naCmyyrzK0gpTK^f#L#ak4l3{K7H^-a~1}P?~CTrAedF6 zd?eBc)Bp}SQ4utc(wY<_@i8*Xvd?Tm&$fx%Em(6IaCLu{L{kxd0wpM4|FDmk7}_@Q zZjCN4Agd=OTtZoF5Iq+H+osnAm=~2Y$fET}!P?lojT%LXEsRwuzf&==0%9jV2{g*_ zDNKn<9ErJ^%C~`wkDAS4Yr#~@>jPJBiTfMhvfvxLx|@dN!TSQ&D7ofA;FNa+nMWzO zNB`DexQ83LdQOg*#&3j{7Z;4m{R41JZ5mamX4z~m0*vaj(YrL;rDx61%hVIyHcdc_ z$0zC2zDHmGczBJ%)`HIZP* zQTl4eX9}BHYCJOilRgs*5Mx_YE?qjhI>aeNKdC~CH5kT{!Ua#kNZiXF~@pxvB55Jy8`Sx_E* zpp>pOe4-gqTbalvbwl^yY&6uJB+9!s{o?2QpStg#cB^szl7YH@Duh;_Zj%42ed@o?NB-|i z2^czl-dH0iwZF>vOj3JsMOi}oEgFxdfnP<47)S_WXegzRlr2-NL*7UeC{T+dmC8|1 zXk<|hDaCwgzE5i17;kzOZ)1Vnny)39VkgU4#MaD7+w~CgoAd?r)ks#Z>xm`VgeLNa z)8j7r?ef>9>s8y;%eRlWp)R0|?jph<#=KtGo{%&To&f6HG&C7q7VsH$!sS$y4qLWv zff`^sNx0OZ0CV&KJIelr6lE_duP)krUG%xC&_fl*CrPNP9cQ#HBxbcUf{(5fX_rDPEI!; zHx5Q0$}~*gpPYRXA4f2&N?}eO+fKW)*BooghAfVh%<+Efv z3Vf;T5tRnej&X075+1edO-76Ak|&NZ=?QB0?!2lF6JC)h+NE+o=8`Xyad+Ju_AK?_ zP%MbQYjJL9U=JVQrk{3jEYrgp-?_{T$88^3^fT51IBhvB`qJ3zW_z3m*0X3iqYYYW z`g7xGTOQLhrsvEw3&-5rXBv2A8>f0fLy9~DfWyatSjlC^Flo^WVafC44mjtM>+3vx z1YM;WAIU&3BQ=!m9H`|%l|<4fT9I*?XP9a$Ewt`8jt9CAbPp9Nlbtv2CzKpNZJ%FG!L$7O1z-f@7@0z57stOr&fE%KmXc4XCl7zt?x0Jtl z3d-LVqv?e`39xDHYh;f7Y4Tje5!WasyNJm)0DaOd_WA_XfZZ9;9I*JHX)j7Y$?(RC zI|L0kbR7i1?`Og7QGnAMOrY{9-ywf40TkIu4otDM$25m$eaA%ILSXR3kVzL{$= zZM7S6INw1%uq@vL8T+n$ZOD$nDCP7t`}1H2gEjA%;;I&xs@_SZjvqx_)|zuP@yzS- zT3U+J4awC{UFi60(YOdI_J#Y0=iskMp53KAnr%Cmd(b&4Fqx;4n=+19hiR7+O6Bth zy(4Q?far8?Wg!m|sH#mS-C7zXNDYotPV2)lgB`*{Pcmhe^+*i{D}VNBR-!Rm`^5~D zyt>^~NU)plkW!_nVH~vWsz(FF=I6BmI_qicBr|PLtMWy!1YM%5q z7Zv0Ko;ib?mtp*=avcnqj`KzcT=uEIhF-+)a=zdlS7M8CMB8A#37hD5$s|I5o`l(M z5XXXvbd6MI&U^KJ=sU4E~a8ePnx|W>(&DhrO^7#c|!g<&2-HTSUi1ETb?@Qb-cTEgtbBm)X0vf}8q?S{YSMJ{) zoxJ}_;c-Of%(@3ed;61G;VIkyf{`Vz*fjnQ>V%Wu5Uym0=#!Wy(ka*u9^9%SX`>4Ir~vF zO_OO=T6ATtCLsCD3sKcPdJ}0rf;6eC6|KHvmynE@nT#S7p6MNrMPB9}O1Tv|AEm;$ z=;x9mWaa>0WWqIUf=NK_6ytFM+VoqqEv#MwOMe*!Lho1UGPg#5Pl0VGQba^oEJ$Gc z4g12{-bav!*Gvib#R_HPvojY6`W>S#M$Q`-F6k(-AJn}_mgfS)kGT~YwrNdS2f zP}_x7Z2Zk~gTg|9?KU{Q?3Jg*0+vQkrLhxuTUAcwZ@~1@>Br|>J)f|v;S>y)P5!B^ z`~-gCy#SL&bidCa(C{2@gs4?vrx0`s@*PicM3OR(u%l5kiBj%dpu$iRMvl@^Y|!x= zhzwOBX6QLYqhnHjtK@gVzA#HSkY||3rwi+o4@}Q*LTAQndH%jcJSSV8IFPeV_Sfj} z8!Z}~1#JYs&%Dzh^h|WzqwMsK+&eUrR>*n$xNj(6HLycQy?p%l7Q(W(YmGf$KmH(T zY2T#;9Uy^#h7taSBK%2V_-h&Pzx%qlk;OmeibULuOzfO2Y;FFbG04lv^?qgvX~86E z3Va4s35B z+{dTG7l@;~@e58Am83b>vQzbdevB{~X1PI3jzhj-5*~BnFM}$)>76iq3BW7=z%5l) zFjEWKPGagHznCF*s4$wF#OlgVNvuamE-XjhlpC6uXJhn3N?-Y~j8T?@qqx?5s=?cH z#`rtwljqnTtg-X?bAz`FLzE}cBMFkX((8>EqhfBPr{2K8JSVpCTQBXrn5E-o5B5O?MEBE1tm*RxES}*b@QDY&le_lzxkX za0uW1PGHnT6@-B ziMajguKcy`^WS&nAGbu&#K^?L#l+#i^PQB|J|_bZdB2CGw$e#R;lKo=hv!7&EC}!f z@Drhy#0wRmCv|d7#@pA?W-Z*WNi%t)O_lg0up4OLe+4<6VUcsZB#K@d1vI$nv#U!lc zx|kCbGi@ry;P^^ks|3h572 zfk0DIDn>}^v0t)n3~Q)$lI+&S7B}-;1ABc&hJ|56=f@A#iaxGi{P($Y9@pE;3n&5y_CIwS5sj;w$Kvl&F;O zAOkugm`srGQ~~(z(cfS4M}mnwiH0a-P;Fd|p0n^Z)=b=hIY*n=MU!&iQZMgcPgn0a zo@dKeb%-k~8#}5YLT?~!K0z>zAH$hyy|pYRuwNe)kt*Vuo$k+N;_5lc-pv~RRw57! zB22R>ThOdW`n{Ehmt?AlF>yE%Q4o7C!o@;?hK&O#tF~lSg7#xc3N9SW^#xx#u9qxh zTOplQ@jb#Fx6SOk?aIAx0a_GSg@S5UCvDN-P^$sJWvgRJif%b`{Xpy_io8*O=<@Rx zN=tmeQ==KD_!&?r`@Jgr040Rd8k!wuUuK<1r*Nbz@S!)tw5l21_p=s3LM^k~J{^35 zh~hsEj{Gk{#QY}^b%7z!X8m#i7N)(enAM&q+T)L|lb8KP8yE zd)p0ptq`hFR2%>~{|Tb=%QR-Oy1H+^-XFmF7#Ak(QfDw&8~@GCiL;{F)d$m=5Y9S6e(7R1s!QwmQ1b({3-1wzH|?Jw5)!QS*N+Mt^OR_3wJzKTwopq4eqe z1lWR>VAD_y5{4JK2~Q0&3Ute+u;fT&;)h1Je6d)GWKdo%cqDkPs+WYBq4M?nHd<@u z3O?^A!8dNq^^0#~ZH@Qk=I+cDXfIW657r3{kD>&#kJcU6cF1~N!A?yC`Dx!ku*VzC zf=8aABo_|>jYpB8V4E?J%~oSQtrDwpOb39vbx?S<0^g@^GPlg=w1vXDQBOPPifiNk zYPDb8`xDRLR$_KO=*&Ks0hPM3u5xW{ef3tx+wuE^>qD!ZTU1-+Z;zbwwxr<{r;(6e z!b;=N+d(A2=1`7^3qonV! zEt9eXEddRc^cB_)b9tHHm}uXU(H`HsOY)?yYL|8&&@A=Rm-`O4G8-Ay5R^=e+9lXz4{1J#C?wiQy0L<_Qe96z zvKfW1>3MP=&tq8ciuQy=4S(T})|G9Ia3g0NnM02_SO8jq)jC>tOfv3XdmkjI&HoM2 zD*^a;HR0FS9ddKC`m%wk(60nNcKs@#-t>%bAerbMqU{_IcAUBG{6yCilB4V-Tj(K= zIk*I9Z;(rJ61u#}x{nMUG?mlzOda2wH!(BdyDf$6qPvC2{T!Q-{2=Y?7@K*4zQgt8 zfpvO=aKwP9o56^{-~~JsXG4S3#e@_a>(56oF#7KRq4bGYN6&hK=Bypta=ho~@nYF@ z{jF|qFoj|Kk@&dw$@c%xobpdQcmKsH3;xmkfBZ<;o@EJ&Pu9vQl#wI^33AI07Z(a3 z+7%7Yd(8PfIZP84xL2|0@&@@1GvybADEJGA4~CH~SW@LHe-cj$b0#lyCf>*M%LQgP z3eWH!_9&-uO(L-^XTTEcdT95A_{OZARv#Wf4dA>F_SnA<2~ai?!iTa&UmbK}gZ17U zR_I0Rv^`H-%h^iIBtP$t+qAWEp!XWPAW#-$Bzfe9U7=lb<{>X;CZS%?0Vpd)gY|hW z-So?H;J~nM3#AYZRMxk&jU}P%By4A(*}dUCARAex655W)ujF0`UrCw0x^WzYWGriC zUPx};3>HF>IpptD0#xvxstbu@q_`FpW)ub>`W&3{4;^f^*Wy&o^-nj|;7JZe!9Z{iA zhMB;(;D~mxQZ4+t6nV~?J9@;S=k8#i@X6z?#yrJ1n9IlJ#CP4R$YGUaAHPt|YAiC+ z4D5xuu#bLBM;o277R48F8jtIcKIVU#KDkp?=s3$U_wi|?8882fH zcMl^GXQ)w2VO%my6p11AcaTwD@;iZl>b<4^v8exlwu%1?vVR$DZcx>-#SuaMZF*sR zVd7}99z<3@-K&T^^gw0-8XPp;Uq3K!Lm;a-i`?VUA;VtdTCu@OSp}ur%tug46dSt@ z99D!JCrX6ee)j`$cYMA^2BpOTo(AG&g=PI%uf;Yuv1AK*Z zc+Mss6Q(CVe%QAfYSk`7rL~=*NS2sQag@nxw{m04S_+=o2FOSx9a*}x^iyV9Kz`o{ zA9g`~iQVZ4Y=+3(c~)8aGCCNNTtt5<ZNPowtCZ zr6>egG`XGP@IH=QLqs8}=pAdUFH|s?@ioPU&|{yXD;@+dPnCx5-TD%Y5LdfK<1Qp@ z!VVmIWc-CG&z=WA#xUYhUJ9I9lO7^0U%W(P3I0O(5pjFzz>B;xk3`6nLgvul>w&fDQYJnHhH( zNwIlj66TeJ$>PW>^)>{}ZrR0M35$GlUXWSIVnOTXAxeI(idY3F63s6k!|%RiF}8d$ z={kpw|G6C8s<$XoTdCtzb^mal{WLYNgDbg#)XAAb!n^HP1%1Ju+E1G?VC~WB9f8}~ zN7Xs>_6&UXj0Eq4{BAle;epidXRsS<9jS^#Y&fT{HvlD~zn@dr$t9_XhNnps_l#qY zMVUb)k?1tr!FY^Wb~TW=%*rntty{3aWkJ%VL~@7%Y`UEZGkCUREIs|m6Vz9Gu8RGU{tq%~DPtFf z{P{o<_8+xI!T+pu{kiV^?YoSDjkD>eX7%YT`j5fc0;TVAC<3UwLQNYk8oC9!JYIQ? zA{t2Y32fpeu`Ct?PDn!U`i*CbDOP3^HW;E?jM#8lzZ3?!g^c(^8BeSnt}ENEq`KrV+7R6EM+y=)xkepOnX$`=uI^ z7P$I`?^Y#I7isW6M+~@OGW-Op>@LAfreK7~jnzwE1?H}|INH%x24%~Tf(NzO?Rdyx z;WTs~OS)~^__FvGD!a3lEe|0YDpK&(7WI>(j#k1 zt!Ph8B-Vb68eo>TuDc^QV9Qn;u=1=bkdD2w3Gr%e)VR?0)w#_vtoe`q2-dD@KPk1< z+AKtC4*0a^wJSRaU8dwqZ#{Ns>gZ>10c1$pB$TdI>2M__p0Kr$x?vh>Ac><}S82W9 zt6xyXXbx;1suLNcpNRQil(!fLjO(%11;`F@+M5hH(da7-cmoXS%elyI_=nK67Z`); zqZJI8#F=3;Goo!eFJ82dM-QWAIyaYd4)b|GKDaQ^W4^X~F$CAq4N5=jA0`CDy z2lj**1m9L4uy#Vo`(Hv~^X>34x*24TvE6Ix;g`+s@rWM75c|MZf+9Qe-X)}+?=Ysx zT^s7-KLVZ!QRB8;i9U!leo=f$vC|kl!5s;QcMP3@EPhEV0VR3-txy@y6CZuXWQHxV zPT)z>pCiyuIa_Uf1c5{C4KNMB|vYeW1w z*eZDrzwL9XxJzn>*$pJ^aeff_l1NNRLLv5wla^CW+)qmT&i!(pdU)#U`USiVk=ZsA zDFK|-st_3Z1%Lh1O^zFBGQBopk4{&9?@2UEU@F{?GH zAkGQe7?d?-YeqaAaXltZU8H>!Dso|N$!{L<)Epam1vE!-zy9rpkO7hf%>?Aaf;){& z2&x`l2t+;=?o!Oc^4mC^StH8C_<3Uro73=g*d}@gj==_%v>icZg#!!f4x+QWS;=rs zF9lk{0(26covjzqkd{hB3_cuAIwif*?j^f{Xk1`|%66?2qjy*pF$5uRDmfsbYOh+% zO(v@|Q`U`)xU^KuaX|1)7N#bFqO%NY+7hM6MKL>X$jqs&`Pf*agNgOTXF0@d2&#q(N%ye! zDZA+f&A~dlunGYx=$?Qc%Va8{?DZt)ptg1R z;c5NP+{3suA%kX23h!00G4_UX5noX;V)lxTq-F1n1PyR3sVaAy$c7E^MW1Rh>ujNnMB$DH~4O>1fT9bQ^{AN7qa#L>t8= z+!g2M;w4hz8`|yagfo(3u4FF?qWNqL*XNmx!X?x|FuW&bO?uWx9@dkkDVO$=k)dTp zocc27~npNiWE z;3Qos)x~6z!&`2^xm@^z>*bi1Q6s&(1Jb8ej06s5tfj!yseuT63VpcpQsdd;mozp(zf^c#HoTl%1!9Wf7aH z6y9+@IciGlw9H+QxJ|&_HxO5-W29;7Y`JU ze{e!R2My5S19pjB-mP}(R{9!(mz~Z`>7Xvyo-vRF+RxQ?6&FQzICwAvIJwGBMw+6? zi8`DSWaHW<$uBfa^%r88G{l72Nagj(K=xlkaZ5R6JFUn`bPLI_EO2=P&V~2ZSBhZ?NMmf z_3`|U*3DqLyBxZQEIT;kr^G1EF`-B$I~y9r%=0u2={{~hewDP2H5 zSh^_e*tIBW28KA8WkTt}mI~{X>+;xLCPVWWkm7euGuWj$+C^>ouf^ddnvSFON0^J+ zR2!aB1WX1;PX5V}1}97JUaGF9^W_rE)>v9f6I!K4*UcPQH-~UUd8B@sGcYENqJkj` zHDu|?2zO(}SQRl?{&1#@F8=$k?#SkuO**zTDURlG?&_krH}chZa{T!x{mrfHSazve z+$rU$rxh_};(XUGcHWaTfLvd|4=f zKtOp7rlDzlcH#;#+_oi~`HfNd589AHthk-PN*v2O>0*W=Bcs2R_Ha+@5TApT`zz zPH(7082XXgU)hm@C`!6`VlD(KGAI?Y3{JrTO9|OqQBY@-lK+phZ{QAfZI;}zZQHhO z+qP}n*|BXWJGO1xwv!$1B$M-a;+BwyvX&j@#zc~Qc}yOyO!o+cuOKij?{9nscR+-87Wn{~Y=Enz5lt` zPCu&lQTrFLua0|PW$jx)%6czrUm0+WRoE~r0n#hCW*0)6)ToHDlBHN{54XXvV=^7Y zUEw~G!PC=LLRWVo_jHq(IOIlms*vE_slZutx##=^Xv*4IGvJQ3q?AYV>_EMZ)!BP0 zT{6A6=0pwVsJPn;ola7FIiSrL*T|7&N}*z9q*qJ7r7Wi*K6r6Y-8MFjPK<#pIry5q zQG35t=1_T)sU|&AY*j z!@^vrBn_)*RbmPJ!IxnVhXaUyLPl$oHA#9}hD4zpm>e?Iw4Vkno-6bN>icYc;nt86 zPi(f3;tVCPlZ?|0(@eDZAOA8gh)nTzv->6+aQ+dO`ae6U{7tDR z%j5Zn2~N_|bm1#d&&;rK@o{lI9GtWu{gAsG9fD!TDl^I#ST6`=!`YnQZ8mkorsim! zfCs&+%OWid#{;Fr13i~FPGp1_PW06P9$`c)Cy&(U^VC6&$NbKj?n4#&Fi9AfmR-T9 zxSGZdT4D)n;7O0Ta-2tdrM)RNPQNG*F7@J0#mvPt+ozD*R+I@kGlaIS8s|pHRLrGI z^VCT;AxxJ{?wW_di+x2XMa0rbs1P(A5SJFS+}+IyLV*Lg_Of>v8=%z0T|cF#K98e6 zTBF}obx4n9EfxpE@~ClygKOLnYD8Zyd$?_Un03a@al!q(BJINrY7r^v%)Bpv2MO}i zwJ7By)$?5cnjDA!L$&`5d`Xm((r#Y5#c`@bd<+5k5k#*c^aI z(HY>OIfI#@p3kv7+mFOeq%lNI(k$}^cN?J=UV~o$j5A5_HZb~H;gB}Ml7L!+q(|yF z<#YCM@i(@~IAHm;^Ibz!|5!s3|JDHTcMbg)rG|u!z4hNOMYWoaGtvmkr>dsK&(2{+ z{J(~}<`$J-qpey@K%Du1vZ z#A1e`8DNNdt4Apam<-ylhA|lX6b*wUBb9ojDBZiU{KQ=t%T>LxN0d4Q$Wxp059y#ba(PRg#(WNIAQJL1lux z!1Jm&ty;TEi^)+LN4E}P)K5*hnl_3iR`42_jzw2o5H=DfsoFT6h{8R0$Q@4u+4*EX zY-ypV{$gHYZYnbod3_O;Ox>Quq~uDPyTQwQ2<~R)h~bqPsFc5v(ru$>XF(8OXU!xw zy(|D?DaUJrxFt+#c1;TrC1FH9kxdfeJw6JHITWp~#;Ot!7+CD1Y#N>BHJOyoVTgKg z5JGJbyYOHrw_Hn|D9vTdwwMCah}fB7J){S+M~9mU=vJ3>fR3m@`N$K;ZjH@QknYXA z-E&Z$qyZl~rpBg^RbIzih;L+E&lJLvh0uq&oCdfe-+|6bY5w^uG|L7xsyNW54F zM3=gSySlqJLq?&rbw6vi&3zki^iLI^u27pnr;r35eKZ*(ZTDQ3v#yL|fHt^6XUnWrfFJ*g;@&sp?7b=~2i; zK`HJPEUc571Lft>pQH3*MLkxi{M{?kM+@njqfK{}A`|1_dK|yu;IvAr_pqrn15^b< zk*`J$`ho0p>*GdM_cgWm7#9F(J#Vl^QzI#y%tVXy4g@V41YRoj7i1KT-s6TcB-U0U zE>``^VqCrD#DEEKY}9}4vEckV=#0U2zG%e{y(!FWrH^ue*)Er?-rkQmV7 z^bKzDz~l6dc4L-4e}V9=*sXc3Rjn_rkQA8t%GY-8jB<1SBMr?rLg$PQ3tTe_P=7YE773opsL=zxNE_QLbo zd)_7CzcR!K))Gv`p*e}?@-{OMQX$cY!$wX70`rwP0lB=>4mNWUH@DG==2{iKlsKhj zs@a@7qV7OZcNoB;S-{QTo^)F@JZid@WTRw1q-8g& zi5i>s#0{?+s%bZGjtYKLGJ}>vEXA6R!S+C+T7#L8TorjtJa8nm#&>gl7%(!PVJc92 zlMr9@Z4olQibM?ML3N<)KSGZB6QXaC5))T}2__yg-%2POYC#A7SS{F)tv`R>^fcC0 zxD)#k4(T_g^~!Iyn>3C-v2Qec{2(EFOth`9(50pQOY&M#LNf^LcQuKR}P(qe%_V;kPV1 zCbLyCUhW7RT&$)SMVAlAZPf?Fvh@m*TS;-rG-iTeTSCpjIuuS|@xT^S_b2Q%t57Ea zs6E|vs$DgJ#VkoVU4B$4WOLpILmBm6#O@AWH}nc~C)zI541Xr|*TP!ap5+{IGco1K ziGtr$pE5gR(7Hz!jL!=725&~f9`mePZq~v4O>Gv}SyC;}iw?yj>YF2lSb5d%zG4bID@S z4yK(p<*Nz$Q-eA^xO9TZt%ap+JJ_xMk3?e6^D9)5y`M2Az-MYHk6naS&M=N`T~;5Y z?9X??zJ-NWC%qyG*~Fs|T2PLa?Dg|m2UY7?Y zGm^S>iWByj(Y3a6CiA^j8E8Cwv*`D($sU}f7^P1G%hjQGiCx~u_{v;uuQD_L(xCmi z3F4ht1@2I8Soxd8#XsyheMsz}-#2Q(O5z9D(%lKsyNDb+!v?(fBlJHVyu-0Sg~#|F zm@bS~U#NQKKd0qr91i4!O7Y&$WWrnA@OU-^#$~o_P5Wdum~vI?d!u^xCfvi*9io@9 zAp|%Of4>B8UlS6NogEBpKFn4E3@?g}xs@BRJu`bQ4!;PN#cP-d&?zkrNYCG*-2Oqx zYnJAw(u&+POt?}QFRQ$!OmLXfF?l(GdeG04COc;G4P_xt$TesxU}iiLX)$;(`~j$4 zTJ^E{aBpl!?5HARhq~6V)KjTsf{W7=Y|;k`Vj;(x$5?)|&J)qD?}FeUwwbx~cxKFA zuWS5nQk5a{0DYYk{A+^f{3C|~^Nf(w=aOAk)SAC~um^Pp(f>r%1Ll@Zrk!^oeIHhq9hdPK896sIKJ%UC+{?m zieV4r`HA{SY#!spQXiE>QjY!+-t28rYJs;*mGfFG)v0qZUKg|q&mbrk1ZXDgR6$7c zRvDvcXw5P_1wbG5@@QmAwmNW6hE2uKaIU^z$uRpU;dZYk?#bC-&>YhVCoGQHMC`v3 zZvvs)g+NyoU}_5y^YWlE^72BB5owLyCGQp?Y7wp(m!?8U8Tk)Kf+8jJ8@(CRCOu|h zLJEv7gKNJ2r9)`b=dhps_Pv5c_@}+<|9lqqkE8hCBU6*wh7y(v`j<`pSZHlyd;%(D zdEHNvqFMg(MZs!B%_PY()>+iK^LmcndE?Y;+txBYOX(sMUfDe^Q$clgn@o2&;N-yN5Mt#`KTALb)o0@i&|0*eKDf}G%V zpp^1qg#w+ysjpjgKNt}~Uwz5L57-IzjNutWUyrB+qIXrgJBL%11Uh5bUPAiI6=ci3 z$0YR;hl!)1;@+LJ(|I_C>eEQ{l`=F^7^hGD{C=5 z+SW8g2|vW-XInjJ^5dVpk>J-op@ZM9w%z|wiv{7RYDh<+K)Us9qpq&8xYEy5HPQhH z`C(g|ih&5fS6oEFaYfCw7s;WH+Q(L1H|UNd{#wA3^CDrQDRGSK!<}9yBZEh_p@N~M zAXl19(E=YBW~RbA^dNPFKa-X*8o0YGCl=9hZ`i^ zjQZ1D^d5c-n`TuBBr}?UL~i&GlJuA(7~naulAKmZr@v&#ibCkx2zpZnT@IxtefCyk z0u?-Y?ijV{f<@Mjjq*=!3lkn~vCxa#5i#*-d~xIe?gbl&_fZrPm9b}aE2~QXu2|T$ z@%iIs?gnD4Npuh;lP*j+c^!`JtxXGznS3o#AVNUTWeaLm(-M0XBt~XwNp>2du+C^8 zchTOMbZ1L)q$7$LR@NLVpUOIh3Jry=+Co)>$Q6Xb$KaHDA7|$T>#f*&<4h1d(~l%PKrGm47-EQXk<_J-!aiV=BanIwBX`33kCpQ zs`00|O=V=tULnN4zdR4tNq2*OmwtX_!6>VYshf&6EJ0XyphFC6_oWe`YavG`W2UpI z=lZW|X5?h_(l=z(9A^0v)cM;WZ|LyLbe3h7X&v~A5`=8MV3-6omkA8QVgkMFiRtE!|s~5cI;x^KA}{f z7OvM-9K=wBGrFZHDW9AZk7B#CP<^Mz9z2T{;jM~1kSDHujBCg!rZjc!ouxho{sJ#} zR4G9HL-r#e_B$?p&K92VW8T70&1nLVdnNp7*FiJr5tuhPI941}9QpMYxVgjA6<<`7 z{Wq~I=OzF%p{!o8Ff)SRoFJaJGobQ!!%VB9=I_*G+;>Cv-bW8dU6TZ*tcyy5fK_3} zFTkLAJhb$4U5fF6-Kp_-aq~L|pVJQ{8o0qcZAj#`iID&02QY8ah=c;kZk&~fh0Uml z18-MoxGEJHn0`?vy%WqWEne9z8w?vY=rlDqif}{9+>7G)LAlo<`c5|+Kv#$Na5@YK zyGI4R2P(UVif)_AjU@K^5WPwW%SMw)onsBZGzczytwB$b&5DrDbh+J3^Q@4g5h8dw zEVQzg(248u-=yS|W8~J|Ksr0-0_(ytRFC$Kws%suzPVir^FgDzVn<$3Nc?TC~rv~>kk|Oul<-U1KAx4E= zOI7!;Jyfe(W98dTgrlpv9TWN(e~pDFjF}bsnwy0ye^vxM-+M*-3)Q2;c!|aOHlz6Z zM^umX-&*1&Y@D3`Qwf)>C}sCI4z&L-94NmN(j^mEeo81DC>%(EN@Vb=PsQuh;-D%) z2FLD5Y=Un0mpv$PF#I{(t{8!7svw;>^2Ec7=}BD!)3=!}55O%l9FDe7Wdvvqn}pW< zZRpQT%+2}B=AT@w*Nniv&eT!vi1$KzCPsQVkUfXDvM8;K910xMV}ul>7e|MAu&8LgtJ>}D z`-U*rX*o|Bsn6o@_s-Z?6%{b#zEL1iER7OIwCAP`phgT_g@Zneg8&Qemo4|()TozO zj7?sRdfom9?L+8ojr^;=Ylpbyeh% z85HE=GV|){=5|dRt1HUp=Ii`y9cZ*sVhB=%;)@iJ3=M2J9Jmdtb*NRm4c^?3I<#6e z7B5@C>o~ya4X@`89S5czX>3e{CDQs@`($hnJrwEb)0&Wz5AZFgQ>dj9n0!rv$rz-S zINk0U_&lG+#g_C;d%2cLQyCZ}SU@9ukHAFVYGJxJWHP*_Bp&HW1bDRUCPrAq` zGufQndRhnJJ;G#ifJQM}J5Hpy&=fmcf|Bx;Nk1z`mWzgwPiDzSGqj4L?3-$C-HU$} znRf~wP)Ia87tU&NGiV>S#6Drj&y;&6xY^V|E4<*=@jjQW7&(UbKc^YiVqXSUjcU&+ zdajtg0Vdy|eBbTMr-LA+7P*+aR-1-Kd6%y7uHv79Y=EK0y)$*@PMH_XyJB5@Qe${2d3kY1*;D9jkcpd%M9o* z0?Xx%p9z_iQMhbxWnL)mnfkd#bKyUcUzz%IiTKF=0KP)?cZ||O_(f17Yzw78 zki~QCAvc%O4#8hAZ_*J2a@kmOH?}%~83t$cn$i0GXL!uiSTH zFWrA|7Z^rUUfhrJb?>2}><*@zc!7lKEK5j8O?$K4?K1jxd$ZJA)1%VCXX+<2?7{04 zfTpK*2ji_Y+@fsVD}q5+xe6}T*W&RBi}zj=cti2h9f(JNkq|fm>07od2I*V1PuAMM znM>J92|s=)Gcs=00mH*Zh_47(VH<;dA9_~g2KD0fWS+qasIi6ZGJGnm$xmypwALwV zy@P-V&M7L3XWytTz+VpvdMdmYQ3HUuZkra`kXSXSSg!mgPMIyshEpJ<^NOI8SFu9> z+h=IA))vNcKx2RL@t8^TDb5t5la_(RY_fK&IwyLrbfcM7vQvJY*`<=Ia;;g`3#ZG& ziaDO{)U+l|BKjmW%5}fd;7o%v*S&yFrLp6lIL;xuGktn(`VB+}=kPc@#{q}YXyVj? zda4o1V`9uwsXZAzOY7;rzVJIBNL#dJLLt@)Nt3WLy)Xh`gmF?~9dY1M3<91bK+-Hi zAz%t`O->R;&oJ9;e3Z=9Dkg;$N+gW>Tr&m^y5+vsg8`6y2qkH4a*Vfnw?t@|rN1gV zcPy{7!i$v2Gr)*w>!`@g_zRnHdGNEc+!?7*6qA6pT)%u9u@#x@M#sN5QhmX3MUjR3 z=rr3kmY2hg9#M+MOA%)&+rzFX%}R-M8vrh^jucacCNbZ*%Fd>E){Q3h11?iat;;T9 zu?*<7AbG9>X85|oWKeTeBY@_aVVT2PiIZtsXP)4)cN$*Nog`T7HUfU#-TWjUXhUdq zXrPeW*^ujqJXq~c6Z{(qCbuE@gScYSNn3kG6e=OLk*_MBvEqi6awW`xFc;rxD>l`M z)PytTSWpUMM%|w8GO>pma4RVL7B(W09nB{Ag0h{&fYT3c3%)o_K)l5VM+|GHIy?ox z-A@xZ7nss`cABC>&Hgr8uwZAz9c;vo+s`H473C1n{KXM#-9;6=;Vw7)OXvm0mul7# z&ju<%3h*Vncf5ljof2~mfp)us_Sccxn;THx(bFLD@+hUXchv8h56xH8=~HhLlUE!0&-ywM+QC!|MfWE`T* z(?)8WNG%Uiy|i;a2mi|i-XX4WoM|~?Ef{lBG$LB~tnxtFh|F1S3tywDC31(r?v zt+p;^r447Z^UCYa%0}NtX1DeRK)h!V@XL}VJqwgWy4Dw%q@jyaf+4Juy;wF`BKTPG zg}4KkPm7oR_+I8Dci|jSdR&E){BBuA-phOjSQPk5f(TDw5gI=_rldg3%OpoK2;Vic zkz8*~Pxw0XLPsQy^?OWTL-D8hJ;p2JxmsR+wKvFeqe@l9x9h)b4dqN{lJZ2t5duY3 z76es}GP8y+lvNm^;vgP`@F6D&Vsu&jJ(jv#wyhP|Q7u;GX$(6U={W@$oB*l}c$7?d zxM>wCO?V`5bEN7eyo7Uv*eucdEE%W0xb=ua9TG+zROsm-#TX~iJJj}BJ-q8u*htO>72lrM5Rt z?~f_Qrab{{;vS;b5Fy)?cY9R1Rt;X^FKc>GV(gHg1H!N6Mkm_P!20@&L;lf9kL-+~ z$n3#{!~F0kFj@E?`pyV#5e`-%;f`!DJC+o4L|zKQ{X~ z9l%%s<>HfsahDTlJ%f3m_bBhsooIf~-?b!nAJ@M<1l^gI`+~w3Tx_hHu zU@&57k8(SAymCsor)vzHB8MTs3kCNPDPxG$Npg#b#>N{BT+7%JL0ybih}JR8Yi)J} zN;w=Z7Lv2ZY>I6!8Kt?K*d_^t`{l2}829<~&FGZ(e#sJQYnIv>9L&NDT4tES$M!&2 zq(tHCE?m-l6dw(2GsVJ|8Dc3gy}bN>rL9Zl zgdZf&cZ%L;D`Sc_DrAp9jTcco1kWc`B#dn&*Ux1S3F^jdVQd_E&66xBN_J<2ML#3!7y>-Mw0dB5KC;*=7JUNNG zM)kFgukASu!LN#;Bf|E+qTOqFh73c=5`G#`uTdw=a-w-J1G*J|K6&!Q>q48!54$c- zIGzFg+nN5Mb?Q9f8z6>*|0jT0{NEpc{#%;8SWQa}Ne%gv6c}_^0FYc$>n@ZeSgKYN zN>^TpUkQmnPY;OxV6qqMt4PNx#igT|`7_D%nNGk136)8Dn4w?ndr zDS}{?I{rS+X*#Rxx#gExZsN}8>nnr*!Al8%SDbt;oRJ|*jXt0nat)~ov57QAoP$bS zDacWVdKbw-dzu}1GX{v6y%Mw?l{;7q=IHMlK$wO=5)vXB^Y-YCKrSNQV`Fs048-`# zF|GKQ>b<+V^0B*CU<$%~hgC)#HKr{Kn#>g|b3^5R*_CB!GP5;XZLN75h>`u2Y>00s zr>vRMez#+@2iexiZ-RWU9EPxzWUOPc2hGX*2l zO`E`Lm>$MWyuYQd8q#iUUIt|lW@Jgm(N#w37Ea3|yDu86gf}HOcj=14hIJx4r8RlJ zej&pq{ARvHZ;)TmDo|fjGN-7BviZ2SMjP}v#l^7rDtG1#u!Bz8?0Eq^dC;q= zJHI9lk&)$Ek=2FiaCTJs+$c8@a-l88iN~h7z#4anLY)f@jeK@=a6?~>4F+*33|aBo zB17X+kURvjV+C<_XZE}pj6$kcCHP~+VL5y70A&n*<^B)Y@iaAqenp1x+TbTgUm3NQ zEAQ#01(foX*_NgZh4=w4N^5pX?O>I|kd$Rgl_EocC`#J>gh($zQ;3_17yhBJ7xAHy z7vaBsCMmmnQxRi2`Dq|N-w_En_*^Ls-Ia#aUNXY2IqYY`d0kj4)6;JkvutQkJBD|^ zrW=E|r!I_z_`Gz7D^3ml7_#=Nj!rFpX>98MG<9aHXv2hatd+`2BVBO(xgj-Ycm_GV zzAG(EUMT}1VaV>}!Dd5KZ@3;gDX}ts^yoatOd3IgS+y?HsaP@$VMAeMaaQm)X3Th; zK2YbuQ&b?yL^ydL*t$7vr=&x_jPeB+*6MJ5E_Eqi(BO60X=OaHiC^y20G}MBmx{_V zJj9MuQ*exNI^{LsNd}I5z+6I?()8Op=X@MgU)9L(bE&oKxY)cLnUC}B{S}`0@z$>`e~vJ8Ve&aNAxj2p(R$r665J4{vzd^LX0?!j}_4 zIDDSKFRx3e>b&OKC%4YpCo}HF5Afeq=Hyi$3dZu{TMD}&D*IosGAO}E^3RZ%Z1${V zl)PTP8lpJWTM@_eotZbHv-kAMcHPqq(MsFgkrUh62d`XFUD#X{OCoK&gu&=Ryo8T= zKYg2=e87Poz2dk^!yTbN+<#UAXzy-=>(v#qOH2Yt-+QeuF!qw2;yc$cvFpU>VVzTW zMKhe6Edxgq8!AVhIec=CG7uaL)<&1FV3!{1h0mX6 zLPitEIo>AcC}8#ruF-qkWEY5${d_yiAWzy(xNFLiQ_2aMcH|O6#$#BIK+XrZ_&@tH1YZ$A~BqFm=*!F~4=)-kxEt9!zO6ZYe` zd6p|+e?ndMr7LaRlKM39Ow#8ZsfAKguS%3YP?`Vb;oFGNnUf`wSzba20qKyFm#kWW z6@Ad8$|;H_Im{B=u6CDzHd}fkb_dP`+@ql?Jbd3~L0M)ymop$rxYx6J^&osFZnn;rz1(*+1V$u!aBCHNG#P3?8rv%B#mF+Ulv#sRA$1xT{D9@OTnjJ zd@MzPs}ya-c_iuC#h^Evl?u@7S|XC8Hag2{UA0-+;oV&0{tx$e^8oqwm8K4;`A+}n zJz*U4V#|gcw!&z*pd`JuEF|2*u{bd1x+!UofrMb~u)>Dm#^)Maj5@|R5Z;HkUnVu41_yi0bYuStNio>@z|SREoNQJCa&TOCieS#a#)sz@|-=QP7*dF-6j zHuhZdMI{9h-NKi;!UsxFvtcQcPCWUeK4s+>@3U&DA~m2if=(1qStuIOAc$?aX>}+( zITqK2&=4CG4meVVo<;DPL@jGPSi{M25KiH=xUBEzGBIiXLb(pYn7NTL2C5`U$n&dr zPClSAKXL&xr+0;U(ol9cLWc8pI6w5`bcsHe2~v0VmQWYb-Po1L!?e>R>nS8guN<=W?5$E~)gGa) z!fi!p?jB3P_0$~NsvBC+=NP1$$+V8yC`8*B@fFHyk3{0qO_tR2cI*Y|{g+eCPa3r$ zLk-4l5tdWO6?a)JRdVy)*DX9N-1xG|Apa{|b1lKBe@ zs^v{kzLvb9TP|=Kz!U%lcztZLbZmP)-TTCc0O&bEPZ{fxLFDZaq@5~L0zWJ+_f-0I zkFytVVnS>Qc6$Q!!^1kS+dHi|Gq!gqK5UHTI5}V?HbOcpRekAo2-Xd#}heehx9kJ+>sW(~EJM zi64rPo>C{ez=E;okUaW0d-^YW-nz_88N|}w!qA-s6v0r8{OzU@0^%n} zj2lsTv&%@rGuMh4dkADlgbFvj#Gg}t#$7zOVfm{$Gji@M_&8R~&cKR0Z*q$+JEvoQ zp=eWeC7Tp}14#0@c{)98FZ zA_c4U1~X|p&2LXoSX-+e2nzb%=nssa*=&>WG<|aKL}$k+uNR2cYq#Vs%kpm**NqvC zwF&7d6w(J6>^{0%WZyBF2P)ghO7h*6?|F8gdwbCC7S%Vlr_f9;N_0s>@inqWQn9kfqq-a8CjM5&_9`eO^5p`fo_7x##6A}baD zui36%2JaSLJkKs%;Kmwt_T8T9nyr#13g_%azQ_5}CFhHPcl%AQSa%WcG;xUZpJ|cr z95=4Bnn%9@!NDN!GH*z=_lrYa?2W1KA=G# zxthi1BWfI2tCbj5rojep9|Z-=9=B5-kKwM&?=B3a`%WLQDK8kdqX%uMg~SYnGCzrE z(J5L`?rT>#hrty`GW%tl#ghIBSIYm*EUQeqpdfc>#Nuqf(pMk$vrZfP-+V03Kk~71|KG**-=YT9YF2JYBN)E_66dd`Fu-px z5=2;7L&UWe<%42VV@2{eM^y9sH{WS$Za`J)n7D;FNIF?$#5J}hv8J+0V4Y4~wK&4+ zXCw;ovGEm^?z~q|mA_9~R*x?BL6q`}H84F)cRujWc%|Q&eZ3yS`t!Ub2AVDB<0LY0 z1Is*?2rI$MFo5DLFg%$GGf{dW?tfIKj8hu!=-n3iJr^J4}|gpS-@!CNnKFU(z|FP6AW$f)r3u5Oblec^Qgu_!wD zw@~wuXU0+X;$$Rl$+`iAOirPO?V4+67V9rAMAsv0Gg6yWmr7-dqM76pO5674VPvcM zR}`%$C8s%is0^h^<&x5nm{|>n%_Kff1FRG>(&DP%GB&H$EgwW&y`v1>$t#3fX6 z+|G%H)5F)hRe4Gp4`Zs|6nv;AMow_4Ox?@_Xk}Dh;i>+>Je~dxbSl_PW-rj1TMWpW zjlk+rpPCpXfG0u7QDx!Hjq`ez1O%!P51O)6nuAIf@R%`a0EC$sh+{H$MI>QVa2YbI6HBeoCi!X zn1kPIhR=y|sI-`ql^*>x6Q%tAld0=L6Cse<$T#+c~jVmHy__q=Q)h z)pDqCn;#i_ONG)iJOWu$(Hgo__Cn)Jwio{k9-q=T776k*&k(@R_-SjPwnJ90WFag) z*X6R!5@qe%T|!%-5VwPlR-FmjZM0FR>lDqONjf?ZU2%6>wX=XnEQd`c(X78=!lX-` zC*_ocd$z$+1^$V7>Qr}`QjI$vOH)G7OwP`xPJD}#V8QAkHC(u)&T!E&oK5!43f0RL zk%`7G$+=@cB5=>lFbD~m5RZ*Yd;*f~#Y|LNX=TE2f}xJ2n-SM6*zzc_)nlYY0`d3p zr&&nr_VmR=RqX!f?GVZg{X$mW>0DD5Dlk+svdCy1lPIn#$1&|vmldd7Ub<+b^7oKG zvn0FG?RradgEq*l#sW=;R%^oO;{=K$mT8v9f_i!XXZ4Z28g)*^R zdDnm&5Mm2Eq!y;S0J(05GBS~G(aH=`BknxDIM=PN`6P(12nlLLm z?CZ{AKxTC8Rz^5s!;a8%Yo#H|2JAV=lV}n1KmyBs8EA1wxrfo4>{7#=SS$(iQq_{^dgh3J$ zEpB>gnUz>6O1(!A6R#TU`sKo9Cxdpq^lgWF1NrHFr#AKk-DAHYLH(&9owa3d99DBR zM9A1V4%@V~F!%IH$bTKe1rO3uKo-8y_Ej)Kn_uk3A3#x6@6oL8Q>bu!H)^n z(BDZItM&odPewZ)UBSLAeQMXU=CG+x_2n3;o_h7H($G>;1hHk7JMOeSQWT)e1i+FJ zjnGlBF^Z&Qay$Ho z(cEM7SCcy|@$g9T;tJ9`ZOzUk6r2VOVxfOVb=BJ!F-GPefOPH;OJP17JFu@OTP7R> zr*bVk0~>9O?A0i+OPNA0l(YIwvx=P)9DnZdhd4;hFJzmj=pUxpDoWVRIz+v`%s(V(?b?${`%>(-|ACtPuVF(t2%K~V$E3Aq z7pN1P9~g!WVafB-z6D-+^|2(I1k&B2z?BDHt$!OrK1oF)gkqmk0^Fl<(!`4>q<%J> z{*Y3H@XDc$b#dEOz1@xDmgRln3(WDWmU-WX%i@St%Af;h_$7OG)_tXFRt;*(p!k!U z_qotZ&9C2H?$JBwD=iDZy%65${u&bobv=|SzGqJ8e>~sk`u`mh{u3A4vQsj__T6sQ z>bS0T0TK;>;VgSM2gAW9geJ2#QnXgn85zmaE8Ws?YktadoTdPYMkC#Jr$gJuiWr94 zHB^CTz@%F=FNh$`cyTdUME?RR{M>FS)xj{4Mr=7;`Oet%$vt`XY0mxh`SAJiZa)__ z#eOZi2n8nVzBh8PIoKNE#Iz>@C};m*jMS_@A4Ock&3-ma(an7|gdyE=mxRVUc?%`y z^Z?B^_&J~duao(6q!5NR=vVWKEObVPXyBb%!AS8r!oh^~!={`vDxW?}S$MtT~M&-fhUv;zYEbPwX*D0%k z-m)=ep2pJ6hr)g1XjMff}20}An(hcpB zS~^q#_Stcsnj{vK3)i!34~?&BJ*+6#FO6SG2A-B4!3AO#@!7n@A$m9*`xFZ>e~)El zP*6Gij&PzDF_1Y2A*zn9+&s8)yD8hsQW8XKW)QxqBnxsWK?YMMjy9C;$7V#t9rT3& za~S>U0YSN=+glJ!?PzO`=zNIbih4jqmI!bynb-0h(ud~d1yq4C>rADOdf%ywk`dp1 z1Wl$0RV3Q4fo_|INR4c6t{R9gqf8Yw!9ivK9!88kOoSNP=7B6mjO?J>4?@j&*+X}O z@l@k@V?JJFDy2nrz3``_M_%ciZmWahJa+oa>4(Bxlj6nlGPRJI-j}6QuV0HBG>nK) zQ<*K+aXpArPDV#8lW^czfo1VYd14STjky#Qq(xt3qrMBR{IAJbWAGZ2Q2JIEBh{tD zD*Zw$Tq!y^`19sSqL!It8N(UHX{}@eE?Upb?t+ucp}uY|y6Hhx)mwR;m!B@z_w?co z73nEI*C2FQQ1YLEo4gcMX$trWBRoZoj9|Q{SSK~QFDdjm8;>&Vo46dDm7XiBZF~zo7a_IU-j_Bbw#-Ch7=tuL7;%(eLjp= zZR+XsoEKWx!yN4tqt@z8@C*au$}S9DzQ?l`VE8`39<*D%5mwHR&TiiD{cPj&a8`L(~cRKoVH0AEDqos-~H zDKe}hHFx(YZxOK4ioDMEe5K}lD*L*WJZWfseAZe$=IH$S4x^NUg1O3Z`gM#2ZC=7y zQ#?4!^aE}vF!@_J|>>pLH8Mc^K2Ra12qc=x7 zI>B>(ymB|*E4_1kg8bEC66~_`UcV`a<$t6cX#XENjI@Q5vx)70`pt#NPe=j@z*kLM zu~;J0y+KfgJKdhz5Cql|fC<3mur^FFPMDgeixu=7_rcv35+}}O@i?>M7nlC%33EM(J~HR6o6mHzS}tJZqb{uX~6)vQoH9)A|7oHmi zPaNJ;8GE&?z~6r<;=IgpwmB!;4mvJ<+v8R?Lh8C0b{ky59YO@qhqFZMVZ?wFMF+5x zjm~rTI|v*0u7a_z{YzVI%3;cX^8JDMzxVn7-sp_x|KSt<$JYLLTTWKha#|2Y{zRT= zCWLl?hG4NNYvqC*+*8bh1*Rw1T_&)RMv`6+wEl$yvgSy_5tGkP_pN5kkM+r8f-%UT z@Hug<-O~~Y z+br^w<1W2)uFu-T^I!qXle3y0pS2;(dhX@vv(+tqCLUw7^0d9s7(e5XZ5M{!d1dI@=6E9t~R2fksI)~@oC z4@7rr3nj;AcNcTh6LgC}-k%73J^g#NUET4ch5sv=s-$bLWAxkP zpo+HCUg66XB7Z;BOMJ#Vm~YY>O2|mr^NBibQ23fhvyb_PR>LhvBHb@>kDvRHC;Rp* zXBRzdgT}YJWv@y4_ot(-$nZ^l5c{HJt@PymE|*9(zUiGMIPIvGwtTM%YC4_vqDYbE z&yS@KzvfIdkdIo+JSaZB^}Nq+!!Yep|0d&V{E-I9Qi;pY3+G!K8=;fWhkHe^UOtmGjE&&HPK_MNp>usKT%j>0`Hoe6#)+hr^K>Fn)C?6qxY zL#>bAV2+iSiOBAMJlbufMH&xb4kQ*go;Bot#^5g7kgNTXL;R{xzk*83w|K$+>szC} zqK7p)?w3Ze*@<4s+j*?%*v+P<=$li!g;W1{#c7t7yeW?3Qk{OHQoz~%+Cq-nWA-`8 z3>FWJa z4LCe4u8LH8x$wbvucv}9w|QutYE5nl6&!6>m9dE~P;LqI#}Un+@lacO%k0(n%z2|$ zal3T1u95d+=a}n;`4ekR6?&`)kLB~c6H6a%Csw;~6Vt`IUD$G}JcH&l5)OtZ*zXAF z%KG*u*`9s3qGR-20x4uW-{hWpfzTZ~pP3lD2oW_Y7ILqOr}R}P+IlLc^e-Q{9ksR1 zp23(jLI0JhGe6gZr6M&oD;fm?ol9N3*(Z8hH=y+C@v5Kq66=(kSL@EX_A zchh*zFu(PF;rqY0j|yKl3|91Gx)xD8@`5&~sq;&fRD#ScZeX8&Vssv+h z!4m^cn#>uK&G(GN&h%J1lC(2oPM^AmeN5kDDMN}*;)?$3Gb>)twz)9e)>i#Zkhy@Xl8>xb z)2%y8Q>E3>P5VWXs@71zsFP%%KfJ#ZEyg$O*44e#!JcLfOfcmi z-h1O*&X7FmqjQY&L`?cQe(Q5JEucPdL1?}DbuD$fk_R_li6HIV?q6&t73m(-q-{NSQ$6IB<=>KHuC@5$?oTxKV>?6w zYVo-^>sj;6MrOXTOhcC^mX`{fD`xnFoBKk1cuVFst6r~8+$X2Z+CNK8GdH@1&gq+* z>OR)6Xrh(vXDc0*8HV794|Nl|^wpokUhUGD;Nl*Mq4r_P*VS;%+HgLG+9-zGNFuhK zpGQ94pTI2 z>T_o9O&7Y-es8-sW;Jq7>vsEh@zLs(Z7f{s>6zVxc0%T-jIa+yuU{Xhe$i;bM14!% zy+eatv&Y4`mLm!`__Fn zs_;>tCsL>rYvcrYh_|blZQK%r0+XYhWyhz~;HFke3!9lIHDGj#3&Q3A&sHne%PLyQ<4FLwQRl78Hk)}E=_;oS1n zS6;0AL3cWo3TV=$S`UpYDrcSan461Z+8kVOY#n8WWlU$98Y(==GsDC*ko$=3+Z~-V zQKVq$^6kB4m#7lHsz2dLD$BWju=FiAw&TtpuH)9}yP0d^tZN?C8^%gu9&r&5jqSA` zC-~8tRwanY4qSCTz30Rm#ur?T(k%1(8ygHtTf{GlhBJ;y9A8`FYHo4*tEI3szi+tb z`V(>aGhC-#1`=kx5)A)r_mDE*)C%hq@c&s@v3eu|A=kKpSr6ZeD;>z;<{qg7y zT+?4F*A=d3@8`_s=Ut;G$Ryvw$KE97emSiX+hb0o|0z*}bHk*w9mUUSYmn0b* zYmjdFQFbQp#E;|Y_uHQz5eK2GF!)4yufRkY2e@Fs-u)P4~}I1Dvg?x^S=&^1iG>%sB#<#U<^d# zrJ#VTawaq&byIaYMRX|DKCeh}f8PUg3B#bq!94y~nKT^y9Y9GfAH4I=*NjNvM3MsP z3jm@4cuI15I7&MVcC;>uNCvlX@CpoA zY;ZLNeqBm0=sg!0*N_$s?7tUCECIv23X*W4;DKIqcJn@ps8NShf?+93elAcjZZlHM z<3MmU*g!X@B|0ouIb5F`biDdjqzeUFq2hUZ5zM{a7IC5TfAZ0B0B2bXBm}yIv;kK% zG)-?OvOj{JG5BvdL_Tr(p5sL~0MZEfh(i!D7)>8EOnCL)#oT~1OG97}=$akC4afx_ zC~d#*3Q)-;DA}ozvInjU3D*TIihzZjGlbGc@GE3Nre8%;bY0By-2{B=9xyVv$`b;X zr$D#e79~1Y@*!e!K^75;%;4%<%wm`eUuhT9x5n#__e6l>>VZ%#1B2Waw;bdG^x{#+ zQ8rWb4luhrASMdIzzfeW1CFkMEK_Tb0dT(YDF|cRV1Tv1qXFWa9K3PBfxVp_@F7@d zXFMoPjgBm@|5z5AxCprM)XzF*v%e zhawq=_X3&Af!&dlXHweWQ&eadyrU1n<)2{n5l-;{N#`~13vIk^hoFOm`ijfVtyw;(FnLY+J%OVsCAFD4P`+wXI`M=0-$5K zs8uV0gB8y~V$YQxsZ|XGJk6lS%5%kxY2EtIx@DAPtUmv{xViX8lgwMD+NN5!QCD=mh8CoY8KAIn18VKu1 z0CGF|BlgmxyCt;lE!-ZJr91W|m}Fh*ijHx=1}it7F3PMZT;G zUmjgU(5i`WeD~Q^;V)F&M9L4X5(gjWO8;IBKDzYK@?dc31^-zR9br9-4Rz2mKyWLK zo?jLIl2H>{jztX?bLMHNQ2h2kr$Ro(kKcbSFof(AXypNQ7~de>>iJd>w4tfb_rnMs z*R7s#xfl!0%?&p>Qq=QD% zLX%m+2asF5tENR)pDiTY5jiUsrHKo#h60)+3;7t(d?)0*NtEUerrW=cgNymo4-QBc zB_t2@U@Le`MBxEfU)BF#sugPz`Zk}uD~!?v^#4zCbcI6SdBf-Tr%ab8 zTH>n+Sa5~DOo4y(dx%>edqG#|Vnf%-$!}vQt;Ko;(2&KbHDKzF!C=9UGgu7JwFR?b G%>Mv5zCG&z diff --git a/org.openhab.binding.wmbus/pom.xml b/org.openhab.binding.wmbus/pom.xml index acb5938..bca66e6 100644 --- a/org.openhab.binding.wmbus/pom.xml +++ b/org.openhab.binding.wmbus/pom.xml @@ -1,129 +1,28 @@ - - - 4.0.0 - - - org.openhab.binding - wmbus - 2.5.0-SNAPSHOT - - - org.openhab.binding - org.openhab.binding.wmbus - - WMBus Binding - - - false - org.openmuc.jmbus.* - - - - - com.google.guava - guava - 20.0 - provided - - - - - com.neuronrobotics - nrjavaserial - 3.13.0 - - - - - - - - org.openmuc - jrxtx - 1.0.1 - - - - org.openmuc - jmbus - 3.1.1.sp2 - system - ${basedir}/lib/jmbus-3.1.1.sp2.jar - - - - junit - junit - 4.12 - test - - - org.assertj - assertj-core - 3.11.1 - test - - - org.slf4j - slf4j-simple - 1.7.2 - test - - - org.mockito - mockito-core - 2.23.0 - test - - - - org.openhab.core.bom - org.openhab.core.bom.compile - pom - provided - - - org.openhab.core.bom - org.openhab.core.bom.openhab-core - pom - provided - - - org.openhab.core.bom - org.openhab.core.bom.test - pom - test - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.0.0-M1 - - - test - test - - - **/*Test.java - - - - test - - - - - - - + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 3.1.0-SNAPSHOT + + + org.openhab.binding.wmbus + + openHAB Add-ons :: Bundles :: wmbus Binding + + false + org.openhab.core.io.transport.serial + + + + com.google.guava + guava + 20.0 + provided + + + diff --git a/org.openhab.binding.wmbus/src/main/feature/feature.xml b/org.openhab.binding.wmbus/src/main/feature/feature.xml new file mode 100644 index 0000000..8aa0a06 --- /dev/null +++ b/org.openhab.binding.wmbus/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.wmbus/${project.version} + + diff --git a/org.openhab.binding.wmbus/src/main/history/dependencies.xml b/org.openhab.binding.wmbus/src/main/history/dependencies.xml new file mode 100644 index 0000000..014e781 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/history/dependencies.xml @@ -0,0 +1,9 @@ + + + + openhab-runtime-base + wrap + mvn:org.openhab.addons.bundles/org.openhab.binding.wmbus/3.1.0-SNAPSHOT + wrap:mvn:org.lastnpe.eea/eea-all/2.2.1 + + diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/BindingConfiguration.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/BindingConfiguration.java index b4c23bc..a6316c8 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/BindingConfiguration.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/BindingConfiguration.java @@ -1,22 +1,25 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus; - -/** - * Configuration of binding - defined as service so it can be injected into different places. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public interface BindingConfiguration { - - Long getTimeToLive(); - - Boolean getIncludeBridgeUID(); - -} +/** + * Copyright (c) 2010-2021 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.wmbus; + +/** + * Configuration of binding - defined as service so it can be injected into different places. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public interface BindingConfiguration { + + Long getTimeToLive(); + + Boolean getIncludeBridgeUID(); +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/RecordType.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/RecordType.java index 6ee3fb6..ea91e5a 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/RecordType.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/RecordType.java @@ -1,101 +1,105 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ - -package org.openhab.binding.wmbus; - -import java.util.Arrays; - -import org.eclipse.smarthome.core.util.HexUtils; -import org.openmuc.jmbus.DataRecord; - -/** - * The {@link RecordType} class defines RecordType - * - * @author Hanno - Felix Wagner - Initial contribution - * @author Łukasz Dywicki - Hash/equality calculation and toString implementation. - */ - -public class RecordType { - - /** - * Constant for manufacturer data. - * - * Below combination of dib/vib is intentionally invalid. It is a mark for manufacturer specific data which can be - * appended as part of standard payload in the frame. - */ - public final static RecordType MANUFACTURER_DATA = new RecordType(new byte[] {0x0, 0x0, 0x0, 0x0}, new byte[] {0x0, 0x0, 0x0, 0x0}); - - private final byte[] dib; - private final byte[] vib; - - public RecordType(byte[] dib, int vib) { - this(dib, new byte[] { (byte) vib }); - } - - public RecordType(int dib, byte[] vib) { - this(new byte[] { (byte) dib }, vib); - } - - public RecordType(byte[] dib, byte[] vib) { - this.dib = dib; - this.vib = vib; - } - - public RecordType(int dib, int vib) { - this(new byte[] { (byte) dib }, new byte[] { (byte) vib }); - } - - public byte[] getDib() { - return dib; - } - - public byte[] getVib() { - return vib; - } - - public boolean matches(DataRecord record) { - return Arrays.equals(record.getDib(), getDib()) && Arrays.equals(record.getVib(), getVib()); - } - - @Override - public String toString() { - return "RecordType DIB:" + HexUtils.bytesToHex(dib) + ", VIB:" + HexUtils.bytesToHex(vib); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + Arrays.hashCode(dib); - result = prime * result + Arrays.hashCode(vib); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - RecordType other = (RecordType) obj; - if (!Arrays.equals(dib, other.dib)) { - return false; - } - if (!Arrays.equals(vib, other.vib)) { - return false; - } - return true; - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus; + +import java.util.Arrays; + +import org.openhab.core.util.HexUtils; +import org.openmuc.jmbus.DataRecord; + +/** + * The {@link RecordType} class defines RecordType + * + * @author Hanno - Felix Wagner - Initial contribution + * @author Łukasz Dywicki - Hash/equality calculation and toString implementation. + */ + +public class RecordType { + + /** + * Constant for manufacturer data. + * + * Below combination of dib/vib is intentionally invalid. It is a mark for manufacturer specific data which can be + * appended as part of standard payload in the frame. + */ + public final static RecordType MANUFACTURER_DATA = new RecordType(new byte[] { 0x0, 0x0, 0x0, 0x0 }, + new byte[] { 0x0, 0x0, 0x0, 0x0 }); + + private final byte[] dib; + private final byte[] vib; + + public RecordType(byte[] dib, int vib) { + this(dib, new byte[] { (byte) vib }); + } + + public RecordType(int dib, byte[] vib) { + this(new byte[] { (byte) dib }, vib); + } + + public RecordType(byte[] dib, byte[] vib) { + this.dib = dib; + this.vib = vib; + } + + public RecordType(int dib, int vib) { + this(new byte[] { (byte) dib }, new byte[] { (byte) vib }); + } + + public byte[] getDib() { + return dib; + } + + public byte[] getVib() { + return vib; + } + + public boolean matches(DataRecord record) { + return Arrays.equals(record.getDib(), getDib()) && Arrays.equals(record.getVib(), getVib()); + } + + @Override + public String toString() { + return "RecordType DIB:" + HexUtils.bytesToHex(dib) + ", VIB:" + HexUtils.bytesToHex(vib); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(dib); + result = prime * result + Arrays.hashCode(vib); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + RecordType other = (RecordType) obj; + if (!Arrays.equals(dib, other.dib)) { + return false; + } + if (!Arrays.equals(vib, other.vib)) { + return false; + } + return true; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/UnitRegistry.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/UnitRegistry.java index 65c7689..61829a8 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/UnitRegistry.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/UnitRegistry.java @@ -1,44 +1,47 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus; - -import java.util.Optional; - -import javax.measure.Quantity; -import javax.measure.Unit; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openmuc.jmbus.DlmsUnit; - -/** - * Definition of unit registry which allows to provide mapping from wmbus types to javax.measure units. - * - * @author Łukasz Dywicki - Initial contribution. - */ -@NonNullByDefault -public interface UnitRegistry { - - /** - * Provides mapping from wmbus type to javax.measure type. - * - * @param wmbusType Data value type as per wmbus/IEC standard. - * @return Optional containing unit representing same value kind based on javax.measure types. - */ - Optional> lookup(@Nullable DlmsUnit wmbusType); - - /** - * Provides information of what kind of quantity given dlms unit is - ie. Power, Force. - * - * @param wmbusType Data value type as per wmbus/IEC standard. - * @return Optional containing quantity type according to unit mapping. - */ - Optional>> quantity(@Nullable DlmsUnit wmbusType); - -} +/** + * Copyright (c) 2010-2021 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.wmbus; + +import java.util.Optional; + +import javax.measure.Quantity; +import javax.measure.Unit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openmuc.jmbus.DlmsUnit; + +/** + * Definition of unit registry which allows to provide mapping from wmbus types to javax.measure units. + * + * @author Łukasz Dywicki - Initial contribution. + */ +@NonNullByDefault +public interface UnitRegistry { + + /** + * Provides mapping from wmbus type to javax.measure type. + * + * @param wmbusType Data value type as per wmbus/IEC standard. + * @return Optional containing unit representing same value kind based on javax.measure types. + */ + Optional> lookup(@Nullable DlmsUnit wmbusType); + + /** + * Provides information of what kind of quantity given dlms unit is - ie. Power, Force. + * + * @param wmbusType Data value type as per wmbus/IEC standard. + * @return Optional containing quantity type according to unit mapping. + */ + Optional>> quantity(@Nullable DlmsUnit wmbusType); +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/WMBusBindingConstants.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/WMBusBindingConstants.java index c487889..20e7171 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/WMBusBindingConstants.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/WMBusBindingConstants.java @@ -1,125 +1,128 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus; - -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; - -import org.eclipse.smarthome.core.thing.ThingTypeUID; -import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; -import org.openmuc.jmbus.DeviceType; - -import com.google.common.collect.ImmutableSet; - -/** - * The {@link WMBusBindingConstants} class defines common constants, which are - * used across the whole binding. - * - * @author Hanno - Felix Wagner, Ernst Rohlicek, Roman Malyugin - Initial contribution - * @author Łűkasz Dywicki - meter thing type and surroundings - */ - -public class WMBusBindingConstants { - - public static final String BINDING_ID = "wmbus"; - public static final String THING_TYPE_NAME_BRIDGE = "wmbusbridge"; - public static final String THING_TYPE_NAME_VIRTUAL_BRIDGE = "wmbusvirtualbridge"; - public static final String THING_TYPE_NAME_METER = "meter"; - public static final String THING_TYPE_NAME_ENCRYPTED_METER = "encrypted_meter"; - - /** - * Time to live - by default 24 hours after which discovery result is discarded. - */ - public static final Long DEFAULT_TIME_TO_LIVE = TimeUnit.HOURS.toSeconds(24); - - // List all Thing Type UIDs, related to the WMBus Binding - public final static ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, THING_TYPE_NAME_BRIDGE); - public final static ThingTypeUID THING_TYPE_VIRTUAL_BRIDGE = new ThingTypeUID(BINDING_ID, - THING_TYPE_NAME_VIRTUAL_BRIDGE); - - public final static ThingTypeUID THING_TYPE_METER = new ThingTypeUID(BINDING_ID, THING_TYPE_NAME_METER); - public final static ThingTypeUID THING_TYPE_ENCRYPTED_METER = new ThingTypeUID(BINDING_ID, - THING_TYPE_NAME_ENCRYPTED_METER); - - public static final String CHANNEL_LAST_FRAME = "last_frame"; - public static final String CHANNEL_ERRORDATE = "error_date"; - public static final String CHANNEL_ERRORFLAGS = "error_flags"; - - public static final String CHANNEL_CURRENTPOWER = "current_power_w"; - public static final String CHANNEL_CURRENTENERGYTOTAL = "current_energy_total_kwh"; - public static final String CHANNEL_CURRENTVOLUMEFLOW = "current_volume_flow_m3h"; - public static final String CHANNEL_CURRENTVOLUMETOTAL = "current_volume_total_m3"; - public static final String CHANNEL_RETURNTEMPERATURE = "return_temperature"; - public static final String CHANNEL_TEMPERATUREDIFFERENCE = "temperature_difference"; - - public static final String CHANNEL_PREVIOUSREADING = "previous_reading"; - public static final String CHANNEL_PREVIOUSENERGYTOTAL = "previous_energy_total_kwh"; - public static final String CHANNEL_PREVIOUSDATE = "previous_date"; - - public final static ChannelTypeUID CHANNEL_LAST_FRAME_TYPE = new ChannelTypeUID(BINDING_ID, CHANNEL_LAST_FRAME); - - // add new devices here - public final static Set SUPPORTED_THING_TYPES_UIDS = ImmutableSet.of(THING_TYPE_BRIDGE, - THING_TYPE_VIRTUAL_BRIDGE, THING_TYPE_METER, THING_TYPE_ENCRYPTED_METER); - - // Bridge config properties - public static final String CONFKEY_STICK_MODEL = "stickModel"; - public static final String CONFKEY_INTERFACE_NAME = "serialDevice"; - public static final String CONFKEY_RADIO_MODE = "radioMode"; - public static final String CONFKEY_DATEFIELD_MODE = "dateFieldMode"; - public static final String CONFKEY_ENCRYPTION_KEYS = "encryptionKeys"; - public static final String CONFKEY_DEVICEID_FILTER = "deviceIDFilter"; - - // device config properties - public static final String PROPERTY_DEVICE_ADDRESS = "deviceAddress"; - public static final String PROPERTY_DEVICE_FREQUENCY_OF_UPDATES = "frequencyOfUpdates"; - public static final String PROPERTY_DEVICE_ENCRYPTION_KEY = "encryptionKey"; - // device property which says if we expected secure communication - public static final String PROPERTY_DEVICE_ENCRYPTED = "encrypted"; - - public static final String PROPERTY_WMBUS_MESSAGE = "wmBusMessage"; - - // Manufacturer options - public static final String MANUFACTURER_AMBER = "amber"; - public static final String MANUFACTURER_RADIO_CRAFTS = "rc"; - public static final String MANUFACTURER_IMST = "imst"; - - /** - * Default frequency of reports. This is at the same time value after which device is considered to be offline. - * Value in minutes. - */ - public static final Long DEFAULT_DEVICE_FREQUENCY_OF_UPDATES = 60l; - - /** - * A default encryption key. - */ - public static final byte[] DEFAULT_DEVICE_ENCRYPTION_KEY = new byte[0]; - - public static final Function DEVICE_TYPE_TRANSFORMATION = deviceType -> deviceType.name() - .toLowerCase().replace("_", " "); - - /** - * Generic device types which are supported by binding. - */ - public static final Set SUPPORTED_DEVICE_TYPES = ImmutableSet.of(DeviceType.OIL_METER, - DeviceType.ELECTRICITY_METER, DeviceType.GAS_METER, DeviceType.HEAT_METER, DeviceType.STEAM_METER, - DeviceType.WARM_WATER_METER, DeviceType.WATER_METER, DeviceType.HEAT_COST_ALLOCATOR, - DeviceType.COMPRESSED_AIR, DeviceType.COOLING_METER_OUTLET, DeviceType.COOLING_METER_INLET, - DeviceType.HEAT_METER_INLET, DeviceType.HEAT_COOLING_METER, DeviceType.CALORIFIC_VALUE, - DeviceType.HOT_WATER_METER, DeviceType.COLD_WATER_METER, DeviceType.DUAL_REGISTER_WATER_METER, - DeviceType.PRESSURE_METER, DeviceType.SMOKE_DETECTOR, DeviceType.ROOM_SENSOR_TEMP_HUM, - DeviceType.GAS_DETECTOR, DeviceType.BREAKER_ELEC, DeviceType.VALVE_GAS_OR_WATER, - DeviceType.WASTE_WATER_METER, DeviceType.RADIO_CONVERTER_SYSTEM_SIDE, - DeviceType.RADIO_CONVERTER_METER_SIDE); - - public static final String CONFKEY_BINDING_TIME_TO_LIVE = "timeToLive"; - public static final String CONFKEY_BINDING_INCLUDE_BRIDGE_UID = "includeBridgeUID"; - -} +/** + * Copyright (c) 2010-2021 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.wmbus; + +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.type.ChannelTypeUID; +import org.openmuc.jmbus.DeviceType; + +import com.google.common.collect.ImmutableSet; + +/** + * The {@link WMBusBindingConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Hanno - Felix Wagner, Ernst Rohlicek, Roman Malyugin - Initial contribution + * @author Łűkasz Dywicki - meter thing type and surroundings + */ + +public class WMBusBindingConstants { + + public static final String BINDING_ID = "wmbus"; + public static final String THING_TYPE_NAME_BRIDGE = "wmbusbridge"; + public static final String THING_TYPE_NAME_VIRTUAL_BRIDGE = "wmbusvirtualbridge"; + public static final String THING_TYPE_NAME_METER = "meter"; + public static final String THING_TYPE_NAME_ENCRYPTED_METER = "encrypted_meter"; + + /** + * Time to live - by default 24 hours after which discovery result is discarded. + */ + public static final Long DEFAULT_TIME_TO_LIVE = TimeUnit.HOURS.toSeconds(24); + + // List all Thing Type UIDs, related to the WMBus Binding + public final static ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, THING_TYPE_NAME_BRIDGE); + public final static ThingTypeUID THING_TYPE_VIRTUAL_BRIDGE = new ThingTypeUID(BINDING_ID, + THING_TYPE_NAME_VIRTUAL_BRIDGE); + + public final static ThingTypeUID THING_TYPE_METER = new ThingTypeUID(BINDING_ID, THING_TYPE_NAME_METER); + public final static ThingTypeUID THING_TYPE_ENCRYPTED_METER = new ThingTypeUID(BINDING_ID, + THING_TYPE_NAME_ENCRYPTED_METER); + + public static final String CHANNEL_LAST_FRAME = "last_frame"; + public static final String CHANNEL_ERRORDATE = "error_date"; + public static final String CHANNEL_ERRORFLAGS = "error_flags"; + + public static final String CHANNEL_CURRENTPOWER = "current_power_w"; + public static final String CHANNEL_CURRENTENERGYTOTAL = "current_energy_total_kwh"; + public static final String CHANNEL_CURRENTVOLUMEFLOW = "current_volume_flow_m3h"; + public static final String CHANNEL_CURRENTVOLUMETOTAL = "current_volume_total_m3"; + public static final String CHANNEL_RETURNTEMPERATURE = "return_temperature"; + public static final String CHANNEL_TEMPERATUREDIFFERENCE = "temperature_difference"; + + public static final String CHANNEL_PREVIOUSREADING = "previous_reading"; + public static final String CHANNEL_PREVIOUSENERGYTOTAL = "previous_energy_total_kwh"; + public static final String CHANNEL_PREVIOUSDATE = "previous_date"; + + public final static ChannelTypeUID CHANNEL_LAST_FRAME_TYPE = new ChannelTypeUID(BINDING_ID, CHANNEL_LAST_FRAME); + + // add new devices here + public final static Set SUPPORTED_THING_TYPES_UIDS = ImmutableSet.of(THING_TYPE_BRIDGE, + THING_TYPE_VIRTUAL_BRIDGE, THING_TYPE_METER, THING_TYPE_ENCRYPTED_METER); + + // Bridge config properties + public static final String CONFKEY_STICK_MODEL = "stickModel"; + public static final String CONFKEY_INTERFACE_NAME = "serialDevice"; + public static final String CONFKEY_RADIO_MODE = "radioMode"; + public static final String CONFKEY_DATEFIELD_MODE = "dateFieldMode"; + public static final String CONFKEY_ENCRYPTION_KEYS = "encryptionKeys"; + public static final String CONFKEY_DEVICEID_FILTER = "deviceIDFilter"; + + // device config properties + public static final String PROPERTY_DEVICE_ADDRESS = "deviceAddress"; + public static final String PROPERTY_DEVICE_FREQUENCY_OF_UPDATES = "frequencyOfUpdates"; + public static final String PROPERTY_DEVICE_ENCRYPTION_KEY = "encryptionKey"; + // device property which says if we expected secure communication + public static final String PROPERTY_DEVICE_ENCRYPTED = "encrypted"; + + public static final String PROPERTY_WMBUS_MESSAGE = "wmBusMessage"; + + // Manufacturer options + public static final String MANUFACTURER_AMBER = "amber"; + public static final String MANUFACTURER_RADIO_CRAFTS = "rc"; + public static final String MANUFACTURER_IMST = "imst"; + + /** + * Default frequency of reports. This is at the same time value after which device is considered to be offline. + * Value in minutes. + */ + public static final Long DEFAULT_DEVICE_FREQUENCY_OF_UPDATES = 60l; + + /** + * A default encryption key. + */ + public static final byte[] DEFAULT_DEVICE_ENCRYPTION_KEY = new byte[0]; + + public static final Function DEVICE_TYPE_TRANSFORMATION = deviceType -> deviceType.name() + .toLowerCase().replace("_", " "); + + /** + * Generic device types which are supported by binding. + */ + public static final Set SUPPORTED_DEVICE_TYPES = ImmutableSet.of(DeviceType.OIL_METER, + DeviceType.ELECTRICITY_METER, DeviceType.GAS_METER, DeviceType.HEAT_METER, DeviceType.STEAM_METER, + DeviceType.WARM_WATER_METER, DeviceType.WATER_METER, DeviceType.HEAT_COST_ALLOCATOR, + DeviceType.COMPRESSED_AIR, DeviceType.COOLING_METER_OUTLET, DeviceType.COOLING_METER_INLET, + DeviceType.HEAT_METER_INLET, DeviceType.HEAT_COOLING_METER, DeviceType.CALORIFIC_VALUE, + DeviceType.HOT_WATER_METER, DeviceType.COLD_WATER_METER, DeviceType.DUAL_REGISTER_WATER_METER, + DeviceType.PRESSURE_METER, DeviceType.SMOKE_DETECTOR, DeviceType.ROOM_SENSOR_TEMP_HUM, + DeviceType.GAS_DETECTOR, DeviceType.BREAKER_ELEC, DeviceType.VALVE_GAS_OR_WATER, + DeviceType.WASTE_WATER_METER, DeviceType.RADIO_CONVERTER_SYSTEM_SIDE, + DeviceType.RADIO_CONVERTER_METER_SIDE); + + public static final String CONFKEY_BINDING_TIME_TO_LIVE = "timeToLive"; + public static final String CONFKEY_BINDING_INCLUDE_BRIDGE_UID = "includeBridgeUID"; +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/WMBusCompanyIdentifiers.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/WMBusCompanyIdentifiers.java index 37568fc..0d716ec 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/WMBusCompanyIdentifiers.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/WMBusCompanyIdentifiers.java @@ -1,52 +1,55 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus; - -import java.util.HashMap; -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * Basic lookup table from 3 letter identifier code to full name of manufacturer. - * - * Captured from http://www.dlms.com/organization/flagmanufacturesids/index.html. - * Last update June 2018. - * - * @author Łukasz Dywicki - initial contribution. - */ -@NonNullByDefault -public class WMBusCompanyIdentifiers { - - private static final Map CIC = new HashMap<>(); - - static { - CIC.put("TCH", "TECHEM"); - CIC.put("QDS", "QUNDIS"); - CIC.put("KAM", "KAMSTRUP"); - CIC.put("ARF", "ADEUNIS"); - CIC.put("EFE", "ENGELMANN"); - } - - /** - * Returns the company name as a String - * - * @param manufacturer the WMBus manufacturer identifier - * @return The company name - */ - public static @Nullable String get(String manufacturer) { - if (manufacturer != null) { - return CIC.get(manufacturer); - } else { - return null; - } - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * Basic lookup table from 3 letter identifier code to full name of manufacturer. + * + * Captured from http://www.dlms.com/organization/flagmanufacturesids/index.html. + * Last update June 2018. + * + * @author Łukasz Dywicki - initial contribution. + */ +@NonNullByDefault +public class WMBusCompanyIdentifiers { + + private static final Map CIC = new HashMap<>(); + + static { + CIC.put("TCH", "TECHEM"); + CIC.put("QDS", "QUNDIS"); + CIC.put("KAM", "KAMSTRUP"); + CIC.put("ARF", "ADEUNIS"); + CIC.put("EFE", "ENGELMANN"); + } + + /** + * Returns the company name as a String + * + * @param manufacturer the WMBus manufacturer identifier + * @return The company name + */ + public static @Nullable String get(String manufacturer) { + if (manufacturer != null) { + return CIC.get(manufacturer); + } else { + return null; + } + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/WMBusDevice.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/WMBusDevice.java index 4eed9d4..601f82b 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/WMBusDevice.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/WMBusDevice.java @@ -1,115 +1,119 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ - -package org.openhab.binding.wmbus; - -import org.eclipse.smarthome.core.util.HexUtils; -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openmuc.jmbus.DataRecord; -import org.openmuc.jmbus.DecodingException; -import org.openmuc.jmbus.EncryptionMode; -import org.openmuc.jmbus.wireless.WMBusMessage; - -/** - * The {@link WMBusDevice} class defines WMBusDevice - * - * @author Hanno - Felix Wagner - Initial contribution - */ - -public class WMBusDevice { - - private final WMBusMessage originalMessage; - private final WMBusAdapter adapter; - - public WMBusDevice(WMBusMessage originalMessage, WMBusAdapter adapter) { - this.originalMessage = originalMessage; - this.adapter = adapter; - } - - public WMBusMessage getOriginalMessage() { - return originalMessage; - } - - public WMBusAdapter getAdapter() { - return adapter; - } - - public void decode() throws DecodingException { - originalMessage.getVariableDataResponse().decode(); - } - - public String getDeviceId() { - return originalMessage.getSecondaryAddress().getDeviceId().toString(); - } - - public DataRecord findRecord(RecordType recordType) { - if (RecordType.MANUFACTURER_DATA == recordType) { - return new ManufacturerData(originalMessage.getVariableDataResponse().getManufacturerData()); - } - - for (DataRecord record : originalMessage.getVariableDataResponse().getDataRecords()) { - if (recordType.matches(record)) { - return record; - } - } - return null; - } - - public DataRecord findRecord(byte[] dib, byte[] vib) { - return findRecord(new RecordType(dib, vib)); - } - - public boolean isEnrypted() { - return originalMessage.getVariableDataResponse().getEncryptionMode() != EncryptionMode.NONE; - } - - public String getDeviceAddress() { - return HexUtils.bytesToHex(originalMessage.getSecondaryAddress().asByteArray()); - } - - public String getDeviceType() { - return originalMessage.getControlField() + "" + originalMessage.getSecondaryAddress().getManufacturerId() + "" - + originalMessage.getSecondaryAddress().getVersion() + "" - + originalMessage.getSecondaryAddress().getDeviceType().getId(); - } - - public String getRawDeviceType() { - return originalMessage.getControlField() + "" + originalMessage.getSecondaryAddress().getManufacturerId() + "" - + originalMessage.getSecondaryAddress().getVersion() + "" + getOriginalDeviceTypeField(); - } - - public int getOriginalDeviceTypeField() { - byte[] addressArray = originalMessage.getSecondaryAddress().asByteArray(); - return addressArray[addressArray.length - 1] & 0xFF; - } - - @Override - public String toString() { - return originalMessage.getSecondaryAddress().toString(); - } -} - -class ManufacturerData extends DataRecord { - - private final byte[] rawData; - - ManufacturerData(byte[] rawData) { - this.rawData = rawData; - } - - @Override - public byte[] getDataValue() { - return getRawData(); - } - - @Override - public byte[] getRawData() { - return rawData; - } -} \ No newline at end of file +/** + * Copyright (c) 2010-2021 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.wmbus; + +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openhab.core.util.HexUtils; +import org.openmuc.jmbus.DataRecord; +import org.openmuc.jmbus.DecodingException; +import org.openmuc.jmbus.EncryptionMode; +import org.openmuc.jmbus.wireless.WMBusMessage; + +/** + * The {@link WMBusDevice} class defines WMBusDevice + * + * @author Hanno - Felix Wagner - Initial contribution + */ + +public class WMBusDevice { + + private final WMBusMessage originalMessage; + private final WMBusAdapter adapter; + + public WMBusDevice(WMBusMessage originalMessage, WMBusAdapter adapter) { + this.originalMessage = originalMessage; + this.adapter = adapter; + } + + public WMBusMessage getOriginalMessage() { + return originalMessage; + } + + public WMBusAdapter getAdapter() { + return adapter; + } + + public void decode() throws DecodingException { + originalMessage.getVariableDataResponse().decode(); + } + + public String getDeviceId() { + return originalMessage.getSecondaryAddress().getDeviceId().toString(); + } + + public DataRecord findRecord(RecordType recordType) { + if (RecordType.MANUFACTURER_DATA == recordType) { + return new ManufacturerData(originalMessage.getVariableDataResponse().getManufacturerData()); + } + + for (DataRecord record : originalMessage.getVariableDataResponse().getDataRecords()) { + if (recordType.matches(record)) { + return record; + } + } + return null; + } + + public DataRecord findRecord(byte[] dib, byte[] vib) { + return findRecord(new RecordType(dib, vib)); + } + + public boolean isEnrypted() { + return originalMessage.getVariableDataResponse().getEncryptionMode() != EncryptionMode.NONE; + } + + public String getDeviceAddress() { + return HexUtils.bytesToHex(originalMessage.getSecondaryAddress().asByteArray()); + } + + public String getDeviceType() { + return originalMessage.getControlField() + "" + originalMessage.getSecondaryAddress().getManufacturerId() + "" + + originalMessage.getSecondaryAddress().getVersion() + "" + + originalMessage.getSecondaryAddress().getDeviceType().getId(); + } + + public String getRawDeviceType() { + return originalMessage.getControlField() + "" + originalMessage.getSecondaryAddress().getManufacturerId() + "" + + originalMessage.getSecondaryAddress().getVersion() + "" + getOriginalDeviceTypeField(); + } + + public int getOriginalDeviceTypeField() { + byte[] addressArray = originalMessage.getSecondaryAddress().asByteArray(); + return addressArray[addressArray.length - 1] & 0xFF; + } + + @Override + public String toString() { + return originalMessage.getSecondaryAddress().toString(); + } +} + +class ManufacturerData extends DataRecord { + + private final byte[] rawData; + + ManufacturerData(byte[] rawData) { + this.rawData = rawData; + } + + @Override + public byte[] getDataValue() { + return getRawData(); + } + + @Override + public byte[] getRawData() { + return rawData; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/DateFieldMode.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/DateFieldMode.java index 0ebbda8..482cc72 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/DateFieldMode.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/DateFieldMode.java @@ -1,23 +1,27 @@ -/** - * Copyright (c) 2010-2019 by the respective copyright holders. - *

- * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.config; - -/** - * Setting describing possible modes for handling WMBus date/date time fields and their mapping back to openhab universe - * through binding code. - * - * @author Łukasz Dywicki - Initial contribution - */ -public enum DateFieldMode { - - FORMATTED_STRING, - UNIX_TIMESTAMP, - DATE_TIME - -} +/** + * Copyright (c) 2010-2021 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.wmbus.config; + +/** + * Setting describing possible modes for handling WMBus date/date time fields and their mapping back to openhab universe + * through binding code. + * + * @author Łukasz Dywicki - Initial contribution + */ +public enum DateFieldMode { + + FORMATTED_STRING, + UNIX_TIMESTAMP, + DATE_TIME + +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/StickModel.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/StickModel.java index 84eabd8..cf1c155 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/StickModel.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/StickModel.java @@ -1,24 +1,28 @@ -/** - * Copyright (c) 2010-2019 by the respective copyright holders. - *

- * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.config; - -/** - * Radio stick model. - * - * @author Łukasz Dywicki - Initial contribution - */ -public enum StickModel { - - // be aware that these are coded lower case so they match stick configuration defined in XML - - amber, - rc, - imst - -} +/** + * Copyright (c) 2010-2021 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.wmbus.config; + +/** + * Radio stick model. + * + * @author Łukasz Dywicki - Initial contribution + */ +public enum StickModel { + + // be aware that these are coded lower case so they match stick configuration defined in XML + + amber, + rc, + imst + +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/WMBusBridgeConfig.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/WMBusBridgeConfig.java index 16c2881..be2ebbf 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/WMBusBridgeConfig.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/WMBusBridgeConfig.java @@ -1,32 +1,35 @@ -/** - * Copyright (c) 2010-2019 by the respective copyright holders. - *

- * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.config; - -/** - * Configuration of WM-Bus communication device. - * - * @author Łukasz Dywicki - Initial contribution - */ -public class WMBusBridgeConfig { - - public String encryptionKeys; - public String deviceIDFilter; - public DateFieldMode dateFieldMode = DateFieldMode.DATE_TIME; - - public int[] getDeviceIDFilter() { - String[] ids = deviceIDFilter.split(";"); - int[] idInts = new int[ids.length]; - for (int i = 0; i < ids.length; i++) { - String curID = ids[i]; - idInts[i] = Integer.parseInt(curID); - } - return idInts; - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.config; + +/** + * Configuration of WM-Bus communication device. + * + * @author Łukasz Dywicki - Initial contribution + */ +public class WMBusBridgeConfig { + + public String encryptionKeys; + public String deviceIDFilter; + public DateFieldMode dateFieldMode = DateFieldMode.DATE_TIME; + + public int[] getDeviceIDFilter() { + String[] ids = deviceIDFilter.split(";"); + int[] idInts = new int[ids.length]; + for (int i = 0; i < ids.length; i++) { + String curID = ids[i]; + idInts[i] = Integer.parseInt(curID); + } + return idInts; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/WMBusSerialBridgeConfig.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/WMBusSerialBridgeConfig.java index 8902261..9534d00 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/WMBusSerialBridgeConfig.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/WMBusSerialBridgeConfig.java @@ -1,24 +1,27 @@ -/** - * Copyright (c) 2010-2019 by the respective copyright holders. - *

- * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.config; - -import org.openmuc.jmbus.wireless.WMBusMode; - -/** - * A specialized version of configuration of serial devices - USB sticks. - * - * @author Łukasz Dywicki - Initial contribution - */ -public class WMBusSerialBridgeConfig extends WMBusBridgeConfig { - - public StickModel stickModel; - public String serialDevice; - public WMBusMode radioMode; - -} +/** + * Copyright (c) 2010-2021 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.wmbus.config; + +import org.openmuc.jmbus.wireless.WMBusMode; + +/** + * A specialized version of configuration of serial devices - USB sticks. + * + * @author Łukasz Dywicki - Initial contribution + */ +public class WMBusSerialBridgeConfig extends WMBusBridgeConfig { + + public StickModel stickModel; + public String serialDevice; + public WMBusMode radioMode; +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/AbstractWMBusDiscoveryParticipant.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/AbstractWMBusDiscoveryParticipant.java index 2ccd103..b42b31d 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/AbstractWMBusDiscoveryParticipant.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/AbstractWMBusDiscoveryParticipant.java @@ -1,53 +1,57 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.device; - -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.smarthome.core.thing.ThingTypeUID; -import org.eclipse.smarthome.core.thing.ThingUID; -import org.openhab.binding.wmbus.BindingConfiguration; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.discovery.WMBusDiscoveryParticipant; - -/** - * Base class for discovery participants. - * - * @author Łukasz Dywicki - initial contribution - */ -public abstract class AbstractWMBusDiscoveryParticipant implements WMBusDiscoveryParticipant { - - private BindingConfiguration configuration; - - @Override - public @Nullable ThingUID getThingUID(WMBusDevice device) { - if (configuration.getIncludeBridgeUID()) { - return new ThingUID(getThingType(device), device.getAdapter().getUID(), getDeviceID(device)); - } else { - return new ThingUID(getThingType(device), getDeviceID(device)); - } - } - - private String getDeviceID(WMBusDevice device) { - return device.getDeviceId(); - } - - protected abstract ThingTypeUID getThingType(WMBusDevice device); - - public void setBindingConfiguration(BindingConfiguration configuration) { - this.configuration = configuration; - } - - public void unsetBindingConfiguration(BindingConfiguration configuration) { - this.configuration = null; - } - - protected Long getTimeToLive() { - return configuration.getTimeToLive(); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device; + +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.wmbus.BindingConfiguration; +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.discovery.WMBusDiscoveryParticipant; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; + +/** + * Base class for discovery participants. + * + * @author Łukasz Dywicki - initial contribution + */ +public abstract class AbstractWMBusDiscoveryParticipant implements WMBusDiscoveryParticipant { + + private BindingConfiguration configuration; + + @Override + public @Nullable ThingUID getThingUID(WMBusDevice device) { + if (configuration.getIncludeBridgeUID()) { + return new ThingUID(getThingType(device), device.getAdapter().getUID(), getDeviceID(device)); + } else { + return new ThingUID(getThingType(device), getDeviceID(device)); + } + } + + private String getDeviceID(WMBusDevice device) { + return device.getDeviceId(); + } + + protected abstract ThingTypeUID getThingType(WMBusDevice device); + + public void setBindingConfiguration(BindingConfiguration configuration) { + this.configuration = configuration; + } + + public void unsetBindingConfiguration(BindingConfiguration configuration) { + this.configuration = null; + } + + protected Long getTimeToLive() { + return configuration.getTimeToLive(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/Meter.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/Meter.java index 12e96b6..a89e162 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/Meter.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/Meter.java @@ -1,44 +1,48 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ - -package org.openhab.binding.wmbus.device; - -import java.util.Set; - -import org.eclipse.smarthome.core.thing.ThingTypeUID; - -/** - * The {@link Meter} class defines common Meter device - * - * @author Roman Malyugin - Initial contribution - */ - -public class Meter { - - protected Set supportedThingTypes; - protected ThingTypeUID thingType; - protected String thingTypeName; - protected String thingTypeId; - - public Set getSupportedThingTypes() { - return supportedThingTypes; - } - - public ThingTypeUID getThingType() { - return thingType; - } - - public String getThingTypeName() { - return thingTypeName; - } - - public String getThingTypeId() { - return thingTypeId; - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device; + +import java.util.Set; + +import org.openhab.core.thing.ThingTypeUID; + +/** + * The {@link Meter} class defines common Meter device + * + * @author Roman Malyugin - Initial contribution + */ + +public class Meter { + + protected Set supportedThingTypes; + protected ThingTypeUID thingType; + protected String thingTypeName; + protected String thingTypeId; + + public Set getSupportedThingTypes() { + return supportedThingTypes; + } + + public ThingTypeUID getThingType() { + return thingType; + } + + public String getThingTypeName() { + return thingTypeName; + } + + public String getThingTypeId() { + return thingTypeId; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/UnknownMeter.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/UnknownMeter.java index 3f8612b..7144bc9 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/UnknownMeter.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/UnknownMeter.java @@ -1,74 +1,77 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ - -package org.openhab.binding.wmbus.device; - -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.smarthome.core.thing.ChannelUID; -import org.eclipse.smarthome.core.thing.Thing; -import org.eclipse.smarthome.core.types.Command; -import org.eclipse.smarthome.core.types.RefreshType; -import org.eclipse.smarthome.core.types.State; -import org.eclipse.smarthome.core.types.UnDefType; -import org.openhab.binding.wmbus.handler.WMBusDeviceHandler; -import org.openhab.io.transport.mbus.wireless.KeyStorage; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Deactivate; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link UnknownMeter} class defines unknown abstract Meter device - * - * @author Roman Malyugin - Initial contribution - */ - -@Component(service = { UnknownMeter.class }) -public class UnknownMeter extends Meter { - - public static final Logger logger = LoggerFactory.getLogger(UnknownMeter.class); - - @Activate - protected void activate(Map properties) { - } - - @Deactivate - protected void deactivate() { - } - - public static class UnknownWMBusDeviceHandler extends WMBusDeviceHandler { - - public UnknownWMBusDeviceHandler(Thing thing, KeyStorage keyStorage) { - super(thing, keyStorage); - } - - @Override - public void handleCommand(@NonNull ChannelUID channelUID, @NonNull Command command) { - logger.trace("handleCommand(): (1/5) command for channel " + channelUID.toString() + " command: " - + command.toString()); - if (command == RefreshType.REFRESH) { - logger.trace("handleCommand(): (2/5) command.refreshtype == REFRESH"); - State newState = UnDefType.NULL; - if (wmbusDevice != null) { - logger.trace("handleCommand(): (3/5) deviceMessage != null"); - logger.trace("handleCommand(): (4/5): got channel id: " + channelUID.getId()); - logger.trace("handleCommand(): (5/5) assigning new state to channel '" - + channelUID.getId().toString() + "': " + newState.toString()); - updateState(channelUID.getId(), newState); - - } - - } - } - - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNull; +import org.openhab.binding.wmbus.handler.WMBusDeviceHandler; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; +import org.openhab.io.transport.mbus.wireless.KeyStorage; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link UnknownMeter} class defines unknown abstract Meter device + * + * @author Roman Malyugin - Initial contribution + */ + +@Component(service = { UnknownMeter.class }) +public class UnknownMeter extends Meter { + + public static final Logger logger = LoggerFactory.getLogger(UnknownMeter.class); + + @Activate + protected void activate(Map properties) { + } + + @Deactivate + protected void deactivate() { + } + + public static class UnknownWMBusDeviceHandler extends WMBusDeviceHandler { + + public UnknownWMBusDeviceHandler(Thing thing, KeyStorage keyStorage) { + super(thing, keyStorage); + } + + @Override + public void handleCommand(@NonNull ChannelUID channelUID, @NonNull Command command) { + logger.trace("handleCommand(): (1/5) command for channel " + channelUID.toString() + " command: " + + command.toString()); + if (command == RefreshType.REFRESH) { + logger.trace("handleCommand(): (2/5) command.refreshtype == REFRESH"); + State newState = UnDefType.NULL; + if (wmbusDevice != null) { + logger.trace("handleCommand(): (3/5) deviceMessage != null"); + logger.trace("handleCommand(): (4/5): got channel id: " + channelUID.getId()); + logger.trace("handleCommand(): (5/5) assigning new state to channel '" + + channelUID.getId().toString() + "': " + newState.toString()); + updateState(channelUID.getId(), newState); + + } + + } + } + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/generic/DynamicWMBusThingHandler.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/generic/DynamicWMBusThingHandler.java index 77f0884..a1e7f1b 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/generic/DynamicWMBusThingHandler.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/generic/DynamicWMBusThingHandler.java @@ -1,147 +1,150 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.device.generic; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.smarthome.core.library.types.QuantityType; -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.binding.builder.ChannelBuilder; -import org.eclipse.smarthome.core.thing.binding.builder.ThingBuilder; -import org.eclipse.smarthome.core.thing.type.ChannelType; -import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; -import org.eclipse.smarthome.core.thing.util.ThingHelper; -import org.eclipse.smarthome.core.types.Command; -import org.eclipse.smarthome.core.types.RefreshType; -import org.eclipse.smarthome.core.types.State; -import org.eclipse.smarthome.core.util.HexUtils; -import org.openhab.binding.wmbus.RecordType; -import org.openhab.binding.wmbus.UnitRegistry; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openhab.binding.wmbus.handler.WMBusDeviceHandler; -import org.openhab.binding.wmbus.internal.WMBusChannelTypeProvider; -import org.openhab.io.transport.mbus.wireless.KeyStorage; -import org.openmuc.jmbus.DataRecord; -import org.openmuc.jmbus.VariableDataStructure; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.collect.ImmutableMap; - -/** - * Universal dynamic handler which covers devices based on dib/vib and channel type mapping. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public class DynamicWMBusThingHandler extends WMBusDeviceHandler { - - private static final String CHANNEL_PROPERTY_VIB = "vib"; - - private static final String CHANNEL_PROPERTY_DIB = "dib"; - - private final Logger logger = LoggerFactory.getLogger(DynamicWMBusThingHandler.class); - - private final UnitRegistry unitRegistry; - private final WMBusChannelTypeProvider channelTypeProvider; - - public DynamicWMBusThingHandler(Thing thing, KeyStorage keyStorage, UnitRegistry unitRegistry, - WMBusChannelTypeProvider channelTypeProvider) { - super(thing, keyStorage); - this.unitRegistry = unitRegistry; - this.channelTypeProvider = channelTypeProvider; - } - - @Override - public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice receivedDevice) { - if (receivedDevice.getDeviceAddress().equals(deviceAddress)) { - VariableDataStructure response = receivedDevice.getOriginalMessage().getVariableDataResponse(); - - List channels = new ArrayList<>(); - for (DataRecord record : response.getDataRecords()) { - Optional typeId = WMBusChannelTypeProvider.getChannelType(record); - Optional channel = typeId.map(type -> thing.getChannel(type.getId())); - - if (typeId.isPresent() && !channel.isPresent()) { - Channel newChannel = createChannel(typeId.get(), record); - try { - ThingHelper.ensureUniqueChannels(channels, newChannel); - channels.add(newChannel); - } catch (IllegalArgumentException ex) { - logger.debug("Cannot create channel: {}", ex.getMessage()); - // TODO instead of dropping, rename the duplicate with a suffix like _1,_2 etc - } - } - } - - if (!channels.isEmpty()) { - ThingBuilder updatedThing = editThing().withChannels(channels); - updateThing(updatedThing.build()); - } - } - - super.onChangedWMBusDevice(adapter, receivedDevice); - } - - private Channel createChannel(ChannelTypeUID typeId, DataRecord record) { - ChannelType type = channelTypeProvider.getChannelType(typeId, null); - - ChannelBuilder channelBuilder = ChannelBuilder.create(new ChannelUID(thing.getUID(), typeId.getId()), - type.getItemType()); - - Map properties = ImmutableMap.of(CHANNEL_PROPERTY_DIB, HexUtils.bytesToHex(record.getDib()), - CHANNEL_PROPERTY_VIB, HexUtils.bytesToHex(record.getVib())); - - channelBuilder.withType(typeId).withProperties(properties).withLabel(type.getLabel()); - - String description = type.getDescription(); - if (description != null) { - channelBuilder.withDescription(description); - } - - return channelBuilder.build(); - } - - @Override - public void handleCommand(@NonNull ChannelUID channelUID, @NonNull Command command) { - logger.trace("Received command {} for channel {}", command, channelUID); - if (wmbusDevice != null && command == RefreshType.REFRESH) { - Optional> properties = Optional.ofNullable(thing.getChannel(channelUID.getId())) - .map(ch -> ch.getProperties()); - - Optional dib = properties.map(map -> map.get(CHANNEL_PROPERTY_DIB)).map(HexUtils::hexToBytes); - Optional vib = properties.map(map -> map.get(CHANNEL_PROPERTY_VIB)).map(HexUtils::hexToBytes); - - if (dib.isPresent() && vib.isPresent()) { - RecordType recordType = new RecordType(dib.get(), vib.get()); - DataRecord record = wmbusDevice.findRecord(recordType); - - if (record != null) { - State newState = unitRegistry.lookup(record.getUnit()) - .map(unit -> new QuantityType<>(record.getScaledDataValue(), unit)).map(State.class::cast) - .orElseGet(() -> convertRecordData(record)); - - logger.trace("Assigning new state {} to channel {}", newState, channelUID.getId()); - updateState(channelUID.getId(), newState); - } else { - logger.warn("Could not read value of record {} in received frame", recordType); - } - } else { - logger.warn("Unknown channel {}, not supported by {}", channelUID, thing); - } - } - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.generic; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNull; +import org.openhab.binding.wmbus.RecordType; +import org.openhab.binding.wmbus.UnitRegistry; +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openhab.binding.wmbus.handler.WMBusDeviceHandler; +import org.openhab.binding.wmbus.internal.WMBusChannelTypeProvider; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.binding.builder.ChannelBuilder; +import org.openhab.core.thing.binding.builder.ThingBuilder; +import org.openhab.core.thing.type.ChannelType; +import org.openhab.core.thing.type.ChannelTypeUID; +import org.openhab.core.thing.util.ThingHelper; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; +import org.openhab.core.util.HexUtils; +import org.openhab.io.transport.mbus.wireless.KeyStorage; +import org.openmuc.jmbus.DataRecord; +import org.openmuc.jmbus.VariableDataStructure; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableMap; + +/** + * Universal dynamic handler which covers devices based on dib/vib and channel type mapping. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public class DynamicWMBusThingHandler extends WMBusDeviceHandler { + + private static final String CHANNEL_PROPERTY_VIB = "vib"; + + private static final String CHANNEL_PROPERTY_DIB = "dib"; + + private final Logger logger = LoggerFactory.getLogger(DynamicWMBusThingHandler.class); + + private final UnitRegistry unitRegistry; + private final WMBusChannelTypeProvider channelTypeProvider; + + public DynamicWMBusThingHandler(Thing thing, KeyStorage keyStorage, UnitRegistry unitRegistry, + WMBusChannelTypeProvider channelTypeProvider) { + super(thing, keyStorage); + this.unitRegistry = unitRegistry; + this.channelTypeProvider = channelTypeProvider; + } + + @Override + public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice receivedDevice) { + if (receivedDevice.getDeviceAddress().equals(deviceAddress)) { + VariableDataStructure response = receivedDevice.getOriginalMessage().getVariableDataResponse(); + + List channels = new ArrayList<>(); + for (DataRecord record : response.getDataRecords()) { + Optional typeId = WMBusChannelTypeProvider.getChannelType(record); + Optional channel = typeId.map(type -> thing.getChannel(type.getId())); + + if (typeId.isPresent() && !channel.isPresent()) { + Channel newChannel = createChannel(typeId.get(), record); + try { + ThingHelper.ensureUniqueChannels(channels, newChannel); + channels.add(newChannel); + } catch (IllegalArgumentException ex) { + logger.debug("Cannot create channel: {}", ex.getMessage()); + // TODO instead of dropping, rename the duplicate with a suffix like _1,_2 etc + } + } + } + + if (!channels.isEmpty()) { + ThingBuilder updatedThing = editThing().withChannels(channels); + updateThing(updatedThing.build()); + } + } + + super.onChangedWMBusDevice(adapter, receivedDevice); + } + + private Channel createChannel(ChannelTypeUID typeId, DataRecord record) { + ChannelType type = channelTypeProvider.getChannelType(typeId, null); + + ChannelBuilder channelBuilder = ChannelBuilder.create(new ChannelUID(thing.getUID(), typeId.getId()), + type.getItemType()); + + Map properties = ImmutableMap.of(CHANNEL_PROPERTY_DIB, HexUtils.bytesToHex(record.getDib()), + CHANNEL_PROPERTY_VIB, HexUtils.bytesToHex(record.getVib())); + + channelBuilder.withType(typeId).withProperties(properties).withLabel(type.getLabel()); + + String description = type.getDescription(); + if (description != null) { + channelBuilder.withDescription(description); + } + + return channelBuilder.build(); + } + + @Override + public void handleCommand(@NonNull ChannelUID channelUID, @NonNull Command command) { + logger.trace("Received command {} for channel {}", command, channelUID); + if (wmbusDevice != null && command == RefreshType.REFRESH) { + Optional> properties = Optional.ofNullable(thing.getChannel(channelUID.getId())) + .map(ch -> ch.getProperties()); + + Optional dib = properties.map(map -> map.get(CHANNEL_PROPERTY_DIB)).map(HexUtils::hexToBytes); + Optional vib = properties.map(map -> map.get(CHANNEL_PROPERTY_VIB)).map(HexUtils::hexToBytes); + + if (dib.isPresent() && vib.isPresent()) { + RecordType recordType = new RecordType(dib.get(), vib.get()); + DataRecord record = wmbusDevice.findRecord(recordType); + + if (record != null) { + State newState = unitRegistry.lookup(record.getUnit()) + .map(unit -> new QuantityType<>(record.getScaledDataValue(), unit)).map(State.class::cast) + .orElseGet(() -> convertRecordData(record)); + + logger.trace("Assigning new state {} to channel {}", newState, channelUID.getId()); + updateState(channelUID.getId(), newState); + } else { + logger.warn("Could not read value of record {} in received frame", recordType); + } + } else { + logger.warn("Unknown channel {}, not supported by {}", channelUID, thing); + } + } + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/generic/GenericWMBusThingHandler.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/generic/GenericWMBusThingHandler.java index 2b049f1..88c8a8f 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/generic/GenericWMBusThingHandler.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/generic/GenericWMBusThingHandler.java @@ -1,74 +1,77 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.device.generic; - -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.smarthome.core.library.types.QuantityType; -import org.eclipse.smarthome.core.thing.ChannelUID; -import org.eclipse.smarthome.core.thing.Thing; -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.wmbus.RecordType; -import org.openhab.binding.wmbus.UnitRegistry; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.handler.WMBusDeviceHandler; -import org.openhab.io.transport.mbus.wireless.KeyStorage; -import org.openmuc.jmbus.DataRecord; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Universal handler which covers all devices based on channel/record type mapping. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public class GenericWMBusThingHandler extends WMBusDeviceHandler { - - private final Logger logger = LoggerFactory.getLogger(GenericWMBusThingHandler.class); - - private final UnitRegistry unitRegistry; - private final Map channelMapping; - - protected GenericWMBusThingHandler(Thing thing, KeyStorage keyStorage, UnitRegistry unitRegistry, - Map channelMapping) { - super(thing, keyStorage); - this.unitRegistry = unitRegistry; - this.channelMapping = channelMapping; - } - - @Override - public void handleCommand(@NonNull ChannelUID channelUID, @NonNull Command command) { - logger.trace("Received command {} for channel {}", command, channelUID); - if (command == RefreshType.REFRESH) { - if (wmbusDevice != null) { - RecordType recordType = channelMapping.get(channelUID.getId()); - if (recordType != null) { - DataRecord record = wmbusDevice.findRecord(recordType); - - if (record != null) { - State newState = unitRegistry.lookup(record.getUnit()) - .map(unit -> new QuantityType<>(record.getScaledDataValue(), unit)) - .map(State.class::cast).orElseGet(() -> convertRecordData(record)); - - logger.trace("Assigning new state {} to channel {}", newState, channelUID.getId()); - updateState(channelUID.getId(), newState); - } else { - logger.warn("Could not read value of record {} in received frame", recordType); - } - } else { - logger.warn("Unown channel {}, not supported by {}", channelUID, thing); - } - } - } - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.generic; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNull; +import org.openhab.binding.wmbus.RecordType; +import org.openhab.binding.wmbus.UnitRegistry; +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.handler.WMBusDeviceHandler; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; +import org.openhab.io.transport.mbus.wireless.KeyStorage; +import org.openmuc.jmbus.DataRecord; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Universal handler which covers all devices based on channel/record type mapping. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public class GenericWMBusThingHandler extends WMBusDeviceHandler { + + private final Logger logger = LoggerFactory.getLogger(GenericWMBusThingHandler.class); + + private final UnitRegistry unitRegistry; + private final Map channelMapping; + + protected GenericWMBusThingHandler(Thing thing, KeyStorage keyStorage, UnitRegistry unitRegistry, + Map channelMapping) { + super(thing, keyStorage); + this.unitRegistry = unitRegistry; + this.channelMapping = channelMapping; + } + + @Override + public void handleCommand(@NonNull ChannelUID channelUID, @NonNull Command command) { + logger.trace("Received command {} for channel {}", command, channelUID); + if (command == RefreshType.REFRESH) { + if (wmbusDevice != null) { + RecordType recordType = channelMapping.get(channelUID.getId()); + if (recordType != null) { + DataRecord record = wmbusDevice.findRecord(recordType); + + if (record != null) { + State newState = unitRegistry.lookup(record.getUnit()) + .map(unit -> new QuantityType<>(record.getScaledDataValue(), unit)) + .map(State.class::cast).orElseGet(() -> convertRecordData(record)); + + logger.trace("Assigning new state {} to channel {}", newState, channelUID.getId()); + updateState(channelUID.getId(), newState); + } else { + logger.warn("Could not read value of record {} in received frame", recordType); + } + } else { + logger.warn("Unown channel {}, not supported by {}", channelUID, thing); + } + } + } + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronBindingConstants.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronBindingConstants.java index 125fc6b..0861a9f 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronBindingConstants.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronBindingConstants.java @@ -1,72 +1,85 @@ -package org.openhab.binding.wmbus.device.itron; - -import java.util.Set; -import org.eclipse.smarthome.core.thing.ThingTypeUID; -import org.openhab.binding.wmbus.WMBusBindingConstants; -import com.google.common.collect.ImmutableSet; - -public interface ItronBindingConstants { - - String ITRON_SMOKE_DETECTOR = "itron_smoke_detector"; - - ThingTypeUID THING_TYPE_ITRON_SMOKE_DETECTOR = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, ITRON_SMOKE_DETECTOR); - - Set SUPPORTED_THING_TYPES = ImmutableSet.of(THING_TYPE_ITRON_SMOKE_DETECTOR); - - String ITRON_MANUFACTURER_ID = "ITW"; - - String CHANNEL_CURRENT_DATE = "current_date"; - String CHANNEL_CURRENT_DATE_STRING = "current_date_string"; - String CHANNEL_CURRENT_DATE_NUMBER = "current_date_number"; - - String CHANNEL_STATUS_BILLING_DATE = "status_billing_date"; - String CHANNEL_STATUS_REMOVAL_OCCURRED = "status_removal_occurred"; - String CHANNEL_STATUS_PRODUCT_INSTALLED = "status_product_installed"; - String CHANNEL_STATUS_OPERATION_MODE = "status_operation_mode"; - String CHANNEL_STATUS_PERIMETER_INTRUSION_OCCURRED = "status_perimeter_intrusion_occurred"; - String CHANNEL_STATUS_SMOKE_INLET_BLOCKED_OCCURRED = "status_smoke_inlet_blocked_occurred"; - String CHANNEL_STATUS_OUT_OF_TEMP_RANGE_OCCURRED = "status_out_of_temp_range_occurred"; - String CHANNEL_STATUS_PRODUCT_CODE = "status_product_code"; - String CHANNEL_STATUS_BATTERY_LIFETIME = "status_battery_lifetime"; - //String CHANNEL_STATUS_PERIMETER_INTRUSION = "status_perimeter_intrusion"; - //String CHANNEL_STATUS_REMOVAL_ERROR = "status_removal_error"; - //String CHANNEL_STATUS_DATA_ENCRYPTED = "status_data_encrypted"; - - String CHANNEL_LAST_SMOKE_ALERT_START_DATE = "last_smoke_alert_start_date"; - String CHANNEL_LAST_SMOKE_ALERT_START_DATE_STRING = "last_smoke_alert_start_date_string"; - String CHANNEL_LAST_SMOKE_ALERT_START_DATE_NUMBER = "last_smoke_alert_start_date_number"; - String CHANNEL_LAST_SMOKE_ALERT_END_DATE = "last_smoke_alert_end_date"; - String CHANNEL_LAST_SMOKE_ALERT_END_DATE_STRING = "last_smoke_alert_end_date_string"; - String CHANNEL_LAST_SMOKE_ALERT_END_DATE_NUMBER = "last_smoke_alert_end_date_number"; - String CHANNEL_LAST_BEEPER_STOPPED_DURING_SMOKE_ALERT_DATE = "last_beeper_stopped_during_smoke_alert_date"; - String CHANNEL_LAST_BEEPER_STOPPED_DURING_SMOKE_ALERT_DATE_STRING = "last_beeper_stopped_during_smoke_alert_date_string"; - String CHANNEL_LAST_BEEPER_STOPPED_DURING_SMOKE_ALERT_DATE_NUMBER = "last_beeper_stopped_during_smoke_alert_date_number"; - - String CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_OCCURRED_DATE = "last_perimeter_intrusion_obstacle_occurred_date"; - String CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_OCCURRED_DATE_STRING = "last_perimeter_intrusion_obstacle_occurred_date_string"; - String CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_OCCURRED_DATE_NUMBER = "last_perimeter_intrusion_obstacle_occurred_date_number"; - String CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_REMOVED_DATE = "last_perimeter_intrusion_obstacle_removed_date"; - String CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_REMOVED_DATE_STRING = "last_perimeter_intrusion_obstacle_removed_date_string"; - String CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_REMOVED_DATE_NUMBER = "last_perimeter_intrusion_obstacle_removed_date_number"; - - String CHANNEL_LAST_SMOKE_INLET_BLOCKED_DATE = "last_smoke_inlet_blocked_date"; - String CHANNEL_LAST_SMOKE_INLET_BLOCKED_DATE_STRING = "last_smoke_inlet_blocked_date_string"; - String CHANNEL_LAST_SMOKE_INLET_BLOCKED_DATE_NUMBER = "last_smoke_inlet_blocked_date_number"; - String CHANNEL_LAST_SMOKE_INLET_BLOCKING_REMOVED_DATE = "last_smoke_inlet_blocking_removed_date"; - String CHANNEL_LAST_SMOKE_INLET_BLOCKING_REMOVED_DATE_STRING = "last_smoke_inlet_blocking_removed_date_string"; - String CHANNEL_LAST_SMOKE_INLET_BLOCKING_REMOVED_DATE_NUMBER = "last_smoke_inlet_blocking_removed_date_number"; - - String CHANNEL_LAST_TEMPERATURE_OUT_OF_RANGE_DATE = "last_temperature_out_of_range_date"; - String CHANNEL_LAST_TEMPERATURE_OUT_OF_RANGE_DATE_STRING = "last_temperature_out_of_range_date_string"; - String CHANNEL_LAST_TEMPERATURE_OUT_OF_RANGE_DATE_NUMBER = "last_temperature_out_of_range_date_number"; - - String CHANNEL_LAST_TEST_SWITCH_DATE = "last_test_switch_date"; - String CHANNEL_LAST_TEST_SWITCH_DATE_STRING = "last_test_switch_date_string"; - String CHANNEL_LAST_TEST_SWITCH_DATE_NUMBER = "last_test_switch_date_number"; - - String CHANNEL_NUMBER_OF_TEST_SWITCHES_OPERATED = "number_of_test_switches_operated"; - String CHANNEL_PERIMETER_INTRUSION_DAY_COUNTER_CUMULATED = "perimeter_intrusion_day_counter_cumulated"; - String CHANNEL_SMOKE_INLET_DAY_COUNTER_CUMULATED = "smoke_inlet_day_counter_cumulated"; - -} - +/** + * Copyright (c) 2010-2021 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.wmbus.device.itron; + +import java.util.Set; + +import org.openhab.binding.wmbus.WMBusBindingConstants; +import org.openhab.core.thing.ThingTypeUID; + +import com.google.common.collect.ImmutableSet; + +public interface ItronBindingConstants { + + String ITRON_SMOKE_DETECTOR = "itron_smoke_detector"; + + ThingTypeUID THING_TYPE_ITRON_SMOKE_DETECTOR = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, + ITRON_SMOKE_DETECTOR); + + Set SUPPORTED_THING_TYPES = ImmutableSet.of(THING_TYPE_ITRON_SMOKE_DETECTOR); + + String ITRON_MANUFACTURER_ID = "ITW"; + + String CHANNEL_CURRENT_DATE = "current_date"; + String CHANNEL_CURRENT_DATE_STRING = "current_date_string"; + String CHANNEL_CURRENT_DATE_NUMBER = "current_date_number"; + + String CHANNEL_STATUS_BILLING_DATE = "status_billing_date"; + String CHANNEL_STATUS_REMOVAL_OCCURRED = "status_removal_occurred"; + String CHANNEL_STATUS_PRODUCT_INSTALLED = "status_product_installed"; + String CHANNEL_STATUS_OPERATION_MODE = "status_operation_mode"; + String CHANNEL_STATUS_PERIMETER_INTRUSION_OCCURRED = "status_perimeter_intrusion_occurred"; + String CHANNEL_STATUS_SMOKE_INLET_BLOCKED_OCCURRED = "status_smoke_inlet_blocked_occurred"; + String CHANNEL_STATUS_OUT_OF_TEMP_RANGE_OCCURRED = "status_out_of_temp_range_occurred"; + String CHANNEL_STATUS_PRODUCT_CODE = "status_product_code"; + String CHANNEL_STATUS_BATTERY_LIFETIME = "status_battery_lifetime"; + // String CHANNEL_STATUS_PERIMETER_INTRUSION = "status_perimeter_intrusion"; + // String CHANNEL_STATUS_REMOVAL_ERROR = "status_removal_error"; + // String CHANNEL_STATUS_DATA_ENCRYPTED = "status_data_encrypted"; + + String CHANNEL_LAST_SMOKE_ALERT_START_DATE = "last_smoke_alert_start_date"; + String CHANNEL_LAST_SMOKE_ALERT_START_DATE_STRING = "last_smoke_alert_start_date_string"; + String CHANNEL_LAST_SMOKE_ALERT_START_DATE_NUMBER = "last_smoke_alert_start_date_number"; + String CHANNEL_LAST_SMOKE_ALERT_END_DATE = "last_smoke_alert_end_date"; + String CHANNEL_LAST_SMOKE_ALERT_END_DATE_STRING = "last_smoke_alert_end_date_string"; + String CHANNEL_LAST_SMOKE_ALERT_END_DATE_NUMBER = "last_smoke_alert_end_date_number"; + String CHANNEL_LAST_BEEPER_STOPPED_DURING_SMOKE_ALERT_DATE = "last_beeper_stopped_during_smoke_alert_date"; + String CHANNEL_LAST_BEEPER_STOPPED_DURING_SMOKE_ALERT_DATE_STRING = "last_beeper_stopped_during_smoke_alert_date_string"; + String CHANNEL_LAST_BEEPER_STOPPED_DURING_SMOKE_ALERT_DATE_NUMBER = "last_beeper_stopped_during_smoke_alert_date_number"; + + String CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_OCCURRED_DATE = "last_perimeter_intrusion_obstacle_occurred_date"; + String CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_OCCURRED_DATE_STRING = "last_perimeter_intrusion_obstacle_occurred_date_string"; + String CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_OCCURRED_DATE_NUMBER = "last_perimeter_intrusion_obstacle_occurred_date_number"; + String CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_REMOVED_DATE = "last_perimeter_intrusion_obstacle_removed_date"; + String CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_REMOVED_DATE_STRING = "last_perimeter_intrusion_obstacle_removed_date_string"; + String CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_REMOVED_DATE_NUMBER = "last_perimeter_intrusion_obstacle_removed_date_number"; + + String CHANNEL_LAST_SMOKE_INLET_BLOCKED_DATE = "last_smoke_inlet_blocked_date"; + String CHANNEL_LAST_SMOKE_INLET_BLOCKED_DATE_STRING = "last_smoke_inlet_blocked_date_string"; + String CHANNEL_LAST_SMOKE_INLET_BLOCKED_DATE_NUMBER = "last_smoke_inlet_blocked_date_number"; + String CHANNEL_LAST_SMOKE_INLET_BLOCKING_REMOVED_DATE = "last_smoke_inlet_blocking_removed_date"; + String CHANNEL_LAST_SMOKE_INLET_BLOCKING_REMOVED_DATE_STRING = "last_smoke_inlet_blocking_removed_date_string"; + String CHANNEL_LAST_SMOKE_INLET_BLOCKING_REMOVED_DATE_NUMBER = "last_smoke_inlet_blocking_removed_date_number"; + + String CHANNEL_LAST_TEMPERATURE_OUT_OF_RANGE_DATE = "last_temperature_out_of_range_date"; + String CHANNEL_LAST_TEMPERATURE_OUT_OF_RANGE_DATE_STRING = "last_temperature_out_of_range_date_string"; + String CHANNEL_LAST_TEMPERATURE_OUT_OF_RANGE_DATE_NUMBER = "last_temperature_out_of_range_date_number"; + + String CHANNEL_LAST_TEST_SWITCH_DATE = "last_test_switch_date"; + String CHANNEL_LAST_TEST_SWITCH_DATE_STRING = "last_test_switch_date_string"; + String CHANNEL_LAST_TEST_SWITCH_DATE_NUMBER = "last_test_switch_date_number"; + + String CHANNEL_NUMBER_OF_TEST_SWITCHES_OPERATED = "number_of_test_switches_operated"; + String CHANNEL_PERIMETER_INTRUSION_DAY_COUNTER_CUMULATED = "perimeter_intrusion_day_counter_cumulated"; + String CHANNEL_SMOKE_INLET_DAY_COUNTER_CUMULATED = "smoke_inlet_day_counter_cumulated"; +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronConfigStatusDataParser.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronConfigStatusDataParser.java index c859834..88e39f5 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronConfigStatusDataParser.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronConfigStatusDataParser.java @@ -1,67 +1,79 @@ -package org.openhab.binding.wmbus.device.itron; - -import java.util.BitSet; -import org.openhab.binding.wmbus.device.techem.decoder.Buffer; - -public class ItronConfigStatusDataParser { - - private final Byte billing; - private final BitSet status; - private final Byte product; - private final Byte battery; - private final byte[] sdErrors; - private final Byte modemErrors; - private final Byte config; - - ItronConfigStatusDataParser(byte[] buffer) { - this(new Buffer(buffer)); - } - - ItronConfigStatusDataParser(Buffer buffer) { - this.billing = buffer.readByte(); - this.status = BitSet.valueOf(new byte[] {buffer.readByte()}); - this.product = buffer.readByte(); - this.battery = buffer.readByte(); - this.sdErrors = buffer.readBytes(2); - this.modemErrors = buffer.readByte(); - this.config = buffer.readByte(); - } - - public int getBillingDate() { - return billing.intValue(); - } - - public boolean isRemovalOccurred() { - return status.get(0); - } - - public boolean isProductInstalled() { - return status.get(1); - } - - public int getOperationMode() { - int value = status.toByteArray()[0]; - return value > 3 ? (value >> 2) ^ 4 : value >> 2; - } - - public boolean isPerimeterIntrusionOccurred() { - return status.get(4); - } - - public boolean isSmokeInletBlockedOccurred() { - return status.get(5); - } - - public boolean isOutOfRangeTemperatureOccurred() { - return status.get(6); - } - - public byte getProductCode() { - return product; - } - - public int getBatteryLifetime() { - return (battery & 0xFF) >> 1; - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.itron; + +import java.util.BitSet; + +import org.openhab.binding.wmbus.device.techem.decoder.Buffer; + +public class ItronConfigStatusDataParser { + + private final Byte billing; + private final BitSet status; + private final Byte product; + private final Byte battery; + private final byte[] sdErrors; + private final Byte modemErrors; + private final Byte config; + + ItronConfigStatusDataParser(byte[] buffer) { + this(new Buffer(buffer)); + } + + ItronConfigStatusDataParser(Buffer buffer) { + this.billing = buffer.readByte(); + this.status = BitSet.valueOf(new byte[] { buffer.readByte() }); + this.product = buffer.readByte(); + this.battery = buffer.readByte(); + this.sdErrors = buffer.readBytes(2); + this.modemErrors = buffer.readByte(); + this.config = buffer.readByte(); + } + + public int getBillingDate() { + return billing.intValue(); + } + + public boolean isRemovalOccurred() { + return status.get(0); + } + + public boolean isProductInstalled() { + return status.get(1); + } + + public int getOperationMode() { + int value = status.toByteArray()[0]; + return value > 3 ? (value >> 2) ^ 4 : value >> 2; + } + + public boolean isPerimeterIntrusionOccurred() { + return status.get(4); + } + + public boolean isSmokeInletBlockedOccurred() { + return status.get(5); + } + + public boolean isOutOfRangeTemperatureOccurred() { + return status.get(6); + } + + public byte getProductCode() { + return product; + } + + public int getBatteryLifetime() { + return (battery & 0xFF) >> 1; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronDiscoveryParticipant.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronDiscoveryParticipant.java index a8cac6d..2912dcb 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronDiscoveryParticipant.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronDiscoveryParticipant.java @@ -1,106 +1,111 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.device.itron; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.smarthome.config.discovery.DiscoveryResult; -import org.eclipse.smarthome.config.discovery.DiscoveryResultBuilder; -import org.eclipse.smarthome.core.thing.Thing; -import org.eclipse.smarthome.core.thing.ThingTypeUID; -import org.eclipse.smarthome.core.thing.ThingUID; -import org.openhab.binding.wmbus.BindingConfiguration; -import org.openhab.binding.wmbus.WMBusBindingConstants; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.discovery.WMBusDiscoveryParticipant; -import org.openmuc.jmbus.DeviceType; -import org.openmuc.jmbus.SecondaryAddress; -import org.openmuc.jmbus.wireless.WMBusMessage; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Reference; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Discovers itron devices and adds additional layer for decoding on top of generic WM-Bus devices. - * - * @author Łukasz Dywicki - initial contribution. - */ -@Component -public class ItronDiscoveryParticipant implements WMBusDiscoveryParticipant { - - private final Logger logger = LoggerFactory.getLogger(ItronDiscoveryParticipant.class); - - private BindingConfiguration configuration; - - @Override - public @NonNull Set getSupportedThingTypeUIDs() { - return ItronBindingConstants.SUPPORTED_THING_TYPES; - } - - @Override - public @Nullable ThingUID getThingUID(WMBusDevice device) { - ThingTypeUID type = ItronBindingConstants.THING_TYPE_ITRON_SMOKE_DETECTOR; - if (configuration.getIncludeBridgeUID()) { - return new ThingUID(type, device.getAdapter().getUID(), device.getDeviceId()); - } - return new ThingUID(type, device.getDeviceId()); - } - - @Override - public DiscoveryResult createResult(WMBusDevice device) { - if (isItronSmokeDetector(device)) { - String label = "Itron smoke detector #" + device.getDeviceId(); - - Map properties = new HashMap<>(); - properties.put(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS, device.getDeviceAddress()); - properties.put(Thing.PROPERTY_VENDOR, "Itron"); - properties.put(Thing.PROPERTY_SERIAL_NUMBER, device.getDeviceId()); - SecondaryAddress secondaryAddress = device.getOriginalMessage().getSecondaryAddress(); - properties.put(Thing.PROPERTY_MODEL_ID, secondaryAddress.getVersion()); - properties.put(WMBusBindingConstants.PROPERTY_DEVICE_ENCRYPTED, device.isEnrypted()); - - ThingUID thingUID = getThingUID(device); - return DiscoveryResultBuilder.create(thingUID).withProperties(properties) - .withRepresentationProperty(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS).withLabel(label) - .withThingType(ItronBindingConstants.THING_TYPE_ITRON_SMOKE_DETECTOR) - .withBridge(device.getAdapter().getUID()).withLabel(label).withTTL(getTimeToLive()).build(); - } - - return null; - } - - private boolean isItronSmokeDetector(WMBusDevice device) { - WMBusMessage message = device.getOriginalMessage(); - - if (!ItronBindingConstants.ITRON_MANUFACTURER_ID.equals(message.getSecondaryAddress().getManufacturerId()) || - message.getSecondaryAddress().getDeviceType() != DeviceType.SMOKE_DETECTOR) { - return false; - } - - return true; - } - - @Reference - public void setBindingConfiguration(BindingConfiguration configuration) { - this.configuration = configuration; - } - - public void unsetBindingConfiguration(BindingConfiguration configuration) { - this.configuration = null; - } - - private Long getTimeToLive() { - return configuration.getTimeToLive(); - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.itron; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNull; +import org.openhab.binding.wmbus.BindingConfiguration; +import org.openhab.binding.wmbus.WMBusBindingConstants; +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.discovery.WMBusDiscoveryParticipant; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.openmuc.jmbus.DeviceType; +import org.openmuc.jmbus.SecondaryAddress; +import org.openmuc.jmbus.wireless.WMBusMessage; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Discovers itron devices and adds additional layer for decoding on top of generic WM-Bus devices. + * + * @author Łukasz Dywicki - initial contribution. + */ +@Component +public class ItronDiscoveryParticipant implements WMBusDiscoveryParticipant { + + private final Logger logger = LoggerFactory.getLogger(ItronDiscoveryParticipant.class); + + private BindingConfiguration configuration; + + @Override + public @NonNull Set getSupportedThingTypeUIDs() { + return ItronBindingConstants.SUPPORTED_THING_TYPES; + } + + @Override + public @NonNull ThingUID getThingUID(WMBusDevice device) { + ThingTypeUID type = ItronBindingConstants.THING_TYPE_ITRON_SMOKE_DETECTOR; + if (configuration.getIncludeBridgeUID()) { + return new ThingUID(type, device.getAdapter().getUID(), device.getDeviceId()); + } + return new ThingUID(type, device.getDeviceId()); + } + + @Override + public DiscoveryResult createResult(WMBusDevice device) { + if (isItronSmokeDetector(device)) { + String label = "Itron smoke detector #" + device.getDeviceId(); + + Map properties = new HashMap<>(); + properties.put(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS, device.getDeviceAddress()); + properties.put(Thing.PROPERTY_VENDOR, "Itron"); + properties.put(Thing.PROPERTY_SERIAL_NUMBER, device.getDeviceId()); + SecondaryAddress secondaryAddress = device.getOriginalMessage().getSecondaryAddress(); + properties.put(Thing.PROPERTY_MODEL_ID, secondaryAddress.getVersion()); + properties.put(WMBusBindingConstants.PROPERTY_DEVICE_ENCRYPTED, device.isEnrypted()); + @NonNull + ThingUID thingUID = getThingUID(device); + if (thingUID != null) + return DiscoveryResultBuilder.create(thingUID).withProperties(properties) + .withRepresentationProperty(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS).withLabel(label) + .withThingType(ItronBindingConstants.THING_TYPE_ITRON_SMOKE_DETECTOR) + .withBridge(device.getAdapter().getUID()).withLabel(label).withTTL(getTimeToLive()).build(); + + } + + return null; + } + + private boolean isItronSmokeDetector(WMBusDevice device) { + WMBusMessage message = device.getOriginalMessage(); + + if (!ItronBindingConstants.ITRON_MANUFACTURER_ID.equals(message.getSecondaryAddress().getManufacturerId()) + || message.getSecondaryAddress().getDeviceType() != DeviceType.SMOKE_DETECTOR) { + return false; + } + + return true; + } + + @Reference + public void setBindingConfiguration(BindingConfiguration configuration) { + this.configuration = configuration; + } + + public void unsetBindingConfiguration(BindingConfiguration configuration) { + this.configuration = null; + } + + private Long getTimeToLive() { + return configuration.getTimeToLive(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronHandlerFactory.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronHandlerFactory.java index 0b4d0b9..839f95b 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronHandlerFactory.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronHandlerFactory.java @@ -1,81 +1,77 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ - -package org.openhab.binding.wmbus.device.itron; - -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.openhab.binding.wmbus.UnitRegistry; -import org.openhab.binding.wmbus.device.techem.TechemDevice; -import org.openhab.binding.wmbus.device.techem.TechemHeatMeter; -import org.openhab.binding.wmbus.device.techem.decoder.TechemFrameDecoder; -import org.openhab.binding.wmbus.device.techem.handler.TechemMeterHandler; -import org.openhab.io.transport.mbus.wireless.FilteredKeyStorage; -import org.openhab.io.transport.mbus.wireless.KeyStorage; -import org.osgi.service.component.ComponentContext; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Deactivate; -import org.osgi.service.component.annotations.Reference; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link ItronHandlerFactory} covers logic specific to Itron devices. - * - * @author Łukasz Dywicki - Initial contribution. - */ - -@Component(service = { ItronHandlerFactory.class, BaseThingHandlerFactory.class, ThingHandlerFactory.class }) -public class ItronHandlerFactory extends BaseThingHandlerFactory { - - // OpenHAB logger - private final Logger logger = LoggerFactory.getLogger(ItronHandlerFactory.class); - private KeyStorage keyStorage; - private UnitRegistry unitRegistry; - - @Override - public boolean supportsThingType(ThingTypeUID thingTypeUID) { - return ItronBindingConstants.SUPPORTED_THING_TYPES.contains(thingTypeUID); - } - - @Override - protected ThingHandler createHandler(Thing thing) { - ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - - if (thingTypeUID.equals(ItronBindingConstants.THING_TYPE_ITRON_SMOKE_DETECTOR)) { - logger.debug("Creating handler for Itron device {}", thing.getUID().getId()); - return new ItronSmokeDetectorHandler(thing, new FilteredKeyStorage(keyStorage, thing), unitRegistry); - } - - return null; - } - - @Reference - public void setKeyStorage(KeyStorage keyStorage) { - this.keyStorage = keyStorage; - } - - public void unsetKeyStorage(KeyStorage keyStorage) { - this.keyStorage = null; - } - - @Reference - protected void setUnitRegistry(UnitRegistry unitRegistry) { - this.unitRegistry = unitRegistry; - } - - protected void unsetUnitRegistry(UnitRegistry unitRegistry) { - this.unitRegistry = null; - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.itron; + +import org.openhab.binding.wmbus.UnitRegistry; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.BaseThingHandlerFactory; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.openhab.io.transport.mbus.wireless.FilteredKeyStorage; +import org.openhab.io.transport.mbus.wireless.KeyStorage; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link ItronHandlerFactory} covers logic specific to Itron devices. + * + * @author Łukasz Dywicki - Initial contribution. + */ + +@Component(service = { ItronHandlerFactory.class, BaseThingHandlerFactory.class, ThingHandlerFactory.class }) +public class ItronHandlerFactory extends BaseThingHandlerFactory { + + // OpenHAB logger + private final Logger logger = LoggerFactory.getLogger(ItronHandlerFactory.class); + private KeyStorage keyStorage; + private UnitRegistry unitRegistry; + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return ItronBindingConstants.SUPPORTED_THING_TYPES.contains(thingTypeUID); + } + + @Override + protected ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (thingTypeUID.equals(ItronBindingConstants.THING_TYPE_ITRON_SMOKE_DETECTOR)) { + logger.debug("Creating handler for Itron device {}", thing.getUID().getId()); + return new ItronSmokeDetectorHandler(thing, new FilteredKeyStorage(keyStorage, thing), unitRegistry); + } + + return null; + } + + @Reference + public void setKeyStorage(KeyStorage keyStorage) { + this.keyStorage = keyStorage; + } + + public void unsetKeyStorage(KeyStorage keyStorage) { + this.keyStorage = null; + } + + @Reference + protected void setUnitRegistry(UnitRegistry unitRegistry) { + this.unitRegistry = unitRegistry; + } + + protected void unsetUnitRegistry(UnitRegistry unitRegistry) { + this.unitRegistry = null; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronManufacturerDataParser.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronManufacturerDataParser.java index 04a5b2f..9937d4e 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronManufacturerDataParser.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronManufacturerDataParser.java @@ -1,68 +1,81 @@ -package org.openhab.binding.wmbus.device.itron; - -import java.time.DayOfWeek; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.Arrays; -import org.openhab.binding.wmbus.device.techem.decoder.Buffer; - -public class ItronManufacturerDataParser { - - private final static byte[] EMPTY_SHORT_DATE_1 = new byte[] {0x0, 0x0, 0x1, 0x1}; - private final static byte[] EMPTY_SHORT_DATE_2 = new byte[] {0x0, 0x0, 0x0, 0x0}; - private final Buffer buffer; - - ItronManufacturerDataParser(Buffer buffer) { - this.buffer = buffer; - } - - public LocalDateTime readShortDateTime() { - return parseShortDateTime(buffer.readBytes(4)); - } - - public LocalDateTime readLongDateTime() { - return parseLongDateTime(buffer.readBytes(5)); - } - - private LocalDateTime parseShortDateTime(byte[] date) { - if (Arrays.equals(date, EMPTY_SHORT_DATE_1) || Arrays.equals(date, EMPTY_SHORT_DATE_2)) { - return null; - } - - int minute = date[0] & 0xFF >> 2; - - int yearLSB = (date[2] & 0xFF) >> 5; - int yearMSB = ((date[3] & 0xFF) & 0xF0) >> 1; - int hour = (date[1] & 0xFF >> 3); - int day = (date[2] & 0xFF >> 3); - int month = (date[3] & 0xFF >> 4); - - // ((0x2C & 0xF0) >> 0x1) | (0x7D >> 0x5) = 0x13 = 19d - int year = yearMSB | yearLSB; - - return LocalDateTime.of(2000 + year, month, day, hour, minute); - } - - private LocalDateTime parseLongDateTime(byte[] date) { - int second = (date[0] & 0xFF) >> 2; - int minute = (date[1] & 0xFF) >> 2; - int hour = (date[2] & 0xFF >> 3); - int day = (date[2] & 0xFF >> 4); - int month = (date[3] & 0xFF >> 4); - - int yearLSB = (date[3] & 0xFF) >> 5; - int yearMSB = ((date[4] & 0xFF) & 0xF0) >> 1; - - // ((0x2C & 0xF0) >> 0x1) | (0x7D >> 0x5) = 0x13 = 19d - int year = yearMSB | yearLSB; - - LocalTime time = LocalTime.of(hour, minute, second); - DayOfWeek dayOfWeek = DayOfWeek.of(day); - LocalDate today = LocalDate.now(); - LocalDate parsed = LocalDate.of(year, month, today.getDayOfMonth()); - - LocalTime timeWithDay = time.with(dayOfWeek); - return parsed.atStartOfDay().with(timeWithDay); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.itron; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Arrays; + +import org.openhab.binding.wmbus.device.techem.decoder.Buffer; + +public class ItronManufacturerDataParser { + + private final static byte[] EMPTY_SHORT_DATE_1 = new byte[] { 0x0, 0x0, 0x1, 0x1 }; + private final static byte[] EMPTY_SHORT_DATE_2 = new byte[] { 0x0, 0x0, 0x0, 0x0 }; + private final Buffer buffer; + + ItronManufacturerDataParser(Buffer buffer) { + this.buffer = buffer; + } + + public LocalDateTime readShortDateTime() { + return parseShortDateTime(buffer.readBytes(4)); + } + + public LocalDateTime readLongDateTime() { + return parseLongDateTime(buffer.readBytes(5)); + } + + private LocalDateTime parseShortDateTime(byte[] date) { + if (Arrays.equals(date, EMPTY_SHORT_DATE_1) || Arrays.equals(date, EMPTY_SHORT_DATE_2)) { + return null; + } + + int minute = date[0] & 0xFF >> 2; + + int yearLSB = (date[2] & 0xFF) >> 5; + int yearMSB = ((date[3] & 0xFF) & 0xF0) >> 1; + int hour = (date[1] & 0xFF >> 3); + int day = (date[2] & 0xFF >> 3); + int month = (date[3] & 0xFF >> 4); + + // ((0x2C & 0xF0) >> 0x1) | (0x7D >> 0x5) = 0x13 = 19d + int year = yearMSB | yearLSB; + + return LocalDateTime.of(2000 + year, month, day, hour, minute); + } + + private LocalDateTime parseLongDateTime(byte[] date) { + int second = (date[0] & 0xFF) >> 2; + int minute = (date[1] & 0xFF) >> 2; + int hour = (date[2] & 0xFF >> 3); + int day = (date[2] & 0xFF >> 4); + int month = (date[3] & 0xFF >> 4); + + int yearLSB = (date[3] & 0xFF) >> 5; + int yearMSB = ((date[4] & 0xFF) & 0xF0) >> 1; + + // ((0x2C & 0xF0) >> 0x1) | (0x7D >> 0x5) = 0x13 = 19d + int year = yearMSB | yearLSB; + + LocalTime time = LocalTime.of(hour, minute, second); + DayOfWeek dayOfWeek = DayOfWeek.of(day); + LocalDate today = LocalDate.now(); + LocalDate parsed = LocalDate.of(year, month, today.getDayOfMonth()); + + LocalTime timeWithDay = time.with(dayOfWeek); + return parsed.atStartOfDay().with(timeWithDay); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronSmokeDetectorHandler.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronSmokeDetectorHandler.java index 50bf91e..4585caa 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronSmokeDetectorHandler.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronSmokeDetectorHandler.java @@ -1,159 +1,177 @@ -package org.openhab.binding.wmbus.device.itron; - -import java.time.LocalDateTime; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.smarthome.core.library.types.DecimalType; -import org.eclipse.smarthome.core.library.types.OnOffType; -import org.eclipse.smarthome.core.thing.ChannelUID; -import org.eclipse.smarthome.core.thing.Thing; -import org.eclipse.smarthome.core.types.Command; -import org.eclipse.smarthome.core.types.UnDefType; -import org.eclipse.smarthome.core.util.HexUtils; -import org.openhab.binding.wmbus.RecordType; -import org.openhab.binding.wmbus.UnitRegistry; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.device.generic.GenericWMBusThingHandler; -import org.openhab.binding.wmbus.device.techem.decoder.Buffer; -import org.openhab.io.transport.mbus.wireless.KeyStorage; -import org.openmuc.jmbus.DataRecord; -import org.openmuc.jmbus.DecodingException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import com.google.common.primitives.Longs; - -public class ItronSmokeDetectorHandler extends GenericWMBusThingHandler { - - public static final RecordType CURRENT_DATE_06_6D = new RecordType(0x06, 0x6D); - public static final RecordType CONFIG_STATUS_07_7F = new RecordType(0x07, 0x7F); - - private final Logger logger = LoggerFactory.getLogger(ItronSmokeDetectorHandler.class); - private Map parsedFrame = new HashMap<>(); - - public ItronSmokeDetectorHandler(Thing thing, KeyStorage keyStorage, UnitRegistry unitRegistry) { - super(thing, keyStorage, unitRegistry, Collections.emptyMap()); - } - - @Override - protected WMBusDevice parseDevice(WMBusDevice device) throws DecodingException { - WMBusDevice parsedDevice = super.parseDevice(device); - - DataRecord record = device.findRecord(CONFIG_STATUS_07_7F); - if (record != null && record.getDataValueType() == DataRecord.DataValueType.LONG && record.getDescription() == DataRecord.Description.MANUFACTURER_SPECIFIC) { - Long dataValue = (Long) record.getDataValue(); - ItronConfigStatusDataParser configStatus = new ItronConfigStatusDataParser(Longs.toByteArray(dataValue)); - - // first (MSB) byte with billing date - parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_BILLING_DATE, configStatus.getBillingDate()); - - // second byte with status codes - parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_REMOVAL_OCCURRED, configStatus.isRemovalOccurred()); - parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_PRODUCT_INSTALLED, configStatus.isProductInstalled()); - parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_OPERATION_MODE, configStatus.getOperationMode()); - parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_PERIMETER_INTRUSION_OCCURRED, configStatus.isPerimeterIntrusionOccurred()); - parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_SMOKE_INLET_BLOCKED_OCCURRED, configStatus.isSmokeInletBlockedOccurred()); - parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_OUT_OF_TEMP_RANGE_OCCURRED, configStatus.isOutOfRangeTemperatureOccurred()); - - // third byte with error codes - parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_PRODUCT_CODE, HexUtils.byteToHex(configStatus.getProductCode())); - - // fourth byte with battery lifetime - parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_BATTERY_LIFETIME, configStatus.getBatteryLifetime()); - } - - // fifth and sixth byte SD errors - //parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_PERIMETER_INTRUSION, configStatus.readBytes(2)); - - // 7tn byte is modem error codes - //parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_REMOVAL_ERROR, configStatus.readByte()); - - // 8tn byte is config byte - //parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_DATA_ENCRYPTED, configStatus.readByte()); - - - byte[] manufacturerData = parsedDevice.getOriginalMessage().getVariableDataResponse().getManufacturerData(); - - Buffer buffer = new Buffer(manufacturerData); - ItronManufacturerDataParser parser = new ItronManufacturerDataParser(buffer); - - LocalDateTime eventDate = parser.readShortDateTime(); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_ALERT_START_DATE, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_ALERT_START_DATE_NUMBER, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_ALERT_START_DATE_STRING, eventDate); - - eventDate = parser.readShortDateTime(); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_ALERT_END_DATE, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_ALERT_END_DATE_NUMBER, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_ALERT_END_DATE_STRING, eventDate); - - eventDate = parser.readShortDateTime(); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_BEEPER_STOPPED_DURING_SMOKE_ALERT_DATE, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_BEEPER_STOPPED_DURING_SMOKE_ALERT_DATE_NUMBER, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_BEEPER_STOPPED_DURING_SMOKE_ALERT_DATE_STRING, eventDate); - - eventDate = parser.readShortDateTime(); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_OCCURRED_DATE, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_OCCURRED_DATE_NUMBER, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_OCCURRED_DATE_STRING, eventDate); - - eventDate = parser.readShortDateTime(); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_REMOVED_DATE, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_REMOVED_DATE_NUMBER, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_REMOVED_DATE_STRING, eventDate); - - eventDate = parser.readShortDateTime(); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_INLET_BLOCKED_DATE, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_INLET_BLOCKED_DATE_NUMBER, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_INLET_BLOCKED_DATE_STRING, eventDate); - - eventDate = parser.readShortDateTime(); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_INLET_BLOCKING_REMOVED_DATE, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_INLET_BLOCKING_REMOVED_DATE_NUMBER, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_INLET_BLOCKING_REMOVED_DATE_STRING, eventDate); - - eventDate = parser.readShortDateTime(); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_TEMPERATURE_OUT_OF_RANGE_DATE, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_TEMPERATURE_OUT_OF_RANGE_DATE_NUMBER, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_TEMPERATURE_OUT_OF_RANGE_DATE_STRING, eventDate); - - eventDate = parser.readShortDateTime(); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_TEST_SWITCH_DATE, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_TEST_SWITCH_DATE_NUMBER, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_TEST_SWITCH_DATE_STRING, eventDate); - - parsedFrame.put(ItronBindingConstants.CHANNEL_NUMBER_OF_TEST_SWITCHES_OPERATED, buffer.readShort()); - parsedFrame.put(ItronBindingConstants.CHANNEL_PERIMETER_INTRUSION_DAY_COUNTER_CUMULATED, buffer.readShort()); - parsedFrame.put(ItronBindingConstants.CHANNEL_SMOKE_INLET_DAY_COUNTER_CUMULATED, buffer.available() < 2 ? buffer.readByte() : buffer.readShort()); - - return parsedDevice; - } - - @Override - public void handleCommand(@NonNull ChannelUID channelUID, @NonNull Command command) { - if (parsedFrame.containsKey(channelUID.getId())) { - // channel directly maps to manufacturer data appended to frame - logger.debug("Mapping custom smoke detector channel {} to manufacturer data", channelUID); - - Object value = parsedFrame.get(channelUID.getId()); - if (value == null) { - updateState(channelUID, UnDefType.NULL); - } else if (value instanceof LocalDateTime) { - updateState(channelUID, convertDate(value)); - } else if (value instanceof Number) { - updateState(channelUID, new DecimalType(((Number) value).floatValue())); - } else if (value instanceof Boolean) { - updateState(channelUID, ((boolean) value) ? OnOffType.ON : OnOffType.OFF); - } else { - logger.warn("Unsupported value type {}", value); - } - } else { - // try to do a lookup based on channel to record mapping - super.handleCommand(channelUID, command); - } - } - - - -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.itron; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNull; +import org.openhab.binding.wmbus.RecordType; +import org.openhab.binding.wmbus.UnitRegistry; +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.device.generic.GenericWMBusThingHandler; +import org.openhab.binding.wmbus.device.techem.decoder.Buffer; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; +import org.openhab.core.types.UnDefType; +import org.openhab.core.util.HexUtils; +import org.openhab.io.transport.mbus.wireless.KeyStorage; +import org.openmuc.jmbus.DataRecord; +import org.openmuc.jmbus.DecodingException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.primitives.Longs; + +public class ItronSmokeDetectorHandler extends GenericWMBusThingHandler { + + public static final RecordType CURRENT_DATE_06_6D = new RecordType(0x06, 0x6D); + public static final RecordType CONFIG_STATUS_07_7F = new RecordType(0x07, 0x7F); + + private final Logger logger = LoggerFactory.getLogger(ItronSmokeDetectorHandler.class); + private Map parsedFrame = new HashMap<>(); + + public ItronSmokeDetectorHandler(Thing thing, KeyStorage keyStorage, UnitRegistry unitRegistry) { + super(thing, keyStorage, unitRegistry, Collections.emptyMap()); + } + + @Override + protected WMBusDevice parseDevice(WMBusDevice device) throws DecodingException { + WMBusDevice parsedDevice = super.parseDevice(device); + + DataRecord record = device.findRecord(CONFIG_STATUS_07_7F); + if (record != null && record.getDataValueType() == DataRecord.DataValueType.LONG + && record.getDescription() == DataRecord.Description.MANUFACTURER_SPECIFIC) { + Long dataValue = (Long) record.getDataValue(); + ItronConfigStatusDataParser configStatus = new ItronConfigStatusDataParser(Longs.toByteArray(dataValue)); + + // first (MSB) byte with billing date + parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_BILLING_DATE, configStatus.getBillingDate()); + + // second byte with status codes + parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_REMOVAL_OCCURRED, configStatus.isRemovalOccurred()); + parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_PRODUCT_INSTALLED, configStatus.isProductInstalled()); + parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_OPERATION_MODE, configStatus.getOperationMode()); + parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_PERIMETER_INTRUSION_OCCURRED, + configStatus.isPerimeterIntrusionOccurred()); + parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_SMOKE_INLET_BLOCKED_OCCURRED, + configStatus.isSmokeInletBlockedOccurred()); + parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_OUT_OF_TEMP_RANGE_OCCURRED, + configStatus.isOutOfRangeTemperatureOccurred()); + + // third byte with error codes + parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_PRODUCT_CODE, + HexUtils.byteToHex(configStatus.getProductCode())); + + // fourth byte with battery lifetime + parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_BATTERY_LIFETIME, configStatus.getBatteryLifetime()); + } + + // fifth and sixth byte SD errors + // parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_PERIMETER_INTRUSION, configStatus.readBytes(2)); + + // 7tn byte is modem error codes + // parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_REMOVAL_ERROR, configStatus.readByte()); + + // 8tn byte is config byte + // parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_DATA_ENCRYPTED, configStatus.readByte()); + + byte[] manufacturerData = parsedDevice.getOriginalMessage().getVariableDataResponse().getManufacturerData(); + + Buffer buffer = new Buffer(manufacturerData); + ItronManufacturerDataParser parser = new ItronManufacturerDataParser(buffer); + + LocalDateTime eventDate = parser.readShortDateTime(); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_ALERT_START_DATE, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_ALERT_START_DATE_NUMBER, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_ALERT_START_DATE_STRING, eventDate); + + eventDate = parser.readShortDateTime(); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_ALERT_END_DATE, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_ALERT_END_DATE_NUMBER, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_ALERT_END_DATE_STRING, eventDate); + + eventDate = parser.readShortDateTime(); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_BEEPER_STOPPED_DURING_SMOKE_ALERT_DATE, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_BEEPER_STOPPED_DURING_SMOKE_ALERT_DATE_NUMBER, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_BEEPER_STOPPED_DURING_SMOKE_ALERT_DATE_STRING, eventDate); + + eventDate = parser.readShortDateTime(); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_OCCURRED_DATE, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_OCCURRED_DATE_NUMBER, + eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_OCCURRED_DATE_STRING, + eventDate); + + eventDate = parser.readShortDateTime(); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_REMOVED_DATE, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_REMOVED_DATE_NUMBER, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_REMOVED_DATE_STRING, eventDate); + + eventDate = parser.readShortDateTime(); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_INLET_BLOCKED_DATE, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_INLET_BLOCKED_DATE_NUMBER, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_INLET_BLOCKED_DATE_STRING, eventDate); + + eventDate = parser.readShortDateTime(); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_INLET_BLOCKING_REMOVED_DATE, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_INLET_BLOCKING_REMOVED_DATE_NUMBER, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_INLET_BLOCKING_REMOVED_DATE_STRING, eventDate); + + eventDate = parser.readShortDateTime(); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_TEMPERATURE_OUT_OF_RANGE_DATE, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_TEMPERATURE_OUT_OF_RANGE_DATE_NUMBER, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_TEMPERATURE_OUT_OF_RANGE_DATE_STRING, eventDate); + + eventDate = parser.readShortDateTime(); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_TEST_SWITCH_DATE, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_TEST_SWITCH_DATE_NUMBER, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_TEST_SWITCH_DATE_STRING, eventDate); + + parsedFrame.put(ItronBindingConstants.CHANNEL_NUMBER_OF_TEST_SWITCHES_OPERATED, buffer.readShort()); + parsedFrame.put(ItronBindingConstants.CHANNEL_PERIMETER_INTRUSION_DAY_COUNTER_CUMULATED, buffer.readShort()); + parsedFrame.put(ItronBindingConstants.CHANNEL_SMOKE_INLET_DAY_COUNTER_CUMULATED, + buffer.available() < 2 ? buffer.readByte() : buffer.readShort()); + + return parsedDevice; + } + + @Override + public void handleCommand(@NonNull ChannelUID channelUID, @NonNull Command command) { + if (parsedFrame.containsKey(channelUID.getId())) { + // channel directly maps to manufacturer data appended to frame + logger.debug("Mapping custom smoke detector channel {} to manufacturer data", channelUID); + + Object value = parsedFrame.get(channelUID.getId()); + if (value == null) { + updateState(channelUID, UnDefType.NULL); + } else if (value instanceof LocalDateTime) { + updateState(channelUID, convertDate(value)); + } else if (value instanceof Number) { + updateState(channelUID, new DecimalType(((Number) value).floatValue())); + } else if (value instanceof Boolean) { + updateState(channelUID, ((boolean) value) ? OnOffType.ON : OnOffType.OFF); + } else { + logger.warn("Unsupported value type {}", value); + } + } else { + // try to do a lookup based on channel to record mapping + super.handleCommand(channelUID, command); + } + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/Record.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/Record.java index 6c0115a..5ddbf24 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/Record.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/Record.java @@ -1,61 +1,65 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.device.techem; - -public class Record { - - public enum Type { - CURRENT_VOLUME, - CURRENT_READING_DATE(true), - CURRENT_READING_DATE_SMOKE(true), - PAST_VOLUME, - PAST_READING_DATE(true), - ROOM_TEMPERATURE, - RADIATOR_TEMPERATURE, - RSSI, - ALMANAC, - STATUS, - COUNTER; - - private final boolean dateField; - - Type() { - this(false); - } - - Type(boolean dateField) { - this.dateField = dateField; - } - - public boolean isDate() { - return dateField; - } - } - - private final Type type; - private final T value; - - public Record(Type type, T value) { - this.type = type; - this.value = value; - } - - public final Type getType() { - return this.type; - } - - public final T getValue() { - return this.value; - } - - @Override - public String toString() { - return "Record [" + type + ", " + value + "]"; - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem; + +public class Record { + + public enum Type { + CURRENT_VOLUME, + CURRENT_READING_DATE(true), + CURRENT_READING_DATE_SMOKE(true), + PAST_VOLUME, + PAST_READING_DATE(true), + ROOM_TEMPERATURE, + RADIATOR_TEMPERATURE, + RSSI, + ALMANAC, + STATUS, + COUNTER; + + private final boolean dateField; + + Type() { + this(false); + } + + Type(boolean dateField) { + this.dateField = dateField; + } + + public boolean isDate() { + return dateField; + } + } + + private final Type type; + private final T value; + + public Record(Type type, T value) { + this.type = type; + this.value = value; + } + + public final Type getType() { + return this.type; + } + + public final T getValue() { + return this.value; + } + + @Override + public String toString() { + return "Record [" + type + ", " + value + "]"; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemBindingConstants.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemBindingConstants.java index 1ac961a..5ef4007 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemBindingConstants.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemBindingConstants.java @@ -1,210 +1,214 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.device.techem; - -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import org.eclipse.smarthome.core.thing.ThingTypeUID; -import org.openhab.binding.wmbus.WMBusBindingConstants; -import org.openhab.binding.wmbus.device.techem.Record.Type; -import org.openmuc.jmbus.DeviceType; - -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; - -/** - * Subset of WMBus constants specific to Techem devices. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public interface TechemBindingConstants { - - String MANUFACTURER_ID = "TCH"; - - String CHANNEL_STATUS = "status"; - String CHANNEL_ALMANAC = "almanac"; - // temperatures - String CHANNEL_ROOMTEMPERATURE = "room_temperature"; - String CHANNEL_RADIATORTEMPERATURE = "radiator_temperature"; - // values - String CHANNEL_CURRENTREADING = "current_reading"; - String CHANNEL_LASTREADING = "last_reading"; - - // dates - String CHANNEL_LASTDATE_NUMBER = "last_date_number"; - String CHANNEL_LASTDATE_STRING = "last_date_string"; - String CHANNEL_LASTDATE = "last_date"; - String CHANNEL_CURRENTDATE_NUMBER = "current_date_number"; - String CHANNEL_CURRENTDATE_STRING = "current_date_string"; - String CHANNEL_CURRENTDATE = "current_date"; - String CHANNEL_RECEPTION = "reception"; - String CHANNEL_CURRENTDATE_SMOKE_NUMBER = "current_date_smoke_number"; - String CHANNEL_CURRENTDATE_SMOKE_STRING = "current_date_smoke_string"; - String CHANNEL_CURRENTDATE_SMOKE = "current_date_smoke"; - - // warm water version 0x70 -> 112, type 0x62 -> 98 - Variant _68TCH11298_6 = new Variant(0x70, 0x62, 0xA0, DeviceType.WARM_WATER_METER); - // cold water version 0x70 -> 112, type 0x72 -> 114 - Variant _68TCH112114_16 = new Variant(0x70, 0x72, 0xA0, DeviceType.COLD_WATER_METER); - // warm water version 0x74 -> 116, type 0x62 -> 98 - Variant _68TCH11698_6 = new Variant(0x74, 0x62, 0xA2, DeviceType.WARM_WATER_METER); - // cold water version 0x74 -> 116, type 0x72 -> 114 - Variant _68TCH116114_16 = new Variant(0x74, 0x72, 0xA2, DeviceType.COLD_WATER_METER); - // warm water version 0x95 -> 149, type 0x62 -> 98 - Variant _68TCH14998_6 = new Variant(0x95, 0x62, 0xA2, DeviceType.WARM_WATER_METER); - // cold water version 0x95 -> 149, type 0x72 -> 114 - Variant _68TCH149114_16 = new Variant(0x95, 0x72, 0xA2, DeviceType.COLD_WATER_METER); - - // heat version 0x22 -> v 34, type 0x43 -> 67 - Variant _68TCH3467_4 = new Variant(0x22, 0x43, 0xA2, DeviceType.HEAT_METER); - // heat version 0x39 -> v 57, type 0x43 -> 67 - Variant _68TCH5767_4 = new Variant(0x39, 0x43, 0xA2, DeviceType.HEAT_METER); - // heat version 0x71 -> v 113, type 0x43 -> 67 - Variant _68TCH11367_4_A0 = new Variant(0x71, 0x43, 0xA0, DeviceType.HEAT_METER); - Variant _68TCH11367_4_A2 = new Variant(0x71, 0x43, 0xA2, DeviceType.HEAT_METER); - - // heat version 0x57 -> v 87, type 0x44 -> 68 - Variant _68TCH8768_4 = new Variant(0x57, 0x44, 0xA2, DeviceType.HEAT_METER); - - // TODO Isn't this a heat meter? - // hkv version 0x45 -> 69, type 0x43 -> 67 - Variant _68TCH6967_8 = new Variant(0x45, 0x43, 0xA1, DeviceType.HEAT_COST_ALLOCATOR); - - // hkv version 0x61 -> 97, type ? - Variant _68TCH97255_8 = new Variant(0x61, DeviceType.RESERVED.getId(), 0xA2, DeviceType.HEAT_COST_ALLOCATOR); - // hkv version 0x64 -> 100, type 0x80 -> 128 - Variant _68TCH100128_8 = new Variant(0x64, 0x80, 0xA0, DeviceType.HEAT_COST_ALLOCATOR); - // hkv version 0x69 -> 105, type 0x80 -> 128 - Variant _68TCH105128_8 = new Variant(0x69, 0x80, 0xA0, DeviceType.HEAT_COST_ALLOCATOR); - // hkv version 0x94 -> 148, type 0x80 -> 128 - Variant _68TCH148128_8 = new Variant(0x94, 0x80, 0xA2, DeviceType.HEAT_COST_ALLOCATOR); - - // smoke detector version 0x76 -> 118, type 0xf0 -> 240 - Variant _68TCH118255_161_A0 = new Variant(0x76, 0xf0, 0xA0, DeviceType.SMOKE_DETECTOR); - Variant _68TCH118255_161_A1 = new Variant(0x76, 0xf0, 0xA1, DeviceType.SMOKE_DETECTOR); - - // water meters - String THING_TYPE_NAME_TECHEM_WARM_WATER_METER = "techem_wz62"; - String THING_TYPE_NAME_TECHEM_COLD_WATER_METER = "techem_wz72"; - - // heat meter - String THING_TYPE_NAME_TECHEM_HEAT_METER = "techem_wmz43"; - - // techem heat cost allocators (Heizkostenverteiler) - String THING_TYPE_NAME_TECHEM_HKV45 = "techem_hkv45"; - String THING_TYPE_NAME_TECHEM_HKV61 = "techem_hkv61"; - String THING_TYPE_NAME_TECHEM_HKV64 = "techem_hkv64"; - String THING_TYPE_NAME_TECHEM_HKV69 = "techem_hkv69"; - String THING_TYPE_NAME_TECHEM_HKV94 = "techem_hkv94"; - - // techem smoke detector - String THING_TYPE_NAME_TECHEM_SD76 = "techem_sd76"; - - ThingTypeUID THING_TYPE_TECHEM_WARM_WATER_METER = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, - THING_TYPE_NAME_TECHEM_WARM_WATER_METER); - ThingTypeUID THING_TYPE_TECHEM_COLD_WATER_METER = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, - THING_TYPE_NAME_TECHEM_COLD_WATER_METER); - - ThingTypeUID THING_TYPE_TECHEM_HEAT_METER = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, - THING_TYPE_NAME_TECHEM_HEAT_METER); - - ThingTypeUID THING_TYPE_TECHEM_HKV45 = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, - THING_TYPE_NAME_TECHEM_HKV45); - ThingTypeUID THING_TYPE_TECHEM_HKV61 = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, - THING_TYPE_NAME_TECHEM_HKV61); - ThingTypeUID THING_TYPE_TECHEM_HKV64 = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, - THING_TYPE_NAME_TECHEM_HKV64); - ThingTypeUID THING_TYPE_TECHEM_HKV69 = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, - THING_TYPE_NAME_TECHEM_HKV69); - ThingTypeUID THING_TYPE_TECHEM_HKV94 = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, - THING_TYPE_NAME_TECHEM_HKV94); - - ThingTypeUID THING_TYPE_TECHEM_SD76 = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, - THING_TYPE_NAME_TECHEM_SD76); - - // TODO remove this part once all deployments are migrated - // old device type, remained here as alias for hkv64 - String THING_TYPE_NAME_TECHEM_HKV = "techem_hkv"; - ThingTypeUID THING_TYPE_TECHEM_HKV = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, THING_TYPE_NAME_TECHEM_HKV); - - Map SUPPORTED_DEVICE_VARIANTS = ImmutableMap. builder() - .put(_68TCH11298_6, THING_TYPE_TECHEM_WARM_WATER_METER) // WZ 112_62 - .put(_68TCH112114_16, THING_TYPE_TECHEM_COLD_WATER_METER) // WZ 112_72 - .put(_68TCH11698_6, THING_TYPE_TECHEM_WARM_WATER_METER) // WZ 116_62 - .put(_68TCH116114_16, THING_TYPE_TECHEM_COLD_WATER_METER) // WZ 116_72 - .put(_68TCH14998_6, THING_TYPE_TECHEM_WARM_WATER_METER) // WZ 149_62 - .put(_68TCH149114_16, THING_TYPE_TECHEM_COLD_WATER_METER) // WZ 149_72 - .put(_68TCH3467_4, THING_TYPE_TECHEM_HEAT_METER) // WMZ 34_43 - .put(_68TCH5767_4, THING_TYPE_TECHEM_HEAT_METER) // WMZ 57_43 - .put(_68TCH11367_4_A0, THING_TYPE_TECHEM_HEAT_METER) // WMZ 113_43 with A0 encoding - .put(_68TCH11367_4_A2, THING_TYPE_TECHEM_HEAT_METER) // WMZ 113_43 with A2 encoding - .put(_68TCH8768_4, THING_TYPE_TECHEM_HEAT_METER) // WMZ 87_44 with A2 encoding - .put(_68TCH6967_8, THING_TYPE_TECHEM_HKV45) // HKV 45 - .put(_68TCH97255_8, THING_TYPE_TECHEM_HKV61) // HKV 61 - .put(_68TCH100128_8, THING_TYPE_TECHEM_HKV64) // HKV 64 - .put(_68TCH105128_8, THING_TYPE_TECHEM_HKV69) // HKV 69 - .put(_68TCH148128_8, THING_TYPE_TECHEM_HKV94) // HKV 94 - .put(_68TCH118255_161_A0, THING_TYPE_TECHEM_SD76) // SD 76 with A0 encoding - .put(_68TCH118255_161_A1, THING_TYPE_TECHEM_SD76) // SD 76 with A1 encoding - .build(); - - Set SUPPORTED_DEVICE_TYPES = ImmutableSet - .copyOf(SUPPORTED_DEVICE_VARIANTS.keySet().stream().map(Variant::getRawType).collect(Collectors.toSet())); - - Set SUPPORTED_THING_TYPES = ImmutableSet.copyOf(SUPPORTED_DEVICE_VARIANTS.values()); - - // List all channels - // general channels - Map TECHEM_METER_MAPPING = ImmutableMap. builder() - .put(CHANNEL_CURRENTREADING, Type.CURRENT_VOLUME) // current value - .put(CHANNEL_CURRENTDATE, Type.CURRENT_READING_DATE) // present date - .put(CHANNEL_CURRENTDATE_STRING, Type.CURRENT_READING_DATE) // present date - .put(CHANNEL_CURRENTDATE_NUMBER, Type.CURRENT_READING_DATE) // present date - .put(CHANNEL_LASTREADING, Type.PAST_VOLUME) // last billing value - .put(CHANNEL_LASTDATE, Type.PAST_READING_DATE) // past billing date - .put(CHANNEL_LASTDATE_STRING, Type.PAST_READING_DATE) // past billing date - .put(CHANNEL_LASTDATE_NUMBER, Type.PAST_READING_DATE) // past billing date - .put(CHANNEL_RECEPTION, Type.RSSI) // Received Signal Strength Indicator - .put(CHANNEL_ALMANAC, Type.ALMANAC) // bi-weekly history - .build(); - - Map HEAT_ALLOCATOR_MAPPING_69 = ImmutableMap. builder() - .putAll(TECHEM_METER_MAPPING) // inherit main HKV channel map - .put(CHANNEL_ROOMTEMPERATURE, Type.ROOM_TEMPERATURE) // room - .put(CHANNEL_RADIATORTEMPERATURE, Type.RADIATOR_TEMPERATURE) // radiator - .build(); - - // measurement readings - Map SMOKE_DETECTOR_MAPPING = ImmutableMap. builder() - .put(CHANNEL_STATUS, Type.STATUS) // present date - .put(CHANNEL_CURRENTDATE, Type.CURRENT_READING_DATE) // present date - .put(CHANNEL_CURRENTDATE_STRING, Type.CURRENT_READING_DATE) // present date - .put(CHANNEL_CURRENTDATE_NUMBER, Type.CURRENT_READING_DATE) // present date - .put(CHANNEL_CURRENTDATE_SMOKE, Type.CURRENT_READING_DATE_SMOKE) // smoke detector 2nd date - .put(CHANNEL_CURRENTDATE_SMOKE_STRING, Type.CURRENT_READING_DATE_SMOKE) // smoke detector 2nd date - .put(CHANNEL_CURRENTDATE_SMOKE_NUMBER, Type.CURRENT_READING_DATE_SMOKE) // smoke detector 2nd date - .build(); - - // channel mapping for thing types - Map> RECORD_MAP = ImmutableMap.> builder() - .put(THING_TYPE_TECHEM_WARM_WATER_METER, TECHEM_METER_MAPPING) // warm - .put(THING_TYPE_TECHEM_COLD_WATER_METER, TECHEM_METER_MAPPING) // cold - - .put(THING_TYPE_TECHEM_HEAT_METER, TECHEM_METER_MAPPING) // heat meter have same set of channels as heat - // cost allocator - .put(THING_TYPE_TECHEM_HKV45, TECHEM_METER_MAPPING) // basic HKV mapping - .put(THING_TYPE_TECHEM_HKV61, TECHEM_METER_MAPPING) // basic HKV mapping - .put(THING_TYPE_TECHEM_HKV64, TECHEM_METER_MAPPING) // again basic HKV mapping - .put(THING_TYPE_TECHEM_HKV69, HEAT_ALLOCATOR_MAPPING_69) // here we have two temperature channels - .put(THING_TYPE_TECHEM_HKV94, HEAT_ALLOCATOR_MAPPING_69) // try to decode 0x94 variant in same way as 0x69 - .put(THING_TYPE_TECHEM_SD76, SMOKE_DETECTOR_MAPPING) // v118 is smoke detector, experimental channels apply - .build(); -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem; + +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.openhab.binding.wmbus.WMBusBindingConstants; +import org.openhab.binding.wmbus.device.techem.Record.Type; +import org.openhab.core.thing.ThingTypeUID; +import org.openmuc.jmbus.DeviceType; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +/** + * Subset of WMBus constants specific to Techem devices. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public interface TechemBindingConstants { + + String MANUFACTURER_ID = "TCH"; + + String CHANNEL_STATUS = "status"; + String CHANNEL_ALMANAC = "almanac"; + // temperatures + String CHANNEL_ROOMTEMPERATURE = "room_temperature"; + String CHANNEL_RADIATORTEMPERATURE = "radiator_temperature"; + // values + String CHANNEL_CURRENTREADING = "current_reading"; + String CHANNEL_LASTREADING = "last_reading"; + + // dates + String CHANNEL_LASTDATE_NUMBER = "last_date_number"; + String CHANNEL_LASTDATE_STRING = "last_date_string"; + String CHANNEL_LASTDATE = "last_date"; + String CHANNEL_CURRENTDATE_NUMBER = "current_date_number"; + String CHANNEL_CURRENTDATE_STRING = "current_date_string"; + String CHANNEL_CURRENTDATE = "current_date"; + String CHANNEL_RECEPTION = "reception"; + String CHANNEL_CURRENTDATE_SMOKE_NUMBER = "current_date_smoke_number"; + String CHANNEL_CURRENTDATE_SMOKE_STRING = "current_date_smoke_string"; + String CHANNEL_CURRENTDATE_SMOKE = "current_date_smoke"; + + // warm water version 0x70 -> 112, type 0x62 -> 98 + Variant _68TCH11298_6 = new Variant(0x70, 0x62, 0xA0, DeviceType.WARM_WATER_METER); + // cold water version 0x70 -> 112, type 0x72 -> 114 + Variant _68TCH112114_16 = new Variant(0x70, 0x72, 0xA0, DeviceType.COLD_WATER_METER); + // warm water version 0x74 -> 116, type 0x62 -> 98 + Variant _68TCH11698_6 = new Variant(0x74, 0x62, 0xA2, DeviceType.WARM_WATER_METER); + // cold water version 0x74 -> 116, type 0x72 -> 114 + Variant _68TCH116114_16 = new Variant(0x74, 0x72, 0xA2, DeviceType.COLD_WATER_METER); + // warm water version 0x95 -> 149, type 0x62 -> 98 + Variant _68TCH14998_6 = new Variant(0x95, 0x62, 0xA2, DeviceType.WARM_WATER_METER); + // cold water version 0x95 -> 149, type 0x72 -> 114 + Variant _68TCH149114_16 = new Variant(0x95, 0x72, 0xA2, DeviceType.COLD_WATER_METER); + + // heat version 0x22 -> v 34, type 0x43 -> 67 + Variant _68TCH3467_4 = new Variant(0x22, 0x43, 0xA2, DeviceType.HEAT_METER); + // heat version 0x39 -> v 57, type 0x43 -> 67 + Variant _68TCH5767_4 = new Variant(0x39, 0x43, 0xA2, DeviceType.HEAT_METER); + // heat version 0x71 -> v 113, type 0x43 -> 67 + Variant _68TCH11367_4_A0 = new Variant(0x71, 0x43, 0xA0, DeviceType.HEAT_METER); + Variant _68TCH11367_4_A2 = new Variant(0x71, 0x43, 0xA2, DeviceType.HEAT_METER); + + // heat version 0x57 -> v 87, type 0x44 -> 68 + Variant _68TCH8768_4 = new Variant(0x57, 0x44, 0xA2, DeviceType.HEAT_METER); + + // TODO Isn't this a heat meter? + // hkv version 0x45 -> 69, type 0x43 -> 67 + Variant _68TCH6967_8 = new Variant(0x45, 0x43, 0xA1, DeviceType.HEAT_COST_ALLOCATOR); + + // hkv version 0x61 -> 97, type ? + Variant _68TCH97255_8 = new Variant(0x61, DeviceType.RESERVED.getId(), 0xA2, DeviceType.HEAT_COST_ALLOCATOR); + // hkv version 0x64 -> 100, type 0x80 -> 128 + Variant _68TCH100128_8 = new Variant(0x64, 0x80, 0xA0, DeviceType.HEAT_COST_ALLOCATOR); + // hkv version 0x69 -> 105, type 0x80 -> 128 + Variant _68TCH105128_8 = new Variant(0x69, 0x80, 0xA0, DeviceType.HEAT_COST_ALLOCATOR); + // hkv version 0x94 -> 148, type 0x80 -> 128 + Variant _68TCH148128_8 = new Variant(0x94, 0x80, 0xA2, DeviceType.HEAT_COST_ALLOCATOR); + + // smoke detector version 0x76 -> 118, type 0xf0 -> 240 + Variant _68TCH118255_161_A0 = new Variant(0x76, 0xf0, 0xA0, DeviceType.SMOKE_DETECTOR); + Variant _68TCH118255_161_A1 = new Variant(0x76, 0xf0, 0xA1, DeviceType.SMOKE_DETECTOR); + + // water meters + String THING_TYPE_NAME_TECHEM_WARM_WATER_METER = "techem_wz62"; + String THING_TYPE_NAME_TECHEM_COLD_WATER_METER = "techem_wz72"; + + // heat meter + String THING_TYPE_NAME_TECHEM_HEAT_METER = "techem_wmz43"; + + // techem heat cost allocators (Heizkostenverteiler) + String THING_TYPE_NAME_TECHEM_HKV45 = "techem_hkv45"; + String THING_TYPE_NAME_TECHEM_HKV61 = "techem_hkv61"; + String THING_TYPE_NAME_TECHEM_HKV64 = "techem_hkv64"; + String THING_TYPE_NAME_TECHEM_HKV69 = "techem_hkv69"; + String THING_TYPE_NAME_TECHEM_HKV94 = "techem_hkv94"; + + // techem smoke detector + String THING_TYPE_NAME_TECHEM_SD76 = "techem_sd76"; + + ThingTypeUID THING_TYPE_TECHEM_WARM_WATER_METER = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, + THING_TYPE_NAME_TECHEM_WARM_WATER_METER); + ThingTypeUID THING_TYPE_TECHEM_COLD_WATER_METER = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, + THING_TYPE_NAME_TECHEM_COLD_WATER_METER); + + ThingTypeUID THING_TYPE_TECHEM_HEAT_METER = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, + THING_TYPE_NAME_TECHEM_HEAT_METER); + + ThingTypeUID THING_TYPE_TECHEM_HKV45 = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, + THING_TYPE_NAME_TECHEM_HKV45); + ThingTypeUID THING_TYPE_TECHEM_HKV61 = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, + THING_TYPE_NAME_TECHEM_HKV61); + ThingTypeUID THING_TYPE_TECHEM_HKV64 = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, + THING_TYPE_NAME_TECHEM_HKV64); + ThingTypeUID THING_TYPE_TECHEM_HKV69 = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, + THING_TYPE_NAME_TECHEM_HKV69); + ThingTypeUID THING_TYPE_TECHEM_HKV94 = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, + THING_TYPE_NAME_TECHEM_HKV94); + + ThingTypeUID THING_TYPE_TECHEM_SD76 = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, + THING_TYPE_NAME_TECHEM_SD76); + + // TODO remove this part once all deployments are migrated + // old device type, remained here as alias for hkv64 + String THING_TYPE_NAME_TECHEM_HKV = "techem_hkv"; + ThingTypeUID THING_TYPE_TECHEM_HKV = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, THING_TYPE_NAME_TECHEM_HKV); + + Map SUPPORTED_DEVICE_VARIANTS = ImmutableMap. builder() + .put(_68TCH11298_6, THING_TYPE_TECHEM_WARM_WATER_METER) // WZ 112_62 + .put(_68TCH112114_16, THING_TYPE_TECHEM_COLD_WATER_METER) // WZ 112_72 + .put(_68TCH11698_6, THING_TYPE_TECHEM_WARM_WATER_METER) // WZ 116_62 + .put(_68TCH116114_16, THING_TYPE_TECHEM_COLD_WATER_METER) // WZ 116_72 + .put(_68TCH14998_6, THING_TYPE_TECHEM_WARM_WATER_METER) // WZ 149_62 + .put(_68TCH149114_16, THING_TYPE_TECHEM_COLD_WATER_METER) // WZ 149_72 + .put(_68TCH3467_4, THING_TYPE_TECHEM_HEAT_METER) // WMZ 34_43 + .put(_68TCH5767_4, THING_TYPE_TECHEM_HEAT_METER) // WMZ 57_43 + .put(_68TCH11367_4_A0, THING_TYPE_TECHEM_HEAT_METER) // WMZ 113_43 with A0 encoding + .put(_68TCH11367_4_A2, THING_TYPE_TECHEM_HEAT_METER) // WMZ 113_43 with A2 encoding + .put(_68TCH8768_4, THING_TYPE_TECHEM_HEAT_METER) // WMZ 87_44 with A2 encoding + .put(_68TCH6967_8, THING_TYPE_TECHEM_HKV45) // HKV 45 + .put(_68TCH97255_8, THING_TYPE_TECHEM_HKV61) // HKV 61 + .put(_68TCH100128_8, THING_TYPE_TECHEM_HKV64) // HKV 64 + .put(_68TCH105128_8, THING_TYPE_TECHEM_HKV69) // HKV 69 + .put(_68TCH148128_8, THING_TYPE_TECHEM_HKV94) // HKV 94 + .put(_68TCH118255_161_A0, THING_TYPE_TECHEM_SD76) // SD 76 with A0 encoding + .put(_68TCH118255_161_A1, THING_TYPE_TECHEM_SD76) // SD 76 with A1 encoding + .build(); + + Set SUPPORTED_DEVICE_TYPES = ImmutableSet + .copyOf(SUPPORTED_DEVICE_VARIANTS.keySet().stream().map(Variant::getRawType).collect(Collectors.toSet())); + + Set SUPPORTED_THING_TYPES = ImmutableSet.copyOf(SUPPORTED_DEVICE_VARIANTS.values()); + + // List all channels + // general channels + Map TECHEM_METER_MAPPING = ImmutableMap. builder() + .put(CHANNEL_CURRENTREADING, Type.CURRENT_VOLUME) // current value + .put(CHANNEL_CURRENTDATE, Type.CURRENT_READING_DATE) // present date + .put(CHANNEL_CURRENTDATE_STRING, Type.CURRENT_READING_DATE) // present date + .put(CHANNEL_CURRENTDATE_NUMBER, Type.CURRENT_READING_DATE) // present date + .put(CHANNEL_LASTREADING, Type.PAST_VOLUME) // last billing value + .put(CHANNEL_LASTDATE, Type.PAST_READING_DATE) // past billing date + .put(CHANNEL_LASTDATE_STRING, Type.PAST_READING_DATE) // past billing date + .put(CHANNEL_LASTDATE_NUMBER, Type.PAST_READING_DATE) // past billing date + .put(CHANNEL_RECEPTION, Type.RSSI) // Received Signal Strength Indicator + .put(CHANNEL_ALMANAC, Type.ALMANAC) // bi-weekly history + .build(); + + Map HEAT_ALLOCATOR_MAPPING_69 = ImmutableMap. builder() + .putAll(TECHEM_METER_MAPPING) // inherit main HKV channel map + .put(CHANNEL_ROOMTEMPERATURE, Type.ROOM_TEMPERATURE) // room + .put(CHANNEL_RADIATORTEMPERATURE, Type.RADIATOR_TEMPERATURE) // radiator + .build(); + + // measurement readings + Map SMOKE_DETECTOR_MAPPING = ImmutableMap. builder() + .put(CHANNEL_STATUS, Type.STATUS) // present date + .put(CHANNEL_CURRENTDATE, Type.CURRENT_READING_DATE) // present date + .put(CHANNEL_CURRENTDATE_STRING, Type.CURRENT_READING_DATE) // present date + .put(CHANNEL_CURRENTDATE_NUMBER, Type.CURRENT_READING_DATE) // present date + .put(CHANNEL_CURRENTDATE_SMOKE, Type.CURRENT_READING_DATE_SMOKE) // smoke detector 2nd date + .put(CHANNEL_CURRENTDATE_SMOKE_STRING, Type.CURRENT_READING_DATE_SMOKE) // smoke detector 2nd date + .put(CHANNEL_CURRENTDATE_SMOKE_NUMBER, Type.CURRENT_READING_DATE_SMOKE) // smoke detector 2nd date + .build(); + + // channel mapping for thing types + Map> RECORD_MAP = ImmutableMap.> builder() + .put(THING_TYPE_TECHEM_WARM_WATER_METER, TECHEM_METER_MAPPING) // warm + .put(THING_TYPE_TECHEM_COLD_WATER_METER, TECHEM_METER_MAPPING) // cold + + .put(THING_TYPE_TECHEM_HEAT_METER, TECHEM_METER_MAPPING) // heat meter have same set of channels as heat + // cost allocator + .put(THING_TYPE_TECHEM_HKV45, TECHEM_METER_MAPPING) // basic HKV mapping + .put(THING_TYPE_TECHEM_HKV61, TECHEM_METER_MAPPING) // basic HKV mapping + .put(THING_TYPE_TECHEM_HKV64, TECHEM_METER_MAPPING) // again basic HKV mapping + .put(THING_TYPE_TECHEM_HKV69, HEAT_ALLOCATOR_MAPPING_69) // here we have two temperature channels + .put(THING_TYPE_TECHEM_HKV94, HEAT_ALLOCATOR_MAPPING_69) // try to decode 0x94 variant in same way as 0x69 + .put(THING_TYPE_TECHEM_SD76, SMOKE_DETECTOR_MAPPING) // v118 is smoke detector, experimental channels apply + .build(); +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemDevice.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemDevice.java index 281d5a3..7ee6f6e 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemDevice.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemDevice.java @@ -1,60 +1,62 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ - -package org.openhab.binding.wmbus.device.techem; - -import java.util.List; -import java.util.Optional; - -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.device.techem.Record.Type; -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openmuc.jmbus.DeviceType; -import org.openmuc.jmbus.SecondaryAddress; -import org.openmuc.jmbus.wireless.WMBusMessage; - -/** - * The {@link TechemDevice} groups devices manufactured by Techem. - * - * @author Łukasz Dywicki - Initial contribution - */ -public class TechemDevice extends WMBusDevice { - - private final List> measurements; - private final Variant variant; - - protected TechemDevice(WMBusMessage originalMessage, WMBusAdapter adapter, Variant variant, - List> measurements) { - super(originalMessage, adapter); - this.variant = variant; - this.measurements = measurements; - } - - public final DeviceType getTechemDeviceType() { - return variant.getDesiredWMBusType(); - } - - public Variant getDeviceVariant() { - return variant; - } - - @Override - public String getDeviceType() { - return variant.getTechemType(); - } - - public List> getMeasurements() { - return measurements; - } - - public Optional> getRecord(Type type) { - return measurements.stream().filter(record -> record.getType().equals(type)).findFirst(); - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem; + +import java.util.List; +import java.util.Optional; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.device.techem.Record.Type; +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openmuc.jmbus.DeviceType; +import org.openmuc.jmbus.wireless.WMBusMessage; + +/** + * The {@link TechemDevice} groups devices manufactured by Techem. + * + * @author Łukasz Dywicki - Initial contribution + */ +public class TechemDevice extends WMBusDevice { + + private final List> measurements; + private final Variant variant; + + protected TechemDevice(WMBusMessage originalMessage, WMBusAdapter adapter, Variant variant, + List> measurements) { + super(originalMessage, adapter); + this.variant = variant; + this.measurements = measurements; + } + + public final DeviceType getTechemDeviceType() { + return variant.getDesiredWMBusType(); + } + + public Variant getDeviceVariant() { + return variant; + } + + @Override + public String getDeviceType() { + return variant.getTechemType(); + } + + public List> getMeasurements() { + return measurements; + } + + public Optional> getRecord(Type type) { + return measurements.stream().filter(record -> record.getType().equals(type)).findFirst(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemDiscoveryParticipant.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemDiscoveryParticipant.java index e7cc974..7ae6789 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemDiscoveryParticipant.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemDiscoveryParticipant.java @@ -1,149 +1,152 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.device.techem; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.smarthome.config.discovery.DiscoveryResult; -import org.eclipse.smarthome.config.discovery.DiscoveryResultBuilder; -import org.eclipse.smarthome.core.thing.Thing; -import org.eclipse.smarthome.core.thing.ThingTypeUID; -import org.eclipse.smarthome.core.thing.ThingUID; -import org.openhab.binding.wmbus.BindingConfiguration; -import org.openhab.binding.wmbus.WMBusBindingConstants; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.device.techem.decoder.TechemFrameDecoder; -import org.openhab.binding.wmbus.discovery.WMBusDiscoveryParticipant; -import org.openmuc.jmbus.SecondaryAddress; -import org.openmuc.jmbus.wireless.WMBusMessage; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Reference; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Discovers techem devices and decodes records which are broadcasted by it. - * - * @author Łukasz Dywicki - extraction of logic from compound discovery service. - */ -@Component -public class TechemDiscoveryParticipant implements WMBusDiscoveryParticipant { - - private final Logger logger = LoggerFactory.getLogger(TechemDiscoveryParticipant.class); - - private TechemFrameDecoder techemFrameDecoder; - - private BindingConfiguration configuration; - - @Override - public @NonNull Set getSupportedThingTypeUIDs() { - return TechemBindingConstants.SUPPORTED_THING_TYPES; - } - - @Override - public @Nullable ThingUID getThingUID(WMBusDevice device) { - if (configuration.getIncludeBridgeUID()) { - return decodeDevice(device).map(this::getThingType) - .map(type -> new ThingUID(type, device.getAdapter().getUID(), device.getDeviceId())).orElse(null); - } else { - return decodeDevice(device).map(this::getThingType).map(type -> new ThingUID(type, device.getDeviceId())) - .orElse(null); - } - } - - protected @Nullable ThingUID getThingUID(TechemDevice device) { - if (configuration.getIncludeBridgeUID()) { - return Optional.ofNullable(getThingType(device)) - .map(type -> new ThingUID(type, device.getAdapter().getUID(), device.getDeviceId())).orElse(null); - } else { - return Optional.ofNullable(getThingType(device)).map(type -> new ThingUID(type, device.getDeviceId())) - .orElse(null); - } - } - - @Override - public DiscoveryResult createResult(WMBusDevice device) { - Optional decodeDevice = decodeDevice(device); - ThingUID thingUID = decodeDevice.map(this::getThingUID).orElse(null); - - if (thingUID != null) { - String deviceTypeLabel = decodeDevice.map(TechemDevice::getTechemDeviceType) - .map(WMBusBindingConstants.DEVICE_TYPE_TRANSFORMATION).orElse("Unknown"); - Variant deviceTypeTag = decodeDevice.map(TechemDevice::getDeviceVariant).orElseThrow( - () -> new IllegalArgumentException("Unmapped techem device " + device.getDeviceType())); - - String label = "Techem " + deviceTypeLabel + " #" + device.getDeviceId() + " (" - + deviceTypeTag.getTechemType() + ")"; - - Map properties = new HashMap<>(); - properties.put(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS, device.getDeviceAddress()); - properties.put(Thing.PROPERTY_VENDOR, "Techem"); - properties.put(Thing.PROPERTY_SERIAL_NUMBER, device.getDeviceId()); - SecondaryAddress secondaryAddress = device.getOriginalMessage().getSecondaryAddress(); - properties.put(Thing.PROPERTY_MODEL_ID, secondaryAddress.getVersion()); - - // Create the discovery result and add to the inbox - return DiscoveryResultBuilder.create(thingUID).withProperties(properties) - .withRepresentationProperty(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS).withLabel(label) - .withThingType(TechemBindingConstants.SUPPORTED_DEVICE_VARIANTS.get(deviceTypeTag)) - .withBridge(device.getAdapter().getUID()).withLabel(label).withTTL(getTimeToLive()).build(); - } - - return null; - } - - private final Optional decodeDevice(WMBusDevice device) { - WMBusMessage message = device.getOriginalMessage(); - - if (!"TCH".equals(message.getSecondaryAddress().getManufacturerId())) { - return Optional.empty(); - } - - if (!TechemBindingConstants.SUPPORTED_DEVICE_TYPES.contains(device.getRawDeviceType())) { - logger.trace("Found unsupported Techem device {}, omitting it from discovery results.", - device.getDeviceType()); - return Optional.empty(); - } - - logger.trace("Attempt to decode received Techem telegram"); - return Optional.ofNullable(techemFrameDecoder.decode(device)); - } - - private @Nullable ThingTypeUID getThingType(TechemDevice device) { - return TechemBindingConstants.SUPPORTED_DEVICE_VARIANTS.get(device.getDeviceVariant()); - } - - @Reference - public void setTechemFrameDecoder(TechemFrameDecoder techemFrameDecoder) { - this.techemFrameDecoder = techemFrameDecoder; - } - - public void unsetTechemFrameDecoder(TechemFrameDecoder techemFrameDecoder) { - this.techemFrameDecoder = null; - } - - @Reference - public void setBindingConfiguration(BindingConfiguration configuration) { - this.configuration = configuration; - } - - public void unsetBindingConfiguration(BindingConfiguration configuration) { - this.configuration = null; - } - - private Long getTimeToLive() { - return configuration.getTimeToLive(); - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.wmbus.BindingConfiguration; +import org.openhab.binding.wmbus.WMBusBindingConstants; +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.device.techem.decoder.TechemFrameDecoder; +import org.openhab.binding.wmbus.discovery.WMBusDiscoveryParticipant; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.openmuc.jmbus.SecondaryAddress; +import org.openmuc.jmbus.wireless.WMBusMessage; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Discovers techem devices and decodes records which are broadcasted by it. + * + * @author Łukasz Dywicki - extraction of logic from compound discovery service. + */ +@Component +public class TechemDiscoveryParticipant implements WMBusDiscoveryParticipant { + + private final Logger logger = LoggerFactory.getLogger(TechemDiscoveryParticipant.class); + + private TechemFrameDecoder techemFrameDecoder; + + private BindingConfiguration configuration; + + @Override + public @NonNull Set getSupportedThingTypeUIDs() { + return TechemBindingConstants.SUPPORTED_THING_TYPES; + } + + @Override + public @Nullable ThingUID getThingUID(WMBusDevice device) { + if (configuration.getIncludeBridgeUID()) { + return decodeDevice(device).map(this::getThingType) + .map(type -> new ThingUID(type, device.getAdapter().getUID(), device.getDeviceId())).orElse(null); + } else { + return decodeDevice(device).map(this::getThingType).map(type -> new ThingUID(type, device.getDeviceId())) + .orElse(null); + } + } + + protected @Nullable ThingUID getThingUID(TechemDevice device) { + if (configuration.getIncludeBridgeUID()) { + return Optional.ofNullable(getThingType(device)) + .map(type -> new ThingUID(type, device.getAdapter().getUID(), device.getDeviceId())).orElse(null); + } else { + return Optional.ofNullable(getThingType(device)).map(type -> new ThingUID(type, device.getDeviceId())) + .orElse(null); + } + } + + @Override + public DiscoveryResult createResult(WMBusDevice device) { + Optional decodeDevice = decodeDevice(device); + ThingUID thingUID = decodeDevice.map(this::getThingUID).orElse(null); + + if (thingUID != null) { + String deviceTypeLabel = decodeDevice.map(TechemDevice::getTechemDeviceType) + .map(WMBusBindingConstants.DEVICE_TYPE_TRANSFORMATION).orElse("Unknown"); + Variant deviceTypeTag = decodeDevice.map(TechemDevice::getDeviceVariant).orElseThrow( + () -> new IllegalArgumentException("Unmapped techem device " + device.getDeviceType())); + + String label = "Techem " + deviceTypeLabel + " #" + device.getDeviceId() + " (" + + deviceTypeTag.getTechemType() + ")"; + + Map properties = new HashMap<>(); + properties.put(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS, device.getDeviceAddress()); + properties.put(Thing.PROPERTY_VENDOR, "Techem"); + properties.put(Thing.PROPERTY_SERIAL_NUMBER, device.getDeviceId()); + SecondaryAddress secondaryAddress = device.getOriginalMessage().getSecondaryAddress(); + properties.put(Thing.PROPERTY_MODEL_ID, secondaryAddress.getVersion()); + + // Create the discovery result and add to the inbox + return DiscoveryResultBuilder.create(thingUID).withProperties(properties) + .withRepresentationProperty(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS).withLabel(label) + .withThingType(TechemBindingConstants.SUPPORTED_DEVICE_VARIANTS.get(deviceTypeTag)) + .withBridge(device.getAdapter().getUID()).withLabel(label).withTTL(getTimeToLive()).build(); + } + + return null; + } + + private final Optional decodeDevice(WMBusDevice device) { + WMBusMessage message = device.getOriginalMessage(); + + if (!"TCH".equals(message.getSecondaryAddress().getManufacturerId())) { + return Optional.empty(); + } + + if (!TechemBindingConstants.SUPPORTED_DEVICE_TYPES.contains(device.getRawDeviceType())) { + logger.trace("Found unsupported Techem device {}, omitting it from discovery results.", + device.getDeviceType()); + return Optional.empty(); + } + + logger.trace("Attempt to decode received Techem telegram"); + return Optional.ofNullable(techemFrameDecoder.decode(device)); + } + + private @Nullable ThingTypeUID getThingType(TechemDevice device) { + return TechemBindingConstants.SUPPORTED_DEVICE_VARIANTS.get(device.getDeviceVariant()); + } + + @Reference + public void setTechemFrameDecoder(TechemFrameDecoder techemFrameDecoder) { + this.techemFrameDecoder = techemFrameDecoder; + } + + public void unsetTechemFrameDecoder(TechemFrameDecoder techemFrameDecoder) { + this.techemFrameDecoder = null; + } + + @Reference + public void setBindingConfiguration(BindingConfiguration configuration) { + this.configuration = configuration; + } + + public void unsetBindingConfiguration(BindingConfiguration configuration) { + this.configuration = null; + } + + private Long getTimeToLive() { + return configuration.getTimeToLive(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemHandlerFactory.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemHandlerFactory.java index b5e4246..f9d0c5a 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemHandlerFactory.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemHandlerFactory.java @@ -1,99 +1,102 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ - -package org.openhab.binding.wmbus.device.techem; - -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.openhab.binding.wmbus.device.techem.decoder.TechemFrameDecoder; -import org.openhab.binding.wmbus.device.techem.handler.TechemMeterHandler; -import org.osgi.service.component.ComponentContext; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Deactivate; -import org.osgi.service.component.annotations.Reference; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link TechemHandlerFactory} covers logic specific to TechemH devices. - * - * @author Łukasz Dywicki - Initial contribution - */ - -@Component(service = { TechemHandlerFactory.class, BaseThingHandlerFactory.class, ThingHandlerFactory.class }) -public class TechemHandlerFactory extends BaseThingHandlerFactory { - - // OpenHAB logger - private final Logger logger = LoggerFactory.getLogger(TechemHandlerFactory.class); - - private TechemFrameDecoder techemFrameDecoder; - - public TechemHandlerFactory() { - logger.debug("Techem handler factory starting up."); - } - - @Override - public boolean supportsThingType(ThingTypeUID thingTypeUID) { - return TechemBindingConstants.SUPPORTED_THING_TYPES.contains(thingTypeUID); - } - - @Override - protected ThingHandler createHandler(Thing thing) { - ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - - if (thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_HKV45) - || thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_HKV61) - || thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_HKV64) - || thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_HKV69) - || thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_HKV94)) { - logger.debug("Creating handler for TechemDevice device {}", thing.getUID().getId()); - return new TechemMeterHandler<>(thing, TechemHeatCostAllocator.class, techemFrameDecoder); - } else if (thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_WARM_WATER_METER) - || thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_COLD_WATER_METER)) { - logger.debug("Creating handler for TechemDevice device {}", thing.getUID().getId()); - return new TechemMeterHandler<>(thing, TechemWaterMeter.class, techemFrameDecoder); - } else if (thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_SD76)) { - logger.debug("Creating handler for Techem Smoke Detector device {}", thing.getUID().getId()); - return new TechemMeterHandler<>(thing, TechemSmokeDetector.class, techemFrameDecoder); - } else if (thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_HEAT_METER)) { - logger.debug("Creating handler for TechemDevice device {}", thing.getUID().getId()); - return new TechemMeterHandler<>(thing, TechemHeatMeter.class, techemFrameDecoder); - } - - logger.warn("Unsupported thing type {}. TechemHandlerFactory can not handle {}", thingTypeUID, thing.getUID()); - - return null; - } - - @Override - @Activate - protected void activate(ComponentContext componentContext) { - super.activate(componentContext); - } - - @Override - @Deactivate - protected void deactivate(ComponentContext componentContext) { - super.deactivate(componentContext); - } - - @Reference - public void setTechemFrameDecoder(TechemFrameDecoder decoder) { - this.techemFrameDecoder = decoder; - } - - public void unsetTechemFrameDecoder(TechemFrameDecoder decoder) { - this.techemFrameDecoder = null; - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem; + +import org.openhab.binding.wmbus.device.techem.decoder.TechemFrameDecoder; +import org.openhab.binding.wmbus.device.techem.handler.TechemMeterHandler; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.BaseThingHandlerFactory; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link TechemHandlerFactory} covers logic specific to TechemH devices. + * + * @author Łukasz Dywicki - Initial contribution + */ + +@Component(service = { TechemHandlerFactory.class, BaseThingHandlerFactory.class, ThingHandlerFactory.class }) +public class TechemHandlerFactory extends BaseThingHandlerFactory { + + // OpenHAB logger + private final Logger logger = LoggerFactory.getLogger(TechemHandlerFactory.class); + + private TechemFrameDecoder techemFrameDecoder; + + public TechemHandlerFactory() { + logger.debug("Techem handler factory starting up."); + } + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return TechemBindingConstants.SUPPORTED_THING_TYPES.contains(thingTypeUID); + } + + @Override + protected ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_HKV45) + || thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_HKV61) + || thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_HKV64) + || thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_HKV69) + || thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_HKV94)) { + logger.debug("Creating handler for TechemDevice device {}", thing.getUID().getId()); + return new TechemMeterHandler<>(thing, TechemHeatCostAllocator.class, techemFrameDecoder); + } else if (thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_WARM_WATER_METER) + || thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_COLD_WATER_METER)) { + logger.debug("Creating handler for TechemDevice device {}", thing.getUID().getId()); + return new TechemMeterHandler<>(thing, TechemWaterMeter.class, techemFrameDecoder); + } else if (thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_SD76)) { + logger.debug("Creating handler for Techem Smoke Detector device {}", thing.getUID().getId()); + return new TechemMeterHandler<>(thing, TechemSmokeDetector.class, techemFrameDecoder); + } else if (thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_HEAT_METER)) { + logger.debug("Creating handler for TechemDevice device {}", thing.getUID().getId()); + return new TechemMeterHandler<>(thing, TechemHeatMeter.class, techemFrameDecoder); + } + + logger.warn("Unsupported thing type {}. TechemHandlerFactory can not handle {}", thingTypeUID, thing.getUID()); + + return null; + } + + @Override + @Activate + protected void activate(ComponentContext componentContext) { + super.activate(componentContext); + } + + @Override + @Deactivate + protected void deactivate(ComponentContext componentContext) { + super.deactivate(componentContext); + } + + @Reference + public void setTechemFrameDecoder(TechemFrameDecoder decoder) { + this.techemFrameDecoder = decoder; + } + + public void unsetTechemFrameDecoder(TechemFrameDecoder decoder) { + this.techemFrameDecoder = null; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemHeatCostAllocator.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemHeatCostAllocator.java index 12ebbf9..4bd8210 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemHeatCostAllocator.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemHeatCostAllocator.java @@ -1,28 +1,32 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ - -package org.openhab.binding.wmbus.device.techem; - -import java.util.List; - -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openmuc.jmbus.wireless.WMBusMessage; - -/** - * The {@link TechemHeatCostAllocator} class covers heat cost allocator devices at very basic level. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public class TechemHeatCostAllocator extends TechemDevice { - - public TechemHeatCostAllocator(WMBusMessage originalMessage, WMBusAdapter adapter, Variant variant, List> measures) { - super(originalMessage, adapter, variant, measures); - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem; + +import java.util.List; + +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openmuc.jmbus.wireless.WMBusMessage; + +/** + * The {@link TechemHeatCostAllocator} class covers heat cost allocator devices at very basic level. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public class TechemHeatCostAllocator extends TechemDevice { + + public TechemHeatCostAllocator(WMBusMessage originalMessage, WMBusAdapter adapter, Variant variant, + List> measures) { + super(originalMessage, adapter, variant, measures); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemHeatMeter.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemHeatMeter.java index 4e7b73c..e1b413c 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemHeatMeter.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemHeatMeter.java @@ -1,29 +1,32 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ - -package org.openhab.binding.wmbus.device.techem; - -import java.util.List; - -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openmuc.jmbus.DeviceType; -import org.openmuc.jmbus.wireless.WMBusMessage; - -/** - * The {@link TechemHeatMeter} class covers heat meter devices at very basic level. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public class TechemHeatMeter extends TechemDevice { - - public TechemHeatMeter(WMBusMessage originalMessage, WMBusAdapter adapter, Variant variant, List> measures) { - super(originalMessage, adapter, variant, measures); - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem; + +import java.util.List; + +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openmuc.jmbus.wireless.WMBusMessage; + +/** + * The {@link TechemHeatMeter} class covers heat meter devices at very basic level. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public class TechemHeatMeter extends TechemDevice { + + public TechemHeatMeter(WMBusMessage originalMessage, WMBusAdapter adapter, Variant variant, + List> measures) { + super(originalMessage, adapter, variant, measures); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemSmokeDetector.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemSmokeDetector.java index ea23b0c..28f0632 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemSmokeDetector.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemSmokeDetector.java @@ -1,27 +1,32 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ - -package org.openhab.binding.wmbus.device.techem; - -import java.util.List; -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openmuc.jmbus.wireless.WMBusMessage; - -/** - * The {@link TechemSmokeDetector} class covers smoke detector devices at very basic level. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public class TechemSmokeDetector extends TechemDevice { - - public TechemSmokeDetector(WMBusMessage originalMessage, WMBusAdapter adapter, Variant variant, List> records) { - super(originalMessage, adapter, variant, records); - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem; + +import java.util.List; + +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openmuc.jmbus.wireless.WMBusMessage; + +/** + * The {@link TechemSmokeDetector} class covers smoke detector devices at very basic level. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public class TechemSmokeDetector extends TechemDevice { + + public TechemSmokeDetector(WMBusMessage originalMessage, WMBusAdapter adapter, Variant variant, + List> records) { + super(originalMessage, adapter, variant, records); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemUnknownDevice.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemUnknownDevice.java index 6a997de..c13dd18 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemUnknownDevice.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemUnknownDevice.java @@ -1,29 +1,31 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ - -package org.openhab.binding.wmbus.device.techem; - -import java.util.ArrayList; - -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openmuc.jmbus.DeviceType; -import org.openmuc.jmbus.wireless.WMBusMessage; - -/** - * The {@link TechemHeatCostAllocator} class covers heat cost allocator devices at very basic level. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public class TechemUnknownDevice extends TechemHeatCostAllocator { - - public TechemUnknownDevice(WMBusMessage originalMessage, WMBusAdapter adapter, Variant variant) { - super(originalMessage, adapter, variant, new ArrayList<>()); - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem; + +import java.util.ArrayList; + +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openmuc.jmbus.wireless.WMBusMessage; + +/** + * The {@link TechemHeatCostAllocator} class covers heat cost allocator devices at very basic level. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public class TechemUnknownDevice extends TechemHeatCostAllocator { + + public TechemUnknownDevice(WMBusMessage originalMessage, WMBusAdapter adapter, Variant variant) { + super(originalMessage, adapter, variant, new ArrayList<>()); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemWaterMeter.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemWaterMeter.java index e25ec3e..ca43756 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemWaterMeter.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemWaterMeter.java @@ -1,29 +1,33 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ - -package org.openhab.binding.wmbus.device.techem; - -import java.util.List; - -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openmuc.jmbus.wireless.WMBusMessage; - -/** - * The {@link TechemWaterMeter} class covers basic water measurement devices. - * - * @author Łukasz Dywicki - Initial contribution. - */ - -public class TechemWaterMeter extends TechemDevice { - - public TechemWaterMeter(WMBusMessage originalMessage, WMBusAdapter adapter, Variant variant, List> measures) { - super(originalMessage, adapter, variant, measures); - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem; + +import java.util.List; + +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openmuc.jmbus.wireless.WMBusMessage; + +/** + * The {@link TechemWaterMeter} class covers basic water measurement devices. + * + * @author Łukasz Dywicki - Initial contribution. + */ + +public class TechemWaterMeter extends TechemDevice { + + public TechemWaterMeter(WMBusMessage originalMessage, WMBusAdapter adapter, Variant variant, + List> measures) { + super(originalMessage, adapter, variant, measures); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/Variant.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/Variant.java index 9e3c1e5..059819c 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/Variant.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/Variant.java @@ -1,68 +1,69 @@ -package org.openhab.binding.wmbus.device.techem; - -import java.util.Objects; -import org.openmuc.jmbus.DeviceType; - -public class Variant { - - public final int version; - public final int reportedType; - public final int coding; - public final DeviceType desiredType; - public final int matchingType; // since all unknown device types are converted to RESERVED/255 by jMBus - - public Variant(int version, int reportedType, int coding, DeviceType desiredType) { - this(version, reportedType, coding, desiredType, DeviceType.getInstance(reportedType)); - } - - public Variant(int version, int reportedType, int coding, DeviceType desiredType, DeviceType matchingType) { - this.version = version; - this.reportedType = reportedType; - this.coding = coding; - this.desiredType = desiredType; - this.matchingType = matchingType.getId(); - } - - public String getRawType() { - return "68" + TechemBindingConstants.MANUFACTURER_ID + version + "" + reportedType; - } - - public int getDesiredType() { - return desiredType.getId(); - } - - public DeviceType getDesiredWMBusType() { - return desiredType; - } - - public int getCoding() { - return coding; - } - - public String getTechemType() { - return getRawType() + desiredType; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof Variant)) { - return false; - } - Variant variant = (Variant) o; - return version == variant.version && reportedType == variant.reportedType && coding == variant - .coding && desiredType == variant.desiredType && matchingType == variant.matchingType; - } - - @Override - public int hashCode() { - return Objects.hash(version, reportedType, coding, desiredType, matchingType); - } - - @Override - public String toString() { - return getRawType() + "->" + getTechemType(); - } -} \ No newline at end of file +package org.openhab.binding.wmbus.device.techem; + +import java.util.Objects; + +import org.openmuc.jmbus.DeviceType; + +public class Variant { + + public final int version; + public final int reportedType; + public final int coding; + public final DeviceType desiredType; + public final int matchingType; // since all unknown device types are converted to RESERVED/255 by jMBus + + public Variant(int version, int reportedType, int coding, DeviceType desiredType) { + this(version, reportedType, coding, desiredType, DeviceType.getInstance(reportedType)); + } + + public Variant(int version, int reportedType, int coding, DeviceType desiredType, DeviceType matchingType) { + this.version = version; + this.reportedType = reportedType; + this.coding = coding; + this.desiredType = desiredType; + this.matchingType = matchingType.getId(); + } + + public String getRawType() { + return "68" + TechemBindingConstants.MANUFACTURER_ID + version + "" + reportedType; + } + + public int getDesiredType() { + return desiredType.getId(); + } + + public DeviceType getDesiredWMBusType() { + return desiredType; + } + + public int getCoding() { + return coding; + } + + public String getTechemType() { + return getRawType() + desiredType; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Variant)) { + return false; + } + Variant variant = (Variant) o; + return version == variant.version && reportedType == variant.reportedType && coding == variant.coding + && desiredType == variant.desiredType && matchingType == variant.matchingType; + } + + @Override + public int hashCode() { + return Objects.hash(version, reportedType, coding, desiredType, matchingType); + } + + @Override + public String toString() { + return getRawType() + "->" + getTechemType(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/AbstractTechemFrameDecoder.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/AbstractTechemFrameDecoder.java index cd93030..a873825 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/AbstractTechemFrameDecoder.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/AbstractTechemFrameDecoder.java @@ -1,114 +1,117 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.device.techem.decoder; - -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; -import java.util.function.Function; - -import org.eclipse.smarthome.core.util.HexUtils; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.device.techem.TechemDevice; -import org.openhab.binding.wmbus.device.techem.Variant; -import org.openmuc.jmbus.SecondaryAddress; -import org.openmuc.jmbus.wireless.WMBusMessage; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -abstract class AbstractTechemFrameDecoder implements TechemFrameDecoder { - - private final Logger logger = LoggerFactory.getLogger(AbstractTechemFrameDecoder.class); - protected final Variant variant; - - protected final Function _SCALE_FACTOR_1_10th = value -> value / 10; - protected final Function _SCALE_FACTOR_1_100th = value -> value / 100; - - protected AbstractTechemFrameDecoder(Variant variant) { - this.variant = variant; - } - - @Override - public final boolean supports(String deviceVariant) { - boolean supports = variant.getRawType().equals(deviceVariant); - logger.debug("Does decoder {} support meter variant {}? {}", variant, deviceVariant, supports); - return supports; - } - - protected final byte[] read(byte[] buffer, int... indexes) { - byte[] value = new byte[indexes.length]; - for (int element = 0; element < indexes.length; element++) { - value[element] = buffer[indexes[element]]; - } - return value; - } - - protected final int parseBigEndianInt(byte[] buffer, int index) { - if (buffer.length < index + 1) { - return 0x00; - } - return parseBigEndianInt(buffer, index, index + 1); - } - - protected final int parseBigEndianInt(byte[] buffer, int lsb, int msb) { - byte[] value = read(buffer, lsb, msb); - return (value[0] & 0xFF) + ((value[1] & 0xFF) << 8); - } - - protected final float parseValue(byte[] buffer, int index, Function scale) { - float value = parseBigEndianInt(buffer, index); - - return scale.apply(value); - } - - protected final LocalDateTime parseLastDate(byte[] buffer, int index) { - int dateint = parseBigEndianInt(buffer, index); - - int day = (dateint >> 0) & 0x1F; - int month = (dateint >> 5) & 0x0F; - int year = (dateint >> 9) & 0x3F; - - LocalDateTime dateTime = LocalDateTime.of(2000 + year, month, day, 0, 0, 0, 0); - return dateTime.truncatedTo(ChronoUnit.DAYS); - } - - protected final LocalDateTime parseCurrentDate(byte[] buffer, int index) { - int dateint = parseBigEndianInt(buffer, index); - - int day = (dateint >> 4) & 0x1F; - int month = (dateint >> 9) & 0x0F; - - if (day <= 0) { - logger.trace("Detected invalid day number {} in byte representation: {}, changing to 1st day of month", day, - HexUtils.bytesToHex(read(buffer, index, index + 1))); - day = 1; - } - if (month <= 0) { - logger.trace("Detected invalid month number {} in byte representation: {}, changing to last month of year", - month, HexUtils.bytesToHex(read(buffer, index, index + 1))); - month = 12; - } - - LocalDateTime dateTime = LocalDateTime.now().withMonth(month).withDayOfMonth(day); - return dateTime.truncatedTo(ChronoUnit.SECONDS); - } - - protected final float parseTemperature(byte[] buffer, int index) { - return parseValue(buffer, index, _SCALE_FACTOR_1_100th); - } - - @Override - public T decode(WMBusDevice device) { - WMBusMessage message = device.getOriginalMessage(); - return decode(device, message.getSecondaryAddress(), message.asBlob()); - } - - // FIXME make this method abstract - protected abstract T decode(WMBusDevice device, SecondaryAddress address, byte[] buffer); - -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.function.Function; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.device.techem.TechemDevice; +import org.openhab.binding.wmbus.device.techem.Variant; +import org.openhab.core.util.HexUtils; +import org.openmuc.jmbus.SecondaryAddress; +import org.openmuc.jmbus.wireless.WMBusMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +abstract class AbstractTechemFrameDecoder implements TechemFrameDecoder { + + private final Logger logger = LoggerFactory.getLogger(AbstractTechemFrameDecoder.class); + protected final Variant variant; + + protected final Function _SCALE_FACTOR_1_10th = value -> value / 10; + protected final Function _SCALE_FACTOR_1_100th = value -> value / 100; + + protected AbstractTechemFrameDecoder(Variant variant) { + this.variant = variant; + } + + @Override + public final boolean supports(String deviceVariant) { + boolean supports = variant.getRawType().equals(deviceVariant); + logger.debug("Does decoder {} support meter variant {}? {}", variant, deviceVariant, supports); + return supports; + } + + protected final byte[] read(byte[] buffer, int... indexes) { + byte[] value = new byte[indexes.length]; + for (int element = 0; element < indexes.length; element++) { + value[element] = buffer[indexes[element]]; + } + return value; + } + + protected final int parseBigEndianInt(byte[] buffer, int index) { + if (buffer.length < index + 1) { + return 0x00; + } + return parseBigEndianInt(buffer, index, index + 1); + } + + protected final int parseBigEndianInt(byte[] buffer, int lsb, int msb) { + byte[] value = read(buffer, lsb, msb); + return (value[0] & 0xFF) + ((value[1] & 0xFF) << 8); + } + + protected final float parseValue(byte[] buffer, int index, Function scale) { + float value = parseBigEndianInt(buffer, index); + + return scale.apply(value); + } + + protected final LocalDateTime parseLastDate(byte[] buffer, int index) { + int dateint = parseBigEndianInt(buffer, index); + + int day = (dateint >> 0) & 0x1F; + int month = (dateint >> 5) & 0x0F; + int year = (dateint >> 9) & 0x3F; + + LocalDateTime dateTime = LocalDateTime.of(2000 + year, month, day, 0, 0, 0, 0); + return dateTime.truncatedTo(ChronoUnit.DAYS); + } + + protected final LocalDateTime parseCurrentDate(byte[] buffer, int index) { + int dateint = parseBigEndianInt(buffer, index); + + int day = (dateint >> 4) & 0x1F; + int month = (dateint >> 9) & 0x0F; + + if (day <= 0) { + logger.trace("Detected invalid day number {} in byte representation: {}, changing to 1st day of month", day, + HexUtils.bytesToHex(read(buffer, index, index + 1))); + day = 1; + } + if (month <= 0) { + logger.trace("Detected invalid month number {} in byte representation: {}, changing to last month of year", + month, HexUtils.bytesToHex(read(buffer, index, index + 1))); + month = 12; + } + + LocalDateTime dateTime = LocalDateTime.now().withMonth(month).withDayOfMonth(day); + return dateTime.truncatedTo(ChronoUnit.SECONDS); + } + + protected final float parseTemperature(byte[] buffer, int index) { + return parseValue(buffer, index, _SCALE_FACTOR_1_100th); + } + + @Override + public T decode(WMBusDevice device) { + WMBusMessage message = device.getOriginalMessage(); + return decode(device, message.getSecondaryAddress(), message.asBlob()); + } + + // FIXME make this method abstract + protected abstract T decode(WMBusDevice device, SecondaryAddress address, byte[] buffer); +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/Buffer.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/Buffer.java index 8c058dc..d529a65 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/Buffer.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/Buffer.java @@ -1,165 +1,168 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.device.techem.decoder; - -import java.nio.BufferOverflowException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; -import java.util.function.Function; -import org.eclipse.smarthome.core.util.HexUtils; -import org.openmuc.jmbus.SecondaryAddress; -import org.openmuc.jmbus.wireless.WMBusMessage; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Very basic wrapper around WMBus Message which turns it into readable sequence of bytes allowing to navigate over - * data part with automatic adjustment of reader index. - * - * @author Łukasz Dywicki - initial contribution. - */ -public class Buffer { - - protected final Logger logger = LoggerFactory.getLogger(Buffer.class); - protected final ByteBuffer buffer; - - public final static Function _SCALE_FACTOR_1_10th = value -> value / 10; - public final static Function _SCALE_FACTOR_1_100th = value -> value / 100; - - public Buffer(WMBusMessage message) { - this(message.asBlob(), message.getSecondaryAddress()); - } - - public Buffer(WMBusMessage message, SecondaryAddress secondaryAddress) { - this(message.asBlob(), secondaryAddress); - } - - public Buffer(byte[] buffer, SecondaryAddress secondaryAddress) { - this(buffer, secondaryAddress.asByteArray().length); - } - - public Buffer(byte[] buffer) { - this(buffer, 0); - } - - public Buffer(byte[] buffer, int offset) { - this.buffer = ByteBuffer.wrap(buffer); - skip(offset); - } - - public Buffer skip(int bytes) { - int position = buffer.position() + bytes; - if (position > buffer.limit()) { - throw new BufferOverflowException(); - } - - this.buffer.position(position); - return this; - } - - public byte readByte() { - return buffer.get(); - } - - public byte[] readBytes(int len) { - byte[] bytes = new byte[len]; - for (int index = 0; index < len; index++) { - bytes[index] = readByte(); - } - return bytes; - } - - public int readInt() { - int number = orderedRead(ByteOrder.LITTLE_ENDIAN, ByteBuffer::getInt); - return number >> 8; - } - - public short readShort() { - return orderedRead(ByteOrder.LITTLE_ENDIAN, ByteBuffer::getShort); - } - - public float readFloat() { - return readShort(); - } - - public LocalDateTime readPastDate() { - int dateint = readShort(); - - int day = dateint & 0x1F; - int month = (dateint >> 5) & 0x0F; - int year = (dateint >> 9) & 0x3F; - - return LocalDate.of(2000 + year, month, day).atStartOfDay(); - } - - public LocalDateTime readCurrentDate() { - int position = buffer.position(); - int dateint = readShort(); - - int day = (dateint >> 4) & 0x1F; - int month = (dateint >> 9) & 0x0F; - - if (day <= 0) { - logger.trace("Detected invalid day number {} in byte representation: {}, changing to 1st day of month", day, - String.format("%02X", buffer.get(position))); - day = 1; - } - if (month <= 0) { - logger.trace("Detected invalid month number {} in byte representation: {}, changing to last month of year", - month, String.format("%02X", buffer.get(position))); - month = 12; - } - - LocalDateTime dateTime = LocalDateTime.now().withMonth(month).withDayOfMonth(day); - return dateTime.truncatedTo(ChronoUnit.SECONDS); - } - - public String readHistory() { - StringBuilder history = new StringBuilder(); - while (buffer.position() + 1 < buffer.limit()) { - history.append((readByte() & 0xFF)).append(";"); - } - - return history.substring(0, history.length() -1); - } - - public float readFloat(Function scale) { - return scale.apply(readFloat()); - } - - public int position() { - return buffer.position(); - } - - public int limit() { - return buffer.limit(); - } - - public int available() { - return limit() - position(); - } - - private final T orderedRead(ByteOrder readOrder, Function callback) { - ByteOrder byteOrder = buffer.order(); - if (!readOrder.equals(byteOrder)) { - try { - buffer.order(readOrder); - return callback.apply(buffer); - } finally { - buffer.order(byteOrder); - } - } - - return callback.apply(buffer); - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; + +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.function.Function; + +import org.openmuc.jmbus.SecondaryAddress; +import org.openmuc.jmbus.wireless.WMBusMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Very basic wrapper around WMBus Message which turns it into readable sequence of bytes allowing to navigate over + * data part with automatic adjustment of reader index. + * + * @author Łukasz Dywicki - initial contribution. + */ +public class Buffer { + + protected final Logger logger = LoggerFactory.getLogger(Buffer.class); + protected final ByteBuffer buffer; + + public final static Function _SCALE_FACTOR_1_10th = value -> value / 10; + public final static Function _SCALE_FACTOR_1_100th = value -> value / 100; + + public Buffer(WMBusMessage message) { + this(message.asBlob(), message.getSecondaryAddress()); + } + + public Buffer(WMBusMessage message, SecondaryAddress secondaryAddress) { + this(message.asBlob(), secondaryAddress); + } + + public Buffer(byte[] buffer, SecondaryAddress secondaryAddress) { + this(buffer, secondaryAddress.asByteArray().length); + } + + public Buffer(byte[] buffer) { + this(buffer, 0); + } + + public Buffer(byte[] buffer, int offset) { + this.buffer = ByteBuffer.wrap(buffer); + skip(offset); + } + + public Buffer skip(int bytes) { + int position = buffer.position() + bytes; + if (position > buffer.limit()) { + throw new BufferOverflowException(); + } + + this.buffer.position(position); + return this; + } + + public byte readByte() { + return buffer.get(); + } + + public byte[] readBytes(int len) { + byte[] bytes = new byte[len]; + for (int index = 0; index < len; index++) { + bytes[index] = readByte(); + } + return bytes; + } + + public int readInt() { + int number = orderedRead(ByteOrder.LITTLE_ENDIAN, ByteBuffer::getInt); + return number >> 8; + } + + public short readShort() { + return orderedRead(ByteOrder.LITTLE_ENDIAN, ByteBuffer::getShort); + } + + public float readFloat() { + return readShort(); + } + + public LocalDateTime readPastDate() { + int dateint = readShort(); + + int day = dateint & 0x1F; + int month = (dateint >> 5) & 0x0F; + int year = (dateint >> 9) & 0x3F; + + return LocalDate.of(2000 + year, month, day).atStartOfDay(); + } + + public LocalDateTime readCurrentDate() { + int position = buffer.position(); + int dateint = readShort(); + + int day = (dateint >> 4) & 0x1F; + int month = (dateint >> 9) & 0x0F; + + if (day <= 0) { + logger.trace("Detected invalid day number {} in byte representation: {}, changing to 1st day of month", day, + String.format("%02X", buffer.get(position))); + day = 1; + } + if (month <= 0) { + logger.trace("Detected invalid month number {} in byte representation: {}, changing to last month of year", + month, String.format("%02X", buffer.get(position))); + month = 12; + } + + LocalDateTime dateTime = LocalDateTime.now().withMonth(month).withDayOfMonth(day); + return dateTime.truncatedTo(ChronoUnit.SECONDS); + } + + public String readHistory() { + StringBuilder history = new StringBuilder(); + while (buffer.position() + 1 < buffer.limit()) { + history.append((readByte() & 0xFF)).append(";"); + } + + return history.substring(0, history.length() - 1); + } + + public float readFloat(Function scale) { + return scale.apply(readFloat()); + } + + public int position() { + return buffer.position(); + } + + public int limit() { + return buffer.limit(); + } + + public int available() { + return limit() - position(); + } + + private final T orderedRead(ByteOrder readOrder, Function callback) { + ByteOrder byteOrder = buffer.order(); + if (!readOrder.equals(byteOrder)) { + try { + buffer.order(readOrder); + return callback.apply(buffer); + } finally { + buffer.order(byteOrder); + } + } + + return callback.apply(buffer); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/CompositeTechemFrameDecoder.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/CompositeTechemFrameDecoder.java index e973c9e..49061e3 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/CompositeTechemFrameDecoder.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/CompositeTechemFrameDecoder.java @@ -1,78 +1,82 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.device.techem.decoder; - -import java.util.List; - -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.device.techem.TechemBindingConstants; -import org.openhab.binding.wmbus.device.techem.TechemDevice; -import org.osgi.service.component.annotations.Component; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.collect.ImmutableList; - -@Component -public class CompositeTechemFrameDecoder implements TechemFrameDecoder { - - private final Logger logger = LoggerFactory.getLogger(CompositeTechemFrameDecoder.class); - - private final List> decoders = ImmutableList.of(new TechemVersionFrameDecoderSelector(), - // warm water - new TechemWaterMeterFrameDecoder(TechemBindingConstants._68TCH11298_6, 1), - new TechemWaterMeterFrameDecoder(TechemBindingConstants._68TCH11698_6, 1), - new TechemWaterMeterFrameDecoder(TechemBindingConstants._68TCH14998_6, -1), - // cold water - new TechemWaterMeterFrameDecoder(TechemBindingConstants._68TCH112114_16, -1), - new TechemWaterMeterFrameDecoder(TechemBindingConstants._68TCH116114_16, -1), - new TechemWaterMeterFrameDecoder(TechemBindingConstants._68TCH149114_16, -1)); - - @Override - public boolean supports(String deviceVariant) { - for (TechemFrameDecoder decoder : decoders) { - if (decoder.supports(deviceVariant)) { - return true; - } - } - - return false; - } - - @Override - public TechemDevice decode(WMBusDevice device) { - if (device instanceof TechemDevice) { - return (TechemDevice) device; - } - TechemDevice result = null; - // TODO failing test: wrong water meter returned? - for (TechemFrameDecoder decoder : decoders) { - if (decoder.supports(device.getRawDeviceType())) { - // same variant might be supported by multiple decoders, but first one which gives decoded result wins - logger.debug("Found decoder capable of handling device {}: {}", device.getRawDeviceType(), decoder.getClass().getName()); - result = decoder.decode(device); - if (result != null) { - logger.debug("Decoding result: {}, {}, {}", result, result.getRawDeviceType(), - result.getTechemDeviceType()); - break; - } else { - logger.debug("Decoding of frame failed, unsupported device variant"); - } - } - } - if (result != null) { - return result; - } - - logger.debug("Could not find decoder capable of handling device {}", device.getRawDeviceType()); - - return null; - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; + +import java.util.List; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.device.techem.TechemBindingConstants; +import org.openhab.binding.wmbus.device.techem.TechemDevice; +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableList; + +@Component +public class CompositeTechemFrameDecoder implements TechemFrameDecoder { + + private final Logger logger = LoggerFactory.getLogger(CompositeTechemFrameDecoder.class); + + private final List> decoders = ImmutableList.of(new TechemVersionFrameDecoderSelector(), + // warm water + new TechemWaterMeterFrameDecoder(TechemBindingConstants._68TCH11298_6, 1), + new TechemWaterMeterFrameDecoder(TechemBindingConstants._68TCH11698_6, 1), + new TechemWaterMeterFrameDecoder(TechemBindingConstants._68TCH14998_6, -1), + // cold water + new TechemWaterMeterFrameDecoder(TechemBindingConstants._68TCH112114_16, -1), + new TechemWaterMeterFrameDecoder(TechemBindingConstants._68TCH116114_16, -1), + new TechemWaterMeterFrameDecoder(TechemBindingConstants._68TCH149114_16, -1)); + + @Override + public boolean supports(String deviceVariant) { + for (TechemFrameDecoder decoder : decoders) { + if (decoder.supports(deviceVariant)) { + return true; + } + } + + return false; + } + + @Override + public TechemDevice decode(WMBusDevice device) { + if (device instanceof TechemDevice) { + return (TechemDevice) device; + } + TechemDevice result = null; + // TODO failing test: wrong water meter returned? + for (TechemFrameDecoder decoder : decoders) { + if (decoder.supports(device.getRawDeviceType())) { + // same variant might be supported by multiple decoders, but first one which gives decoded result wins + logger.debug("Found decoder capable of handling device {}: {}", device.getRawDeviceType(), + decoder.getClass().getName()); + result = decoder.decode(device); + if (result != null) { + logger.debug("Decoding result: {}, {}, {}", result, result.getRawDeviceType(), + result.getTechemDeviceType()); + break; + } else { + logger.debug("Decoding of frame failed, unsupported device variant"); + } + } + } + if (result != null) { + return result; + } + + logger.debug("Could not find decoder capable of handling device {}", device.getRawDeviceType()); + + return null; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/DebugBuffer.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/DebugBuffer.java index 87a5bc0..cf7bfc7 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/DebugBuffer.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/DebugBuffer.java @@ -1,110 +1,114 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.device.techem.decoder; - -import java.time.LocalDateTime; -import java.util.function.Function; -import org.openmuc.jmbus.SecondaryAddress; -import org.openmuc.jmbus.wireless.WMBusMessage; - -/** - * Buffer implementation which dumps contents of frame during processing so it is easier to spot how payload is utilized - * by various parsers. - * - * @author Łukasz Dywicki - initial contribution. - */ -public class DebugBuffer extends Buffer { - - public DebugBuffer(WMBusMessage message) { - this(message.asBlob(), message.getSecondaryAddress()); - } - - public DebugBuffer(WMBusMessage message, SecondaryAddress secondaryAddress) { - this(message.asBlob(), secondaryAddress); - } - - public DebugBuffer(byte[] buffer, SecondaryAddress secondaryAddress) { - this(buffer, secondaryAddress.asByteArray().length); - } - - public DebugBuffer(byte[] buffer) { - this(buffer, 0); - } - - public DebugBuffer(byte[] buffer, int offset) { - super(buffer, offset); - logger.info("Skipped first {} bytes {}", offset, dump()); - } - - public DebugBuffer skip(int bytes) { - logger.info("Attempting to move reader index from {} by {} byte(s) {}", position(), bytes, dump()); - super.skip(bytes); - return this; - } - - public byte readByte() { - logger.info("Read byte {}", dump()); - return super.readByte(); - } - - public int readInt() { - logger.info("Read 3 byte integer from {}", dump()); - return super.readInt(); - } - - public short readShort() { - logger.info("Read 2 byte (short) from {}", dump()); - return super.readShort(); - } - - public float readFloat() { - logger.info("Read 2 byte (float) from {}", dump()); - return super.readFloat(); - } - - public LocalDateTime readPastDate() { - logger.info("Read 2 bytes (short) and turn int into data {}", dump()); - return super.readPastDate(); - } - - public LocalDateTime readCurrentDate() { - logger.info("Read 2 bytes (short) and turn int into data {}", dump()); - return super.readCurrentDate(); - } - - public float readFloat(Function scale) { - logger.info("Read 2 bytes (short) and turn int into float and rescale {}", dump()); - return super.readFloat(scale); - } - - private final String dump() { - byte[] array = buffer.array(); - - String output = "\nBuffer size: " + array.length + ", read index: " + buffer.position() + "\n"; - String middleLine = ""; - String lowerLine = ""; - for (int index = 0; index < array.length; index++) { - output += String.format("%02X", array[index]) + " "; - if (index < 10 && index != 0) { - lowerLine += " "; - } else { - lowerLine += " "; - } - lowerLine += index; - if (index == buffer.position()) { - middleLine += " ^ "; - } else { - middleLine += " "; - } - } - - return output + "\n" + lowerLine + "\n" + middleLine; - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; + +import java.time.LocalDateTime; +import java.util.function.Function; + +import org.openmuc.jmbus.SecondaryAddress; +import org.openmuc.jmbus.wireless.WMBusMessage; + +/** + * Buffer implementation which dumps contents of frame during processing so it is easier to spot how payload is utilized + * by various parsers. + * + * @author Łukasz Dywicki - initial contribution. + */ +public class DebugBuffer extends Buffer { + + public DebugBuffer(WMBusMessage message) { + this(message.asBlob(), message.getSecondaryAddress()); + } + + public DebugBuffer(WMBusMessage message, SecondaryAddress secondaryAddress) { + this(message.asBlob(), secondaryAddress); + } + + public DebugBuffer(byte[] buffer, SecondaryAddress secondaryAddress) { + this(buffer, secondaryAddress.asByteArray().length); + } + + public DebugBuffer(byte[] buffer) { + this(buffer, 0); + } + + public DebugBuffer(byte[] buffer, int offset) { + super(buffer, offset); + logger.info("Skipped first {} bytes {}", offset, dump()); + } + + public DebugBuffer skip(int bytes) { + logger.info("Attempting to move reader index from {} by {} byte(s) {}", position(), bytes, dump()); + super.skip(bytes); + return this; + } + + public byte readByte() { + logger.info("Read byte {}", dump()); + return super.readByte(); + } + + public int readInt() { + logger.info("Read 3 byte integer from {}", dump()); + return super.readInt(); + } + + public short readShort() { + logger.info("Read 2 byte (short) from {}", dump()); + return super.readShort(); + } + + public float readFloat() { + logger.info("Read 2 byte (float) from {}", dump()); + return super.readFloat(); + } + + public LocalDateTime readPastDate() { + logger.info("Read 2 bytes (short) and turn int into data {}", dump()); + return super.readPastDate(); + } + + public LocalDateTime readCurrentDate() { + logger.info("Read 2 bytes (short) and turn int into data {}", dump()); + return super.readCurrentDate(); + } + + public float readFloat(Function scale) { + logger.info("Read 2 bytes (short) and turn int into float and rescale {}", dump()); + return super.readFloat(scale); + } + + private final String dump() { + byte[] array = buffer.array(); + + String output = "\nBuffer size: " + array.length + ", read index: " + buffer.position() + "\n"; + String middleLine = ""; + String lowerLine = ""; + for (int index = 0; index < array.length; index++) { + output += String.format("%02X", array[index]) + " "; + if (index < 10 && index != 0) { + lowerLine += " "; + } else { + lowerLine += " "; + } + lowerLine += index; + if (index == buffer.position()) { + middleLine += " ^ "; + } else { + middleLine += " "; + } + } + + return output + "\n" + lowerLine + "\n" + middleLine; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemFrameDecoder.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemFrameDecoder.java index 3c2ae19..99a76d0 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemFrameDecoder.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemFrameDecoder.java @@ -1,20 +1,23 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.device.techem.decoder; - -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.device.techem.TechemDevice; - -public interface TechemFrameDecoder { - - boolean supports(String deviceVariant); - - T decode(WMBusDevice device); - -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.device.techem.TechemDevice; + +public interface TechemFrameDecoder { + + boolean supports(String deviceVariant); + + T decode(WMBusDevice device); +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemHKVFrameDecoder.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemHKVFrameDecoder.java index 4233dcb..5bbf69f 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemHKVFrameDecoder.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemHKVFrameDecoder.java @@ -1,61 +1,65 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.device.techem.decoder; - -import java.util.ArrayList; -import java.util.List; - -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.device.techem.Record; -import org.openhab.binding.wmbus.device.techem.TechemHeatCostAllocator; -import org.openhab.binding.wmbus.device.techem.TechemUnknownDevice; -import org.openhab.binding.wmbus.device.techem.Variant; -import org.openmuc.jmbus.DeviceType; -import org.openmuc.jmbus.SecondaryAddress; - -class TechemHKVFrameDecoder extends AbstractTechemFrameDecoder { - - protected final boolean reportsTemperature; - - TechemHKVFrameDecoder(Variant variant) { - this(variant, false); - } - - TechemHKVFrameDecoder(Variant variant, boolean temperature) { - super(variant); - this.reportsTemperature = temperature; - } - - @Override - protected TechemHeatCostAllocator decode(WMBusDevice device, SecondaryAddress address, byte[] buffer) { - Buffer buff = new Buffer(device.getOriginalMessage(), address); - - int coding = buff.skip(2).readByte() & 0xFF; - if (variant.getCoding() == coding) { - - List> records = new ArrayList<>(); - records.add(new Record<>(Record.Type.STATUS, ((Byte) buff.readByte()).intValue())); - records.add(new Record<>(Record.Type.PAST_READING_DATE, buff.readPastDate())); - records.add(new Record<>(Record.Type.PAST_VOLUME, (float) buff.readShort())); - records.add(new Record<>(Record.Type.CURRENT_READING_DATE, buff.readCurrentDate())); - records.add(new Record<>(Record.Type.CURRENT_VOLUME, (float) buff.readShort())); - records.add(new Record<>(Record.Type.RSSI, device.getOriginalMessage().getRssi())); - records.add(new Record<>(Record.Type.ALMANAC, "")); - - return new TechemHeatCostAllocator(device.getOriginalMessage(), device.getAdapter(), variant, records); - } - - if (coding == 0xA3) { - return new TechemUnknownDevice(device.getOriginalMessage(), device.getAdapter(), new Variant(variant.version, variant.reportedType, coding, DeviceType.UNKNOWN)); - } - - return null; - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; + +import java.util.ArrayList; +import java.util.List; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.device.techem.Record; +import org.openhab.binding.wmbus.device.techem.TechemHeatCostAllocator; +import org.openhab.binding.wmbus.device.techem.TechemUnknownDevice; +import org.openhab.binding.wmbus.device.techem.Variant; +import org.openmuc.jmbus.DeviceType; +import org.openmuc.jmbus.SecondaryAddress; + +class TechemHKVFrameDecoder extends AbstractTechemFrameDecoder { + + protected final boolean reportsTemperature; + + TechemHKVFrameDecoder(Variant variant) { + this(variant, false); + } + + TechemHKVFrameDecoder(Variant variant, boolean temperature) { + super(variant); + this.reportsTemperature = temperature; + } + + @Override + protected TechemHeatCostAllocator decode(WMBusDevice device, SecondaryAddress address, byte[] buffer) { + Buffer buff = new Buffer(device.getOriginalMessage(), address); + + int coding = buff.skip(2).readByte() & 0xFF; + if (variant.getCoding() == coding) { + + List> records = new ArrayList<>(); + records.add(new Record<>(Record.Type.STATUS, ((Byte) buff.readByte()).intValue())); + records.add(new Record<>(Record.Type.PAST_READING_DATE, buff.readPastDate())); + records.add(new Record<>(Record.Type.PAST_VOLUME, (float) buff.readShort())); + records.add(new Record<>(Record.Type.CURRENT_READING_DATE, buff.readCurrentDate())); + records.add(new Record<>(Record.Type.CURRENT_VOLUME, (float) buff.readShort())); + records.add(new Record<>(Record.Type.RSSI, device.getOriginalMessage().getRssi())); + records.add(new Record<>(Record.Type.ALMANAC, "")); + + return new TechemHeatCostAllocator(device.getOriginalMessage(), device.getAdapter(), variant, records); + } + + if (coding == 0xA3) { + return new TechemUnknownDevice(device.getOriginalMessage(), device.getAdapter(), + new Variant(variant.version, variant.reportedType, coding, DeviceType.UNKNOWN)); + } + + return null; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemHKVRoomTempFrameDecoder.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemHKVRoomTempFrameDecoder.java index 85113e3..55f46ad 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemHKVRoomTempFrameDecoder.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemHKVRoomTempFrameDecoder.java @@ -1,74 +1,79 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.device.techem.decoder; - -import java.util.ArrayList; -import java.util.List; -import org.eclipse.smarthome.core.library.unit.SIUnits; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.device.techem.Record; -import org.openhab.binding.wmbus.device.techem.TechemHeatCostAllocator; -import org.openhab.binding.wmbus.device.techem.TechemUnknownDevice; -import org.openhab.binding.wmbus.device.techem.Variant; -import org.openmuc.jmbus.DeviceType; -import org.openmuc.jmbus.SecondaryAddress; - -import tec.uom.se.quantity.Quantities; - -class TechemHKVRoomTempFrameDecoder extends AbstractTechemFrameDecoder { - - private final int currentReadingOffset; - private final int historyOffset; - - TechemHKVRoomTempFrameDecoder(Variant variant) { - this(variant, 0, 0); - } - - TechemHKVRoomTempFrameDecoder(Variant variant, int currentReadingOffset, int historyOffset) { - super(variant); - this.currentReadingOffset = currentReadingOffset; - this.historyOffset = historyOffset; - } - - @Override - protected TechemHeatCostAllocator decode(WMBusDevice device, SecondaryAddress address, byte[] buffer) { - Buffer buff = new Buffer(device.getOriginalMessage(), address); - - int coding = buff.skip(2).readByte() & 0xFF; - if (variant.getCoding() == coding) { - - List> records = new ArrayList<>(); - records.add(new Record<>(Record.Type.STATUS, ((Byte) buff.readByte()).intValue())); - records.add(new Record<>(Record.Type.PAST_READING_DATE, buff.readPastDate())); - records.add(new Record<>(Record.Type.PAST_VOLUME, (float) buff.readShort())); - records.add(new Record<>(Record.Type.CURRENT_READING_DATE, buff.readCurrentDate())); - records.add(new Record<>(Record.Type.CURRENT_VOLUME, (float) buff.skip(currentReadingOffset).readShort())); - records.add(new Record<>(Record.Type.RSSI, device.getOriginalMessage().getRssi())); - - float temp1 = buff.readFloat(Buffer._SCALE_FACTOR_1_100th); - float temp2 = buff.readFloat(Buffer._SCALE_FACTOR_1_100th); - records.add(new Record<>(Record.Type.ROOM_TEMPERATURE, Quantities.getQuantity(temp1, SIUnits.CELSIUS))); - records.add(new Record<>(Record.Type.RADIATOR_TEMPERATURE, Quantities.getQuantity(temp2, SIUnits.CELSIUS))); - - records.add(new Record<>(Record.Type.COUNTER, buff.readByte() & 0xF0)); - - buff.skip(historyOffset); - records.add(new Record<>(Record.Type.ALMANAC, buff.readHistory())); - - return new TechemHeatCostAllocator(device.getOriginalMessage(), device.getAdapter(), variant, records); - } - - if (coding == 0xA3) { - return new TechemUnknownDevice(device.getOriginalMessage(), device.getAdapter(), new Variant(variant.version, variant.reportedType, coding, DeviceType.UNKNOWN)); - } - - return null; - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; + +import java.util.ArrayList; +import java.util.List; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.device.techem.Record; +import org.openhab.binding.wmbus.device.techem.TechemHeatCostAllocator; +import org.openhab.binding.wmbus.device.techem.TechemUnknownDevice; +import org.openhab.binding.wmbus.device.techem.Variant; +import org.openhab.core.library.unit.SIUnits; +import org.openmuc.jmbus.DeviceType; +import org.openmuc.jmbus.SecondaryAddress; + +import tec.uom.se.quantity.Quantities; + +class TechemHKVRoomTempFrameDecoder extends AbstractTechemFrameDecoder { + + private final int currentReadingOffset; + private final int historyOffset; + + TechemHKVRoomTempFrameDecoder(Variant variant) { + this(variant, 0, 0); + } + + TechemHKVRoomTempFrameDecoder(Variant variant, int currentReadingOffset, int historyOffset) { + super(variant); + this.currentReadingOffset = currentReadingOffset; + this.historyOffset = historyOffset; + } + + @Override + protected TechemHeatCostAllocator decode(WMBusDevice device, SecondaryAddress address, byte[] buffer) { + Buffer buff = new Buffer(device.getOriginalMessage(), address); + + int coding = buff.skip(2).readByte() & 0xFF; + if (variant.getCoding() == coding) { + + List> records = new ArrayList<>(); + records.add(new Record<>(Record.Type.STATUS, ((Byte) buff.readByte()).intValue())); + records.add(new Record<>(Record.Type.PAST_READING_DATE, buff.readPastDate())); + records.add(new Record<>(Record.Type.PAST_VOLUME, (float) buff.readShort())); + records.add(new Record<>(Record.Type.CURRENT_READING_DATE, buff.readCurrentDate())); + records.add(new Record<>(Record.Type.CURRENT_VOLUME, (float) buff.skip(currentReadingOffset).readShort())); + records.add(new Record<>(Record.Type.RSSI, device.getOriginalMessage().getRssi())); + + float temp1 = buff.readFloat(Buffer._SCALE_FACTOR_1_100th); + float temp2 = buff.readFloat(Buffer._SCALE_FACTOR_1_100th); + records.add(new Record<>(Record.Type.ROOM_TEMPERATURE, Quantities.getQuantity(temp1, SIUnits.CELSIUS))); + records.add(new Record<>(Record.Type.RADIATOR_TEMPERATURE, Quantities.getQuantity(temp2, SIUnits.CELSIUS))); + + records.add(new Record<>(Record.Type.COUNTER, buff.readByte() & 0xF0)); + + buff.skip(historyOffset); + records.add(new Record<>(Record.Type.ALMANAC, buff.readHistory())); + + return new TechemHeatCostAllocator(device.getOriginalMessage(), device.getAdapter(), variant, records); + } + + if (coding == 0xA3) { + return new TechemUnknownDevice(device.getOriginalMessage(), device.getAdapter(), + new Variant(variant.version, variant.reportedType, coding, DeviceType.UNKNOWN)); + } + + return null; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemHeatMeterFrameDecoder.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemHeatMeterFrameDecoder.java index 138e95e..c99dc48 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemHeatMeterFrameDecoder.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemHeatMeterFrameDecoder.java @@ -1,81 +1,83 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.device.techem.decoder; - -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.List; - -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.device.techem.Record; -import org.openhab.binding.wmbus.device.techem.TechemHeatMeter; -import org.openhab.binding.wmbus.device.techem.Variant; -import org.openmuc.jmbus.SecondaryAddress; - -// TODO adjust after finding test frame -class TechemHeatMeterFrameDecoder extends AbstractTechemFrameDecoder { - - private final Variant[] variants; - - TechemHeatMeterFrameDecoder(Variant ... variants) { - super(variants[0]); - this.variants = variants; - } - - @Override - protected TechemHeatMeter decode(WMBusDevice device, SecondaryAddress address, byte[] buffer) { - int offset = address.asByteArray().length + 2; - int coding = buffer[offset] & 0xFF; - - for (Variant variant : variants) { - if (variant.getCoding() == coding) { - LocalDateTime lastReading = parseLastDate(buffer, offset + 2); - float lastValue = parseLastPeriod(buffer, offset + 4); - LocalDateTime currentDate = parseCurrentDate(buffer, offset + 6); - float currentValue = parseActualPeriod(buffer, offset + 8); - - List> records = new ArrayList<>(); - records.add(new Record<>(Record.Type.CURRENT_READING_DATE, currentDate)); - records.add(new Record<>(Record.Type.CURRENT_VOLUME, currentValue)); - records.add(new Record<>(Record.Type.PAST_VOLUME, lastValue)); - records.add(new Record<>(Record.Type.PAST_READING_DATE, lastReading)); - records.add(new Record<>(Record.Type.RSSI, device.getOriginalMessage().getRssi())); - - return new TechemHeatMeter(device.getOriginalMessage(), device.getAdapter(), variant, records); - } - } - - return null; - } - - private float parseLastPeriod(byte[] buffer, int index) { - byte[] value = read(buffer, index, index + 1, index + 2); - - return (value[2] & 0xFF) + ((value[1] & 0xFF) << 8) + ((value[0] & 0xFF) << 16); - } - - private float parseActualPeriod(byte[] buffer, int index) { - byte[] value = read(buffer, index, index + 1, index + 2); - - return (value[2] & 0xFF) + ((value[1] & 0xFF) << 8) + ((value[0] & 0xFF) << 16); - } - - protected LocalDateTime parseActualDate(byte[] buffer, int dayIndex, int monthIndex) { - int dateint = parseBigEndianInt(buffer, dayIndex); - - int day = (dateint >> 7) & 0x1F; - int month = (buffer[monthIndex] >> 3) & 0x0F; - - LocalDateTime dateTime = LocalDateTime.now().withMonth(month).withDayOfMonth(day); - return dateTime.truncatedTo(ChronoUnit.SECONDS); - - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.List; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.device.techem.Record; +import org.openhab.binding.wmbus.device.techem.TechemHeatMeter; +import org.openhab.binding.wmbus.device.techem.Variant; +import org.openmuc.jmbus.SecondaryAddress; + +// TODO adjust after finding test frame +class TechemHeatMeterFrameDecoder extends AbstractTechemFrameDecoder { + + private final Variant[] variants; + + TechemHeatMeterFrameDecoder(Variant... variants) { + super(variants[0]); + this.variants = variants; + } + + @Override + protected TechemHeatMeter decode(WMBusDevice device, SecondaryAddress address, byte[] buffer) { + int offset = address.asByteArray().length + 2; + int coding = buffer[offset] & 0xFF; + + for (Variant variant : variants) { + if (variant.getCoding() == coding) { + LocalDateTime lastReading = parseLastDate(buffer, offset + 2); + float lastValue = parseLastPeriod(buffer, offset + 4); + LocalDateTime currentDate = parseCurrentDate(buffer, offset + 6); + float currentValue = parseActualPeriod(buffer, offset + 8); + + List> records = new ArrayList<>(); + records.add(new Record<>(Record.Type.CURRENT_READING_DATE, currentDate)); + records.add(new Record<>(Record.Type.CURRENT_VOLUME, currentValue)); + records.add(new Record<>(Record.Type.PAST_VOLUME, lastValue)); + records.add(new Record<>(Record.Type.PAST_READING_DATE, lastReading)); + records.add(new Record<>(Record.Type.RSSI, device.getOriginalMessage().getRssi())); + + return new TechemHeatMeter(device.getOriginalMessage(), device.getAdapter(), variant, records); + } + } + + return null; + } + + private float parseLastPeriod(byte[] buffer, int index) { + byte[] value = read(buffer, index, index + 1, index + 2); + + return (value[2] & 0xFF) + ((value[1] & 0xFF) << 8) + ((value[0] & 0xFF) << 16); + } + + private float parseActualPeriod(byte[] buffer, int index) { + byte[] value = read(buffer, index, index + 1, index + 2); + + return (value[2] & 0xFF) + ((value[1] & 0xFF) << 8) + ((value[0] & 0xFF) << 16); + } + + protected LocalDateTime parseActualDate(byte[] buffer, int dayIndex, int monthIndex) { + int dateint = parseBigEndianInt(buffer, dayIndex); + + int day = (dateint >> 7) & 0x1F; + int month = (buffer[monthIndex] >> 3) & 0x0F; + + LocalDateTime dateTime = LocalDateTime.now().withMonth(month).withDayOfMonth(day); + return dateTime.truncatedTo(ChronoUnit.SECONDS); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemSmokeDetectorFrameDecoder.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemSmokeDetectorFrameDecoder.java index 5e8903a..86b31f6 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemSmokeDetectorFrameDecoder.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemSmokeDetectorFrameDecoder.java @@ -1,51 +1,54 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.device.techem.decoder; - -import java.util.ArrayList; -import java.util.List; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.device.techem.Record; -import org.openhab.binding.wmbus.device.techem.TechemSmokeDetector; -import org.openhab.binding.wmbus.device.techem.Variant; -import org.openmuc.jmbus.SecondaryAddress; - -class TechemSmokeDetectorFrameDecoder extends AbstractTechemFrameDecoder { - - private final Variant[] variants; - - TechemSmokeDetectorFrameDecoder(Variant ... variants) { - super(variants[0]); - this.variants = variants; - } - - @Override - protected TechemSmokeDetector decode(WMBusDevice device, SecondaryAddress address, byte[] buffer) { - int offset = address.asByteArray().length + 2; // 2 first bytes of data is CRC - Buffer buff = new Buffer(device.getOriginalMessage(), address); - int coding = buff.skip(2).readByte() & 0xFF; - - for (Variant variant : variants) { - if (variant.getCoding() == coding) { - List> records = new ArrayList<>(); - records.add(new Record<>(Record.Type.STATUS, ((Byte) buff.readByte()).intValue())); - records.add(new Record<>(Record.Type.CURRENT_READING_DATE, buff.skip(2).readCurrentDate())); - buff.skip(2); - records.add(new Record<>(Record.Type.CURRENT_READING_DATE_SMOKE, buff.readPastDate())); - records.add(new Record<>(Record.Type.RSSI, device.getOriginalMessage().getRssi())); - - return new TechemSmokeDetector(device.getOriginalMessage(), device.getAdapter(), variant, records); - } - } - - return null; - - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; + +import java.util.ArrayList; +import java.util.List; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.device.techem.Record; +import org.openhab.binding.wmbus.device.techem.TechemSmokeDetector; +import org.openhab.binding.wmbus.device.techem.Variant; +import org.openmuc.jmbus.SecondaryAddress; + +class TechemSmokeDetectorFrameDecoder extends AbstractTechemFrameDecoder { + + private final Variant[] variants; + + TechemSmokeDetectorFrameDecoder(Variant... variants) { + super(variants[0]); + this.variants = variants; + } + + @Override + protected TechemSmokeDetector decode(WMBusDevice device, SecondaryAddress address, byte[] buffer) { + int offset = address.asByteArray().length + 2; // 2 first bytes of data is CRC + Buffer buff = new Buffer(device.getOriginalMessage(), address); + int coding = buff.skip(2).readByte() & 0xFF; + + for (Variant variant : variants) { + if (variant.getCoding() == coding) { + List> records = new ArrayList<>(); + records.add(new Record<>(Record.Type.STATUS, ((Byte) buff.readByte()).intValue())); + records.add(new Record<>(Record.Type.CURRENT_READING_DATE, buff.skip(2).readCurrentDate())); + buff.skip(2); + records.add(new Record<>(Record.Type.CURRENT_READING_DATE_SMOKE, buff.readPastDate())); + records.add(new Record<>(Record.Type.RSSI, device.getOriginalMessage().getRssi())); + + return new TechemSmokeDetector(device.getOriginalMessage(), device.getAdapter(), variant, records); + } + } + + return null; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemVariantFrameDecoderSelector.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemVariantFrameDecoderSelector.java index 8bcf4e5..40639c8 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemVariantFrameDecoderSelector.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemVariantFrameDecoderSelector.java @@ -1,67 +1,71 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.device.techem.decoder; - -import java.util.Map; - -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.device.techem.TechemDevice; -import org.openmuc.jmbus.SecondaryAddress; - -import com.google.common.collect.ImmutableMap; - -/** - * Frame decoder which passes actual work to delegate after confirming matching device variant. - * - * Since Techem devices tend to use very similar wmbus identification and most of them reports themselves as RESERVED we - * need to determine its type based on device version. This decoder reads byte at fixed, given position and then try to - * match given decoders with it. - * Since standard leaves space for vendor specific device types this type is an attempt to sort it in a way which does - * not interfere with actual frame decoding logic. - * Tag offset is calculated from end of address field which contains manufacturer, device id, device version and device - * type. Value -1 passed to constructor indicates that variant determination is based on device version while value 0 - * points to last byte in address which is device type. - * - * @author Łukasz Dywicki - initial contribution - */ -class TechemVariantFrameDecoderSelector implements TechemFrameDecoder { - - private final Map> decoders; - private final int tagOffset; - protected static final int BASED_ON_VERSION = -1; - protected static final int BASED_ON_DEVICETYPE = 0; - - protected TechemVariantFrameDecoderSelector(int tagOffset, ImmutableMap> codecMap) { - this.tagOffset = tagOffset; - this.decoders = codecMap; - } - - @Override - public TechemDevice decode(WMBusDevice device) { - SecondaryAddress address = device.getOriginalMessage().getSecondaryAddress(); - byte[] addressArray = address.asByteArray(); - int tagIndex = addressArray.length + tagOffset - 1; - - if (addressArray.length <= tagIndex) { - return null; - } - - Byte tag = addressArray[tagIndex]; - if (decoders.containsKey(tag)) { - return decoders.get(tag).decode(device); - } - - return null; - } - - @Override - public boolean supports(String deviceVariant) { - return decoders.values().stream().filter(decoder -> decoder.supports(deviceVariant)).findFirst().isPresent(); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; + +import java.util.Map; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.device.techem.TechemDevice; +import org.openmuc.jmbus.SecondaryAddress; + +import com.google.common.collect.ImmutableMap; + +/** + * Frame decoder which passes actual work to delegate after confirming matching device variant. + * + * Since Techem devices tend to use very similar wmbus identification and most of them reports themselves as RESERVED we + * need to determine its type based on device version. This decoder reads byte at fixed, given position and then try to + * match given decoders with it. + * Since standard leaves space for vendor specific device types this type is an attempt to sort it in a way which does + * not interfere with actual frame decoding logic. + * Tag offset is calculated from end of address field which contains manufacturer, device id, device version and device + * type. Value -1 passed to constructor indicates that variant determination is based on device version while value 0 + * points to last byte in address which is device type. + * + * @author Łukasz Dywicki - initial contribution + */ +class TechemVariantFrameDecoderSelector implements TechemFrameDecoder { + + private final Map> decoders; + private final int tagOffset; + protected static final int BASED_ON_VERSION = -1; + protected static final int BASED_ON_DEVICETYPE = 0; + + protected TechemVariantFrameDecoderSelector(int tagOffset, ImmutableMap> codecMap) { + this.tagOffset = tagOffset; + this.decoders = codecMap; + } + + @Override + public TechemDevice decode(WMBusDevice device) { + SecondaryAddress address = device.getOriginalMessage().getSecondaryAddress(); + byte[] addressArray = address.asByteArray(); + int tagIndex = addressArray.length + tagOffset - 1; + + if (addressArray.length <= tagIndex) { + return null; + } + + Byte tag = addressArray[tagIndex]; + if (decoders.containsKey(tag)) { + return decoders.get(tag).decode(device); + } + + return null; + } + + @Override + public boolean supports(String deviceVariant) { + return decoders.values().stream().filter(decoder -> decoder.supports(deviceVariant)).findFirst().isPresent(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemVersionFrameDecoderSelector.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemVersionFrameDecoderSelector.java index 684da3b..ed1c2db 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemVersionFrameDecoderSelector.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemVersionFrameDecoderSelector.java @@ -1,37 +1,41 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.device.techem.decoder; - -import static org.openhab.binding.wmbus.device.techem.TechemBindingConstants.*; - -import com.google.common.collect.ImmutableMap; - -class TechemVersionFrameDecoderSelector extends TechemVariantFrameDecoderSelector { - - private static final ImmutableMap> CODEC_MAP = ImmutableMap - .> builder() - .put(Byte.valueOf((byte) 0x45), new TechemHKVFrameDecoder(_68TCH6967_8)) - .put(Byte.valueOf((byte) 0x61), new TechemHKVFrameDecoder(_68TCH97255_8)) - .put(Byte.valueOf((byte) 0x64), new TechemHKVFrameDecoder(_68TCH100128_8)) - .put(Byte.valueOf((byte) 0x69), new TechemHKVRoomTempFrameDecoder(_68TCH105128_8, 0, 1)) - .put(Byte.valueOf((byte) 0x94), new TechemHKVRoomTempFrameDecoder(_68TCH148128_8, 1, 1)) - .put(Byte.valueOf((byte) 0x76), - new TechemSmokeDetectorFrameDecoder(_68TCH118255_161_A0, _68TCH118255_161_A1)) - - .put(Byte.valueOf((byte) 0x22), new TechemHeatMeterFrameDecoder(_68TCH3467_4)) - .put(Byte.valueOf((byte) 0x39), new TechemHeatMeterFrameDecoder(_68TCH5767_4)) - .put(Byte.valueOf((byte) 0x71), new TechemHeatMeterFrameDecoder(_68TCH11367_4_A0, _68TCH11367_4_A2)) - .put(Byte.valueOf((byte) 0x57), new TechemHeatMeterFrameDecoder(_68TCH8768_4)) - - .build(); - - TechemVersionFrameDecoderSelector() { - super(BASED_ON_VERSION, CODEC_MAP); - } -} \ No newline at end of file +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; + +import static org.openhab.binding.wmbus.device.techem.TechemBindingConstants.*; + +import com.google.common.collect.ImmutableMap; + +class TechemVersionFrameDecoderSelector extends TechemVariantFrameDecoderSelector { + + private static final ImmutableMap> CODEC_MAP = ImmutableMap + .> builder() + .put(Byte.valueOf((byte) 0x45), new TechemHKVFrameDecoder(_68TCH6967_8)) + .put(Byte.valueOf((byte) 0x61), new TechemHKVFrameDecoder(_68TCH97255_8)) + .put(Byte.valueOf((byte) 0x64), new TechemHKVFrameDecoder(_68TCH100128_8)) + .put(Byte.valueOf((byte) 0x69), new TechemHKVRoomTempFrameDecoder(_68TCH105128_8, 0, 1)) + .put(Byte.valueOf((byte) 0x94), new TechemHKVRoomTempFrameDecoder(_68TCH148128_8, 1, 1)) + .put(Byte.valueOf((byte) 0x76), + new TechemSmokeDetectorFrameDecoder(_68TCH118255_161_A0, _68TCH118255_161_A1)) + + .put(Byte.valueOf((byte) 0x22), new TechemHeatMeterFrameDecoder(_68TCH3467_4)) + .put(Byte.valueOf((byte) 0x39), new TechemHeatMeterFrameDecoder(_68TCH5767_4)) + .put(Byte.valueOf((byte) 0x71), new TechemHeatMeterFrameDecoder(_68TCH11367_4_A0, _68TCH11367_4_A2)) + .put(Byte.valueOf((byte) 0x57), new TechemHeatMeterFrameDecoder(_68TCH8768_4)) + + .build(); + + TechemVersionFrameDecoderSelector() { + super(BASED_ON_VERSION, CODEC_MAP); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemWaterMeterFrameDecoder.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemWaterMeterFrameDecoder.java index c88a2e2..eee9e2f 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemWaterMeterFrameDecoder.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemWaterMeterFrameDecoder.java @@ -1,67 +1,72 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.device.techem.decoder; - -import java.util.ArrayList; -import java.util.List; -import javax.measure.Unit; -import javax.measure.quantity.Volume; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.device.techem.Record; -import org.openhab.binding.wmbus.device.techem.TechemWaterMeter; -import org.openhab.binding.wmbus.device.techem.Variant; -import org.openmuc.jmbus.SecondaryAddress; - -import tec.uom.se.quantity.Quantities; -import tec.uom.se.unit.Units; - -class TechemWaterMeterFrameDecoder extends AbstractTechemFrameDecoder { - - /** - * Offset of counter byte, if set to -1 means that there is no counter and history to be read. - */ - private final int counterByteOffset; - - TechemWaterMeterFrameDecoder(Variant variant, int counterByteOffset) { - super(variant); - this.counterByteOffset = counterByteOffset; - } - - @Override - protected TechemWaterMeter decode(WMBusDevice device, SecondaryAddress address, byte[] buffer) { - Buffer buff = new Buffer(device.getOriginalMessage(), address); - - int coding = buff.skip(2).readByte() & 0xFF; - - if (coding == variant.getCoding()) { - Unit unit = Units.CUBIC_METRE; - - List> records = new ArrayList<>(); - records.add(new Record<>(Record.Type.STATUS, ((Byte) buff.readByte()).intValue())); - records.add(new Record<>(Record.Type.PAST_READING_DATE, buff.readPastDate())); - float pastVolume = buff.readFloat(Buffer._SCALE_FACTOR_1_10th); - records.add(new Record<>(Record.Type.PAST_VOLUME, Quantities.getQuantity(pastVolume, unit))); - records.add(new Record<>(Record.Type.CURRENT_READING_DATE, buff.readCurrentDate())); - float number = buff.readFloat(Buffer._SCALE_FACTOR_1_10th); - records.add(new Record<>(Record.Type.CURRENT_VOLUME, Quantities.getQuantity(number, unit))); - records.add(new Record<>(Record.Type.RSSI, device.getOriginalMessage().getRssi())); - - if (counterByteOffset >= 0) { - int counter = buff.skip(counterByteOffset).readByte() & 0xFF; - records.add(new Record<>(Record.Type.COUNTER, counter)); - records.add(new Record<>(Record.Type.ALMANAC, buff.readHistory())); - } - - return new TechemWaterMeter(device.getOriginalMessage(), device.getAdapter(), variant, records); - } - - return null; - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; + +import java.util.ArrayList; +import java.util.List; + +import javax.measure.Unit; +import javax.measure.quantity.Volume; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.device.techem.Record; +import org.openhab.binding.wmbus.device.techem.TechemWaterMeter; +import org.openhab.binding.wmbus.device.techem.Variant; +import org.openmuc.jmbus.SecondaryAddress; + +import tec.uom.se.quantity.Quantities; +import tec.uom.se.unit.Units; + +class TechemWaterMeterFrameDecoder extends AbstractTechemFrameDecoder { + + /** + * Offset of counter byte, if set to -1 means that there is no counter and history to be read. + */ + private final int counterByteOffset; + + TechemWaterMeterFrameDecoder(Variant variant, int counterByteOffset) { + super(variant); + this.counterByteOffset = counterByteOffset; + } + + @Override + protected TechemWaterMeter decode(WMBusDevice device, SecondaryAddress address, byte[] buffer) { + Buffer buff = new Buffer(device.getOriginalMessage(), address); + + int coding = buff.skip(2).readByte() & 0xFF; + + if (coding == variant.getCoding()) { + Unit unit = Units.CUBIC_METRE; + + List> records = new ArrayList<>(); + records.add(new Record<>(Record.Type.STATUS, ((Byte) buff.readByte()).intValue())); + records.add(new Record<>(Record.Type.PAST_READING_DATE, buff.readPastDate())); + float pastVolume = buff.readFloat(Buffer._SCALE_FACTOR_1_10th); + records.add(new Record<>(Record.Type.PAST_VOLUME, Quantities.getQuantity(pastVolume, unit))); + records.add(new Record<>(Record.Type.CURRENT_READING_DATE, buff.readCurrentDate())); + float number = buff.readFloat(Buffer._SCALE_FACTOR_1_10th); + records.add(new Record<>(Record.Type.CURRENT_VOLUME, Quantities.getQuantity(number, unit))); + records.add(new Record<>(Record.Type.RSSI, device.getOriginalMessage().getRssi())); + + if (counterByteOffset >= 0) { + int counter = buff.skip(counterByteOffset).readByte() & 0xFF; + records.add(new Record<>(Record.Type.COUNTER, counter)); + records.add(new Record<>(Record.Type.ALMANAC, buff.readHistory())); + } + + return new TechemWaterMeter(device.getOriginalMessage(), device.getAdapter(), variant, records); + } + + return null; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/handler/TechemMeterHandler.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/handler/TechemMeterHandler.java index cb777aa..8b079f8 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/handler/TechemMeterHandler.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/handler/TechemMeterHandler.java @@ -1,148 +1,155 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ - -package org.openhab.binding.wmbus.device.techem.handler; - -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.Map; -import java.util.Optional; - -import javax.measure.Quantity; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.smarthome.core.library.CoreItemFactory; -import org.eclipse.smarthome.core.library.types.DateTimeType; -import org.eclipse.smarthome.core.library.types.DecimalType; -import org.eclipse.smarthome.core.library.types.QuantityType; -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.ThingTypeUID; -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.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.config.DateFieldMode; -import org.openhab.binding.wmbus.device.techem.Record; -import org.openhab.binding.wmbus.device.techem.Record.Type; -import org.openhab.binding.wmbus.device.techem.TechemBindingConstants; -import org.openhab.binding.wmbus.device.techem.TechemDevice; -import org.openhab.binding.wmbus.device.techem.decoder.TechemFrameDecoder; -import org.openhab.binding.wmbus.handler.WMBusDeviceHandler; -import org.openmuc.jmbus.DecodingException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link TechemMeterHandler} holds logic related to retrieval (actually mapping) of data reported by various techem - * devices. - * - * @author Łukasz Dywicki - Initial contribution - */ - -public class TechemMeterHandler extends WMBusDeviceHandler { - - private final Logger logger = LoggerFactory.getLogger(TechemMeterHandler.class); - - private final Class type; - private final TechemFrameDecoder decoder; - private final Map channelMapping; - - public TechemMeterHandler(Thing thing, Class type, TechemFrameDecoder decoder) { - this(thing, type, decoder, fetchMapping(thing.getThingTypeUID())); - } - - protected TechemMeterHandler(Thing thing, Class type, TechemFrameDecoder decoder, - Map channelMapping) { - super(thing); - this.type = type; - this.decoder = decoder; - this.channelMapping = channelMapping; - } - - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - logger.trace("handleCommand {} for channel {}", command.toString(), channelUID.toString()); - if (command == RefreshType.REFRESH) { - if (wmbusDevice != null) { - Type recordType = channelMapping.get(channelUID.getId()); - if (recordType != null) { - Optional> record = wmbusDevice.getRecord(recordType); - - if (recordType.isDate()) { - String acceptedType = ""; - Channel channel = getThing().getChannel(channelUID.getId()); - if (channel != null) { - acceptedType = channel.getAcceptedItemType(); - } - if (CoreItemFactory.DATETIME.equals(acceptedType) && DateFieldMode.DATE_TIME == getDateFieldMode()) { - record.map(measurement -> map(measurement, measurement.getValue())) - .ifPresent(state -> updateState(channelUID.getId(), state)); - } else if (CoreItemFactory.STRING.equals(acceptedType) && DateFieldMode.FORMATTED_STRING == getDateFieldMode()) { - record.map(measurement -> map(measurement, measurement.getValue())) - .ifPresent(state -> updateState(channelUID.getId(), state)); - } else if (CoreItemFactory.NUMBER.equals(acceptedType) && DateFieldMode.UNIX_TIMESTAMP == getDateFieldMode()) { - record.map(measurement -> map(measurement, measurement.getValue())) - .ifPresent(state -> updateState(channelUID.getId(), state)); - } else { - logger.info("Ignoring update of channel {}, it is date field with no proper mapping available.", channelUID); - } - } else { - record.map(measurement -> map(measurement, measurement.getValue())) - .ifPresent(state -> updateState(channelUID.getId(), state)); - } - - if (!record.isPresent()) { - logger.warn("Could not read value of record {} in received frame", recordType); - } - } else { - logger.warn("Unown channel {}, not supported by {}", channelUID, thing.getUID()); - } - } - } - } - - private State map(Record measurement, Object value) { - State returnvalue = null; // maybe Undef would be better? - if (value instanceof Quantity) { - Quantity quantity = (Quantity) value; - returnvalue = new QuantityType<>(quantity.getValue(), quantity.getUnit()); - } else if (value instanceof Integer) { - returnvalue = new DecimalType(((Integer) value).floatValue()); - } else if (value instanceof Double) { - returnvalue = new DecimalType(((Double) value).floatValue()); - } else if (value instanceof Float) { - returnvalue = new DecimalType(((Float) value).floatValue()); - } else if (value instanceof LocalDateTime) { - returnvalue = convertDate( - new DateTimeType(ZonedDateTime.of((LocalDateTime) value, ZoneId.systemDefault()))); - } else if (value instanceof ZonedDateTime) { - returnvalue = convertDate(new DateTimeType((ZonedDateTime) value)); - } - - return returnvalue; - } - - @Override - protected T parseDevice(WMBusDevice device) throws DecodingException { - TechemDevice decodedDevice = decoder.decode(device); - if (type.isInstance(decodedDevice)) { - return type.cast(decodedDevice); - } - return null; - } - - private static Map fetchMapping(@NonNull ThingTypeUID thingTypeUID) { - return TechemBindingConstants.RECORD_MAP.get(thingTypeUID); - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem.handler; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Map; +import java.util.Optional; + +import javax.measure.Quantity; + +import org.eclipse.jdt.annotation.NonNull; +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.config.DateFieldMode; +import org.openhab.binding.wmbus.device.techem.Record; +import org.openhab.binding.wmbus.device.techem.Record.Type; +import org.openhab.binding.wmbus.device.techem.TechemBindingConstants; +import org.openhab.binding.wmbus.device.techem.TechemDevice; +import org.openhab.binding.wmbus.device.techem.decoder.TechemFrameDecoder; +import org.openhab.binding.wmbus.handler.WMBusDeviceHandler; +import org.openhab.core.library.CoreItemFactory; +import org.openhab.core.library.types.DateTimeType; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; +import org.openmuc.jmbus.DecodingException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link TechemMeterHandler} holds logic related to retrieval (actually mapping) of data reported by various techem + * devices. + * + * @author Łukasz Dywicki - Initial contribution + */ + +public class TechemMeterHandler extends WMBusDeviceHandler { + + private final Logger logger = LoggerFactory.getLogger(TechemMeterHandler.class); + + private final Class type; + private final TechemFrameDecoder decoder; + private final Map channelMapping; + + public TechemMeterHandler(Thing thing, Class type, TechemFrameDecoder decoder) { + this(thing, type, decoder, fetchMapping(thing.getThingTypeUID())); + } + + protected TechemMeterHandler(Thing thing, Class type, TechemFrameDecoder decoder, + Map channelMapping) { + super(thing); + this.type = type; + this.decoder = decoder; + this.channelMapping = channelMapping; + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + logger.trace("handleCommand {} for channel {}", command.toString(), channelUID.toString()); + if (command == RefreshType.REFRESH) { + if (wmbusDevice != null) { + Type recordType = channelMapping.get(channelUID.getId()); + if (recordType != null) { + Optional> record = wmbusDevice.getRecord(recordType); + + if (recordType.isDate()) { + String acceptedType = ""; + Channel channel = getThing().getChannel(channelUID.getId()); + if (channel != null) { + acceptedType = channel.getAcceptedItemType(); + } + if (CoreItemFactory.DATETIME.equals(acceptedType) + && DateFieldMode.DATE_TIME == getDateFieldMode()) { + record.map(measurement -> map(measurement, measurement.getValue())) + .ifPresent(state -> updateState(channelUID.getId(), state)); + } else if (CoreItemFactory.STRING.equals(acceptedType) + && DateFieldMode.FORMATTED_STRING == getDateFieldMode()) { + record.map(measurement -> map(measurement, measurement.getValue())) + .ifPresent(state -> updateState(channelUID.getId(), state)); + } else if (CoreItemFactory.NUMBER.equals(acceptedType) + && DateFieldMode.UNIX_TIMESTAMP == getDateFieldMode()) { + record.map(measurement -> map(measurement, measurement.getValue())) + .ifPresent(state -> updateState(channelUID.getId(), state)); + } else { + logger.info( + "Ignoring update of channel {}, it is date field with no proper mapping available.", + channelUID); + } + } else { + record.map(measurement -> map(measurement, measurement.getValue())) + .ifPresent(state -> updateState(channelUID.getId(), state)); + } + + if (!record.isPresent()) { + logger.warn("Could not read value of record {} in received frame", recordType); + } + } else { + logger.warn("Unown channel {}, not supported by {}", channelUID, thing.getUID()); + } + } + } + } + + private State map(Record measurement, Object value) { + State returnvalue = null; // maybe Undef would be better? + if (value instanceof Quantity) { + Quantity quantity = (Quantity) value; + returnvalue = new QuantityType<>(quantity.getValue(), quantity.getUnit()); + } else if (value instanceof Integer) { + returnvalue = new DecimalType(((Integer) value).floatValue()); + } else if (value instanceof Double) { + returnvalue = new DecimalType(((Double) value).floatValue()); + } else if (value instanceof Float) { + returnvalue = new DecimalType(((Float) value).floatValue()); + } else if (value instanceof LocalDateTime) { + returnvalue = convertDate( + new DateTimeType(ZonedDateTime.of((LocalDateTime) value, ZoneId.systemDefault()))); + } else if (value instanceof ZonedDateTime) { + returnvalue = convertDate(new DateTimeType((ZonedDateTime) value)); + } + + return returnvalue; + } + + @Override + protected T parseDevice(WMBusDevice device) throws DecodingException { + TechemDevice decodedDevice = decoder.decode(device); + if (type.isInstance(decodedDevice)) { + return type.cast(decodedDevice); + } + return null; + } + + private static Map fetchMapping(@NonNull ThingTypeUID thingTypeUID) { + return TechemBindingConstants.RECORD_MAP.get(thingTypeUID); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/discovery/CompositeMessageListener.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/discovery/CompositeMessageListener.java index 64f9442..10c8461 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/discovery/CompositeMessageListener.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/discovery/CompositeMessageListener.java @@ -1,57 +1,60 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.discovery; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openhab.binding.wmbus.handler.WMBusMessageListener; - -/** - * Listener backed by given set of other listeners. - * - * @author Łukasz Dywicki - initial contribution - */ -public class CompositeMessageListener implements WMBusMessageListener { - - private final Set listeners; - - public CompositeMessageListener() { - this(new LinkedHashSet<>()); - } - - public CompositeMessageListener(Set listeners) { - this.listeners = listeners; - } - - @Override - public void onNewWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { - for (WMBusMessageListener listener : listeners) { - listener.onNewWMBusDevice(adapter, device); - } - } - - @Override - public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { - for (WMBusMessageListener listener : listeners) { - listener.onChangedWMBusDevice(adapter, device); - } - } - - public void addMessageListener(WMBusMessageListener listener) { - this.listeners.add(listener); - } - - public void removeMessageListener(WMBusMessageListener listener) { - this.listeners.add(listener); - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.discovery; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openhab.binding.wmbus.handler.WMBusMessageListener; + +/** + * Listener backed by given set of other listeners. + * + * @author Łukasz Dywicki - initial contribution + */ +public class CompositeMessageListener implements WMBusMessageListener { + + private final Set listeners; + + public CompositeMessageListener() { + this(new LinkedHashSet<>()); + } + + public CompositeMessageListener(Set listeners) { + this.listeners = listeners; + } + + @Override + public void onNewWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { + for (WMBusMessageListener listener : listeners) { + listener.onNewWMBusDevice(adapter, device); + } + } + + @Override + public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { + for (WMBusMessageListener listener : listeners) { + listener.onChangedWMBusDevice(adapter, device); + } + } + + public void addMessageListener(WMBusMessageListener listener) { + this.listeners.add(listener); + } + + public void removeMessageListener(WMBusMessageListener listener) { + this.listeners.add(listener); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/discovery/DebugMessageListener.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/discovery/DebugMessageListener.java index 67044a7..e42a130 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/discovery/DebugMessageListener.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/discovery/DebugMessageListener.java @@ -1,61 +1,71 @@ -package org.openhab.binding.wmbus.discovery; - -import org.eclipse.smarthome.core.util.HexUtils; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openhab.binding.wmbus.handler.WMBusMessageListener; -import org.openmuc.jmbus.DataRecord; -import org.openmuc.jmbus.DecodingException; -import org.openmuc.jmbus.EncryptionMode; -import org.openmuc.jmbus.SecondaryAddress; -import org.openmuc.jmbus.VariableDataStructure; -import org.osgi.service.component.annotations.Component; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@Component(immediate = true) -public class DebugMessageListener implements WMBusMessageListener { - - private final Logger logger = LoggerFactory.getLogger(DebugMessageListener.class); - - @Override - public void onNewWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { - log(device); - } - - @Override - public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { - log(device); - } - - private void log(WMBusDevice device) { - SecondaryAddress secondaryAddress = device.getOriginalMessage().getSecondaryAddress(); - - if (!"TCH".equals(secondaryAddress.getManufacturerId())) { - - try { - VariableDataStructure vdr = device.getOriginalMessage().getVariableDataResponse(); - - if (vdr.getEncryptionMode() == EncryptionMode.NONE) { - vdr.decode(); - - logger.debug( - "Received telegram ({}): access number: {}, status: {}, encryption mode: {}, number of encrypted blocks: {}", - secondaryAddress, vdr.getAccessNumber(), vdr.getStatus(), vdr.getEncryptionMode(), - vdr.getNumberOfEncryptedBlocks()); - logger.debug("Message in hex: {}", HexUtils.bytesToHex(device.getOriginalMessage().asBlob())); - - for (DataRecord record : vdr.getDataRecords()) { - logger.debug("> record: {}", record.toString()); - } - } else { - logger.debug("Encrypted block {}", vdr); - } - } catch (DecodingException e) { - logger.debug("Could not decode frame ({})", secondaryAddress, e.getMessage()); - } - } - - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.discovery; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openhab.binding.wmbus.handler.WMBusMessageListener; +import org.openhab.core.util.HexUtils; +import org.openmuc.jmbus.DataRecord; +import org.openmuc.jmbus.DecodingException; +import org.openmuc.jmbus.EncryptionMode; +import org.openmuc.jmbus.SecondaryAddress; +import org.openmuc.jmbus.VariableDataStructure; +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component(immediate = true) +public class DebugMessageListener implements WMBusMessageListener { + + private final Logger logger = LoggerFactory.getLogger(DebugMessageListener.class); + + @Override + public void onNewWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { + log(device); + } + + @Override + public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { + log(device); + } + + private void log(WMBusDevice device) { + SecondaryAddress secondaryAddress = device.getOriginalMessage().getSecondaryAddress(); + + if (!"TCH".equals(secondaryAddress.getManufacturerId())) { + + try { + VariableDataStructure vdr = device.getOriginalMessage().getVariableDataResponse(); + + if (vdr.getEncryptionMode() == EncryptionMode.NONE) { + vdr.decode(); + + logger.debug( + "Received telegram ({}): access number: {}, status: {}, encryption mode: {}, number of encrypted blocks: {}", + secondaryAddress, vdr.getAccessNumber(), vdr.getStatus(), vdr.getEncryptionMode(), + vdr.getNumberOfEncryptedBlocks()); + logger.debug("Message in hex: {}", HexUtils.bytesToHex(device.getOriginalMessage().asBlob())); + + for (DataRecord record : vdr.getDataRecords()) { + logger.debug("> record: {}", record.toString()); + } + } else { + logger.debug("Encrypted block {}", vdr); + } + } catch (DecodingException e) { + logger.debug("Could not decode frame ({})", secondaryAddress, e.getMessage()); + } + } + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/discovery/WMBusDiscoveryParticipant.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/discovery/WMBusDiscoveryParticipant.java index 8bbb4ba..91ff9c9 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/discovery/WMBusDiscoveryParticipant.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/discovery/WMBusDiscoveryParticipant.java @@ -1,55 +1,58 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.discovery; - -import java.util.Set; - -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.smarthome.config.discovery.DiscoveryResult; -import org.eclipse.smarthome.core.thing.ThingTypeUID; -import org.eclipse.smarthome.core.thing.ThingUID; -import org.openhab.binding.wmbus.WMBusDevice; - -/** - * Discovery participant which is responsible for identification of WMBus device. - * - * Realizations of this interface might return empty results when they do not know device in order to let other - * participants do their job. - * - * @author Łukasz Dywicki - initial contribution. - */ -public interface WMBusDiscoveryParticipant { - - /** - * Defines the list of thing types that this participant can identify - * - * @return a set of thing type UIDs for which results can be created - */ - Set getSupportedThingTypeUIDs(); - - /** - * Creates a discovery result for a WMBus device - * - * @param device the WMbus device found on the network - * @return the according discovery result or null, if device is not - * supported by this participant - */ - @Nullable - DiscoveryResult createResult(WMBusDevice device); - - /** - * Returns the thing UID for a WMBus device - * - * @param device the WMBus device - * @return a thing UID or null, if the device is not supported by this participant - */ - @Nullable - public ThingUID getThingUID(WMBusDevice device); - -} +/** + * Copyright (c) 2010-2021 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.wmbus.discovery; + +import java.util.Set; + +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; + +/** + * Discovery participant which is responsible for identification of WMBus device. + * + * Realizations of this interface might return empty results when they do not know device in order to let other + * participants do their job. + * + * @author Łukasz Dywicki - initial contribution. + */ +public interface WMBusDiscoveryParticipant { + + /** + * Defines the list of thing types that this participant can identify + * + * @return a set of thing type UIDs for which results can be created + */ + Set getSupportedThingTypeUIDs(); + + /** + * Creates a discovery result for a WMBus device + * + * @param device the WMbus device found on the network + * @return the according discovery result or null, if device is not + * supported by this participant + */ + @Nullable + DiscoveryResult createResult(WMBusDevice device); + + /** + * Returns the thing UID for a WMBus device + * + * @param device the WMBus device + * @return a thing UID or null, if the device is not supported by this participant + */ + @Nullable + public ThingUID getThingUID(WMBusDevice device); +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/VirtualWMBusBridgeHandler.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/VirtualWMBusBridgeHandler.java index b94a798..2d5e5b6 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/VirtualWMBusBridgeHandler.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/VirtualWMBusBridgeHandler.java @@ -1,86 +1,87 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - *

- * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ - -package org.openhab.binding.wmbus.handler; - -import java.util.Collection; -import java.util.Collections; -import java.util.Set; - -import org.eclipse.smarthome.config.core.status.ConfigStatusMessage; -import org.eclipse.smarthome.core.thing.Bridge; -import org.eclipse.smarthome.core.thing.ThingStatus; -import org.eclipse.smarthome.core.thing.ThingTypeUID; -import org.openhab.binding.wmbus.WMBusBindingConstants; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.config.WMBusBridgeConfig; -import org.openhab.binding.wmbus.internal.WMBusReceiver; -import org.openhab.io.transport.mbus.wireless.KeyStorage; - -import static org.openhab.binding.wmbus.WMBusBindingConstants.THING_TYPE_VIRTUAL_BRIDGE; - -/** - * The {@link VirtualWMBusBridgeHandler} class defines This class represents the WMBus bridge which - * does not ship any real connection . - * - * @author Hanno - Felix Wagner - Initial contribution - * @author Łukasz Dywicki - Bringing back functionality via separate handler - */ -public class VirtualWMBusBridgeHandler extends WMBusBridgeHandlerBase { - - public final static Set SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_VIRTUAL_BRIDGE); - - public VirtualWMBusBridgeHandler(Bridge bridge, KeyStorage keyStorage) { - super(bridge, keyStorage); - } - - @Override - public Collection getConfigStatus() { - return Collections.emptyList(); // all good, otherwise add some messages - } - - /** - * Connects to the WMBus radio module and updates bridge status. - * - * @see org.eclipse.smarthome.core.thing.binding.BaseThingHandler#initialize() - */ - @Override - public void initialize() { - logger.debug("WMBusBridgeHandler: initialize()"); - - updateStatus(ThingStatus.UNKNOWN); - wmbusReceiver = new WMBusReceiver(this); - - WMBusBridgeConfig config = getConfigAs(WMBusBridgeConfig.class); - if (config.deviceIDFilter == null || config.deviceIDFilter.trim().isEmpty()) { - logger.debug("Device ID filter is empty."); - } else { - wmbusReceiver.setFilterIDs(config.getDeviceIDFilter()); - } - - // success - logger.debug("WMBusBridgeHandler: Initialization done! Setting bridge online"); - updateStatus(ThingStatus.ONLINE); - } - - @Override - public void dispose() { - super.dispose(); - - if (wmbusReceiver != null) { - wmbusReceiver = null; - } - } - - public void reset() { - wmbusReceiver = null; - initialize(); - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.handler; + +import static org.openhab.binding.wmbus.WMBusBindingConstants.THING_TYPE_VIRTUAL_BRIDGE; + +import java.util.Collection; +import java.util.Collections; +import java.util.Set; + +import org.openhab.binding.wmbus.config.WMBusBridgeConfig; +import org.openhab.binding.wmbus.internal.WMBusReceiver; +import org.openhab.core.config.core.status.ConfigStatusMessage; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.io.transport.mbus.wireless.KeyStorage; + +/** + * The {@link VirtualWMBusBridgeHandler} class defines This class represents the WMBus bridge which + * does not ship any real connection . + * + * @author Hanno - Felix Wagner - Initial contribution + * @author Łukasz Dywicki - Bringing back functionality via separate handler + */ +public class VirtualWMBusBridgeHandler extends WMBusBridgeHandlerBase { + + public final static Set SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_VIRTUAL_BRIDGE); + + public VirtualWMBusBridgeHandler(Bridge bridge, KeyStorage keyStorage) { + super(bridge, keyStorage); + } + + @Override + public Collection getConfigStatus() { + return Collections.emptyList(); // all good, otherwise add some messages + } + + /** + * Connects to the WMBus radio module and updates bridge status. + * + * @see org.openhab.core.thing.binding.BaseThingHandler#initialize() + */ + @Override + public void initialize() { + logger.debug("WMBusBridgeHandler: initialize()"); + + updateStatus(ThingStatus.UNKNOWN); + wmbusReceiver = new WMBusReceiver(this); + + WMBusBridgeConfig config = getConfigAs(WMBusBridgeConfig.class); + if (config.deviceIDFilter == null || config.deviceIDFilter.trim().isEmpty()) { + logger.debug("Device ID filter is empty."); + } else { + wmbusReceiver.setFilterIDs(config.getDeviceIDFilter()); + } + + // success + logger.debug("WMBusBridgeHandler: Initialization done! Setting bridge online"); + updateStatus(ThingStatus.ONLINE); + } + + @Override + public void dispose() { + super.dispose(); + + if (wmbusReceiver != null) { + wmbusReceiver = null; + } + } + + public void reset() { + wmbusReceiver = null; + initialize(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusAdapter.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusAdapter.java index 5257add..11847b8 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusAdapter.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusAdapter.java @@ -1,31 +1,36 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - *

- * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.handler; - -import org.eclipse.smarthome.core.common.registry.Identifiable; -import org.eclipse.smarthome.core.thing.ThingUID; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.config.DateFieldMode; - -/** - * Representation of WMBus device which is holds a link to radio device. - *

- * Main purpose of this interface is to cut off hard dependency on {@link WMBusBridgeHandler}. - * - * @author Łukasz Dywicki - */ -public interface WMBusAdapter extends Identifiable { - - void processMessage(WMBusDevice device); - - void reset(); - - DateFieldMode getDateFieldMode(); - -} +/** + * Copyright (c) 2010-2021 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.wmbus.handler; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.config.DateFieldMode; +import org.openhab.core.common.registry.Identifiable; +import org.openhab.core.thing.ThingUID; + +/** + * Representation of WMBus device which is holds a link to radio device. + *

+ * Main purpose of this interface is to cut off hard dependency on {@link WMBusBridgeHandler}. + * + * @author Łukasz Dywicki + */ +@NonNullByDefault +public interface WMBusAdapter extends Identifiable { + + void processMessage(WMBusDevice device); + + void reset(); + + DateFieldMode getDateFieldMode(); +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusBridgeHandler.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusBridgeHandler.java index bc7a1e4..c7b7394 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusBridgeHandler.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusBridgeHandler.java @@ -1,211 +1,208 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - *

- * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ - -package org.openhab.binding.wmbus.handler; - -import static org.openhab.binding.wmbus.WMBusBindingConstants.*; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import org.eclipse.smarthome.config.core.Configuration; -import org.eclipse.smarthome.config.core.status.ConfigStatusMessage; -import org.eclipse.smarthome.core.thing.Bridge; -import org.eclipse.smarthome.core.thing.ThingStatus; -import org.eclipse.smarthome.core.thing.ThingStatusDetail; -import org.openhab.binding.wmbus.WMBusBindingConstants; -import org.openhab.binding.wmbus.config.DateFieldMode; -import org.openhab.binding.wmbus.config.StickModel; -import org.openhab.binding.wmbus.config.WMBusSerialBridgeConfig; -import org.openhab.binding.wmbus.internal.WMBusReceiver; -import org.openhab.io.transport.mbus.wireless.KeyStorage; -import org.openmuc.jmbus.wireless.WMBusConnection; -import org.openmuc.jmbus.wireless.WMBusConnection.WMBusManufacturer; -import org.openmuc.jmbus.wireless.WMBusConnection.WMBusSerialBuilder; -import org.openmuc.jmbus.wireless.WMBusMode; - -/** - * The {@link WMBusBridgeHandler} class defines This class represents the WMBus bridge and handles general events for - * the whole group of WMBus devices. - * - * @author Hanno - Felix Wagner - Initial contribution - */ - -public class WMBusBridgeHandler extends WMBusBridgeHandlerBase { - - private ScheduledFuture initFuture; - private WMBusConnection wmbusConnection; - - public WMBusBridgeHandler(Bridge bridge, KeyStorage keyStorage) { - super(bridge, keyStorage); - } - - @Override - public Collection getConfigStatus() { - List messages = new ArrayList<>(); - WMBusSerialBridgeConfig config = getConfigAs(WMBusSerialBridgeConfig.class); - - // check stick model - if (config.stickModel == null) { - messages.add(ConfigStatusMessage.Builder.error(CONFKEY_STICK_MODEL).withMessageKeySuffix(CONFKEY_STICK_MODEL).build()); - } - // check serial device name - if (config.serialDevice == null) { - messages.add(ConfigStatusMessage.Builder.error(CONFKEY_INTERFACE_NAME).withMessageKeySuffix(CONFKEY_INTERFACE_NAME).build()); - } - // check radio mode - if (config.radioMode == null) { - messages.add(ConfigStatusMessage.Builder.error(CONFKEY_RADIO_MODE).withMessageKeySuffix(CONFKEY_RADIO_MODE).build()); - } - - return messages; - } - - /** - * Connects to the WMBus radio module and updates bridge status. - * - * @see org.eclipse.smarthome.core.thing.binding.BaseThingHandler#initialize() - */ - @Override - public void initialize() { - logger.debug("WMBusBridgeHandler: initialize()"); - - WMBusSerialBridgeConfig config = getConfigAs(WMBusSerialBridgeConfig.class); - updateStatus(ThingStatus.UNKNOWN); - initFuture = scheduler.schedule(() -> { - // set up WMBus receiver = handler for radio telegrams - if (wmbusReceiver == null) { - StickModel stickModel = config.stickModel; - String interfaceName = config.serialDevice; - WMBusMode radioMode = config.radioMode; - - // connect to the radio module / open WMBus connection - logger.debug("Opening wmbus stick {} serial port {} in mode {}", stickModel, interfaceName, - radioMode); - - WMBusManufacturer wmBusManufacturer = parseManufacturer(stickModel.name().toUpperCase()); - if (wmBusManufacturer == null) { - logger.error("Cannot open WMBus device. Unknown manufacturer given: " + stickModel - + ". Expected 'amber' or 'imst' or 'rc'."); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, - "Cannot open WMBus device. Unknown manufacturer given: " + stickModel - + ". Expected 'amber' or 'imst' or 'rc'."); - return; - } - logger.debug("Building new connection"); - - wmbusReceiver = new WMBusReceiver(this); - - if (config.deviceIDFilter == null || config.deviceIDFilter.trim().isEmpty()) { - logger.debug("Device ID filter is empty."); - } else { - wmbusReceiver.setFilterIDs(config.getDeviceIDFilter()); - } - - WMBusSerialBuilder connectionBuilder = new WMBusSerialBuilder(wmBusManufacturer, wmbusReceiver, - interfaceName); - - logger.debug("Setting WMBus radio mode to {}", radioMode.toString()); - connectionBuilder.setMode(radioMode); - connectionBuilder.setTimeout(1000); // infinite - // connectionBuilder.setTimeout(0); // infinite - - try { - logger.debug("Building/opening connection"); - logger.debug( - "NOTE: if initialization does not progress from here, check systemd journal for Execptions -- probably native lib still loaded by another ClassLoader = previous version or instance of WMBus binding -> restart OpenHAB"); - if (wmbusConnection != null) { - logger.debug("Connection already set, closing old"); - wmbusConnection.close(); - wmbusConnection = null; - } - wmbusConnection = connectionBuilder.build(); - } catch (IOException e) { - logger.error("Cannot open WMBus device. Connection builder returned: " + e.getMessage()); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, - "Cannot open WMBus device. Connection builder returned: " + e.getMessage()); - return; - } - - logger.debug("Connected to WMBus serial port"); - - // close WMBus connection on shutdown - logger.trace("Setting shutdown hook"); - Runtime.getRuntime().addShutdownHook(new Thread() { - @Override - public void run() { - if (wmbusConnection != null) { - try { - logger.debug("Closing connection to WMBus radio device"); - wmbusConnection.close(); - } catch (IOException e) { - logger.error("Cannot close connection to WMBus radio module: " + e.getMessage()); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, - "Cannot close connection to WMBus radio module: " + e.getMessage()); - return; - } - } - } - }); - - // success - logger.debug("WMBusBridgeHandler: Initialization done! Setting bridge online"); - updateStatus(ThingStatus.ONLINE); - } - }, 0, TimeUnit.SECONDS); - } - - private static WMBusManufacturer parseManufacturer(String manufacturer) { - switch (manufacturer.toLowerCase()) { - case MANUFACTURER_AMBER: - return WMBusManufacturer.AMBER; - case MANUFACTURER_RADIO_CRAFTS: - return WMBusManufacturer.RADIO_CRAFTS; - case MANUFACTURER_IMST: - return WMBusManufacturer.IMST; - default: - return null; - } - } - - @Override - public void dispose() { - super.dispose(); - logger.debug("WMBus bridge Handler disposed."); - - if (wmbusConnection != null) { - logger.debug("Close serial device connection"); - try { - wmbusConnection.close(); - } catch (IOException e) { - logger.error("An exception occurred while closing the wmbusConnection", e); - } - wmbusConnection = null; - } - - if (wmbusReceiver != null) { - wmbusReceiver = null; - } - - if (initFuture != null) { - initFuture.cancel(true); - initFuture = null; - } - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.handler; + +import static org.openhab.binding.wmbus.WMBusBindingConstants.*; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.openhab.binding.wmbus.config.StickModel; +import org.openhab.binding.wmbus.config.WMBusSerialBridgeConfig; +import org.openhab.binding.wmbus.internal.WMBusReceiver; +import org.openhab.core.config.core.status.ConfigStatusMessage; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.io.transport.mbus.wireless.KeyStorage; +import org.openmuc.jmbus.wireless.WMBusConnection; +import org.openmuc.jmbus.wireless.WMBusConnection.WMBusManufacturer; +import org.openmuc.jmbus.wireless.WMBusConnection.WMBusSerialBuilder; +import org.openmuc.jmbus.wireless.WMBusMode; + +/** + * The {@link WMBusBridgeHandler} class defines This class represents the WMBus bridge and handles general events for + * the whole group of WMBus devices. + * + * @author Hanno - Felix Wagner - Initial contribution + */ + +public class WMBusBridgeHandler extends WMBusBridgeHandlerBase { + + private ScheduledFuture initFuture; + private WMBusConnection wmbusConnection; + + public WMBusBridgeHandler(Bridge bridge, KeyStorage keyStorage) { + super(bridge, keyStorage); + } + + @Override + public Collection getConfigStatus() { + List messages = new ArrayList<>(); + WMBusSerialBridgeConfig config = getConfigAs(WMBusSerialBridgeConfig.class); + + // check stick model + if (config.stickModel == null) { + messages.add(ConfigStatusMessage.Builder.error(CONFKEY_STICK_MODEL) + .withMessageKeySuffix(CONFKEY_STICK_MODEL).build()); + } + // check serial device name + if (config.serialDevice == null) { + messages.add(ConfigStatusMessage.Builder.error(CONFKEY_INTERFACE_NAME) + .withMessageKeySuffix(CONFKEY_INTERFACE_NAME).build()); + } + // check radio mode + if (config.radioMode == null) { + messages.add(ConfigStatusMessage.Builder.error(CONFKEY_RADIO_MODE).withMessageKeySuffix(CONFKEY_RADIO_MODE) + .build()); + } + + return messages; + } + + /** + * Connects to the WMBus radio module and updates bridge status. + * + * @see org.openhab.core.thing.binding.BaseThingHandler#initialize() + */ + @Override + public void initialize() { + logger.debug("WMBusBridgeHandler: initialize()"); + + WMBusSerialBridgeConfig config = getConfigAs(WMBusSerialBridgeConfig.class); + updateStatus(ThingStatus.UNKNOWN); + initFuture = scheduler.schedule(() -> { + // set up WMBus receiver = handler for radio telegrams + if (wmbusReceiver == null) { + StickModel stickModel = config.stickModel; + String interfaceName = config.serialDevice; + WMBusMode radioMode = config.radioMode; + + // connect to the radio module / open WMBus connection + logger.debug("Opening wmbus stick {} serial port {} in mode {}", stickModel, interfaceName, radioMode); + + WMBusManufacturer wmBusManufacturer = parseManufacturer(stickModel.name().toUpperCase()); + if (wmBusManufacturer == null) { + logger.error("Cannot open WMBus device. Unknown manufacturer given: " + stickModel + + ". Expected 'amber' or 'imst' or 'rc'."); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, + "Cannot open WMBus device. Unknown manufacturer given: " + stickModel + + ". Expected 'amber' or 'imst' or 'rc'."); + return; + } + logger.debug("Building new connection"); + + wmbusReceiver = new WMBusReceiver(this); + + if (config.deviceIDFilter == null || config.deviceIDFilter.trim().isEmpty()) { + logger.debug("Device ID filter is empty."); + } else { + wmbusReceiver.setFilterIDs(config.getDeviceIDFilter()); + } + + WMBusSerialBuilder connectionBuilder = new WMBusSerialBuilder(wmBusManufacturer, wmbusReceiver, + interfaceName); + + logger.debug("Setting WMBus radio mode to {}", radioMode.toString()); + connectionBuilder.setMode(radioMode); + connectionBuilder.setTimeout(1000); // infinite + // connectionBuilder.setTimeout(0); // infinite + + try { + logger.debug("Building/opening connection"); + logger.debug( + "NOTE: if initialization does not progress from here, check systemd journal for Execptions -- probably native lib still loaded by another ClassLoader = previous version or instance of WMBus binding -> restart OpenHAB"); + if (wmbusConnection != null) { + logger.debug("Connection already set, closing old"); + wmbusConnection.close(); + wmbusConnection = null; + } + wmbusConnection = connectionBuilder.build(); + } catch (IOException e) { + logger.error("Cannot open WMBus device. Connection builder returned: " + e.getMessage()); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, + "Cannot open WMBus device. Connection builder returned: " + e.getMessage()); + return; + } + + logger.debug("Connected to WMBus serial port"); + + // close WMBus connection on shutdown + logger.trace("Setting shutdown hook"); + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + if (wmbusConnection != null) { + try { + logger.debug("Closing connection to WMBus radio device"); + wmbusConnection.close(); + } catch (IOException e) { + logger.error("Cannot close connection to WMBus radio module: " + e.getMessage()); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, + "Cannot close connection to WMBus radio module: " + e.getMessage()); + return; + } + } + } + }); + + // success + logger.debug("WMBusBridgeHandler: Initialization done! Setting bridge online"); + updateStatus(ThingStatus.ONLINE); + } + }, 0, TimeUnit.SECONDS); + } + + private static WMBusManufacturer parseManufacturer(String manufacturer) { + switch (manufacturer.toLowerCase()) { + case MANUFACTURER_AMBER: + return WMBusManufacturer.AMBER; + case MANUFACTURER_RADIO_CRAFTS: + return WMBusManufacturer.RADIO_CRAFTS; + case MANUFACTURER_IMST: + return WMBusManufacturer.IMST; + default: + return null; + } + } + + @Override + public void dispose() { + super.dispose(); + logger.debug("WMBus bridge Handler disposed."); + + if (wmbusConnection != null) { + logger.debug("Close serial device connection"); + try { + wmbusConnection.close(); + } catch (IOException e) { + logger.error("An exception occurred while closing the wmbusConnection", e); + } + wmbusConnection = null; + } + + if (wmbusReceiver != null) { + wmbusReceiver = null; + } + + if (initFuture != null) { + initFuture.cancel(true); + initFuture = null; + } + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusBridgeHandlerBase.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusBridgeHandlerBase.java index 3f36ff0..9e423f1 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusBridgeHandlerBase.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusBridgeHandlerBase.java @@ -1,274 +1,280 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - *

- * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.handler; - -import static org.openhab.binding.wmbus.WMBusBindingConstants.*; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.smarthome.core.common.ThreadPoolManager; -import org.eclipse.smarthome.core.library.types.StringType; -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.ThingUID; -import org.eclipse.smarthome.core.thing.binding.ConfigStatusBridgeHandler; -import org.eclipse.smarthome.core.thing.binding.ThingHandler; -import org.eclipse.smarthome.core.types.Command; -import org.eclipse.smarthome.core.util.HexUtils; -import org.openhab.binding.wmbus.WMBusBindingConstants; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.config.DateFieldMode; -import org.openhab.binding.wmbus.config.WMBusBridgeConfig; -import org.openhab.binding.wmbus.internal.WMBusReceiver; -import org.openhab.io.transport.mbus.wireless.KeyStorage; -import org.openmuc.jmbus.DecodingException; -import org.openmuc.jmbus.wireless.VirtualWMBusMessageHelper; -import org.openmuc.jmbus.wireless.WMBusMessage; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link WMBusBridgeHandlerBase} class defines base operations which are common for all bridge - * handlers such as device and key association. - * - * @author Łukasz Dywicki - Initial contribution, extracted from {@link WMBusBridgeHandler}. - */ -public abstract class WMBusBridgeHandlerBase extends ConfigStatusBridgeHandler implements WMBusAdapter { - - private static final ScheduledExecutorService SCHEDULER = ThreadPoolManager.getScheduledPool("wmbus"); - - private static final String DEVICE_STATE_ADDED = "added"; - private static final String DEVICE_STATE_CHANGED = "changed"; - - protected final Logger logger = LoggerFactory.getLogger(getClass()); - - private final KeyStorage keyStorage; - private final Map knownDevices = new ConcurrentHashMap<>(); - private final Set> handlers = Collections.synchronizedSet(new HashSet<>()); - private final List wmBusMessageListeners = new CopyOnWriteArrayList<>(); - protected WMBusReceiver wmbusReceiver; - private ScheduledFuture statusFuture; - private AtomicBoolean updateFrames = new AtomicBoolean(false); - - public WMBusBridgeHandlerBase(Bridge bridge, KeyStorage keyStorage) { - super(bridge); - this.keyStorage = keyStorage; - this.statusFuture = SCHEDULER.scheduleAtFixedRate(new StatusRunnable(handlers), 60, 60, TimeUnit.SECONDS); - } - - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - // judging from the hue bridge, this seems to be not needed...? - logger.warn("Unexpected command for bridge. Parameters are channelUID={} and command={}", channelUID, command); - } - - @Override - public void dispose() { - if (statusFuture != null) { - statusFuture.cancel(true); - statusFuture = null; - } - } - - public boolean registerWMBusMessageListener(WMBusMessageListener wmBusMessageListener) { - if (wmBusMessageListener == null) { - return false; - } - - logger.trace("register listener: Adding"); - boolean result = wmBusMessageListeners.add(wmBusMessageListener); - logger.trace("register listener: Success"); - if (result) { - // inform the listener initially about all devices and their states - for (WMBusDevice device : knownDevices.values()) { - wmBusMessageListener.onNewWMBusDevice(this, device); - } - } - return result; - } - - /** - * Iterate through wmBusMessageListeners and notify them about a newly received message. - * - * @param device - */ - private void notifyWMBusMessageListeners(final WMBusDevice device, final String type) { - logger.trace("bridge: notify message listeners: sending to all"); - WMBusDevice decrypt = decrypt(device); - - // below we append thing handlers which are configured for given device address - ArrayList listeners = new ArrayList<>(this.wmBusMessageListeners); - handlers.stream().filter(h -> device.getDeviceAddress().equals(h.getDeviceAddress())) - .collect(Collectors.toCollection(() -> listeners)); - - for (WMBusMessageListener wmBusMessageListener : listeners) { - try { - switch (type) { - case DEVICE_STATE_ADDED: { - wmBusMessageListener.onNewWMBusDevice(this, decrypt); - break; - } - case DEVICE_STATE_CHANGED: { - wmBusMessageListener.onChangedWMBusDevice(this, decrypt); - break; - } - default: { - throw new IllegalArgumentException( - "Could not notify wmBusMessageListeners for unknown event type " + type); - } - } - } catch (Exception e) { - logger.error("An exception occurred while notifying the WMBusMessageListener", e); - } - } - - logger.trace("bridge: notify message listeners: return"); - } - - /** - * Because we do not add encryption keys to connection and they are propagated from connection down to received - * frame and its parsing logic we need to inject encryption keys after message is received and before its first use - * to avoid troubles. - * Yes, we do it manually because jmbus does not offer any API/SPI for that. - * - * @param device Incoming frame. - * @return Decrypted frame or original (unencrypted) frame when parsing fails. - */ - protected WMBusDevice decrypt(WMBusDevice device) { - try { - device.decode(); - } catch (DecodingException parseException) { - if (parseException.getMessage().startsWith("Unable to decode encrypted payload")) { - try { - WMBusMessage message = VirtualWMBusMessageHelper - .decode(device.getOriginalMessage().asBlob(), device.getOriginalMessage().getRssi(), - keyStorage.toMap()); - message.getVariableDataResponse().decode(); - logger.info("Message from {} successfully decrypted, forwarding it to receivers", - device.getDeviceAddress()); - return new WMBusDevice(message, this); - } catch (DecodingException decodingException) { - logger.info( - "Could not decode frame, probably we still miss encryption key, forwarding frame in original form. {}", - decodingException.getMessage()); - } - } else if (parseException.getMessage().startsWith("Manufacturer specific CI:") || parseException - .getMessage().startsWith("Unable to decode message with this CI Field")) { - logger.debug("Found frame with manufacturer specific encoding, forwarding for futher processing."); - } else { - logger.debug("Unexpected error while parsing frame, forwarding frame in original form", parseException); - } - } - return device; - } - - @Override - public void processMessage(WMBusDevice device) { - if (updateFrames.get()) { - StringType frame = StringType.valueOf(HexUtils.bytesToHex(device.getOriginalMessage().asBlob())); - getCallback().stateUpdated(new ChannelUID(getUID(), WMBusBindingConstants.CHANNEL_LAST_FRAME), frame); - } - logger.trace("bridge: processMessage begin"); - - String deviceAddress = device.getDeviceAddress(); - String deviceState = DEVICE_STATE_ADDED; - if (knownDevices.containsKey(deviceAddress)) { - deviceState = DEVICE_STATE_CHANGED; - } - knownDevices.put(deviceAddress, device); - logger.trace("bridge processMessage: notifying listeners"); - notifyWMBusMessageListeners(device, deviceState); - logger.trace("bridge: processMessage end"); - } - - public WMBusDevice getDeviceByAddress(String deviceAddress) { - logger.trace("bridge: get device by address: " + deviceAddress); - if (knownDevices.containsKey(deviceAddress)) { - logger.trace("bridge: found device"); - } else { - logger.trace("bridge: device not found"); - } - return knownDevices.get(deviceAddress); - } - - @Override - public ThingUID getUID() { - return getThing().getUID(); - } - - @Override - public void channelLinked(ChannelUID channelUID) { - if (CHANNEL_LAST_FRAME.equals(channelUID.getId())) { - updateFrames.set(true); - } - } - - @Override - public void channelUnlinked(ChannelUID channelUID) { - if (CHANNEL_LAST_FRAME.equals(channelUID.getId())) { - updateFrames.set(false); - } - } - - - @Override - public void childHandlerInitialized(@NonNull ThingHandler childHandler, @NonNull Thing childThing) { - if (childHandler instanceof WMBusDeviceHandler) { - handlers.add((WMBusDeviceHandler) childHandler); - } - } - - @Override - public void childHandlerDisposed(@NonNull ThingHandler childHandler, @NonNull Thing childThing) { - if (childHandler instanceof WMBusDeviceHandler) { - handlers.remove(childHandler); - } - } - - public void reset() { - wmbusReceiver = null; - initialize(); - } - - @Override - public DateFieldMode getDateFieldMode() { - return Optional.ofNullable(getConfigAs(WMBusBridgeConfig.class)) - .map(cfg -> cfg.dateFieldMode) - .orElse(DateFieldMode.DATE_TIME); - } - - static class StatusRunnable implements Runnable { - - private final Set> handlers; - - public StatusRunnable(Set> handlers) { - this.handlers = handlers; - } - - @Override public void run() { - handlers.stream().forEach(WMBusDeviceHandler::checkStatus); - } - - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.handler; + +import static org.openhab.binding.wmbus.WMBusBindingConstants.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNull; +import org.openhab.binding.wmbus.WMBusBindingConstants; +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.config.DateFieldMode; +import org.openhab.binding.wmbus.config.WMBusBridgeConfig; +import org.openhab.binding.wmbus.internal.WMBusReceiver; +import org.openhab.core.common.ThreadPoolManager; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.ConfigStatusBridgeHandler; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.types.Command; +import org.openhab.core.util.HexUtils; +import org.openhab.io.transport.mbus.wireless.KeyStorage; +import org.openmuc.jmbus.DecodingException; +import org.openmuc.jmbus.wireless.VirtualWMBusMessageHelper; +import org.openmuc.jmbus.wireless.WMBusMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link WMBusBridgeHandlerBase} class defines base operations which are common for all bridge + * handlers such as device and key association. + * + * @author Łukasz Dywicki - Initial contribution, extracted from {@link WMBusBridgeHandler}. + */ +public abstract class WMBusBridgeHandlerBase extends ConfigStatusBridgeHandler implements WMBusAdapter { + + private static final ScheduledExecutorService SCHEDULER = ThreadPoolManager.getScheduledPool("wmbus"); + + private static final String DEVICE_STATE_ADDED = "added"; + private static final String DEVICE_STATE_CHANGED = "changed"; + + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + private final KeyStorage keyStorage; + private final Map knownDevices = new ConcurrentHashMap<>(); + private final Set> handlers = Collections.synchronizedSet(new HashSet<>()); + private final List wmBusMessageListeners = new CopyOnWriteArrayList<>(); + protected WMBusReceiver wmbusReceiver; + private ScheduledFuture statusFuture; + private AtomicBoolean updateFrames = new AtomicBoolean(false); + + public WMBusBridgeHandlerBase(Bridge bridge, KeyStorage keyStorage) { + super(bridge); + this.keyStorage = keyStorage; + this.statusFuture = SCHEDULER.scheduleAtFixedRate(new StatusRunnable(handlers), 60, 60, TimeUnit.SECONDS); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + // judging from the hue bridge, this seems to be not needed...? + logger.warn("Unexpected command for bridge. Parameters are channelUID={} and command={}", channelUID, command); + } + + @Override + public void dispose() { + if (statusFuture != null) { + statusFuture.cancel(true); + statusFuture = null; + } + } + + public boolean registerWMBusMessageListener(WMBusMessageListener wmBusMessageListener) { + if (wmBusMessageListener == null) { + return false; + } + + logger.trace("register listener: Adding"); + boolean result = wmBusMessageListeners.add(wmBusMessageListener); + logger.trace("register listener: Success"); + if (result) { + // inform the listener initially about all devices and their states + for (WMBusDevice device : knownDevices.values()) { + wmBusMessageListener.onNewWMBusDevice(this, device); + } + } + return result; + } + + /** + * Iterate through wmBusMessageListeners and notify them about a newly received message. + * + * @param device + */ + private void notifyWMBusMessageListeners(final WMBusDevice device, final String type) { + logger.trace("bridge: notify message listeners: sending to all"); + WMBusDevice decrypt = decrypt(device); + + // below we append thing handlers which are configured for given device address + ArrayList listeners = new ArrayList<>(this.wmBusMessageListeners); + handlers.stream().filter(h -> device.getDeviceAddress().equals(h.getDeviceAddress())) + .collect(Collectors.toCollection(() -> listeners)); + + for (WMBusMessageListener wmBusMessageListener : listeners) { + try { + switch (type) { + case DEVICE_STATE_ADDED: { + wmBusMessageListener.onNewWMBusDevice(this, decrypt); + break; + } + case DEVICE_STATE_CHANGED: { + wmBusMessageListener.onChangedWMBusDevice(this, decrypt); + break; + } + default: { + throw new IllegalArgumentException( + "Could not notify wmBusMessageListeners for unknown event type " + type); + } + } + } catch (Exception e) { + logger.error("An exception occurred while notifying the WMBusMessageListener", e); + } + } + + logger.trace("bridge: notify message listeners: return"); + } + + /** + * Because we do not add encryption keys to connection and they are propagated from connection down to received + * frame and its parsing logic we need to inject encryption keys after message is received and before its first use + * to avoid troubles. + * Yes, we do it manually because jmbus does not offer any API/SPI for that. + * + * @param device Incoming frame. + * @return Decrypted frame or original (unencrypted) frame when parsing fails. + */ + protected WMBusDevice decrypt(WMBusDevice device) { + try { + device.decode(); + } catch (DecodingException parseException) { + if (parseException.getMessage().startsWith("Unable to decode encrypted payload")) { + try { + WMBusMessage message = VirtualWMBusMessageHelper.decode(device.getOriginalMessage().asBlob(), + device.getOriginalMessage().getRssi(), keyStorage.toMap()); + message.getVariableDataResponse().decode(); + logger.info("Message from {} successfully decrypted, forwarding it to receivers", + device.getDeviceAddress()); + return new WMBusDevice(message, this); + } catch (DecodingException decodingException) { + logger.info( + "Could not decode frame, probably we still miss encryption key, forwarding frame in original form. {}, {}, {}", + decodingException.getMessage(), device.getOriginalMessage().toString(), + keyStorage.toMap().toString()); + } catch (NoClassDefFoundError decodingException) { + logger.info( + "Could not decode frame, probably we still miss encryption key, forwarding frame in original form. {}", + decodingException.getMessage()); + } + } else if (parseException.getMessage().startsWith("Manufacturer specific CI:") + || parseException.getMessage().startsWith("Unable to decode message with this CI Field")) { + logger.debug("Found frame with manufacturer specific encoding, forwarding for futher processing."); + } else { + logger.debug("Unexpected error while parsing frame, forwarding frame in original form", parseException); + } + } + return device; + } + + @Override + public void processMessage(WMBusDevice device) { + if (updateFrames.get()) { + StringType frame = StringType.valueOf(HexUtils.bytesToHex(device.getOriginalMessage().asBlob())); + getCallback().stateUpdated(new ChannelUID(getUID(), WMBusBindingConstants.CHANNEL_LAST_FRAME), frame); + } + logger.trace("bridge: processMessage begin"); + + String deviceAddress = device.getDeviceAddress(); + String deviceState = DEVICE_STATE_ADDED; + if (knownDevices.containsKey(deviceAddress)) { + deviceState = DEVICE_STATE_CHANGED; + } + knownDevices.put(deviceAddress, device); + logger.trace("bridge processMessage: notifying listeners"); + notifyWMBusMessageListeners(device, deviceState); + logger.trace("bridge: processMessage end"); + } + + public WMBusDevice getDeviceByAddress(String deviceAddress) { + logger.trace("bridge: get device by address: " + deviceAddress); + if (knownDevices.containsKey(deviceAddress)) { + logger.trace("bridge: found device"); + } else { + logger.trace("bridge: device not found"); + } + return knownDevices.get(deviceAddress); + } + + @Override + public ThingUID getUID() { + return getThing().getUID(); + } + + @Override + public void channelLinked(ChannelUID channelUID) { + if (CHANNEL_LAST_FRAME.equals(channelUID.getId())) { + updateFrames.set(true); + } + } + + @Override + public void channelUnlinked(ChannelUID channelUID) { + if (CHANNEL_LAST_FRAME.equals(channelUID.getId())) { + updateFrames.set(false); + } + } + + @Override + public void childHandlerInitialized(@NonNull ThingHandler childHandler, @NonNull Thing childThing) { + if (childHandler instanceof WMBusDeviceHandler) { + handlers.add((WMBusDeviceHandler) childHandler); + } + } + + @Override + public void childHandlerDisposed(@NonNull ThingHandler childHandler, @NonNull Thing childThing) { + if (childHandler instanceof WMBusDeviceHandler) { + handlers.remove(childHandler); + } + } + + public void reset() { + wmbusReceiver = null; + initialize(); + } + + @Override + public DateFieldMode getDateFieldMode() { + return Optional.ofNullable(getConfigAs(WMBusBridgeConfig.class)).map(cfg -> cfg.dateFieldMode) + .orElse(DateFieldMode.DATE_TIME); + } + + static class StatusRunnable implements Runnable { + + private final Set> handlers; + + public StatusRunnable(Set> handlers) { + this.handlers = handlers; + } + + @Override + public void run() { + handlers.stream().forEach(WMBusDeviceHandler::checkStatus); + } + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusDeviceHandler.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusDeviceHandler.java index 2b0a393..35d7ec1 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusDeviceHandler.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusDeviceHandler.java @@ -1,310 +1,314 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ - -package org.openhab.binding.wmbus.handler; - -import static org.openhab.binding.wmbus.WMBusBindingConstants.*; - -import java.math.BigDecimal; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.time.temporal.ChronoUnit; -import java.util.Date; -import java.util.Optional; -import java.util.concurrent.TimeUnit; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.smarthome.config.core.Configuration; -import org.eclipse.smarthome.core.library.types.DateTimeType; -import org.eclipse.smarthome.core.library.types.DecimalType; -import org.eclipse.smarthome.core.library.types.StringType; -import org.eclipse.smarthome.core.thing.Bridge; -import org.eclipse.smarthome.core.thing.Channel; -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.ThingHandler; -import org.eclipse.smarthome.core.types.RefreshType; -import org.eclipse.smarthome.core.types.State; -import org.eclipse.smarthome.core.util.HexUtils; -import org.openhab.binding.wmbus.WMBusBindingConstants; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.config.DateFieldMode; -import org.openhab.binding.wmbus.internal.WMBusException; -import org.openhab.io.transport.mbus.wireless.KeyStorage; -import org.openhab.io.transport.mbus.wireless.MapKeyStorage; -import org.openmuc.jmbus.DataRecord; -import org.openmuc.jmbus.DecodingException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link WMBusDeviceHandler} class defines abstract WMBusDeviceHandler - * - * @author Hanno - Felix Wagner - Initial contribution - * @author Łukasz Dywicki - Added possibility to customize message parsing. - */ - -public abstract class WMBusDeviceHandler extends BaseThingHandler - implements WMBusMessageListener { - - private final Logger logger = LoggerFactory.getLogger(WMBusDeviceHandler.class); - private final KeyStorage keyStorage; - - protected String deviceAddress; - private WMBusBridgeHandlerBase bridgeHandler; - protected T wmbusDevice; - protected Long lastUpdate; - private Long frequencyOfUpdates = WMBusBindingConstants.DEFAULT_DEVICE_FREQUENCY_OF_UPDATES; - private ThingStatus status; - - protected WMBusDeviceHandler(Thing thing) { - this(thing, new MapKeyStorage()); - } - - public WMBusDeviceHandler(Thing thing, KeyStorage keyStorage) { - super(thing); - this.keyStorage = keyStorage; - logger.debug("Created new handler for thing {}", thing.getUID()); - } - - // entry point - gets device here - @Override - public void onNewWMBusDevice(WMBusAdapter adapter, WMBusDevice wmBusDevice) { - logger.trace("onNewWMBusDevice(): is it me?"); - if (wmBusDevice.getDeviceAddress().equals(deviceAddress)) { - logger.trace("onNewWMBusDevice(): yes it's me"); - logger.trace("onNewWMBusDevice(): calling onChangedWMBusDevice()"); - onChangedWMBusDevice(adapter, wmBusDevice); - } - logger.trace("onNewWMBusDevice(): no"); - } - - @Override - public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice receivedDevice) { - logger.trace("onChangedWMBusDevice(): is it me?"); - if (receivedDevice.getDeviceAddress().equals(deviceAddress)) { - logger.trace("onChangedWMBusDevice(): yes"); - // in between the good messages, there are messages with invalid values -> filter these out - if (!checkMessage(receivedDevice)) { - logger.trace("onChangedWMBusDevice(): this is a malformed message, ignoring this message"); - } else { - try { - wmbusDevice = parseDevice(receivedDevice); - logger.trace("onChangedWMBusDevice(): updating status to online"); - updateStatus(ThingStatus.ONLINE); - - if (wmbusDevice != null) { - logger.trace("onChangedWMBusDevice(): inform all channels to refresh"); - triggerRefresh(); - } - } catch (DecodingException e) { - // FIXME decoding exception should not be necessary over time - logger.debug("onChangedWMBusDevice(): could not decode frame {} because {}. Detail: {}", - HexUtils.bytesToHex(receivedDevice.getOriginalMessage().asBlob()), e.getMessage(), - receivedDevice.getOriginalMessage()); - } - } - } else { - logger.trace("onChangedWMBusDevice(): no"); - } - logger.trace("onChangedWMBusDevice(): return"); - } - - @Override - protected void updateStatus(@NonNull ThingStatus status, @NonNull ThingStatusDetail statusDetail, - @Nullable String description) { - super.updateStatus(status, statusDetail, description); - this.status = status; - } - - protected void triggerRefresh() { - lastUpdate = System.currentTimeMillis(); - - for (Channel curChan : getThing().getChannels()) { - handleCommand(curChan.getUID(), RefreshType.REFRESH); - } - } - - protected State convertRecordData(DataRecord record) { - - switch (record.getDataValueType()) { - case LONG: - case DOUBLE: - case BCD: - return new DecimalType(record.getScaledDataValue()); - case DATE: - return convertDate(record.getDataValue()); - case STRING: - case NONE: - return new StringType(record.getDataValue().toString()); - } - return null; - } - - protected DateFieldMode getDateFieldMode() { - return getBridgeHandler().getDateFieldMode(); - } - - protected State convertDate(Object input) { - DateTimeType value = null; - if (input instanceof Date) { - Date date = (Date) input; - ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); - zonedDateTime.truncatedTo(ChronoUnit.SECONDS); // throw away millisecond value to avoid, eg. _previous_date - // changed from 2018-02-28T00:00:00.353+0100 to - // 2018-02-28T00:00:00.159+0100 - value = new DateTimeType(zonedDateTime); - } - if (input instanceof DateTimeType) { - value = (DateTimeType) input; - } - - if (value == null) { - return null; - } - - switch (getDateFieldMode()) { - case FORMATTED_STRING: - return new StringType(value.format(null)); - case UNIX_TIMESTAMP: - return new DecimalType(value.getZonedDateTime().toEpochSecond()); - default: - return value; - } - } - - @Override - public void initialize() { - logger.debug("Initializing handler."); - updateStatus(ThingStatus.UNKNOWN); - - Configuration config = getConfig(); - deviceAddress = (String) config.getProperties().get(PROPERTY_DEVICE_ADDRESS); - - if (deviceAddress == null || deviceAddress.trim().isEmpty()) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, - "Missing device address, please update."); - return; - } - - try { - wmbusDevice = getDevice(); - if (wmbusDevice != null) { - initialize(wmbusDevice); - } - } catch (WMBusException e) { - logger.error("Could not obtain Wireless M-Bus device information", e); - } - - Long updateFrequency = Optional.of(config.getProperties()) - .map(cfg -> cfg.get(PROPERTY_DEVICE_FREQUENCY_OF_UPDATES)) // - .filter(BigDecimal.class::isInstance) // - .map(BigDecimal.class::cast) // - .map(BigDecimal::longValue) // - .orElse(DEFAULT_DEVICE_FREQUENCY_OF_UPDATES); - this.frequencyOfUpdates = TimeUnit.MINUTES.toMillis(updateFrequency); - - if (Boolean.valueOf(thing.getProperties().get(PROPERTY_DEVICE_ENCRYPTED))) { - Optional encryptionKey = Optional.of(config.getProperties()) // - .map(cfg -> cfg.get(PROPERTY_DEVICE_ENCRYPTION_KEY)) // - .filter(String.class::isInstance) // - .map(String.class::cast) // - .map(HexUtils::hexToBytes); - if (!encryptionKey.isPresent()) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, - "Please provide encryption key to read device communication."); - } - encryptionKey.ifPresent(key -> keyStorage.registerKey(HexUtils.hexToBytes(deviceAddress), key)); - } - } - - protected void initialize(T device) { - updateStatus(ThingStatus.ONLINE); - } - - @Override - public void dispose() { - logger.debug("Disposing handler."); - this.deviceAddress = null; - this.wmbusDevice = null; - } - - protected synchronized WMBusBridgeHandlerBase getBridgeHandler() { - logger.trace("getBridgeHandler() begin"); - if (bridgeHandler == null) { - Bridge bridge = getBridge(); - if (bridge == null) { - return null; - } - ThingHandler handler = bridge.getHandler(); - if (handler instanceof WMBusBridgeHandlerBase) { - bridgeHandler = (WMBusBridgeHandlerBase) handler; - } else { - return null; - } - } - logger.trace("getBridgeHandler() returning bridgehandler"); - return bridgeHandler; - } - - protected T getDevice() throws WMBusException { - logger.trace("getDevice() begin"); - WMBusBridgeHandlerBase bridgeHandler = getBridgeHandler(); - if (bridgeHandler == null) { - logger.debug("Device handler is not linked with bridge, skipping call"); - return null; - } - - logger.trace("Lookup known devices by address {}", deviceAddress); - WMBusDevice device = bridgeHandler.getDeviceByAddress(deviceAddress); - if (device != null) { - logger.trace("Found device matching given addresss {}, {}", deviceAddress, device); - try { - return parseDevice(device); - } catch (DecodingException e) { - logger.trace("Unable to parse received message {}", device, e); - return null; - } - } - - logger.trace("Couldn't find device matching address {}", deviceAddress); - return null; - } - - boolean checkMessage(WMBusDevice receivedDevice) { - return true; - } - - public void checkStatus() { - // status check is relevant only if device is considered to be online - we determine if it should be marked - // offline - if (this.status != ThingStatus.ONLINE) { - return; - } - - long currentTime = System.currentTimeMillis(); - if (lastUpdate == null || lastUpdate + frequencyOfUpdates <= currentTime) { - logger.info("WMBus device was not seen since {}, marking it as offline", new Date(lastUpdate)); - updateStatus(ThingStatus.OFFLINE); - } - } - - @SuppressWarnings("unchecked") - protected T parseDevice(WMBusDevice device) throws DecodingException { - device.decode(); - return (T) device; - } - - public String getDeviceAddress() { - return deviceAddress; - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.handler; + +import static org.openhab.binding.wmbus.WMBusBindingConstants.*; + +import java.math.BigDecimal; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Date; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.wmbus.WMBusBindingConstants; +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.config.DateFieldMode; +import org.openhab.binding.wmbus.internal.WMBusException; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.library.types.DateTimeType; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; +import org.openhab.core.util.HexUtils; +import org.openhab.io.transport.mbus.wireless.KeyStorage; +import org.openhab.io.transport.mbus.wireless.MapKeyStorage; +import org.openmuc.jmbus.DataRecord; +import org.openmuc.jmbus.DecodingException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link WMBusDeviceHandler} class defines abstract WMBusDeviceHandler + * + * @author Hanno - Felix Wagner - Initial contribution + * @author Łukasz Dywicki - Added possibility to customize message parsing. + */ + +public abstract class WMBusDeviceHandler extends BaseThingHandler + implements WMBusMessageListener { + + private final Logger logger = LoggerFactory.getLogger(WMBusDeviceHandler.class); + private final KeyStorage keyStorage; + + protected String deviceAddress; + private WMBusBridgeHandlerBase bridgeHandler; + protected T wmbusDevice; + protected Long lastUpdate; + private Long frequencyOfUpdates = WMBusBindingConstants.DEFAULT_DEVICE_FREQUENCY_OF_UPDATES; + private ThingStatus status; + + protected WMBusDeviceHandler(Thing thing) { + this(thing, new MapKeyStorage()); + } + + public WMBusDeviceHandler(Thing thing, KeyStorage keyStorage) { + super(thing); + this.keyStorage = keyStorage; + logger.debug("Created new handler for thing {}", thing.getUID()); + } + + // entry point - gets device here + @Override + public void onNewWMBusDevice(WMBusAdapter adapter, WMBusDevice wmBusDevice) { + logger.trace("onNewWMBusDevice(): is it me?"); + if (wmBusDevice.getDeviceAddress().equals(deviceAddress)) { + logger.trace("onNewWMBusDevice(): yes it's me"); + logger.trace("onNewWMBusDevice(): calling onChangedWMBusDevice()"); + onChangedWMBusDevice(adapter, wmBusDevice); + } + logger.trace("onNewWMBusDevice(): no"); + } + + @Override + public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice receivedDevice) { + logger.trace("onChangedWMBusDevice(): is it me?"); + if (receivedDevice.getDeviceAddress().equals(deviceAddress)) { + logger.trace("onChangedWMBusDevice(): yes"); + // in between the good messages, there are messages with invalid values -> filter these out + if (!checkMessage(receivedDevice)) { + logger.trace("onChangedWMBusDevice(): this is a malformed message, ignoring this message"); + } else { + try { + wmbusDevice = parseDevice(receivedDevice); + logger.trace("onChangedWMBusDevice(): updating status to online"); + updateStatus(ThingStatus.ONLINE); + + if (wmbusDevice != null) { + logger.trace("onChangedWMBusDevice(): inform all channels to refresh"); + triggerRefresh(); + } + } catch (DecodingException e) { + // FIXME decoding exception should not be necessary over time + logger.debug("onChangedWMBusDevice(): could not decode frame {} because {}. Detail: {}", + HexUtils.bytesToHex(receivedDevice.getOriginalMessage().asBlob()), e.getMessage(), + receivedDevice.getOriginalMessage()); + } + } + } else { + logger.trace("onChangedWMBusDevice(): no"); + } + logger.trace("onChangedWMBusDevice(): return"); + } + + @Override + protected void updateStatus(@NonNull ThingStatus status, @NonNull ThingStatusDetail statusDetail, + @Nullable String description) { + super.updateStatus(status, statusDetail, description); + this.status = status; + } + + protected void triggerRefresh() { + lastUpdate = System.currentTimeMillis(); + + for (Channel curChan : getThing().getChannels()) { + handleCommand(curChan.getUID(), RefreshType.REFRESH); + } + } + + protected State convertRecordData(DataRecord record) { + + switch (record.getDataValueType()) { + case LONG: + case DOUBLE: + case BCD: + return new DecimalType(record.getScaledDataValue()); + case DATE: + return convertDate(record.getDataValue()); + case STRING: + case NONE: + return new StringType(record.getDataValue().toString()); + } + return null; + } + + protected DateFieldMode getDateFieldMode() { + return getBridgeHandler().getDateFieldMode(); + } + + protected State convertDate(Object input) { + DateTimeType value = null; + if (input instanceof Date) { + Date date = (Date) input; + ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); + zonedDateTime.truncatedTo(ChronoUnit.SECONDS); // throw away millisecond value to avoid, eg. _previous_date + // changed from 2018-02-28T00:00:00.353+0100 to + // 2018-02-28T00:00:00.159+0100 + value = new DateTimeType(zonedDateTime); + } + if (input instanceof DateTimeType) { + value = (DateTimeType) input; + } + + if (value == null) { + return null; + } + + switch (getDateFieldMode()) { + case FORMATTED_STRING: + return new StringType(value.format(null)); + case UNIX_TIMESTAMP: + return new DecimalType(value.getZonedDateTime().toEpochSecond()); + default: + return value; + } + } + + @Override + public void initialize() { + logger.debug("Initializing handler."); + updateStatus(ThingStatus.UNKNOWN); + + Configuration config = getConfig(); + deviceAddress = (String) config.getProperties().get(PROPERTY_DEVICE_ADDRESS); + + if (deviceAddress == null || deviceAddress.trim().isEmpty()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, + "Missing device address, please update."); + return; + } + + try { + wmbusDevice = getDevice(); + if (wmbusDevice != null) { + initialize(wmbusDevice); + } + } catch (WMBusException e) { + logger.error("Could not obtain Wireless M-Bus device information", e); + } + + Long updateFrequency = Optional.of(config.getProperties()) + .map(cfg -> cfg.get(PROPERTY_DEVICE_FREQUENCY_OF_UPDATES)) // + .filter(BigDecimal.class::isInstance) // + .map(BigDecimal.class::cast) // + .map(BigDecimal::longValue) // + .orElse(DEFAULT_DEVICE_FREQUENCY_OF_UPDATES); + this.frequencyOfUpdates = TimeUnit.MINUTES.toMillis(updateFrequency); + + if (Boolean.valueOf(thing.getProperties().get(PROPERTY_DEVICE_ENCRYPTED))) { + Optional encryptionKey = Optional.of(config.getProperties()) // + .map(cfg -> cfg.get(PROPERTY_DEVICE_ENCRYPTION_KEY)) // + .filter(String.class::isInstance) // + .map(String.class::cast) // + .map(HexUtils::hexToBytes); + if (!encryptionKey.isPresent()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, + "Please provide encryption key to read device communication."); + } + encryptionKey.ifPresent(key -> keyStorage.registerKey(HexUtils.hexToBytes(deviceAddress), key)); + } + } + + protected void initialize(T device) { + updateStatus(ThingStatus.ONLINE); + } + + @Override + public void dispose() { + logger.debug("Disposing handler."); + this.deviceAddress = null; + this.wmbusDevice = null; + } + + protected synchronized WMBusBridgeHandlerBase getBridgeHandler() { + logger.trace("getBridgeHandler() begin"); + if (bridgeHandler == null) { + Bridge bridge = getBridge(); + if (bridge == null) { + return null; + } + ThingHandler handler = bridge.getHandler(); + if (handler instanceof WMBusBridgeHandlerBase) { + bridgeHandler = (WMBusBridgeHandlerBase) handler; + } else { + return null; + } + } + logger.trace("getBridgeHandler() returning bridgehandler"); + return bridgeHandler; + } + + protected T getDevice() throws WMBusException { + logger.trace("getDevice() begin"); + WMBusBridgeHandlerBase bridgeHandler = getBridgeHandler(); + if (bridgeHandler == null) { + logger.debug("Device handler is not linked with bridge, skipping call"); + return null; + } + + logger.trace("Lookup known devices by address {}", deviceAddress); + WMBusDevice device = bridgeHandler.getDeviceByAddress(deviceAddress); + if (device != null) { + logger.trace("Found device matching given addresss {}, {}", deviceAddress, device); + try { + return parseDevice(device); + } catch (DecodingException e) { + logger.trace("Unable to parse received message {}", device, e); + return null; + } + } + + logger.trace("Couldn't find device matching address {}", deviceAddress); + return null; + } + + boolean checkMessage(WMBusDevice receivedDevice) { + return true; + } + + public void checkStatus() { + // status check is relevant only if device is considered to be online - we determine if it should be marked + // offline + if (this.status != ThingStatus.ONLINE) { + return; + } + + long currentTime = System.currentTimeMillis(); + if (lastUpdate == null || lastUpdate + frequencyOfUpdates <= currentTime) { + logger.info("WMBus device was not seen since {}, marking it as offline", new Date(lastUpdate)); + updateStatus(ThingStatus.OFFLINE); + } + } + + @SuppressWarnings("unchecked") + protected T parseDevice(WMBusDevice device) throws DecodingException { + device.decode(); + return (T) device; + } + + public String getDeviceAddress() { + return deviceAddress; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusMessageListener.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusMessageListener.java index 4ee1fb0..6c73791 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusMessageListener.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusMessageListener.java @@ -1,36 +1,39 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ - -package org.openhab.binding.wmbus.handler; - -import org.openhab.binding.wmbus.WMBusDevice; - -/** - * The {@link WMBusMessageListener} class defines interface WMBusMessageListener - * - * @author Hanno - Felix Wagner - Initial contribution - */ - -public interface WMBusMessageListener { - - /** - * - * @param adapter Adapter which received message. - * @param device The message which was received. - */ - public void onNewWMBusDevice(WMBusAdapter adapter, WMBusDevice device); - - /** - * - * @param adapter WMBusAdapter adapter who discovered change, - * @param device The message which was received. - */ - public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice device); - -} +/** + * Copyright (c) 2010-2021 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.wmbus.handler; + +import org.openhab.binding.wmbus.WMBusDevice; + +/** + * The {@link WMBusMessageListener} class defines interface WMBusMessageListener + * + * @author Hanno - Felix Wagner - Initial contribution + */ + +public interface WMBusMessageListener { + + /** + * + * @param adapter Adapter which received message. + * @param device The message which was received. + */ + public void onNewWMBusDevice(WMBusAdapter adapter, WMBusDevice device); + + /** + * + * @param adapter WMBusAdapter adapter who discovered change, + * @param device The message which was received. + */ + public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice device); +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/DynamicBindingConfiguration.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/DynamicBindingConfiguration.java index c01c81c..25ea27f 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/DynamicBindingConfiguration.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/DynamicBindingConfiguration.java @@ -1,93 +1,94 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.internal; - -import java.util.Map; - -import org.openhab.binding.wmbus.BindingConfiguration; -import org.openhab.binding.wmbus.WMBusBindingConstants; -import org.osgi.service.component.ComponentContext; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Modified; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Implementation of configuration which rely on OSGi configuration admin and keep updating of time to live once its - * set. - * - * @author Łukasz Dywicki - Initial contribution. - */ -@Component(service = BindingConfiguration.class, configurationPid = "binding.wmbus") -public class DynamicBindingConfiguration implements BindingConfiguration { - - private final Logger logger = LoggerFactory.getLogger(DynamicBindingConfiguration.class); - private Long timeToLive = WMBusBindingConstants.DEFAULT_TIME_TO_LIVE; - private Boolean includeBridgeUID = false; - - @Activate - public void activate(ComponentContext context) { - setTimeToLive(context.getProperties().get(WMBusBindingConstants.CONFKEY_BINDING_TIME_TO_LIVE)); - setIncludeBridgeUID(context.getProperties().get(WMBusBindingConstants.CONFKEY_BINDING_INCLUDE_BRIDGE_UID)); - } - - private void setTimeToLive(Object value) { - if (value == null) { - logger.debug("Setting up time to live to default value"); - this.timeToLive = WMBusBindingConstants.DEFAULT_TIME_TO_LIVE; - return; - } - - logger.debug("Setting up time to live to new value {}", value); - if (value instanceof Long) { - this.timeToLive = (Long) value; - } - - if (value instanceof String) { - this.timeToLive = Long.parseLong((String) value); - } - - } - - @Override - public Long getTimeToLive() { - return timeToLive; - } - - private void setIncludeBridgeUID(Object value) { - if (value == null) { - logger.debug("Setting up includeBridgeUID to default value"); - this.includeBridgeUID = false; - return; - } - - logger.debug("Setting up includeBridgeUID to new value {}", value); - if (value instanceof Boolean) { - this.includeBridgeUID = (Boolean) value; - } - - if (value instanceof String) { - this.includeBridgeUID = Boolean.parseBoolean((String) value); - } - - } - - @Override - public Boolean getIncludeBridgeUID() { - return includeBridgeUID; - } - - @Modified - void updated(Map configuration) { - setTimeToLive(configuration.get(WMBusBindingConstants.CONFKEY_BINDING_TIME_TO_LIVE)); - setIncludeBridgeUID(configuration.get(WMBusBindingConstants.CONFKEY_BINDING_INCLUDE_BRIDGE_UID)); - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.internal; + +import java.util.Map; + +import org.openhab.binding.wmbus.BindingConfiguration; +import org.openhab.binding.wmbus.WMBusBindingConstants; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Modified; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implementation of configuration which rely on OSGi configuration admin and keep updating of time to live once its + * set. + * + * @author Łukasz Dywicki - Initial contribution. + */ +@Component(service = BindingConfiguration.class, configurationPid = "binding.wmbus") +public class DynamicBindingConfiguration implements BindingConfiguration { + + private final Logger logger = LoggerFactory.getLogger(DynamicBindingConfiguration.class); + private Long timeToLive = WMBusBindingConstants.DEFAULT_TIME_TO_LIVE; + private Boolean includeBridgeUID = false; + + @Activate + public void activate(ComponentContext context) { + setTimeToLive(context.getProperties().get(WMBusBindingConstants.CONFKEY_BINDING_TIME_TO_LIVE)); + setIncludeBridgeUID(context.getProperties().get(WMBusBindingConstants.CONFKEY_BINDING_INCLUDE_BRIDGE_UID)); + } + + private void setTimeToLive(Object value) { + if (value == null) { + logger.debug("Setting up time to live to default value"); + this.timeToLive = WMBusBindingConstants.DEFAULT_TIME_TO_LIVE; + return; + } + + logger.debug("Setting up time to live to new value {}", value); + if (value instanceof Long) { + this.timeToLive = (Long) value; + } + + if (value instanceof String) { + this.timeToLive = Long.parseLong((String) value); + } + } + + @Override + public Long getTimeToLive() { + return timeToLive; + } + + private void setIncludeBridgeUID(Object value) { + if (value == null) { + logger.debug("Setting up includeBridgeUID to default value"); + this.includeBridgeUID = false; + return; + } + + logger.debug("Setting up includeBridgeUID to new value {}", value); + if (value instanceof Boolean) { + this.includeBridgeUID = (Boolean) value; + } + + if (value instanceof String) { + this.includeBridgeUID = Boolean.parseBoolean((String) value); + } + } + + @Override + public Boolean getIncludeBridgeUID() { + return includeBridgeUID; + } + + @Modified + void updated(Map configuration) { + setTimeToLive(configuration.get(WMBusBindingConstants.CONFKEY_BINDING_TIME_TO_LIVE)); + setIncludeBridgeUID(configuration.get(WMBusBindingConstants.CONFKEY_BINDING_INCLUDE_BRIDGE_UID)); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/HexConverter.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/HexConverter.java index b14f849..c595c25 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/HexConverter.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/HexConverter.java @@ -1,30 +1,34 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ - -package org.openhab.binding.wmbus.internal; - -/** - * The {@link HexConverter} class defines HexConverter - * - * @author Hanno - Felix Wagner - Initial contribution - */ - -class HexConverter { - private final static char[] hexArray = "0123456789ABCDEF".toCharArray(); - - static String bytesToHex(byte[] bytes) { - char[] hexChars = new char[bytes.length * 2]; - for (int j = 0; j < bytes.length; j++) { - int v = bytes[j] & 0xFF; - hexChars[j * 2] = hexArray[v >>> 4]; - hexChars[j * 2 + 1] = hexArray[v & 0x0F]; - } - return new String(hexChars); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.internal; + +/** + * The {@link HexConverter} class defines HexConverter + * + * @author Hanno - Felix Wagner - Initial contribution + */ + +class HexConverter { + private final static char[] hexArray = "0123456789ABCDEF".toCharArray(); + + static String bytesToHex(byte[] bytes) { + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusChannelTypeProvider.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusChannelTypeProvider.java index b2faf47..19b51ce 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusChannelTypeProvider.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusChannelTypeProvider.java @@ -1,236 +1,252 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.internal; - -import java.util.Collection; -import java.util.Collections; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import javax.measure.Unit; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.smarthome.core.library.CoreItemFactory; -import org.eclipse.smarthome.core.thing.type.ChannelKind; -import org.eclipse.smarthome.core.thing.type.ChannelType; -import org.eclipse.smarthome.core.thing.type.ChannelTypeProvider; -import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; -import org.eclipse.smarthome.core.types.EventDescription; -import org.eclipse.smarthome.core.types.StateDescription; -import org.eclipse.smarthome.core.util.HexUtils; -import org.openhab.binding.wmbus.UnitRegistry; -import org.openhab.binding.wmbus.WMBusBindingConstants; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.config.DateFieldMode; -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openhab.binding.wmbus.handler.WMBusMessageListener; -import org.openmuc.jmbus.DataRecord; -import org.openmuc.jmbus.DataRecord.DataValueType; -import org.openmuc.jmbus.DataRecord.Description; -import org.openmuc.jmbus.DataRecord.FunctionField; -import org.openmuc.jmbus.DlmsUnit; -import org.openmuc.jmbus.VariableDataStructure; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Reference; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Dynamic channel type provider which uses received wmbus frames to create channel types. - * - * Because wmbus frames contain variable data part which have many mutations it is not possible to create a static - * configuration which would cover all combinations. Fields which are multipliers are: - * - dib (inst/min/max/error val) - * - vib (subunit, tariff, storage number, vif) - * - * While most of devices uses just small subset of values its not possible to predict all variations of above. - * - * @author Łukasz Dywicki - Initial contribution. - */ -@Component(service = { ChannelTypeProvider.class, WMBusMessageListener.class, WMBusChannelTypeProvider.class }) -public class WMBusChannelTypeProvider implements ChannelTypeProvider, WMBusMessageListener { - - private final Logger logger = LoggerFactory.getLogger(WMBusChannelTypeProvider.class); - - private final Map wmbusChannelMap = new ConcurrentHashMap<>(); - private UnitRegistry unitRegistry; - - @Override - public Collection getChannelTypes(@Nullable Locale locale) { - return wmbusChannelMap.values(); - } - - @Override - public @Nullable ChannelType getChannelType(ChannelTypeUID channelTypeUID, @Nullable Locale locale) { - return wmbusChannelMap.values().stream().filter(channelType -> channelType.getUID().equals(channelTypeUID)) - .findFirst().orElse(null); - } - - @Override - public void onNewWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { - calculateChannelTypes(device); - } - - @Override - public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { - calculateChannelTypes(device); - } - - private void calculateChannelTypes(WMBusDevice device) { - VariableDataStructure response = device.getOriginalMessage().getVariableDataResponse(); - DateFieldMode dateFieldMode = device.getAdapter().getDateFieldMode(); - - for (DataRecord record : response.getDataRecords()) { - Optional channelTypeUID = getChannelType(record); - if (channelTypeUID.isPresent()) { - ChannelTypeUID typeUID = channelTypeUID.get(); - if (!wmbusChannelMap.containsKey(typeUID.getId())) { - Optional> unit = unitRegistry.lookup(record.getUnit()); - String label = getFunction(record.getFunctionField()) + " "; - label += record.getDescription().name().toLowerCase().replace("_", " "); - if (record.getTariff() != 0) { - label += " tariff " + record.getTariff(); - } - if (record.getStorageNumber() != 0) { - label += " storage " + record.getStorageNumber(); - } - - logger.info("Calculating new channel type {} for record {}", channelTypeUID, record); - - Optional itemType = getItemType(record.getDataValueType(), record.getUnit(), dateFieldMode); - ChannelKind kind = ChannelKind.STATE; - String description = getDescription(record); - String category = ""; - Set tags = Collections.emptySet(); - StateDescription state = getStateDescription(record.getDataValueType(), record.getDescription(), - unit, dateFieldMode); - EventDescription event = null; - ChannelType channelType = new ChannelType(typeUID, false, itemType.get(), kind, label, description, - category, tags, state, event, null); - wmbusChannelMap.put(typeUID.getId(), channelType); - } - } - } - - } - - private StateDescription getStateDescription(DataValueType type, Description description, - Optional> mappedUnit, DateFieldMode dateFieldMode) { - - boolean number; - if (type == DataValueType.BCD || type == DataValueType.DOUBLE || type == DataValueType.LONG) { - number = true; - } else { - number = false; - } - - boolean date = type == DataValueType.DATE; - - String pattern = mappedUnit.map(unit -> formatUnit(false, number, date, dateFieldMode)) - .orElseGet(() -> formatUnit(true, number, date, dateFieldMode)); - return new StateDescription(null, null, null, pattern, true, null); - } - - private String formatUnit(boolean unitless, boolean number, boolean date, DateFieldMode dateFieldMode) { - if (number) { - if (unitless) { - return "%.2f"; - } else { - return "%.2f %unit%"; - } - } - - if (date) { - switch (dateFieldMode) { - case UNIX_TIMESTAMP: - return "%d"; - case DATE_TIME: - return ""; - // default in this case is string - } - } - - return "%s"; - } - - private String getDescription(DataRecord record) { - String description = record.getDescription().name().replace("_", " ").toLowerCase(); - String function = getFunction(record.getFunctionField()); - return function + " value of " + description + " registry. Storage " + record.getStorageNumber() + ", tarif " - + record.getTariff() + ". Emmited under DIB " + HexUtils.bytesToHex(record.getDib()) + " and VIB:" - + HexUtils.bytesToHex(record.getVib()); - } - - private String getFunction(FunctionField function) { - switch (function) { - case ERROR_VAL: - return "Error"; - case INST_VAL: - return "Present"; - case MAX_VAL: - return "Maximum"; - case MIN_VAL: - return "Minimum"; - - } - - return "Unknown"; - } - - private Optional getItemType(DataValueType dataValueType, DlmsUnit dlmsUnit, DateFieldMode dateFieldMode) { - switch (dataValueType) { - case BCD: - case DOUBLE: - case LONG: - String quantity = unitRegistry.quantity(dlmsUnit).map(Class::getSimpleName) - .map(quantityName -> ":" + quantityName).orElse(""); - return Optional.of(CoreItemFactory.NUMBER + quantity); - case DATE: - switch (dateFieldMode) { - case FORMATTED_STRING: - return Optional.of(CoreItemFactory.STRING); - case UNIX_TIMESTAMP: - return Optional.of(CoreItemFactory.NUMBER); - default: - return Optional.of(CoreItemFactory.DATETIME); - } - case STRING: - return Optional.of(CoreItemFactory.STRING); - } - - return Optional.of(CoreItemFactory.STRING); - } - - @Reference - protected void setUnitRegistry(UnitRegistry unitRegistry) { - this.unitRegistry = unitRegistry; - } - - protected void unsetUnitRegistry(UnitRegistry unitRegistry) { - this.unitRegistry = null; - } - - public final static Optional getChannelId(DataRecord record) { - if (record.getDescription() == Description.RESERVED || record.getDescription() == Description.NOT_SUPPORTED - || record.getDescription() == Description.MANUFACTURER_SPECIFIC) { - return Optional.empty(); - } - - String dib = HexUtils.bytesToHex(record.getDib()); - String vib = HexUtils.bytesToHex(record.getVib()); - - return Optional.of(record.getDescription().name().toLowerCase() + "_" + dib + "_" + vib); - } - - public final static Optional getChannelType(DataRecord record) { - return getChannelId(record).map(id -> new ChannelTypeUID(WMBusBindingConstants.BINDING_ID, id)); - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.internal; + +import java.util.Collection; +import java.util.Collections; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import javax.measure.Unit; + +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.wmbus.UnitRegistry; +import org.openhab.binding.wmbus.WMBusBindingConstants; +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.config.DateFieldMode; +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openhab.binding.wmbus.handler.WMBusMessageListener; +import org.openhab.core.library.CoreItemFactory; +import org.openhab.core.thing.type.ChannelKind; +import org.openhab.core.thing.type.ChannelType; +import org.openhab.core.thing.type.ChannelTypeBuilder; +import org.openhab.core.thing.type.ChannelTypeProvider; +import org.openhab.core.thing.type.ChannelTypeUID; +import org.openhab.core.types.EventDescription; +import org.openhab.core.types.StateDescription; +import org.openhab.core.types.StateDescriptionFragmentBuilder; +import org.openhab.core.util.HexUtils; +import org.openmuc.jmbus.DataRecord; +import org.openmuc.jmbus.DataRecord.DataValueType; +import org.openmuc.jmbus.DataRecord.Description; +import org.openmuc.jmbus.DataRecord.FunctionField; +import org.openmuc.jmbus.DlmsUnit; +import org.openmuc.jmbus.VariableDataStructure; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Dynamic channel type provider which uses received wmbus frames to create channel types. + * + * Because wmbus frames contain variable data part which have many mutations it is not possible to create a static + * configuration which would cover all combinations. Fields which are multipliers are: + * - dib (inst/min/max/error val) + * - vib (subunit, tariff, storage number, vif) + * + * While most of devices uses just small subset of values its not possible to predict all variations of above. + * + * @author Łukasz Dywicki - Initial contribution. + */ +@Component(service = { ChannelTypeProvider.class, WMBusMessageListener.class, WMBusChannelTypeProvider.class }) +public class WMBusChannelTypeProvider implements ChannelTypeProvider, WMBusMessageListener { + + private final Logger logger = LoggerFactory.getLogger(WMBusChannelTypeProvider.class); + + private final Map wmbusChannelMap = new ConcurrentHashMap<>(); + private UnitRegistry unitRegistry; + + @Override + public Collection getChannelTypes(@Nullable Locale locale) { + return wmbusChannelMap.values(); + } + + @Override + public @Nullable ChannelType getChannelType(ChannelTypeUID channelTypeUID, @Nullable Locale locale) { + return wmbusChannelMap.values().stream().filter(channelType -> channelType.getUID().equals(channelTypeUID)) + .findFirst().orElse(null); + } + + @Override + public void onNewWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { + calculateChannelTypes(device); + } + + @Override + public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { + calculateChannelTypes(device); + } + + private void calculateChannelTypes(WMBusDevice device) { + VariableDataStructure response = device.getOriginalMessage().getVariableDataResponse(); + DateFieldMode dateFieldMode = device.getAdapter().getDateFieldMode(); + + for (DataRecord record : response.getDataRecords()) { + Optional channelTypeUID = getChannelType(record); + if (channelTypeUID.isPresent()) { + ChannelTypeUID typeUID = channelTypeUID.get(); + if (!wmbusChannelMap.containsKey(typeUID.getId())) { + Optional> unit = unitRegistry.lookup(record.getUnit()); + String label = getFunction(record.getFunctionField()) + " "; + label += record.getDescription().name().toLowerCase().replace("_", " "); + if (record.getTariff() != 0) { + label += " tariff " + record.getTariff(); + } + if (record.getStorageNumber() != 0) { + label += " storage " + record.getStorageNumber(); + } + + logger.info("Calculating new channel type {} for record {}", channelTypeUID, record); + + Optional itemType = getItemType(record.getDataValueType(), record.getUnit(), dateFieldMode); + ChannelKind kind = ChannelKind.STATE; + String description = getDescription(record); + String category = ""; + Set tags = Collections.emptySet(); + StateDescription state = getStateDescription(record.getDataValueType(), record.getDescription(), + unit, dateFieldMode); + EventDescription event = null; + ChannelTypeBuilder channelTypeBuilder; + ChannelType channelType = ChannelTypeBuilder.state(channelTypeUID.get(), label, itemType.get()) + .isAdvanced(false).withDescription(description).withCategory(category) + .build();/* + * new ChannelType(typeUID, false, itemType.get(), kind, label, + * description, + * category, tags, state, null, event, null, null) + */ + + wmbusChannelMap.put(typeUID.getId(), channelType); + } + } + } + } + + private StateDescription getStateDescription(DataValueType type, Description description, + Optional> mappedUnit, DateFieldMode dateFieldMode) { + + boolean number; + if (type == DataValueType.BCD || type == DataValueType.DOUBLE || type == DataValueType.LONG) { + number = true; + } else { + number = false; + } + + boolean date = type == DataValueType.DATE; + + String pattern = mappedUnit.map(unit -> formatUnit(false, number, date, dateFieldMode)) + .orElseGet(() -> formatUnit(true, number, date, dateFieldMode)); + StateDescriptionFragmentBuilder stateFragment = StateDescriptionFragmentBuilder.create().withPattern(pattern) + .withReadOnly(true); + return stateFragment.build() + .toStateDescription()/* new StateDescription(null, null, null, pattern, true, null) */; + } + + private String formatUnit(boolean unitless, boolean number, boolean date, DateFieldMode dateFieldMode) { + if (number) { + if (unitless) { + return "%.2f"; + } else { + return "%.2f %unit%"; + } + } + + if (date) { + switch (dateFieldMode) { + case UNIX_TIMESTAMP: + return "%d"; + case DATE_TIME: + return ""; + // default in this case is string + } + } + + return "%s"; + } + + private String getDescription(DataRecord record) { + String description = record.getDescription().name().replace("_", " ").toLowerCase(); + String function = getFunction(record.getFunctionField()); + return function + " value of " + description + " registry. Storage " + record.getStorageNumber() + ", tarif " + + record.getTariff() + ". Emmited under DIB " + HexUtils.bytesToHex(record.getDib()) + " and VIB:" + + HexUtils.bytesToHex(record.getVib()); + } + + private String getFunction(FunctionField function) { + switch (function) { + case ERROR_VAL: + return "Error"; + case INST_VAL: + return "Present"; + case MAX_VAL: + return "Maximum"; + case MIN_VAL: + return "Minimum"; + + } + + return "Unknown"; + } + + private Optional getItemType(DataValueType dataValueType, DlmsUnit dlmsUnit, DateFieldMode dateFieldMode) { + switch (dataValueType) { + case BCD: + case DOUBLE: + case LONG: + String quantity = unitRegistry.quantity(dlmsUnit).map(Class::getSimpleName) + .map(quantityName -> ":" + quantityName).orElse(""); + return Optional.of(CoreItemFactory.NUMBER + quantity); + case DATE: + switch (dateFieldMode) { + case FORMATTED_STRING: + return Optional.of(CoreItemFactory.STRING); + case UNIX_TIMESTAMP: + return Optional.of(CoreItemFactory.NUMBER); + default: + return Optional.of(CoreItemFactory.DATETIME); + } + case STRING: + return Optional.of(CoreItemFactory.STRING); + } + + return Optional.of(CoreItemFactory.STRING); + } + + @Reference + protected void setUnitRegistry(UnitRegistry unitRegistry) { + this.unitRegistry = unitRegistry; + } + + protected void unsetUnitRegistry(UnitRegistry unitRegistry) { + this.unitRegistry = null; + } + + public final static Optional getChannelId(DataRecord record) { + if (record.getDescription() == Description.RESERVED || record.getDescription() == Description.NOT_SUPPORTED + || record.getDescription() == Description.MANUFACTURER_SPECIFIC) { + return Optional.empty(); + } + + String dib = HexUtils.bytesToHex(record.getDib()); + String vib = HexUtils.bytesToHex(record.getVib()); + + return Optional.of(record.getDescription().name().toLowerCase() + "_" + dib + "_" + vib); + } + + public final static Optional getChannelType(DataRecord record) { + return getChannelId(record).map(id -> new ChannelTypeUID(WMBusBindingConstants.BINDING_ID, id)); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusException.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusException.java index 9058390..ba5c171 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusException.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusException.java @@ -1,24 +1,27 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ - -package org.openhab.binding.wmbus.internal; - -/** - * The {@link WMBusException} class defines WMBusException - * - * @author Roman Malyugin - Initial contribution - */ - -public class WMBusException extends Exception { - - public WMBusException(String string) { - super(string); - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.internal; + +/** + * The {@link WMBusException} class defines WMBusException + * + * @author Roman Malyugin - Initial contribution + */ + +public class WMBusException extends Exception { + + public WMBusException(String string) { + super(string); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusHandlerFactory.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusHandlerFactory.java index 7f007c8..c6d249b 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusHandlerFactory.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusHandlerFactory.java @@ -1,143 +1,146 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ - -package org.openhab.binding.wmbus.internal; - -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.openhab.binding.wmbus.UnitRegistry; -import org.openhab.binding.wmbus.WMBusBindingConstants; -import org.openhab.binding.wmbus.device.UnknownMeter.UnknownWMBusDeviceHandler; -import org.openhab.binding.wmbus.device.generic.DynamicWMBusThingHandler; -import org.openhab.binding.wmbus.discovery.CompositeMessageListener; -import org.openhab.binding.wmbus.handler.VirtualWMBusBridgeHandler; -import org.openhab.binding.wmbus.handler.WMBusBridgeHandler; -import org.openhab.binding.wmbus.handler.WMBusMessageListener; -import org.openhab.io.transport.mbus.wireless.FilteredKeyStorage; -import org.openhab.io.transport.mbus.wireless.KeyStorage; -import org.osgi.service.component.ComponentContext; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Deactivate; -import org.osgi.service.component.annotations.Reference; -import org.osgi.service.component.annotations.ReferenceCardinality; -import org.osgi.service.component.annotations.ReferencePolicy; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link WMBusHandlerFactory} class defines WMBusHandlerFactory. This class is the main entry point of the binding. - * - * @author Hanno - Felix Wagner - Roman Malyugin - Initial contribution - */ - -@Component(service = { WMBusHandlerFactory.class, BaseThingHandlerFactory.class, ThingHandlerFactory.class }) -public class WMBusHandlerFactory extends BaseThingHandlerFactory { - - // OpenHAB logger - private final Logger logger = LoggerFactory.getLogger(WMBusHandlerFactory.class); - - private final CompositeMessageListener messageListener = new CompositeMessageListener(); - - private KeyStorage keyStorage; - private UnitRegistry unitRegistry; - private WMBusChannelTypeProvider channelTypeProvider; - - public WMBusHandlerFactory() { - logger.debug("wmbus handler factory is starting up."); - } - - @Override - public boolean supportsThingType(ThingTypeUID thingTypeUID) { - return WMBusBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); - } - - @Override - protected ThingHandler createHandler(Thing thing) { - ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - - if (thing instanceof Bridge) { - if (thingTypeUID.equals(WMBusBindingConstants.THING_TYPE_BRIDGE)) { - logger.debug("Creating handler for WMBus bridge."); - WMBusBridgeHandler handler = new WMBusBridgeHandler((Bridge) thing, keyStorage); - handler.registerWMBusMessageListener(messageListener); - return handler; - } else if (thingTypeUID.equals(WMBusBindingConstants.THING_TYPE_VIRTUAL_BRIDGE)) { - logger.debug("Creating handler for virtual WMBus bridge."); - VirtualWMBusBridgeHandler handler = new VirtualWMBusBridgeHandler((Bridge) thing, keyStorage); - handler.registerWMBusMessageListener(messageListener); - return handler; - } - } - - // add new devices here - if (thingTypeUID.equals(WMBusBindingConstants.THING_TYPE_METER) - || thingTypeUID.equals(WMBusBindingConstants.THING_TYPE_ENCRYPTED_METER)) { - logger.debug("Creating standard wmbus handler."); - return new DynamicWMBusThingHandler<>(thing, new FilteredKeyStorage(keyStorage, thing), unitRegistry, - channelTypeProvider); - } else { - logger.debug("Creating (handler for) Unknown device."); - return new UnknownWMBusDeviceHandler(thing, new FilteredKeyStorage(keyStorage, thing)); - } - } - - @Override - @Activate - protected void activate(ComponentContext componentContext) { - super.activate(componentContext); - } - - @Override - @Deactivate - protected void deactivate(ComponentContext componentContext) { - super.deactivate(componentContext); - } - - @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) - public void registerWMBusMessageListener(WMBusMessageListener wmBusMessageListener) { - messageListener.addMessageListener(wmBusMessageListener); - } - - public void unregisterWMBusMessageListener(WMBusMessageListener wmBusMessageListener) { - messageListener.removeMessageListener(wmBusMessageListener); - } - - @Reference - public void setKeyStorage(KeyStorage keyStorage) { - this.keyStorage = keyStorage; - } - - public void unsetKeyStorage(KeyStorage keyStorage) { - this.keyStorage = null; - } - - @Reference - protected void setUnitRegistry(UnitRegistry unitRegistry) { - this.unitRegistry = unitRegistry; - } - - protected void unsetUnitRegistry(UnitRegistry unitRegistry) { - this.unitRegistry = null; - } - - @Reference - protected void setChannelTypeProvider(WMBusChannelTypeProvider channelTypeProvider) { - this.channelTypeProvider = channelTypeProvider; - } - - protected void unsetChannelTypeProvider(WMBusChannelTypeProvider channelTypeProvider) { - this.channelTypeProvider = null; - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.internal; + +import org.openhab.binding.wmbus.UnitRegistry; +import org.openhab.binding.wmbus.WMBusBindingConstants; +import org.openhab.binding.wmbus.device.UnknownMeter.UnknownWMBusDeviceHandler; +import org.openhab.binding.wmbus.device.generic.DynamicWMBusThingHandler; +import org.openhab.binding.wmbus.discovery.CompositeMessageListener; +import org.openhab.binding.wmbus.handler.VirtualWMBusBridgeHandler; +import org.openhab.binding.wmbus.handler.WMBusBridgeHandler; +import org.openhab.binding.wmbus.handler.WMBusMessageListener; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.BaseThingHandlerFactory; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.openhab.io.transport.mbus.wireless.FilteredKeyStorage; +import org.openhab.io.transport.mbus.wireless.KeyStorage; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link WMBusHandlerFactory} class defines WMBusHandlerFactory. This class is the main entry point of the binding. + * + * @author Hanno - Felix Wagner - Roman Malyugin - Initial contribution + */ + +@Component(service = { WMBusHandlerFactory.class, BaseThingHandlerFactory.class, ThingHandlerFactory.class }) +public class WMBusHandlerFactory extends BaseThingHandlerFactory { + + // OpenHAB logger + private final Logger logger = LoggerFactory.getLogger(WMBusHandlerFactory.class); + + private final CompositeMessageListener messageListener = new CompositeMessageListener(); + + private KeyStorage keyStorage; + private UnitRegistry unitRegistry; + private WMBusChannelTypeProvider channelTypeProvider; + + public WMBusHandlerFactory() { + logger.debug("wmbus handler factory is starting up."); + } + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return WMBusBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + @Override + protected ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (thing instanceof Bridge) { + if (thingTypeUID.equals(WMBusBindingConstants.THING_TYPE_BRIDGE)) { + logger.debug("Creating handler for WMBus bridge."); + WMBusBridgeHandler handler = new WMBusBridgeHandler((Bridge) thing, keyStorage); + handler.registerWMBusMessageListener(messageListener); + return handler; + } else if (thingTypeUID.equals(WMBusBindingConstants.THING_TYPE_VIRTUAL_BRIDGE)) { + logger.debug("Creating handler for virtual WMBus bridge."); + VirtualWMBusBridgeHandler handler = new VirtualWMBusBridgeHandler((Bridge) thing, keyStorage); + handler.registerWMBusMessageListener(messageListener); + return handler; + } + } + + // add new devices here + if (thingTypeUID.equals(WMBusBindingConstants.THING_TYPE_METER) + || thingTypeUID.equals(WMBusBindingConstants.THING_TYPE_ENCRYPTED_METER)) { + logger.debug("Creating standard wmbus handler."); + return new DynamicWMBusThingHandler<>(thing, new FilteredKeyStorage(keyStorage, thing), unitRegistry, + channelTypeProvider); + } else { + logger.debug("Creating (handler for) Unknown device."); + return new UnknownWMBusDeviceHandler(thing, new FilteredKeyStorage(keyStorage, thing)); + } + } + + @Override + @Activate + protected void activate(ComponentContext componentContext) { + super.activate(componentContext); + } + + @Override + @Deactivate + protected void deactivate(ComponentContext componentContext) { + super.deactivate(componentContext); + } + + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + public void registerWMBusMessageListener(WMBusMessageListener wmBusMessageListener) { + messageListener.addMessageListener(wmBusMessageListener); + } + + public void unregisterWMBusMessageListener(WMBusMessageListener wmBusMessageListener) { + messageListener.removeMessageListener(wmBusMessageListener); + } + + @Reference + public void setKeyStorage(KeyStorage keyStorage) { + this.keyStorage = keyStorage; + } + + public void unsetKeyStorage(KeyStorage keyStorage) { + this.keyStorage = null; + } + + @Reference + protected void setUnitRegistry(UnitRegistry unitRegistry) { + this.unitRegistry = unitRegistry; + } + + protected void unsetUnitRegistry(UnitRegistry unitRegistry) { + this.unitRegistry = null; + } + + @Reference + protected void setChannelTypeProvider(WMBusChannelTypeProvider channelTypeProvider) { + this.channelTypeProvider = channelTypeProvider; + } + + protected void unsetChannelTypeProvider(WMBusChannelTypeProvider channelTypeProvider) { + this.channelTypeProvider = null; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusReceiver.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusReceiver.java index df4d712..9e64411 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusReceiver.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusReceiver.java @@ -1,96 +1,99 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ - -package org.openhab.binding.wmbus.internal; - -import java.io.IOException; -import java.util.Arrays; - -import org.eclipse.smarthome.core.util.HexUtils; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openmuc.jmbus.wireless.WMBusListener; -import org.openmuc.jmbus.wireless.WMBusMessage; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link WMBusReceiver} class defines WMBusReceiver. Keeps connection with the WMBus radio module and forwards - * - * @author Hanno - Felix Wagner - Roman Malyugin - Initial contribution - */ - -public class WMBusReceiver implements WMBusListener { - - int[] filterIDs = new int[] {}; - - private final WMBusAdapter wmBusBridgeHandler; - - private final Logger logger = LoggerFactory.getLogger(WMBusReceiver.class); - - public WMBusReceiver(WMBusAdapter wmBusBridgeHandler) { - this.wmBusBridgeHandler = wmBusBridgeHandler; - } - - public int[] getFilterIDs() { - logger.debug("getFilterIDs(): got {}", Arrays.toString(filterIDs)); - return filterIDs; - } - - public void setFilterIDs(int[] filterIDs) { - logger.debug("setFilterIDs() to {}", Arrays.toString(filterIDs)); - this.filterIDs = filterIDs; - } - - boolean filterMatch(int inQuestion) { - logger.trace("filterMatch(): are we interested in device {}?", Integer.toString(inQuestion)); - if (filterIDs.length == 0) { - logger.trace("filterMatch(): length is zero -> yes"); - return true; - } - for (int i = 0; i < filterIDs.length; i++) { - if (filterIDs[i] == inQuestion) { - logger.debug("filterMatch(): found the device -> yes"); - return true; - } - } - logger.trace("filterMatch(): not found"); - return false; - } - - /* - * Handle incoming WMBus message from radio module. - * - * @see org.openmuc.jmbus.WMBusListener#newMessage(org.openmuc.jmbus.WMBusMessage) - */ - @Override - public void newMessage(WMBusMessage message) { - logger.trace("Received WMBus message"); - if (filterMatch(message.getSecondaryAddress().getDeviceId().intValue())) { - WMBusDevice device = new WMBusDevice(message, wmBusBridgeHandler); - - logger.trace("Forwarding message to further processing: {}", HexUtils.bytesToHex(message.asBlob())); - wmBusBridgeHandler.processMessage(device); - } else { - logger.trace("Unmatched message received: {}", HexUtils.bytesToHex(message.asBlob())); - } - } - - @Override - public void discardedBytes(byte[] bytes) { - logger.debug("Bytes discarded by radio module: {}", HexConverter.bytesToHex(bytes)); - } - - @Override - public void stoppedListening(IOException e) { - wmBusBridgeHandler.reset(); - logger.warn("Stopped listening for new messages. Reason: {}", e); - } - -} \ No newline at end of file +/** + * Copyright (c) 2010-2021 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.wmbus.internal; + +import java.io.IOException; +import java.util.Arrays; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openhab.core.util.HexUtils; +import org.openmuc.jmbus.wireless.WMBusListener; +import org.openmuc.jmbus.wireless.WMBusMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link WMBusReceiver} class defines WMBusReceiver. Keeps connection with the WMBus radio module and forwards + * + * @author Hanno - Felix Wagner - Roman Malyugin - Initial contribution + */ + +public class WMBusReceiver implements WMBusListener { + + int[] filterIDs = new int[] {}; + + private final WMBusAdapter wmBusBridgeHandler; + + private final Logger logger = LoggerFactory.getLogger(WMBusReceiver.class); + + public WMBusReceiver(WMBusAdapter wmBusBridgeHandler) { + this.wmBusBridgeHandler = wmBusBridgeHandler; + } + + public int[] getFilterIDs() { + logger.debug("getFilterIDs(): got {}", Arrays.toString(filterIDs)); + return filterIDs; + } + + public void setFilterIDs(int[] filterIDs) { + logger.debug("setFilterIDs() to {}", Arrays.toString(filterIDs)); + this.filterIDs = filterIDs; + } + + boolean filterMatch(int inQuestion) { + logger.trace("filterMatch(): are we interested in device {}?", Integer.toString(inQuestion)); + if (filterIDs.length == 0) { + logger.trace("filterMatch(): length is zero -> yes"); + return true; + } + for (int i = 0; i < filterIDs.length; i++) { + if (filterIDs[i] == inQuestion) { + logger.debug("filterMatch(): found the device -> yes"); + return true; + } + } + logger.trace("filterMatch(): not found"); + return false; + } + + /* + * Handle incoming WMBus message from radio module. + * + * @see org.openmuc.jmbus.WMBusListener#newMessage(org.openmuc.jmbus.WMBusMessage) + */ + @Override + public void newMessage(WMBusMessage message) { + logger.trace("Received WMBus message"); + if (filterMatch(message.getSecondaryAddress().getDeviceId().intValue())) { + WMBusDevice device = new WMBusDevice(message, wmBusBridgeHandler); + + logger.trace("Forwarding message to further processing: {}", HexUtils.bytesToHex(message.asBlob())); + wmBusBridgeHandler.processMessage(device); + } else { + logger.trace("Unmatched message received: {}", HexUtils.bytesToHex(message.asBlob())); + } + } + + @Override + public void discardedBytes(byte[] bytes) { + logger.debug("Bytes discarded by radio module: {}", HexConverter.bytesToHex(bytes)); + } + + @Override + public void stoppedListening(IOException e) { + wmBusBridgeHandler.reset(); + logger.warn("Stopped listening for new messages. Reason: {}", e); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/discovery/WMBusDiscoveryService.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/discovery/WMBusDiscoveryService.java index 10db94f..4ca9a90 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/discovery/WMBusDiscoveryService.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/discovery/WMBusDiscoveryService.java @@ -1,151 +1,154 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ - -package org.openhab.binding.wmbus.internal.discovery; - -import static org.openhab.binding.wmbus.WMBusBindingConstants.*; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import org.eclipse.smarthome.config.discovery.AbstractDiscoveryService; -import org.eclipse.smarthome.config.discovery.DiscoveryResult; -import org.eclipse.smarthome.config.discovery.DiscoveryResultBuilder; -import org.eclipse.smarthome.core.thing.ThingTypeUID; -import org.eclipse.smarthome.core.thing.ThingUID; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openhab.binding.wmbus.handler.WMBusBridgeHandler; -import org.openhab.binding.wmbus.handler.WMBusMessageListener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link WMBusDiscoveryService2} class defines WMBusDiscoveryService2 - * - * FIXME: Left for backward compatibility verification. - * - * @author Hanno - Felix Wagner - Initial contribution - */ - -public class WMBusDiscoveryService extends AbstractDiscoveryService implements WMBusMessageListener { - - private final Logger logger = LoggerFactory.getLogger(WMBusDiscoveryService.class); - - // add new devices here; these IDs here are generated in getThingTypeUID() - // basically: control field (0x44 = dec. 68) + manufacturer ID (3 characters) + device version (as output by test - // program) + device type from jMBus DeviceType class (eg. HEAT_METER = 0x04 = 4) - // you can get these values using the diagnostics radio message printer included with the JMBus library - private Map typeToWMBUSIdMap; - - private WMBusBridgeHandler bridgeHandler; - - public WMBusDiscoveryService(Set supportedThingTypes, WMBusBridgeHandler bridgeHandler, - Map types) { - super(supportedThingTypes, 1); - this.bridgeHandler = bridgeHandler; - this.typeToWMBUSIdMap = types; - } - - public WMBusDiscoveryService(Set supportedThingTypes, int timeout, - boolean backgroundDiscoveryEnabledByDefault) throws IllegalArgumentException { - super(supportedThingTypes, timeout, backgroundDiscoveryEnabledByDefault); - } - - @Override - // add new devices there - public Set getSupportedThingTypes() { - logger.trace("getSupportedThingTypes(): currently *implemented* are {}", super.getSupportedThingTypes()); - return super.getSupportedThingTypes(); - } - - @Override - protected void startScan() { - // do nothing since there is no active scan possible at the moment, only receiving - logger.debug("startScan(): unimplemented - devices will be added upon reception of telegrams from them"); - } - - @Override - public void onNewWMBusDevice(WMBusAdapter adapter, WMBusDevice wmBusDevice) { - logger.debug( - "onnewWMBusDevice(): new device " + wmBusDevice.getOriginalMessage().getSecondaryAddress().toString()); - onWMBusMessageReceivedInternal(wmBusDevice); - } - - private void onWMBusMessageReceivedInternal(WMBusDevice wmBusDevice) { - logger.trace("msgreceivedInternal() begin"); - // try to find this device in the list of supported devices - ThingUID thingUID = getThingUID(wmBusDevice); - - if (thingUID != null) { - logger.trace("msgReceivedInternal(): device is known"); - // device known -> create discovery result - ThingUID bridgeUID = bridgeHandler.getThing().getUID(); - Map properties = new HashMap<>(1); - properties.put(PROPERTY_DEVICE_ADDRESS, wmBusDevice.getDeviceId().toString()); - - DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties) - .withRepresentationProperty(wmBusDevice.getDeviceId().toString()).withBridge(bridgeUID) - .withLabel("WMBus device #" + wmBusDevice.getDeviceId() + " (" + getTypeID(wmBusDevice) + ")") - .build(); - logger.trace("msgReceivedInternal(): notifying OpenHAB of new thing"); - thingDiscovered(discoveryResult); - } else { - // device unknown -> log message - logger.debug( - "discovered unsupported WMBus device with our type ID {} of WMBus type '{}' with secondary address {}", - getTypeID(wmBusDevice), wmBusDevice.getOriginalMessage().getSecondaryAddress().getDeviceType(), - wmBusDevice.getOriginalMessage().getSecondaryAddress().toString()); - } - } - - // checks if this device is of the supported kind -> if yes, will be discovered - private ThingUID getThingUID(WMBusDevice wmBusDevice) { - logger.trace("getThingUID begin"); - ThingUID bridgeUID = bridgeHandler.getThing().getUID(); - ThingTypeUID thingTypeUID = getThingTypeUID(wmBusDevice); - - if (thingTypeUID != null && getSupportedThingTypes().contains(thingTypeUID)) { - logger.trace("getThingUID have bridgeUID " + bridgeUID.toString()); - logger.trace("getThingUID have thingTypeUID " + thingTypeUID.toString()); - return new ThingUID(thingTypeUID, bridgeUID, wmBusDevice.getDeviceId() + ""); - } else { - logger.debug("get ThingUID found no supported device"); - return null; - } - } - - private ThingTypeUID getThingTypeUID(WMBusDevice wmBusDevice) { - String typeIdString = getTypeID(wmBusDevice); - logger.trace("getThingTypeUID(): This device has typeID " + typeIdString + " -- supported device types are " - + Arrays.toString(typeToWMBUSIdMap.keySet().toArray())); - String thingTypeId = typeToWMBUSIdMap.get(typeIdString); - return thingTypeId != null ? new ThingTypeUID(BINDING_ID, thingTypeId) : null; - } - - // this ID is needed to add new devices - private String getTypeID(WMBusDevice wmBusDevice) { - return wmBusDevice.getOriginalMessage().getControlField() + "" - + wmBusDevice.getOriginalMessage().getSecondaryAddress().getManufacturerId() + "" - + wmBusDevice.getOriginalMessage().getSecondaryAddress().getVersion() + "" - + wmBusDevice.getOriginalMessage().getSecondaryAddress().getDeviceType().getId(); - } - - public void activate() { - bridgeHandler.registerWMBusMessageListener(this); - } - - @Override - public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice wmBusDevice) { - // nothing to do - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.internal.discovery; + +import static org.openhab.binding.wmbus.WMBusBindingConstants.*; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openhab.binding.wmbus.handler.WMBusBridgeHandler; +import org.openhab.binding.wmbus.handler.WMBusMessageListener; +import org.openhab.core.config.discovery.AbstractDiscoveryService; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link WMBusDiscoveryService2} class defines WMBusDiscoveryService2 + * + * FIXME: Left for backward compatibility verification. + * + * @author Hanno - Felix Wagner - Initial contribution + */ + +public class WMBusDiscoveryService extends AbstractDiscoveryService implements WMBusMessageListener { + + private final Logger logger = LoggerFactory.getLogger(WMBusDiscoveryService.class); + + // add new devices here; these IDs here are generated in getThingTypeUID() + // basically: control field (0x44 = dec. 68) + manufacturer ID (3 characters) + device version (as output by test + // program) + device type from jMBus DeviceType class (eg. HEAT_METER = 0x04 = 4) + // you can get these values using the diagnostics radio message printer included with the JMBus library + private Map typeToWMBUSIdMap; + + private WMBusBridgeHandler bridgeHandler; + + public WMBusDiscoveryService(Set supportedThingTypes, WMBusBridgeHandler bridgeHandler, + Map types) { + super(supportedThingTypes, 1); + this.bridgeHandler = bridgeHandler; + this.typeToWMBUSIdMap = types; + } + + public WMBusDiscoveryService(Set supportedThingTypes, int timeout, + boolean backgroundDiscoveryEnabledByDefault) throws IllegalArgumentException { + super(supportedThingTypes, timeout, backgroundDiscoveryEnabledByDefault); + } + + @Override + // add new devices there + public Set getSupportedThingTypes() { + logger.trace("getSupportedThingTypes(): currently *implemented* are {}", super.getSupportedThingTypes()); + return super.getSupportedThingTypes(); + } + + @Override + protected void startScan() { + // do nothing since there is no active scan possible at the moment, only receiving + logger.debug("startScan(): unimplemented - devices will be added upon reception of telegrams from them"); + } + + @Override + public void onNewWMBusDevice(WMBusAdapter adapter, WMBusDevice wmBusDevice) { + logger.debug( + "onnewWMBusDevice(): new device " + wmBusDevice.getOriginalMessage().getSecondaryAddress().toString()); + onWMBusMessageReceivedInternal(wmBusDevice); + } + + private void onWMBusMessageReceivedInternal(WMBusDevice wmBusDevice) { + logger.trace("msgreceivedInternal() begin"); + // try to find this device in the list of supported devices + ThingUID thingUID = getThingUID(wmBusDevice); + + if (thingUID != null) { + logger.trace("msgReceivedInternal(): device is known"); + // device known -> create discovery result + ThingUID bridgeUID = bridgeHandler.getThing().getUID(); + Map properties = new HashMap<>(1); + properties.put(PROPERTY_DEVICE_ADDRESS, wmBusDevice.getDeviceId().toString()); + + DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties) + .withRepresentationProperty(wmBusDevice.getDeviceId().toString()).withBridge(bridgeUID) + .withLabel("WMBus device #" + wmBusDevice.getDeviceId() + " (" + getTypeID(wmBusDevice) + ")") + .build(); + logger.trace("msgReceivedInternal(): notifying OpenHAB of new thing"); + thingDiscovered(discoveryResult); + } else { + // device unknown -> log message + logger.debug( + "discovered unsupported WMBus device with our type ID {} of WMBus type '{}' with secondary address {}", + getTypeID(wmBusDevice), wmBusDevice.getOriginalMessage().getSecondaryAddress().getDeviceType(), + wmBusDevice.getOriginalMessage().getSecondaryAddress().toString()); + } + } + + // checks if this device is of the supported kind -> if yes, will be discovered + private ThingUID getThingUID(WMBusDevice wmBusDevice) { + logger.trace("getThingUID begin"); + ThingUID bridgeUID = bridgeHandler.getThing().getUID(); + ThingTypeUID thingTypeUID = getThingTypeUID(wmBusDevice); + + if (thingTypeUID != null && getSupportedThingTypes().contains(thingTypeUID)) { + logger.trace("getThingUID have bridgeUID " + bridgeUID.toString()); + logger.trace("getThingUID have thingTypeUID " + thingTypeUID.toString()); + return new ThingUID(thingTypeUID, bridgeUID, wmBusDevice.getDeviceId() + ""); + } else { + logger.debug("get ThingUID found no supported device"); + return null; + } + } + + private ThingTypeUID getThingTypeUID(WMBusDevice wmBusDevice) { + String typeIdString = getTypeID(wmBusDevice); + logger.trace("getThingTypeUID(): This device has typeID " + typeIdString + " -- supported device types are " + + Arrays.toString(typeToWMBUSIdMap.keySet().toArray())); + String thingTypeId = typeToWMBUSIdMap.get(typeIdString); + return thingTypeId != null ? new ThingTypeUID(BINDING_ID, thingTypeId) : null; + } + + // this ID is needed to add new devices + private String getTypeID(WMBusDevice wmBusDevice) { + return wmBusDevice.getOriginalMessage().getControlField() + "" + + wmBusDevice.getOriginalMessage().getSecondaryAddress().getManufacturerId() + "" + + wmBusDevice.getOriginalMessage().getSecondaryAddress().getVersion() + "" + + wmBusDevice.getOriginalMessage().getSecondaryAddress().getDeviceType().getId(); + } + + public void activate() { + bridgeHandler.registerWMBusMessageListener(this); + } + + @Override + public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice wmBusDevice) { + // nothing to do + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/discovery/WMBusDiscoveryService2.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/discovery/WMBusDiscoveryService2.java index 6f5932e..ef219bc 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/discovery/WMBusDiscoveryService2.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/discovery/WMBusDiscoveryService2.java @@ -1,203 +1,205 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.internal.discovery; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArraySet; - -import org.eclipse.smarthome.config.discovery.AbstractDiscoveryService; -import org.eclipse.smarthome.config.discovery.DiscoveryResult; -import org.eclipse.smarthome.config.discovery.DiscoveryResultBuilder; -import org.eclipse.smarthome.config.discovery.DiscoveryService; -import org.eclipse.smarthome.core.thing.Thing; -import org.eclipse.smarthome.core.thing.ThingTypeUID; -import org.eclipse.smarthome.core.thing.ThingUID; -import org.openhab.binding.wmbus.BindingConfiguration; -import org.openhab.binding.wmbus.WMBusBindingConstants; -import org.openhab.binding.wmbus.WMBusCompanyIdentifiers; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.discovery.WMBusDiscoveryParticipant; -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openhab.binding.wmbus.handler.WMBusMessageListener; -import org.openmuc.jmbus.SecondaryAddress; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Deactivate; -import org.osgi.service.component.annotations.Modified; -import org.osgi.service.component.annotations.Reference; -import org.osgi.service.component.annotations.ReferenceCardinality; -import org.osgi.service.component.annotations.ReferencePolicy; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link WMBusDiscoveryService2} handles searching for WMBus devices. - * - * @author Łukasz Dywicki - initial Contribution - */ -@Component(immediate = true, service = { DiscoveryService.class, - WMBusMessageListener.class }, configurationPid = "discovery.wmbus") -public class WMBusDiscoveryService2 extends AbstractDiscoveryService implements WMBusMessageListener { - - private final Logger logger = LoggerFactory.getLogger(WMBusDiscoveryService2.class); - - private static final int SEARCH_TIME = 0; - - private final Set participants = new CopyOnWriteArraySet<>(); - private final Set supportedThingTypes = new CopyOnWriteArraySet<>(); - - private BindingConfiguration configuration; - - public WMBusDiscoveryService2() { - super(SEARCH_TIME); - supportedThingTypes.add(WMBusBindingConstants.THING_TYPE_METER); - } - - @Override - public boolean isBackgroundDiscoveryEnabled() { - return true; - } - - @Override - @Activate - protected void activate(Map configProperties) { - logger.debug("Activating WMBus discovery service"); - super.activate(configProperties); - startScan(); - } - - @Override - @Modified - protected void modified(Map configProperties) { - super.modified(configProperties); - } - - @Override - @Deactivate - public void deactivate() { - logger.debug("Deactivating WMBus discovery service"); - } - - @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) - protected void addWMBusDiscoveryParticipant(WMBusDiscoveryParticipant participant) { - this.participants.add(participant); - supportedThingTypes.addAll(participant.getSupportedThingTypeUIDs()); - } - - protected void removeWMBusDiscoveryParticipant(WMBusDiscoveryParticipant participant) { - supportedThingTypes.removeAll(participant.getSupportedThingTypeUIDs()); - this.participants.remove(participant); - } - - @Override - public Set getSupportedThingTypes() { - return supportedThingTypes; - } - - @Override - public void startScan() { - } - - @Override - public void stopScan() { - removeOlderResults(getTimestampOfLastScan()); - } - - @Override - public void onNewWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { - deviceDiscovered(adapter, device); - } - - @Override - public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { - - } - - private void deviceDiscovered(WMBusAdapter adapter, WMBusDevice device) { - for (WMBusDiscoveryParticipant participant : participants) { - try { - DiscoveryResult result = participant.createResult(device); - if (result != null) { - thingDiscovered(result); - return; - } - } catch (Exception e) { - logger.error("Participant '{}' threw an exception.", participant.getClass().getName(), e); - } - } - - // Here we have an additional section which discard devices of unknown type. - // Some of standard WMBus devices are not meant to be read by this binding. - // If earlier discovery participants didn't handle properly device we need to narrow scope to meters, - // valves and other standard accessory excluding repeaters and other special things. - SecondaryAddress secondaryAddress = device.getOriginalMessage().getSecondaryAddress(); - if (/* - * !WMBusBindingConstants.SUPPORTED_DEVICE_TYPES.contains(secondaryAddress.getDeviceType()) - * || - */ !device.getDeviceId().matches("^[a-zA-Z0-9]+$")) { - logger.info("Discarded discovery of device {} which is unsupported by binding: {}", device.getDeviceType(), - secondaryAddress); - return; - } - - // We did not find a thing type for this device, so let's treat it as a generic one - String label = "WMBus device: " + secondaryAddress.getDeviceType().name().toLowerCase().replace("_", " ") + " #" - + device.getDeviceId() + " (" + device.getDeviceType() + ")"; - - Map properties = new HashMap<>(); - properties.put(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS, device.getDeviceAddress()); - properties.put(Thing.PROPERTY_SERIAL_NUMBER, device.getDeviceId()); - properties.put(Thing.PROPERTY_MODEL_ID, secondaryAddress.getVersion()); - - String manufacturer = WMBusCompanyIdentifiers.get(secondaryAddress.getManufacturerId()); - if (manufacturer != null) { - properties.put(Thing.PROPERTY_VENDOR, manufacturer); - label += " (" + manufacturer + ")"; - } - - ThingTypeUID typeUID; - if (device.isEnrypted()) { - typeUID = WMBusBindingConstants.THING_TYPE_ENCRYPTED_METER; - properties.put(WMBusBindingConstants.PROPERTY_DEVICE_ENCRYPTED, true); - } else { - typeUID = WMBusBindingConstants.THING_TYPE_METER; - properties.put(WMBusBindingConstants.PROPERTY_DEVICE_ENCRYPTED, false); - } - - ThingUID thingUID; - if (configuration.getIncludeBridgeUID()) { - thingUID = new ThingUID(typeUID, device.getAdapter().getUID(), device.getDeviceAddress()); - } else { - thingUID = new ThingUID(typeUID, device.getDeviceAddress()); - } - - // Create the discovery result and add to the inbox - DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties) - .withRepresentationProperty(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS).withBridge(adapter.getUID()) - .withThingType(typeUID).withLabel(label).withTTL(getTimeToLive()).build(); - - thingDiscovered(discoveryResult); - } - - @Reference - public void setBindingConfiguration(BindingConfiguration configuration) { - this.configuration = configuration; - } - - public void unsetBindingConfiguration(BindingConfiguration configuration) { - this.configuration = null; - } - - private Long getTimeToLive() { - return configuration.getTimeToLive(); - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.internal.discovery; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +import org.openhab.binding.wmbus.BindingConfiguration; +import org.openhab.binding.wmbus.WMBusBindingConstants; +import org.openhab.binding.wmbus.WMBusCompanyIdentifiers; +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.discovery.WMBusDiscoveryParticipant; +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openhab.binding.wmbus.handler.WMBusMessageListener; +import org.openhab.core.config.discovery.AbstractDiscoveryService; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.config.discovery.DiscoveryService; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.openmuc.jmbus.SecondaryAddress; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Modified; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link WMBusDiscoveryService2} handles searching for WMBus devices. + * + * @author Łukasz Dywicki - initial Contribution + */ +@Component(immediate = true, service = { DiscoveryService.class, + WMBusMessageListener.class }, configurationPid = "discovery.wmbus") +public class WMBusDiscoveryService2 extends AbstractDiscoveryService implements WMBusMessageListener { + + private final Logger logger = LoggerFactory.getLogger(WMBusDiscoveryService2.class); + + private static final int SEARCH_TIME = 0; + + private final Set participants = new CopyOnWriteArraySet<>(); + private final Set supportedThingTypes = new CopyOnWriteArraySet<>(); + + private BindingConfiguration configuration; + + public WMBusDiscoveryService2() { + super(SEARCH_TIME); + supportedThingTypes.add(WMBusBindingConstants.THING_TYPE_METER); + } + + @Override + public boolean isBackgroundDiscoveryEnabled() { + return true; + } + + @Override + @Activate + protected void activate(Map configProperties) { + logger.debug("Activating WMBus discovery service"); + super.activate(configProperties); + startScan(); + } + + @Override + @Modified + protected void modified(Map configProperties) { + super.modified(configProperties); + } + + @Override + @Deactivate + public void deactivate() { + logger.debug("Deactivating WMBus discovery service"); + } + + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + protected void addWMBusDiscoveryParticipant(WMBusDiscoveryParticipant participant) { + this.participants.add(participant); + supportedThingTypes.addAll(participant.getSupportedThingTypeUIDs()); + } + + protected void removeWMBusDiscoveryParticipant(WMBusDiscoveryParticipant participant) { + supportedThingTypes.removeAll(participant.getSupportedThingTypeUIDs()); + this.participants.remove(participant); + } + + @Override + public Set getSupportedThingTypes() { + return supportedThingTypes; + } + + @Override + public void startScan() { + } + + @Override + public void stopScan() { + removeOlderResults(getTimestampOfLastScan()); + } + + @Override + public void onNewWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { + deviceDiscovered(adapter, device); + } + + @Override + public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { + } + + private void deviceDiscovered(WMBusAdapter adapter, WMBusDevice device) { + for (WMBusDiscoveryParticipant participant : participants) { + try { + DiscoveryResult result = participant.createResult(device); + if (result != null) { + thingDiscovered(result); + return; + } + } catch (Exception e) { + logger.error("Participant '{}' threw an exception.", participant.getClass().getName(), e); + } + } + + // Here we have an additional section which discard devices of unknown type. + // Some of standard WMBus devices are not meant to be read by this binding. + // If earlier discovery participants didn't handle properly device we need to narrow scope to meters, + // valves and other standard accessory excluding repeaters and other special things. + SecondaryAddress secondaryAddress = device.getOriginalMessage().getSecondaryAddress(); + if (/* + * !WMBusBindingConstants.SUPPORTED_DEVICE_TYPES.contains(secondaryAddress.getDeviceType()) + * || + */ !device.getDeviceId().matches("^[a-zA-Z0-9]+$")) { + logger.info("Discarded discovery of device {} which is unsupported by binding: {}", device.getDeviceType(), + secondaryAddress); + return; + } + + // We did not find a thing type for this device, so let's treat it as a generic one + String label = "WMBus device: " + secondaryAddress.getDeviceType().name().toLowerCase().replace("_", " ") + " #" + + device.getDeviceId() + " (" + device.getDeviceType() + ")"; + + Map properties = new HashMap<>(); + properties.put(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS, device.getDeviceAddress()); + properties.put(Thing.PROPERTY_SERIAL_NUMBER, device.getDeviceId()); + properties.put(Thing.PROPERTY_MODEL_ID, secondaryAddress.getVersion()); + + String manufacturer = WMBusCompanyIdentifiers.get(secondaryAddress.getManufacturerId()); + if (manufacturer != null) { + properties.put(Thing.PROPERTY_VENDOR, manufacturer); + label += " (" + manufacturer + ")"; + } + + ThingTypeUID typeUID; + if (device.isEnrypted()) { + typeUID = WMBusBindingConstants.THING_TYPE_ENCRYPTED_METER; + properties.put(WMBusBindingConstants.PROPERTY_DEVICE_ENCRYPTED, true); + } else { + typeUID = WMBusBindingConstants.THING_TYPE_METER; + properties.put(WMBusBindingConstants.PROPERTY_DEVICE_ENCRYPTED, false); + } + + ThingUID thingUID; + if (configuration.getIncludeBridgeUID()) { + thingUID = new ThingUID(typeUID, device.getAdapter().getUID(), device.getDeviceAddress()); + } else { + thingUID = new ThingUID(typeUID, device.getDeviceAddress()); + } + // logger.error("adapter '{}' thingUID '{}'", adapter.getUID(), thingUID); + // Create the discovery result and add to the inbox + DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties) + .withRepresentationProperty(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS).withBridge(adapter.getUID()) + .withThingType(typeUID).withLabel(label).withTTL(getTimeToLive()).build(); + + thingDiscovered(discoveryResult); + } + + @Reference + public void setBindingConfiguration(BindingConfiguration configuration) { + this.configuration = configuration; + } + + public void unsetBindingConfiguration(BindingConfiguration configuration) { + this.configuration = null; + } + + private Long getTimeToLive() { + return configuration.getTimeToLive(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/units/CompositeUnitRegistry.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/units/CompositeUnitRegistry.java index 55930e6..6dd4fc8 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/units/CompositeUnitRegistry.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/units/CompositeUnitRegistry.java @@ -1,97 +1,100 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.internal.units; - -import java.util.Arrays; -import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Stream; - -import javax.measure.Quantity; -import javax.measure.Unit; - -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.wmbus.UnitRegistry; -import org.openmuc.jmbus.DlmsUnit; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Reference; -import org.osgi.service.component.annotations.ReferenceCardinality; - -/** - * An unit registry intended to aggregate several other registers to mask actual lookup operation. - * - * By default this registry is started up with {@link SmartHomeUnitsRegistry} which covers standard DLMS-SI/Imperial - * units known to - * framework. However some DLMS units are not supported and very specific to narrow fields which might not be added any - * time soon. For this reason we leave an extensions for future cases if there is a device we desperately want, but its - * dlms measurements units are not supported. - * - * @author Łukasz Dywicki - Initial contribution. - */ -@Component(property = { "composite=true" }) -public class CompositeUnitRegistry implements UnitRegistry { - - private final Set registers = new LinkedHashSet<>(); - - public CompositeUnitRegistry() { - this(new SmartHomeUnitsRegistry()); - } - - CompositeUnitRegistry(UnitRegistry... registers) { - this(Arrays.asList(registers)); - } - - CompositeUnitRegistry(Collection initial) { - this.registers.addAll(initial); - } - - @Override - public Optional> lookup(DlmsUnit wmbusType) { - return registers.stream() // - .flatMap(registry -> get(registry, wmbusType)) // - .findFirst(); - } - - @Override - public Optional>> quantity(@Nullable DlmsUnit wmbusType) { - return registers.stream() // - .flatMap(registry -> getQuantity(registry, wmbusType)) // - .findFirst(); - } - - private Stream> get(UnitRegistry registry, DlmsUnit wmbusType) { - Optional> lookup = registry.lookup(wmbusType); - - if (lookup.isPresent()) { - return Stream.of(lookup.get()); - } - return Stream.empty(); - } - - private Stream>> getQuantity(UnitRegistry registry, @Nullable DlmsUnit wmbusType) { - Optional>> lookup = registry.quantity(wmbusType); - - if (lookup.isPresent()) { - return Stream.of(lookup.get()); - } - return Stream.empty(); - } - - @Reference(cardinality = ReferenceCardinality.MULTIPLE, target = "(!(composite=true))") - protected void setUnitRegistry(UnitRegistry registry) { - this.registers.add(registry); - } - - protected void unsetUnitRegistry(UnitRegistry registry) { - this.registers.remove(registry); - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.internal.units; + +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; + +import javax.measure.Quantity; +import javax.measure.Unit; + +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.wmbus.UnitRegistry; +import org.openmuc.jmbus.DlmsUnit; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; + +/** + * An unit registry intended to aggregate several other registers to mask actual lookup operation. + * + * By default this registry is started up with {@link UnitsRegistry} which covers standard DLMS-SI/Imperial + * units known to + * framework. However some DLMS units are not supported and very specific to narrow fields which might not be added any + * time soon. For this reason we leave an extensions for future cases if there is a device we desperately want, but its + * dlms measurements units are not supported. + * + * @author Łukasz Dywicki - Initial contribution. + */ +@Component(property = { "composite=true" }) +public class CompositeUnitRegistry implements UnitRegistry { + + private final Set registers = new LinkedHashSet<>(); + + public CompositeUnitRegistry() { + this(new UnitsRegistry()); + } + + CompositeUnitRegistry(UnitRegistry... registers) { + this(Arrays.asList(registers)); + } + + CompositeUnitRegistry(Collection initial) { + this.registers.addAll(initial); + } + + @Override + public Optional> lookup(DlmsUnit wmbusType) { + return registers.stream() // + .flatMap(registry -> get(registry, wmbusType)) // + .findFirst(); + } + + @Override + public Optional>> quantity(@Nullable DlmsUnit wmbusType) { + return registers.stream() // + .flatMap(registry -> getQuantity(registry, wmbusType)) // + .findFirst(); + } + + private Stream> get(UnitRegistry registry, DlmsUnit wmbusType) { + Optional> lookup = registry.lookup(wmbusType); + + if (lookup.isPresent()) { + return Stream.of(lookup.get()); + } + return Stream.empty(); + } + + private Stream>> getQuantity(UnitRegistry registry, @Nullable DlmsUnit wmbusType) { + Optional>> lookup = registry.quantity(wmbusType); + + if (lookup.isPresent()) { + return Stream.of(lookup.get()); + } + return Stream.empty(); + } + + @Reference(cardinality = ReferenceCardinality.MULTIPLE, target = "(!(composite=true))") + protected void setUnitRegistry(UnitRegistry registry) { + this.registers.add(registry); + } + + protected void unsetUnitRegistry(UnitRegistry registry) { + this.registers.remove(registry); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/units/SmartHomeUnitsRegistry.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/units/UnitsRegistry.java similarity index 82% rename from org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/units/SmartHomeUnitsRegistry.java rename to org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/units/UnitsRegistry.java index b965b94..3c91437 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/units/SmartHomeUnitsRegistry.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/units/UnitsRegistry.java @@ -1,358 +1,361 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.internal.units; - -import java.util.Optional; - -import javax.measure.Quantity; -import javax.measure.Unit; -import javax.measure.quantity.Angle; -import javax.measure.quantity.Dimensionless; -import javax.measure.quantity.ElectricCapacitance; -import javax.measure.quantity.ElectricCharge; -import javax.measure.quantity.ElectricCurrent; -import javax.measure.quantity.ElectricInductance; -import javax.measure.quantity.ElectricPotential; -import javax.measure.quantity.ElectricResistance; -import javax.measure.quantity.Energy; -import javax.measure.quantity.Force; -import javax.measure.quantity.Frequency; -import javax.measure.quantity.Length; -import javax.measure.quantity.MagneticFlux; -import javax.measure.quantity.MagneticFluxDensity; -import javax.measure.quantity.Mass; -import javax.measure.quantity.Power; -import javax.measure.quantity.Pressure; -import javax.measure.quantity.Speed; -import javax.measure.quantity.Temperature; -import javax.measure.quantity.Time; -import javax.measure.quantity.Volume; - -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.smarthome.core.library.unit.ImperialUnits; -import org.eclipse.smarthome.core.library.unit.SIUnits; -import org.eclipse.smarthome.core.library.unit.SmartHomeUnits; -import org.openhab.binding.wmbus.UnitRegistry; -import org.openmuc.jmbus.DlmsUnit; - -/** - * Lookup table between wmbus and smart home units which contains default mapping of units. - * - * Since there are many units which are not covered by Eclipse SmartHome there are empty case - * statements. These are left for future to gets filled in once support for them is present. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public class SmartHomeUnitsRegistry implements UnitRegistry { - - @Override - public Optional> lookup(DlmsUnit wmbusType) { - if (wmbusType == null) { - return Optional.empty(); - } - - switch (wmbusType) { - case AMPERE: - return Optional.of(SmartHomeUnits.AMPERE); - case AMPERE_HOUR: - break; - case AMPERE_PER_METRE: - break; - case AMPERE_SQUARED_HOURS: - break; - case AMPERE_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE: - break; - case APPARENT_ENERGY_METER_CONSTANT_OR_PULSE_VALUE: - break; - case BAR: - // Not present in ESH 0.9 / 0.10.0.oh230 - // return Optional.of(SmartHomeUnits.BAR); - break; - case CALORIFIC_VALUE: - break; - case COULOMB: - return Optional.of(SmartHomeUnits.COULOMB); - case COUNT: - break; - case CUBIC_FEET: - return Optional.of(ImperialUnits.CUBIC_FOOT); - case CUBIC_METRE: - case CUBIC_METRE_CORRECTED: - return Optional.of(SIUnits.CUBIC_METRE); - case CUBIC_METRE_PER_DAY: - case CUBIC_METRE_PER_DAY_CORRECTED: - case CUBIC_METRE_PER_HOUR: - case CUBIC_METRE_PER_HOUR_CORRECTED: - case CUBIC_METRE_PER_MINUTE: - case CUBIC_METRE_PER_SECOND: - // there is no support for VolumetricFlowRate yet. - return Optional.empty(); - case CURRENCY: - break; - case DAY: - return Optional.of(SmartHomeUnits.DAY); - case DEGREE: - break; - case DEGREE_CELSIUS: - return Optional.of(SIUnits.CELSIUS); - case DEGREE_FAHRENHEIT: - break; - case ENERGY_PER_VOLUME: - break; - case FARAD: - return Optional.of(SmartHomeUnits.FARAD); - case HENRY: - return Optional.of(SmartHomeUnits.HENRY); - case HERTZ: - return Optional.of(SmartHomeUnits.HERTZ); - case HOUR: - return Optional.of(SmartHomeUnits.HOUR); - case JOULE: - return Optional.of(SmartHomeUnits.JOULE); - case JOULE_PER_HOUR: - break; - case KELVIN: - return Optional.of(SmartHomeUnits.KELVIN); - case KILOGRAM: - return Optional.of(SIUnits.KILOGRAM); - case KILOGRAM_PER_HOUR: - break; - case KILOGRAM_PER_SECOND: - break; - case LITRE: - return Optional.of(SmartHomeUnits.LITRE); - case MASS_DENSITY: - break; - case METER_CONSTANT_OR_PULSE_VALUE: - break; - case METRE: - return Optional.of(SIUnits.METRE); - case METRE_PER_SECOND: - return Optional.of(SmartHomeUnits.METRE_PER_SECOND); - case MIN: - return Optional.of(SmartHomeUnits.MINUTE); - case MOLE_PERCENT: - break; - case MONTH: - return Optional.of(SmartHomeUnits.FARAD); - case NEWTON: - return Optional.of(SmartHomeUnits.NEWTON); - case NEWTONMETER: - break; - case OHM: - return Optional.of(SmartHomeUnits.OHM); - case OHM_METRE: - break; - case OTHER_UNIT: - break; - case PASCAL: - return Optional.of(SIUnits.PASCAL); - case PASCAL_SECOND: - break; - case PERCENTAGE: - break; - case REACTIVE_ENERGY_METER_CONSTANT_OR_PULSE_VALUE: - break; - case RESERVED: - break; - case SECOND: - return Optional.of(SmartHomeUnits.SECOND); - case SIGNAL_STRENGTH: - break; - case SPECIFIC_ENERGY: - break; - case TESLA: - return Optional.of(SmartHomeUnits.TESLA); - case US_GALLON: - break; - case US_GALLON_PER_HOUR: - break; - case US_GALLON_PER_MINUTE: - break; - case VAR: - break; - case VAR_HOUR: - break; - case VOLT: - return Optional.of(SmartHomeUnits.VOLT); - case VOLT_AMPERE: - break; - case VOLT_AMPERE_HOUR: - break; - case VOLT_PER_METRE: - break; - case VOLT_SQUARED_HOURS: - break; - case VOLT_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE: - break; - case WATT: - return Optional.of(SmartHomeUnits.WATT); - case WATT_HOUR: - return Optional.of(SmartHomeUnits.WATT_HOUR); - case WEBER: - return Optional.of(SmartHomeUnits.WEBER); - case WEEK: - return Optional.of(SmartHomeUnits.WEEK); - case YEAR: - return Optional.of(SmartHomeUnits.YEAR); - default: - break; - } - - return Optional.empty(); - } - - @Override - public Optional>> quantity(@Nullable DlmsUnit wmbusType) { - - if (wmbusType == null) { - return Optional.empty(); - } - - switch (wmbusType) { - case AMPERE: - return Optional.of(ElectricCurrent.class); - case AMPERE_HOUR: - break; - case AMPERE_PER_METRE: - break; - case AMPERE_SQUARED_HOURS: - break; - case AMPERE_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE: - break; - case APPARENT_ENERGY_METER_CONSTANT_OR_PULSE_VALUE: - break; - case BAR: - return Optional.of(Pressure.class); - case CALORIFIC_VALUE: - break; - case COULOMB: - return Optional.of(ElectricCharge.class); - case COUNT: - break; - case CUBIC_FEET: - case CUBIC_METRE: - case CUBIC_METRE_CORRECTED: - return Optional.of(Volume.class); - case CUBIC_METRE_PER_DAY: - case CUBIC_METRE_PER_DAY_CORRECTED: - case CUBIC_METRE_PER_HOUR: - case CUBIC_METRE_PER_HOUR_CORRECTED: - case CUBIC_METRE_PER_MINUTE: - case CUBIC_METRE_PER_SECOND: - // VolumetricFlow - return Optional.empty(); - case CURRENCY: - break; - case DEGREE: - return Optional.of(Angle.class); - case DEGREE_CELSIUS: - case DEGREE_FAHRENHEIT: - case KELVIN: - return Optional.of(Temperature.class); - case ENERGY_PER_VOLUME: - break; - case FARAD: - return Optional.of(ElectricCapacitance.class); - case HENRY: - return Optional.of(ElectricInductance.class); - case HERTZ: - return Optional.of(Frequency.class); - case JOULE: - return Optional.of(Energy.class); - case JOULE_PER_HOUR: - break; - case KILOGRAM: - return Optional.of(Mass.class); - case KILOGRAM_PER_HOUR: - return Optional.of(Speed.class); - case KILOGRAM_PER_SECOND: - break; - case LITRE: - return Optional.of(Volume.class); - case MASS_DENSITY: - return Optional.empty(); - case METER_CONSTANT_OR_PULSE_VALUE: - break; - case METRE: - return Optional.of(Length.class); - case METRE_PER_SECOND: - return Optional.of(Speed.class); - case MOLE_PERCENT: - break; - case NEWTON: - return Optional.of(Force.class); - case NEWTONMETER: - break; - case OHM: - return Optional.of(ElectricResistance.class); - case OHM_METRE: - break; - case OTHER_UNIT: - break; - case PASCAL: - return Optional.of(Pressure.class); - case PASCAL_SECOND: - break; - case PERCENTAGE: - return Optional.of(Dimensionless.class); - case REACTIVE_ENERGY_METER_CONSTANT_OR_PULSE_VALUE: - break; - case RESERVED: - break; - case SIGNAL_STRENGTH: - break; - case SPECIFIC_ENERGY: - break; - case TESLA: - return Optional.of(MagneticFluxDensity.class); - case US_GALLON: - break; - case US_GALLON_PER_HOUR: - case US_GALLON_PER_MINUTE: - // VolumetricFlow - break; - case VAR: - break; - case VAR_HOUR: - break; - case VOLT: - return Optional.of(ElectricPotential.class); - case VOLT_AMPERE: - break; - case VOLT_AMPERE_HOUR: - break; - case VOLT_PER_METRE: - break; - case VOLT_SQUARED_HOURS: - break; - case VOLT_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE: - break; - case WATT: - return Optional.of(Power.class); - case WATT_HOUR: - return Optional.of(Energy.class); - case WEBER: - return Optional.of(MagneticFlux.class); - case SECOND: - case MIN: - case HOUR: - case DAY: - case WEEK: - case MONTH: - case YEAR: - return Optional.of(Time.class); - default: - break; - } - - return Optional.empty(); - } - -} +/** + * Copyright (c) 2010-2021 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.wmbus.internal.units; + +import java.util.Optional; + +import javax.measure.Quantity; +import javax.measure.Unit; +import javax.measure.quantity.Angle; +import javax.measure.quantity.Dimensionless; +import javax.measure.quantity.ElectricCapacitance; +import javax.measure.quantity.ElectricCharge; +import javax.measure.quantity.ElectricCurrent; +import javax.measure.quantity.ElectricInductance; +import javax.measure.quantity.ElectricPotential; +import javax.measure.quantity.ElectricResistance; +import javax.measure.quantity.Energy; +import javax.measure.quantity.Force; +import javax.measure.quantity.Frequency; +import javax.measure.quantity.Length; +import javax.measure.quantity.MagneticFlux; +import javax.measure.quantity.MagneticFluxDensity; +import javax.measure.quantity.Mass; +import javax.measure.quantity.Power; +import javax.measure.quantity.Pressure; +import javax.measure.quantity.Speed; +import javax.measure.quantity.Temperature; +import javax.measure.quantity.Time; +import javax.measure.quantity.Volume; + +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.wmbus.UnitRegistry; +import org.openhab.core.library.unit.ImperialUnits; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.library.unit.Units; +import org.openmuc.jmbus.DlmsUnit; + +/** + * Lookup table between wmbus and smart home units which contains default mapping of units. + * + * Since there are many units which are not covered by Eclipse SmartHome there are empty case + * statements. These are left for future to gets filled in once support for them is present. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public class UnitsRegistry implements UnitRegistry { + + @Override + public Optional> lookup(DlmsUnit wmbusType) { + if (wmbusType == null) { + return Optional.empty(); + } + + switch (wmbusType) { + case AMPERE: + return Optional.of(Units.AMPERE); + case AMPERE_HOUR: + break; + case AMPERE_PER_METRE: + break; + case AMPERE_SQUARED_HOURS: + break; + case AMPERE_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE: + break; + case APPARENT_ENERGY_METER_CONSTANT_OR_PULSE_VALUE: + break; + case BAR: + // Not present in ESH 0.9 / 0.10.0.oh230 + // return Optional.of(Units.BAR); + break; + case CALORIFIC_VALUE: + break; + case COULOMB: + return Optional.of(Units.COULOMB); + case COUNT: + break; + case CUBIC_FEET: + return Optional.of(ImperialUnits.CUBIC_FOOT); + case CUBIC_METRE: + case CUBIC_METRE_CORRECTED: + return Optional.of(SIUnits.CUBIC_METRE); + case CUBIC_METRE_PER_DAY: + case CUBIC_METRE_PER_DAY_CORRECTED: + case CUBIC_METRE_PER_HOUR: + case CUBIC_METRE_PER_HOUR_CORRECTED: + case CUBIC_METRE_PER_MINUTE: + case CUBIC_METRE_PER_SECOND: + // there is no support for VolumetricFlowRate yet. + return Optional.empty(); + case CURRENCY: + break; + case DAY: + return Optional.of(Units.DAY); + case DEGREE: + break; + case DEGREE_CELSIUS: + return Optional.of(SIUnits.CELSIUS); + case DEGREE_FAHRENHEIT: + break; + case ENERGY_PER_VOLUME: + break; + case FARAD: + return Optional.of(Units.FARAD); + case HENRY: + return Optional.of(Units.HENRY); + case HERTZ: + return Optional.of(Units.HERTZ); + case HOUR: + return Optional.of(Units.HOUR); + case JOULE: + return Optional.of(Units.JOULE); + case JOULE_PER_HOUR: + break; + case KELVIN: + return Optional.of(Units.KELVIN); + case KILOGRAM: + return Optional.of(SIUnits.KILOGRAM); + case KILOGRAM_PER_HOUR: + break; + case KILOGRAM_PER_SECOND: + break; + case LITRE: + return Optional.of(Units.LITRE); + case MASS_DENSITY: + break; + case METER_CONSTANT_OR_PULSE_VALUE: + break; + case METRE: + return Optional.of(SIUnits.METRE); + case METRE_PER_SECOND: + return Optional.of(Units.METRE_PER_SECOND); + case MIN: + return Optional.of(Units.MINUTE); + case MOLE_PERCENT: + break; + case MONTH: + return Optional.of(Units.FARAD); + case NEWTON: + return Optional.of(Units.NEWTON); + case NEWTONMETER: + break; + case OHM: + return Optional.of(Units.OHM); + case OHM_METRE: + break; + case OTHER_UNIT: + break; + case PASCAL: + return Optional.of(SIUnits.PASCAL); + case PASCAL_SECOND: + break; + case PERCENTAGE: + break; + case REACTIVE_ENERGY_METER_CONSTANT_OR_PULSE_VALUE: + break; + case RESERVED: + break; + case SECOND: + return Optional.of(Units.SECOND); + case SIGNAL_STRENGTH: + break; + case SPECIFIC_ENERGY: + break; + case TESLA: + return Optional.of(Units.TESLA); + case US_GALLON: + break; + case US_GALLON_PER_HOUR: + break; + case US_GALLON_PER_MINUTE: + break; + case VAR: + break; + case VAR_HOUR: + break; + case VOLT: + return Optional.of(Units.VOLT); + case VOLT_AMPERE: + break; + case VOLT_AMPERE_HOUR: + break; + case VOLT_PER_METRE: + break; + case VOLT_SQUARED_HOURS: + break; + case VOLT_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE: + break; + case WATT: + return Optional.of(Units.WATT); + case WATT_HOUR: + return Optional.of(Units.WATT_HOUR); + case WEBER: + return Optional.of(Units.WEBER); + case WEEK: + return Optional.of(Units.WEEK); + case YEAR: + return Optional.of(Units.YEAR); + default: + break; + } + + return Optional.empty(); + } + + @Override + public Optional>> quantity(@Nullable DlmsUnit wmbusType) { + + if (wmbusType == null) { + return Optional.empty(); + } + + switch (wmbusType) { + case AMPERE: + return Optional.of(ElectricCurrent.class); + case AMPERE_HOUR: + break; + case AMPERE_PER_METRE: + break; + case AMPERE_SQUARED_HOURS: + break; + case AMPERE_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE: + break; + case APPARENT_ENERGY_METER_CONSTANT_OR_PULSE_VALUE: + break; + case BAR: + return Optional.of(Pressure.class); + case CALORIFIC_VALUE: + break; + case COULOMB: + return Optional.of(ElectricCharge.class); + case COUNT: + break; + case CUBIC_FEET: + case CUBIC_METRE: + case CUBIC_METRE_CORRECTED: + return Optional.of(Volume.class); + case CUBIC_METRE_PER_DAY: + case CUBIC_METRE_PER_DAY_CORRECTED: + case CUBIC_METRE_PER_HOUR: + case CUBIC_METRE_PER_HOUR_CORRECTED: + case CUBIC_METRE_PER_MINUTE: + case CUBIC_METRE_PER_SECOND: + // VolumetricFlow + return Optional.empty(); + case CURRENCY: + break; + case DEGREE: + return Optional.of(Angle.class); + case DEGREE_CELSIUS: + case DEGREE_FAHRENHEIT: + case KELVIN: + return Optional.of(Temperature.class); + case ENERGY_PER_VOLUME: + break; + case FARAD: + return Optional.of(ElectricCapacitance.class); + case HENRY: + return Optional.of(ElectricInductance.class); + case HERTZ: + return Optional.of(Frequency.class); + case JOULE: + return Optional.of(Energy.class); + case JOULE_PER_HOUR: + break; + case KILOGRAM: + return Optional.of(Mass.class); + case KILOGRAM_PER_HOUR: + return Optional.of(Speed.class); + case KILOGRAM_PER_SECOND: + break; + case LITRE: + return Optional.of(Volume.class); + case MASS_DENSITY: + return Optional.empty(); + case METER_CONSTANT_OR_PULSE_VALUE: + break; + case METRE: + return Optional.of(Length.class); + case METRE_PER_SECOND: + return Optional.of(Speed.class); + case MOLE_PERCENT: + break; + case NEWTON: + return Optional.of(Force.class); + case NEWTONMETER: + break; + case OHM: + return Optional.of(ElectricResistance.class); + case OHM_METRE: + break; + case OTHER_UNIT: + break; + case PASCAL: + return Optional.of(Pressure.class); + case PASCAL_SECOND: + break; + case PERCENTAGE: + return Optional.of(Dimensionless.class); + case REACTIVE_ENERGY_METER_CONSTANT_OR_PULSE_VALUE: + break; + case RESERVED: + break; + case SIGNAL_STRENGTH: + break; + case SPECIFIC_ENERGY: + break; + case TESLA: + return Optional.of(MagneticFluxDensity.class); + case US_GALLON: + break; + case US_GALLON_PER_HOUR: + case US_GALLON_PER_MINUTE: + // VolumetricFlow + break; + case VAR: + break; + case VAR_HOUR: + break; + case VOLT: + return Optional.of(ElectricPotential.class); + case VOLT_AMPERE: + break; + case VOLT_AMPERE_HOUR: + break; + case VOLT_PER_METRE: + break; + case VOLT_SQUARED_HOURS: + break; + case VOLT_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE: + break; + case WATT: + return Optional.of(Power.class); + case WATT_HOUR: + return Optional.of(Energy.class); + case WEBER: + return Optional.of(MagneticFlux.class); + case SECOND: + case MIN: + case HOUR: + case DAY: + case WEEK: + case MONTH: + case YEAR: + return Optional.of(Time.class); + default: + break; + } + + return Optional.empty(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/FilteredKeyStorage.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/FilteredKeyStorage.java index 8c43620..e6fae27 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/FilteredKeyStorage.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/FilteredKeyStorage.java @@ -1,63 +1,66 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.io.transport.mbus.wireless; - -import java.util.Arrays; -import java.util.Collections; -import java.util.Map; -import java.util.Optional; - -import org.eclipse.smarthome.core.thing.Thing; -import org.eclipse.smarthome.core.util.HexUtils; -import org.openhab.binding.wmbus.WMBusBindingConstants; -import org.openmuc.jmbus.SecondaryAddress; - -/** - * Filtering implementation of {@link KeyStorage} - permits operation on single address and no other. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public class FilteredKeyStorage implements KeyStorage { - - private final KeyStorage delegate; - private final byte[] address; - - public FilteredKeyStorage(KeyStorage delegate, Thing thing) { - this.delegate = delegate; - this.address = Optional.ofNullable(thing.getConfiguration()) - .map(cfg -> cfg.get(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS)).map(Object::toString) - .map(HexUtils::hexToBytes).orElse(new byte[0]); - } - - @Override - public Optional lookupKey(byte[] address) { - if (Arrays.equals(this.address, address)) { - return delegate.lookupKey(address); - } - return Optional.empty(); - } - - @Override - public void registerKey(byte[] address, byte[] key) { - if (Arrays.equals(this.address, address)) { - delegate.registerKey(address, key); - } - } - - @Override - public Map toMap() { - return lookupKey(address).map(key -> Collections.singletonMap(createKey(address), key)) - .orElse(Collections.emptyMap()); - } - - private SecondaryAddress createKey(byte[] address) { - return SecondaryAddress.newFromWMBusLlHeader(address, 0); - } - -} +/** + * Copyright (c) 2010-2021 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.io.transport.mbus.wireless; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; + +import org.openhab.binding.wmbus.WMBusBindingConstants; +import org.openhab.core.thing.Thing; +import org.openhab.core.util.HexUtils; +import org.openmuc.jmbus.SecondaryAddress; + +/** + * Filtering implementation of {@link KeyStorage} - permits operation on single address and no other. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public class FilteredKeyStorage implements KeyStorage { + + private final KeyStorage delegate; + private final byte[] address; + + public FilteredKeyStorage(KeyStorage delegate, Thing thing) { + this.delegate = delegate; + this.address = Optional.ofNullable(thing.getConfiguration()) + .map(cfg -> cfg.get(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS)).map(Object::toString) + .map(HexUtils::hexToBytes).orElse(new byte[0]); + } + + @Override + public Optional lookupKey(byte[] address) { + if (Arrays.equals(this.address, address)) { + return delegate.lookupKey(address); + } + return Optional.empty(); + } + + @Override + public void registerKey(byte[] address, byte[] key) { + if (Arrays.equals(this.address, address)) { + delegate.registerKey(address, key); + } + } + + @Override + public Map toMap() { + return lookupKey(address).map(key -> Collections.singletonMap(createKey(address), key)) + .orElse(Collections.emptyMap()); + } + + private SecondaryAddress createKey(byte[] address) { + return SecondaryAddress.newFromWMBusLlHeader(address, 0); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/KeyStorage.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/KeyStorage.java index 8d510f7..64a261f 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/KeyStorage.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/KeyStorage.java @@ -1,30 +1,33 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.io.transport.mbus.wireless; - -import java.util.Map; -import java.util.Optional; - -import org.openmuc.jmbus.SecondaryAddress; - -/** - * A simple abstraction over wmbus transport layer which allows to work with message encryption keys without knowing - * anything at all about jmbus library, its dependencies and wmbus in general. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public interface KeyStorage { - - Optional lookupKey(byte[] address); - - void registerKey(byte[] address, byte[] key); - - Map toMap(); - -} +/** + * Copyright (c) 2010-2021 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.io.transport.mbus.wireless; + +import java.util.Map; +import java.util.Optional; + +import org.openmuc.jmbus.SecondaryAddress; + +/** + * A simple abstraction over wmbus transport layer which allows to work with message encryption keys without knowing + * anything at all about jmbus library, its dependencies and wmbus in general. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public interface KeyStorage { + + Optional lookupKey(byte[] address); + + void registerKey(byte[] address, byte[] key); + + Map toMap(); +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/MapKeyStorage.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/MapKeyStorage.java index 937da47..04a1572 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/MapKeyStorage.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/MapKeyStorage.java @@ -1,48 +1,51 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.io.transport.mbus.wireless; - -import java.util.Collections; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; - -import org.openmuc.jmbus.SecondaryAddress; -import org.osgi.service.component.annotations.Component; - -/** - * Simplistic implementation of {@link KeyStorage} backed by Map. - * - * @author Łukasz Dywicki - Initial contribution. - */ -@Component -public class MapKeyStorage implements KeyStorage { - - private final Map keyMap = new ConcurrentHashMap<>(); - - @Override - public Optional lookupKey(byte[] address) { - return Optional.ofNullable(keyMap.get(createKey(address))); - } - - @Override - public void registerKey(byte[] address, byte[] key) { - keyMap.put(createKey(address), key); - } - - @Override - public Map toMap() { - return Collections.unmodifiableMap(keyMap); - } - - private SecondaryAddress createKey(byte[] address) { - return SecondaryAddress.newFromWMBusLlHeader(address, 0); - } - -} +/** + * Copyright (c) 2010-2021 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.io.transport.mbus.wireless; + +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +import org.openmuc.jmbus.SecondaryAddress; +import org.osgi.service.component.annotations.Component; + +/** + * Simplistic implementation of {@link KeyStorage} backed by Map. + * + * @author Łukasz Dywicki - Initial contribution. + */ +@Component +public class MapKeyStorage implements KeyStorage { + + private final Map keyMap = new ConcurrentHashMap<>(); + + @Override + public Optional lookupKey(byte[] address) { + return Optional.ofNullable(keyMap.get(createKey(address))); + } + + @Override + public void registerKey(byte[] address, byte[] key) { + keyMap.put(createKey(address), key); + } + + @Override + public Map toMap() { + return Collections.unmodifiableMap(keyMap); + } + + private SecondaryAddress createKey(byte[] address) { + return SecondaryAddress.newFromWMBusLlHeader(address, 0); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/AesCrypt.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/AesCrypt.java new file mode 100644 index 0000000..226fc43 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/AesCrypt.java @@ -0,0 +1,68 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus; + +import java.security.GeneralSecurityException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Arrays; + +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +class AesCrypt { + private final byte[] key; + private final byte[] iv; + + protected final SecretKeySpec skeySpec; + protected final AlgorithmParameterSpec paramSpec; + protected Cipher cipher; + + public static AesCrypt newAesCrypt(byte[] key, byte[] iv) throws DecodingException { + return new AesCrypt(key, iv, "AES/CBC/NoPadding"); + } + + public static AesCrypt newAesCtrCrypt(byte[] key, byte[] iv) throws DecodingException { + return new AesCrypt(key, iv, "AES/CTR/NoPadding"); + } + + private AesCrypt(byte[] key, byte[] iv, String cipherName) throws DecodingException { + try { + this.cipher = Cipher.getInstance(cipherName); + } catch (NoSuchPaddingException | NoSuchAlgorithmException e) { + throw new DecodingException(e); + } + + this.key = Arrays.copyOf(key, key.length); + this.iv = Arrays.copyOf(iv, iv.length); + this.skeySpec = new SecretKeySpec(this.key, "AES"); + this.paramSpec = new IvParameterSpec(this.iv); + } + + public byte[] encrypt(byte[] rawData, int length) throws GeneralSecurityException { + byte[] tempData = Arrays.copyOf(rawData, length); + + this.cipher.init(Cipher.ENCRYPT_MODE, skeySpec, paramSpec); + return this.cipher.doFinal(tempData); + } + + public byte[] decrypt(byte[] rawData, int length) throws DecodingException { + byte[] encrypted = rawData; + + if (length != 0) { + encrypted = Arrays.copyOf(rawData, length); + } + + try { + cipher.init(Cipher.DECRYPT_MODE, skeySpec, paramSpec); + return cipher.doFinal(encrypted); + } catch (GeneralSecurityException e) { + throw new DecodingException(e); + } + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/Bcd.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/Bcd.java new file mode 100644 index 0000000..b1316f9 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/Bcd.java @@ -0,0 +1,91 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus; + +/** + * This class represents a binary-coded decimal (BCD) number as defined by the M-Bus standard. The class provides + * methods to convert the BCD to other types such as double, int or String. + */ +public class Bcd extends Number { + + private static final long serialVersionUID = 790515601507532939L; + private final byte[] value; + + /** + * Constructs a Bcd from the given bytes. The constructed Bcd will use the given byte array for + * internal storage of its value. It is therefore recommended not to change the byte array after construction. + * + * @param bcdBytes + * the byte array to be used for construction of the Bcd. + */ + public Bcd(byte[] bcdBytes) { + this.value = bcdBytes; + } + + public byte[] getBytes() { + return value; + } + + @Override + public String toString() { + byte[] bytes = new byte[value.length * 2]; + int c = 0; + + if ((value[value.length - 1] & 0xf0) == 0xf0) { + bytes[c++] = 0x2d; + } else { + bytes[c++] = (byte) (((value[value.length - 1] >> 4) & 0x0f) + 48); + } + + bytes[c++] = (byte) ((value[value.length - 1] & 0x0f) + 48); + + for (int i = value.length - 2; i >= 0; i--) { + bytes[c++] = (byte) (((value[i] >> 4) & 0x0f) + 48); + bytes[c++] = (byte) ((value[i] & 0x0f) + 48); + } + + return new String(bytes); + } + + @Override + public double doubleValue() { + return longValue(); + } + + @Override + public float floatValue() { + return longValue(); + } + + @Override + public int intValue() { + return (int) longValue(); + } + + @Override + public long longValue() { + long result = 0l; + long factor = 1l; + + for (int i = 0; i < (value.length - 1); i++) { + result += (value[i] & 0x0f) * factor; + factor = factor * 10l; + result += ((value[i] >> 4) & 0x0f) * factor; + factor = factor * 10l; + } + + result += (value[value.length - 1] & 0x0f) * factor; + factor = factor * 10l; + + if ((value[value.length - 1] & 0xf0) == 0xf0) { + result = result * -1; + } else { + result += ((value[value.length - 1] >> 4) & 0x0f) * factor; + } + + return result; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/CRC16.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/CRC16.java new file mode 100644 index 0000000..379092d --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/CRC16.java @@ -0,0 +1,56 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus; + +import java.nio.ByteBuffer; + +/** + * 16 bit cyclic redundancy check implementation. + */ +class CRC16 { + + private static byte[] computeCrc(byte[] bytes, int poly, int initialValue, int xorValue) { + int i; + int crcVal = initialValue; + byte[] crc = new byte[2]; + + for (byte b : bytes) { + for (i = 0x80; i != 0; i >>= 1) { + if ((crcVal & 0x8000) != 0) { + crcVal = (crcVal << 1) ^ poly; + } else { + crcVal = crcVal << 1; + } + if ((b & i) != 0) { + crcVal ^= poly; + } + } + } + + byte[] tmpCrc = ByteBuffer.allocate(4).putInt(Integer.reverseBytes(crcVal & 0xffff ^ xorValue)).array(); + crc[0] = tmpCrc[0]; + crc[1] = tmpCrc[1]; + + return crc; + } + + /** + * Computes the CRC16 according EN13757. + * + * @param bytes + * the data to be checked. + * @return the CRC16 result. + */ + public static byte[] calculateCrc16(byte[] bytes) { + return computeCrc(bytes, 0x3D65, 0x0000, 0xFFFF); + } + + /** + * Do not let this class be instantiated. + */ + private CRC16() { + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DataRecord.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DataRecord.java index ed2b76b..afa4cec 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DataRecord.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DataRecord.java @@ -1,1378 +1,1381 @@ -package org.openmuc.jmbus; - -import static javax.xml.bind.DatatypeConverter.printHexBinary; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; -import java.util.Calendar; - -import org.openmuc.jmbus.Bcd; -import org.openmuc.jmbus.DecodingException; -import org.openmuc.jmbus.DlmsUnit; - -/** - * Representation of a data record (sometimes called variable data block). - * - * A data record is the basic data entity of the M-Bus application layer. A variable data structure contains a list of - * data records. Each data record represents a single data point. A data record consists of three fields: The data - * information block (DIB), the value information block (VIB) and the data field. - * - * The DIB codes the following parameters: - *

- * - * The VIB codes the following parameters: - *
    - *
  • Description - the meaning of the data value (e.g. "Energy", "Volume" etc.)
  • - *
  • Unit - the unit of the data value.
  • - *
  • Multiplier - a factor by which the data value coded in the data field has to be multiplied with. - * getScaledDataValue() returns the result of the data value multiplied with the multiplier.
  • - *
- * - */ -public class DataRecord { - - /** - * The data value type - * - */ - public enum DataValueType { - LONG, - DOUBLE, - DATE, - STRING, - BCD, - NONE; - } - - /** - * Function coded in the DIB - * - */ - public enum FunctionField { - /** - * instantaneous value - */ - INST_VAL, - /** - * maximum value - */ - MAX_VAL, - /** - * minimum value - */ - MIN_VAL, - /** - * value during error state - */ - ERROR_VAL; - } - - /** - * Data description stored in the VIB - * - */ - public enum Description { - ENERGY, - VOLUME, - MASS, - ON_TIME, - OPERATING_TIME, - POWER, - VOLUME_FLOW, - VOLUME_FLOW_EXT, - MASS_FLOW, - FLOW_TEMPERATURE, - RETURN_TEMPERATURE, - TEMPERATURE_DIFFERENCE, - EXTERNAL_TEMPERATURE, - PRESSURE, - DATE, - DATE_TIME, - VOLTAGE, - CURRENT, - AVERAGING_DURATION, - ACTUALITY_DURATION, - FABRICATION_NO, - MODEL_VERSION, - PARAMETER_SET_ID, - HARDWARE_VERSION, - FIRMWARE_VERSION, - ERROR_FLAGS, - CUSTOMER, - RESERVED, - OPERATING_TIME_BATTERY, - HCA, - REACTIVE_ENERGY, - TEMPERATURE_LIMIT, - MAX_POWER, - REACTIVE_POWER, - REL_HUMIDITY, - FREQUENCY, - PHASE, - EXTENDED_IDENTIFICATION, - ADDRESS, - NOT_SUPPORTED, - MANUFACTURER_SPECIFIC, - FUTURE_VALUE, - USER_DEFINED, - APPARENT_ENERGY, - CUSTOMER_LOCATION, - ACCSESS_CODE_OPERATOR, - ACCSESS_CODE_USER, - PASSWORD, - ACCSESS_CODE_SYSTEM_DEVELOPER, - OTHER_SOFTWARE_VERSION, - ACCSESS_CODE_SYSTEM_OPERATOR, - ERROR_MASK, - SECURITY_KEY, - DIGITAL_INPUT, - BAUDRATE, - DIGITAL_OUTPUT, - RESPONSE_DELAY_TIME, - RETRY, - FIRST_STORAGE_NUMBER_CYCLIC, - REMOTE_CONTROL, - LAST_STORAGE_NUMBER_CYCLIC, - SIZE_STORAGE_BLOCK, - STORAGE_INTERVALL, - TARIF_START, - DURATION_LAST_READOUT, - TIME_POINT, - TARIF_DURATION, - OPERATOR_SPECIFIC_DATA, - TARIF_PERIOD, - NUMBER_STOPS, - LAST_CUMULATION_DURATION, - SPECIAL_SUPPLIER_INFORMATION, - PARAMETER_ACTIVATION_STATE, - CONTROL_SIGNAL, - WEEK_NUMBER, - DAY_OF_WEEK, - REMAINING_BATTERY_LIFE_TIME, - TIME_POINT_DAY_CHANGE, - CUMULATION_COUNTER, - RESET_COUNTER; - } - - // // Data Information Block that contains a DIF and optionally up to 10 DIFEs - private byte[] dib; - // // Value Information Block that contains a VIF and optionally up to 10 VIFEs - private byte[] vib; - - private Object dataValue; - private DataValueType dataValueType; - - // DIB fields: - private FunctionField functionField; - - private long storageNumber; // max is 41 bits - private int tariff; // max 20 bits - private short subunit; // max 10 bits - - // VIB fields: - private Description description; - private String userDefinedDescription; - private int multiplierExponent = 0; - private DlmsUnit unit; - - private boolean dateTypeF = false; - private boolean dateTypeG = false; - - int dataLength; - - byte[] rawData; - - public byte[] getRawData() { - return rawData; - } - - int decode(byte[] buffer, int offset, int length) throws DecodingException { - int i = offset; - - decodeDib(buffer, i); - - int dataField = buffer[i] & 0x0f; - dataLength = dataField; - storageNumber = (buffer[i] & 0x40) >> 6; - - subunit = 0; - tariff = 0; - - int numDife = 0; - while ((buffer[i++] & 0x80) == 0x80) { - subunit += (((buffer[i] & 0x40) >> 6) << numDife); - tariff += ((buffer[i] & 0x30) >> 4) << (numDife * 2); - storageNumber += ((buffer[i] & 0x0f) << ((numDife * 4) + 1)); - numDife++; - } - - multiplierExponent = 0; - - unit = null; - - dib = Arrays.copyOfRange(buffer, offset, i); - - // decode VIB - - int vif = buffer[i++] & 0xff; - - boolean decodeFurtherVifs = false; - - if (vif == 0xfb) { - decodeAlternateExtendedVif(buffer[i]); - if ((buffer[i] & 0x80) == 0x80) { - decodeFurtherVifs = true; - } - i++; - } else if ((vif & 0x7f) == 0x7c) { - i += decodeUserDefinedVif(buffer, i); - if ((vif & 0x80) == 0x80) { - decodeFurtherVifs = true; - } - } else if (vif == 0xfd) { - decodeMainExtendedVif(buffer[i]); - if ((buffer[i] & 0x80) == 0x80) { - decodeFurtherVifs = true; - } - i++; - } else { - decodeMainVif(vif); - if ((vif & 0x80) == 0x80) { - decodeFurtherVifs = true; - } - } - - if (decodeFurtherVifs) { - while ((buffer[i++] & 0x80) == 0x80) { - // TODO these vifes should not be ignored! - } - } - - vib = Arrays.copyOfRange(buffer, offset + dib.length, i); - - switch (dataField) { - case 0x00: - case 0x08: /* no data - selection for readout request */ - dataValue = null; - dataValueType = DataValueType.NONE; - break; - case 0x01: /* INT8 */ - dataValue = Long.valueOf(buffer[i++]); - dataValueType = DataValueType.LONG; - break; - case 0x02: /* INT16 */ - if (dateTypeG) { - int day = (0x1f) & buffer[i]; - int year1 = ((0xe0) & buffer[i++]) >> 5; - int month = (0x0f) & buffer[i]; - int year2 = ((0xf0) & buffer[i++]) >> 1; - int year = (2000 + year1 + year2); - - Calendar calendar = Calendar.getInstance(); - - calendar.set(year, month - 1, day, 0, 0, 0); - - dataValue = calendar.getTime(); - dataValueType = DataValueType.DATE; - } else { - dataValue = Long.valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8)); - dataValueType = DataValueType.LONG; - } - break; - case 0x03: /* INT24 */ - if ((buffer[i + 2] & 0x80) == 0x80) { - // negative - dataValue = Long.valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) - | ((buffer[i++] & 0xff) << 16) | 0xff << 24); - } else { - dataValue = Long - .valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) | ((buffer[i++] & 0xff) << 16)); - } - dataValueType = DataValueType.LONG; - break; - case 0x04: /* INT32 */ - if (dateTypeF) { - int min = (buffer[i++] & 0x3f); - int hour = (buffer[i] & 0x1f); - int yearh = (0x60 & buffer[i++]) >> 5; - int day = (buffer[i] & 0x1f); - int year1 = (0xe0 & buffer[i++]) >> 5; - int mon = (buffer[i] & 0x0f); - int year2 = (0xf0 & buffer[i++]) >> 1; - - if (yearh == 0) { - yearh = 1; - } - - int year = 1900 + 100 * yearh + year1 + year2; - - Calendar calendar = Calendar.getInstance(); - - calendar.set(year, mon - 1, day, hour, min, 0); - - dataValue = calendar.getTime(); - dataValueType = DataValueType.DATE; - } else { - dataValue = Long.valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) - | ((buffer[i++] & 0xff) << 16) | ((buffer[i++] & 0xff) << 24)); - dataValueType = DataValueType.LONG; - } - break; - case 0x05: /* FLOAT32 */ - Float doubleDatavalue = ByteBuffer.wrap(buffer, i, 4).order(ByteOrder.LITTLE_ENDIAN).getFloat(); - i += 4; - dataValue = Double.valueOf(doubleDatavalue); - dataValueType = DataValueType.DOUBLE; - break; - case 0x06: /* INT48 */ - if ((buffer[i + 5] & 0x80) == 0x80) { - // negative - dataValue = Long - .valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) | ((buffer[i++] & 0xff) << 16) - | ((buffer[i++] & 0xff) << 24) | (((long) buffer[i++] & 0xff) << 32) - | (((long) buffer[i++] & 0xff) << 40) | (0xffl << 48) | (0xffl << 56)); - } else { - dataValue = Long.valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) - | ((buffer[i++] & 0xff) << 16) | ((buffer[i++] & 0xff) << 24) - | (((long) buffer[i++] & 0xff) << 32) | (((long) buffer[i++] & 0xff) << 40)); - } - dataValueType = DataValueType.LONG; - break; - case 0x07: /* INT64 */ - dataValue = Long.valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) - | ((buffer[i++] & 0xff) << 16) | ((buffer[i++] & 0xff) << 24) - | (((long) buffer[i++] & 0xff) << 32) | (((long) buffer[i++] & 0xff) << 40) - | (((long) buffer[i++] & 0xff) << 48) | (((long) buffer[i++] & 0xff) << 56)); - dataValueType = DataValueType.LONG; - break; - case 0x09: - i = setBCD(buffer, i, 1); - break; - case 0x0a: - i = setBCD(buffer, i, 2); - break; - case 0x0b: - i = setBCD(buffer, i, 3); - break; - case 0x0c: - i = setBCD(buffer, i, 4); - break; - case 0x0e: - i = setBCD(buffer, i, 6); - break; - case 0x0d: - - int variableLength = buffer[i++] & 0xff; - int dataLength0x0d; - - if (variableLength < 0xc0) { - dataLength0x0d = variableLength; - } else if ((variableLength >= 0xc0) && (variableLength <= 0xc9)) { - dataLength0x0d = 2 * (variableLength - 0xc0); - } else if ((variableLength >= 0xd0) && (variableLength <= 0xd9)) { - dataLength0x0d = 2 * (variableLength - 0xd0); - } else if ((variableLength >= 0xe0) && (variableLength <= 0xef)) { - dataLength0x0d = variableLength - 0xe0; - } else if (variableLength == 0xf8) { - dataLength0x0d = 4; - } else { - throw new DecodingException("Unsupported LVAR Field: " + variableLength); - } - - // TODO check this: - // if (variableLength >= 0xc0) { - // throw new DecodingException("Variable length (LVAR) field >= 0xc0: " + variableLength); - // } - - byte[] rawData = new byte[dataLength0x0d]; - - for (int j = 0; j < dataLength0x0d; j++) { - rawData[j] = buffer[i + dataLength0x0d - 1 - j]; - } - i += dataLength0x0d; - - this.rawData = rawData; - dataValue = new String(rawData); - dataValueType = DataValueType.STRING; - break; - default: - String msg = String.format("Unknown Data Field in DIF: %02X.", dataField); - throw new DecodingException(msg); - } - - return i; - } - - private int setBCD(byte[] buffer, int i, int j) { - dataValue = new Bcd(Arrays.copyOfRange(buffer, i, i + j)); - dataValueType = DataValueType.BCD; - return i + j; - } - - private void decodeDib(byte[] buffer, int i) { - int ff = ((buffer[i] & 0x30) >> 4); - switch (ff) { - case 0: - functionField = FunctionField.INST_VAL; - break; - case 1: - functionField = FunctionField.MAX_VAL; - break; - case 2: - functionField = FunctionField.MIN_VAL; - break; - case 3: - functionField = FunctionField.ERROR_VAL; - break; - default: - this.functionField = null; - } - } - - int encode(byte[] buffer, int offset) { - - int i = offset; - - System.arraycopy(dib, 0, buffer, i, dib.length); - - i += dib.length; - - System.arraycopy(vib, 0, buffer, i, vib.length); - - i += vib.length; - - return i - offset; - } - - /** - * Returns a byte array containing the DIB (i.e. the DIF and the DIFEs) contained in the data record. - * - * @return a byte array containing the DIB - */ - public byte[] getDib() { - return dib; - } - - /** - * Returns a byte array containing the VIB (i.e. the VIF and the VIFEs) contained in the data record. - * - * @return a byte array containing the VIB - */ - public byte[] getVib() { - return vib; - } - - /** - * Returns the decoded data field of the data record as an Object. The Object is of one of the four types Long, - * Double, String or Date depending on information coded in the DIB/VIB. The DataType can be checked using - * getDataValueType(). - * - * @return the data value - */ - public Object getDataValue() { - return dataValue; - } - - public DataValueType getDataValueType() { - return dataValueType; - } - - /** - * Returns the data (value) multiplied by the multiplier as a Double. If the data is not a number than null is - * returned. - * - * @return the data (value) multiplied by the multiplier as a Double - */ - public Double getScaledDataValue() { - try { - return ((Number) dataValue).doubleValue() * Math.pow(10, multiplierExponent); - } catch (ClassCastException e) { - return null; - } - } - - public FunctionField getFunctionField() { - return functionField; - } - - public long getStorageNumber() { - return storageNumber; - } - - public int getTariff() { - return tariff; - } - - public short getSubunit() { - return subunit; - } - - public Description getDescription() { - return description; - } - - public String getUserDefinedDescription() { - if (description == Description.USER_DEFINED) { - return userDefinedDescription; - } else { - return description.toString(); - } - } - - /** - * The multiplier is coded in the VIF. Is always a power of 10. This function returns the exponent. The base is - * always 10. - * - * @return the exponent of the multiplier. - */ - public int getMultiplierExponent() { - return multiplierExponent; - } - - public DlmsUnit getUnit() { - return unit; - } - - private void decodeTimeUnit(int vif) { - if ((vif & 0x02) == 0) { - if ((vif & 0x01) == 0) { - unit = DlmsUnit.SECOND; - } else { - unit = DlmsUnit.MIN; - } - } else { - if ((vif & 0x01) == 0) { - unit = DlmsUnit.HOUR; - } else { - unit = DlmsUnit.DAY; - } - } - } - - private int decodeUserDefinedVif(byte[] buffer, int offset) throws DecodingException { - - int length = buffer[offset]; - StringBuilder sb = new StringBuilder(); - for (int i = offset + length; i > offset; i--) { - sb.append((char) buffer[i]); - } - - description = Description.USER_DEFINED; - userDefinedDescription = sb.toString(); - - return length + 1; - - } - - private void decodeMainVif(int vif) { - description = Description.NOT_SUPPORTED; - - if ((vif & 0x40) == 0) { - decodeE0(vif); - } else { - decodeE1(vif); - - } - - } - - private void decodeE1(int vif) { - // E1 - if ((vif & 0x20) == 0) { - decodeE10(vif); - } else { - decodeE11(vif); - } - } - - private void decodeE11(int vif) { - // E11 - if ((vif & 0x10) == 0) { - decodeE110(vif); - } else { - decodeE111(vif); - } - } - - private void decodeE111(int vif) { - // E111 - if ((vif & 0x08) == 0) { - // E111 0 - if ((vif & 0x04) == 0) { - description = Description.AVERAGING_DURATION; - } else { - description = Description.ACTUALITY_DURATION; - } - decodeTimeUnit(vif); - } else { - // E111 1 - if ((vif & 0x04) == 0) { - // E111 10 - if ((vif & 0x02) == 0) { - // E111 100 - if ((vif & 0x01) == 0) { - // E111 1000 - description = Description.FABRICATION_NO; - } else { - // E111 1001 - description = Description.EXTENDED_IDENTIFICATION; - } - } else { - // E111 101 - if ((vif & 0x01) == 0) { - description = Description.ADDRESS; - } else { - // E111 1011 - // Codes used with extension indicator 0xFB (table 29 of DIN EN 13757-3:2011) - throw new IllegalArgumentException( - "Trying to decode a mainVIF even though it is an alternate extended vif"); - } - } - } else { - // E111 11 - if ((vif & 0x02) == 0) { - // E111 110 - if ((vif & 0x01) == 0) { - // E111 1100 - // Extension indicator 0xFC: VIF is given in following string - description = Description.NOT_SUPPORTED; - } else { - // E111 1101 - // Extension indicator 0xFD: main VIFE-code extension table (table 28 of DIN EN - // 13757-3:2011) - throw new IllegalArgumentException( - "Trying to decode a mainVIF even though it is a main extended vif"); - - } - } else { - // E111 111 - if ((vif & 0x01) == 0) { - // E111 1110 - description = Description.FUTURE_VALUE; - } else { - // E111 1111 - description = Description.MANUFACTURER_SPECIFIC; - } - } - } - } - } - - private void decodeE110(int vif) { - // E110 - if ((vif & 0x08) == 0) { - // E110 0 - if ((vif & 0x04) == 0) { - // E110 00 - description = Description.TEMPERATURE_DIFFERENCE; - multiplierExponent = (vif & 0x03) - 3; - unit = DlmsUnit.KELVIN; - } else { - // E110 01 - description = Description.EXTERNAL_TEMPERATURE; - multiplierExponent = (vif & 0x03) - 3; - unit = DlmsUnit.DEGREE_CELSIUS; - } - } else { - // E110 1 - if ((vif & 0x04) == 0) { - // E110 10 - description = Description.PRESSURE; - multiplierExponent = (vif & 0x03) - 3; - unit = DlmsUnit.BAR; - } else { - // E110 11 - if ((vif & 0x02) == 0) { - // E110 110 - if ((vif & 0x01) == 0) { - // E110 1100 - description = Description.DATE; - dateTypeG = true; - } else { - // E110 1101 - description = Description.DATE_TIME; - dateTypeF = true; - } - } else { - // E110 111 - if ((vif & 0x01) == 0) { - // E110 1110 - description = Description.HCA; - unit = DlmsUnit.RESERVED; - } else { - description = Description.NOT_SUPPORTED; - } - - } - - } - } - } - - private void decodeE10(int vif) { - // E10 - if ((vif & 0x10) == 0) { - // E100 - if ((vif & 0x08) == 0) { - // E100 0 - description = Description.VOLUME_FLOW_EXT; - multiplierExponent = (vif & 0x07) - 7; - unit = DlmsUnit.CUBIC_METRE_PER_MINUTE; - } else { - // E100 1 - description = Description.VOLUME_FLOW_EXT; - multiplierExponent = (vif & 0x07) - 9; - unit = DlmsUnit.CUBIC_METRE_PER_SECOND; - } - } else { - // E101 - if ((vif & 0x08) == 0) { - // E101 0 - description = Description.MASS_FLOW; - multiplierExponent = (vif & 0x07) - 3; - unit = DlmsUnit.KILOGRAM_PER_HOUR; - } else { - // E101 1 - if ((vif & 0x04) == 0) { - // E101 10 - description = Description.FLOW_TEMPERATURE; - multiplierExponent = (vif & 0x03) - 3; - unit = DlmsUnit.DEGREE_CELSIUS; - } else { - // E101 11 - description = Description.RETURN_TEMPERATURE; - multiplierExponent = (vif & 0x03) - 3; - unit = DlmsUnit.DEGREE_CELSIUS; - } - } - } - } - - private void decodeE0(int vif) { - // E0 - if ((vif & 0x20) == 0) { - decodeE00(vif); - } else { - decode01(vif); - } - } - - private void decode01(int vif) { - // E01 - if ((vif & 0x10) == 0) { - // E010 - if ((vif & 0x08) == 0) { - // E010 0 - if ((vif & 0x04) == 0) { - // E010 00 - description = Description.ON_TIME; - } else { - // E010 01 - description = Description.OPERATING_TIME; - } - decodeTimeUnit(vif); - } else { - // E010 1 - description = Description.POWER; - multiplierExponent = (vif & 0x07) - 3; - unit = DlmsUnit.WATT; - } - } else { - // E011 - if ((vif & 0x08) == 0) { - // E011 0 - description = Description.POWER; - multiplierExponent = vif & 0x07; - unit = DlmsUnit.JOULE_PER_HOUR; - } else { - // E011 1 - description = Description.VOLUME_FLOW; - multiplierExponent = (vif & 0x07) - 6; - unit = DlmsUnit.CUBIC_METRE_PER_HOUR; - } - } - } - - private void decodeE00(int vif) { - // E00 - if ((vif & 0x10) == 0) { - // E000 - if ((vif & 0x08) == 0) { - // E000 0 - description = Description.ENERGY; - multiplierExponent = (vif & 0x07) - 3; - unit = DlmsUnit.WATT_HOUR; - } else { - // E000 1 - description = Description.ENERGY; - multiplierExponent = vif & 0x07; - unit = DlmsUnit.JOULE; - } - } else { - // E001 - if ((vif & 0x08) == 0) { - // E001 0 - description = Description.VOLUME; - multiplierExponent = (vif & 0x07) - 6; - unit = DlmsUnit.CUBIC_METRE; - } else { - // E001 1 - description = Description.MASS; - multiplierExponent = (vif & 0x07) - 3; - unit = DlmsUnit.KILOGRAM; - } - } - } - - // implements table 28 of DIN EN 13757-3:2013 - private void decodeMainExtendedVif(byte vif) throws DecodingException { - if ((vif & 0x7f) == 0x0b) { // E000 1011 - description = Description.PARAMETER_SET_ID; - } else if ((vif & 0x7f) == 0x0c) { // E000 1100 - description = Description.MODEL_VERSION; - } else if ((vif & 0x7f) == 0x0d) { // E000 1101 - description = Description.HARDWARE_VERSION; - } else if ((vif & 0x7f) == 0x0e) { // E000 1110 - description = Description.FIRMWARE_VERSION; - } else if ((vif & 0x7f) == 0x0f) { // E000 1111 - description = Description.OTHER_SOFTWARE_VERSION; - } else if ((vif & 0x7f) == 0x10) { // E001 0000 - description = Description.CUSTOMER_LOCATION; - } else if ((vif & 0x7f) == 0x11) { // E001 0001 - description = Description.CUSTOMER; - } else if ((vif & 0x7f) == 0x12) { // E001 0010 - description = Description.ACCSESS_CODE_USER; - } else if ((vif & 0x7f) == 0x13) { // E001 0011 - description = Description.ACCSESS_CODE_OPERATOR; - } else if ((vif & 0x7f) == 0x14) { // E001 0100 - description = Description.ACCSESS_CODE_SYSTEM_OPERATOR; - } else if ((vif & 0x7f) == 0x15) { // E001 0101 - description = Description.ACCSESS_CODE_SYSTEM_DEVELOPER; - } else if ((vif & 0x7f) == 0x16) { // E001 0110 - description = Description.PASSWORD; - } else if ((vif & 0x7f) == 0x17) { // E001 0111 - description = Description.ERROR_FLAGS; - } else if ((vif & 0x7f) == 0x18) { // E001 1000 - description = Description.ERROR_MASK; - } else if ((vif & 0x7f) == 0x19) { // E001 1001 - description = Description.SECURITY_KEY; - } else if ((vif & 0x7f) == 0x1a) { // E001 1010 - description = Description.DIGITAL_OUTPUT; - } else if ((vif & 0x7f) == 0x1b) { // E001 1011 - description = Description.DIGITAL_INPUT; - } else if ((vif & 0x7f) == 0x1c) { // E001 1100 - description = Description.BAUDRATE; - } else if ((vif & 0x7f) == 0x1d) { // E001 1101 - description = Description.RESPONSE_DELAY_TIME; - } else if ((vif & 0x7f) == 0x1e) { // E001 1110 - description = Description.RETRY; - } else if ((vif & 0x7f) == 0x1f) { // E001 1111 - description = Description.REMOTE_CONTROL; - } else if ((vif & 0x7f) == 0x20) { // E010 0000 - description = Description.FIRST_STORAGE_NUMBER_CYCLIC; - } else if ((vif & 0x7f) == 0x21) { // E010 0001 - description = Description.LAST_STORAGE_NUMBER_CYCLIC; - } else if ((vif & 0x7f) == 0x22) { // E010 0010 - description = Description.SIZE_STORAGE_BLOCK; - } else if ((vif & 0x7f) == 0x23) { // E010 0011 - description = Description.RESERVED; - } else if ((vif & 0x7c) == 0x24) { // E010 01nn - description = Description.STORAGE_INTERVALL; - this.unit = unitFor(vif); - } else if ((vif & 0x7f) == 0x28) { // E010 1000 - description = Description.STORAGE_INTERVALL; - unit = DlmsUnit.MONTH; - } else if ((vif & 0x7f) == 0x29) { // E010 1001 - description = Description.STORAGE_INTERVALL; - unit = DlmsUnit.YEAR; - } else if ((vif & 0x7f) == 0x2a) { // E010 1010 - description = Description.OPERATOR_SPECIFIC_DATA; - } else if ((vif & 0x7f) == 0x2b) { // E010 1011 - description = Description.TIME_POINT; - unit = DlmsUnit.SECOND; - } else if ((vif & 0x7c) == 0x2c) { // E010 11nn - description = Description.DURATION_LAST_READOUT; - this.unit = unitFor(vif); - } else if ((vif & 0x7c) == 0x30) { // E011 00nn - description = Description.TARIF_DURATION; - switch (vif & 0x03) { - case 0: // E011 0000 - description = Description.NOT_SUPPORTED; // TODO: TARIF_START (Date/Time) - break; - default: - this.unit = unitFor(vif); - } - } else if ((vif & 0x7c) == 0x34) { // E011 01nn - description = Description.TARIF_PERIOD; - this.unit = unitFor(vif); - } else if ((vif & 0x7f) == 0x38) { // E011 1000 - description = Description.TARIF_PERIOD; - unit = DlmsUnit.MONTH; - } else if ((vif & 0x7f) == 0x39) { // E011 1001 - description = Description.TARIF_PERIOD; - unit = DlmsUnit.YEAR; - } else if ((vif & 0x70) == 0x40) { // E100 0000 - description = Description.VOLTAGE; - multiplierExponent = (vif & 0x0f) - 9; - unit = DlmsUnit.VOLT; - } else if ((vif & 0x70) == 0x50) { // E101 0000 - description = Description.CURRENT; - multiplierExponent = (vif & 0x0f) - 12; - unit = DlmsUnit.AMPERE; - } else if ((vif & 0x7f) == 0x60) { // E110 0000 - description = Description.RESET_COUNTER; - } else if ((vif & 0x7f) == 0x61) { // E110 0001 - description = Description.CUMULATION_COUNTER; - } else if ((vif & 0x7f) == 0x62) { // E110 0010 - description = Description.CONTROL_SIGNAL; - } else if ((vif & 0x7f) == 0x63) { // E110 0011 - description = Description.DAY_OF_WEEK; // 1 = Monday; 7 = Sunday; 0 = all Days - } else if ((vif & 0x7f) == 0x64) { // E110 0100 - description = Description.WEEK_NUMBER; - } else if ((vif & 0x7f) == 0x65) { // E110 0101 - description = Description.TIME_POINT_DAY_CHANGE; - } else if ((vif & 0x7f) == 0x66) { // E110 0110 - description = Description.PARAMETER_ACTIVATION_STATE; - } else if ((vif & 0x7f) == 0x67) { // E110 0111 - description = Description.SPECIAL_SUPPLIER_INFORMATION; - } else if ((vif & 0x7c) == 0x68) { // E110 10nn - description = Description.LAST_CUMULATION_DURATION; - this.unit = unitBiggerFor(vif); - } else if ((vif & 0x7c) == 0x6c) { // E110 11nn - description = Description.OPERATING_TIME_BATTERY; - this.unit = unitBiggerFor(vif); - } else if ((vif & 0x7f) == 0x70) { // E111 0000 - description = Description.NOT_SUPPORTED; // TODO: BATTERY_CHANGE_DATE_TIME - } else if ((vif & 0x7f) == 0x71) { // E111 0001 - description = Description.NOT_SUPPORTED; // TODO: RF_LEVEL dBm - } else if ((vif & 0x7f) == 0x72) { // E111 0010 - description = Description.NOT_SUPPORTED; // TODO: DAYLIGHT_SAVING (begin, ending, deviation) - } else if ((vif & 0x7f) == 0x73) { // E111 0011 - description = Description.NOT_SUPPORTED; // TODO: Listening window management data type L - } else if ((vif & 0x7f) == 0x74) { // E111 0100 - description = Description.REMAINING_BATTERY_LIFE_TIME; - unit = DlmsUnit.DAY; - } else if ((vif & 0x7f) == 0x75) { // E111 0101 - description = Description.NUMBER_STOPS; - } else if ((vif & 0x7f) == 0x76) { // E111 0110 - description = Description.MANUFACTURER_SPECIFIC; - } else if ((vif & 0x7f) >= 0x77) { // E111 0111 - E111 1111 - description = Description.RESERVED; - } else { - description = Description.NOT_SUPPORTED; - } - } - - private static DlmsUnit unitBiggerFor(byte vif) throws DecodingException { - int u = vif & 0x03; - switch (u) { - case 0: // E110 1100 - return DlmsUnit.HOUR; - case 1: // E110 1101 - return DlmsUnit.DAY; - case 2: // E110 1110 - return DlmsUnit.MONTH; - case 3: // E110 1111 - return DlmsUnit.YEAR; - default: - throw new DecodingException(String.format("Unknown unit 0x%02X.", u)); - } - } - - private static DlmsUnit unitFor(byte vif) throws DecodingException { - int u = vif & 0x03; - switch (u) { - case 0: // E010 1100 - return DlmsUnit.SECOND; - case 1: // E010 1101 - return DlmsUnit.MIN; - case 2: // E010 1110 - return DlmsUnit.HOUR; - case 3: // E010 1111 - return DlmsUnit.DAY; - default: - throw new DecodingException(String.format("Unknown unit 0x%02X.", u)); - } - } - - // implements table 29 of DIN EN 13757-3:2011 - private void decodeAlternateExtendedVif(byte vif) { - description = Description.NOT_SUPPORTED; // default value - - if ((vif & 0x40) == 0) { - // E0 - if ((vif & 0x20) == 0) { - // E00 - if ((vif & 0x10) == 0) { - // E000 - if ((vif & 0x08) == 0) { - // E000 0 - if ((vif & 0x04) == 0) { - // E000 00 - if ((vif & 0x02) == 0) { - // E000 000 - description = Description.ENERGY; - multiplierExponent = 5 + (vif & 0x01); - unit = DlmsUnit.WATT_HOUR; - } else { - // E000 001 - description = Description.REACTIVE_ENERGY; - multiplierExponent = 3 + (vif & 0x01); - unit = DlmsUnit.VAR_HOUR; - } - - } else { - // E000 01 - if ((vif & 0x02) == 0) { - // E000 010 - description = Description.APPARENT_ENERGY; - multiplierExponent = 3 + (vif & 0x01); - unit = DlmsUnit.VOLT_AMPERE_HOUR; - } else { - // E000 011 - description = Description.NOT_SUPPORTED; - } - } - } else { - // E000 1 - if ((vif & 0x04) == 0) { - // E000 10 - if ((vif & 0x02) == 0) { - // E000 100 - description = Description.ENERGY; - multiplierExponent = 8 + (vif & 0x01); - unit = DlmsUnit.JOULE; - } else { - // E000 101 - description = Description.NOT_SUPPORTED; - } - - } else { - // E000 11 - description = Description.ENERGY; - multiplierExponent = 5 + (vif & 0x03); - unit = DlmsUnit.CALORIFIC_VALUE; - } - } - } else { - // E001 - if ((vif & 0x08) == 0) { - // E001 0 - if ((vif & 0x04) == 0) { - // E001 00 - if ((vif & 0x02) == 0) { - // E001 000 - description = Description.VOLUME; - multiplierExponent = 2 + (vif & 0x01); - unit = DlmsUnit.CUBIC_METRE; - } else { - // E001 001 - description = Description.NOT_SUPPORTED; - } - } else { - // E001 01 - description = Description.REACTIVE_POWER; - multiplierExponent = (vif & 0x03); - unit = DlmsUnit.VAR; - } - } else { - // E001 1 - if ((vif & 0x04) == 0) { - // E001 10 - if ((vif & 0x02) == 0) { - // E001 100 - description = Description.MASS; - multiplierExponent = 5 + (vif & 0x01); - unit = DlmsUnit.KILOGRAM; - } else { - // E001 101 - description = Description.REL_HUMIDITY; - multiplierExponent = -1 + (vif & 0x01); - unit = DlmsUnit.PERCENTAGE; - } - - } else { - // E001 11 - description = Description.NOT_SUPPORTED; - } - } - - } - } else { - // E01 - if ((vif & 0x10) == 0) { - // E010 - if ((vif & 0x08) == 0) { - // E010 0 - if ((vif & 0x04) == 0) { - // E010 00 - if ((vif & 0x02) == 0) { - // E010 000 - if ((vif & 0x01) == 0) { - // E010 0000 - description = Description.VOLUME; - multiplierExponent = 0; - unit = DlmsUnit.CUBIC_FEET; - } else { - // E010 0001 - description = Description.VOLUME; - multiplierExponent = -1; - unit = DlmsUnit.CUBIC_FEET; - } - } else { - // E010 001 - // outdated value ! - description = Description.VOLUME; - multiplierExponent = -1 + (vif & 0x01); - unit = DlmsUnit.US_GALLON; - } - } else { - // E010 01 - if ((vif & 0x02) == 0) { - // E010 010 - if ((vif & 0x01) == 0) { - // E010 0100 - // outdated value ! - description = Description.VOLUME_FLOW; - multiplierExponent = -3; - unit = DlmsUnit.US_GALLON_PER_MINUTE; - } else { - // E010 0101 - // outdated value ! - description = Description.VOLUME_FLOW; - multiplierExponent = 0; - unit = DlmsUnit.US_GALLON_PER_MINUTE; - } - } else { - // E010 011 - if ((vif & 0x01) == 0) { - // E010 0110 - // outdated value ! - description = Description.VOLUME_FLOW; - multiplierExponent = 0; - unit = DlmsUnit.US_GALLON_PER_HOUR; - } else { - // E010 0111 - description = Description.NOT_SUPPORTED; - } - } - - } - } else { - // E010 1 - if ((vif & 0x04) == 0) { - // E010 10 - if ((vif & 0x02) == 0) { - // E010 100 - description = Description.POWER; - multiplierExponent = 5 + (vif & 0x01); - unit = DlmsUnit.WATT; - } else { - if ((vif & 0x01) == 0) { - // E010 1010 - description = Description.PHASE; - multiplierExponent = -1; // is -1 or 0 correct ?? - unit = DlmsUnit.DEGREE; - } - // TODO same - // else { - // // E010 1011 - // description = Description.PHASE; - // multiplierExponent = -1; // is -1 or 0 correct ?? - // unit = DlmsUnit.DEGREE; - // } - } - } else { - // E010 11 - description = Description.FREQUENCY; - multiplierExponent = -3 + (vif & 0x03); - unit = DlmsUnit.HERTZ; - } - } - } else { - // E011 - if ((vif & 0x08) == 0) { - // E011 0 - if ((vif & 0x04) == 0) { - // E011 00 - if ((vif & 0x02) == 0) { - // E011 000 - description = Description.POWER; - multiplierExponent = 8 + (vif & 0x01); - unit = DlmsUnit.JOULE_PER_HOUR; - } else { - // E011 001 - description = Description.NOT_SUPPORTED; - } - } else { - // E011 01 - description = Description.APPARENT_ENERGY; - multiplierExponent = (vif & 0x03); - unit = DlmsUnit.VOLT_AMPERE; - } - } else { - // E011 1 - description = Description.NOT_SUPPORTED; - } - } - } - } else { - // E1 - if ((vif & 0x20) == 0) { - // E10 - if ((vif & 0x10) == 0) { - // E100 - description = Description.NOT_SUPPORTED; - } else { - // E101 - if ((vif & 0x08) == 0) { - // E101 0 - description = Description.NOT_SUPPORTED; - } else { - // E101 1 - if ((vif & 0x04) == 0) { - // E101 10 - // outdated value ! - description = Description.FLOW_TEMPERATURE; - multiplierExponent = (vif & 0x03) - 3; - unit = DlmsUnit.DEGREE_FAHRENHEIT; - } else { - // E101 11 - // outdated value ! - description = Description.RETURN_TEMPERATURE; - multiplierExponent = (vif & 0x03) - 3; - unit = DlmsUnit.DEGREE_FAHRENHEIT; - } - } - } - } else { - // E11 - if ((vif & 0x10) == 0) { - // E110 - if ((vif & 0x08) == 0) { - // E110 0 - if ((vif & 0x04) == 0) { - // E110 00 - // outdated value ! - description = Description.TEMPERATURE_DIFFERENCE; - multiplierExponent = (vif & 0x03) - 3; - unit = DlmsUnit.DEGREE_FAHRENHEIT; - } else { - // E110 01 - // outdated value ! - description = Description.FLOW_TEMPERATURE; - multiplierExponent = (vif & 0x03) - 3; - unit = DlmsUnit.DEGREE_FAHRENHEIT; - } - } else { - // E110 1 - description = Description.NOT_SUPPORTED; - } - } else { - // E111 - if ((vif & 0x08) == 0) { - // E111 0 - if ((vif & 0x04) == 0) { - // E111 00 - // outdated value ! - description = Description.TEMPERATURE_LIMIT; - multiplierExponent = (vif & 0x03) - 3; - unit = DlmsUnit.DEGREE_FAHRENHEIT; - } else { - // E111 01 - description = Description.TEMPERATURE_LIMIT; - multiplierExponent = (vif & 0x03) - 3; - unit = DlmsUnit.DEGREE_CELSIUS; - } - } else { - // E111 1 - description = Description.MAX_POWER; - multiplierExponent = (vif & 0x07) - 3; - unit = DlmsUnit.WATT; - } - } - } - - } - - } - - @Override - public String toString() { - - StringBuilder builder = new StringBuilder().append("DIB:").append(printHexBinary(dib)).append(", VIB:") - .append(printHexBinary(vib)).append(" -> descr:").append(description); - - if (description == Description.USER_DEFINED) { - builder.append(" :").append(getUserDefinedDescription()); - } - builder.append(", function:").append(functionField); - - if (storageNumber > 0) { - builder.append(", storage:").append(storageNumber); - } - - if (tariff > 0) { - builder.append(", tariff:").append(tariff); - } - - if (subunit > 0) { - builder.append(", subunit:").append(subunit); - } - - final String valuePlacHolder = ", value:"; - final String scaledValueString = ", scaled value:"; - - switch (dataValueType) { - case DATE: - case STRING: - builder.append(valuePlacHolder).append((dataValue).toString()); - break; - case DOUBLE: - builder.append(scaledValueString).append(getScaledDataValue()); - break; - case LONG: - if (multiplierExponent == 0) { - builder.append(valuePlacHolder).append(dataValue); - } else { - builder.append(scaledValueString).append(getScaledDataValue()); - } - break; - case BCD: - if (multiplierExponent == 0) { - builder.append(valuePlacHolder).append((dataValue).toString()); - } else { - builder.append(scaledValueString).append(getScaledDataValue()); - } - break; - case NONE: - builder.append(", value:NONE"); - break; - } - - if (unit != null) { - builder.append(", unit:").append(unit); - if (!unit.getUnit().isEmpty()) { - builder.append(", ").append(unit.getUnit()); - } - } - - return builder.toString(); - - } - - public int getDataLength() { - return dataLength; - } - -} +/** + * Copyright (c) 2010-2021 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.openmuc.jmbus; + +import static javax.xml.bind.DatatypeConverter.printHexBinary; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.Calendar; + +/** + * Representation of a data record (sometimes called variable data block). + * + * A data record is the basic data entity of the M-Bus application layer. A variable data structure contains a list of + * data records. Each data record represents a single data point. A data record consists of three fields: The data + * information block (DIB), the value information block (VIB) and the data field. + * + * The DIB codes the following parameters: + *
    + *
  • Storage number - a meter can have several storages e.g. to store historical time series data. The storage number + * 0 signals an actual value.
  • + *
  • Function - data can have the following four function types: instantaneous value, max value, min value, value + * during error state.
  • + *
  • Length and coding of the data field.
  • + *
  • Tariff - indicates the tariff number of this data field. The data of tariff 0 is usually the sum of all other + * tariffs.
  • + *
  • Subunit - can be used by a slave to distinguish several subunits of the metering device
  • + *
+ * + * The VIB codes the following parameters: + *
    + *
  • Description - the meaning of the data value (e.g. "Energy", "Volume" etc.)
  • + *
  • Unit - the unit of the data value.
  • + *
  • Multiplier - a factor by which the data value coded in the data field has to be multiplied with. + * getScaledDataValue() returns the result of the data value multiplied with the multiplier.
  • + *
+ * + */ +public class DataRecord { + + /** + * The data value type + * + */ + public enum DataValueType { + LONG, + DOUBLE, + DATE, + STRING, + BCD, + NONE; + } + + /** + * Function coded in the DIB + * + */ + public enum FunctionField { + /** + * instantaneous value + */ + INST_VAL, + /** + * maximum value + */ + MAX_VAL, + /** + * minimum value + */ + MIN_VAL, + /** + * value during error state + */ + ERROR_VAL; + } + + /** + * Data description stored in the VIB + * + */ + public enum Description { + ENERGY, + VOLUME, + MASS, + ON_TIME, + OPERATING_TIME, + POWER, + VOLUME_FLOW, + VOLUME_FLOW_EXT, + MASS_FLOW, + FLOW_TEMPERATURE, + RETURN_TEMPERATURE, + TEMPERATURE_DIFFERENCE, + EXTERNAL_TEMPERATURE, + PRESSURE, + DATE, + DATE_TIME, + VOLTAGE, + CURRENT, + AVERAGING_DURATION, + ACTUALITY_DURATION, + FABRICATION_NO, + MODEL_VERSION, + PARAMETER_SET_ID, + HARDWARE_VERSION, + FIRMWARE_VERSION, + ERROR_FLAGS, + CUSTOMER, + RESERVED, + OPERATING_TIME_BATTERY, + HCA, + REACTIVE_ENERGY, + TEMPERATURE_LIMIT, + MAX_POWER, + REACTIVE_POWER, + REL_HUMIDITY, + FREQUENCY, + PHASE, + EXTENDED_IDENTIFICATION, + ADDRESS, + NOT_SUPPORTED, + MANUFACTURER_SPECIFIC, + FUTURE_VALUE, + USER_DEFINED, + APPARENT_ENERGY, + CUSTOMER_LOCATION, + ACCSESS_CODE_OPERATOR, + ACCSESS_CODE_USER, + PASSWORD, + ACCSESS_CODE_SYSTEM_DEVELOPER, + OTHER_SOFTWARE_VERSION, + ACCSESS_CODE_SYSTEM_OPERATOR, + ERROR_MASK, + SECURITY_KEY, + DIGITAL_INPUT, + BAUDRATE, + DIGITAL_OUTPUT, + RESPONSE_DELAY_TIME, + RETRY, + FIRST_STORAGE_NUMBER_CYCLIC, + REMOTE_CONTROL, + LAST_STORAGE_NUMBER_CYCLIC, + SIZE_STORAGE_BLOCK, + STORAGE_INTERVALL, + TARIF_START, + DURATION_LAST_READOUT, + TIME_POINT, + TARIF_DURATION, + OPERATOR_SPECIFIC_DATA, + TARIF_PERIOD, + NUMBER_STOPS, + LAST_CUMULATION_DURATION, + SPECIAL_SUPPLIER_INFORMATION, + PARAMETER_ACTIVATION_STATE, + CONTROL_SIGNAL, + WEEK_NUMBER, + DAY_OF_WEEK, + REMAINING_BATTERY_LIFE_TIME, + TIME_POINT_DAY_CHANGE, + CUMULATION_COUNTER, + RESET_COUNTER; + } + + // // Data Information Block that contains a DIF and optionally up to 10 DIFEs + private byte[] dib; + // // Value Information Block that contains a VIF and optionally up to 10 VIFEs + private byte[] vib; + + private Object dataValue; + private DataValueType dataValueType; + + // DIB fields: + private FunctionField functionField; + + private long storageNumber; // max is 41 bits + private int tariff; // max 20 bits + private short subunit; // max 10 bits + + // VIB fields: + private Description description; + private String userDefinedDescription; + private int multiplierExponent = 0; + private DlmsUnit unit; + + private boolean dateTypeF = false; + private boolean dateTypeG = false; + + int dataLength; + + byte[] rawData; + + public byte[] getRawData() { + return rawData; + } + + int decode(byte[] buffer, int offset, int length) throws DecodingException { + int i = offset; + + decodeDib(buffer, i); + + int dataField = buffer[i] & 0x0f; + dataLength = dataField; + storageNumber = (buffer[i] & 0x40) >> 6; + + subunit = 0; + tariff = 0; + + int numDife = 0; + while ((buffer[i++] & 0x80) == 0x80) { + subunit += (((buffer[i] & 0x40) >> 6) << numDife); + tariff += ((buffer[i] & 0x30) >> 4) << (numDife * 2); + storageNumber += ((buffer[i] & 0x0f) << ((numDife * 4) + 1)); + numDife++; + } + + multiplierExponent = 0; + + unit = null; + + dib = Arrays.copyOfRange(buffer, offset, i); + + // decode VIB + + int vif = buffer[i++] & 0xff; + + boolean decodeFurtherVifs = false; + + if (vif == 0xfb) { + decodeAlternateExtendedVif(buffer[i]); + if ((buffer[i] & 0x80) == 0x80) { + decodeFurtherVifs = true; + } + i++; + } else if ((vif & 0x7f) == 0x7c) { + i += decodeUserDefinedVif(buffer, i); + if ((vif & 0x80) == 0x80) { + decodeFurtherVifs = true; + } + } else if (vif == 0xfd) { + decodeMainExtendedVif(buffer[i]); + if ((buffer[i] & 0x80) == 0x80) { + decodeFurtherVifs = true; + } + i++; + } else { + decodeMainVif(vif); + if ((vif & 0x80) == 0x80) { + decodeFurtherVifs = true; + } + } + + if (decodeFurtherVifs) { + while ((buffer[i++] & 0x80) == 0x80) { + // TODO these vifes should not be ignored! + } + } + + vib = Arrays.copyOfRange(buffer, offset + dib.length, i); + + switch (dataField) { + case 0x00: + case 0x08: /* no data - selection for readout request */ + dataValue = null; + dataValueType = DataValueType.NONE; + break; + case 0x01: /* INT8 */ + dataValue = Long.valueOf(buffer[i++]); + dataValueType = DataValueType.LONG; + break; + case 0x02: /* INT16 */ + if (dateTypeG) { + int day = (0x1f) & buffer[i]; + int year1 = ((0xe0) & buffer[i++]) >> 5; + int month = (0x0f) & buffer[i]; + int year2 = ((0xf0) & buffer[i++]) >> 1; + int year = (2000 + year1 + year2); + + Calendar calendar = Calendar.getInstance(); + + calendar.set(year, month - 1, day, 0, 0, 0); + + dataValue = calendar.getTime(); + dataValueType = DataValueType.DATE; + } else { + dataValue = Long.valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8)); + dataValueType = DataValueType.LONG; + } + break; + case 0x03: /* INT24 */ + if ((buffer[i + 2] & 0x80) == 0x80) { + // negative + dataValue = Long.valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) + | ((buffer[i++] & 0xff) << 16) | 0xff << 24); + } else { + dataValue = Long + .valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) | ((buffer[i++] & 0xff) << 16)); + } + dataValueType = DataValueType.LONG; + break; + case 0x04: /* INT32 */ + if (dateTypeF) { + int min = (buffer[i++] & 0x3f); + int hour = (buffer[i] & 0x1f); + int yearh = (0x60 & buffer[i++]) >> 5; + int day = (buffer[i] & 0x1f); + int year1 = (0xe0 & buffer[i++]) >> 5; + int mon = (buffer[i] & 0x0f); + int year2 = (0xf0 & buffer[i++]) >> 1; + + if (yearh == 0) { + yearh = 1; + } + + int year = 1900 + 100 * yearh + year1 + year2; + + Calendar calendar = Calendar.getInstance(); + + calendar.set(year, mon - 1, day, hour, min, 0); + + dataValue = calendar.getTime(); + dataValueType = DataValueType.DATE; + } else { + dataValue = Long.valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) + | ((buffer[i++] & 0xff) << 16) | ((buffer[i++] & 0xff) << 24)); + dataValueType = DataValueType.LONG; + } + break; + case 0x05: /* FLOAT32 */ + Float doubleDatavalue = ByteBuffer.wrap(buffer, i, 4).order(ByteOrder.LITTLE_ENDIAN).getFloat(); + i += 4; + dataValue = Double.valueOf(doubleDatavalue); + dataValueType = DataValueType.DOUBLE; + break; + case 0x06: /* INT48 */ + if ((buffer[i + 5] & 0x80) == 0x80) { + // negative + dataValue = Long + .valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) | ((buffer[i++] & 0xff) << 16) + | ((buffer[i++] & 0xff) << 24) | (((long) buffer[i++] & 0xff) << 32) + | (((long) buffer[i++] & 0xff) << 40) | (0xffl << 48) | (0xffl << 56)); + } else { + dataValue = Long.valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) + | ((buffer[i++] & 0xff) << 16) | ((buffer[i++] & 0xff) << 24) + | (((long) buffer[i++] & 0xff) << 32) | (((long) buffer[i++] & 0xff) << 40)); + } + dataValueType = DataValueType.LONG; + break; + case 0x07: /* INT64 */ + dataValue = Long.valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) + | ((buffer[i++] & 0xff) << 16) | ((buffer[i++] & 0xff) << 24) + | (((long) buffer[i++] & 0xff) << 32) | (((long) buffer[i++] & 0xff) << 40) + | (((long) buffer[i++] & 0xff) << 48) | (((long) buffer[i++] & 0xff) << 56)); + dataValueType = DataValueType.LONG; + break; + case 0x09: + i = setBCD(buffer, i, 1); + break; + case 0x0a: + i = setBCD(buffer, i, 2); + break; + case 0x0b: + i = setBCD(buffer, i, 3); + break; + case 0x0c: + i = setBCD(buffer, i, 4); + break; + case 0x0e: + i = setBCD(buffer, i, 6); + break; + case 0x0d: + + int variableLength = buffer[i++] & 0xff; + int dataLength0x0d; + + if (variableLength < 0xc0) { + dataLength0x0d = variableLength; + } else if ((variableLength >= 0xc0) && (variableLength <= 0xc9)) { + dataLength0x0d = 2 * (variableLength - 0xc0); + } else if ((variableLength >= 0xd0) && (variableLength <= 0xd9)) { + dataLength0x0d = 2 * (variableLength - 0xd0); + } else if ((variableLength >= 0xe0) && (variableLength <= 0xef)) { + dataLength0x0d = variableLength - 0xe0; + } else if (variableLength == 0xf8) { + dataLength0x0d = 4; + } else { + throw new DecodingException("Unsupported LVAR Field: " + variableLength); + } + + // TODO check this: + // if (variableLength >= 0xc0) { + // throw new DecodingException("Variable length (LVAR) field >= 0xc0: " + variableLength); + // } + + byte[] rawData = new byte[dataLength0x0d]; + + for (int j = 0; j < dataLength0x0d; j++) { + rawData[j] = buffer[i + dataLength0x0d - 1 - j]; + } + i += dataLength0x0d; + + this.rawData = rawData; + dataValue = new String(rawData); + dataValueType = DataValueType.STRING; + break; + default: + String msg = String.format("Unknown Data Field in DIF: %02X.", dataField); + throw new DecodingException(msg); + } + + return i; + } + + private int setBCD(byte[] buffer, int i, int j) { + dataValue = new Bcd(Arrays.copyOfRange(buffer, i, i + j)); + dataValueType = DataValueType.BCD; + return i + j; + } + + private void decodeDib(byte[] buffer, int i) { + int ff = ((buffer[i] & 0x30) >> 4); + switch (ff) { + case 0: + functionField = FunctionField.INST_VAL; + break; + case 1: + functionField = FunctionField.MAX_VAL; + break; + case 2: + functionField = FunctionField.MIN_VAL; + break; + case 3: + functionField = FunctionField.ERROR_VAL; + break; + default: + this.functionField = null; + } + } + + int encode(byte[] buffer, int offset) { + + int i = offset; + + System.arraycopy(dib, 0, buffer, i, dib.length); + + i += dib.length; + + System.arraycopy(vib, 0, buffer, i, vib.length); + + i += vib.length; + + return i - offset; + } + + /** + * Returns a byte array containing the DIB (i.e. the DIF and the DIFEs) contained in the data record. + * + * @return a byte array containing the DIB + */ + public byte[] getDib() { + return dib; + } + + /** + * Returns a byte array containing the VIB (i.e. the VIF and the VIFEs) contained in the data record. + * + * @return a byte array containing the VIB + */ + public byte[] getVib() { + return vib; + } + + /** + * Returns the decoded data field of the data record as an Object. The Object is of one of the four types Long, + * Double, String or Date depending on information coded in the DIB/VIB. The DataType can be checked using + * getDataValueType(). + * + * @return the data value + */ + public Object getDataValue() { + return dataValue; + } + + public DataValueType getDataValueType() { + return dataValueType; + } + + /** + * Returns the data (value) multiplied by the multiplier as a Double. If the data is not a number than null is + * returned. + * + * @return the data (value) multiplied by the multiplier as a Double + */ + public Double getScaledDataValue() { + try { + return ((Number) dataValue).doubleValue() * Math.pow(10, multiplierExponent); + } catch (ClassCastException e) { + return null; + } + } + + public FunctionField getFunctionField() { + return functionField; + } + + public long getStorageNumber() { + return storageNumber; + } + + public int getTariff() { + return tariff; + } + + public short getSubunit() { + return subunit; + } + + public Description getDescription() { + return description; + } + + public String getUserDefinedDescription() { + if (description == Description.USER_DEFINED) { + return userDefinedDescription; + } else { + return description.toString(); + } + } + + /** + * The multiplier is coded in the VIF. Is always a power of 10. This function returns the exponent. The base is + * always 10. + * + * @return the exponent of the multiplier. + */ + public int getMultiplierExponent() { + return multiplierExponent; + } + + public DlmsUnit getUnit() { + return unit; + } + + private void decodeTimeUnit(int vif) { + if ((vif & 0x02) == 0) { + if ((vif & 0x01) == 0) { + unit = DlmsUnit.SECOND; + } else { + unit = DlmsUnit.MIN; + } + } else { + if ((vif & 0x01) == 0) { + unit = DlmsUnit.HOUR; + } else { + unit = DlmsUnit.DAY; + } + } + } + + private int decodeUserDefinedVif(byte[] buffer, int offset) throws DecodingException { + + int length = buffer[offset]; + StringBuilder sb = new StringBuilder(); + for (int i = offset + length; i > offset; i--) { + sb.append((char) buffer[i]); + } + + description = Description.USER_DEFINED; + userDefinedDescription = sb.toString(); + + return length + 1; + } + + private void decodeMainVif(int vif) { + description = Description.NOT_SUPPORTED; + + if ((vif & 0x40) == 0) { + decodeE0(vif); + } else { + decodeE1(vif); + + } + } + + private void decodeE1(int vif) { + // E1 + if ((vif & 0x20) == 0) { + decodeE10(vif); + } else { + decodeE11(vif); + } + } + + private void decodeE11(int vif) { + // E11 + if ((vif & 0x10) == 0) { + decodeE110(vif); + } else { + decodeE111(vif); + } + } + + private void decodeE111(int vif) { + // E111 + if ((vif & 0x08) == 0) { + // E111 0 + if ((vif & 0x04) == 0) { + description = Description.AVERAGING_DURATION; + } else { + description = Description.ACTUALITY_DURATION; + } + decodeTimeUnit(vif); + } else { + // E111 1 + if ((vif & 0x04) == 0) { + // E111 10 + if ((vif & 0x02) == 0) { + // E111 100 + if ((vif & 0x01) == 0) { + // E111 1000 + description = Description.FABRICATION_NO; + } else { + // E111 1001 + description = Description.EXTENDED_IDENTIFICATION; + } + } else { + // E111 101 + if ((vif & 0x01) == 0) { + description = Description.ADDRESS; + } else { + // E111 1011 + // Codes used with extension indicator 0xFB (table 29 of DIN EN 13757-3:2011) + throw new IllegalArgumentException( + "Trying to decode a mainVIF even though it is an alternate extended vif"); + } + } + } else { + // E111 11 + if ((vif & 0x02) == 0) { + // E111 110 + if ((vif & 0x01) == 0) { + // E111 1100 + // Extension indicator 0xFC: VIF is given in following string + description = Description.NOT_SUPPORTED; + } else { + // E111 1101 + // Extension indicator 0xFD: main VIFE-code extension table (table 28 of DIN EN + // 13757-3:2011) + throw new IllegalArgumentException( + "Trying to decode a mainVIF even though it is a main extended vif"); + + } + } else { + // E111 111 + if ((vif & 0x01) == 0) { + // E111 1110 + description = Description.FUTURE_VALUE; + } else { + // E111 1111 + description = Description.MANUFACTURER_SPECIFIC; + } + } + } + } + } + + private void decodeE110(int vif) { + // E110 + if ((vif & 0x08) == 0) { + // E110 0 + if ((vif & 0x04) == 0) { + // E110 00 + description = Description.TEMPERATURE_DIFFERENCE; + multiplierExponent = (vif & 0x03) - 3; + unit = DlmsUnit.KELVIN; + } else { + // E110 01 + description = Description.EXTERNAL_TEMPERATURE; + multiplierExponent = (vif & 0x03) - 3; + unit = DlmsUnit.DEGREE_CELSIUS; + } + } else { + // E110 1 + if ((vif & 0x04) == 0) { + // E110 10 + description = Description.PRESSURE; + multiplierExponent = (vif & 0x03) - 3; + unit = DlmsUnit.BAR; + } else { + // E110 11 + if ((vif & 0x02) == 0) { + // E110 110 + if ((vif & 0x01) == 0) { + // E110 1100 + description = Description.DATE; + dateTypeG = true; + } else { + // E110 1101 + description = Description.DATE_TIME; + dateTypeF = true; + } + } else { + // E110 111 + if ((vif & 0x01) == 0) { + // E110 1110 + description = Description.HCA; + unit = DlmsUnit.RESERVED; + } else { + description = Description.NOT_SUPPORTED; + } + + } + + } + } + } + + private void decodeE10(int vif) { + // E10 + if ((vif & 0x10) == 0) { + // E100 + if ((vif & 0x08) == 0) { + // E100 0 + description = Description.VOLUME_FLOW_EXT; + multiplierExponent = (vif & 0x07) - 7; + unit = DlmsUnit.CUBIC_METRE_PER_MINUTE; + } else { + // E100 1 + description = Description.VOLUME_FLOW_EXT; + multiplierExponent = (vif & 0x07) - 9; + unit = DlmsUnit.CUBIC_METRE_PER_SECOND; + } + } else { + // E101 + if ((vif & 0x08) == 0) { + // E101 0 + description = Description.MASS_FLOW; + multiplierExponent = (vif & 0x07) - 3; + unit = DlmsUnit.KILOGRAM_PER_HOUR; + } else { + // E101 1 + if ((vif & 0x04) == 0) { + // E101 10 + description = Description.FLOW_TEMPERATURE; + multiplierExponent = (vif & 0x03) - 3; + unit = DlmsUnit.DEGREE_CELSIUS; + } else { + // E101 11 + description = Description.RETURN_TEMPERATURE; + multiplierExponent = (vif & 0x03) - 3; + unit = DlmsUnit.DEGREE_CELSIUS; + } + } + } + } + + private void decodeE0(int vif) { + // E0 + if ((vif & 0x20) == 0) { + decodeE00(vif); + } else { + decode01(vif); + } + } + + private void decode01(int vif) { + // E01 + if ((vif & 0x10) == 0) { + // E010 + if ((vif & 0x08) == 0) { + // E010 0 + if ((vif & 0x04) == 0) { + // E010 00 + description = Description.ON_TIME; + } else { + // E010 01 + description = Description.OPERATING_TIME; + } + decodeTimeUnit(vif); + } else { + // E010 1 + description = Description.POWER; + multiplierExponent = (vif & 0x07) - 3; + unit = DlmsUnit.WATT; + } + } else { + // E011 + if ((vif & 0x08) == 0) { + // E011 0 + description = Description.POWER; + multiplierExponent = vif & 0x07; + unit = DlmsUnit.JOULE_PER_HOUR; + } else { + // E011 1 + description = Description.VOLUME_FLOW; + multiplierExponent = (vif & 0x07) - 6; + unit = DlmsUnit.CUBIC_METRE_PER_HOUR; + } + } + } + + private void decodeE00(int vif) { + // E00 + if ((vif & 0x10) == 0) { + // E000 + if ((vif & 0x08) == 0) { + // E000 0 + description = Description.ENERGY; + multiplierExponent = (vif & 0x07) - 3; + unit = DlmsUnit.WATT_HOUR; + } else { + // E000 1 + description = Description.ENERGY; + multiplierExponent = vif & 0x07; + unit = DlmsUnit.JOULE; + } + } else { + // E001 + if ((vif & 0x08) == 0) { + // E001 0 + description = Description.VOLUME; + multiplierExponent = (vif & 0x07) - 6; + unit = DlmsUnit.CUBIC_METRE; + } else { + // E001 1 + description = Description.MASS; + multiplierExponent = (vif & 0x07) - 3; + unit = DlmsUnit.KILOGRAM; + } + } + } + + // implements table 28 of DIN EN 13757-3:2013 + private void decodeMainExtendedVif(byte vif) throws DecodingException { + if ((vif & 0x7f) == 0x0b) { // E000 1011 + description = Description.PARAMETER_SET_ID; + } else if ((vif & 0x7f) == 0x0c) { // E000 1100 + description = Description.MODEL_VERSION; + } else if ((vif & 0x7f) == 0x0d) { // E000 1101 + description = Description.HARDWARE_VERSION; + } else if ((vif & 0x7f) == 0x0e) { // E000 1110 + description = Description.FIRMWARE_VERSION; + } else if ((vif & 0x7f) == 0x0f) { // E000 1111 + description = Description.OTHER_SOFTWARE_VERSION; + } else if ((vif & 0x7f) == 0x10) { // E001 0000 + description = Description.CUSTOMER_LOCATION; + } else if ((vif & 0x7f) == 0x11) { // E001 0001 + description = Description.CUSTOMER; + } else if ((vif & 0x7f) == 0x12) { // E001 0010 + description = Description.ACCSESS_CODE_USER; + } else if ((vif & 0x7f) == 0x13) { // E001 0011 + description = Description.ACCSESS_CODE_OPERATOR; + } else if ((vif & 0x7f) == 0x14) { // E001 0100 + description = Description.ACCSESS_CODE_SYSTEM_OPERATOR; + } else if ((vif & 0x7f) == 0x15) { // E001 0101 + description = Description.ACCSESS_CODE_SYSTEM_DEVELOPER; + } else if ((vif & 0x7f) == 0x16) { // E001 0110 + description = Description.PASSWORD; + } else if ((vif & 0x7f) == 0x17) { // E001 0111 + description = Description.ERROR_FLAGS; + } else if ((vif & 0x7f) == 0x18) { // E001 1000 + description = Description.ERROR_MASK; + } else if ((vif & 0x7f) == 0x19) { // E001 1001 + description = Description.SECURITY_KEY; + } else if ((vif & 0x7f) == 0x1a) { // E001 1010 + description = Description.DIGITAL_OUTPUT; + } else if ((vif & 0x7f) == 0x1b) { // E001 1011 + description = Description.DIGITAL_INPUT; + } else if ((vif & 0x7f) == 0x1c) { // E001 1100 + description = Description.BAUDRATE; + } else if ((vif & 0x7f) == 0x1d) { // E001 1101 + description = Description.RESPONSE_DELAY_TIME; + } else if ((vif & 0x7f) == 0x1e) { // E001 1110 + description = Description.RETRY; + } else if ((vif & 0x7f) == 0x1f) { // E001 1111 + description = Description.REMOTE_CONTROL; + } else if ((vif & 0x7f) == 0x20) { // E010 0000 + description = Description.FIRST_STORAGE_NUMBER_CYCLIC; + } else if ((vif & 0x7f) == 0x21) { // E010 0001 + description = Description.LAST_STORAGE_NUMBER_CYCLIC; + } else if ((vif & 0x7f) == 0x22) { // E010 0010 + description = Description.SIZE_STORAGE_BLOCK; + } else if ((vif & 0x7f) == 0x23) { // E010 0011 + description = Description.RESERVED; + } else if ((vif & 0x7c) == 0x24) { // E010 01nn + description = Description.STORAGE_INTERVALL; + this.unit = unitFor(vif); + } else if ((vif & 0x7f) == 0x28) { // E010 1000 + description = Description.STORAGE_INTERVALL; + unit = DlmsUnit.MONTH; + } else if ((vif & 0x7f) == 0x29) { // E010 1001 + description = Description.STORAGE_INTERVALL; + unit = DlmsUnit.YEAR; + } else if ((vif & 0x7f) == 0x2a) { // E010 1010 + description = Description.OPERATOR_SPECIFIC_DATA; + } else if ((vif & 0x7f) == 0x2b) { // E010 1011 + description = Description.TIME_POINT; + unit = DlmsUnit.SECOND; + } else if ((vif & 0x7c) == 0x2c) { // E010 11nn + description = Description.DURATION_LAST_READOUT; + this.unit = unitFor(vif); + } else if ((vif & 0x7c) == 0x30) { // E011 00nn + description = Description.TARIF_DURATION; + switch (vif & 0x03) { + case 0: // E011 0000 + description = Description.NOT_SUPPORTED; // TODO: TARIF_START (Date/Time) + break; + default: + this.unit = unitFor(vif); + } + } else if ((vif & 0x7c) == 0x34) { // E011 01nn + description = Description.TARIF_PERIOD; + this.unit = unitFor(vif); + } else if ((vif & 0x7f) == 0x38) { // E011 1000 + description = Description.TARIF_PERIOD; + unit = DlmsUnit.MONTH; + } else if ((vif & 0x7f) == 0x39) { // E011 1001 + description = Description.TARIF_PERIOD; + unit = DlmsUnit.YEAR; + } else if ((vif & 0x70) == 0x40) { // E100 0000 + description = Description.VOLTAGE; + multiplierExponent = (vif & 0x0f) - 9; + unit = DlmsUnit.VOLT; + } else if ((vif & 0x70) == 0x50) { // E101 0000 + description = Description.CURRENT; + multiplierExponent = (vif & 0x0f) - 12; + unit = DlmsUnit.AMPERE; + } else if ((vif & 0x7f) == 0x60) { // E110 0000 + description = Description.RESET_COUNTER; + } else if ((vif & 0x7f) == 0x61) { // E110 0001 + description = Description.CUMULATION_COUNTER; + } else if ((vif & 0x7f) == 0x62) { // E110 0010 + description = Description.CONTROL_SIGNAL; + } else if ((vif & 0x7f) == 0x63) { // E110 0011 + description = Description.DAY_OF_WEEK; // 1 = Monday; 7 = Sunday; 0 = all Days + } else if ((vif & 0x7f) == 0x64) { // E110 0100 + description = Description.WEEK_NUMBER; + } else if ((vif & 0x7f) == 0x65) { // E110 0101 + description = Description.TIME_POINT_DAY_CHANGE; + } else if ((vif & 0x7f) == 0x66) { // E110 0110 + description = Description.PARAMETER_ACTIVATION_STATE; + } else if ((vif & 0x7f) == 0x67) { // E110 0111 + description = Description.SPECIAL_SUPPLIER_INFORMATION; + } else if ((vif & 0x7c) == 0x68) { // E110 10nn + description = Description.LAST_CUMULATION_DURATION; + this.unit = unitBiggerFor(vif); + } else if ((vif & 0x7c) == 0x6c) { // E110 11nn + description = Description.OPERATING_TIME_BATTERY; + this.unit = unitBiggerFor(vif); + } else if ((vif & 0x7f) == 0x70) { // E111 0000 + description = Description.NOT_SUPPORTED; // TODO: BATTERY_CHANGE_DATE_TIME + } else if ((vif & 0x7f) == 0x71) { // E111 0001 + description = Description.NOT_SUPPORTED; // TODO: RF_LEVEL dBm + } else if ((vif & 0x7f) == 0x72) { // E111 0010 + description = Description.NOT_SUPPORTED; // TODO: DAYLIGHT_SAVING (begin, ending, deviation) + } else if ((vif & 0x7f) == 0x73) { // E111 0011 + description = Description.NOT_SUPPORTED; // TODO: Listening window management data type L + } else if ((vif & 0x7f) == 0x74) { // E111 0100 + description = Description.REMAINING_BATTERY_LIFE_TIME; + unit = DlmsUnit.DAY; + } else if ((vif & 0x7f) == 0x75) { // E111 0101 + description = Description.NUMBER_STOPS; + } else if ((vif & 0x7f) == 0x76) { // E111 0110 + description = Description.MANUFACTURER_SPECIFIC; + } else if ((vif & 0x7f) >= 0x77) { // E111 0111 - E111 1111 + description = Description.RESERVED; + } else { + description = Description.NOT_SUPPORTED; + } + } + + private static DlmsUnit unitBiggerFor(byte vif) throws DecodingException { + int u = vif & 0x03; + switch (u) { + case 0: // E110 1100 + return DlmsUnit.HOUR; + case 1: // E110 1101 + return DlmsUnit.DAY; + case 2: // E110 1110 + return DlmsUnit.MONTH; + case 3: // E110 1111 + return DlmsUnit.YEAR; + default: + throw new DecodingException(String.format("Unknown unit 0x%02X.", u)); + } + } + + private static DlmsUnit unitFor(byte vif) throws DecodingException { + int u = vif & 0x03; + switch (u) { + case 0: // E010 1100 + return DlmsUnit.SECOND; + case 1: // E010 1101 + return DlmsUnit.MIN; + case 2: // E010 1110 + return DlmsUnit.HOUR; + case 3: // E010 1111 + return DlmsUnit.DAY; + default: + throw new DecodingException(String.format("Unknown unit 0x%02X.", u)); + } + } + + // implements table 29 of DIN EN 13757-3:2011 + private void decodeAlternateExtendedVif(byte vif) { + description = Description.NOT_SUPPORTED; // default value + + if ((vif & 0x40) == 0) { + // E0 + if ((vif & 0x20) == 0) { + // E00 + if ((vif & 0x10) == 0) { + // E000 + if ((vif & 0x08) == 0) { + // E000 0 + if ((vif & 0x04) == 0) { + // E000 00 + if ((vif & 0x02) == 0) { + // E000 000 + description = Description.ENERGY; + multiplierExponent = 5 + (vif & 0x01); + unit = DlmsUnit.WATT_HOUR; + } else { + // E000 001 + description = Description.REACTIVE_ENERGY; + multiplierExponent = 3 + (vif & 0x01); + unit = DlmsUnit.VAR_HOUR; + } + + } else { + // E000 01 + if ((vif & 0x02) == 0) { + // E000 010 + description = Description.APPARENT_ENERGY; + multiplierExponent = 3 + (vif & 0x01); + unit = DlmsUnit.VOLT_AMPERE_HOUR; + } else { + // E000 011 + description = Description.NOT_SUPPORTED; + } + } + } else { + // E000 1 + if ((vif & 0x04) == 0) { + // E000 10 + if ((vif & 0x02) == 0) { + // E000 100 + description = Description.ENERGY; + multiplierExponent = 8 + (vif & 0x01); + unit = DlmsUnit.JOULE; + } else { + // E000 101 + description = Description.NOT_SUPPORTED; + } + + } else { + // E000 11 + description = Description.ENERGY; + multiplierExponent = 5 + (vif & 0x03); + unit = DlmsUnit.CALORIFIC_VALUE; + } + } + } else { + // E001 + if ((vif & 0x08) == 0) { + // E001 0 + if ((vif & 0x04) == 0) { + // E001 00 + if ((vif & 0x02) == 0) { + // E001 000 + description = Description.VOLUME; + multiplierExponent = 2 + (vif & 0x01); + unit = DlmsUnit.CUBIC_METRE; + } else { + // E001 001 + description = Description.NOT_SUPPORTED; + } + } else { + // E001 01 + description = Description.REACTIVE_POWER; + multiplierExponent = (vif & 0x03); + unit = DlmsUnit.VAR; + } + } else { + // E001 1 + if ((vif & 0x04) == 0) { + // E001 10 + if ((vif & 0x02) == 0) { + // E001 100 + description = Description.MASS; + multiplierExponent = 5 + (vif & 0x01); + unit = DlmsUnit.KILOGRAM; + } else { + // E001 101 + description = Description.REL_HUMIDITY; + multiplierExponent = -1 + (vif & 0x01); + unit = DlmsUnit.PERCENTAGE; + } + + } else { + // E001 11 + description = Description.NOT_SUPPORTED; + } + } + + } + } else { + // E01 + if ((vif & 0x10) == 0) { + // E010 + if ((vif & 0x08) == 0) { + // E010 0 + if ((vif & 0x04) == 0) { + // E010 00 + if ((vif & 0x02) == 0) { + // E010 000 + if ((vif & 0x01) == 0) { + // E010 0000 + description = Description.VOLUME; + multiplierExponent = 0; + unit = DlmsUnit.CUBIC_FEET; + } else { + // E010 0001 + description = Description.VOLUME; + multiplierExponent = -1; + unit = DlmsUnit.CUBIC_FEET; + } + } else { + // E010 001 + // outdated value ! + description = Description.VOLUME; + multiplierExponent = -1 + (vif & 0x01); + unit = DlmsUnit.US_GALLON; + } + } else { + // E010 01 + if ((vif & 0x02) == 0) { + // E010 010 + if ((vif & 0x01) == 0) { + // E010 0100 + // outdated value ! + description = Description.VOLUME_FLOW; + multiplierExponent = -3; + unit = DlmsUnit.US_GALLON_PER_MINUTE; + } else { + // E010 0101 + // outdated value ! + description = Description.VOLUME_FLOW; + multiplierExponent = 0; + unit = DlmsUnit.US_GALLON_PER_MINUTE; + } + } else { + // E010 011 + if ((vif & 0x01) == 0) { + // E010 0110 + // outdated value ! + description = Description.VOLUME_FLOW; + multiplierExponent = 0; + unit = DlmsUnit.US_GALLON_PER_HOUR; + } else { + // E010 0111 + description = Description.NOT_SUPPORTED; + } + } + + } + } else { + // E010 1 + if ((vif & 0x04) == 0) { + // E010 10 + if ((vif & 0x02) == 0) { + // E010 100 + description = Description.POWER; + multiplierExponent = 5 + (vif & 0x01); + unit = DlmsUnit.WATT; + } else { + if ((vif & 0x01) == 0) { + // E010 1010 + description = Description.PHASE; + multiplierExponent = -1; // is -1 or 0 correct ?? + unit = DlmsUnit.DEGREE; + } + // TODO same + // else { + // // E010 1011 + // description = Description.PHASE; + // multiplierExponent = -1; // is -1 or 0 correct ?? + // unit = DlmsUnit.DEGREE; + // } + } + } else { + // E010 11 + description = Description.FREQUENCY; + multiplierExponent = -3 + (vif & 0x03); + unit = DlmsUnit.HERTZ; + } + } + } else { + // E011 + if ((vif & 0x08) == 0) { + // E011 0 + if ((vif & 0x04) == 0) { + // E011 00 + if ((vif & 0x02) == 0) { + // E011 000 + description = Description.POWER; + multiplierExponent = 8 + (vif & 0x01); + unit = DlmsUnit.JOULE_PER_HOUR; + } else { + // E011 001 + description = Description.NOT_SUPPORTED; + } + } else { + // E011 01 + description = Description.APPARENT_ENERGY; + multiplierExponent = (vif & 0x03); + unit = DlmsUnit.VOLT_AMPERE; + } + } else { + // E011 1 + description = Description.NOT_SUPPORTED; + } + } + } + } else { + // E1 + if ((vif & 0x20) == 0) { + // E10 + if ((vif & 0x10) == 0) { + // E100 + description = Description.NOT_SUPPORTED; + } else { + // E101 + if ((vif & 0x08) == 0) { + // E101 0 + description = Description.NOT_SUPPORTED; + } else { + // E101 1 + if ((vif & 0x04) == 0) { + // E101 10 + // outdated value ! + description = Description.FLOW_TEMPERATURE; + multiplierExponent = (vif & 0x03) - 3; + unit = DlmsUnit.DEGREE_FAHRENHEIT; + } else { + // E101 11 + // outdated value ! + description = Description.RETURN_TEMPERATURE; + multiplierExponent = (vif & 0x03) - 3; + unit = DlmsUnit.DEGREE_FAHRENHEIT; + } + } + } + } else { + // E11 + if ((vif & 0x10) == 0) { + // E110 + if ((vif & 0x08) == 0) { + // E110 0 + if ((vif & 0x04) == 0) { + // E110 00 + // outdated value ! + description = Description.TEMPERATURE_DIFFERENCE; + multiplierExponent = (vif & 0x03) - 3; + unit = DlmsUnit.DEGREE_FAHRENHEIT; + } else { + // E110 01 + // outdated value ! + description = Description.FLOW_TEMPERATURE; + multiplierExponent = (vif & 0x03) - 3; + unit = DlmsUnit.DEGREE_FAHRENHEIT; + } + } else { + // E110 1 + description = Description.NOT_SUPPORTED; + } + } else { + // E111 + if ((vif & 0x08) == 0) { + // E111 0 + if ((vif & 0x04) == 0) { + // E111 00 + // outdated value ! + description = Description.TEMPERATURE_LIMIT; + multiplierExponent = (vif & 0x03) - 3; + unit = DlmsUnit.DEGREE_FAHRENHEIT; + } else { + // E111 01 + description = Description.TEMPERATURE_LIMIT; + multiplierExponent = (vif & 0x03) - 3; + unit = DlmsUnit.DEGREE_CELSIUS; + } + } else { + // E111 1 + description = Description.MAX_POWER; + multiplierExponent = (vif & 0x07) - 3; + unit = DlmsUnit.WATT; + } + } + } + + } + } + + @Override + public String toString() { + + StringBuilder builder = new StringBuilder().append("DIB:").append(printHexBinary(dib)).append(", VIB:") + .append(printHexBinary(vib)).append(" -> descr:").append(description); + + if (description == Description.USER_DEFINED) { + builder.append(" :").append(getUserDefinedDescription()); + } + builder.append(", function:").append(functionField); + + if (storageNumber > 0) { + builder.append(", storage:").append(storageNumber); + } + + if (tariff > 0) { + builder.append(", tariff:").append(tariff); + } + + if (subunit > 0) { + builder.append(", subunit:").append(subunit); + } + + final String valuePlacHolder = ", value:"; + final String scaledValueString = ", scaled value:"; + + switch (dataValueType) { + case DATE: + case STRING: + builder.append(valuePlacHolder).append((dataValue).toString()); + break; + case DOUBLE: + builder.append(scaledValueString).append(getScaledDataValue()); + break; + case LONG: + if (multiplierExponent == 0) { + builder.append(valuePlacHolder).append(dataValue); + } else { + builder.append(scaledValueString).append(getScaledDataValue()); + } + break; + case BCD: + if (multiplierExponent == 0) { + builder.append(valuePlacHolder).append((dataValue).toString()); + } else { + builder.append(scaledValueString).append(getScaledDataValue()); + } + break; + case NONE: + builder.append(", value:NONE"); + break; + } + + if (unit != null) { + builder.append(", unit:").append(unit); + if (!unit.getUnit().isEmpty()) { + builder.append(", ").append(unit.getUnit()); + } + } + + return builder.toString(); + } + + public int getDataLength() { + return dataLength; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DecodingException.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DecodingException.java new file mode 100644 index 0000000..5dc0544 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DecodingException.java @@ -0,0 +1,26 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus; + +/** + * Signals that a M-Bus message could not be decoded. + */ +public class DecodingException extends Exception { + + private static final long serialVersionUID = 1735527302166708223L; + + public DecodingException(String msg) { + super(msg); + } + + public DecodingException(Throwable cause) { + super(cause); + } + + public DecodingException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DeviceType.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DeviceType.java new file mode 100644 index 0000000..c69aa81 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DeviceType.java @@ -0,0 +1,122 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.openmuc.jmbus; + +import java.util.HashMap; +import java.util.Map; + +/** + * The device type that is part of the data header of a Variable Data Response. + */ +public enum DeviceType { + OTHER(0x00), + OIL_METER(0x01), + ELECTRICITY_METER(0x02), + GAS_METER(0x03), + HEAT_METER(0x04), + STEAM_METER(0x05), + WARM_WATER_METER(0x06), + WATER_METER(0x07), + HEAT_COST_ALLOCATOR(0x08), + COMPRESSED_AIR(0x09), + COOLING_METER_OUTLET(0x0a), + COOLING_METER_INLET(0x0b), + HEAT_METER_INLET(0x0c), + HEAT_COOLING_METER(0x0d), + BUS_SYSTEM_COMPONENT(0x0e), + UNKNOWN(0x0f), + RESERVED_FOR_METER_16(0x10), + RESERVED_FOR_METER_17(0x11), + RESERVED_FOR_METER_18(0x12), + RESERVED_FOR_METER_19(0x13), + CALORIFIC_VALUE(0x14), + HOT_WATER_METER(0x15), + COLD_WATER_METER(0x16), + DUAL_REGISTER_WATER_METER(0x17), + PRESSURE_METER(0x18), + AD_CONVERTER(0x19), + SMOKE_DETECTOR(0x1a), + ROOM_SENSOR_TEMP_HUM(0x1b), + GAS_DETECTOR(0x1c), + RESERVED_FOR_SENSOR_0X1D(0x1d), + RESERVED_FOR_SENSOR_0X1E(0x1e), + RESERVED_FOR_SENSOR_0X1F(0x1f), + BREAKER_ELEC(0x20), + VALVE_GAS_OR_WATER(0x21), + RESERVED_FOR_SWITCHING_DEVICE_0X22(0x22), + RESERVED_FOR_SWITCHING_DEVICE_0X23(0x23), + RESERVED_FOR_SWITCHING_DEVICE_0X24(0x24), + CUSTOMER_UNIT_DISPLAY_DEVICE(0x25), + RESERVED_FOR_CUSTOMER_UNIT_0X26(0x26), + RESERVED_FOR_CUSTOMER_UNIT_0X27(0x27), + WASTE_WATER_METER(0x28), + GARBAGE(0x29), + RESERVED_FOR_CO2(0x2a), + RESERVED_FOR_ENV_METER_0X2B(0x2b), + RESERVED_FOR_ENV_METER_0X2C(0x2c), + RESERVED_FOR_ENV_METER_0X2D(0x2d), + RESERVED_FOR_ENV_METER_0X2E(0x2e), + RESERVED_FOR_ENV_METER_0X2F(0x2f), + RESERVED_FOR_SYSTEM_DEVICES_0X30(0x30), + COM_CONTROLLER(0x31), + UNIDIRECTION_REPEATER(0x32), + BIDIRECTION_REPEATER(0x33), + RESERVED_FOR_SYSTEM_DEVICES_0X34(0x34), + RESERVED_FOR_SYSTEM_DEVICES_0X35(0x35), + RADIO_CONVERTER_SYSTEM_SIDE(0x36), + RADIO_CONVERTER_METER_SIDE(0x37), + RESERVED_FOR_SYSTEM_DEVICES_0X38(0x38), + RESERVED_FOR_SYSTEM_DEVICES_0X39(0x39), + RESERVED_FOR_SYSTEM_DEVICES_0X3A(0x3a), + RESERVED_FOR_SYSTEM_DEVICES_0X3B(0x3b), + RESERVED_FOR_SYSTEM_DEVICES_0X3C(0x3c), + RESERVED_FOR_SYSTEM_DEVICES_0X3D(0x3d), + RESERVED_FOR_SYSTEM_DEVICES_0X3E(0x3e), + RESERVED_FOR_SYSTEM_DEVICES_0X3F(0x3f), + RESERVED(0xff); + + private final int id; + + private static final Map idMap = new HashMap<>(); + + static { + for (DeviceType enumInstance : DeviceType.values()) { + if (idMap.put(enumInstance.getId(), enumInstance) != null) { + throw new IllegalArgumentException("duplicate ID: " + enumInstance.getId()); + } + } + } + + private DeviceType(int id) { + this.id = id; + } + + /** + * Returns the ID of this DeviceType. + * + * @return the ID + */ + public int getId() { + return id; + } + + /** + * Returns the DeviceType that corresponds to the given ID. Returns DeviceType.RESERVED if no DeviceType with the + * given ID exists. + * + * @param id + * the ID + * @return the DeviceType that corresponds to the given ID + */ + public static DeviceType getInstance(int id) { + DeviceType enumInstance = idMap.get(id); + if (enumInstance == null) { + enumInstance = DeviceType.RESERVED; + } + return enumInstance; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DlmsUnit.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DlmsUnit.java new file mode 100644 index 0000000..eed1598 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DlmsUnit.java @@ -0,0 +1,146 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus; + +import java.util.HashMap; +import java.util.Map; + +/** + * The units as defined in IEC 62056-6-2. Some units not defined in IEC 62056-6-2 but needed by M-Bus were added. + */ +public enum DlmsUnit { + // can be found in IEC 62056-6-2 2013 Capture 5.2.2 + YEAR(1, "a"), + MONTH(2, "mo"), + WEEK(3, "wk"), + DAY(4, "d"), + HOUR(5, "h"), + MIN(6, "min"), + SECOND(7, "s"), + DEGREE(8, "°"), + DEGREE_CELSIUS(9, "°C"), + CURRENCY(10, ""), + METRE(11, "m"), + METRE_PER_SECOND(12, "m/s"), + CUBIC_METRE(13, "m³"), + CUBIC_METRE_CORRECTED(14, "m³"), + CUBIC_METRE_PER_HOUR(15, "m³/h"), + CUBIC_METRE_PER_HOUR_CORRECTED(16, "m³/h"), + CUBIC_METRE_PER_DAY(17, "m³/d"), + CUBIC_METRE_PER_DAY_CORRECTED(18, "m³/d"), + LITRE(19, "l"), + KILOGRAM(20, "kg"), + NEWTON(21, "N"), + NEWTONMETER(22, "n"), + PASCAL(23, "Nm"), + BAR(24, "bar"), + JOULE(25, "J"), + JOULE_PER_HOUR(26, "J/h"), + WATT(27, "W"), + VOLT_AMPERE(28, "VA"), + VAR(29, "var"), + WATT_HOUR(30, "Wh"), + VOLT_AMPERE_HOUR(31, "VAh"), + VAR_HOUR(32, "varh"), + AMPERE(33, "A"), + COULOMB(34, "C"), + VOLT(35, "V"), + VOLT_PER_METRE(36, "V/m"), + FARAD(37, "F"), + OHM(38, "Ohm"), + OHM_METRE(39, "Ohm m²/m"), + WEBER(40, "Wb"), + TESLA(41, "T"), + AMPERE_PER_METRE(42, "A/m"), + HENRY(43, "H"), + HERTZ(44, "Hz"), + ACTIVE_ENERGY_METER_CONSTANT_OR_PULSE_VALUE(45, "1/(Wh)"), + REACTIVE_ENERGY_METER_CONSTANT_OR_PULSE_VALUE(46, "1/(varh)"), + APPARENT_ENERGY_METER_CONSTANT_OR_PULSE_VALUE(47, "1(VAh)"), + VOLT_SQUARED_HOURS(48, "V²h"), + AMPERE_SQUARED_HOURS(49, "A²h"), + KILOGRAM_PER_SECOND(50, "kg/s"), + KELVIN(52, "S"), + VOLT_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE(53, "K"), + AMPERE_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE(54, "1/(V²h)"), + METER_CONSTANT_OR_PULSE_VALUE(55, "1/(A²h)"), + PERCENTAGE(56, "%"), + AMPERE_HOUR(57, "Ah"), + + ENERGY_PER_VOLUME(60, "Wh/m³"), + CALORIFIC_VALUE(61, "J/m³"), + MOLE_PERCENT(62, "Mol %"), + MASS_DENSITY(63, "g/m³"), + PASCAL_SECOND(64, "Pa s"), + SPECIFIC_ENERGY(65, "J/kg"), + + SIGNAL_STRENGTH(70, "dBm"), + + RESERVED(253, ""), + OTHER_UNIT(254, ""), + COUNT(255, ""), + // not mentioned in 62056, added for MBus: + CUBIC_METRE_PER_SECOND(150, "m³/s"), + CUBIC_METRE_PER_MINUTE(151, "m³/min"), + KILOGRAM_PER_HOUR(152, "kg/h"), + CUBIC_FEET(153, "cft"), + US_GALLON(154, "Impl. gal."), + US_GALLON_PER_MINUTE(155, "Impl. gal./min"), + US_GALLON_PER_HOUR(156, "Impl. gal./h"), + DEGREE_FAHRENHEIT(157, "°F"); + + private final int id; + private final String unit; + + private static final Map idMap = new HashMap<>(); + + static { + for (DlmsUnit enumInstance : DlmsUnit.values()) { + if (idMap.put(enumInstance.getId(), enumInstance) != null) { + throw new IllegalArgumentException("duplicate ID: " + enumInstance.getId()); + } + } + } + + private DlmsUnit(int id, String unit) { + this.id = id; + this.unit = unit; + } + + /** + * Returns the ID of this DlmsUnit. + * + * @return the ID + */ + public int getId() { + return id; + } + + /** + * Returns the unit sign of this DlmsUnit. + * + * @return the ID + */ + public String getUnit() { + return unit; + } + + /** + * Returns the DlmsUnit that corresponds to the given ID. Returns DlmsUnit.RESERVED if no DlmsUnit with the given ID + * exists. + * + * @param id + * the ID + * @return the DlmsUnit that corresponds to the given ID + */ + public static DlmsUnit getInstance(int id) { + DlmsUnit enumInstance = idMap.get(id); + if (enumInstance == null) { + enumInstance = DlmsUnit.RESERVED; + } + return enumInstance; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/EncryptionMode.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/EncryptionMode.java new file mode 100644 index 0000000..ab856f9 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/EncryptionMode.java @@ -0,0 +1,99 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus; + +import java.util.HashMap; +import java.util.Map; + +/** + * The encryption modes. + */ +public enum EncryptionMode { + /** + * No encryption. + */ + NONE(0), + /** + * AES with Counter Mode (CTR) noPadding and IV. + */ + AES_128(1), + /** + * DES with Cipher Block Chaining Mode (CBC).
+ * Not supported yet. + */ + DES_CBC(2), + /** + * DES with Cipher Block Chaining Mode (CBC) and Initial Vector.
+ * Not supported yet. + */ + DES_CBC_IV(3), + RESERVED_04(4), + /** + * AES with Cipher Block Chaining Mode (CBC) and Initial Vector. + */ + AES_CBC_IV(5), + RESERVED_06(6), + /** + * AES 128 with Cipher Block Chaining Mode (CBC) and dynamic key and Initial Vector with 0.
+ * TR-03109-1 Anlage Feinspezifikation Drahtlose LMN Schnittstelle-Teil2
+ * Not supported yet. + */ + AES_CBC_IV_0(7), + RESERVED_08(8), + RESERVED_09(9), + RESERVED_10(10), + RESERVED_11(11), + RESERVED_12(12), + /** + * TLS 1.2
+ * TR-03109-1 Anlage Feinspezifikation Drahtlose LMN Schnittstelle-Teil2
+ * Not supported yet. + */ + TLS(13), + RESERVED_14(14), + RESERVED_15(15); + + private final int id; + + private static final Map idMap = new HashMap<>(); + + static { + for (EncryptionMode enumInstance : EncryptionMode.values()) { + if (idMap.put(enumInstance.getId(), enumInstance) != null) { + throw new IllegalArgumentException("duplicate ID: " + enumInstance.getId()); + } + } + } + + private EncryptionMode(int id) { + this.id = id; + } + + /** + * Returns the ID of this EncryptionMode. + * + * @return the ID + */ + public int getId() { + return id; + } + + /** + * Returns the EncryptionMode that corresponds to the given ID. Throws an IllegalArgumentException if no + * EncryptionMode with the given ID exists. + * + * @param id + * the ID + * @return the EncryptionMode that corresponds to the given ID + */ + public static EncryptionMode getInstance(int id) { + EncryptionMode enumInstance = idMap.get(id); + if (enumInstance == null) { + throw new IllegalArgumentException("invalid ID: " + id); + } + return enumInstance; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/MBusConnection.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/MBusConnection.java new file mode 100644 index 0000000..a0cd165 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/MBusConnection.java @@ -0,0 +1,522 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.List; + +import org.openmuc.jmbus.MBusMessage.MessageType; +import org.openmuc.jmbus.VerboseMessage.MessageDirection; +import org.openmuc.jmbus.transportlayer.SerialBuilder; +import org.openmuc.jmbus.transportlayer.TcpBuilder; +import org.openmuc.jmbus.transportlayer.TransportLayer; + +/** + * M-Bus Application Layer connection. + *

+ * Use this access point to communicate using the M-Bus wired protocol. + *

+ * + * @see MBusConnection#newSerialBuilder(String) + * @see MBusConnection#newTcpBuilder(String, int) + */ +public class MBusConnection implements AutoCloseable { + + // 261 is the maximum size of a long frame + private static final int MAX_MESSAGE_SIZE = 261; + + private final byte[] outputBuffer = new byte[MAX_MESSAGE_SIZE]; + + private final byte[] dataRecordsAsBytes = new byte[MAX_MESSAGE_SIZE]; + + private final boolean[] frameCountBits; + + private DataOutputStream os; + private DataInputStream is; + + private SecondaryAddress secondaryAddress; + + private VerboseMessageListener verboseMessageListener; + + private final TransportLayer transportLayer; + + /** + * Creates an M-Bus Service Access Point that is used to read meters. + * + * @param transportLayer + * Underlying transport layer + * @see MBusConnection#open() + */ + private MBusConnection(TransportLayer transportLayer) { + this.transportLayer = transportLayer; + + // set all frame bits to true + this.frameCountBits = new boolean[254]; + for (int i = 0; i < frameCountBits.length; i++) { + frameCountBits[i] = true; + } + } + + private void open() throws IOException { + try { + this.transportLayer.open(); + } catch (IOException e) { + this.transportLayer.close(); + throw e; + } + + this.os = transportLayer.getOutputStream(); + this.is = transportLayer.getInputStream(); + } + + /** + * Closes the service access point. + */ + @Override + public void close() { + transportLayer.close(); + } + + /** + * Sets the verbose mode on if a implementation of debugMessageListener has been set. + * + * @param verboseMessageListener + * Implementation of debugMessageListener + */ + public void setVerboseMessageListener(VerboseMessageListener verboseMessageListener) { + this.verboseMessageListener = verboseMessageListener; + } + + /** + * Scans for secondary addresses and returns all detected devices in a list and if SecondaryAddressListener not null + * to the listen listener. + * + * @param wildcardMask + * a wildcard mask for masking + * @param secondaryAddressListener + * listener to get scan messages and scanned secondary address just at time.
+ * If null, all detected address will only returned if finished. + * + * @return a list of secondary addresses of all detected devices + * @throws IOException + * if any kind of error (including timeout) occurs while writing to the remote device. Note that the + * connection is not closed when an IOException is thrown. + */ + public List scan(String wildcardMask, SecondaryAddressListener secondaryAddressListener) + throws IOException { + + if (wildcardMask == null || wildcardMask.isEmpty()) { + wildcardMask = "ffffffff"; + } + + return ScanSecondaryAddress.scan(this, wildcardMask, secondaryAddressListener); + } + + /** + * Reads a meter using primary addressing. Sends a data request (REQ_UD2) to the remote device and returns the + * variable data structure from the received RSP_UD frame. + * + * @param primaryAddress + * the primary address of the meter to read. For secondary address use 0xfd. + * @return the variable data structure from the received RSP_UD frame + * @throws InterruptedIOException + * if no response at all (not even a single byte) was received from the meter within the timeout span. + * @throws IOException + * if any kind of error (including timeout) occurs while trying to read the remote device. Note that the + * connection is not closed when an IOException is thrown. + * @throws InterruptedIOException + * if no response at all (not even a single byte) was received from the meter within the timeout span. + */ + public VariableDataStructure read(int primaryAddress) throws IOException, InterruptedIOException { + if (transportLayer.isClosed()) { + throw new IllegalStateException("Port is not open."); + } + + if (frameCountBits[primaryAddress]) { + sendShortMessage(primaryAddress, 0x7b); + frameCountBits[primaryAddress] = false; + } else { + sendShortMessage(primaryAddress, 0x5b); + frameCountBits[primaryAddress] = true; + } + + MBusMessage mBusMessage = receiveMessage(); + + if (mBusMessage.getMessageType() != MessageType.RSP_UD) { + throw new IOException( + "Received wrong kind of message. Expected RSP_UD but got: " + mBusMessage.getMessageType()); + } + + if (mBusMessage.getAddressField() != primaryAddress) { + // throw new IOException("Received RSP_UD message with unexpected address field. Expected " + primaryAddress + // + " but received " + mBusMessage.getAddressField()); + } + + try { + mBusMessage.getVariableDataResponse().decode(); + } catch (DecodingException e) { + throw new IOException("Error decoding incoming RSP_UD message.", e); + } + + return mBusMessage.getVariableDataResponse(); + } + + /** + * Sends a long message with individual parameters. Used for messages which arn't not predefined in + * {@link MBusConnection}.
+ * If no response message is expected, set hasResponse to false. Returns null if hasResponse is + * false + * + * @param primaryAddr + * the primary address of the meter to read. For secondary address use 0xfd. + * @param controlField + * control field (C Field) has the size of 1 byte. + * @param ci + * control information field (CI Field) has the size of 1 byte. + * @param data + * the data to sends to the meter. + * @param responseExpected + * set this flag to false if no response is expected else true. If false + * returns null + * @return returns null if boolean hasResponse is false.
+ * returns the {@link MBusMessage} if a message received. + * @throws IOException + * if any kind of error (including timeout) occurs while trying to send to or read the remote device. + * Note that the connection is not closed when an IOException is thrown. + */ + public MBusMessage sendLongMessage(int primaryAddr, int controlField, int ci, byte[] data, boolean responseExpected) + throws IOException { + MBusMessage mBusMessage = null; + + sendLongMessage(primaryAddr, controlField, ci, data.length, data); + + if (responseExpected) { + mBusMessage = receiveMessage(); + } + return mBusMessage; + } + + /** + * Sends a short message with individual parameters. Used for messages which arn't not predefined in + * {@link MBusConnection}.
+ * For normal readout use {@link MBusConnection#read(int)}.
+ * If no response message is expected, set hasResponse to false. Returns null if hasResponse is + * false + * + * @param primaryAddr + * the primary address of the meter to read. For secondary address use 0xfd. + * @param cmd + * the command to send to the meter. + * @param responseExpected + * set this flag to false if no response is expected else true. If false + * returns null + * @return returns null if boolean hasResponse is false.
+ * returns the {@link MBusMessage} if a message received. + * @throws IOException + * f any kind of error (including timeout) occurs while trying to send to or read the remote device. + * Note that the connection is not closed when an IOException is thrown. + */ + public MBusMessage sendShortMessage(int primaryAddr, int cmd, boolean responseExpected) throws IOException { + MBusMessage mBusMessage = null; + + sendShortMessage(primaryAddr, cmd); + + if (responseExpected) { + mBusMessage = receiveMessage(); + } + return mBusMessage; + } + + /** + * Writes to a meter using primary addressing. Sends a data send (SND_UD) to the remote device and returns a true if + * slave sends a 0x7e else false + * + * @param primaryAddress + * the primary address of the meter to write. For secondary address use 0xfd. + * @param data + * the data to sends to the meter. + * @throws IOException + * if any kind of error (including timeout) occurs while writing to the remote device. Note that the + * connection is not closed when an IOException is thrown. + * @throws InterruptedIOException + * if no response at all (not even a single byte) was received from the meter within the timeout span. + */ + public void write(int primaryAddress, byte[] data) throws IOException, InterruptedIOException { + if (data == null) { + data = new byte[0]; + } + + sendLongMessage(primaryAddress, 0x73, 0x51, data.length, data); + MBusMessage mBusMessage = receiveMessage(); + + if (mBusMessage.getMessageType() != MessageType.SINGLE_CHARACTER) { + throw new IOException("Unable to select component."); + } + } + + /** + * Selects the meter with the specified secondary address. After this the meter can be read on primary address 0xfd. + * + * @param secondaryAddress + * the secondary address of the meter to select. + * @throws IOException + * if any kind of error (including timeout) occurs while trying to read the remote device. Note that the + * connection is not closed when an IOException is thrown. + * @throws InterruptedIOException + * if no response at all (not even a single byte) was received from the meter within the timeout span. + */ + public void selectComponent(SecondaryAddress secondaryAddress) throws IOException, InterruptedIOException { + this.secondaryAddress = secondaryAddress; + componentSelection(false); + } + + /** + * Deselects the previously selected meter. + * + * @throws IOException + * if any kind of error (including timeout) occurs while trying to read the remote device. Note that the + * connection is not closed when an IOException is thrown. + * @throws InterruptedIOException + * if no response at all (not even a single byte) was received from the meter within the timeout span. + */ + public void deselectComponent() throws IOException, InterruptedIOException { + if (secondaryAddress == null) { + return; + } + componentSelection(true); + secondaryAddress = null; + } + + /** + * Selection of wanted records. + * + * @param primaryAddress + * primary address of the slave + * @param dataRecords + * data record to select + * @throws IOException + * if any kind of error (including timeout) occurs while trying to read the remote device. Note that the + * connection is not closed when an IOException is thrown. + * @throws InterruptedIOException + * if no response at all (not even a single byte) was received from the meter within the timeout span. + */ + public void selectForReadout(int primaryAddress, List dataRecords) + throws IOException, InterruptedIOException { + int i = 0; + for (DataRecord dataRecord : dataRecords) { + i += dataRecord.encode(dataRecordsAsBytes, i); + } + sendLongMessage(primaryAddress, 0x53, 0x51, i, dataRecordsAsBytes); + MBusMessage mBusMessage = receiveMessage(); + + if (mBusMessage.getMessageType() != MessageType.SINGLE_CHARACTER) { + throw new IOException("unable to select component"); + } + } + + /** + * Sends a application reset to the slave with specified primary address. + * + * @param primaryAddress + * primary address of the slave + * @throws IOException + * if any kind of error (including timeout) occurs while trying to read the remote device. Note that the + * connection is not closed when an IOException is thrown. + * @throws InterruptedIOException + * if no response at all (not even a single byte) was received from the meter within the timeout span. + */ + public void resetReadout(int primaryAddress) throws IOException, InterruptedIOException { + sendLongMessage(primaryAddress, 0x53, 0x50, 0, new byte[] {}); + MBusMessage mBusMessage = receiveMessage(); + + if (mBusMessage.getMessageType() != MessageType.SINGLE_CHARACTER) { + throw new IOException("Unable to reset application."); + } + } + + /** + * Sends a SND_NKE message to reset the FCB (frame counter bit). + * + * @param primaryAddress + * the primary address of the meter to reset. + * @throws InterruptedIOException + * if the slave does not answer with an 0xe5 message within the configured timeout span. + * @throws IOException + * if an error occurs during the reset process. + * @throws InterruptedIOException + * if the slave does not answer with an 0xe5 message within the configured timeout span. + */ + public void linkReset(int primaryAddress) throws IOException, InterruptedIOException { + sendShortMessage(primaryAddress, 0x40); + MBusMessage mBusMessage = receiveMessage(); + + if (mBusMessage.getMessageType() != MessageType.SINGLE_CHARACTER) { + throw new IOException("Unable to reset link."); + } + + frameCountBits[primaryAddress] = true; + } + + private void componentSelection(boolean deselect) throws IOException, InterruptedIOException { + byte[] ba = secondaryAddressAsBa(); + + // send select/deselect + if (deselect) { + sendLongMessage(0xfd, 0x53, 0x56, 8, ba); + } else { + sendLongMessage(0xfd, 0x53, 0x52, 8, ba); + } + + MBusMessage mBusMessage = receiveMessage(); + + if (mBusMessage.getMessageType() != MessageType.SINGLE_CHARACTER) { + throw new IOException("unable to select component"); + } + } + + private byte[] secondaryAddressAsBa() { + byte[] ba = new byte[8]; + + ((ByteBuffer) ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).put(secondaryAddress.asByteArray()) + .position(0)).get(ba, 0, 8); + return ba; + } + + private void sendShortMessage(int slaveAddr, int cmd) throws IOException { + synchronized (os) { + outputBuffer[0] = 0x10; + outputBuffer[1] = (byte) (cmd); + outputBuffer[2] = (byte) (slaveAddr); + outputBuffer[3] = (byte) (cmd + slaveAddr); + outputBuffer[4] = 0x16; + + verboseMessage(MessageDirection.SEND, outputBuffer, 0, 5); + + os.write(outputBuffer, 0, 5); + } + } + + void sendLongMessage(int slaveAddr, int controlField, int ci, int length, byte[] data) throws IOException { + synchronized (os) { + outputBuffer[0] = 0x68; + outputBuffer[1] = (byte) (length + 3); + outputBuffer[2] = (byte) (length + 3); + outputBuffer[3] = 0x68; + outputBuffer[4] = (byte) controlField; + outputBuffer[5] = (byte) slaveAddr; + outputBuffer[6] = (byte) ci; + + for (int i = 0; i < length; i++) { + outputBuffer[7 + i] = data[i]; + } + + outputBuffer[length + 7] = computeChecksum(length, outputBuffer); + + outputBuffer[length + 8] = 0x16; + + verboseMessage(MessageDirection.SEND, outputBuffer, 0, length + 9); + + os.write(outputBuffer, 0, length + 9); + } + } + + private static byte computeChecksum(int length, byte[] oBuffer) { + int checksum = 0; + for (int j = 4; j < (length + 7); j++) { + checksum += oBuffer[j]; + } + return (byte) (checksum & 0xff); + } + + MBusMessage receiveMessage() throws IOException { + byte[] receivedBytes; + + int b0 = is.read(); + if (b0 == 0xe5) { + // messageLength = 1; + receivedBytes = new byte[] { (byte) b0 }; + } else if ((b0 & 0xff) == 0x68) { + int b1 = is.readByte() & 0xff; + + /** + * The L field gives the quantity of the user data inputs plus 3 (for C,A,CI). + */ + int messageLength = b1 + 6; + + receivedBytes = new byte[messageLength]; + receivedBytes[0] = (byte) b0; + receivedBytes[1] = (byte) b1; + + int lenRead = messageLength - 2; + + is.readFully(receivedBytes, 2, lenRead); + } else { + throw new IOException(String.format("Received unknown message: %02X", b0)); + } + + verboseMessage(MessageDirection.RECEIVE, receivedBytes, 0, receivedBytes.length); + + return MBusMessage.decode(receivedBytes, receivedBytes.length); + } + + private void verboseMessage(MessageDirection direction, byte[] array, int from, int to) { + if (this.verboseMessageListener != null) { + byte[] message = Arrays.copyOfRange(array, from, to); + + VerboseMessage debugMessage = new VerboseMessage(direction, message); + this.verboseMessageListener.newVerboseMessage(debugMessage); + } + } + + public static MBusTcpBuilder newTcpBuilder(String hostAddress, int port) { + return new MBusTcpBuilder(hostAddress, port); + } + + public static class MBusTcpBuilder extends TcpBuilder { + + protected MBusTcpBuilder(String hostAddress, int port) { + super(hostAddress, port); + } + + @Override + public MBusConnection build() throws IOException { + MBusConnection mBusConnection = new MBusConnection(buildTransportLayer()); + mBusConnection.open(); + return mBusConnection; + } + } + + /** + * Create a new builder to connect to a serial. + * + * @param serialPortName + * the serial port. e.g. /dev/ttyS0. + * @return a new connection builder. + */ + public static MBusSerialBuilder newSerialBuilder(String serialPortName) { + return new MBusSerialBuilder(serialPortName); + } + + public static class MBusSerialBuilder extends SerialBuilder { + + protected MBusSerialBuilder(String serialPortName) { + super(serialPortName); + } + + @Override + public MBusConnection build() throws IOException { + MBusConnection mBusConnection = new MBusConnection(buildTransportLayer()); + mBusConnection.open(); + return mBusConnection; + } + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/MBusMessage.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/MBusMessage.java new file mode 100644 index 0000000..367e800 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/MBusMessage.java @@ -0,0 +1,131 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus; + +import java.io.IOException; + +/** + * + * Represents a wired M-Bus link layer message according to EN 13757-2. The messages are in format class FT 1.2 + * according to IEC 60870-5-2. + * + * If the M-Bus message is of frame type Long Frame it contains user data and it contains the following fields: + *
    + *
  • Length (1 byte) -
  • + *
  • Control field (1 byte) -
  • + *
  • Address field (1 byte) -
  • + *
  • CI field (1 byte) -
  • + *
  • The APDU (Variable Data Response) -
  • + *
+ */ +public class MBusMessage { + + private static final int RSP_UD_HEADER_LENGTH = 6; + + public enum MessageType { + // the other message types (e.g. SND_NKE, REQ_UD2) cannot be sent from slave to master and are therefore + // omitted. + SINGLE_CHARACTER(0xE5), + RSP_UD(0x68); + + private static final MessageType[] VALUES = values(); + private final int value; + + private MessageType(int value) { + this.value = value; + } + + private static MessageType messageTypeFor(byte value) throws IOException { + int vAsint = value & 0xff; + for (MessageType messageType : VALUES) { + if (vAsint == messageType.value) { + return messageType; + } + } + throw new IOException(String.format("Unexpected first frame byte: 0x%02X.", value)); + } + } + + private final MessageType messageType; + private final int addressField; + private final VariableDataStructure variableDataStructure; + + private MBusMessage(MessageType messageType, int addressField, VariableDataStructure variableDataStructure) { + this.messageType = messageType; + this.addressField = addressField; + this.variableDataStructure = variableDataStructure; + } + + public static MBusMessage decode(byte[] buffer, int length) throws IOException { + MessageType messageType = MessageType.messageTypeFor(buffer[0]); + int addressField; + VariableDataStructure variableDataStructure; + + switch (messageType) { + case SINGLE_CHARACTER: + addressField = 0; + variableDataStructure = null; + break; + case RSP_UD: + int messageLength = getLongFrameMessageLength(buffer, length); + checkLongFrameFields(buffer); + addressField = buffer[5] & 0xff; + variableDataStructure = new VariableDataStructure(buffer, RSP_UD_HEADER_LENGTH, messageLength, null, + null); + break; + default: + // should not occur. + throw new RuntimeException("Case not supported " + messageType); + } + + return new MBusMessage(messageType, addressField, variableDataStructure); + } + + private static void checkLongFrameFields(byte[] buffer) throws IOException { + if (buffer[1] != buffer[2]) { + throw new IOException("Length fields are not identical in long frame!"); + } + + if (buffer[3] != MessageType.RSP_UD.value) { + throw new IOException("Fourth byte of long frame was not 0x68."); + } + + int controlField = buffer[4] & 0xff; + + if ((controlField & 0xcf) != 0x08) { + throw new IOException(String.format("Unexpected control field value: 0x%02X.", controlField)); + } + } + + private static int getLongFrameMessageLength(byte[] buffer, int length) throws IOException { + int messageLength = buffer[1] & 0xff; + + if (messageLength != length - RSP_UD_HEADER_LENGTH) { + throw new IOException("Wrong length field in frame header does not match the buffer length. Length field: " + + messageLength + ", buffer length: " + length + " !"); + } + return messageLength; + } + + public int getAddressField() { + return addressField; + } + + public MessageType getMessageType() { + return messageType; + } + + public VariableDataStructure getVariableDataResponse() { + return variableDataStructure; + } + + @Override + public String toString() { + return new StringBuilder().append("message type: ").append(messageType).append("\naddress field: ") + .append(addressField & 0xff).append("\nVariable Data Structure:\n").append(variableDataStructure) + .toString(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/ScanSecondaryAddress.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/ScanSecondaryAddress.java new file mode 100644 index 0000000..5bfd194 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/ScanSecondaryAddress.java @@ -0,0 +1,218 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus; + +import static javax.xml.bind.DatatypeConverter.printHexBinary; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.text.MessageFormat; +import java.util.LinkedList; +import java.util.List; + +import org.openmuc.jmbus.MBusMessage.MessageType; + +class ScanSecondaryAddress { + + private static final int MAX_LENGTH = 16; + private static int pos = 0; + private static byte[] value = new byte[MAX_LENGTH]; + + public static List scan(MBusConnection mBusConnection, String wildcardMask, + SecondaryAddressListener secondaryAddressListener) throws IOException { + + List secondaryAddresses = new LinkedList<>(); + + boolean stop = false; + boolean collision = false; + + wildcardMask = flipString(wildcardMask); + wildcardMask += "ffffffff"; + + for (int i = 0; i < MAX_LENGTH; ++i) { + value[i] = Byte.parseByte(wildcardMask.substring(i, i + 1), 16); + } + + pos = wildcardMask.indexOf('f'); + if (pos == 8 || pos < 0) { + pos = 7; + } + value[pos] = 0; + + while (!stop) { + String msg = MessageFormat.format("scan with wildcard: {0}", printHexBinary(toSendByteArray(value))); + notifyScanMsg(secondaryAddressListener, msg); + + SecondaryAddress secondaryAddessesWildCard = SecondaryAddress.newFromLongHeader(toSendByteArray(value), 0); + SecondaryAddress readSecondaryAddress = null; + + if (scanSelection(mBusConnection, secondaryAddessesWildCard)) { + + try { + readSecondaryAddress = mBusConnection.read(0xfd).getSecondaryAddress(); + + } catch (InterruptedIOException e) { + notifyScanMsg(secondaryAddressListener, "Read (REQ_UD2) TimeoutException"); + collision = false; + } catch (IOException e) { + notifyScanMsg(secondaryAddressListener, "Read (REQ_UD2) IOException / Collision"); + collision = true; + } + + if (collision) { + if (pos < 7) { + ++pos; + value[pos] = 0; + } else { + stop = handler(); + } + collision = false; + } else { + if (readSecondaryAddress != null) { + String message = "Detected Device:\n" + readSecondaryAddress.toString(); + notifyScanMsg(secondaryAddressListener, message); + secondaryAddresses.add(readSecondaryAddress); + if (secondaryAddressListener != null) { + secondaryAddressListener.newDeviceFound(readSecondaryAddress); + } + stop = handler(); + } else { + + notifyScanMsg(secondaryAddressListener, + "Problem to decode secondary address. Perhaps a collision."); + if (pos < 7) { + ++pos; + value[pos] = 0; + } else { + stop = handler(); + } + collision = false; + } + } + } else { + stop = handler(); + } + } + if (mBusConnection != null) { + mBusConnection.close(); + } + return secondaryAddresses; + } + + /** + * Scans if any device response to the given wildcard. + * + * @param mBusConnection + * object to the open mbus connection + * + * @param wildcard + * secondary address wildcard e.g. f1ffffffffffffff + * @return true if any device responsed else false + * @throws IOException + */ + private static boolean scanSelection(MBusConnection mBusConnection, SecondaryAddress wildcard) throws IOException { + ByteBuffer bf = ByteBuffer.allocate(8); + byte[] ba = new byte[8]; + + bf.order(ByteOrder.LITTLE_ENDIAN); + + bf.put(wildcard.asByteArray()); + + bf.position(0); + bf.get(ba, 0, 8); + + mBusConnection.sendLongMessage(0xfd, 0x53, 0x52, 8, ba); + + try { + MBusMessage mBusMessage = mBusConnection.receiveMessage(); + + return mBusMessage.getMessageType() == MessageType.SINGLE_CHARACTER; + } catch (InterruptedIOException e) { + return false; + } catch (IOException e) { + return true; + } + } + + private static void notifyScanMsg(SecondaryAddressListener secondaryAddressListener, String message) { + if (secondaryAddressListener != null) { + secondaryAddressListener.newScanMessage(message); + } + } + + private static boolean handler() { + boolean stop; + + ++value[pos]; + + if (value[pos] < 10) { + stop = false; + } else { + if (pos > 0) { + --pos; + ++value[pos]; + setFValue(); + + while (value[pos] > 10) { + --pos; + ++value[pos]; + setFValue(); + } + stop = false; + } else { + stop = true; + } + } + + return stop; + } + + private static void setFValue() { + for (int i = pos + 1; i < 8; ++i) { + value[i] = 0xf; + } + } + + private static byte[] toSendByteArray(byte[] value) { + byte[] sendByteArray = new byte[8]; + + for (int i = 0; i < MAX_LENGTH; ++i) { + + if (i % 2 > 0) { + sendByteArray[i / 2] |= value[i] << 4; + } else { + sendByteArray[i / 2] |= value[i]; + } + } + return sendByteArray; + } + + /** + * Flips character pairs.
+ * from 01253fffffffffff to 1052f3ffffffffff + * + * @param value + * a string value like 01253fffffffffff + * @return a fliped string value. + */ + private static String flipString(String value) { + StringBuilder flipped = new StringBuilder(); + + for (int i = 0; i < value.length(); i += 2) { + flipped.append(value.charAt(i + 1)); + flipped.append(value.charAt(i)); + } + return flipped.toString(); + } + + /** + * Don't let anyone instantiate this class. + */ + private ScanSecondaryAddress() { + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/SecondaryAddress.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/SecondaryAddress.java new file mode 100644 index 0000000..86f41cf --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/SecondaryAddress.java @@ -0,0 +1,224 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; + +import javax.xml.bind.DatatypeConverter; + +/** + * This class represents a secondary address. Use the static initializer to initialize the + */ +public class SecondaryAddress implements Comparable { + + private static final int SECONDARY_ADDRESS_LENGTH = 8; + + private static final int ID_NUMBER_LENGTH = 4; + + private final String manufacturerId; + private final Bcd deviceId; + private final int version; + private final DeviceType deviceType; + private final byte[] bytes; + private final int hashCode; + private final boolean isLongHeader; + + /** + * Instantiate a new secondary address within a long header. + * + * @param buffer + * the byte buffer. + * @param offset + * the offset. + * @return a new secondary address. + */ + public static SecondaryAddress newFromLongHeader(byte[] buffer, int offset) { + return new SecondaryAddress(buffer, offset, true); + } + + /** + * Instantiate a new secondary address within a wireless M-Bus link layer header. + * + * @param buffer + * the byte buffer. + * @param offset + * the offset. + * @return a new secondary address. + */ + public static SecondaryAddress newFromWMBusLlHeader(byte[] buffer, int offset) { + return new SecondaryAddress(buffer, offset, false); + } + + /** + * Instantiate a new secondary address for a manufacturer ID. + * + * @param idNumber + * ID number. + * @param manufactureId + * manufacturer ID. + * @param version + * the version. + * @param media + * the media. + * @return a new secondary address. + * @throws NumberFormatException + * if the idNumber is not long enough. + */ + public static SecondaryAddress newFromManufactureId(byte[] idNumber, String manufactureId, byte version, byte media) + throws NumberFormatException { + if (idNumber.length != ID_NUMBER_LENGTH) { + throw new NumberFormatException("Wrong length of ID. Length must be " + ID_NUMBER_LENGTH + " byte."); + } + + byte[] mfId = encodeManufacturerId(manufactureId); + byte[] buffer = ByteBuffer.allocate(idNumber.length + mfId.length + 1 + 1).put(idNumber).put(mfId).put(version) + .put(media).array(); + return new SecondaryAddress(buffer, 0, true); + } + + /** + * The {@link SecondaryAddress} as byte array. + * + * @return the byte array (octet string) representation. + */ + public byte[] asByteArray() { + return bytes; + } + + /** + * Get the manufacturer ID. + * + * @return the ID. + */ + public String getManufacturerId() { + return manufacturerId; + } + + /** + * Returns the device ID. This is secondary address of the device. + * + * @return the device ID + */ + public Bcd getDeviceId() { + return deviceId; + } + + /** + * Returns the device type (e.g. gas, water etc.) + * + * @return the device type + */ + public DeviceType getDeviceType() { + return deviceType; + } + + /** + * Get the version. + * + * @return the version. + */ + public int getVersion() { + return version; + } + + public boolean isLongHeader() { + return isLongHeader; + } + + @Override + public String toString() { + return new StringBuilder().append("manufacturer ID: ").append(manufacturerId).append(", device ID: ") + .append(deviceId).append(", device version: ").append(version).append(", device type: ") + .append(deviceType).append(", as bytes: ").append(DatatypeConverter.printHexBinary(bytes)).toString(); + } + + @Override + public int hashCode() { + return this.hashCode; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof SecondaryAddress)) { + return false; + } + + SecondaryAddress other = (SecondaryAddress) obj; + + return Arrays.equals(this.bytes, other.bytes); + } + + @Override + public int compareTo(SecondaryAddress sa) { + return Integer.compare(hashCode(), sa.hashCode()); + } + + private SecondaryAddress(byte[] buffer, int offset, boolean longHeader) { + this.bytes = Arrays.copyOfRange(buffer, offset, offset + SECONDARY_ADDRESS_LENGTH); + + this.hashCode = Arrays.hashCode(this.bytes); + this.isLongHeader = longHeader; + + try (ByteArrayInputStream is = new ByteArrayInputStream(this.bytes)) { + if (longHeader) { + this.deviceId = decodeDeviceId(is); + this.manufacturerId = decodeManufacturerId(is); + } else { + this.manufacturerId = decodeManufacturerId(is); + this.deviceId = decodeDeviceId(is); + } + this.version = is.read() & 0xff; + this.deviceType = DeviceType.getInstance(is.read() & 0xff); + } catch (IOException e) { + // should not occur + throw new RuntimeException(e); + } + } + + private static String decodeManufacturerId(ByteArrayInputStream is) { + int manufacturerIdAsInt = (is.read() & 0xff) + (is.read() << 8); + char c = (char) ((manufacturerIdAsInt & 0x1f) + 64); + manufacturerIdAsInt = (manufacturerIdAsInt >> 5); + char c1 = (char) ((manufacturerIdAsInt & 0x1f) + 64); + manufacturerIdAsInt = (manufacturerIdAsInt >> 5); + char c2 = (char) ((manufacturerIdAsInt & 0x1f) + 64); + + return new StringBuilder().append(c2).append(c1).append(c).toString(); + } + + private static byte[] encodeManufacturerId(String manufactureId) { + if (manufactureId.length() != 3) { + return new byte[] { 0, 0 }; + } + + manufactureId = manufactureId.toUpperCase(); + char[] manufactureIdArray = manufactureId.toCharArray(); + int manufacturerIdAsInt = (manufactureIdArray[0] - 64) * 32 * 32; + manufacturerIdAsInt += (manufactureIdArray[1] - 64) * 32; + manufacturerIdAsInt += (manufactureIdArray[2] - 64); + + ByteBuffer buf = ByteBuffer.allocate(2); + buf.order(ByteOrder.LITTLE_ENDIAN); + buf.putShort((short) manufacturerIdAsInt); + + return buf.array(); + } + + private static Bcd decodeDeviceId(ByteArrayInputStream is) throws IOException { + int msgSize = 4; + byte[] idArray = new byte[msgSize]; + int actual = is.read(idArray); + + if (msgSize != actual) { + throw new IOException("Failed to read BCD data. Data missing."); + } + return new Bcd(idArray); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/SecondaryAddressListener.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/SecondaryAddressListener.java new file mode 100644 index 0000000..3c9409c --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/SecondaryAddressListener.java @@ -0,0 +1,30 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus; + +import java.util.EventListener; + +/** + * Listener to get secondary address scan message e.g. for console tools and to get messages. + */ +public interface SecondaryAddressListener extends EventListener { + + /** + * New scan message. + * + * @param message + * messages from scan secondary address + */ + void newScanMessage(String message); + + /** + * New device found. + * + * @param secondaryAddress + * secondary address of detected device + */ + void newDeviceFound(SecondaryAddress secondaryAddress); +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/VariableDataStructure.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/VariableDataStructure.java new file mode 100644 index 0000000..e6cbbdc --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/VariableDataStructure.java @@ -0,0 +1,449 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.xml.bind.DatatypeConverter; + +/** + * Representation of the data transmitted in RESP-UD (M-Bus) and SND-NR (wM-Bus) messages. + * + * @see #decode() + */ +public class VariableDataStructure { + + private static final ConcurrentHashMap> deviceHistory = new ConcurrentHashMap<>(); + + private final byte[] buffer; + private final int offset; + private final int length; + private byte[] header = new byte[0]; + private final SecondaryAddress linkLayerSecondaryAddress; + private final Map keyMap; + + private SecondaryAddress secondaryAddress; + private int accessNumber; + private int status; + + /* Extended Link Layer (ELL) (0x8d) specific */ + private byte communicationControl; + private byte[] sessionNumber; + /* End of ELL specific */ + + private EncryptionMode encryptionMode; + private int numberOfEncryptedBlocks; + private byte[] manufacturerData = new byte[0]; + private byte[] vdr = new byte[0]; + private boolean moreRecordsFollow = false; + + private boolean decoded = false; + + private List dataRecords; + + public VariableDataStructure(byte[] buffer, int offset, int length, SecondaryAddress linkLayerSecondaryAddress, + Map keyMap) { + this.buffer = buffer; + this.offset = offset; + this.length = length; + this.linkLayerSecondaryAddress = linkLayerSecondaryAddress; + this.keyMap = keyMap; + this.dataRecords = new LinkedList<>(); + } + + /** + * This method is used to + * + * @throws DecodingException + */ + public void decode() throws DecodingException { + if (!decoded) { + try { + int ciField = readUnsignedByte(buffer, offset); + + switch (ciField) { + case 0x72: + decodeLongHeaderData(); + break; + case 0x78: /* no header */ + encryptionMode = EncryptionMode.NONE; + decodeDataRecords(buffer, offset + 1, length - 1); + break; + case 0x7a: /* short header */ + decodeWithShortHeader(); + break; + case 0x8d: /* ELL */ + decodeExtendedLinkLayer(buffer, offset + 1); // 6 bytes header + CRC + header = Arrays.copyOfRange(buffer, offset, offset + 7); // don't include CRC + vdr = new byte[length - 7]; + System.arraycopy(buffer, offset + 7, vdr, 0, length - 7); + if (encryptionMode.equals(EncryptionMode.AES_128)) { + decryptMessage(getKey()); + } + + if ((vdr[2] & 0xff) == 0x78) { + decodeDataRecords(vdr, 3, length - 10); + } else if ((vdr[2] & 0xff) == 0x79) { + decodeShortFrame(vdr, 3, length - 10); + } + break; + case 0x33: + String msg = String.format( + "Received telegram with CI 0x33. Decoding not implemented. Device Serial: %s, Manufacturer: %s.", + linkLayerSecondaryAddress.getDeviceId().toString(), + linkLayerSecondaryAddress.getManufacturerId()); + throw new DecodingException(msg); + default: + String strFormat = "Unable to decode message with this CI Field: 0x%02X."; + if ((ciField >= 0xA0) && (ciField <= 0xB7)) { + strFormat = "Manufacturer specific CI: 0x%02X."; + } + + throw new DecodingException(String.format(strFormat, ciField)); + } + } catch (RuntimeException e) { + throw new DecodingException(e); + } + + decoded = true; + } + } + + private void decodeWithShortHeader() throws DecodingException { + decodeShortHeader(buffer, offset + 1); + if (encryptionMode == EncryptionMode.NONE) { + decodeDataRecords(buffer, offset + 5, length - 5); + } else if (encryptionMode == EncryptionMode.AES_CBC_IV) { + decryptAesCbcIv(buffer, offset + 5, numberOfEncryptedBlocks * 16); + } else { + throw new DecodingException("Unsupported encryption mode used: " + encryptionMode); + } + } + + private void decryptAesCbcIv(byte[] buffer, int offset, int encryptedDataLength) throws DecodingException { + final int len = length - 5; + vdr = new byte[len]; + System.arraycopy(buffer, offset, vdr, 0, encryptedDataLength); + + byte[] key = keyMap.get(linkLayerSecondaryAddress); + if (key == null) { + String msg = MessageFormat.format( + "Unable to decode encrypted payload. \nSecondary address key was not registered: \n{0}", + linkLayerSecondaryAddress); + throw new DecodingException(msg); + } + + decodeDataRecords(decryptMessage(key), 0, len); + } + + private void decodeLongHeaderData() throws DecodingException { + final int headerLength = 13; + header = Arrays.copyOfRange(buffer, offset, offset + headerLength); + + secondaryAddress = SecondaryAddress.newFromLongHeader(buffer, offset + 1); + + decodeShortHeader(buffer, offset + 1 + 8); + + vdr = new byte[length - headerLength]; + System.arraycopy(buffer, offset + headerLength, vdr, 0, length - headerLength); + + if (encryptionMode == EncryptionMode.AES_CBC_IV) { + decryptMessage(getKey()); + } else if (encryptionMode != EncryptionMode.NONE) { + throw new DecodingException("Unsupported encryption mode used: " + encryptionMode); + } + decodeDataRecords(vdr, 0, length - headerLength); + } + + public SecondaryAddress getSecondaryAddress() { + return secondaryAddress; + } + + public int getAccessNumber() { + return accessNumber; + } + + public EncryptionMode getEncryptionMode() { + return encryptionMode; + } + + public byte[] getManufacturerData() { + return manufacturerData; + } + + public int getNumberOfEncryptedBlocks() { + return numberOfEncryptedBlocks; + } + + public int getStatus() { + return status; + } + + public List getDataRecords() { + return dataRecords; + } + + public boolean moreRecordsFollow() { + return moreRecordsFollow; + } + + private void decodeExtendedLinkLayer(byte[] buffer, int offset) { + int i = offset; + + communicationControl = buffer[i++]; + accessNumber = buffer[i++]; + sessionNumber = new byte[] { buffer[i++], buffer[i++], buffer[i++], buffer[i++] }; + encryptionMode = EncryptionMode.getInstance(sessionNumber[3] >> 5); + byte[] checksum = new byte[] { buffer[i++], buffer[i++] }; + + byte[] crc = CRC16.calculateCrc16(Arrays.copyOfRange(buffer, i, buffer.length - 1)); + if (checksum[0] == crc[0] && checksum[1] == crc[1]) { + encryptionMode = EncryptionMode.NONE; + } + } + + private void decodeShortHeader(byte[] buffer, int offset) { + int i = offset; + + accessNumber = readUnsignedByte(buffer, i++); + status = readUnsignedByte(buffer, i++); + numberOfEncryptedBlocks = (buffer[i++] & 0xf0) >> 4; + encryptionMode = EncryptionMode.getInstance(buffer[i++] & 0x0f); + + if (msgIsNotEnc(buffer, i)) { + encryptionMode = EncryptionMode.NONE; + } + } + + private static boolean msgIsNotEnc(byte[] buffer, int i) { + byte b = buffer[i]; + byte b2 = buffer[i + 1]; + return b == (byte) 0x2f && b2 == (byte) 0x02; + } + + private static int readUnsignedByte(byte[] msg, int i) { + return msg[i] & 0xff; + } + + public byte[] getHeader() { + return this.header; + } + + private void decodeDataRecords(byte[] buffer, int offset, int length) throws DecodingException { + int i = offset; + + while (i < offset + length - 2) { + + if ((buffer[i] & 0xef) == 0x0f) { + // manufacturer specific data + + moreRecordsFollow = (buffer[i] & 0x10) == 0x10; + + manufacturerData = Arrays.copyOfRange(buffer, i + 1, offset + length - 2); + return; + } + + if (buffer[i] == 0x2f) { + // this is a fill byte because some encryption mechanisms need multiples of 8 bytes to encode data + i++; + continue; + } + + DataRecord dataRecord = new DataRecord(); + i = dataRecord.decode(buffer, i, length); + + dataRecords.add(dataRecord); + } + + if (linkLayerSecondaryAddress != null) { + deviceHistory.put(linkLayerSecondaryAddress, dataRecords); + } + } + + private void decodeShortFrame(byte[] data, int offset, int length) throws DecodingException { + if (!deviceHistory.containsKey(linkLayerSecondaryAddress)) { + deviceHistory.put(linkLayerSecondaryAddress, new LinkedList()); + } + + ByteBuffer buf = ByteBuffer.wrap(data, offset, length); + buf.order(ByteOrder.nativeOrder()); + + // skip checksum data + buf.position(4); + + this.dataRecords = deviceHistory.get(linkLayerSecondaryAddress); + + ListIterator iter = this.dataRecords.listIterator(); + while (iter.hasNext()) { + DataRecord dr = iter.next(); + try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { + os.write(dr.getDib()); + os.write(dr.getVib()); + + int tempOffset = dr.getDib().length + dr.getVib().length; + + // This might not be right + int dataLegth = tempOffset + dr.getDataLength(); + byte[] b = new byte[dataLegth - tempOffset]; + buf.get(b); + os.write(b); + + DataRecord newDataRecord = new DataRecord(); + newDataRecord.decode(os.toByteArray(), 0, dataLegth); + iter.set(newDataRecord); + } catch (IOException e) { + // ignore + } + + } + } + + public byte[] decryptMessage(byte[] key) throws DecodingException { + + if (encryptionMode == EncryptionMode.NONE) { + return vdr; + } + + if (key == null) { + throw new DecodingException("AES key for given address not specified."); + } + + final int len = numberOfEncryptedBlocks * 16; + + if (len > vdr.length) { + throw new DecodingException("Number of encrypted exceeds payload size!"); + } + + switch (encryptionMode) { + case AES_CBC_IV: + decryptAesCbcIv(key, len); + break; + case AES_128: + decryptAes128(key, len); + break; + default: + throw new DecodingException("Unsupported encryption mode: " + encryptionMode); + } + + return vdr; + } + + private void decryptAes128(byte[] key, final int len) throws DecodingException { + byte[] iv = createIvKamstrup(); + byte[] result = AesCrypt.newAesCtrCrypt(key, iv).decrypt(vdr, len); + + byte[] crc = CRC16.calculateCrc16(Arrays.copyOfRange(result, 2, result.length)); + + if (result[0] != crc[0] || result[1] != crc[1]) { + throw new DecodingException(newDecyptionExceptionMsg()); + } + vdr = result; + } + + private void decryptAesCbcIv(byte[] key, final int len) throws DecodingException { + byte[] iv = createIv(); + byte[] result = AesCrypt.newAesCrypt(key, iv).decrypt(this.vdr, len); + if (!(result[0] == 0x2f && result[1] == 0x2f)) { + throw new DecodingException(newDecyptionExceptionMsg()); + } + System.arraycopy(result, 0, vdr, 0, len); + } + + private String newDecyptionExceptionMsg() { + String deviceId = linkLayerSecondaryAddress.getDeviceId().toString(); + String manId = linkLayerSecondaryAddress.getManufacturerId(); + return String.format("%s - %s - Decryption unsuccessful! Wrong AES/CTR Key?", deviceId, manId); + } + + private byte[] createIv() { + byte[] iv = new byte[16]; + byte[] saBytes = linkLayerSecondaryAddress.asByteArray(); + + if (linkLayerSecondaryAddress.isLongHeader()) { + System.arraycopy(saBytes, 0, iv, 4, 2); // Manufacture + System.arraycopy(saBytes, 2, iv, 0, 4); // Identification + System.arraycopy(saBytes, 6, iv, 6, 2); // Version and Device Type + } else { + System.arraycopy(saBytes, 0, iv, 0, 8); + } + + for (int i = 8; i < iv.length; i++) { + iv[i] = (byte) accessNumber; + } + + return iv; + } + + private byte[] createIvKamstrup() throws DecodingException { + try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { + + os.write(linkLayerSecondaryAddress.asByteArray(), 0, 8); + /* set hop count to 0 in case a repeater is used */ + os.write(communicationControl & ~(1 << 4)); + os.write(sessionNumber); + os.write(new byte[3]); // 3 * 0x00 + + return os.toByteArray(); + } catch (IOException e) { + throw new DecodingException("Unable to create initial vector for decryption.", e); + } + } + + private byte[] getKey() throws DecodingException { + byte[] key = keyMap.get(linkLayerSecondaryAddress); + if (key != null) { + return key; + } + + String msg = "Unable to decode encrypted payload because no key for the following secondary address was registered: " + + linkLayerSecondaryAddress; + throw new DecodingException(msg); + } + + @Override + public String toString() { + if (!decoded) { + int from = offset; + int to = from + length; + String hexString = DatatypeConverter.printHexBinary(Arrays.copyOfRange(buffer, from, to)); + return MessageFormat.format("VariableDataResponse has not been decoded. Bytes:\n{0}", hexString); + } + + StringBuilder builder = new StringBuilder(); + + if (secondaryAddress != null) { + builder.append("Secondary address: {").append(secondaryAddress).append("}\n"); + } + + builder.append("Short Header: {Access No.: ").append(accessNumber).append(", status: ").append(status) + .append(", encryption mode: ").append(encryptionMode).append(", number of encrypted blocks: ") + .append(numberOfEncryptedBlocks).append("}"); + + for (DataRecord dataRecord : dataRecords) { + builder.append("\n").append(dataRecord.toString()); + } + + if (manufacturerData.length != 0) { + String manDaraHexStr = DatatypeConverter.printHexBinary(manufacturerData); + builder.append("\nManufacturer specific bytes:\n").append(manDaraHexStr); + } + + if (moreRecordsFollow) { + builder.append("\nMore records follow ..."); + } + return builder.toString(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/VerboseMessage.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/VerboseMessage.java new file mode 100644 index 0000000..31b4387 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/VerboseMessage.java @@ -0,0 +1,48 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus; + +/** + * This class represents a verbose message. This may be useful to debug a connection. + * + * @see VerboseMessageListener + */ +public class VerboseMessage { + + private final MessageDirection messageDirection; + private final byte[] message; + + VerboseMessage(MessageDirection messageDirection, byte[] message) { + this.messageDirection = messageDirection; + this.message = message; + } + + /** + * Get the message data. + * + * @return an octet string. + */ + public byte[] getMessage() { + return message; + } + + /** + * Get the direction of the message. + * + * @return the message direction. + */ + public MessageDirection getMessageDirection() { + return messageDirection; + } + + /** + * The direction of message. + */ + public enum MessageDirection { + SEND, + RECEIVE; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/VerboseMessageListener.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/VerboseMessageListener.java new file mode 100644 index 0000000..60254b9 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/VerboseMessageListener.java @@ -0,0 +1,20 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus; + +import java.util.EventListener; + +/** + * + * @see MBusConnection#setVerboseMessageListener(VerboseMessageListener) + */ +public interface VerboseMessageListener extends EventListener { + /** + * + * @param debugMessage + */ + void newVerboseMessage(VerboseMessage debugMessage); +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/package-info.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/package-info.java new file mode 100644 index 0000000..14376e6 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/package-info.java @@ -0,0 +1,11 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/** + * Main jmbus package. + * + * @see org.openmuc.jmbus.MBusConnection + */ +package org.openmuc.jmbus; diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/Builder.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/Builder.java new file mode 100644 index 0000000..887e435 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/Builder.java @@ -0,0 +1,64 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.transportlayer; + +import java.io.IOException; + +/** + * A abstract builder to an active M-Bus connection. + * + * @param + * the resulting connection after calling {@link #build()}. + * @param + * the inheriting builder type. + */ +public abstract class Builder> { + + private int timeout; + + protected Builder() { + this.timeout = 500; + } + + /** + * Set the connection timeout. The timeout must be ≥ zero. A timeout of zero is interpreted as infinite timeout, + * + * @param timeout + * a timeout in milliseconds. + * @return the builder itself. + */ + public B setTimeout(int timeout) { + this.timeout = timeout; + return self(); + } + + int getTimeout() { + return timeout; + } + + @SuppressWarnings("unchecked") + protected B self() { + return (B) this; + } + + /** + * Build the TransportLayer with the given settings. + * + * @return TransportLayer to connect to the M-Bus device + * @throws IOException + * if an I/O exception occurred. + */ + protected abstract TransportLayer buildTransportLayer() throws IOException; + + /** + * This return an active M-Bus connection. + * + * @return a new active M-Bus connection. + * @throws IOException + * if an I/O exception occurred opening the connection to the remote device. + */ + public abstract C build() throws IOException; +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/SerialBuilder.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/SerialBuilder.java new file mode 100644 index 0000000..0b49c4d --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/SerialBuilder.java @@ -0,0 +1,109 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.transportlayer; + +import org.openmuc.jrxtx.DataBits; +import org.openmuc.jrxtx.Parity; +import org.openmuc.jrxtx.SerialPortBuilder; +import org.openmuc.jrxtx.StopBits; + +/** + * Connection builder for serial connections. + */ +public abstract class SerialBuilder> extends Builder { + + private String serialPortName; + private int baudrate; + + private DataBits dataBits; + private StopBits stopBits; + private Parity parity; + + /** + * Constructor of the Serial Settings Builder, for connecting M-Bus devices over serial connections like RS232 and + * RS485. With default settings. + * + * @param serialPortName + * examples for serial port identifiers are on Linux "/dev/ttyS0" or "/dev/ttyUSB0" and on Windows "COM1" + **/ + protected SerialBuilder(String serialPortName) { + this.serialPortName = serialPortName; + + this.baudrate = 2400; + this.dataBits = DataBits.DATABITS_8; + this.stopBits = StopBits.STOPBITS_1; + this.parity = Parity.EVEN; + } + + /** + * Sets the baudrate of the device + * + * @param baudrate + * the baud rate to use. + * @return the builder itself + */ + public S setBaudrate(int baudrate) { + this.baudrate = baudrate; + return self(); + } + + /** + * Sets the serial port name of the device + * + * @param serialPortName + * examples for serial port identifiers are on Linux {@code "/dev/ttyS0"} or {@code "/dev/ttyUSB0"} and + * on Windows {@code "COM1"}. + * @return the builder itself. + */ + public S setSerialPortName(String serialPortName) { + this.serialPortName = serialPortName; + return self(); + } + + /** + * Sets the number of DataBits, default is {@link DataBits#DATABITS_8}. + * + * @param dataBits + * the new number of databits. + * @return the builder itself. + */ + public S setDataBits(DataBits dataBits) { + this.dataBits = dataBits; + return self(); + } + + /** + * Sets the stop bits, default is 1 + * + * @param stopBits + * Possible values are 1, 1.5 or 2 + * @return the builder itself + */ + public S setStopBits(StopBits stopBits) { + this.stopBits = stopBits; + return self(); + } + + /** + * Sets the parity, default is NONE + * + * @param parity + * Possible values are NONE, EVEN, ODD, SPACE or MARK. + * @return the builder itself + */ + public S setParity(Parity parity) { + this.parity = parity; + return self(); + } + + @Override + protected TransportLayer buildTransportLayer() { + SerialPortBuilder serialPortBuilder = SerialPortBuilder.newBuilder(serialPortName).setBaudRate(baudrate) + .setDataBits(dataBits).setStopBits(stopBits).setStopBits(stopBits).setParity(parity); + + return new SerialLayer(getTimeout(), serialPortBuilder); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/SerialLayer.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/SerialLayer.java new file mode 100644 index 0000000..fb4c2a7 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/SerialLayer.java @@ -0,0 +1,73 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.transportlayer; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import org.openmuc.jrxtx.SerialPort; +import org.openmuc.jrxtx.SerialPortBuilder; + +class SerialLayer implements TransportLayer { + private final SerialPortBuilder serialPortBuilder; + private final int timeout; + + private DataOutputStream os; + private DataInputStream is; + private SerialPort serialPort; + + public SerialLayer(int timeout, SerialPortBuilder serialPortBuilder) { + this.serialPortBuilder = serialPortBuilder; + this.timeout = timeout; + } + + @Override + public void open() throws IOException { + serialPort = serialPortBuilder.build(); + serialPort.setSerialPortTimeout(timeout); + + os = new DataOutputStream(serialPort.getOutputStream()); + is = new DataInputStream(serialPort.getInputStream()); + } + + @Override + public DataOutputStream getOutputStream() { + return os; + } + + @Override + public DataInputStream getInputStream() { + return is; + } + + @Override + public void close() { + if (serialPort == null || serialPort.isClosed()) { + return; + } + try { + serialPort.close(); + } catch (IOException e) { + // ignore + } + } + + @Override + public boolean isClosed() { + return serialPort == null; + } + + @Override + public void setTimeout(int timeout) throws IOException { + serialPort.setSerialPortTimeout(timeout); + } + + @Override + public int getTimeout() { + return serialPort.getSerialPortTimeout(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/TcpBuilder.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/TcpBuilder.java new file mode 100644 index 0000000..28240ce --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/TcpBuilder.java @@ -0,0 +1,73 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.transportlayer; + +/** + * Connection builder for TCP connections.
+ * M-Bus over TCP has the same data like M-Bus over Serial connection, only the transport layer and below differs. + */ +public abstract class TcpBuilder> extends Builder { + + private String hostAddress; + private int port; + private int connectionTimeout = 10000; // 10 s + + /** + * Constructor of the TCP/IP Settings Builder, for connecting M-Bus devices over TCP/IP. + * + * @param hostAddress + * examples for IP host address port are "127.0.0.1, localhost, ..." + * @param port + * the TCP port of the host + **/ + protected TcpBuilder(String hostAddress, int port) { + setHostAddress(hostAddress); + setPort(port); + } + + /** + * Sets the TCP port of this communication + * + * @param port + * the TCP port of the host + * @return the builder itself + * + **/ + public S setPort(int port) { + this.port = port; + return self(); + } + + /** + * Sets the IP host address of the device + * + * @param hostAddress + * examples for IP host address port are "127.0.0.1, localhost, ..." + * @return the builder itself + */ + public S setHostAddress(String hostAddress) { + this.hostAddress = hostAddress; + return self(); + } + + /** + * Sets the TCP connection timeout. + * + * @param connectionTimeout + * the TCP connection timeout + * @return the builder itself + * + **/ + public S setConnectionTimeout(int connectionTimeout) { + this.connectionTimeout = connectionTimeout; + return self(); + } + + @Override + protected TransportLayer buildTransportLayer() { + return new TcpLayer(hostAddress, port, connectionTimeout, getTimeout()); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/TcpLayer.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/TcpLayer.java new file mode 100644 index 0000000..a8c4d0e --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/TcpLayer.java @@ -0,0 +1,98 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.transportlayer; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.text.MessageFormat; + +class TcpLayer implements TransportLayer { + private final String hostAddress; + private final int port; + private final int timeout; + private final int connectionTimeout; + + private Socket client; + private DataOutputStream os; + private DataInputStream is; + + TcpLayer(String hostAddress, int port, int connectionTimeout, int timeout) { + this.hostAddress = hostAddress; + this.port = port; + this.timeout = timeout; + this.connectionTimeout = connectionTimeout; + } + + @Override + public void open() throws IOException { + InetAddress hostname = InetAddress.getByName(hostAddress); + + try { + SocketAddress socketAddress = new InetSocketAddress(hostname, port); + this.client = new Socket(); + this.client.connect(socketAddress, connectionTimeout); + this.client.setSoTimeout(timeout); + } catch (IOException e) { + String msg = MessageFormat.format("Connecting to {0}:{1} failed.", hostname, port); + throw new IOException(msg, e); + } + + initialiseIOStreams(); + } + + private void initialiseIOStreams() throws IOException { + try { + this.os = new DataOutputStream(client.getOutputStream()); + this.is = new DataInputStream(client.getInputStream()); + } catch (IOException e) { + close(); + throw new IOException("Error getting output or input stream from TCP connection.", e); + } + } + + @Override + public void close() { + if (client == null || client.isClosed()) { + return; + } + try { + client.close(); + } catch (IOException e) { + // ignore this here + } + client = null; + } + + @Override + public DataOutputStream getOutputStream() { + return os; + } + + @Override + public DataInputStream getInputStream() { + return is; + } + + @Override + public boolean isClosed() { + return client.isClosed(); + } + + @Override + public void setTimeout(int timeout) throws IOException { + client.setSoTimeout(timeout); + } + + @Override + public int getTimeout() throws IOException { + return client.getSoTimeout(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/TransportLayer.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/TransportLayer.java new file mode 100644 index 0000000..fc9776a --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/TransportLayer.java @@ -0,0 +1,72 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.transportlayer; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * The MBus transport layer interface. + */ +public interface TransportLayer extends AutoCloseable { + + /** + * Opens the transport layer. The layer needs to be opened before attempting to read a device. + * + * @throws IOException + * if an I/O error occurs while opening. + * + */ + void open() throws IOException; + + /** + * Closes the transport layer. + */ + @Override + void close(); + + /** + * Get the output stream of the layer. + * + * @return the output stream. + */ + DataOutputStream getOutputStream(); + + /** + * Get the input stream of the layer. + * + * @return the input stream. + */ + DataInputStream getInputStream(); + + /** + * Check if the layer is open. + * + * @return {@code true} if the layer is closed. + */ + boolean isClosed(); + + /** + * Set the response timeout. + * + * @param timeout + * the timeout in MILLIS. + * @throws IOException + * if an I/O error occurs. + */ + void setTimeout(int timeout) throws IOException; + + /** + * Get the response timeout in MILLIS. + * + * @return the response timeout in MILLIS. + * + * @throws IOException + * if an I/O error occurs. + */ + int getTimeout() throws IOException; +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/package-info.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/package-info.java new file mode 100644 index 0000000..02a656a --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/package-info.java @@ -0,0 +1,9 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/** + * This package contains the transport layers and their builders. + */ +package org.openmuc.jmbus.transportlayer; diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/AbstractWMBusConnection.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/AbstractWMBusConnection.java new file mode 100644 index 0000000..7554611 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/AbstractWMBusConnection.java @@ -0,0 +1,132 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.wireless; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.openmuc.jmbus.SecondaryAddress; +import org.openmuc.jmbus.transportlayer.TransportLayer; + +abstract class AbstractWMBusConnection implements WMBusConnection { + + private static final int ACK = 0x3E; + + protected static final int BUFFER_LENGTH = 1000; + protected static final int MESSAGE_FRAGEMENT_TIMEOUT = 1000; + + private TransportLayer transportLayer; + + private final WMBusMode mode; + private final WMBusListener listener; + + final Map keyMap = new HashMap<>(); + + private volatile boolean closed; + private final ExecutorService receiverService; + + protected AbstractWMBusConnection(WMBusMode mode, WMBusListener listener, TransportLayer tl) { + this.listener = listener; + this.mode = mode; + this.transportLayer = tl; + + this.closed = true; + this.receiverService = Executors.newSingleThreadExecutor(); + } + + @Override + public final void close() { + if (this.transportLayer == null || this.closed) { + // nothing to do + return; + } + + try { + this.receiverService.shutdown(); + this.transportLayer.close(); + } finally { + this.transportLayer = null; + this.closed = true; + } + } + + @Override + public final void addKey(SecondaryAddress address, byte[] key) { + this.keyMap.put(address, key); + } + + @Override + public final void removeKey(SecondaryAddress address) { + this.keyMap.remove(address); + } + + public final void open() throws IOException { + if (!closed) { + return; + } + try { + transportLayer.open(); + + initializeWirelessTransceiver(mode); + + } catch (IOException e) { + transportLayer.close(); + + throw e; + } + this.receiverService.execute(newMessageReceiver(this.transportLayer, this.listener)); + + this.closed = false; + } + + protected abstract MessageReceiver newMessageReceiver(TransportLayer transportLayer, WMBusListener listener); + + protected abstract void initializeWirelessTransceiver(WMBusMode mode) throws IOException; + + protected boolean isClosed() { + return closed; + } + + protected DataInputStream getInputStream() { + return this.transportLayer.getInputStream(); + } + + protected DataOutputStream getOutputStream() { + return this.transportLayer.getOutputStream(); + } + + protected long discardNoise() throws IOException { + DataInputStream inputStream = getInputStream(); + + if (inputStream.available() == 0) { + return 0; + } + + transportLayer.setTimeout(MESSAGE_FRAGEMENT_TIMEOUT); + try { + return inputStream.skip(500); + + } catch (InterruptedIOException e) { + // ignore + return 0; + } + } + + protected void waitForAck() throws IOException { + transportLayer.setTimeout(MESSAGE_FRAGEMENT_TIMEOUT); + + int b = getInputStream().read(); + if (b != ACK) { + throw new IOException(String.format("Did not receive ACK. Received 0x%02X instead.", b)); + } + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/HciMessageException.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/HciMessageException.java new file mode 100644 index 0000000..d9d99c8 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/HciMessageException.java @@ -0,0 +1,16 @@ +package org.openmuc.jmbus.wireless; + +import java.io.IOException; + +class HciMessageException extends IOException { + + private final byte[] data; + + public HciMessageException(byte[] data) { + this.data = data; + } + + public byte[] getData() { + return data; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/MessageReceiver.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/MessageReceiver.java new file mode 100644 index 0000000..32d9d01 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/MessageReceiver.java @@ -0,0 +1,52 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.wireless; + +import java.io.IOException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +abstract class MessageReceiver implements Runnable { + + private final ExecutorService executor; + private final WMBusListener listener; + + public MessageReceiver(WMBusListener listener) { + this.listener = listener; + this.executor = Executors.newSingleThreadExecutor(); + } + + protected void shutdown() { + this.executor.shutdown(); + } + + protected void notifyStoppedListening(final IOException ioException) { + executor.execute(new Runnable() { + @Override + public void run() { + listener.stoppedListening(ioException); + } + }); + } + + protected void notifyNewMessage(final WMBusMessage wmBusMessage) { + executor.execute(new Runnable() { + @Override + public void run() { + listener.newMessage(wmBusMessage); + } + }); + } + + protected void notifyDiscarded(final byte[] discardedBytes) { + executor.execute(new Runnable() { + @Override + public void run() { + listener.discardedBytes(discardedBytes); + } + }); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/VirtualWMBusMessageHelper.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/VirtualWMBusMessageHelper.java index 85e315a..76709ab 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/VirtualWMBusMessageHelper.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/VirtualWMBusMessageHelper.java @@ -1,29 +1,32 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ - -package org.openmuc.jmbus.wireless; - -import java.util.Map; - -import org.openmuc.jmbus.DecodingException; -import org.openmuc.jmbus.SecondaryAddress; - -/** - * The {@link VirtualWMBusMessageHelper} class defines VirtualWMBusMessageHelper - * - * @author Roman Malyugin - Initial contribution - */ -public class VirtualWMBusMessageHelper { - - public static WMBusMessage decode(byte[] buffer, Integer signalStrengthInDBm, Map keyMap) - throws DecodingException { - return WMBusMessage.decode(buffer, signalStrengthInDBm, keyMap); - - } -} +/** + * Copyright (c) 2010-2021 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.openmuc.jmbus.wireless; + +import java.util.Map; + +import org.openmuc.jmbus.DecodingException; +import org.openmuc.jmbus.SecondaryAddress; + +/** + * The {@link VirtualWMBusMessageHelper} class defines VirtualWMBusMessageHelper + * + * @author Roman Malyugin - Initial contribution + */ +public class VirtualWMBusMessageHelper { + + public static WMBusMessage decode(byte[] buffer, Integer signalStrengthInDBm, Map keyMap) + throws DecodingException { + return WMBusMessage.decode(buffer, signalStrengthInDBm, keyMap); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnection.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnection.java new file mode 100644 index 0000000..a41a376 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnection.java @@ -0,0 +1,172 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.wireless; + +import java.io.IOException; +import java.text.MessageFormat; + +import org.openmuc.jmbus.SecondaryAddress; +import org.openmuc.jmbus.transportlayer.SerialBuilder; +import org.openmuc.jmbus.transportlayer.TcpBuilder; +import org.openmuc.jmbus.transportlayer.TransportLayer; +import org.openmuc.jrxtx.DataBits; +import org.openmuc.jrxtx.Parity; +import org.openmuc.jrxtx.StopBits; + +/** + * A Wireless Mbus Connection. + * + * @see #addKey(SecondaryAddress, byte[]) + */ +public interface WMBusConnection extends AutoCloseable { + + /** + * Closes the service access point. + */ + @Override + void close() throws IOException; + + /** + * Stores a pair of secondary address and cryptographic key. The stored keys are automatically used to decrypt + * messages when a wireless M-Bus message is been decoded. + * + * @param address + * the secondary address. + * @param key + * the cryptographic key. + * + * @see #removeKey(SecondaryAddress) + */ + void addKey(SecondaryAddress address, byte[] key); + + /** + * Removes the stored key for the given secondary address. + * + * @param address + * the secondary address for which to remove the stored key. + * + * @see #addKey(SecondaryAddress, byte[]) + */ + void removeKey(SecondaryAddress address); + + class WMBusSerialBuilder extends SerialBuilder { + + private final Builder builder; + + public WMBusSerialBuilder(WMBusManufacturer wmBusManufacturer, WMBusListener listener, String serialPortName) { + super(serialPortName); + builder = new Builder(wmBusManufacturer, listener); + + switch (wmBusManufacturer) { + case RADIO_CRAFTS: + setBaudrate(19200); + break; + case AMBER: + setBaudrate(9600); + break; + case IMST: + setBaudrate(57600); + break; + default: + // should not occur + throw new RuntimeException( + MessageFormat.format("Error unknown manufacturer {0}.", wmBusManufacturer)); + } + setStopBits(StopBits.STOPBITS_1).setParity(Parity.NONE).setDataBits(DataBits.DATABITS_8); + } + + public WMBusSerialBuilder setMode(WMBusMode mode) { + builder.mode = mode; + return self(); + } + + public WMBusSerialBuilder setWmBusManufacturer(WMBusManufacturer wmBusManufacturer) { + builder.wmBusManufacturer = wmBusManufacturer; + return self(); + } + + public WMBusSerialBuilder setListener(WMBusListener connectionListener) { + builder.listener = connectionListener; + return self(); + } + + @Override + public WMBusConnection build() throws IOException { + return builder.build(buildTransportLayer()); + } + } + + class WMBusTcpBuilder extends TcpBuilder { + + private final Builder builder; + + public WMBusTcpBuilder(WMBusManufacturer wmBusManufacturer, WMBusListener listener, String hostAddress, + int port) { + super(hostAddress, port); + builder = new Builder(wmBusManufacturer, listener); + } + + public WMBusTcpBuilder setMode(WMBusMode mode) { + builder.mode = mode; + return self(); + } + + public WMBusTcpBuilder setWmBusManufacturer(WMBusManufacturer wmBusManufacturer) { + builder.wmBusManufacturer = wmBusManufacturer; + return self(); + } + + public WMBusTcpBuilder setListener(WMBusListener connectionListener) { + builder.listener = connectionListener; + return self(); + } + + @Override + public WMBusConnection build() throws IOException { + return builder.build(buildTransportLayer()); + } + } + + class Builder { + + private WMBusManufacturer wmBusManufacturer; + private WMBusMode mode; + private WMBusListener listener; + + Builder(WMBusManufacturer wmBusManufacturer, WMBusListener listener) { + this.listener = listener; + this.wmBusManufacturer = wmBusManufacturer; + this.mode = WMBusMode.T; + } + + WMBusConnection build(TransportLayer transportLayer) throws IOException { + AbstractWMBusConnection wmBusConnection; + switch (this.wmBusManufacturer) { + case AMBER: + wmBusConnection = new WMBusConnectionAmber(this.mode, this.listener, transportLayer); + break; + case IMST: + wmBusConnection = new WMBusConnectionImst(this.mode, this.listener, transportLayer); + break; + case RADIO_CRAFTS: + wmBusConnection = new WMBusConnectionRadioCrafts(this.mode, this.listener, transportLayer); + break; + default: + // should not occur. + throw new RuntimeException("Unknown Manufacturer."); + } + + wmBusConnection.open(); + return wmBusConnection; + } + } + + public enum WMBusManufacturer { + AMBER, + IMST, + RADIO_CRAFTS + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionAmber.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionAmber.java new file mode 100644 index 0000000..c41ec1f --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionAmber.java @@ -0,0 +1,222 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.wireless; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.nio.ByteBuffer; +import java.text.MessageFormat; +import java.util.Arrays; + +import org.openmuc.jmbus.DecodingException; +import org.openmuc.jmbus.transportlayer.TransportLayer; + +/** + * Was tested with the Amber 8426M Wireless M-Bus stick. + */ +class WMBusConnectionAmber extends AbstractWMBusConnection { + + private class MessageReceiverImpl extends MessageReceiver { + + private static final int MBUS_BL_CONTROL = 0x44; + + private int discardCount = 0; + private final TransportLayer transportLayer; + + public MessageReceiverImpl(TransportLayer transportLayer, WMBusListener listener) { + super(listener); + this.transportLayer = transportLayer; + } + + @Override + public void run() { + + try { + + while (!isClosed()) { + task(); + } + + } catch (final IOException e) { + if (isClosed()) { + return; + } + super.notifyStoppedListening(e); + + } finally { + close(); + super.shutdown(); + } + } + + private void task() throws IOException { + + ByteBuffer discardBuffer = ByteBuffer.allocate(100); + + int b0, b1; + DataInputStream is = getInputStream(); + while (true) { + try { + this.transportLayer.setTimeout(0); + b0 = is.read(); + this.transportLayer.setTimeout(MESSAGE_FRAGEMENT_TIMEOUT); + b1 = is.read(); + + if ((b1 ^ MBUS_BL_CONTROL) == 0) { + break; + } + + if (discardBuffer.capacity() - discardBuffer.position() < 2) { + discard(discardBuffer.array(), 0, discardBuffer.position()); + discardBuffer.clear(); + } + discardBuffer.put((byte) b0); + discardBuffer.put((byte) b1); + } catch (InterruptedIOException e) { + continue; + } + } + + int len = (b0 & 0xff) + 1; + byte[] data = new byte[2 + len]; + + data[0] = (byte) b0; + data[1] = (byte) b1; + + int readLength = len - 2; + int actualLength = is.read(data, 2, readLength); + + if (readLength != actualLength) { + discard(data, 0, actualLength); + return; + } + + notifyListener(data); + + if (discardBuffer.position() > 0) { + discard(discardBuffer.array(), 0, discardBuffer.position()); + } + } + + private void notifyListener(final byte[] data) { + int rssi = data[data.length - 1] & 0xff; + final Integer signalStrengthInDBm; + int rssiOffset = 74; + if (rssi >= 128) { + signalStrengthInDBm = ((rssi - 256) / 2) - rssiOffset; + } else { + signalStrengthInDBm = (rssi / 2) - rssiOffset; + } + + data[0] = (byte) (data[0] - 1); + + try { + super.notifyNewMessage(WMBusMessage.decode(data, signalStrengthInDBm, keyMap)); + } catch (DecodingException e) { + super.notifyDiscarded(data); + } + } + + private void discard(byte[] data, int offset, int length) { + discardCount++; + final byte[] discardedBytes = Arrays.copyOfRange(data, offset, offset + length); + + super.notifyDiscarded(discardedBytes); + + if (discardCount >= 5) { + try { + reset(); + } catch (IOException e) { + // ignoring reset errors here.. + } + discardCount = 0; + } + } + } + + public WMBusConnectionAmber(WMBusMode mode, WMBusListener listener, TransportLayer tl) { + super(mode, listener, tl); + } + + @Override + protected MessageReceiver newMessageReceiver(TransportLayer transportLayer, WMBusListener listener) { + return new MessageReceiverImpl(transportLayer, listener); + } + + /** + * @param mode + * - the wMBus mode to be used for transmission + * @throws IOException + */ + @Override + protected void initializeWirelessTransceiver(WMBusMode mode) throws IOException { + switch (mode) { + case S: + amberSetReg((byte) 0x46, (byte) 0x03); + break; + case T: + amberSetReg((byte) 0x46, (byte) 0x08); // T2-OTHER (correct for receiving station in T mode) + break; + case C: + amberSetReg((byte) 0x46, (byte) 0x0e); // C2-OTHER + break; + default: + String message = MessageFormat.format("wMBUS Mode ''{0}'' is not supported", mode.toString()); + throw new IOException(message); + } + amberSetReg((byte) 0x45, (byte) 0x01); // Enable attaching RSSI to message + } + + /** + * Writes a {@code CMD_SET_REQ} to the Amber module. + * + * @param cmd + * register address of the Amber module. + * @param data + * new value(s) for this register address(es). + * @throws IOException + * if an error occurred, while writing the command. + */ + private void writeCommand(byte cmd, byte[] data) throws IOException { + DataOutputStream os = getOutputStream(); + + byte[] header = ByteBuffer.allocate(3).put((byte) 0xFF).put(cmd).put((byte) data.length).array(); + + os.write(header); + os.write(data); + + byte checksum = computeCheckSum(data, computeCheckSum(header, (byte) 0)); + + os.write(checksum); + } + + private void amberSetReg(byte reg, byte value) throws IOException { + byte[] data = { reg, 0x01, value }; + + writeCommand((byte) 0x09, data); + + discardNoise(); + } + + /** + * Writes a reset command to the Amber module + * + * @throws IOException + * if the reset command failed. + */ + private void reset() throws IOException { + writeCommand((byte) 0x05, new byte[] {}); + } + + private static byte computeCheckSum(byte[] data, byte checksum) { + for (byte element : data) { + checksum = (byte) (checksum ^ element); + } + return checksum; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionImst.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionImst.java new file mode 100644 index 0000000..bb7a01e --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionImst.java @@ -0,0 +1,369 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.wireless; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.nio.ByteBuffer; +import java.text.MessageFormat; +import java.util.Arrays; + +import javax.xml.bind.DatatypeConverter; + +import org.openmuc.jmbus.DecodingException; +import org.openmuc.jmbus.transportlayer.TransportLayer; + +/** + * Was tested with the IMST iM871A-USB Wireless M-Bus stick.
+ */ +class WMBusConnectionImst extends AbstractWMBusConnection { + + private class MessageReceiverImpl extends MessageReceiver { + + private static final byte MBUS_BL_CONTROL = 0x44; + private final TransportLayer transportLayer; + + public MessageReceiverImpl(TransportLayer transportLayer, WMBusListener listener) { + super(listener); + this.transportLayer = transportLayer; + } + + @Override + public void run() { + try { + + while (!isClosed()) { + try { + task(); + } catch (InterruptedIOException e) { + // ignore a timeout.. + } catch (HciMessageException e) { + super.notifyDiscarded(e.getData()); + } + } + + } catch (IOException e) { + if (isClosed()) { + return; + } + + super.notifyStoppedListening(e); + } finally { + close(); + super.shutdown(); + } + } + + private void task() throws IOException { + HciMessage hciMessage = readHciMsg(); + + final byte[] wmbusMessage = hciMessage.getPayload(); + final int signalStrengthInDBm = hciMessage.getRSSI(); + try { + super.notifyNewMessage(WMBusMessage.decode(wmbusMessage, signalStrengthInDBm, keyMap)); + } catch (DecodingException e) { + super.notifyDiscarded(wmbusMessage); + } + } + + private HciMessage readHciMsg() throws IOException { + while (true) { + HciMessage hciMessage = HciMessage.decode(this.transportLayer); + + if (hciMessage.getPayload().length <= 1) { + continue; + } + + if (hciMessage.getPayload()[1] == MBUS_BL_CONTROL) { + return hciMessage; + } else { + discard(hciMessage); + } + } + } + + private void discard(HciMessage hciMessage) { + super.notifyDiscarded(hciMessage.payload); + } + } + + public WMBusConnectionImst(WMBusMode mode, WMBusListener listener, TransportLayer tl) { + super(mode, listener, tl); + } + + @Override + protected MessageReceiver newMessageReceiver(TransportLayer transportLayer, WMBusListener listener) { + return new MessageReceiverImpl(transportLayer, listener); + } + + @Override + protected void initializeWirelessTransceiver(WMBusMode mode) throws IOException { + + byte[] payload = ByteBuffer.allocate(6).put((byte) 0x00) // NVM Flag: change configuration only temporary + .put((byte) 0x03) // IIFlag 1: Bit 0 Device Mode and Bit 1 Radio Mode + .put((byte) 0x00) // Device Mode: Meter + .put(linkRadioModeFor(mode)) // Link/Radio Mode + .put((byte) 0x10) // IIFlag 2: Bit 4 : Auto RSSI Attachment + .put((byte) 0x01) // Rx-Timestamp attached for each received Radio message + .array(); + + writeCommand(Const.DEVMGMT_ID, Const.DEVMGMT_MSG_SET_CONFIG_REQ, payload); + } + + private static byte linkRadioModeFor(WMBusMode mode) throws IOException { + switch (mode) { + case S: + return 0x01; // Link/Radio Mode: S1-m + case T: + return 0x04; // Link/Radio Mode: T2 + case C: + return 0x08; // Link/Radio Mode: C2 with telegram format A (C2 + format B = 0x09) + default: + String msg = MessageFormat.format("wMBUS Mode ''{0}'' is not supported", mode.toString()); + throw new IOException(msg); + } + } + + private boolean writeCommand(byte endpointId, byte msgId, byte[] payload) { + byte controlField = 0; + int lengthPayloadAll = payload.length & 0xFF; + double numOfPackagesTemp = (double) lengthPayloadAll / Const.MAX_SINGLE_PAYLOAD_SIZE; + + int numOfPackages = (int) numOfPackagesTemp; + int comma = (int) (numOfPackagesTemp - numOfPackages) * 100; + if (numOfPackages == 0 || comma != 0) { + ++numOfPackages; + } + + if (numOfPackages > Const.MAX_PACKAGES) { + return false; + } + + for (int i = 0; i < numOfPackages; ++i) { + int payloadSendLength = Const.MAX_SINGLE_PAYLOAD_SIZE; + if (numOfPackages - i == 1) { + payloadSendLength = lengthPayloadAll - (numOfPackages - 1) * Const.MAX_SINGLE_PAYLOAD_SIZE; + } + byte[] payloadSend = new byte[payloadSendLength]; + System.arraycopy(payload, i * payloadSendLength, payloadSend, 0, payloadSendLength); + + byte controlField_EndpointField = (byte) ((controlField << 4) | endpointId & 0xff); + byte[] hciHeader = { Const.START_OF_FRAME, controlField_EndpointField, msgId, (byte) payloadSendLength }; + + byte[] hciMessage = new byte[Const.HCI_HEADER_LENGTH + payloadSendLength]; + System.arraycopy(hciHeader, 0, hciMessage, 0, hciHeader.length); + System.arraycopy(payloadSend, 0, hciMessage, hciHeader.length, payloadSend.length); + + try { + getOutputStream().write(hciMessage); + } catch (IOException e) { + return false; + } + } + return true; + } + + /** + * Writes a reset command to the IMST module + */ + public void reset() { + writeCommand(Const.DEVMGMT_MSG_FACTORY_RESET_REQ, Const.DEVMGMT_ID, new byte[0]); + } + + /** + * IMST constants packages + */ + class Const { + public static final byte START_OF_FRAME = (byte) 0xA5; + // A5 01 03 + public static final int MAX_PACKAGES = 255; + public static final int MAX_SINGLE_PAYLOAD_SIZE = 255; + public static final int HCI_HEADER_LENGTH = 4; + + // ControlField + public static final byte RESERVED = 0x00; // 0b0000 + public static final byte TIMESTAMP_ATTACHED = 0x02; // 0b0010 + public static final byte RSSI_ATTACHED = 0x04; // 0b0100 + public static final byte CRC16_ATTACHED = 0x08; // 0b1000 (FCS) + + // List of Endpoint Identifier + public static final byte DEVMGMT_ID = 0x01; + public static final byte RADIOLINK_ID = 0x02; + public static final byte RADIOLINKTEST_ID = 0x03; + public static final byte HWTEST_ID = 0x04; + + // Device Management MessageIdentifier + public static final byte DEVMGMT_MSG_PING_REQ = 0x01; + public static final byte DEVMGMT_MSG_PING_RSP = 0x02; + public static final byte DEVMGMT_MSG_SET_CONFIG_REQ = 0x03; + public static final byte DEVMGMT_MSG_SET_CONFIG_RSP = 0x04; + public static final byte DEVMGMT_MSG_GET_CONFIG_REQ = 0x05; + public static final byte DEVMGMT_MSG_GET_CONFIG_RSP = 0x06; + public static final byte DEVMGMT_MSG_RESET_REQ = 0x07; + public static final byte DEVMGMT_MSG_RESET_RSP = 0x08; + public static final byte DEVMGMT_MSG_FACTORY_RESET_REQ = 0x09; + public static final byte DEVMGMT_MSG_FACTORY_RESET_RSP = 0x0A; + public static final byte DEVMGMT_MSG_GET_OPMODE_REQ = 0x0B; + public static final byte DEVMGMT_MSG_GET_OPMODE_RSP = 0x0C; + public static final byte DEVMGMT_MSG_SET_OPMODE_REQ = 0x0D; + public static final byte DEVMGMT_MSG_SET_OPMODE_RSP = 0x0E; + public static final byte DEVMGMT_MSG_GET_DEVICEINFO_REQ = 0x0F; + public static final byte DEVMGMT_MSG_GET_DEVICEINFO_RSP = 0x10; + public static final byte DEVMGMT_MSG_GET_SYSSTATUS_REQ = 0x11; + public static final byte DEVMGMT_MSG_GET_SYSSTATUS_RSP = 0x12; + public static final byte DEVMGMT_MSG_GET_FWINFO_REQ = 0x13; + public static final byte DEVMGMT_MSG_GET_FWINFO_RSP = 0x14; + public static final byte DEVMGMT_MSG_GET_RTC_REQ = 0x19; + public static final byte DEVMGMT_MSG_GET_RTC_RSP = 0x1A; + public static final byte DEVMGMT_MSG_SET_RTC_REQ = 0x1B; + public static final byte DEVMGMT_MSG_SET_RTC_RSP = 0x1C; + public static final byte DEVMGMT_MSG_ENTER_LPM_REQ = 0x1D; + public static final byte DEVMGMT_MSG_ENTER_LPM_RSP = 0x1E; + public static final byte DEVMGMT_MSG_SET_AES_ENCKEY_REQ = 0x21; + public static final byte DEVMGMT_MSG_SET_AES_ENCKEY_RSP = 0x22; + public static final byte DEVMGMT_MSG_ENABLE_AES_ENCKEY_REQ = 0x23; + public static final byte DEVMGMT_MSG_ENABLE_AES_ENCKEY_RSP = 0x24; + public static final byte DEVMGMT_MSG_SET_AES_DECKEY_RSP_0X25 = 0x25; + public static final byte DEVMGMT_MSG_SET_AES_DECKEY_RSP_0X26 = 0x26; + public static final byte DEVMGMT_MSG_AES_DEC_ERROR_IND = 0x27; + + // Radio Link Message Identifier + public static final byte RADIOLINK_MSG_WMBUSMSG_REQ = 0x01; + public static final byte RADIOLINK_MSG_WMBUSMSG_RSP = 0x02; + public static final byte RADIOLINK_MSG_WMBUSMSG_IND = 0x03; + public static final byte RADIOLINK_MSG_DATA_REQ = 0x04; + public static final byte RADIOLINK_MSG_DATA_RSP = 0x05; + } + + /** + *
  • HCI Message + *
      + *
    • StartOfFrame 8 Bit: 0xA5 + *
    • MsgHeader 24 Bit: + *
        + *
      • ControlField 4 Bit: + *
          + *
        • 0000b Reserved + *
        • 0010b Time Stamp Field attached + *
        • 0100b RSSI Field attached + *
        • 1000b CRC16 Field attached + *
        + *
      • EndPoint ID 4 Bit: Identifies a logical message endpoint which groups several messages. + *
      • Msg ID Field 8 Bit: Identifies the message type. + *
      • LengthFiled 8 Bit: Number of bytes in the payload. If null no payload. + *
      + *
    • PayloadField n * 8 Bit: wMBus Message + *
    • Time Stamp (optional): 32 Bit Timestamp of the RTC + *
    • RSSI (optional) 8 Bit: Receive Signal Strength Indicator + *
    • FCS (optional) 16 Bit: CRC from Control Field up to last byte of Payload, Time Stamp or RSSI Field.
    • + *
    + * + */ + private static class HciMessage { + + private final byte controlField;; + private final byte endpointID; + private final byte msgId; + private final int length; + + private final byte[] payload; + private final int timeStamp; + private final int rSSI; + private final int fCS; + + private HciMessage(byte controlField, byte endpointID, byte msgId, int length, byte[] payload, int timeStamp, + int rSSI, int fCS) { + this.controlField = controlField; + this.endpointID = endpointID; + this.msgId = msgId; + this.length = length; + this.payload = payload; + this.timeStamp = timeStamp; + this.rSSI = rSSI; + this.fCS = fCS; + } + + public static HciMessage decode(TransportLayer transportLayer) throws IOException { + DataInputStream is = transportLayer.getInputStream(); + byte b0, b1; + + transportLayer.setTimeout(0); + b0 = is.readByte(); + if (b0 != Const.START_OF_FRAME) { + String msg = String.format("First byte does not start with %02X.", Const.START_OF_FRAME); + throw new IOException(msg); + } + + transportLayer.setTimeout(MESSAGE_FRAGEMENT_TIMEOUT); + // this may time out. + b1 = is.readByte(); + + byte controlField = (byte) ((b1 >> 4) & 0x0F); + byte endpointId = (byte) (b1 & 0x0F); + + byte msgId = is.readByte(); + int length = is.readUnsignedByte(); + + byte[] payload = readPayload(is, length); + + int timeStamp = 0; + if ((controlField & Const.TIMESTAMP_ATTACHED) == Const.TIMESTAMP_ATTACHED) { + timeStamp = is.readInt(); + } + + int rSSI = 0; + if ((controlField & Const.RSSI_ATTACHED) == Const.RSSI_ATTACHED) { + double b = -100.0 - (4000.0 / 150.0); + double m = 80.0 / 150.0; + rSSI = (int) (m * is.readUnsignedByte() + b); + } + + int fCS = 0; + if ((controlField & Const.CRC16_ATTACHED) == Const.CRC16_ATTACHED) { + fCS = is.readUnsignedShort(); + } + + return new HciMessage(controlField, endpointId, msgId, length, payload, timeStamp, rSSI, fCS); + } + + private static byte[] readPayload(DataInputStream is, final int length) throws IOException { + byte[] payload = new byte[length + 1]; + payload[0] = (byte) length; + + int readLength = is.read(payload, 1, length); + + if (readLength != length) { + byte[] data = Arrays.copyOfRange(payload, 1, 1 + readLength); + throw new HciMessageException(data); + } + + return payload; + } + + @Override + public String toString() { + return new StringBuilder().append("Control Field: ").append(byteAsHexString(controlField)) + .append("\nEndpointID: ").append(byteAsHexString(endpointID)).append("\nMsg ID: ") + .append(byteAsHexString(msgId)).append("\nLength: ").append(length) + .append("\nTimestamp: ").append(timeStamp).append("\nRSSI: ").append(rSSI) + .append("\nFCS: ").append(fCS).append("\nPayload:\n") + .append(DatatypeConverter.printHexBinary(payload)).toString(); + } + + private static String byteAsHexString(byte b) { + return String.format("%02X", b); + } + + public byte[] getPayload() { + return payload; + } + + public int getRSSI() { + return rSSI; + } + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionRadioCrafts.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionRadioCrafts.java new file mode 100644 index 0000000..6237bbf --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionRadioCrafts.java @@ -0,0 +1,229 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.wireless; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.text.MessageFormat; +import java.util.Arrays; + +import org.openmuc.jmbus.DecodingException; +import org.openmuc.jmbus.transportlayer.TransportLayer; + +/* + * Radio Craft W-MBUS frame: + * + * @formatter:off + * +---+----+-----------+ + * | L | CI | APPL_DATA | + * +---+----+-----------+ + * ~ L is the length (not including the length byte itself) + * ~ CI is the Control Information byte + * @formatter:on + */ +class WMBusConnectionRadioCrafts extends AbstractWMBusConnection { + + private class MessageReceiverImpl extends MessageReceiver { + + /** + * Indicates message from primary station, function send/no reply (SND -N + */ + private static final byte CONTROL_BYTE = 0x44; + private final TransportLayer transportLayer; + + private final byte[] discardBuffer = new byte[BUFFER_LENGTH]; + private int bufferPointer = 0; + + public MessageReceiverImpl(TransportLayer transportLayer, WMBusListener listener) { + super(listener); + this.transportLayer = transportLayer; + } + + @Override + public void run() { + try { + + while (!isClosed()) { + + byte[] messageData = initMessageData(); + + int len = messageData.length - 2; + handleData(messageData, len); + + } + } catch (final IOException e) { + if (!isClosed()) { + super.notifyStoppedListening(e); + } + + } finally { + close(); + super.shutdown(); + } + } + + private void handleData(byte[] messageData, int len) throws IOException { + try { + int numReadBytes = getInputStream().read(messageData, 2, len); + + if (len == numReadBytes) { + notifyListener(messageData); + } else { + discard(messageData, 0, numReadBytes + 2); + } + } catch (InterruptedIOException e) { + discard(messageData, 0, 2); + } + } + + private byte[] initMessageData() throws IOException { + byte b0, b1; + while (true) { + this.transportLayer.setTimeout(0); + b0 = getInputStream().readByte(); + this.transportLayer.setTimeout(MESSAGE_FRAGEMENT_TIMEOUT); + + try { + // this may time out. + b1 = getInputStream().readByte(); + } catch (InterruptedIOException e) { + continue; + } + + if (b1 == CONTROL_BYTE) { + break; + } + + discardBuffer[bufferPointer++] = b0; + discardBuffer[bufferPointer++] = b1; + + if (bufferPointer - 2 >= discardBuffer.length) { + discard(discardBuffer, 0, bufferPointer); + bufferPointer = 0; + } + + } + + int messageLength = b0 & 0xff; + + final byte[] messageData = new byte[messageLength + 1]; + messageData[0] = b0; + messageData[1] = b1; + + return messageData; + } + + private void notifyListener(final byte[] messageBytes) { + messageBytes[0] = (byte) (messageBytes[0] - 1); + int rssi = messageBytes[messageBytes.length - 1] & 0xff; + + final int signalStrengthInDBm = (rssi * -1) / 2; + try { + super.notifyNewMessage(WMBusMessage.decode(messageBytes, signalStrengthInDBm, keyMap)); + } catch (DecodingException e) { + super.notifyDiscarded(messageBytes); + } + } + + private void discard(byte[] buffer, int offset, int length) { + final byte[] discardedBytes = Arrays.copyOfRange(buffer, offset, offset + length); + + super.notifyDiscarded(discardedBytes); + } + } + + public WMBusConnectionRadioCrafts(WMBusMode mode, WMBusListener listener, TransportLayer tl) { + super(mode, listener, tl); + } + + @Override + protected MessageReceiver newMessageReceiver(TransportLayer transportLayer, WMBusListener listener) { + return new MessageReceiverImpl(transportLayer, listener); + } + + /** + * @param mode + * - the wMBus mode to be used for transmission + * @throws IOException + */ + @Override + protected void initializeWirelessTransceiver(WMBusMode mode) throws IOException { + // enter config mode + sendByteInConfigMode(0x00); + + DataOutputStream os = getOutputStream(); + int modeFlag = getModeFlag(mode); + + init(os, modeFlag); + + // /* Set Auto Answer Register */ + // sendByteInConfigMode(0x41); + // sendByteInConfigMode(0xff); + + // leave config mode + os.write(0x58); + os.flush(); + } + + private int getModeFlag(WMBusMode mode) throws IOException { + int modeFlag; + + switch (mode) { + case C: + modeFlag = 0x04; + break; + case S: + modeFlag = 0x00; + break; + case T: + modeFlag = 0x02; + break; + default: + String msg = MessageFormat.format("wMBUS Mode ''{0}'' is not supported", mode.toString()); + throw new IOException(msg); + } + return modeFlag; + } + + private void init(DataOutputStream os, int modeFlag) throws IOException { + requestSetMode(os, modeFlag); + + requestSetMasterMode(os); + + requestRssiInformation(os); + } + + private void requestSetMode(DataOutputStream os, int modeFlag) throws IOException { + sendRequest(os, 0x03, modeFlag); + } + + private void requestSetMasterMode(DataOutputStream os) throws IOException { + sendRequest(os, 0x12, 0x01); + } + + private void requestRssiInformation(DataOutputStream os) throws IOException { + sendRequest(os, 0x05, 0x01); + } + + private void sendRequest(DataOutputStream os, int b0, int b1) throws IOException { + sendByteInConfigMode(0x4d); + os.write(b0); + os.write(b1); + sendByteInConfigMode(0xff); + } + + private void sendByteInConfigMode(int b) throws IOException { + + discardNoise(); + + DataOutputStream os = getOutputStream(); + os.write(b); + os.flush(); + + waitForAck(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusListener.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusListener.java new file mode 100644 index 0000000..1a8d1b1 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusListener.java @@ -0,0 +1,39 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.wireless; + +import java.io.IOException; +import java.util.EventListener; + +/** + * The wireless M-Bus event/new message listener interface. + */ +public interface WMBusListener extends EventListener { + + /** + * Received a new wireless M-Bus message. + * + * @param message + * the message. + */ + void newMessage(WMBusMessage message); + + /** + * Callback, when noisy data has been discarded. + * + * @param bytes + * the data which has been discarded. + */ + void discardedBytes(byte[] bytes); + + /** + * Callback, if the connection has been interrupted. + * + * @param cause + * the cause of the interruption. + */ + void stoppedListening(IOException cause); +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusMessage.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusMessage.java new file mode 100644 index 0000000..41e7e0e --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusMessage.java @@ -0,0 +1,129 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.wireless; + +import java.text.MessageFormat; +import java.util.Map; + +import org.openmuc.jmbus.DecodingException; +import org.openmuc.jmbus.SecondaryAddress; +import org.openmuc.jmbus.VariableDataStructure; + +/** + * Represents a wireless M-Bus link layer message without the CRC checksum. + * + * {@link WMBusMessage} is structured as follows: + *
      + *
    • Length (1 byte) - the length (number of bytes) of the complete message without the length byte and the CRC bytes. + *
    • + *
    • Control field (1 byte) - defines the frame type. 0x44 signifies an SND-NR (send no request) message that is sent + * by meters in S1 mode.
    • + *
    • Secondary address (8 bytes) - the secondary address consists of: + *
        + *
      • Manufacturer ID (2 bytes) -
      • + *
      • Address (6 bytes) - consists of + *
          + *
        • Device ID (4 bytes) -
        • + *
        • Version (1 byte) -
        • + *
        • Device type (1 byte) -
        • + *
        + *
      • + *
      + *
    • + *
    + */ +public class WMBusMessage { + + private final Integer signalStrengthInDBm; + + private final byte[] buffer; + private final int controlField; + private final SecondaryAddress secondaryAddress; + private final VariableDataStructure vdr; + + private WMBusMessage(Integer signalStrengthInDBm, byte[] buffer, int controlField, + SecondaryAddress secondaryAddress, VariableDataStructure vdr) { + this.signalStrengthInDBm = signalStrengthInDBm; + this.buffer = buffer; + this.controlField = controlField; + this.secondaryAddress = secondaryAddress; + this.vdr = vdr; + } + + /* + * Only decodes the wireless M-Bus message itself. + */ + static WMBusMessage decode(byte[] buffer, Integer signalStrengthInDBm, Map keyMap) + throws DecodingException { + int length = buffer[0] & 0xff; + + if (length > (buffer.length - 1)) { + String msg = MessageFormat.format( + "Byte buffer has only a length of {0} while the specified length field is {1}.", buffer.length, + length); + throw new DecodingException(msg); + } + + int controlField = buffer[1] & 0xff; + SecondaryAddress secondaryAddress = SecondaryAddress.newFromWMBusLlHeader(buffer, 2); + VariableDataStructure vdr = new VariableDataStructure(buffer, 10, length - 9, secondaryAddress, keyMap); + + return new WMBusMessage(signalStrengthInDBm, buffer, controlField, secondaryAddress, vdr); + } + + /** + * Get the message as binary large object (byte array). + * + * @return the byte array representation of the message. + */ + public byte[] asBlob() { + return buffer; + } + + public int getControlField() { + return controlField; + } + + /** + * Get the secondary address. + * + * @return the secondary address. + */ + public SecondaryAddress getSecondaryAddress() { + return secondaryAddress; + } + + /** + * Get the variable data structure of the message. + * + * @return the variable data structure. + */ + public VariableDataStructure getVariableDataResponse() { + return vdr; + } + + /** + * Returns the received signal string indication (RSSI) in dBm. + * + * @return the RSSI. + */ + public Integer getRssi() { + return signalStrengthInDBm; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + + if (signalStrengthInDBm != null) { + builder.append("Message was received with signal strength: ").append(signalStrengthInDBm).append("dBm\n"); + } + + return builder.append("control field: ").append(String.format("0x%02X", controlField)) + .append("\nSecondary Address -> ").append(secondaryAddress).append("\nVariable Data Response:\n") + .append(vdr).toString(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusMode.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusMode.java new file mode 100644 index 0000000..fdc1597 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusMode.java @@ -0,0 +1,24 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.wireless; + +/** + * The wireless M-Bus modes. + */ +public enum WMBusMode { + /** + * Compact (868.95 MHz). Combination of range (S) and battery efficiency (T). Suitable for frequent sending. + */ + C, + /** + * Frequent (868.95 MHz). Meter sends data several times/day. + */ + T, + /** + * Stationary (868.3 MHz). Meter sends data few times/day. + */ + S; +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/package-info.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/package-info.java new file mode 100644 index 0000000..b5bedb3 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/package-info.java @@ -0,0 +1,12 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/** + * This package contain relevant classes to receive wireless M-Bus messages. + * + * @see org.openmuc.jmbus.wireless.WMBusConnection.WMBusSerialBuilder + * @see org.openmuc.jmbus.wireless.WMBusConnection + */ +package org.openmuc.jmbus.wireless; diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/DataBits.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/DataBits.java new file mode 100644 index 0000000..53550d3 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/DataBits.java @@ -0,0 +1,36 @@ +package org.openmuc.jrxtx; + +import org.openhab.core.io.transport.serial.SerialPort; + +/** + * The data bits. + */ +@SuppressWarnings("deprecation") +public enum DataBits { + /** + * 5 data bits will be used for each character. + */ + DATABITS_5(SerialPort.DATABITS_5), + /** + * 6 data bits will be used for each character. + */ + DATABITS_6(SerialPort.DATABITS_6), + /** + * 8 data bits will be used for each character. + */ + DATABITS_7(SerialPort.DATABITS_7), + /** + * 8 data bits will be used for each character. + */ + DATABITS_8(SerialPort.DATABITS_8),; + + private int odlValue; + + private DataBits(int oldValue) { + this.odlValue = oldValue; + } + + int getOldValue() { + return this.odlValue; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/FlowControl.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/FlowControl.java new file mode 100644 index 0000000..6f303e9 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/FlowControl.java @@ -0,0 +1,29 @@ +package org.openmuc.jrxtx; + +/** + * The flow control. + * + * @see SerialPort#setFlowControl(FlowControl) + * @see SerialPortBuilder#setFlowControl(FlowControl) + */ +public enum FlowControl { + /** + * No flow control. + */ + NONE, + + /** + * Hardware flow control on input and output (RTS/CTS). + * + *

    + * Sets RFR (ready for receiving) formally known as RTS and the CTS (clear to send) flag. + *

    + */ + RTS_CTS, + + /** + * Software flow control on input and output. + */ + XON_XOFF + +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/JRxTxPort.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/JRxTxPort.java new file mode 100644 index 0000000..4c3318f --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/JRxTxPort.java @@ -0,0 +1,340 @@ +package org.openmuc.jrxtx; + +import static java.text.MessageFormat.format; +import static org.openhab.core.io.transport.serial.SerialPort.*; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.openhab.core.io.transport.serial.PortInUseException; +import org.openhab.core.io.transport.serial.SerialPortIdentifier; +import org.openhab.core.io.transport.serial.SerialPortManager; +import org.openhab.core.io.transport.serial.UnsupportedCommOperationException; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.util.tracker.ServiceTracker; + +/** + * This class is a workaround: + * jrxtx library includes gnu.io.* classes and j62056.jar is depending on that. As we are + * using nrjavaserial as an implementation of gnu.io, we can't go that way! + * -> As a workaround I shaded the {@link JRxTxPort} class which is used by j62056.jar and modified it to + * work with any implementation of {@link SerialPort} (formerly it was just working with the implementation + * gnu.io.RXTXPort). + * + * @author MatthiasS + * + */ +class JRxTxPort implements org.openmuc.jrxtx.SerialPort { + + private volatile boolean closed; + + private org.openhab.core.io.transport.serial.SerialPort rxtxPort; + + private SerialInputStream serialIs; + private SerialOutputStream serial0s; + + private String portName; + + private DataBits dataBits; + + private Parity parity; + + private StopBits stopBits; + + private int baudRate; + + private int serialPortTimeout; + + private FlowControl flowControl; + + public static JRxTxPort openSerialPort(String portName, int baudRate, Parity parity, DataBits dataBits, + StopBits stopBits, FlowControl flowControl) throws IOException { + try { + BundleContext bundleContext = FrameworkUtil.getBundle(JRxTxPort.class).getBundleContext(); + ServiceTracker serviceTracker = new ServiceTracker<>(bundleContext, + SerialPortManager.class, null); + serviceTracker.open(); + SerialPortManager serialPortManager = serviceTracker.getService(); + + SerialPortIdentifier serialPortIdentifier = serialPortManager.getIdentifier(portName); + if (serialPortIdentifier == null) { + String errMessage = format("Serial port {0} not found or port is busy.", portName); + throw new PortNotFoundException(errMessage); + } + org.openhab.core.io.transport.serial.SerialPort comPort = serialPortIdentifier.open("meterreader", 0); + // if (!(comPort instanceof RXTXPort)) { + // throw new SerialPortException("Unable to open the serial port. Port is not RXTX."); + // } + + try { + comPort.setSerialPortParams(baudRate, dataBits.getOldValue(), stopBits.getOldValue(), + parity.getOldValue()); + + setFlowControl(flowControl, comPort); + } catch (UnsupportedCommOperationException e) { + String message = format("Unable to apply config on serial port.\n{0}", e.getMessage()); + throw new SerialPortException(message); + } + + return new JRxTxPort(comPort, portName, baudRate, parity, dataBits, stopBits, flowControl); + // } catch (NoSuchPortException e) { + // String errMessage = format("Serial port {0} not found or port is busy.", portName); + // throw new PortNotFoundException(errMessage); + } catch (PortInUseException e) { + String errMessage = format("Serial port {0} is already in use.", portName); + throw new PortNotFoundException(errMessage); + // } catch (UnsupportedCommOperationException e1) { + // throw new IOException(e1); + } + } + + private static void setFlowControl(FlowControl flowControl, + org.openhab.core.io.transport.serial.SerialPort rxtxPort) throws IOException { + try { + switch (flowControl) { + case RTS_CTS: + rxtxPort.setFlowControlMode(FLOWCONTROL_RTSCTS_IN | FLOWCONTROL_RTSCTS_OUT); + break; + case XON_XOFF: + rxtxPort.setFlowControlMode(FLOWCONTROL_XONXOFF_IN | FLOWCONTROL_XONXOFF_OUT); + + break; + + case NONE: + default: + rxtxPort.setFlowControlMode(FLOWCONTROL_NONE); + break; + } + } catch (UnsupportedCommOperationException e) { + throw new IOException("Failed to set FlowControl mode", e); + } + } + + private JRxTxPort(org.openhab.core.io.transport.serial.SerialPort comPort, String portName, int baudRate, + Parity parity, DataBits dataBits, StopBits stopBits, FlowControl flowControl) throws IOException { + this.rxtxPort = comPort; + this.portName = portName; + this.baudRate = baudRate; + this.parity = parity; + this.dataBits = dataBits; + this.stopBits = stopBits; + this.flowControl = flowControl; + + this.closed = false; + + this.serial0s = new SerialOutputStream(this.rxtxPort.getOutputStream()); + this.serialIs = new SerialInputStream(); + } + + @Override + public InputStream getInputStream() throws IOException { + if (isClosed()) { + throw new SerialPortException("Serial port is closed"); + } + return this.serialIs; + } + + @Override + public OutputStream getOutputStream() throws IOException { + if (isClosed()) { + throw new SerialPortException("Serial port is closed"); + } + + return this.serial0s; + } + + @Override + public synchronized void close() throws IOException { + if (isClosed()) { + return; + } + + try { + this.serial0s.closeStream(); + this.serialIs.closeStream(); + this.rxtxPort.close(); + this.serial0s = null; + this.serialIs = null; + this.rxtxPort = null; + } finally { + this.closed = true; + } + } + + @Override + public boolean isClosed() { + return this.closed; + } + + private class SerialInputStream extends InputStream { + private static final long SLEEP_TIME = 10L; // sleep appropriate time + + @Override + public synchronized int read() throws IOException { + long elapsedTime = 0; + + InputStream serialInputStream = rxtxPort.getInputStream(); + do { + if (serialInputStream.available() > 0) { + return serialInputStream.read(); + } + try { + Thread.sleep(SLEEP_TIME); + elapsedTime += SLEEP_TIME; + } catch (InterruptedException e) { + // ignore + } + + if (isClosed()) { + throw new SerialPortException("Serial port has been closed."); + } + } while (getSerialPortTimeout() == 0 || elapsedTime <= getSerialPortTimeout()); + + throw new SerialPortTimeoutException(); + } + + @Override + public int available() throws IOException { + return rxtxPort.getInputStream().available(); + } + + private void closeStream() throws IOException { + rxtxPort.getInputStream().close(); + } + + @Override + public void close() throws IOException { + JRxTxPort.this.close(); + } + } + + private class SerialOutputStream extends OutputStream { + + private OutputStream serialOutputStream; + + public SerialOutputStream(OutputStream serialOutputStream) { + this.serialOutputStream = serialOutputStream; + } + + @Override + public void write(int b) throws IOException { + checkIfOpen(); + + this.serialOutputStream.write(b); + } + + private void checkIfOpen() throws SerialPortException { + if (isClosed()) { + throw new SerialPortException("Port has been closed."); + } + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + checkIfOpen(); + this.serialOutputStream.write(b, off, len); + } + + @Override + public void write(byte[] b) throws IOException { + checkIfOpen(); + this.serialOutputStream.write(b); + } + + @Override + public void flush() throws IOException { + checkIfOpen(); + this.serialOutputStream.flush(); + } + + private void closeStream() throws IOException { + this.serialOutputStream.close(); + } + + @Override + public void close() throws IOException { + JRxTxPort.this.close(); + } + } + + @Override + public String getPortName() { + return this.portName; + } + + @Override + public DataBits getDataBits() { + return this.dataBits; + } + + @Override + public void setDataBits(DataBits dataBits) throws IOException { + this.dataBits = dataBits; + updateWrappedPort(); + } + + @Override + public Parity getParity() { + return this.parity; + } + + @Override + public void setParity(Parity parity) throws IOException { + this.parity = parity; + updateWrappedPort(); + } + + @Override + public StopBits getStopBits() { + return this.stopBits; + } + + @Override + public void setStopBits(StopBits stopBits) throws IOException { + this.stopBits = stopBits; + updateWrappedPort(); + } + + @Override + public int getBaudRate() { + return this.baudRate; + } + + @Override + public void setBaudRate(int baudRate) throws IOException { + this.baudRate = baudRate; + updateWrappedPort(); + } + + private void updateWrappedPort() throws IOException { + try { + this.rxtxPort.setSerialPortParams(this.baudRate, this.dataBits.getOldValue(), this.stopBits.getOldValue(), + this.parity.getOldValue()); + } catch (UnsupportedCommOperationException e) { + throw new IOException(e.getMessage()); + } + } + + @Override + public int getSerialPortTimeout() { + return this.serialPortTimeout; + } + + @Override + public void setSerialPortTimeout(int serialPortTimeout) throws IOException { + this.serialPortTimeout = serialPortTimeout; + } + + @Override + public void setFlowControl(FlowControl flowControl) throws IOException { + setFlowControl(flowControl, this.rxtxPort); + this.flowControl = flowControl; + } + + @Override + public FlowControl getFlowControl() { + return this.flowControl; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/Parity.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/Parity.java new file mode 100644 index 0000000..3c1dce8 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/Parity.java @@ -0,0 +1,54 @@ +package org.openmuc.jrxtx; + +import org.openhab.core.io.transport.serial.SerialPort; + +/** + * The parity. + */ +@SuppressWarnings("deprecation") +public enum Parity { + /** + * No parity bit will be sent with each data character at all. + */ + NONE(SerialPort.PARITY_NONE), + /** + * An odd parity bit will be sent with each data character. I.e. will be set to 1 if the data character contains an + * even number of bits set to 1. + */ + ODD(SerialPort.PARITY_ODD), + /** + * An even parity bit will be sent with each data character. I.e. will be set to 1 if the data character contains an + * odd number of bits set to 1. + */ + EVEN(SerialPort.PARITY_EVEN), + /** + * A mark parity bit (i.e. always 1) will be sent with each data character. + */ + MARK(SerialPort.PARITY_MARK), + /** + * A space parity bit (i.e. always 0) will be sent with each data character + */ + SPACE(4),; + + private static final Parity[] VALUES = values(); + private int odlValue; + + private Parity(int oldValue) { + this.odlValue = oldValue; + } + + int getOldValue() { + return this.odlValue; + } + + static Parity forValue(int parity) { + for (Parity p : VALUES) { + if (p.odlValue == parity) { + return p; + } + } + + // should not occur + throw new RuntimeException("Error."); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/PortNotFoundException.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/PortNotFoundException.java new file mode 100644 index 0000000..28176da --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/PortNotFoundException.java @@ -0,0 +1,20 @@ +package org.openmuc.jrxtx; + +/** + * Signals that the provided serial port name provided via {@link SerialPortBuilder#newBuilder(String)}, + * {@link SerialPortBuilder#setPortName(String)} doesn't exist on the host system. + */ +public class PortNotFoundException extends SerialPortException { + + private static final long serialVersionUID = 2766015292714524756L; + + /** + * Constructs a new PortNotFoundException with the specified detail message. + * + * @param message + * the detail message. + */ + public PortNotFoundException(String message) { + super(message); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPort.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPort.java new file mode 100644 index 0000000..49f33f2 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPort.java @@ -0,0 +1,172 @@ +package org.openmuc.jrxtx; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Serial port for communication using UARTs. Can be used for communication protocols such as RS-232 and RS-485. + *

    + * A SerialPort is created using {@link SerialPortBuilder}. Once closed it cannot be opened again but has to be + * recreated. + */ +public interface SerialPort extends Closeable { + + /** + * Returns the input stream for this serial port. + *

    + * Closing the returned InputStream will close the associated serial port. + * + * @return the InputStream object that can be used to read from the port. + * @throws IOException + * if an I/O error occurred + */ + InputStream getInputStream() throws IOException; + + /** + * Returns the output stream for this serial port. + * + * @return the OutputStream object that can be used to write to the port. + * @throws IOException + * if an I/O error occurred. + */ + OutputStream getOutputStream() throws IOException; + + /** + * Closes the serial port. + *

    + * Also closes the associated input and output streams. + * + * @throws IOException + * if an I/O error occurred. + */ + void close() throws IOException; + + /** + * Returns whether the serial port is currently open and available for communication. + * + * @return true if the serial port is closed. + */ + boolean isClosed(); + + /** + * Get the name of the serial port. + * + * @return the serial port name. + */ + String getPortName(); + + /** + * Get the current data bits config. + * + * @return the dataBits the data bits. + */ + DataBits getDataBits(); + + /** + * Set the data bits. + * + * @param dataBits + * the new dataBits. + * @throws IOException + * if an I/O exception occurred when setting the new data bits.. + */ + void setDataBits(DataBits dataBits) throws IOException; + + /** + * Get the parity. + * + * @return the new parity. + */ + Parity getParity(); + + /** + * Set the new parity. + * + * @param parity + * the new parity. + * @throws IOException + * if an I/O exception occurred when setting the new parity. + */ + void setParity(Parity parity) throws IOException; + + /** + * Get the current stop bits settings. + * + * @return the stopBits the stop bits. + */ + StopBits getStopBits(); + + /** + * Set the stop bits. + * + * @param stopBits + * the stopBits to set + * @throws IOException + * if an I/O exception occurred when setting the new stop bits. + */ + void setStopBits(StopBits stopBits) throws IOException; + + /** + * @return the baudRate setting. + * + * @see #setBaudRate(int) + */ + int getBaudRate(); + + /** + * Sets the baud rate of the system. + * + * @param baudRate + * the new baud rate. + * @throws IOException + * if an I/O exception occurred when setting the new baud rate. + * + * @see #getBaudRate() + */ + void setBaudRate(int baudRate) throws IOException; + + /** + * Returns setting for serial port timeout. 0 returns implies that the option is disabled (i.e., + * timeout of infinity). + * + * @return the serialPortTimeout. + * + * @see #setSerialPortTimeout(int) + */ + int getSerialPortTimeout(); + + /** + * Enable/disable serial port timeout with the specified timeout, in milliseconds. With this option set to a + * non-zero timeout, a read() call on the InputStream associated with this serial port will block for only this + * amount of time. If the timeout expires, a org.openmuc.jrxtx.SerialPortTimeoutExcepption is raised, though the + * serial port is still valid. The option must be enabled prior to entering the blocking operation to have effect. + * The timeout must be > 0. A timeout of zero is interpreted as an infinite timeout. + * + * @param serialPortTimeout + * the specified timeout, in milliseconds. + * @throws IOException + * if there is an error in the underlying protocol. + * + * @see #getSerialPortTimeout() + */ + void setSerialPortTimeout(int serialPortTimeout) throws IOException; + + /** + * Set the flow control type. + * + * @param flowControl + * the flow control. + * @throws IOException + * if an I/O exception occurred when setting the new baud rate. + */ + void setFlowControl(FlowControl flowControl) throws IOException; + + /** + * Get the current flow control settings. + * + * @return the flow control. + */ + FlowControl getFlowControl(); +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPortBuilder.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPortBuilder.java new file mode 100644 index 0000000..7da178c --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPortBuilder.java @@ -0,0 +1,142 @@ +package org.openmuc.jrxtx; + +import java.io.IOException; + +/** + * Builder class for SerialPorts. Provides a convenient way to set the various fields of a SerialPort. + * + * Example: + * + *

    + * 
    + * SerialPort port = newBuilder("/dev/ttyS0")
    + *                   .setBaudRate(19200)
    + *                   .setParity(Parity.EVEN)
    + *                   .build();
    + * InputStream is = port.getInputStream();
    + * ..
    + * 
    + * 
    + */ +@SuppressWarnings("deprecation") +public class SerialPortBuilder { + + private String portName; + private int baudRate; + private DataBits dataBits; + private Parity parity; + private StopBits stopBits; + private FlowControl flowControl; + + private SerialPortBuilder(String portName) { + this.portName = portName; + this.baudRate = 9600; + this.dataBits = DataBits.DATABITS_8; + this.parity = Parity.EVEN; + this.stopBits = StopBits.STOPBITS_1; + this.flowControl = FlowControl.NONE; + } + + /** + * Constructs a new SerialPortBuilder with the default values. + * + * @param portName + * the serial port name. E.g. on Unix systems: "/dev/ttyUSB0" and on Unix + * @return returns the new builder. + */ + public static SerialPortBuilder newBuilder(String portName) { + return new SerialPortBuilder(portName); + } + + /** + * Set the serial port name. + * + * @param portName + * the serial port name e.g. "/dev/ttyUSB0" + * @return the serial port builder. + */ + public SerialPortBuilder setPortName(String portName) { + this.portName = portName; + return this; + } + + /** + * Set the baud rate for the serial port. Values such as 9600 or 115200. + * + * @param baudRate + * the baud rate. + * @return the serial port builder. + * + * @see SerialPortBuilder#setBaudRate(int) + */ + public SerialPortBuilder setBaudRate(int baudRate) { + this.baudRate = baudRate; + return this; + } + + /** + * Set the number of data bits transfered with the serial port. + * + * @param dataBits + * the number of dataBits. + * @return the serial port builder. + * @see SerialPort#setDataBits(DataBits) + */ + public SerialPortBuilder setDataBits(DataBits dataBits) { + this.dataBits = dataBits; + return this; + } + + /** + * Set the parity of the serial port. + * + * @param parity + * the parity. + * @return the serial port builder. + * @see SerialPort#setParity(Parity) + */ + public SerialPortBuilder setParity(Parity parity) { + this.parity = parity; + return this; + } + + /** + * Set the number of stop bits after each data bits. + * + * @param stopBits + * the number of stop bits. + * @return the serial port builder. + * + * @see SerialPort#setStopBits(StopBits) + */ + public SerialPortBuilder setStopBits(StopBits stopBits) { + this.stopBits = stopBits; + return this; + } + + /** + * Set the flow control type. + * + * @param flowControl + * the flow control. + * + * @return the serial port builder. + * + * @see SerialPort#setFlowControl(FlowControl) + */ + public SerialPortBuilder setFlowControl(FlowControl flowControl) { + this.flowControl = flowControl; + return this; + } + + /** + * Combine all of the options that have been set and return a new SerialPort object. + * + * @return a new serial port object. + * @throws IOException + * if an I/O exception occurred while opening the serial port. + */ + public SerialPort build() throws IOException { + return JRxTxPort.openSerialPort(portName, baudRate, parity, dataBits, stopBits, flowControl); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPortException.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPortException.java new file mode 100644 index 0000000..00f31c8 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPortException.java @@ -0,0 +1,23 @@ +package org.openmuc.jrxtx; + +import java.io.IOException; + +/** + * Signals that a I/O exception with the SerialPort occurred. + * + * @see SerialPort + */ +public class SerialPortException extends IOException { + + private static final long serialVersionUID = -4848841747671551647L; + + /** + * Constructs a new SerialPortException with the specified detail message. + * + * @param message + * the detail message. + */ + public SerialPortException(String message) { + super(message); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPortTimeoutException.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPortTimeoutException.java new file mode 100644 index 0000000..aca7969 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPortTimeoutException.java @@ -0,0 +1,25 @@ +package org.openmuc.jrxtx; + +import java.io.InterruptedIOException; + +/** + * Signals that the read function of the SerialPort input stream has timed out. + */ +public class SerialPortTimeoutException extends InterruptedIOException { + + private static final long serialVersionUID = -5808479011360793837L; + + public SerialPortTimeoutException() { + super(); + } + + /** + * Constructs a new SerialPortTimeoutException with the specified detail message. + * + * @param message + * the detail message. + */ + public SerialPortTimeoutException(String message) { + super(message); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/StopBits.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/StopBits.java new file mode 100644 index 0000000..f02c65d --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/StopBits.java @@ -0,0 +1,32 @@ +package org.openmuc.jrxtx; + +import org.openhab.core.io.transport.serial.SerialPort; + +/** + * The stop bits. + */ +@SuppressWarnings("deprecation") +public enum StopBits { + /** + * 1 stop bit will be sent at the end of every character. + */ + STOPBITS_1(SerialPort.STOPBITS_1), + /** + * 1.5 stop bits will be sent at the end of every character + */ + STOPBITS_1_5(SerialPort.STOPBITS_1_5), + /** + * 2 stop bits will be sent at the end of every character + */ + STOPBITS_2(SerialPort.STOPBITS_2); + + private int odlValue; + + private StopBits(int oldValue) { + this.odlValue = oldValue; + } + + int getOldValue() { + return this.odlValue; + } +} diff --git a/org.openhab.binding.wmbus/src/main/resources/ESH-INF/binding/binding.xml b/org.openhab.binding.wmbus/src/main/resources/ESH-INF/binding/binding.xml deleted file mode 100644 index 117087e..0000000 --- a/org.openhab.binding.wmbus/src/main/resources/ESH-INF/binding/binding.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - WMBus Binding - The WMBus binding uses a WMBus radio USB stick to receive messages and decode the contents to show the data of the devices in the UI. It uses the jMBus library, but currently, only the Techem heat cost allocators are supported. - Hanno - Felix Wagner, Ernst Rohlicek, Roman Malyugin - - - - - Amount of time (in minutes) over which discovery results are retained in inbox. By default set to 720 minutes (24 hours) - minute - - - Whether to include the BridgeUID (stick/adapter name) into the ThingUID of the metering device. May be helpful when receiving data from different sites or in different modes. By default set to false. - - - - diff --git a/org.openhab.binding.wmbus/src/main/resources/ESH-INF/thing/bridge.xml b/org.openhab.binding.wmbus/src/main/resources/ESH-INF/thing/bridge.xml deleted file mode 100644 index c7be726..0000000 --- a/org.openhab.binding.wmbus/src/main/resources/ESH-INF/thing/bridge.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - The WMBus bridge represents the USB stick used to receive WMBus messages. There are three different sticks supported: Amber Wireless AMB8465-M, Radiocrafts RC1180-MBUS and IMST iM871A-USB - - - - - - - The stick model used. - - true - - - - - - - - serial-port - - The name of the serial port (e.g. /dev/ttyUSB0 or COM5). - true - - - Radio mode to operate the WMBus radio module on. - - - - - - - true - - - Type of which date/time channels should be. - - - - - - - true - DATE_TIME - - - Encryption Keys in form ID:KEY;ID:KEY all in hex format like given to the jMBus message printer test program. - - true - - - List of device IDs to filter during receive. If empty, all received devices will be handled, if at least one ID is set, only messages from this device will be handled. Device ID in decumal format as shown in WMBus message prints and given out by the jMBus message printer test program. - - true - - - - - - - The WMBus receiver which is not attached to serial port. - - - - - - - - Type of which date/time channels should be. - - - - - - - true - DATE_TIME - - - Encryption Keys in form ID:KEY;ID:KEY all in hex format like given to the jMBus message printer test program. - - true - - - List of device IDs to filter during receive. If empty, all received devices will be handled, if at least one ID is set, only messages from this device will be handled. Device ID in decumal format as shown in WMBus message prints and given out by the jMBus message printer test program. - - true - - - - - - String - - Raw representation of last frame received by device encoded in HEX form. - - - - - diff --git a/org.openhab.binding.wmbus/src/main/resources/ESH-INF/thing/channel-types.xml b/org.openhab.binding.wmbus/src/main/resources/ESH-INF/thing/channel-types.xml deleted file mode 100644 index 9e878a8..0000000 --- a/org.openhab.binding.wmbus/src/main/resources/ESH-INF/thing/channel-types.xml +++ /dev/null @@ -1,157 +0,0 @@ - - - - - Number:Energy - - Current energy reading - Energy - - - - - - Number:Volume - - Current volume reading - Volume - - - - - - - Number:Power - - Current power reading. - Power - - - - - - Number:Dimensionless - - - Current flow volume reading. - VolumetricFlowRate - - - - - - Number:Temperature - - Current flow temperature reading. - FlowPipe - - - - - - Number:Temperature - - Current return temperature reading. - ReturnPipe - - - - - - Number:Temperature - - Current external temperature reading. - Temperature - - - - - - Number:Temperature - - Current difference between flow and return temperatures. - Temperature - - - - - - Number:Power - - Maximum power reading registered by meter. - Power - - - - - - Number:Dimensionless - - - Maximum flow volume reading registered by meter. - VolumetricFlowRate - - - - - - Number:Temperature - - Maximum flow temperature reading registered by meter. - Temperature - - - - - - Number:Temperature - - Maximum return temperature reading registered by meter. - Temperature - - - - - - Number:Temperature - - Maximum external temperature reading registered by meter. - Temperature - - - - - - Number:Time - - Time since meter reported error (?). - Time - - - - - - DateTime - - The date, when the device last encountered an error. May be set to manufacturing date, installation date or sometime far in the future if none happened yet. - Date - - - - - - Number - - Current error flags value. - QualityOfService - - - - - diff --git a/org.openhab.binding.wmbus/src/main/resources/ESH-INF/thing/itron.xml b/org.openhab.binding.wmbus/src/main/resources/ESH-INF/thing/itron.xml deleted file mode 100644 index ec9a9aa..0000000 --- a/org.openhab.binding.wmbus/src/main/resources/ESH-INF/thing/itron.xml +++ /dev/null @@ -1,417 +0,0 @@ - - - - - - - - - - - - - - Smoke detectors implemented on top of WM-Bus specification, manufactured by Itron. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - DateTime - - Date and time with resolution up to a second. - - - - - - DateTime - - Date and time with resolution up to a second, formatted as a string. - - - - - - DateTime - - Date and time with resolution up to a second, formatted as a unix timestamp. - - - - - - - Switch - - Removal occurred - - - - - Switch - - Billing month - - - - - Switch - - Product is installed. - - - - - Number - - Operation mode. - - - - - - - - - - - Switch - - Perimeter intrusion occurred - - - - - Switch - - Smoke inlet blocked occurred - - - - - Switch - - Temperature out of range occurred - - - - - String - - - - - - - Number - - Battery lifetime - - - - - - - - DateTime - - Last Smoke Alert Start dates - - - - - DateTime - - Last Smoke Alert Start dates (formatted as string) - - - - - DateTime - - Last Smoke Alert Start dates (formatted as number) - - - - - DateTime - - Last Smoke Alert End dates - - - - - DateTime - - Last Smoke Alert End dates (formatted as string) - - - - - DateTime - - Last Smoke Alert End dates (formatted as number) - - - - - DateTime - - Last Beeper stopped during smoke alert dates - - - - - DateTime - - Last Beeper stopped during smoke alert dates (formatted as string) - - - - - DateTime - - Last Beeper stopped during smoke alert dates (formatted as number) - - - - - DateTime - - Last Perimeter Intrusion (Obstacle occured) dates - - - - - DateTime - - Last Perimeter Intrusion (Obstacle occured) dates (formatted as string) - - - - - DateTime - - Last Perimeter Intrusion (Obstacle occured) dates (formatted as number) - - - - - DateTime - - Last Perimeter Intrusion (Obstacle removed) dates - - - - - DateTime - - Last Perimeter Intrusion (Obstacle removed) dates (formatted as string) - - - - - DateTime - - Last Perimeter Intrusion (Obstacle removed) dates (formatted as number) - - - - - DateTime - - last Smoke inlet (Blocked) dates - - - - - DateTime - - Last Smoke inlet (Blocked) dates (formatted as string) - - - - - DateTime - - Last Smoke inlet (Blocked) dates (formatted as number) - - - - - DateTime - - Last Smoke inlet (Blocking removed) dates - - - - - DateTime - - Last Smoke inlet (Blocking removed) dates (formatted as string) - - - - - DateTime - - Last Smoke inlet (Blocking removed) dates (formatted as number) - - - - - DateTime - - Last out of Temperate Range date - - - - - DateTime - - Last out of Temperate Range date (formatted as string) - - - - - DateTime - - Last out of Temperate Range date (formatted as number) - - - - - DateTime - - Last Testswitch dates - - - - - DateTime - - Last Testswitch dates (formatted as string) - - - - - DateTime - - Last Testswitch dates (formatted as number) - - - - - Number - - Number of test switches operated - - - - - - Number - - Perimeter Intrusion day counter cumulated - - - - - - Number - - Smoke inlet day counter cumulated - - - - - \ No newline at end of file diff --git a/org.openhab.binding.wmbus/src/main/resources/ESH-INF/thing/techem.xml b/org.openhab.binding.wmbus/src/main/resources/ESH-INF/thing/techem.xml deleted file mode 100644 index 574ad0a..0000000 --- a/org.openhab.binding.wmbus/src/main/resources/ESH-INF/thing/techem.xml +++ /dev/null @@ -1,230 +0,0 @@ - - - - - - - - - - - A heat cost allocator of Techem - - - - - - - - - - - - - - - - - - - - - - - - A heat cost allocator of Techem - - - - - - - - - - - - - - - - - - - - - - - - - A heat cost allocator of Techem - - - - - - - - - - - - - - - - - - - - - - - - - A heat cost allocator of Techem - - - - - - - - - - - - - - - - - - - - - - - - - - - A heat cost allocator of Techem - - - - - - - - - - - - - - - - - - - - - - - - - - A smoke detector of Techem - - - - - - - - - - - - - - - - - - - - - - A warm water meter of Techem - - - - - - - - - - - - - - - - - - - - - - - - - - - A cold water meter of Techem - - - - - - - - - - - - - - - - - - - - - - - - A heat meter of Techem - - - - - - - - - - - - - - - - - diff --git a/org.openhab.binding.wmbus/src/main/resources/ESH-INF/thing/thing-types.xml b/org.openhab.binding.wmbus/src/main/resources/ESH-INF/thing/thing-types.xml deleted file mode 100644 index 03ed6c7..0000000 --- a/org.openhab.binding.wmbus/src/main/resources/ESH-INF/thing/thing-types.xml +++ /dev/null @@ -1,244 +0,0 @@ - - - - - - - - - - - - - - Universal WMBus device. - - - - - Identifier which lets to find device (in hexdecimal format). - true - - - - - Frequency of updates sent by device. This value is used to determine if device goes offline. - Recommended value depends on manufacturer and specific meter and should be adjusted manually. - Defaults to 60 minutes which is quite long. - - false - minutes - - - - - - - - - - - - - WMBus device which uses secured communication - you need to provide a encryption key to read reported values. - - - - - Identifier which lets to find device (in hexdecimal format). - true - - - - - Frequency of updates sent by device. This value is used to determine if device goes offline. - Recommended value depends on manufacturer and specific meter and should be adjusted manually. - Defaults to 60 minutes which is quite long. - - false - minutes - - - - - Encryption key in hexadecimal format to decode frames sent by this device. Value of this configuration parameter - is optional and required only in case when meter is configured to use encrypted communication. - - false - encryption - - - - - - - Number - - Current temperature in the room in degrees celsius. - Temperature - - - - - Number - - Current temperature of the radiator in degrees celsius. - Temperature - - - - - Number - - Current heat cost allocation reading. - Energy - - - - - Number - - Heat cost allocation reading of previous month. - Energy - - - - - Number - - Heat cost allocation reading of the previous accounting period, usually yearly. - Energy - - - - - DateTime - - The time when the current reading was taken. - Date - - - - - String - - The time when the current reading was taken, formatted as text. - Date - - - - - Number - - The time when the current reading was taken, formatted as UNIX timestmap. - Date - - - - - DateTime - - The time when the previous month's reading was taken. - Date - - - - - String - - The time when the previous month's reading was taken, formatted as text. - Date - - - - - Number - - The time when the previous month's reading was taken, formatted as UNIX timestmap. - Date - - - - - DateTime - - The time when the reading of the previous period was taken. - Date - - - - - String - - The time when the reading of the previous period was taken, formatted as text. - Date - - - - - Number - - The time when the reading of the previous period was taken, formatted as UNIX timestmap. - Date - - - - - Number - - The Received Signal Strength Indication, power of the radio signal in dBm (decibel milliwatt) - QualityOfService - - - - - String - - The Readings of all last months separately. - Date - - - - - Number - - The status byte represented as number. - System - - - - - Number - - Consumption of water/heat accumulated in periods between 1-16. - - - - - - DateTime - - The time when the current reading was taken. - Date - - - - - String - - The time when the current reading was taken, formatted as text. - Date - - - - - Number - - The time when the current reading was taken, formatted as UNIX timestmap. - Date - - - - \ No newline at end of file diff --git a/org.openhab.binding.wmbus/src/main/resources/OH-INF/binding/binding.xml b/org.openhab.binding.wmbus/src/main/resources/OH-INF/binding/binding.xml new file mode 100644 index 0000000..f61b2c9 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/resources/OH-INF/binding/binding.xml @@ -0,0 +1,26 @@ + + + + WMBus Binding + The WMBus binding uses a WMBus radio USB stick to receive messages and decode the contents to show the + data of the devices in the UI. It uses the jMBus library, but currently, only the Techem heat cost allocators are + supported. + Hanno - Felix Wagner, Ernst Rohlicek, Roman Malyugin + + + + + Amount of time (in minutes) over which discovery results are retained in inbox. By default set to 720 + minutes (24 hours) + minute + + + Whether to include the BridgeUID (stick/adapter name) into the ThingUID of the metering device. May be + helpful when receiving data from different sites or in different modes. By default set to false. + + + + + diff --git a/org.openhab.binding.wmbus/src/main/resources/ESH-INF/i18n/wmbus.properties b/org.openhab.binding.wmbus/src/main/resources/OH-INF/i18n/wmbus.properties similarity index 100% rename from org.openhab.binding.wmbus/src/main/resources/ESH-INF/i18n/wmbus.properties rename to org.openhab.binding.wmbus/src/main/resources/OH-INF/i18n/wmbus.properties diff --git a/org.openhab.binding.wmbus/src/main/resources/ESH-INF/i18n/wmbus_xx_XX.properties b/org.openhab.binding.wmbus/src/main/resources/OH-INF/i18n/wmbus_xx_XX.properties similarity index 73% rename from org.openhab.binding.wmbus/src/main/resources/ESH-INF/i18n/wmbus_xx_XX.properties rename to org.openhab.binding.wmbus/src/main/resources/OH-INF/i18n/wmbus_xx_XX.properties index 6f17200..76062d0 100644 --- a/org.openhab.binding.wmbus/src/main/resources/ESH-INF/i18n/wmbus_xx_XX.properties +++ b/org.openhab.binding.wmbus/src/main/resources/OH-INF/i18n/wmbus_xx_XX.properties @@ -8,6 +8,10 @@ binding.wmbus.description = thing-type.wmbus.sample.label = thing-type.wmbus.sample.description = +# thing type config description +thing-type.config.wmbus.sample.config1.label = +thing-type.config.wmbus.sample.config1.description = + # channel types channel-type.wmbus.sample-channel.label = channel-type.wmbus.sample-channel.description = diff --git a/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/bridge.xml b/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/bridge.xml new file mode 100644 index 0000000..c203544 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/bridge.xml @@ -0,0 +1,113 @@ + + + + + + The WMBus bridge represents the USB stick used to receive WMBus messages. There are three different + sticks supported: Amber Wireless AMB8465-M, Radiocrafts RC1180-MBUS and IMST iM871A-USB + + + + + + + The stick model used. + + true + + + + + + + + serial-port + + The name of the serial port (e.g. /dev/ttyUSB0 or COM5). + true + + + Radio mode to operate the WMBus radio module on. + + + + + + + true + + + Type of which date/time channels should be. + + + + + + + true + DATE_TIME + + + Encryption Keys in form ID:KEY;ID:KEY all in hex format like given to the jMBus message printer test + program. + + true + + + List of device IDs to filter during receive. If empty, all received devices will be handled, if at + least one ID is set, only messages from this device will be handled. Device ID in decumal format as shown in WMBus + message prints and given out by the jMBus message printer test program. + + true + + + + + + + The WMBus receiver which is not attached to serial port. + + + + + + + + Type of which date/time channels should be. + + + + + + + true + DATE_TIME + + + Encryption Keys in form ID:KEY;ID:KEY all in hex format like given to the jMBus message printer test + program. + + true + + + List of device IDs to filter during receive. If empty, all received devices will be handled, if at + least one ID is set, only messages from this device will be handled. Device ID in decumal format as shown in WMBus + message prints and given out by the jMBus message printer test program. + + true + + + + + + String + + Raw representation of last frame received by device encoded in HEX form. + + + + + diff --git a/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/channel-types.xml b/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/channel-types.xml new file mode 100644 index 0000000..c6934e3 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/channel-types.xml @@ -0,0 +1,159 @@ + + + + + Number:Energy + + Current energy reading + Energy + + + + + + Number:Volume + + Current volume reading + Volume + + + + + + + Number:Power + + Current power reading. + Power + + + + + + Number:Dimensionless + + + Current flow volume reading. + VolumetricFlowRate + + + + + + Number:Temperature + + Current flow temperature reading. + FlowPipe + + + + + + Number:Temperature + + Current return temperature reading. + ReturnPipe + + + + + + Number:Temperature + + Current external temperature reading. + Temperature + + + + + + Number:Temperature + + Current difference between flow and return temperatures. + Temperature + + + + + + Number:Power + + Maximum power reading registered by meter. + Power + + + + + + Number:Dimensionless + + + Maximum flow volume reading registered by meter. + VolumetricFlowRate + + + + + + Number:Temperature + + Maximum flow temperature reading registered by meter. + Temperature + + + + + + Number:Temperature + + Maximum return temperature reading registered by meter. + Temperature + + + + + + Number:Temperature + + Maximum external temperature reading registered by meter. + Temperature + + + + + + Number:Time + + Time since meter reported error (?). + Time + + + + + + DateTime + + The date, when the device last encountered an error. May be set to manufacturing date, installation date + or sometime far in the future if none happened yet. + Date + + + + + + Number + + Current error flags value. + QualityOfService + + + + + diff --git a/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/itron.xml b/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/itron.xml new file mode 100644 index 0000000..9752fed --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/itron.xml @@ -0,0 +1,429 @@ + + + + + + + + + + + + + + Smoke detectors implemented on top of WM-Bus specification, manufactured by Itron. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DateTime + + Date and time with resolution up to a second. + + + + + + DateTime + + Date and time with resolution up to a second, formatted as a string. + + + + + + DateTime + + Date and time with resolution up to a second, formatted as a unix timestamp. + + + + + + + Switch + + Removal occurred + + + + + Switch + + Billing month + + + + + Switch + + Product is installed. + + + + + Number + + Operation mode. + + + + + + + + + + + Switch + + Perimeter intrusion occurred + + + + + Switch + + Smoke inlet blocked occurred + + + + + Switch + + Temperature out of range occurred + + + + + String + + + + + + + Number + + Battery lifetime + + + + + + + + DateTime + + Last Smoke Alert Start dates + + + + + DateTime + + Last Smoke Alert Start dates (formatted as string) + + + + + DateTime + + Last Smoke Alert Start dates (formatted as number) + + + + + DateTime + + Last Smoke Alert End dates + + + + + DateTime + + Last Smoke Alert End dates (formatted as string) + + + + + DateTime + + Last Smoke Alert End dates (formatted as number) + + + + + DateTime + + Last Beeper stopped during smoke alert dates + + + + + DateTime + + Last Beeper stopped during smoke alert dates (formatted as string) + + + + + DateTime + + Last Beeper stopped during smoke alert dates (formatted as number) + + + + + DateTime + + Last Perimeter Intrusion (Obstacle occured) dates + + + + + DateTime + + Last Perimeter Intrusion (Obstacle occured) dates (formatted as string) + + + + + DateTime + + Last Perimeter Intrusion (Obstacle occured) dates (formatted as number) + + + + + DateTime + + Last Perimeter Intrusion (Obstacle removed) dates + + + + + DateTime + + Last Perimeter Intrusion (Obstacle removed) dates (formatted as string) + + + + + DateTime + + Last Perimeter Intrusion (Obstacle removed) dates (formatted as number) + + + + + DateTime + + last Smoke inlet (Blocked) dates + + + + + DateTime + + Last Smoke inlet (Blocked) dates (formatted as string) + + + + + DateTime + + Last Smoke inlet (Blocked) dates (formatted as number) + + + + + DateTime + + Last Smoke inlet (Blocking removed) dates + + + + + DateTime + + Last Smoke inlet (Blocking removed) dates (formatted as string) + + + + + DateTime + + Last Smoke inlet (Blocking removed) dates (formatted as number) + + + + + DateTime + + Last out of Temperate Range date + + + + + DateTime + + Last out of Temperate Range date (formatted as string) + + + + + DateTime + + Last out of Temperate Range date (formatted as number) + + + + + DateTime + + Last Testswitch dates + + + + + DateTime + + Last Testswitch dates (formatted as string) + + + + + DateTime + + Last Testswitch dates (formatted as number) + + + + + Number + + Number of test switches operated + + + + + + Number + + Perimeter Intrusion day counter cumulated + + + + + + Number + + Smoke inlet day counter cumulated + + + + + diff --git a/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/techem.xml b/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/techem.xml new file mode 100644 index 0000000..ede17e3 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/techem.xml @@ -0,0 +1,231 @@ + + + + + + + + + + + A heat cost allocator of Techem + + + + + + + + + + + + + + + + + + + + + + + + A heat cost allocator of Techem + + + + + + + + + + + + + + + + + + + + + + + + + A heat cost allocator of Techem + + + + + + + + + + + + + + + + + + + + + + + + + A heat cost allocator of Techem + + + + + + + + + + + + + + + + + + + + + + + + + + + A heat cost allocator of Techem + + + + + + + + + + + + + + + + + + + + + + + + + + A smoke detector of Techem + + + + + + + + + + + + + + + + + + + + + + A warm water meter of Techem + + + + + + + + + + + + + + + + + + + + + + + + + + + A cold water meter of Techem + + + + + + + + + + + + + + + + + + + + + + + + A heat meter of Techem + + + + + + + + + + + + + + + + + diff --git a/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/thing-types.xml b/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/thing-types.xml new file mode 100644 index 0000000..df38d52 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/thing-types.xml @@ -0,0 +1,248 @@ + + + + + + + + + + + + + Universal WMBus device. + + + + + Identifier which lets to find device (in hexdecimal format). + true + + + + + Frequency of updates sent by device. This value is used to determine if device goes offline. + Recommended value depends on manufacturer and specific meter and should be adjusted manually. + Defaults to 60 minutes + which is quite long. + + false + minutes + + + + + + + + + + + + + WMBus device which uses secured communication - you need to provide a encryption key to read reported + values. + + + + + Identifier which lets to find device (in hexdecimal format). + true + + + + + Frequency of updates sent by device. This value is used to determine if device goes offline. + Recommended value depends on manufacturer and specific meter and should be adjusted manually. + Defaults to 60 minutes + which is quite long. + + false + minutes + + + + + Encryption key in hexadecimal format to decode frames sent by this device. Value of this configuration + parameter + is optional and required only in case when meter is configured to use encrypted communication. + + false + encryption + + + + + + + Number + + Current temperature in the room in degrees celsius. + Temperature + + + + + Number + + Current temperature of the radiator in degrees celsius. + Temperature + + + + + Number + + Current heat cost allocation reading. + Energy + + + + + Number + + Heat cost allocation reading of previous month. + Energy + + + + + Number + + Heat cost allocation reading of the previous accounting period, usually yearly. + Energy + + + + + DateTime + + The time when the current reading was taken. + Date + + + + + String + + The time when the current reading was taken, formatted as text. + Date + + + + + Number + + The time when the current reading was taken, formatted as UNIX timestmap. + Date + + + + + DateTime + + The time when the previous month's reading was taken. + Date + + + + + String + + The time when the previous month's reading was taken, formatted as text. + Date + + + + + Number + + The time when the previous month's reading was taken, formatted as UNIX timestmap. + Date + + + + + DateTime + + The time when the reading of the previous period was taken. + Date + + + + + String + + The time when the reading of the previous period was taken, formatted as text. + Date + + + + + Number + + The time when the reading of the previous period was taken, formatted as UNIX timestmap. + Date + + + + + Number + + The Received Signal Strength Indication, power of the radio signal in dBm (decibel milliwatt) + QualityOfService + + + + + String + + The Readings of all last months separately. + Date + + + + + Number + + The status byte represented as number. + System + + + + + Number + + Consumption of water/heat accumulated in periods between 1-16. + + + + + + DateTime + + The time when the current reading was taken. + Date + + + + + String + + The time when the current reading was taken, formatted as text. + Date + + + + + Number + + The time when the current reading was taken, formatted as UNIX timestmap. + Date + + + + From 8b6dd7764b80c1bf3989d722339f64a23f416eb2 Mon Sep 17 00:00:00 2001 From: MikeTheTux <44850211+MikeTheTux@users.noreply.github.com> Date: Sat, 6 Mar 2021 10:16:22 +0100 Subject: [PATCH 2/8] Updated README.md --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 8da7969..148c527 100644 --- a/README.md +++ b/README.md @@ -39,16 +39,16 @@ There is some more information and discussion [in the forum](https://community.o ![Diagram in HABmin, fed by several HKVs ](doc/diagrams.png) ## Install -1. Drop the .jar from https://github.com/KuguHome/openhab-binding-wmbus/releases (or your own build, see below) into your openHAB2 Karaf deploy directory, e.g. `openhab2/addons` or `/usr/share/openhab2/addons/`. +1. Drop the .jar from https://github.com/KuguHome/openhab-binding-wmbus/releases (or your own build, see below) into your openHAB Karaf deploy directory, e.g. `openhab/addons` or `/usr/share/openhab/addons/`. 2. It should get picked up automatically and started by Karaf. 3. Run `bundle:list` in the [OSGi console](https://www.openhab.org/docs/administration/console.html), it should show a `wmbus` bundle in active state. If it is there but not active yet, try `feature:install openhab-transport-serial` or `bundle:start XXX` where `XXX` is the number of the wmbus bundle shown by the `list` command. -4. Open PaperUI in the browser. -5. Check that Configuration -> Bindings lists the WMBus Binding. -6. Go to Configuration -> Things. +4. Open OH3 UI in the browser. +5. Check that `Settings` -> `Things` -> `(+)` lists the WMBus Binding. +6. Configure WMBus Binding: Set `include the BridgeUID (stick/adapter name) into the ThingUID of the metering device` to `true`. 7. Manually add new WMBus Binding Thing -> WMBus Stick (exactly one). -8. Select/enter serial device (e.g. /dev/ttyUSB0, check via `dmesg` when plugging in the stick) as configuration parameter. +8. Select/enter serial device (e.g. `/dev/ttyUSB0`, check via `dmesg` when plugging in the stick) as configuration parameter. 9. Select receiving mode. T is most common, will also receive frames sent in mode C. S is transmitting only rarely. -10. The Thing should show `ONLINE` as status. If not, edit the Thing, this screen should include some more error details, also check OSGi console and `userdata/logs/openhab.log` or `/var/log/oppenhab2/openhab.log`. +10. The Thing should show `ONLINE` as status. If not, edit the Thing, this screen should include some more error details, also check OSGi console and `userdata/logs/openhab.log` or `/var/log/oppenhab/openhab.log`. 11. If everything is working correctly, devices should be discovered automatically and turn up as new Things in the Inbox as soon as a message is received from them. Manually adding the devices is not necessary, also the active search function when adding a Thing does nothing. Everything goes throught the discovery. 12. Search your devices in the Inbox by the ID that is printed outside or shown on the display (e.g. Techem HKVs display the last 4 digits) and add those devices via the checkmark button. Make sure "Item Linking Simple Mode" is activated or link the channels to items yourself. 13. On the "Control" page, the Thing with it's different channels should display, readings should be updated regularly about every 4 minutes: @@ -68,7 +68,7 @@ If your device is encrypted, you will need to get the AES key from the manufactu ## Build 1. Run `mvn package` in the root directory.. -2. The compilation result will be at `org.openhab.binding.wmbus/target/org.openhab.binding.wmbus-2.3.0-SNAPSHOT.jar`. +2. The compilation result will be at `org.openhab.binding.wmbus/target/org.openhab.binding.wmbus-3.1.0-SNAPSHOT.jar`. ## Development @@ -76,7 +76,7 @@ If your device is encrypted, you will need to get the AES key from the manufactu 2. Clone this repository. 3. File - Import - Maven - Existing Maven Projects. Give path to this git repository, select all three projects, add project to working set "WMBus" or similar. -For debugging and development, it is helpful to add the folloing to `/var/lib/openhab2/etc/org.ops4j.pax.logging.cfg` to log to a separate file `/var/log/openhab2/wmbus.log` +For debugging and development, it is helpful to add the folloing to `/var/lib/openhab/etc/org.ops4j.pax.logging.cfg` to log to a separate file `/var/log/openhab/wmbus.log` ``` # Define WMBus file appender @@ -100,8 +100,8 @@ log4j2.logger.org_openhab_binding_wmbus.appenderRef.wmbus.ref = WMBUS ``` ## Raw tool -There is an additional tool, that compiles as a second .jar file `org.openhab.binding.wmbus.tools-2.3.0-SNAPSHOT.jar` (also available from the releases page). If you drop this bundle into your addons folder or install it otherwise to openHAB, you can access the tool at http://localhost:8080/wmbus. It gives you the ability to inject frames to the binding, as if they were received via a stick. There is also a collector, which lists you each received WMbus frame as raw hex, together with timestamp and basic grouping (ID, Manufacturer, device type). -If you add the following lines to `/var/lib/openhab2/etc/org.ops4j.pax.logging.cfg`, you can also log all frames to a plaintext file `wmbustools.log`. The content of that file can later be fed to the injector. +There is an additional tool, that compiles as a second .jar file `org.openhab.binding.wmbus.tools-3.1.0-SNAPSHOT.jar` (also available from the releases page). If you drop this bundle into your addons folder or install it otherwise to openHAB, you can access the tool at http://localhost:8080/wmbus. It gives you the ability to inject frames to the binding, as if they were received via a stick. There is also a collector, which lists you each received WMbus frame as raw hex, together with timestamp and basic grouping (ID, Manufacturer, device type). +If you add the following lines to `/var/lib/openhab/etc/org.ops4j.pax.logging.cfg`, you can also log all frames to a plaintext file `wmbustools.log`. The content of that file can later be fed to the injector. ``` # Define WMBus Tools file appender From e62df4958bf84dd1290a09e0c708d062b87a1771 Mon Sep 17 00:00:00 2001 From: Michael Weger Date: Sat, 6 Mar 2021 15:06:42 +0100 Subject: [PATCH 3/8] - migrated tests to OH3 - changed default of includeBridgeUID to true --- org.openhab.binding.wmbus/pom.xml | 24 + .../internal/DynamicBindingConfiguration.java | 4 +- .../main/resources/OH-INF/binding/binding.xml | 2 +- .../wmbus/device/AbstractWMBusTest.java | 110 ++- .../binding/wmbus/device/RecordPredicate.java | 54 +- .../generic/GenericWMBusThingHandlerTest.java | 256 +++--- .../ItronConfigStatusDataParserTest.java | 70 +- .../ItronManufacturerDataParserTest.java | 84 +- .../device/techem/TechemDecoderTest.java | 474 ++++++----- .../device/techem/TechemDiscoveryTest.java | 219 +++-- .../techem/TechemHandlerFactoryTest.java | 149 ++-- .../techem/predicate/FloatPredicate.java | 81 +- .../techem/predicate/IntegerPredicate.java | 82 +- .../techem/predicate/LocalDatePredicate.java | 91 +- .../techem/predicate/QuantityPredicate.java | 72 +- .../techem/predicate/RssiPredicate.java | 29 +- .../techem/predicate/StringPredicate.java | 82 +- .../internal/units/BaseUnitRegistryTest.java | 785 +++++++++--------- .../units/CompositeUnitRegistryTest.java | 47 +- .../ExtendedCompositeUnitRegistryTest.java | 118 +-- ...gistryTest.java => UnitsRegistryTest.java} | 43 +- .../mbus/wireless/MapKeyStorageTest.java | 91 +- 22 files changed, 1480 insertions(+), 1487 deletions(-) rename org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/{SmartHomeUnitsRegistryTest.java => UnitsRegistryTest.java} (75%) diff --git a/org.openhab.binding.wmbus/pom.xml b/org.openhab.binding.wmbus/pom.xml index bca66e6..5c34712 100644 --- a/org.openhab.binding.wmbus/pom.xml +++ b/org.openhab.binding.wmbus/pom.xml @@ -18,6 +18,30 @@ org.openhab.core.io.transport.serial + + junit + junit + 4.12 + test + + + org.assertj + assertj-core + 3.11.1 + test + + + org.slf4j + slf4j-simple + 1.7.2 + test + + + org.mockito + mockito-core + 2.23.0 + test + com.google.guava guava diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/DynamicBindingConfiguration.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/DynamicBindingConfiguration.java index 25ea27f..052cd89 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/DynamicBindingConfiguration.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/DynamicBindingConfiguration.java @@ -34,7 +34,7 @@ public class DynamicBindingConfiguration implements BindingConfiguration { private final Logger logger = LoggerFactory.getLogger(DynamicBindingConfiguration.class); private Long timeToLive = WMBusBindingConstants.DEFAULT_TIME_TO_LIVE; - private Boolean includeBridgeUID = false; + private Boolean includeBridgeUID = true; @Activate public void activate(ComponentContext context) { @@ -67,7 +67,7 @@ public Long getTimeToLive() { private void setIncludeBridgeUID(Object value) { if (value == null) { logger.debug("Setting up includeBridgeUID to default value"); - this.includeBridgeUID = false; + this.includeBridgeUID = true; return; } diff --git a/org.openhab.binding.wmbus/src/main/resources/OH-INF/binding/binding.xml b/org.openhab.binding.wmbus/src/main/resources/OH-INF/binding/binding.xml index f61b2c9..0f9de08 100644 --- a/org.openhab.binding.wmbus/src/main/resources/OH-INF/binding/binding.xml +++ b/org.openhab.binding.wmbus/src/main/resources/OH-INF/binding/binding.xml @@ -18,7 +18,7 @@ Whether to include the BridgeUID (stick/adapter name) into the ThingUID of the metering device. May be - helpful when receiving data from different sites or in different modes. By default set to false. + helpful when receiving data from different sites or in different modes. By default set to true. diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/AbstractWMBusTest.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/AbstractWMBusTest.java index 689e2fb..ea3fea4 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/AbstractWMBusTest.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/AbstractWMBusTest.java @@ -1,56 +1,54 @@ -package org.openhab.binding.wmbus.device; - -import java.util.Collections; - -import org.eclipse.smarthome.core.util.HexUtils; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openmuc.jmbus.DecodingException; -import org.openmuc.jmbus.wireless.VirtualWMBusMessageHelper; -import org.openmuc.jmbus.wireless.WMBusMessage; - -public class AbstractWMBusTest { - - // 7062A0 -> warm water - public final static String MESSAGE_112_WARM_WATER = "29446850084481637062A0009F255502D036410000030404030303030303020303030303030304020404"; - // 7072A0 -> cold water - public final static String MESSAGE_112_COLD_WATER = "29446850054381637072A0009F256E07D0366B01000F1313151712140F06101212170E12121414100F10"; - // 7462A2 -> warm water - public final static String MESSAGE_116_WARM_WATER = "2F446850878465427462A2069F234B00D016150000000101010100010100000100000101020201020201020101020201E60000"; - // 7472A2 -> cold water - public final static String MESSAGE_116_COLD_WATER = "2F446850122320417472A2069F23B301D016B50000000609090908080C09080A0A0A0A09080907080609090707060707000000"; - - // 7143A0 -> heat meter - public final static String MESSAGE_113_HEAT = "36446850180780147143A0009F23880400581B0000000800000000000000000000000000000000000000000001081C60400001001000001F0000"; - - // 4543A1 -> HKVv69 - public final static String MESSAGE_69_HKV = "37446850468519484543A1009F23274900607814000039434C3169650F3EB358D207121AD050451C4C2092C926E960F6577AD5E1C556639F16"; - // 61???? -> HKVv97 - public final static String MESSAGE_97_HKV = ""; - // 6480A0 -> HKVv100 - public final static String MESSAGE_100_HKV = "2E446850382041606480A0119F236800D016410000000000000000000000000000000000000009090F110F0C09000FDA0000"; - // 6980A0 -> HKVv105 - public final static String MESSAGE_105_HKV = "32446850591266506980A0119F23CF07E0169A01680845091A000100000200000000000000000000110B0020361D221F6E9287490000"; - // 9480A2 -> HKVv148 - public final static String MESSAGE_148_HKV = "33446850789492029480A20F9F259C01B031910201580A470A0000000000000000071F241E454E42588478775E4F2E1400000000DB0000"; - - // 76F0A0 -> SDv118 - public final static String MESSAGE_118_SD_1 = "294468507764866476F0A000DE246F2500586F2500001A000013006BA1007CB2008DC3009ED4000FE500EE0000"; - public final static String MESSAGE_118_SD_2 = "294468508364866476F0A000DE246F2500586F25010019000013006BA1007CB2008DC3009ED4000FE500F40000"; - public final static String MESSAGE_118_SD_3 = "294468501857639176f0a000e3267b2700dc7b2700000e0001315678006ba1007cb2008dc3009ed4000fe5"; - - - public final static int RSSI = 10; - - protected final WMBusDevice message(String messageHex) throws DecodingException { - return message(messageHex, null); - } - - protected final WMBusDevice message(String messageHex, WMBusAdapter adapter) throws DecodingException { - byte[] buffer = HexUtils.hexToBytes(messageHex); - WMBusMessage message = VirtualWMBusMessageHelper.decode(buffer, RSSI, Collections.emptyMap()); - - return new WMBusDevice(message, adapter); - } - -} +package org.openhab.binding.wmbus.device; + +import java.util.Collections; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openhab.core.util.HexUtils; +import org.openmuc.jmbus.DecodingException; +import org.openmuc.jmbus.wireless.VirtualWMBusMessageHelper; +import org.openmuc.jmbus.wireless.WMBusMessage; + +public class AbstractWMBusTest { + + // 7062A0 -> warm water + public final static String MESSAGE_112_WARM_WATER = "29446850084481637062A0009F255502D036410000030404030303030303020303030303030304020404"; + // 7072A0 -> cold water + public final static String MESSAGE_112_COLD_WATER = "29446850054381637072A0009F256E07D0366B01000F1313151712140F06101212170E12121414100F10"; + // 7462A2 -> warm water + public final static String MESSAGE_116_WARM_WATER = "2F446850878465427462A2069F234B00D016150000000101010100010100000100000101020201020201020101020201E60000"; + // 7472A2 -> cold water + public final static String MESSAGE_116_COLD_WATER = "2F446850122320417472A2069F23B301D016B50000000609090908080C09080A0A0A0A09080907080609090707060707000000"; + + // 7143A0 -> heat meter + public final static String MESSAGE_113_HEAT = "36446850180780147143A0009F23880400581B0000000800000000000000000000000000000000000000000001081C60400001001000001F0000"; + + // 4543A1 -> HKVv69 + public final static String MESSAGE_69_HKV = "37446850468519484543A1009F23274900607814000039434C3169650F3EB358D207121AD050451C4C2092C926E960F6577AD5E1C556639F16"; + // 61???? -> HKVv97 + public final static String MESSAGE_97_HKV = ""; + // 6480A0 -> HKVv100 + public final static String MESSAGE_100_HKV = "2E446850382041606480A0119F236800D016410000000000000000000000000000000000000009090F110F0C09000FDA0000"; + // 6980A0 -> HKVv105 + public final static String MESSAGE_105_HKV = "32446850591266506980A0119F23CF07E0169A01680845091A000100000200000000000000000000110B0020361D221F6E9287490000"; + // 9480A2 -> HKVv148 + public final static String MESSAGE_148_HKV = "33446850789492029480A20F9F259C01B031910201580A470A0000000000000000071F241E454E42588478775E4F2E1400000000DB0000"; + + // 76F0A0 -> SDv118 + public final static String MESSAGE_118_SD_1 = "294468507764866476F0A000DE246F2500586F2500001A000013006BA1007CB2008DC3009ED4000FE500EE0000"; + public final static String MESSAGE_118_SD_2 = "294468508364866476F0A000DE246F2500586F25010019000013006BA1007CB2008DC3009ED4000FE500F40000"; + public final static String MESSAGE_118_SD_3 = "294468501857639176f0a000e3267b2700dc7b2700000e0001315678006ba1007cb2008dc3009ed4000fe5"; + + public final static int RSSI = 10; + + protected final WMBusDevice message(String messageHex) throws DecodingException { + return message(messageHex, null); + } + + protected final WMBusDevice message(String messageHex, WMBusAdapter adapter) throws DecodingException { + byte[] buffer = HexUtils.hexToBytes(messageHex); + WMBusMessage message = VirtualWMBusMessageHelper.decode(buffer, RSSI, Collections.emptyMap()); + + return new WMBusDevice(message, adapter); + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/RecordPredicate.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/RecordPredicate.java index 7941894..82de597 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/RecordPredicate.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/RecordPredicate.java @@ -1,27 +1,27 @@ -package org.openhab.binding.wmbus.device; - -import java.util.function.Predicate; -import org.openhab.binding.wmbus.device.techem.Record; - -/** - * Predicate for testing records reported by techem decoders. - * - * @author Łukasz Dywicki - initial contribution - */ -public interface RecordPredicate extends Predicate> { - - /** - * Predicate/condition description. - * - * @return Textual description of predicate. - */ - String description(); - - /** - * Arguments passed additionally to format description text. - * - * @return Arguments for formatting predicate description. - */ - Object[] arguments(); - -} +package org.openhab.binding.wmbus.device; + +import java.util.function.Predicate; + +import org.openhab.binding.wmbus.device.techem.Record; + +/** + * Predicate for testing records reported by techem decoders. + * + * @author Łukasz Dywicki - initial contribution + */ +public interface RecordPredicate extends Predicate> { + + /** + * Predicate/condition description. + * + * @return Textual description of predicate. + */ + String description(); + + /** + * Arguments passed additionally to format description text. + * + * @return Arguments for formatting predicate description. + */ + Object[] arguments(); +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/generic/GenericWMBusThingHandlerTest.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/generic/GenericWMBusThingHandlerTest.java index 1952628..f772e47 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/generic/GenericWMBusThingHandlerTest.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/generic/GenericWMBusThingHandlerTest.java @@ -1,129 +1,127 @@ -package org.openhab.binding.wmbus.device.generic; - -import java.util.Map; - -import org.assertj.core.api.Assertions; -import org.eclipse.smarthome.config.core.Configuration; -import org.eclipse.smarthome.core.library.types.QuantityType; -import org.eclipse.smarthome.core.library.unit.SIUnits; -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.ThingUID; -import org.eclipse.smarthome.core.thing.binding.ThingHandler; -import org.eclipse.smarthome.core.thing.binding.ThingHandlerCallback; -import org.eclipse.smarthome.core.thing.binding.builder.BridgeBuilder; -import org.eclipse.smarthome.core.thing.binding.builder.ChannelBuilder; -import org.eclipse.smarthome.core.thing.binding.builder.ThingBuilder; -import org.eclipse.smarthome.core.types.State; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.ArgumentMatchers; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnitRunner; -import org.openhab.binding.wmbus.RecordType; -import org.openhab.binding.wmbus.WMBusBindingConstants; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.handler.WMBusBridgeHandler; -import org.openhab.binding.wmbus.internal.units.CompositeUnitRegistry; -import org.openhab.io.transport.mbus.wireless.MapKeyStorage; -import org.openmuc.jmbus.DataRecord; -import org.openmuc.jmbus.DlmsUnit; - -import com.google.common.collect.ImmutableMap; - -/** - * Test of generic handler and its behavior upon receipt of new wmbus frame. - * - * @author Łukasz Dywicki - Initial contribution. - */ -@RunWith(MockitoJUnitRunner.class) -public class GenericWMBusThingHandlerTest { - - private static final String CHANNEL_VOLUME = "volume"; - private static final String DEVICE_ADDRESS = "68TCH100"; - private static final Map CONFIGURATION = ImmutableMap - .of(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS, DEVICE_ADDRESS); - - private static final RecordType VOLUME = new RecordType(0x0C, 0x14); - private static final Double VOLUME_VALUE = 10.0; - - private static final String THING_ID = "foobar"; - private static final String BRIDGE_ID = "usb0"; - private final static ThingUID THING_UID = new ThingUID(WMBusBindingConstants.THING_TYPE_METER, THING_ID); - private final static ChannelUID CHANNEL_UID = new ChannelUID(THING_UID, CHANNEL_VOLUME); - - private final static Map CHANNEL_MAPPING = ImmutableMap.of(CHANNEL_VOLUME, VOLUME); - - private final GenericWMBusThingHandler handler; - - @Mock - public WMBusBridgeHandler adapter; - - @Mock - private ThingHandlerCallback callback; - - public GenericWMBusThingHandlerTest() { - handler = new GenericWMBusThingHandler(createTestThing(), new MapKeyStorage(), - new CompositeUnitRegistry(), CHANNEL_MAPPING) { - @Override - protected org.eclipse.smarthome.core.thing.Bridge getBridge() { - return createTestBridge(adapter); - }; - }; - - } - - @Before - public void setUp() { - handler.initialize(); - handler.setCallback(callback); - } - - @After - public void tearDown() { - handler.dispose(); - } - - @Test - public void testChannelUpdate() throws Exception { - WMBusDevice device = Mockito.mock(WMBusDevice.class); - DataRecord data = Mockito.mock(DataRecord.class); - - Mockito.when(device.getDeviceAddress()).thenReturn(DEVICE_ADDRESS); - Mockito.when(device.findRecord(VOLUME)).thenReturn(data); - Mockito.when(data.getUnit()).thenReturn(DlmsUnit.CUBIC_METRE); - Mockito.when(data.getScaledDataValue()).thenReturn(VOLUME_VALUE); - handler.onChangedWMBusDevice(adapter, device); - - ArgumentCaptor stateCapture = ArgumentCaptor.forClass(State.class); - Mockito.verify(callback).stateUpdated(ArgumentMatchers.eq(CHANNEL_UID), stateCapture.capture()); - - Assertions.assertThat(stateCapture.getAllValues()).hasSize(1); - - Assertions.assertThat(stateCapture.getAllValues().get(0)) - .isEqualTo(new QuantityType<>(VOLUME_VALUE, SIUnits.CUBIC_METRE)); - } - - public static Thing createTestThing() { - ThingBuilder thing = ThingBuilder.create(WMBusBindingConstants.THING_TYPE_METER, THING_ID); - thing.withConfiguration(new Configuration(CONFIGURATION)); - - Channel channel = ChannelBuilder.create(CHANNEL_UID, "DecimalType").build(); - thing.withChannel(channel); - - return thing.build(); - } - - static Bridge createTestBridge(ThingHandler handler) { - Bridge bridge = BridgeBuilder.create(WMBusBindingConstants.THING_TYPE_BRIDGE, BRIDGE_ID).build(); - bridge.setHandler(handler); - return bridge; - } - -} +package org.openhab.binding.wmbus.device.generic; + +import java.util.Map; + +import org.assertj.core.api.Assertions; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.openhab.binding.wmbus.RecordType; +import org.openhab.binding.wmbus.WMBusBindingConstants; +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.handler.WMBusBridgeHandler; +import org.openhab.binding.wmbus.internal.units.CompositeUnitRegistry; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.thing.binding.builder.BridgeBuilder; +import org.openhab.core.thing.binding.builder.ChannelBuilder; +import org.openhab.core.thing.binding.builder.ThingBuilder; +import org.openhab.core.types.State; +import org.openhab.io.transport.mbus.wireless.MapKeyStorage; +import org.openmuc.jmbus.DataRecord; +import org.openmuc.jmbus.DlmsUnit; + +import com.google.common.collect.ImmutableMap; + +/** + * Test of generic handler and its behavior upon receipt of new wmbus frame. + * + * @author Łukasz Dywicki - Initial contribution. + */ +@RunWith(MockitoJUnitRunner.class) +public class GenericWMBusThingHandlerTest { + + private static final String CHANNEL_VOLUME = "volume"; + private static final String DEVICE_ADDRESS = "68TCH100"; + private static final Map CONFIGURATION = ImmutableMap + .of(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS, DEVICE_ADDRESS); + + private static final RecordType VOLUME = new RecordType(0x0C, 0x14); + private static final Double VOLUME_VALUE = 10.0; + + private static final String THING_ID = "foobar"; + private static final String BRIDGE_ID = "usb0"; + private final static ThingUID THING_UID = new ThingUID(WMBusBindingConstants.THING_TYPE_METER, THING_ID); + private final static ChannelUID CHANNEL_UID = new ChannelUID(THING_UID, CHANNEL_VOLUME); + + private final static Map CHANNEL_MAPPING = ImmutableMap.of(CHANNEL_VOLUME, VOLUME); + + private final GenericWMBusThingHandler handler; + + @Mock + public WMBusBridgeHandler adapter; + + @Mock + private ThingHandlerCallback callback; + + public GenericWMBusThingHandlerTest() { + handler = new GenericWMBusThingHandler(createTestThing(), new MapKeyStorage(), + new CompositeUnitRegistry(), CHANNEL_MAPPING) { + @Override + protected org.openhab.core.thing.Bridge getBridge() { + return createTestBridge(adapter); + }; + }; + } + + @Before + public void setUp() { + handler.initialize(); + handler.setCallback(callback); + } + + @After + public void tearDown() { + handler.dispose(); + } + + @Test + public void testChannelUpdate() throws Exception { + WMBusDevice device = Mockito.mock(WMBusDevice.class); + DataRecord data = Mockito.mock(DataRecord.class); + + Mockito.when(device.getDeviceAddress()).thenReturn(DEVICE_ADDRESS); + Mockito.when(device.findRecord(VOLUME)).thenReturn(data); + Mockito.when(data.getUnit()).thenReturn(DlmsUnit.CUBIC_METRE); + Mockito.when(data.getScaledDataValue()).thenReturn(VOLUME_VALUE); + handler.onChangedWMBusDevice(adapter, device); + + ArgumentCaptor stateCapture = ArgumentCaptor.forClass(State.class); + Mockito.verify(callback).stateUpdated(ArgumentMatchers.eq(CHANNEL_UID), stateCapture.capture()); + + Assertions.assertThat(stateCapture.getAllValues()).hasSize(1); + + Assertions.assertThat(stateCapture.getAllValues().get(0)) + .isEqualTo(new QuantityType<>(VOLUME_VALUE, SIUnits.CUBIC_METRE)); + } + + public static Thing createTestThing() { + ThingBuilder thing = ThingBuilder.create(WMBusBindingConstants.THING_TYPE_METER, THING_ID); + thing.withConfiguration(new Configuration(CONFIGURATION)); + + Channel channel = ChannelBuilder.create(CHANNEL_UID, "DecimalType").build(); + thing.withChannel(channel); + + return thing.build(); + } + + static Bridge createTestBridge(ThingHandler handler) { + Bridge bridge = BridgeBuilder.create(WMBusBindingConstants.THING_TYPE_BRIDGE, BRIDGE_ID).build(); + bridge.setHandler(handler); + return bridge; + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/itron/ItronConfigStatusDataParserTest.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/itron/ItronConfigStatusDataParserTest.java index dd9c4fb..2531c72 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/itron/ItronConfigStatusDataParserTest.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/itron/ItronConfigStatusDataParserTest.java @@ -1,39 +1,31 @@ -package org.openhab.binding.wmbus.device.itron; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.eclipse.smarthome.core.util.HexUtils; -import org.junit.Test; - -public class ItronConfigStatusDataParserTest { - - private ItronConfigStatusDataParser parser = new ItronConfigStatusDataParser(HexUtils.hexToBytes("0C173E7E00208080")); - - @Test - public void testParser() { - assertThat(parser.getBillingDate()) - .isEqualTo(12); - - assertThat(parser.isRemovalOccurred()) - .isEqualTo(true); - - assertThat(parser.isProductInstalled()) - .isEqualTo(true); - - assertThat(parser.getOperationMode()) - .isEqualTo(1); - - assertThat(parser.isPerimeterIntrusionOccurred()) - .isEqualTo(true); - - assertThat(parser.isSmokeInletBlockedOccurred()) - .isEqualTo(false); - - assertThat(parser.isOutOfRangeTemperatureOccurred()) - .isEqualTo(false); - - assertThat(parser.getProductCode()) - .isEqualTo((byte) 0x3E); - } - -} \ No newline at end of file +package org.openhab.binding.wmbus.device.itron; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; +import org.openhab.core.util.HexUtils; + +public class ItronConfigStatusDataParserTest { + + private ItronConfigStatusDataParser parser = new ItronConfigStatusDataParser( + HexUtils.hexToBytes("0C173E7E00208080")); + + @Test + public void testParser() { + assertThat(parser.getBillingDate()).isEqualTo(12); + + assertThat(parser.isRemovalOccurred()).isEqualTo(true); + + assertThat(parser.isProductInstalled()).isEqualTo(true); + + assertThat(parser.getOperationMode()).isEqualTo(1); + + assertThat(parser.isPerimeterIntrusionOccurred()).isEqualTo(true); + + assertThat(parser.isSmokeInletBlockedOccurred()).isEqualTo(false); + + assertThat(parser.isOutOfRangeTemperatureOccurred()).isEqualTo(false); + + assertThat(parser.getProductCode()).isEqualTo((byte) 0x3E); + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/itron/ItronManufacturerDataParserTest.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/itron/ItronManufacturerDataParserTest.java index 49fbbbe..164d8d0 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/itron/ItronManufacturerDataParserTest.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/itron/ItronManufacturerDataParserTest.java @@ -1,43 +1,41 @@ -package org.openhab.binding.wmbus.device.itron; - -import static org.assertj.core.api.Assertions.*; - -import java.time.LocalDateTime; -import org.assertj.core.api.Assertions; -import org.eclipse.smarthome.core.util.HexUtils; -import org.junit.Ignore; -import org.junit.Test; -import org.openhab.binding.wmbus.device.techem.decoder.Buffer; - -public class ItronManufacturerDataParserTest { - - public static final byte[] _2019_12_29_0313 = hex("0D 23 7D 2C"); - public static final byte[] _2020_01_09_1241 = hex("29 2C 89 21"); - public static final byte[] _2020_01_08_2121 = hex("15 35 88 21"); - public static final byte[] _2020_01_10_132057 = hex("39 14 AD 8A 21"); - - private static byte[] hex(String hexStr) { - return HexUtils.hexToBytes(hexStr.replace(" ", "")); - } - - @Test - public void testShortDate() { - assertThat(new ItronManufacturerDataParser(new Buffer(_2019_12_29_0313)).readShortDateTime()) - .isEqualTo(LocalDateTime.of(2019, 12, 29, 3, 13)); - - assertThat(new ItronManufacturerDataParser(new Buffer(_2020_01_09_1241)).readShortDateTime()) - .isEqualTo(LocalDateTime.of(2020, 1, 9, 12, 41)); - - assertThat(new ItronManufacturerDataParser(new Buffer(_2020_01_08_2121)).readShortDateTime()) - .isEqualTo(LocalDateTime.of(2020, 1, 8, 21, 21)); - } - - @Test - @Ignore // parsing logic is not yet done - public void testLongDate() { - assertThat(new ItronManufacturerDataParser(new Buffer(_2020_01_10_132057)).readLongDateTime()) - .isEqualTo(LocalDateTime.of(2020, 1, 10, 13, 20, 57)); - - } - -} +package org.openhab.binding.wmbus.device.itron; + +import static org.assertj.core.api.Assertions.*; + +import java.time.LocalDateTime; + +import org.junit.Ignore; +import org.junit.Test; +import org.openhab.binding.wmbus.device.techem.decoder.Buffer; +import org.openhab.core.util.HexUtils; + +public class ItronManufacturerDataParserTest { + + public static final byte[] _2019_12_29_0313 = hex("0D 23 7D 2C"); + public static final byte[] _2020_01_09_1241 = hex("29 2C 89 21"); + public static final byte[] _2020_01_08_2121 = hex("15 35 88 21"); + public static final byte[] _2020_01_10_132057 = hex("39 14 AD 8A 21"); + + private static byte[] hex(String hexStr) { + return HexUtils.hexToBytes(hexStr.replace(" ", "")); + } + + @Test + public void testShortDate() { + assertThat(new ItronManufacturerDataParser(new Buffer(_2019_12_29_0313)).readShortDateTime()) + .isEqualTo(LocalDateTime.of(2019, 12, 29, 3, 13)); + + assertThat(new ItronManufacturerDataParser(new Buffer(_2020_01_09_1241)).readShortDateTime()) + .isEqualTo(LocalDateTime.of(2020, 1, 9, 12, 41)); + + assertThat(new ItronManufacturerDataParser(new Buffer(_2020_01_08_2121)).readShortDateTime()) + .isEqualTo(LocalDateTime.of(2020, 1, 8, 21, 21)); + } + + @Test + @Ignore // parsing logic is not yet done + public void testLongDate() { + assertThat(new ItronManufacturerDataParser(new Buffer(_2020_01_10_132057)).readLongDateTime()) + .isEqualTo(LocalDateTime.of(2020, 1, 10, 13, 20, 57)); + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/TechemDecoderTest.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/TechemDecoderTest.java index 6c0762f..caf98e0 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/TechemDecoderTest.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/TechemDecoderTest.java @@ -1,241 +1,233 @@ -package org.openhab.binding.wmbus.device.techem; - -import java.time.LocalDate; -import java.util.function.Consumer; - -import javax.measure.Unit; - -import org.assertj.core.api.Assertions; -import org.assertj.core.api.Condition; -import org.eclipse.smarthome.core.library.unit.SIUnits; -import org.junit.Test; -import org.openhab.binding.wmbus.device.AbstractWMBusTest; -import org.openhab.binding.wmbus.device.techem.Record.Type; -import org.openhab.binding.wmbus.device.techem.decoder.CompositeTechemFrameDecoder; -import org.openhab.binding.wmbus.device.techem.predicate.FloatPredicate; -import org.openhab.binding.wmbus.device.techem.predicate.IntegerPredicate; -import org.openhab.binding.wmbus.device.techem.predicate.LocalDatePredicate; -import org.openhab.binding.wmbus.device.techem.predicate.QuantityPredicate; -import org.openhab.binding.wmbus.device.techem.predicate.RssiPredicate; -import org.openhab.binding.wmbus.device.techem.predicate.StringPredicate; -import org.openmuc.jmbus.DeviceType; - -import tec.uom.se.unit.Units; - -public class TechemDecoderTest extends AbstractWMBusTest { - - private final CompositeTechemFrameDecoder reader = new CompositeTechemFrameDecoder(); - - @Test - public void testWarmWater112Parser() throws Exception { - TechemDevice device = reader.decode(message(MESSAGE_112_WARM_WATER)); - - Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemWaterMeter.class, - expectedDevice(DeviceType.WARM_WATER_METER)); - Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH11298_6.getTechemType()); - - Assertions.assertThat(device.getMeasurements()).hasSize(8) - .areAtLeastOne(record(Record.Type.STATUS, 0)) - .areAtLeastOne(record(Record.Type.COUNTER, 3)) - .areAtLeastOne(record(Record.Type.ALMANAC, "4;4;3;3;3;3;3;3;2;3;3;3;3;3;3;3;4;2;4")) - .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 6.5, Units.CUBIC_METRE)) - .areAtLeastOne(record(Record.Type.PAST_VOLUME, 59.7, Units.CUBIC_METRE)).areAtLeastOne(rssi()); - } - - @Test - public void testColdWater112Parser() throws Exception { - TechemDevice device = reader.decode(message(MESSAGE_112_COLD_WATER)); - - Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemWaterMeter.class, - expectedDevice(DeviceType.COLD_WATER_METER)); - Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH112114_16.getTechemType()); - - Assertions.assertThat(device.getMeasurements()).hasSize(6) - .areAtLeastOne(record(Record.Type.STATUS, 0)) - .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 36.3, Units.CUBIC_METRE)) - .areAtLeastOne(record(Record.Type.PAST_VOLUME, 190.2, Units.CUBIC_METRE)).areAtLeastOne(rssi()); - } - - @Test - public void testWarmWater116Parser() throws Exception { - TechemDevice device = reader.decode(message(MESSAGE_116_WARM_WATER)); - - Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemWaterMeter.class, - expectedDevice(DeviceType.WARM_WATER_METER)); - Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH11698_6.getTechemType()); - - Assertions.assertThat(device.getMeasurements()).hasSize(8) - .areAtLeastOne(record(Record.Type.STATUS, 6)) - .areAtLeastOne(record(Record.Type.COUNTER, 0)) - .areAtLeastOne(record(Record.Type.ALMANAC, "1;1;1;1;0;1;1;0;0;1;0;0;1;1;2;2;1;2;2;1;2;1;1;2;2;1;230;0")) - .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 2.1, Units.CUBIC_METRE)) - .areAtLeastOne(record(Record.Type.PAST_VOLUME, 7.5, Units.CUBIC_METRE)).areAtLeastOne(rssi()); - } - - @Test - public void testColdWater116Parser() throws Exception { - TechemDevice device = reader.decode(message(MESSAGE_116_COLD_WATER)); - - Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemWaterMeter.class, - expectedDevice(DeviceType.COLD_WATER_METER)); - Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH116114_16.getTechemType()); - - Assertions.assertThat(device.getMeasurements()).hasSize(6) - .areAtLeastOne(record(Record.Type.STATUS, 6)) - .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 18.1, Units.CUBIC_METRE)) - .areAtLeastOne(record(Record.Type.PAST_VOLUME, 43.5, Units.CUBIC_METRE)).areAtLeastOne(rssi()); - } - - @Test - public void testHeat113Parser() throws Exception { - TechemDevice device = reader.decode(message(MESSAGE_113_HEAT)); - - Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemHeatMeter.class, - expectedDevice(DeviceType.HEAT_METER)); - Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH11367_4_A2.getTechemType()); - - Assertions.assertThat(device.getMeasurements()).hasSize(5) - .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 1769472.0)) - .areAtLeastOne(record(Record.Type.PAST_VOLUME, 8913920.0)).areAtLeastOne(rssi()); - } - - @Test - public void testHKV45() throws Exception { - TechemDevice device = reader.decode(message(MESSAGE_69_HKV)); - - Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemHeatCostAllocator.class, - expectedDevice(DeviceType.HEAT_COST_ALLOCATOR)); - Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH6967_8.getTechemType()); - - Assertions.assertThat(device.getMeasurements()).hasSize(7) - .areAtLeastOne(record(Record.Type.STATUS, 0)) - .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 5240.0)) - .areAtLeastOne(record(Record.Type.PAST_VOLUME, 18727.0)).areAtLeastOne(rssi()); - } - - @Test - public void testHKV6480() throws Exception { - TechemDevice device = reader.decode(message(MESSAGE_100_HKV)); - - Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemHeatCostAllocator.class, - expectedDevice(DeviceType.HEAT_COST_ALLOCATOR)); - Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH100128_8.getTechemType()); - - Assertions.assertThat(device.getMeasurements()).hasSize(7) - .areAtLeastOne(record(Record.Type.STATUS, 17)) - .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 65.0)) - .areAtLeastOne(record(Record.Type.PAST_VOLUME, 104.0)).areAtLeastOne(rssi()); - } - - @Test - public void testHKV6980() throws Exception { - TechemDevice device = reader.decode(message(MESSAGE_105_HKV)); - - Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemHeatCostAllocator.class, - expectedDevice(DeviceType.HEAT_COST_ALLOCATOR)); - Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH105128_8.getTechemType()); - - Assertions.assertThat(device.getMeasurements()).hasSize(10) - .areAtLeastOne(record(Record.Type.STATUS, 17)) - .areAtLeastOne(record(Record.Type.COUNTER, 16)) - .areAtLeastOne(record(Record.Type.ALMANAC, "1;0;0;2;0;0;0;0;0;0;0;0;0;0;17;11;0;32;54;29;34;31;110;146;135;73;0")) - .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 410.0)) - .areAtLeastOne(record(Record.Type.PAST_VOLUME, 1999.0)) - .areAtLeastOne(record(Record.Type.ROOM_TEMPERATURE, 21.52, SIUnits.CELSIUS)) - .areAtLeastOne(record(Record.Type.RADIATOR_TEMPERATURE, 23.73, SIUnits.CELSIUS)).areAtLeastOne(rssi()); - } - - @Test - public void testHKV148() throws Exception { - TechemDevice device = reader.decode(message(MESSAGE_148_HKV)); - - Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemHeatCostAllocator.class, - expectedDevice(DeviceType.HEAT_COST_ALLOCATOR)); - Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH148128_8.getTechemType()); - - Assertions.assertThat(device.getMeasurements()).hasSize(10) - .areAtLeastOne(record(Record.Type.STATUS, 15)) - .areAtLeastOne(record(Record.Type.COUNTER, 0)) - .areAtLeastOne(record(Record.Type.ALMANAC, "0;0;0;0;0;0;7;31;36;30;69;78;66;88;132;120;119;94;79;46;20;0;0;0;0;219;0")) - .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 258.0)) - .areAtLeastOne(record(Record.Type.PAST_VOLUME, 412.0)) - .areAtLeastOne(record(Record.Type.ROOM_TEMPERATURE, 26.48, SIUnits.CELSIUS)) - .areAtLeastOne(record(Record.Type.RADIATOR_TEMPERATURE, 26.31, SIUnits.CELSIUS)).areAtLeastOne(rssi()); - } - - @Test - public void testSD76F0() throws Exception { - TechemDevice device = reader.decode(message(MESSAGE_118_SD_3)); - - Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemSmokeDetector.class, - expectedDevice(DeviceType.SMOKE_DETECTOR)); - Assertions.assertThat(device.getDeviceType()) - .isEqualTo(TechemBindingConstants._68TCH118255_161_A0.getTechemType()); - - Assertions.assertThat(device.getMeasurements()).hasSize(4) - .areAtLeastOne(record(Type.STATUS, 0)) - .areAtLeastOne(record(Type.CURRENT_READING_DATE, LocalDate.of(LocalDate.now().getYear(), 3, 23))) - .areAtLeastOne(record(Type.CURRENT_READING_DATE_SMOKE, LocalDate.of(2019, 11, 27))) - .areAtLeastOne(rssi()); - } - - @Test - public void testSD76F0_extra() throws Exception { - // just another test frame - TechemDevice device = reader.decode(message(MESSAGE_118_SD_2)); - - Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemSmokeDetector.class, - expectedDevice(DeviceType.SMOKE_DETECTOR)); - Assertions.assertThat(device.getDeviceType()) - .isEqualTo(TechemBindingConstants._68TCH118255_161_A0.getTechemType()); - - Assertions.assertThat(device.getMeasurements()).hasSize(4) - .areAtLeastOne(record(Type.STATUS, 0)) - .areAtLeastOne(record(Type.CURRENT_READING_DATE, LocalDate.of(LocalDate.now().getYear(), 2, 22))) - .areAtLeastOne(record(Type.CURRENT_READING_DATE_SMOKE, LocalDate.of(2018, 11, 15))) - .areAtLeastOne(rssi()); - } - - private Condition> record(Type type, String expectedValue) { - StringPredicate predicate = new StringPredicate(type, expectedValue); - - return new Condition<>(predicate, predicate.description(), predicate.arguments()); - } - - private Condition> record(Type type, LocalDate expectedValue) { - LocalDatePredicate predicate = new LocalDatePredicate(type, expectedValue); - - return new Condition<>(predicate, predicate.description(), predicate.arguments()); - } - - private Condition> record(Type type, int expectedValue) { - IntegerPredicate predicate = new IntegerPredicate(type, expectedValue); - - return new Condition<>(predicate, predicate.description(), predicate.arguments()); - } - - private Condition> record(Type type, double expectedValue) { - FloatPredicate predicate = new FloatPredicate(type, expectedValue); - - return new Condition<>(predicate, predicate.description(), predicate.arguments()); - } - - private Condition> record(Type type, double expectedValue, Unit unit) { - QuantityPredicate predicate = new QuantityPredicate(type, expectedValue, unit); - - return new Condition<>(predicate, predicate.description(), predicate.arguments()); - } - - private Condition> rssi() { - RssiPredicate predicate = new RssiPredicate(RSSI); - - return new Condition<>(predicate, predicate.description(), predicate.arguments()); - } - - private Consumer expectedDevice(DeviceType deviceType) { - return device -> { - Assertions.assertThat(device.getTechemDeviceType()).isEqualTo(deviceType); - }; - } - -} +package org.openhab.binding.wmbus.device.techem; + +import java.time.LocalDate; +import java.util.function.Consumer; + +import javax.measure.Unit; + +import org.assertj.core.api.Assertions; +import org.assertj.core.api.Condition; +import org.junit.Test; +import org.openhab.binding.wmbus.device.AbstractWMBusTest; +import org.openhab.binding.wmbus.device.techem.Record.Type; +import org.openhab.binding.wmbus.device.techem.decoder.CompositeTechemFrameDecoder; +import org.openhab.binding.wmbus.device.techem.predicate.FloatPredicate; +import org.openhab.binding.wmbus.device.techem.predicate.IntegerPredicate; +import org.openhab.binding.wmbus.device.techem.predicate.LocalDatePredicate; +import org.openhab.binding.wmbus.device.techem.predicate.QuantityPredicate; +import org.openhab.binding.wmbus.device.techem.predicate.RssiPredicate; +import org.openhab.binding.wmbus.device.techem.predicate.StringPredicate; +import org.openhab.core.library.unit.SIUnits; +import org.openmuc.jmbus.DeviceType; + +import tec.uom.se.unit.Units; + +public class TechemDecoderTest extends AbstractWMBusTest { + + private final CompositeTechemFrameDecoder reader = new CompositeTechemFrameDecoder(); + + @Test + public void testWarmWater112Parser() throws Exception { + TechemDevice device = reader.decode(message(MESSAGE_112_WARM_WATER)); + + Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemWaterMeter.class, + expectedDevice(DeviceType.WARM_WATER_METER)); + Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH11298_6.getTechemType()); + + Assertions.assertThat(device.getMeasurements()).hasSize(8).areAtLeastOne(record(Record.Type.STATUS, 0)) + .areAtLeastOne(record(Record.Type.COUNTER, 3)) + .areAtLeastOne(record(Record.Type.ALMANAC, "4;4;3;3;3;3;3;3;2;3;3;3;3;3;3;3;4;2;4")) + .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 6.5, Units.CUBIC_METRE)) + .areAtLeastOne(record(Record.Type.PAST_VOLUME, 59.7, Units.CUBIC_METRE)).areAtLeastOne(rssi()); + } + + @Test + public void testColdWater112Parser() throws Exception { + TechemDevice device = reader.decode(message(MESSAGE_112_COLD_WATER)); + + Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemWaterMeter.class, + expectedDevice(DeviceType.COLD_WATER_METER)); + Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH112114_16.getTechemType()); + + Assertions.assertThat(device.getMeasurements()).hasSize(6).areAtLeastOne(record(Record.Type.STATUS, 0)) + .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 36.3, Units.CUBIC_METRE)) + .areAtLeastOne(record(Record.Type.PAST_VOLUME, 190.2, Units.CUBIC_METRE)).areAtLeastOne(rssi()); + } + + @Test + public void testWarmWater116Parser() throws Exception { + TechemDevice device = reader.decode(message(MESSAGE_116_WARM_WATER)); + + Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemWaterMeter.class, + expectedDevice(DeviceType.WARM_WATER_METER)); + Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH11698_6.getTechemType()); + + Assertions.assertThat(device.getMeasurements()).hasSize(8).areAtLeastOne(record(Record.Type.STATUS, 6)) + .areAtLeastOne(record(Record.Type.COUNTER, 0)) + .areAtLeastOne(record(Record.Type.ALMANAC, "1;1;1;1;0;1;1;0;0;1;0;0;1;1;2;2;1;2;2;1;2;1;1;2;2;1;230;0")) + .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 2.1, Units.CUBIC_METRE)) + .areAtLeastOne(record(Record.Type.PAST_VOLUME, 7.5, Units.CUBIC_METRE)).areAtLeastOne(rssi()); + } + + @Test + public void testColdWater116Parser() throws Exception { + TechemDevice device = reader.decode(message(MESSAGE_116_COLD_WATER)); + + Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemWaterMeter.class, + expectedDevice(DeviceType.COLD_WATER_METER)); + Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH116114_16.getTechemType()); + + Assertions.assertThat(device.getMeasurements()).hasSize(6).areAtLeastOne(record(Record.Type.STATUS, 6)) + .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 18.1, Units.CUBIC_METRE)) + .areAtLeastOne(record(Record.Type.PAST_VOLUME, 43.5, Units.CUBIC_METRE)).areAtLeastOne(rssi()); + } + + @Test + public void testHeat113Parser() throws Exception { + TechemDevice device = reader.decode(message(MESSAGE_113_HEAT)); + + Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemHeatMeter.class, + expectedDevice(DeviceType.HEAT_METER)); + Assertions.assertThat(device.getDeviceType()) + .isEqualTo(TechemBindingConstants._68TCH11367_4_A2.getTechemType()); + + Assertions.assertThat(device.getMeasurements()).hasSize(5) + .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 1769472.0)) + .areAtLeastOne(record(Record.Type.PAST_VOLUME, 8913920.0)).areAtLeastOne(rssi()); + } + + @Test + public void testHKV45() throws Exception { + TechemDevice device = reader.decode(message(MESSAGE_69_HKV)); + + Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemHeatCostAllocator.class, + expectedDevice(DeviceType.HEAT_COST_ALLOCATOR)); + Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH6967_8.getTechemType()); + + Assertions.assertThat(device.getMeasurements()).hasSize(7).areAtLeastOne(record(Record.Type.STATUS, 0)) + .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 5240.0)) + .areAtLeastOne(record(Record.Type.PAST_VOLUME, 18727.0)).areAtLeastOne(rssi()); + } + + @Test + public void testHKV6480() throws Exception { + TechemDevice device = reader.decode(message(MESSAGE_100_HKV)); + + Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemHeatCostAllocator.class, + expectedDevice(DeviceType.HEAT_COST_ALLOCATOR)); + Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH100128_8.getTechemType()); + + Assertions.assertThat(device.getMeasurements()).hasSize(7).areAtLeastOne(record(Record.Type.STATUS, 17)) + .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 65.0)) + .areAtLeastOne(record(Record.Type.PAST_VOLUME, 104.0)).areAtLeastOne(rssi()); + } + + @Test + public void testHKV6980() throws Exception { + TechemDevice device = reader.decode(message(MESSAGE_105_HKV)); + + Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemHeatCostAllocator.class, + expectedDevice(DeviceType.HEAT_COST_ALLOCATOR)); + Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH105128_8.getTechemType()); + + Assertions.assertThat(device.getMeasurements()).hasSize(10).areAtLeastOne(record(Record.Type.STATUS, 17)) + .areAtLeastOne(record(Record.Type.COUNTER, 16)) + .areAtLeastOne(record(Record.Type.ALMANAC, + "1;0;0;2;0;0;0;0;0;0;0;0;0;0;17;11;0;32;54;29;34;31;110;146;135;73;0")) + .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 410.0)) + .areAtLeastOne(record(Record.Type.PAST_VOLUME, 1999.0)) + .areAtLeastOne(record(Record.Type.ROOM_TEMPERATURE, 21.52, SIUnits.CELSIUS)) + .areAtLeastOne(record(Record.Type.RADIATOR_TEMPERATURE, 23.73, SIUnits.CELSIUS)).areAtLeastOne(rssi()); + } + + @Test + public void testHKV148() throws Exception { + TechemDevice device = reader.decode(message(MESSAGE_148_HKV)); + + Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemHeatCostAllocator.class, + expectedDevice(DeviceType.HEAT_COST_ALLOCATOR)); + Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH148128_8.getTechemType()); + + Assertions.assertThat(device.getMeasurements()).hasSize(10).areAtLeastOne(record(Record.Type.STATUS, 15)) + .areAtLeastOne(record(Record.Type.COUNTER, 0)) + .areAtLeastOne(record(Record.Type.ALMANAC, + "0;0;0;0;0;0;7;31;36;30;69;78;66;88;132;120;119;94;79;46;20;0;0;0;0;219;0")) + .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 258.0)) + .areAtLeastOne(record(Record.Type.PAST_VOLUME, 412.0)) + .areAtLeastOne(record(Record.Type.ROOM_TEMPERATURE, 26.48, SIUnits.CELSIUS)) + .areAtLeastOne(record(Record.Type.RADIATOR_TEMPERATURE, 26.31, SIUnits.CELSIUS)).areAtLeastOne(rssi()); + } + + @Test + public void testSD76F0() throws Exception { + TechemDevice device = reader.decode(message(MESSAGE_118_SD_3)); + + Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemSmokeDetector.class, + expectedDevice(DeviceType.SMOKE_DETECTOR)); + Assertions.assertThat(device.getDeviceType()) + .isEqualTo(TechemBindingConstants._68TCH118255_161_A0.getTechemType()); + + Assertions.assertThat(device.getMeasurements()).hasSize(4).areAtLeastOne(record(Type.STATUS, 0)) + .areAtLeastOne(record(Type.CURRENT_READING_DATE, LocalDate.of(LocalDate.now().getYear(), 3, 23))) + .areAtLeastOne(record(Type.CURRENT_READING_DATE_SMOKE, LocalDate.of(2019, 11, 27))) + .areAtLeastOne(rssi()); + } + + @Test + public void testSD76F0_extra() throws Exception { + // just another test frame + TechemDevice device = reader.decode(message(MESSAGE_118_SD_2)); + + Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemSmokeDetector.class, + expectedDevice(DeviceType.SMOKE_DETECTOR)); + Assertions.assertThat(device.getDeviceType()) + .isEqualTo(TechemBindingConstants._68TCH118255_161_A0.getTechemType()); + + Assertions.assertThat(device.getMeasurements()).hasSize(4).areAtLeastOne(record(Type.STATUS, 0)) + .areAtLeastOne(record(Type.CURRENT_READING_DATE, LocalDate.of(LocalDate.now().getYear(), 2, 22))) + .areAtLeastOne(record(Type.CURRENT_READING_DATE_SMOKE, LocalDate.of(2018, 11, 15))) + .areAtLeastOne(rssi()); + } + + private Condition> record(Type type, String expectedValue) { + StringPredicate predicate = new StringPredicate(type, expectedValue); + + return new Condition<>(predicate, predicate.description(), predicate.arguments()); + } + + private Condition> record(Type type, LocalDate expectedValue) { + LocalDatePredicate predicate = new LocalDatePredicate(type, expectedValue); + + return new Condition<>(predicate, predicate.description(), predicate.arguments()); + } + + private Condition> record(Type type, int expectedValue) { + IntegerPredicate predicate = new IntegerPredicate(type, expectedValue); + + return new Condition<>(predicate, predicate.description(), predicate.arguments()); + } + + private Condition> record(Type type, double expectedValue) { + FloatPredicate predicate = new FloatPredicate(type, expectedValue); + + return new Condition<>(predicate, predicate.description(), predicate.arguments()); + } + + private Condition> record(Type type, double expectedValue, Unit unit) { + QuantityPredicate predicate = new QuantityPredicate(type, expectedValue, unit); + + return new Condition<>(predicate, predicate.description(), predicate.arguments()); + } + + private Condition> rssi() { + RssiPredicate predicate = new RssiPredicate(RSSI); + + return new Condition<>(predicate, predicate.description(), predicate.arguments()); + } + + private Consumer expectedDevice(DeviceType deviceType) { + return device -> { + Assertions.assertThat(device.getTechemDeviceType()).isEqualTo(deviceType); + }; + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/TechemDiscoveryTest.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/TechemDiscoveryTest.java index 2a95940..eec2b65 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/TechemDiscoveryTest.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/TechemDiscoveryTest.java @@ -1,110 +1,109 @@ -package org.openhab.binding.wmbus.device.techem; - -import static org.mockito.Mockito.when; - -import org.assertj.core.api.Assertions; -import org.eclipse.smarthome.config.discovery.DiscoveryResult; -import org.eclipse.smarthome.core.thing.ThingUID; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import org.openhab.binding.wmbus.WMBusBindingConstants; -import org.openhab.binding.wmbus.device.AbstractWMBusTest; -import org.openhab.binding.wmbus.device.techem.decoder.CompositeTechemFrameDecoder; -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openhab.binding.wmbus.internal.DynamicBindingConfiguration; -import org.openmuc.jmbus.DecodingException; - -@RunWith(MockitoJUnitRunner.class) -public class TechemDiscoveryTest extends AbstractWMBusTest implements TechemBindingConstants { - - private final TechemDiscoveryParticipant discoverer = new TechemDiscoveryParticipant(); - - @Mock - private WMBusAdapter adapter; - - @Before - public void setUp() { - discoverer.setTechemFrameDecoder(new CompositeTechemFrameDecoder()); - discoverer.setBindingConfiguration(new DynamicBindingConfiguration()); - } - - @Test - public void testWZ7062Support() throws Exception { - DiscoveryResult result = result(MESSAGE_112_WARM_WATER); - Assertions.assertThat(result).isNotNull(); - Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_WARM_WATER_METER); - } - - @Test - public void testWZ7072Support() throws Exception { - DiscoveryResult result = result(MESSAGE_112_COLD_WATER); - Assertions.assertThat(result).isNotNull(); - Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_COLD_WATER_METER); - } - - @Test - public void testWZ7462Support() throws Exception { - DiscoveryResult result = result(MESSAGE_116_WARM_WATER); - Assertions.assertThat(result).isNotNull(); - Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_WARM_WATER_METER); - } - - @Test - public void testWZ7472Support() throws Exception { - DiscoveryResult result = result(MESSAGE_116_COLD_WATER); - Assertions.assertThat(result).isNotNull(); - Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_COLD_WATER_METER); - } - - @Test - public void testWMZ43Support() throws Exception { - DiscoveryResult result = result(MESSAGE_113_HEAT); - Assertions.assertThat(result).isNotNull(); - Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_HEAT_METER); - } - - @Test - public void testHKV4543Support() throws Exception { - DiscoveryResult result = result(MESSAGE_69_HKV); - Assertions.assertThat(result).isNotNull(); - Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_HKV45); - } - - @Test - public void testHKV6480Support() throws Exception { - DiscoveryResult result = result(MESSAGE_100_HKV); - Assertions.assertThat(result).isNotNull(); - Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_HKV64); - } - - @Test - public void testHKV6980Support() throws Exception { - DiscoveryResult result = result(MESSAGE_105_HKV); - Assertions.assertThat(result).isNotNull(); - Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_HKV69); - } - - @Test - public void testHKV9480Support() throws Exception { - DiscoveryResult result = result(MESSAGE_148_HKV); - Assertions.assertThat(result).isNotNull(); - Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_HKV94); - } - - @Test - public void testSD76Support() throws Exception { - DiscoveryResult result = result(MESSAGE_118_SD_1); - Assertions.assertThat(result).isNotNull(); - Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_SD76); - } - - private DiscoveryResult result(String message) throws DecodingException { - when(adapter.getUID()).thenReturn(new ThingUID(WMBusBindingConstants.THING_TYPE_BRIDGE, "bridge0")); - - return discoverer.createResult(message(message, adapter)); - } - -} +package org.openhab.binding.wmbus.device.techem; + +import static org.mockito.Mockito.when; + +import org.assertj.core.api.Assertions; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.openhab.binding.wmbus.WMBusBindingConstants; +import org.openhab.binding.wmbus.device.AbstractWMBusTest; +import org.openhab.binding.wmbus.device.techem.decoder.CompositeTechemFrameDecoder; +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openhab.binding.wmbus.internal.DynamicBindingConfiguration; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.thing.ThingUID; +import org.openmuc.jmbus.DecodingException; + +@RunWith(MockitoJUnitRunner.class) +public class TechemDiscoveryTest extends AbstractWMBusTest implements TechemBindingConstants { + + private final TechemDiscoveryParticipant discoverer = new TechemDiscoveryParticipant(); + + @Mock + private WMBusAdapter adapter; + + @Before + public void setUp() { + discoverer.setTechemFrameDecoder(new CompositeTechemFrameDecoder()); + discoverer.setBindingConfiguration(new DynamicBindingConfiguration()); + } + + @Test + public void testWZ7062Support() throws Exception { + DiscoveryResult result = result(MESSAGE_112_WARM_WATER); + Assertions.assertThat(result).isNotNull(); + Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_WARM_WATER_METER); + } + + @Test + public void testWZ7072Support() throws Exception { + DiscoveryResult result = result(MESSAGE_112_COLD_WATER); + Assertions.assertThat(result).isNotNull(); + Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_COLD_WATER_METER); + } + + @Test + public void testWZ7462Support() throws Exception { + DiscoveryResult result = result(MESSAGE_116_WARM_WATER); + Assertions.assertThat(result).isNotNull(); + Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_WARM_WATER_METER); + } + + @Test + public void testWZ7472Support() throws Exception { + DiscoveryResult result = result(MESSAGE_116_COLD_WATER); + Assertions.assertThat(result).isNotNull(); + Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_COLD_WATER_METER); + } + + @Test + public void testWMZ43Support() throws Exception { + DiscoveryResult result = result(MESSAGE_113_HEAT); + Assertions.assertThat(result).isNotNull(); + Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_HEAT_METER); + } + + @Test + public void testHKV4543Support() throws Exception { + DiscoveryResult result = result(MESSAGE_69_HKV); + Assertions.assertThat(result).isNotNull(); + Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_HKV45); + } + + @Test + public void testHKV6480Support() throws Exception { + DiscoveryResult result = result(MESSAGE_100_HKV); + Assertions.assertThat(result).isNotNull(); + Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_HKV64); + } + + @Test + public void testHKV6980Support() throws Exception { + DiscoveryResult result = result(MESSAGE_105_HKV); + Assertions.assertThat(result).isNotNull(); + Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_HKV69); + } + + @Test + public void testHKV9480Support() throws Exception { + DiscoveryResult result = result(MESSAGE_148_HKV); + Assertions.assertThat(result).isNotNull(); + Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_HKV94); + } + + @Test + public void testSD76Support() throws Exception { + DiscoveryResult result = result(MESSAGE_118_SD_1); + Assertions.assertThat(result).isNotNull(); + Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_SD76); + } + + private DiscoveryResult result(String message) throws DecodingException { + when(adapter.getUID()).thenReturn(new ThingUID(WMBusBindingConstants.THING_TYPE_BRIDGE, "bridge0")); + + return discoverer.createResult(message(message, adapter)); + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/TechemHandlerFactoryTest.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/TechemHandlerFactoryTest.java index 01223e4..22c578d 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/TechemHandlerFactoryTest.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/TechemHandlerFactoryTest.java @@ -1,75 +1,74 @@ -package org.openhab.binding.wmbus.device.techem; - -import org.assertj.core.api.Assertions; -import org.eclipse.smarthome.core.thing.binding.ThingHandler; -import org.eclipse.smarthome.core.thing.internal.ThingImpl; -import org.junit.Before; -import org.junit.Test; -import org.openhab.binding.wmbus.device.techem.decoder.CompositeTechemFrameDecoder; - -public class TechemHandlerFactoryTest implements TechemBindingConstants { - - private final TechemHandlerFactory handlerFactory = new TechemHandlerFactory(); - - @Before - public void setUp() { - handlerFactory.setTechemFrameDecoder(new CompositeTechemFrameDecoder()); - } - - @Test - public void testHKV61Support() { - ThingImpl thing = new ThingImpl(THING_TYPE_TECHEM_HKV61, "00000"); - - ThingHandler handler = handlerFactory.createHandler(thing); - Assertions.assertThat(handler).isNotNull(); - } - - @Test - public void testHKV64Support() { - ThingImpl thing = new ThingImpl(THING_TYPE_TECHEM_HKV64, "00000"); - - ThingHandler handler = handlerFactory.createHandler(thing); - Assertions.assertThat(handler).isNotNull(); - } - - @Test - public void testHKV69Support() { - ThingImpl thing = new ThingImpl(THING_TYPE_TECHEM_HKV69, "00000"); - - ThingHandler handler = handlerFactory.createHandler(thing); - Assertions.assertThat(handler).isNotNull(); - } - - @Test - public void testSD76Support() { - ThingImpl thing = new ThingImpl(THING_TYPE_TECHEM_SD76, "00000"); - - ThingHandler handler = handlerFactory.createHandler(thing); - Assertions.assertThat(handler).isNotNull(); - } - - @Test - public void testWZ62Support() { - ThingImpl thing = new ThingImpl(THING_TYPE_TECHEM_WARM_WATER_METER, "00000"); - - ThingHandler handler = handlerFactory.createHandler(thing); - Assertions.assertThat(handler).isNotNull(); - } - - @Test - public void testWZ72Support() { - ThingImpl thing = new ThingImpl(THING_TYPE_TECHEM_COLD_WATER_METER, "00000"); - - ThingHandler handler = handlerFactory.createHandler(thing); - Assertions.assertThat(handler).isNotNull(); - } - - @Test - public void testWZ43Support() { - ThingImpl thing = new ThingImpl(THING_TYPE_TECHEM_HEAT_METER, "00000"); - - ThingHandler handler = handlerFactory.createHandler(thing); - Assertions.assertThat(handler).isNotNull(); - } - -} +package org.openhab.binding.wmbus.device.techem; + +import org.assertj.core.api.Assertions; +import org.junit.Before; +import org.junit.Test; +import org.openhab.binding.wmbus.device.techem.decoder.CompositeTechemFrameDecoder; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.internal.ThingImpl; + +public class TechemHandlerFactoryTest implements TechemBindingConstants { + + private final TechemHandlerFactory handlerFactory = new TechemHandlerFactory(); + + @Before + public void setUp() { + handlerFactory.setTechemFrameDecoder(new CompositeTechemFrameDecoder()); + } + + @Test + public void testHKV61Support() { + ThingImpl thing = new ThingImpl(THING_TYPE_TECHEM_HKV61, "00000"); + + ThingHandler handler = handlerFactory.createHandler(thing); + Assertions.assertThat(handler).isNotNull(); + } + + @Test + public void testHKV64Support() { + ThingImpl thing = new ThingImpl(THING_TYPE_TECHEM_HKV64, "00000"); + + ThingHandler handler = handlerFactory.createHandler(thing); + Assertions.assertThat(handler).isNotNull(); + } + + @Test + public void testHKV69Support() { + ThingImpl thing = new ThingImpl(THING_TYPE_TECHEM_HKV69, "00000"); + + ThingHandler handler = handlerFactory.createHandler(thing); + Assertions.assertThat(handler).isNotNull(); + } + + @Test + public void testSD76Support() { + ThingImpl thing = new ThingImpl(THING_TYPE_TECHEM_SD76, "00000"); + + ThingHandler handler = handlerFactory.createHandler(thing); + Assertions.assertThat(handler).isNotNull(); + } + + @Test + public void testWZ62Support() { + ThingImpl thing = new ThingImpl(THING_TYPE_TECHEM_WARM_WATER_METER, "00000"); + + ThingHandler handler = handlerFactory.createHandler(thing); + Assertions.assertThat(handler).isNotNull(); + } + + @Test + public void testWZ72Support() { + ThingImpl thing = new ThingImpl(THING_TYPE_TECHEM_COLD_WATER_METER, "00000"); + + ThingHandler handler = handlerFactory.createHandler(thing); + Assertions.assertThat(handler).isNotNull(); + } + + @Test + public void testWZ43Support() { + ThingImpl thing = new ThingImpl(THING_TYPE_TECHEM_HEAT_METER, "00000"); + + ThingHandler handler = handlerFactory.createHandler(thing); + Assertions.assertThat(handler).isNotNull(); + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/FloatPredicate.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/FloatPredicate.java index 87a0f33..d13661a 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/FloatPredicate.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/FloatPredicate.java @@ -1,41 +1,40 @@ -package org.openhab.binding.wmbus.device.techem.predicate; - -import org.assertj.core.api.Assertions; -import org.openhab.binding.wmbus.device.RecordPredicate; -import org.openhab.binding.wmbus.device.techem.Record; - -public class FloatPredicate implements RecordPredicate { - - protected final Record.Type type; - protected final float expectedValue; - - public FloatPredicate(Record.Type type, Double expectedValue) { - this.type = type; - this.expectedValue = expectedValue.floatValue(); - } - - @Override - public boolean test(Record record) { - try { - Assertions.assertThat(record.getType()).isEqualTo(type); - - testValue(record.getValue()); - } catch (AssertionError e) { - return false; - } - return true; - } - - protected void testValue(Object value) { - Assertions.assertThat(value).isInstanceOf(Float.class) - .isEqualTo(expectedValue); - } - - public String description() { - return "record of type %s, with value %f"; - } - - public Object[] arguments() { - return new Object[] { type, expectedValue }; - } -} +package org.openhab.binding.wmbus.device.techem.predicate; + +import org.assertj.core.api.Assertions; +import org.openhab.binding.wmbus.device.RecordPredicate; +import org.openhab.binding.wmbus.device.techem.Record; + +public class FloatPredicate implements RecordPredicate { + + protected final Record.Type type; + protected final float expectedValue; + + public FloatPredicate(Record.Type type, Double expectedValue) { + this.type = type; + this.expectedValue = expectedValue.floatValue(); + } + + @Override + public boolean test(Record record) { + try { + Assertions.assertThat(record.getType()).isEqualTo(type); + + testValue(record.getValue()); + } catch (AssertionError e) { + return false; + } + return true; + } + + protected void testValue(Object value) { + Assertions.assertThat(value).isInstanceOf(Float.class).isEqualTo(expectedValue); + } + + public String description() { + return "record of type %s, with value %f"; + } + + public Object[] arguments() { + return new Object[] { type, expectedValue }; + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/IntegerPredicate.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/IntegerPredicate.java index 9592918..8417783 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/IntegerPredicate.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/IntegerPredicate.java @@ -1,41 +1,41 @@ -package org.openhab.binding.wmbus.device.techem.predicate; - -import java.util.function.Predicate; -import org.assertj.core.api.Assertions; -import org.openhab.binding.wmbus.device.techem.Record; - -public class IntegerPredicate implements Predicate> { - - protected final Record.Type type; - protected final int expectedValue; - - public IntegerPredicate(Record.Type type, int expectedValue) { - this.type = type; - this.expectedValue = expectedValue; - } - - @Override - public boolean test(Record record) { - try { - Assertions.assertThat(record.getType()).isEqualTo(type); - - testValue(record.getValue()); - } catch (AssertionError e) { - return false; - } - return true; - } - - protected void testValue(Object value) { - Assertions.assertThat(value).isInstanceOf(Integer.class) - .isEqualTo(expectedValue); - } - - public String description() { - return "record of type %s, with value %d"; - } - - public Object[] arguments() { - return new Object[] { type, expectedValue }; - } -} +package org.openhab.binding.wmbus.device.techem.predicate; + +import java.util.function.Predicate; + +import org.assertj.core.api.Assertions; +import org.openhab.binding.wmbus.device.techem.Record; + +public class IntegerPredicate implements Predicate> { + + protected final Record.Type type; + protected final int expectedValue; + + public IntegerPredicate(Record.Type type, int expectedValue) { + this.type = type; + this.expectedValue = expectedValue; + } + + @Override + public boolean test(Record record) { + try { + Assertions.assertThat(record.getType()).isEqualTo(type); + + testValue(record.getValue()); + } catch (AssertionError e) { + return false; + } + return true; + } + + protected void testValue(Object value) { + Assertions.assertThat(value).isInstanceOf(Integer.class).isEqualTo(expectedValue); + } + + public String description() { + return "record of type %s, with value %d"; + } + + public Object[] arguments() { + return new Object[] { type, expectedValue }; + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/LocalDatePredicate.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/LocalDatePredicate.java index 28e8d04..b8fb0c6 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/LocalDatePredicate.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/LocalDatePredicate.java @@ -1,45 +1,46 @@ -package org.openhab.binding.wmbus.device.techem.predicate; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import org.assertj.core.api.Assertions; -import org.openhab.binding.wmbus.device.RecordPredicate; -import org.openhab.binding.wmbus.device.techem.Record; - -public class LocalDatePredicate implements RecordPredicate { - - protected final Record.Type type; - protected final LocalDate expectedValue; - - public LocalDatePredicate(Record.Type type, LocalDate expectedValue) { - this.type = type; - this.expectedValue = expectedValue; - } - - @Override - public boolean test(Record record) { - try { - Assertions.assertThat(record.getType()).isEqualTo(type); - - testValue(record.getValue()); - } catch (AssertionError e) { - return false; - } - return true; - } - - protected void testValue(Object value) { - Assertions.assertThat(value).isInstanceOf(LocalDateTime.class); - LocalDate date = ((LocalDateTime) value).toLocalDate(); - - Assertions.assertThat(date).isEqualTo(expectedValue); - } - - public String description() { - return "record of type %s, with value %s"; - } - - public Object[] arguments() { - return new Object[] { type, expectedValue }; - } -} +package org.openhab.binding.wmbus.device.techem.predicate; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import org.assertj.core.api.Assertions; +import org.openhab.binding.wmbus.device.RecordPredicate; +import org.openhab.binding.wmbus.device.techem.Record; + +public class LocalDatePredicate implements RecordPredicate { + + protected final Record.Type type; + protected final LocalDate expectedValue; + + public LocalDatePredicate(Record.Type type, LocalDate expectedValue) { + this.type = type; + this.expectedValue = expectedValue; + } + + @Override + public boolean test(Record record) { + try { + Assertions.assertThat(record.getType()).isEqualTo(type); + + testValue(record.getValue()); + } catch (AssertionError e) { + return false; + } + return true; + } + + protected void testValue(Object value) { + Assertions.assertThat(value).isInstanceOf(LocalDateTime.class); + LocalDate date = ((LocalDateTime) value).toLocalDate(); + + Assertions.assertThat(date).isEqualTo(expectedValue); + } + + public String description() { + return "record of type %s, with value %s"; + } + + public Object[] arguments() { + return new Object[] { type, expectedValue }; + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/QuantityPredicate.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/QuantityPredicate.java index a3c260e..a1d84f2 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/QuantityPredicate.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/QuantityPredicate.java @@ -1,36 +1,36 @@ -package org.openhab.binding.wmbus.device.techem.predicate; - -import javax.measure.Quantity; -import javax.measure.Unit; -import org.assertj.core.api.Assertions; -import org.openhab.binding.wmbus.device.techem.Record; - -public class QuantityPredicate extends FloatPredicate { - - private final Unit unit; - - public QuantityPredicate(Record.Type type, double expectedValue, Unit unit) { - super(type, expectedValue); - this.unit = unit; - } - - @Override - protected void testValue(Object value) { - Assertions.assertThat(value).isInstanceOf(Quantity.class); - - Quantity quantity = (Quantity) value; - Assertions.assertThat(quantity.getValue().floatValue()) - .isEqualTo(Double.valueOf(expectedValue).floatValue()); - Assertions.assertThat(quantity.getUnit()).isEqualTo(unit); - } - - @Override - public String description() { - return "record of type %s, with value %f in %s"; - } - - @Override - public Object[] arguments() { - return new Object[] { type, expectedValue, unit }; - } -} +package org.openhab.binding.wmbus.device.techem.predicate; + +import javax.measure.Quantity; +import javax.measure.Unit; + +import org.assertj.core.api.Assertions; +import org.openhab.binding.wmbus.device.techem.Record; + +public class QuantityPredicate extends FloatPredicate { + + private final Unit unit; + + public QuantityPredicate(Record.Type type, double expectedValue, Unit unit) { + super(type, expectedValue); + this.unit = unit; + } + + @Override + protected void testValue(Object value) { + Assertions.assertThat(value).isInstanceOf(Quantity.class); + + Quantity quantity = (Quantity) value; + Assertions.assertThat(quantity.getValue().floatValue()).isEqualTo(Double.valueOf(expectedValue).floatValue()); + Assertions.assertThat(quantity.getUnit()).isEqualTo(unit); + } + + @Override + public String description() { + return "record of type %s, with value %f in %s"; + } + + @Override + public Object[] arguments() { + return new Object[] { type, expectedValue, unit }; + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/RssiPredicate.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/RssiPredicate.java index 981b1de..8f1d245 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/RssiPredicate.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/RssiPredicate.java @@ -1,15 +1,14 @@ -package org.openhab.binding.wmbus.device.techem.predicate; - -import org.openhab.binding.wmbus.device.techem.Record; - -public class RssiPredicate extends IntegerPredicate { - - public RssiPredicate(int expectedValue) { - super(Record.Type.RSSI, expectedValue); - } - - public String description() { - return "Missing RSSI record, with expected value %d"; - } - -} +package org.openhab.binding.wmbus.device.techem.predicate; + +import org.openhab.binding.wmbus.device.techem.Record; + +public class RssiPredicate extends IntegerPredicate { + + public RssiPredicate(int expectedValue) { + super(Record.Type.RSSI, expectedValue); + } + + public String description() { + return "Missing RSSI record, with expected value %d"; + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/StringPredicate.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/StringPredicate.java index 724bb63..245d015 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/StringPredicate.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/StringPredicate.java @@ -1,42 +1,40 @@ -package org.openhab.binding.wmbus.device.techem.predicate; - -import org.assertj.core.api.Assertions; -import org.openhab.binding.wmbus.device.RecordPredicate; -import org.openhab.binding.wmbus.device.techem.Record; - -public class StringPredicate implements RecordPredicate { - - protected final Record.Type type; - protected final String expectedValue; - - public StringPredicate(Record.Type type, String expectedValue) { - this.type = type; - this.expectedValue = expectedValue; - } - - @Override - public boolean test(Record record) { - try { - Assertions.assertThat(record.getType()).isEqualTo(type); - - testValue(record.getValue()); - } catch (AssertionError e) { - return false; - } - return true; - } - - protected void testValue(Object value) { - Assertions.assertThat(value).isInstanceOf(String.class) - .isEqualTo(expectedValue); - } - - public String description() { - return "record of type %s, with value %s"; - } - - public Object[] arguments() { - return new Object[] { type, expectedValue }; - } - -} +package org.openhab.binding.wmbus.device.techem.predicate; + +import org.assertj.core.api.Assertions; +import org.openhab.binding.wmbus.device.RecordPredicate; +import org.openhab.binding.wmbus.device.techem.Record; + +public class StringPredicate implements RecordPredicate { + + protected final Record.Type type; + protected final String expectedValue; + + public StringPredicate(Record.Type type, String expectedValue) { + this.type = type; + this.expectedValue = expectedValue; + } + + @Override + public boolean test(Record record) { + try { + Assertions.assertThat(record.getType()).isEqualTo(type); + + testValue(record.getValue()); + } catch (AssertionError e) { + return false; + } + return true; + } + + protected void testValue(Object value) { + Assertions.assertThat(value).isInstanceOf(String.class).isEqualTo(expectedValue); + } + + public String description() { + return "record of type %s, with value %s"; + } + + public Object[] arguments() { + return new Object[] { type, expectedValue }; + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/BaseUnitRegistryTest.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/BaseUnitRegistryTest.java index e792527..b3470ec 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/BaseUnitRegistryTest.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/BaseUnitRegistryTest.java @@ -1,393 +1,392 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.internal.units; - -import java.util.Optional; - -import javax.measure.Unit; - -import org.assertj.core.api.Assertions; -import org.eclipse.smarthome.core.library.unit.ImperialUnits; -import org.eclipse.smarthome.core.library.unit.SIUnits; -import org.eclipse.smarthome.core.library.unit.SmartHomeUnits; -import org.junit.Test; -import org.openhab.binding.wmbus.UnitRegistry; -import org.openmuc.jmbus.DlmsUnit; - -/** - * Test of unit conversion assuming just framework measure units registry. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public abstract class BaseUnitRegistryTest { - - protected final UnitRegistry registry; - - protected BaseUnitRegistryTest(UnitRegistry registry) { - this.registry = registry; - } - - @Test - public void testConversionOf_ampere() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.AMPERE)).hasValue(SmartHomeUnits.AMPERE); - } - - @Test - public void testConversionOf_ampere_hour() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.AMPERE_HOUR)).isEmpty(); - } - - @Test - public void testConversionOf_ampere_per_metre() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.AMPERE_PER_METRE)).isEmpty(); - } - - @Test - public void testConversionOf_ampere_squared_hours() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.AMPERE_SQUARED_HOURS)).isEmpty(); - } - - @Test - public void testConversionOf_ampere_squared_hour_meter_constant_or_pulse_value() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.AMPERE_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE)).isEmpty(); - } - - @Test - public void testConversionOf_apparent_energy_meter_constant_or_pulse_value() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.APPARENT_ENERGY_METER_CONSTANT_OR_PULSE_VALUE)).isEmpty(); - } - - @Test - public void testConversionOf_bar() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.BAR)).isEmpty(); - } - - @Test - public void testConversionOf_calorific_value() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.CALORIFIC_VALUE)).isEmpty(); - } - - @Test - public void testConversionOf_coulomb() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.COULOMB)).contains(SmartHomeUnits.COULOMB); - } - - @Test - public void testConversionOf_count() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.COUNT)).isEmpty(); - } - - @Test - public void testConversionOf_cubic_feet() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.CUBIC_FEET)).contains(ImperialUnits.CUBIC_FOOT); - } - - @Test - public void testConversionOf_cubic_metre_corrected() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE)).contains(SIUnits.CUBIC_METRE); - Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE_CORRECTED)).contains(SIUnits.CUBIC_METRE); - } - - @Test - public void testConversionOf_cubic_metre_per_day_corrected() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE_PER_DAY)).isEmpty(); - Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE_PER_DAY_CORRECTED)).isEmpty(); - } - - @Test - public void testConversionOf_cubic_metre_per_hour_corrected() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE_PER_HOUR)).isEmpty(); - Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE_PER_HOUR_CORRECTED)).isEmpty(); - } - - @Test - public void testConversionOf_cubic_metre_per_minute() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE_PER_MINUTE)).isEmpty(); - } - - @Test - public void testConversionOf_cubic_metre_per_second() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE_PER_SECOND)).isEmpty(); - } - - @Test - public void testConversionOf_currency() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.CURRENCY)).isEmpty(); - } - - @Test - public void testConversionOf_day() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.DAY)).contains(SmartHomeUnits.DAY); - } - - @Test - public void testConversionOf_degree() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.DEGREE)).isEmpty(); - } - - @Test - public void testConversionOf_degree_celsius() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.DEGREE_CELSIUS)).contains(SIUnits.CELSIUS); - } - - @Test - public void testConversionOf_degree_fahrenheit() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.DEGREE_FAHRENHEIT)).isEmpty(); - } - - @Test - public void testConversionOf_energy_per_volume() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.ENERGY_PER_VOLUME)).isEmpty(); - } - - @Test - public void testConversionOf_farad() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.FARAD)).contains(SmartHomeUnits.FARAD); - } - - @Test - public void testConversionOf_henry() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.HENRY)).contains(SmartHomeUnits.HENRY); - } - - @Test - public void testConversionOf_hertz() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.HERTZ)).contains(SmartHomeUnits.HERTZ); - } - - @Test - public void testConversionOf_hour() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.HOUR)).contains(SmartHomeUnits.HOUR); - } - - @Test - public void testConversionOf_joule() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.JOULE)).contains(SmartHomeUnits.JOULE); - } - - @Test - public void testConversionOf_joule_per_hour() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.JOULE_PER_HOUR)).isEmpty(); - } - - @Test - public void testConversionOf_kelvin() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.KELVIN)).contains(SmartHomeUnits.KELVIN); - } - - @Test - public void testConversionOf_kilogram() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.KILOGRAM)).contains(SIUnits.KILOGRAM); - } - - @Test - public void testConversionOf_kilogram_per_hour() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.KILOGRAM_PER_HOUR)).isEmpty(); - } - - @Test - public void testConversionOf_kilogram_per_second() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.KILOGRAM_PER_SECOND)).isEmpty(); - } - - @Test - public void testConversionOf_litre() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.LITRE)).contains(SmartHomeUnits.LITRE); - } - - @Test - public void testConversionOf_mass_density() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.MASS_DENSITY)).isEmpty(); - } - - @Test - public void testConversionOf_meter_constant_or_pulse_value() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.METER_CONSTANT_OR_PULSE_VALUE)).isEmpty(); - } - - @Test - public void testConversionOf_metre() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.METRE)).contains(SIUnits.METRE); - } - - @Test - public void testConversionOf_metre_per_second() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.METRE_PER_SECOND)).contains(SmartHomeUnits.METRE_PER_SECOND); - } - - @Test - public void testConversionOf_min() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.MIN)).contains(SmartHomeUnits.MINUTE); - } - - @Test - public void testConversionOf_mole_percent() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.MOLE_PERCENT)).isEmpty(); - } - - @Test - public void testConversionOf_month() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.MONTH)).contains(SmartHomeUnits.FARAD); - } - - @Test - public void testConversionOf_newton() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.NEWTON)).contains(SmartHomeUnits.NEWTON); - } - - @Test - public void testConversionOf_newtonmeter() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.NEWTONMETER)).isEmpty(); - } - - @Test - public void testConversionOf_ohm() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.OHM)).contains(SmartHomeUnits.OHM); - } - - @Test - public void testConversionOf_ohm_metre() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.OHM_METRE)).isEmpty(); - } - - @Test - public void testConversionOf_other_unit() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.OTHER_UNIT)).isEmpty(); - } - - @Test - public void testConversionOf_pascal() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.PASCAL)).contains(SIUnits.PASCAL); - } - - @Test - public void testConversionOf_pascal_second() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.PASCAL_SECOND)).isEmpty(); - } - - @Test - public void testConversionOf_percentage() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.PERCENTAGE)).isEmpty(); - } - - @Test - public void testConversionOf_reactive_energy_meter_constant_or_pulse_value() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.REACTIVE_ENERGY_METER_CONSTANT_OR_PULSE_VALUE)).isEmpty(); - } - - @Test - public void testConversionOf_reserved() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.RESERVED)).isEmpty(); - } - - @Test - public void testConversionOf_second() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.SECOND)).contains(SmartHomeUnits.SECOND); - } - - @Test - public void testConversionOf_signal_strength() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.SIGNAL_STRENGTH)).isEmpty(); - } - - @Test - public void testConversionOf_specific_energy() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.SPECIFIC_ENERGY)).isEmpty(); - } - - @Test - public void testConversionOf_tesla() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.TESLA)).contains(SmartHomeUnits.TESLA); - } - - @Test - public void testConversionOf_us_gallon() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.US_GALLON)).isEmpty(); - } - - @Test - public void testConversionOf_us_gallon_per_hour() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.US_GALLON_PER_HOUR)).isEmpty(); - } - - @Test - public void testConversionOf_us_gallon_per_minute() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.US_GALLON_PER_MINUTE)).isEmpty(); - } - - @Test - public void testConversionOf_var() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.VAR)).isEmpty(); - } - - @Test - public void testConversionOf_var_hour() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.VAR_HOUR)).isEmpty(); - } - - @Test - public void testConversionOf_volt() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.VOLT)).contains(SmartHomeUnits.VOLT); - } - - @Test - public void testConversionOf_volt_ampere() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.VOLT_AMPERE)).isEmpty(); - } - - @Test - public void testConversionOf_volt_ampere_hour() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.VOLT_AMPERE_HOUR)).isEmpty(); - } - - @Test - public void testConversionOf_volt_per_metre() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.VOLT_PER_METRE)).isEmpty(); - } - - @Test - public void testConversionOf_volt_squared_hours() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.VOLT_SQUARED_HOURS)).isEmpty(); - } - - @Test - public void testConversionOf_volt_squared_hour_meter_constant_or_pulse_value() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.VOLT_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE)).isEmpty(); - } - - @Test - public void testConversionOf_watt() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.WATT)).contains(SmartHomeUnits.WATT); - } - - @Test - public void testConversionOf_watt_hour() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.WATT_HOUR)).contains(SmartHomeUnits.WATT_HOUR); - } - - @Test - public void testConversionOf_weber() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.WEBER)).contains(SmartHomeUnits.WEBER); - } - - @Test - public void testConversionOf_week() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.WEEK)).contains(SmartHomeUnits.WEEK); - } - - @Test - public void testConversionOf_year() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.YEAR)).contains(SmartHomeUnits.YEAR); - } - - protected Optional> lookup(DlmsUnit wmbusType) { - return registry.lookup(wmbusType); - } - -} +/** + * Copyright (c) 2010-2018 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.wmbus.internal.units; + +import java.util.Optional; + +import javax.measure.Unit; + +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.openhab.binding.wmbus.UnitRegistry; +import org.openhab.core.library.unit.ImperialUnits; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.library.unit.Units; +import org.openmuc.jmbus.DlmsUnit; + +/** + * Test of unit conversion assuming just framework measure units registry. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public abstract class BaseUnitRegistryTest { + + protected final UnitRegistry registry; + + protected BaseUnitRegistryTest(UnitRegistry registry) { + this.registry = registry; + } + + @Test + public void testConversionOf_ampere() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.AMPERE)).hasValue(Units.AMPERE); + } + + @Test + public void testConversionOf_ampere_hour() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.AMPERE_HOUR)).isEmpty(); + } + + @Test + public void testConversionOf_ampere_per_metre() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.AMPERE_PER_METRE)).isEmpty(); + } + + @Test + public void testConversionOf_ampere_squared_hours() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.AMPERE_SQUARED_HOURS)).isEmpty(); + } + + @Test + public void testConversionOf_ampere_squared_hour_meter_constant_or_pulse_value() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.AMPERE_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE)).isEmpty(); + } + + @Test + public void testConversionOf_apparent_energy_meter_constant_or_pulse_value() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.APPARENT_ENERGY_METER_CONSTANT_OR_PULSE_VALUE)).isEmpty(); + } + + @Test + public void testConversionOf_bar() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.BAR)).isEmpty(); + } + + @Test + public void testConversionOf_calorific_value() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.CALORIFIC_VALUE)).isEmpty(); + } + + @Test + public void testConversionOf_coulomb() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.COULOMB)).contains(Units.COULOMB); + } + + @Test + public void testConversionOf_count() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.COUNT)).isEmpty(); + } + + @Test + public void testConversionOf_cubic_feet() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.CUBIC_FEET)).contains(ImperialUnits.CUBIC_FOOT); + } + + @Test + public void testConversionOf_cubic_metre_corrected() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE)).contains(SIUnits.CUBIC_METRE); + Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE_CORRECTED)).contains(SIUnits.CUBIC_METRE); + } + + @Test + public void testConversionOf_cubic_metre_per_day_corrected() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE_PER_DAY)).isEmpty(); + Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE_PER_DAY_CORRECTED)).isEmpty(); + } + + @Test + public void testConversionOf_cubic_metre_per_hour_corrected() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE_PER_HOUR)).isEmpty(); + Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE_PER_HOUR_CORRECTED)).isEmpty(); + } + + @Test + public void testConversionOf_cubic_metre_per_minute() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE_PER_MINUTE)).isEmpty(); + } + + @Test + public void testConversionOf_cubic_metre_per_second() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE_PER_SECOND)).isEmpty(); + } + + @Test + public void testConversionOf_currency() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.CURRENCY)).isEmpty(); + } + + @Test + public void testConversionOf_day() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.DAY)).contains(Units.DAY); + } + + @Test + public void testConversionOf_degree() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.DEGREE)).isEmpty(); + } + + @Test + public void testConversionOf_degree_celsius() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.DEGREE_CELSIUS)).contains(SIUnits.CELSIUS); + } + + @Test + public void testConversionOf_degree_fahrenheit() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.DEGREE_FAHRENHEIT)).isEmpty(); + } + + @Test + public void testConversionOf_energy_per_volume() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.ENERGY_PER_VOLUME)).isEmpty(); + } + + @Test + public void testConversionOf_farad() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.FARAD)).contains(Units.FARAD); + } + + @Test + public void testConversionOf_henry() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.HENRY)).contains(Units.HENRY); + } + + @Test + public void testConversionOf_hertz() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.HERTZ)).contains(Units.HERTZ); + } + + @Test + public void testConversionOf_hour() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.HOUR)).contains(Units.HOUR); + } + + @Test + public void testConversionOf_joule() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.JOULE)).contains(Units.JOULE); + } + + @Test + public void testConversionOf_joule_per_hour() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.JOULE_PER_HOUR)).isEmpty(); + } + + @Test + public void testConversionOf_kelvin() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.KELVIN)).contains(Units.KELVIN); + } + + @Test + public void testConversionOf_kilogram() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.KILOGRAM)).contains(SIUnits.KILOGRAM); + } + + @Test + public void testConversionOf_kilogram_per_hour() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.KILOGRAM_PER_HOUR)).isEmpty(); + } + + @Test + public void testConversionOf_kilogram_per_second() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.KILOGRAM_PER_SECOND)).isEmpty(); + } + + @Test + public void testConversionOf_litre() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.LITRE)).contains(Units.LITRE); + } + + @Test + public void testConversionOf_mass_density() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.MASS_DENSITY)).isEmpty(); + } + + @Test + public void testConversionOf_meter_constant_or_pulse_value() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.METER_CONSTANT_OR_PULSE_VALUE)).isEmpty(); + } + + @Test + public void testConversionOf_metre() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.METRE)).contains(SIUnits.METRE); + } + + @Test + public void testConversionOf_metre_per_second() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.METRE_PER_SECOND)).contains(Units.METRE_PER_SECOND); + } + + @Test + public void testConversionOf_min() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.MIN)).contains(Units.MINUTE); + } + + @Test + public void testConversionOf_mole_percent() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.MOLE_PERCENT)).isEmpty(); + } + + @Test + public void testConversionOf_month() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.MONTH)).contains(Units.FARAD); + } + + @Test + public void testConversionOf_newton() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.NEWTON)).contains(Units.NEWTON); + } + + @Test + public void testConversionOf_newtonmeter() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.NEWTONMETER)).isEmpty(); + } + + @Test + public void testConversionOf_ohm() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.OHM)).contains(Units.OHM); + } + + @Test + public void testConversionOf_ohm_metre() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.OHM_METRE)).isEmpty(); + } + + @Test + public void testConversionOf_other_unit() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.OTHER_UNIT)).isEmpty(); + } + + @Test + public void testConversionOf_pascal() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.PASCAL)).contains(SIUnits.PASCAL); + } + + @Test + public void testConversionOf_pascal_second() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.PASCAL_SECOND)).isEmpty(); + } + + @Test + public void testConversionOf_percentage() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.PERCENTAGE)).isEmpty(); + } + + @Test + public void testConversionOf_reactive_energy_meter_constant_or_pulse_value() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.REACTIVE_ENERGY_METER_CONSTANT_OR_PULSE_VALUE)).isEmpty(); + } + + @Test + public void testConversionOf_reserved() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.RESERVED)).isEmpty(); + } + + @Test + public void testConversionOf_second() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.SECOND)).contains(Units.SECOND); + } + + @Test + public void testConversionOf_signal_strength() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.SIGNAL_STRENGTH)).isEmpty(); + } + + @Test + public void testConversionOf_specific_energy() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.SPECIFIC_ENERGY)).isEmpty(); + } + + @Test + public void testConversionOf_tesla() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.TESLA)).contains(Units.TESLA); + } + + @Test + public void testConversionOf_us_gallon() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.US_GALLON)).isEmpty(); + } + + @Test + public void testConversionOf_us_gallon_per_hour() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.US_GALLON_PER_HOUR)).isEmpty(); + } + + @Test + public void testConversionOf_us_gallon_per_minute() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.US_GALLON_PER_MINUTE)).isEmpty(); + } + + @Test + public void testConversionOf_var() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.VAR)).isEmpty(); + } + + @Test + public void testConversionOf_var_hour() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.VAR_HOUR)).isEmpty(); + } + + @Test + public void testConversionOf_volt() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.VOLT)).contains(Units.VOLT); + } + + @Test + public void testConversionOf_volt_ampere() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.VOLT_AMPERE)).isEmpty(); + } + + @Test + public void testConversionOf_volt_ampere_hour() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.VOLT_AMPERE_HOUR)).isEmpty(); + } + + @Test + public void testConversionOf_volt_per_metre() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.VOLT_PER_METRE)).isEmpty(); + } + + @Test + public void testConversionOf_volt_squared_hours() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.VOLT_SQUARED_HOURS)).isEmpty(); + } + + @Test + public void testConversionOf_volt_squared_hour_meter_constant_or_pulse_value() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.VOLT_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE)).isEmpty(); + } + + @Test + public void testConversionOf_watt() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.WATT)).contains(Units.WATT); + } + + @Test + public void testConversionOf_watt_hour() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.WATT_HOUR)).contains(Units.WATT_HOUR); + } + + @Test + public void testConversionOf_weber() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.WEBER)).contains(Units.WEBER); + } + + @Test + public void testConversionOf_week() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.WEEK)).contains(Units.WEEK); + } + + @Test + public void testConversionOf_year() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.YEAR)).contains(Units.YEAR); + } + + protected Optional> lookup(DlmsUnit wmbusType) { + return registry.lookup(wmbusType); + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/CompositeUnitRegistryTest.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/CompositeUnitRegistryTest.java index 6a9be2d..10d41f0 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/CompositeUnitRegistryTest.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/CompositeUnitRegistryTest.java @@ -1,24 +1,23 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.internal.units; - -/** - * Test of {@link CompositeUnitRegistry} with {@link SmartHomeUnitsRegistry}. - * - * They should behave in same way together. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public class CompositeUnitRegistryTest extends BaseUnitRegistryTest { - - public CompositeUnitRegistryTest() { - super(new CompositeUnitRegistry(new SmartHomeUnitsRegistry())); - } - -} +/** + * Copyright (c) 2010-2018 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.wmbus.internal.units; + +/** + * Test of {@link CompositeUnitRegistry} with {@link UnitsRegistry}. + * + * They should behave in same way together. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public class CompositeUnitRegistryTest extends BaseUnitRegistryTest { + + public CompositeUnitRegistryTest() { + super(new CompositeUnitRegistry(new UnitsRegistry())); + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/ExtendedCompositeUnitRegistryTest.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/ExtendedCompositeUnitRegistryTest.java index 2431654..13fa31c 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/ExtendedCompositeUnitRegistryTest.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/ExtendedCompositeUnitRegistryTest.java @@ -1,59 +1,59 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.internal.units; - -import java.util.Optional; - -import javax.measure.Quantity; -import javax.measure.Unit; - -import org.assertj.core.api.Assertions; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.smarthome.core.library.unit.SmartHomeUnits; -import org.openhab.binding.wmbus.UnitRegistry; -import org.openmuc.jmbus.DlmsUnit; - -/** - * Test of {@link CompositeUnitRegistry} with {@link SmartHomeUnitsRegistry} and specific extension which provides - * support for {@link DlmsUnit#COUNT} unit. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public class ExtendedCompositeUnitRegistryTest extends BaseUnitRegistryTest { - - public ExtendedCompositeUnitRegistryTest() { - super(new CompositeUnitRegistry(new SmartHomeUnitsRegistry(), new CountUnitRegistry())); - } - - @Override - public void testConversionOf_count() throws Exception { - try { - super.testConversionOf_count(); - } catch (AssertionError e) { - Assertions.assertThat(registry.lookup(DlmsUnit.COUNT)).contains(SmartHomeUnits.ONE); - } - } - - static class CountUnitRegistry implements UnitRegistry { - - @Override - public Optional> lookup(DlmsUnit wmbusType) { - if (DlmsUnit.COUNT.equals(wmbusType)) { - return Optional.of(SmartHomeUnits.ONE); - } - - return Optional.empty(); - } - - @Override - public Optional>> quantity(@Nullable DlmsUnit wmbusType) { - return Optional.empty(); - } - } -} +/** + * Copyright (c) 2010-2018 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.wmbus.internal.units; + +import java.util.Optional; + +import javax.measure.Quantity; +import javax.measure.Unit; + +import org.assertj.core.api.Assertions; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.wmbus.UnitRegistry; +import org.openhab.core.library.unit.Units; +import org.openmuc.jmbus.DlmsUnit; + +/** + * Test of {@link CompositeUnitRegistry} with {@link UnitsRegistry} and specific extension which provides + * support for {@link DlmsUnit#COUNT} unit. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public class ExtendedCompositeUnitRegistryTest extends BaseUnitRegistryTest { + + public ExtendedCompositeUnitRegistryTest() { + super(new CompositeUnitRegistry(new UnitsRegistry(), new CountUnitRegistry())); + } + + @Override + public void testConversionOf_count() throws Exception { + try { + super.testConversionOf_count(); + } catch (AssertionError e) { + Assertions.assertThat(registry.lookup(DlmsUnit.COUNT)).contains(Units.ONE); + } + } + + static class CountUnitRegistry implements UnitRegistry { + + @Override + public Optional> lookup(DlmsUnit wmbusType) { + if (DlmsUnit.COUNT.equals(wmbusType)) { + return Optional.of(Units.ONE); + } + + return Optional.empty(); + } + + @Override + public Optional>> quantity(@Nullable DlmsUnit wmbusType) { + return Optional.empty(); + } + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/SmartHomeUnitsRegistryTest.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/UnitsRegistryTest.java similarity index 75% rename from org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/SmartHomeUnitsRegistryTest.java rename to org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/UnitsRegistryTest.java index a201b61..8742790 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/SmartHomeUnitsRegistryTest.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/UnitsRegistryTest.java @@ -1,22 +1,21 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.internal.units; - -/** - * Test of standard framework unit lookup. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public class SmartHomeUnitsRegistryTest extends BaseUnitRegistryTest { - - public SmartHomeUnitsRegistryTest() { - super(new SmartHomeUnitsRegistry()); - } - -} +/** + * Copyright (c) 2010-2018 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.wmbus.internal.units; + +/** + * Test of standard framework unit lookup. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public class UnitsRegistryTest extends BaseUnitRegistryTest { + + public UnitsRegistryTest() { + super(new UnitsRegistry()); + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/io/transport/mbus/wireless/MapKeyStorageTest.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/io/transport/mbus/wireless/MapKeyStorageTest.java index 1b6e82d..8756033 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/io/transport/mbus/wireless/MapKeyStorageTest.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/io/transport/mbus/wireless/MapKeyStorageTest.java @@ -1,46 +1,45 @@ -package org.openhab.io.transport.mbus.wireless; - -import org.assertj.core.api.Assertions; -import org.eclipse.smarthome.core.util.HexUtils; -import org.junit.Test; -import org.openhab.io.transport.mbus.wireless.MapKeyStorage; -import org.openmuc.jmbus.SecondaryAddress; - -/** - * A basic test of encryption key lookups. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public class MapKeyStorageTest { - - private static final String ADDRESS_HEX = "2423870723421147"; - private static final byte[] ADDRESS_BYTE = HexUtils.hexToBytes(ADDRESS_HEX); - private static final SecondaryAddress ADDRESS_OBJECT = SecondaryAddress.newFromWMBusLlHeader(ADDRESS_BYTE, 0); - private static final byte[] KEY = new byte[] { 0x01, 0x02 }; - - private final MapKeyStorage storage = new MapKeyStorage(); - - @Test - public void testNoKey() { - miss(ADDRESS_BYTE, ADDRESS_OBJECT); - } - - @Test - public void tesKeyUpdate() { - testNoKey(); - - storage.registerKey(ADDRESS_BYTE, KEY); - - hit(ADDRESS_BYTE, ADDRESS_OBJECT, KEY); - } - - protected void miss(byte[] byteForm, SecondaryAddress objectForm) { - Assertions.assertThat(storage.lookupKey(byteForm)).isNotNull().isEmpty(); - Assertions.assertThat(storage.toMap().get(objectForm)).isNull(); - } - - protected void hit(byte[] byteForm, SecondaryAddress objectForm, byte[] key) { - Assertions.assertThat(storage.lookupKey(byteForm)).isNotNull().isNotEmpty().hasValue(key); - Assertions.assertThat(storage.toMap().get(objectForm)).isNotNull().isEqualTo(key); - } -} +package org.openhab.io.transport.mbus.wireless; + +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.openhab.core.util.HexUtils; +import org.openmuc.jmbus.SecondaryAddress; + +/** + * A basic test of encryption key lookups. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public class MapKeyStorageTest { + + private static final String ADDRESS_HEX = "2423870723421147"; + private static final byte[] ADDRESS_BYTE = HexUtils.hexToBytes(ADDRESS_HEX); + private static final SecondaryAddress ADDRESS_OBJECT = SecondaryAddress.newFromWMBusLlHeader(ADDRESS_BYTE, 0); + private static final byte[] KEY = new byte[] { 0x01, 0x02 }; + + private final MapKeyStorage storage = new MapKeyStorage(); + + @Test + public void testNoKey() { + miss(ADDRESS_BYTE, ADDRESS_OBJECT); + } + + @Test + public void tesKeyUpdate() { + testNoKey(); + + storage.registerKey(ADDRESS_BYTE, KEY); + + hit(ADDRESS_BYTE, ADDRESS_OBJECT, KEY); + } + + protected void miss(byte[] byteForm, SecondaryAddress objectForm) { + Assertions.assertThat(storage.lookupKey(byteForm)).isNotNull().isEmpty(); + Assertions.assertThat(storage.toMap().get(objectForm)).isNull(); + } + + protected void hit(byte[] byteForm, SecondaryAddress objectForm, byte[] key) { + Assertions.assertThat(storage.lookupKey(byteForm)).isNotNull().isNotEmpty().hasValue(key); + Assertions.assertThat(storage.toMap().get(objectForm)).isNotNull().isEqualTo(key); + } +} From 05c3150bad6b3b97ca9554e49b256689a5cfccd1 Mon Sep 17 00:00:00 2001 From: Michael Weger Date: Sun, 7 Mar 2021 20:35:26 +0100 Subject: [PATCH 4/8] - OH3 migration of org.openhab.binding.wmbus.tools - dos2unix lf --- org.openhab.binding.wmbus.tools/pom.xml | 128 +- .../binding/wmbus/tools/CollectorServlet.java | 2 +- .../binding/wmbus/tools/InjectorServlet.java | 32 +- .../wmbus/tools/LoggingMessageListener.java | 4 +- .../tools/processor/CalculateLength.java | 17 +- .../wmbus/tools/processor/Processors.java | 25 +- .../tools/processor/RecalculateLength.java | 67 +- .../wmbus/tools/processor/RssiProcessor.java | 44 +- .../tools/processor/SkipCrcProcessor.java | 76 +- .../wmbus/tools/processor/SkipProcessor.java | 22 +- .../processor/RecalculateLengthTest.java | 41 +- .../tools/processor/SkipCrcProcessorTest.java | 53 +- org.openhab.binding.wmbus/pom.xml | 105 +- .../binding/wmbus/BindingConfiguration.java | 50 +- .../org/openhab/binding/wmbus/RecordType.java | 210 +- .../openhab/binding/wmbus/UnitRegistry.java | 94 +- .../binding/wmbus/WMBusBindingConstants.java | 256 +- .../wmbus/WMBusCompanyIdentifiers.java | 110 +- .../openhab/binding/wmbus/WMBusDevice.java | 238 +- .../binding/wmbus/config/DateFieldMode.java | 54 +- .../binding/wmbus/config/StickModel.java | 56 +- .../wmbus/config/WMBusBridgeConfig.java | 70 +- .../wmbus/config/WMBusSerialBridgeConfig.java | 54 +- .../AbstractWMBusDiscoveryParticipant.java | 114 +- .../openhab/binding/wmbus/device/Meter.java | 96 +- .../binding/wmbus/device/UnknownMeter.java | 154 +- .../generic/DynamicWMBusThingHandler.java | 300 +- .../generic/GenericWMBusThingHandler.java | 154 +- .../device/itron/ItronBindingConstants.java | 170 +- .../itron/ItronConfigStatusDataParser.java | 158 +- .../itron/ItronDiscoveryParticipant.java | 222 +- .../device/itron/ItronHandlerFactory.java | 154 +- .../itron/ItronManufacturerDataParser.java | 162 +- .../itron/ItronSmokeDetectorHandler.java | 354 +-- .../binding/wmbus/device/techem/Record.java | 130 +- .../device/techem/TechemBindingConstants.java | 428 +-- .../wmbus/device/techem/TechemDevice.java | 124 +- .../techem/TechemDiscoveryParticipant.java | 304 +- .../device/techem/TechemHandlerFactory.java | 204 +- .../techem/TechemHeatCostAllocator.java | 64 +- .../wmbus/device/techem/TechemHeatMeter.java | 64 +- .../device/techem/TechemSmokeDetector.java | 64 +- .../device/techem/TechemUnknownDevice.java | 62 +- .../wmbus/device/techem/TechemWaterMeter.java | 66 +- .../binding/wmbus/device/techem/Variant.java | 138 +- .../decoder/AbstractTechemFrameDecoder.java | 234 +- .../wmbus/device/techem/decoder/Buffer.java | 336 +- .../decoder/CompositeTechemFrameDecoder.java | 164 +- .../device/techem/decoder/DebugBuffer.java | 228 +- .../techem/decoder/TechemFrameDecoder.java | 46 +- .../techem/decoder/TechemHKVFrameDecoder.java | 130 +- .../TechemHKVRoomTempFrameDecoder.java | 158 +- .../decoder/TechemHeatMeterFrameDecoder.java | 166 +- .../TechemSmokeDetectorFrameDecoder.java | 108 +- .../TechemVariantFrameDecoderSelector.java | 142 +- .../TechemVersionFrameDecoderSelector.java | 82 +- .../decoder/TechemWaterMeterFrameDecoder.java | 144 +- .../techem/handler/TechemMeterHandler.java | 310 +- .../discovery/CompositeMessageListener.java | 120 +- .../wmbus/discovery/DebugMessageListener.java | 142 +- .../discovery/WMBusDiscoveryParticipant.java | 116 +- .../handler/VirtualWMBusBridgeHandler.java | 174 +- .../binding/wmbus/handler/WMBusAdapter.java | 72 +- .../wmbus/handler/WMBusBridgeHandler.java | 416 +-- .../wmbus/handler/WMBusBridgeHandlerBase.java | 560 ++-- .../wmbus/handler/WMBusDeviceHandler.java | 628 ++-- .../wmbus/handler/WMBusMessageListener.java | 78 +- .../internal/DynamicBindingConfiguration.java | 188 +- .../binding/wmbus/internal/HexConverter.java | 68 +- .../internal/WMBusChannelTypeProvider.java | 504 +-- .../wmbus/internal/WMBusException.java | 54 +- .../wmbus/internal/WMBusHandlerFactory.java | 292 +- .../binding/wmbus/internal/WMBusReceiver.java | 198 +- .../discovery/WMBusDiscoveryService.java | 308 +- .../discovery/WMBusDiscoveryService2.java | 410 +-- .../internal/units/CompositeUnitRegistry.java | 200 +- .../wmbus/internal/units/UnitsRegistry.java | 722 ++--- .../mbus/wireless/FilteredKeyStorage.java | 132 +- .../transport/mbus/wireless/KeyStorage.java | 66 +- .../mbus/wireless/MapKeyStorage.java | 102 +- .../main/java/org/openmuc/jmbus/AesCrypt.java | 136 +- .../src/main/java/org/openmuc/jmbus/Bcd.java | 182 +- .../main/java/org/openmuc/jmbus/CRC16.java | 112 +- .../java/org/openmuc/jmbus/DataRecord.java | 2762 ++++++++--------- .../org/openmuc/jmbus/DecodingException.java | 52 +- .../java/org/openmuc/jmbus/DeviceType.java | 244 +- .../main/java/org/openmuc/jmbus/DlmsUnit.java | 292 +- .../org/openmuc/jmbus/EncryptionMode.java | 198 +- .../org/openmuc/jmbus/MBusConnection.java | 1044 +++---- .../java/org/openmuc/jmbus/MBusMessage.java | 262 +- .../openmuc/jmbus/ScanSecondaryAddress.java | 436 +-- .../org/openmuc/jmbus/SecondaryAddress.java | 448 +-- .../jmbus/SecondaryAddressListener.java | 60 +- .../openmuc/jmbus/VariableDataStructure.java | 898 +++--- .../org/openmuc/jmbus/VerboseMessage.java | 96 +- .../openmuc/jmbus/VerboseMessageListener.java | 40 +- .../java/org/openmuc/jmbus/package-info.java | 22 +- .../openmuc/jmbus/transportlayer/Builder.java | 128 +- .../jmbus/transportlayer/SerialBuilder.java | 218 +- .../jmbus/transportlayer/SerialLayer.java | 146 +- .../jmbus/transportlayer/TcpBuilder.java | 146 +- .../jmbus/transportlayer/TcpLayer.java | 196 +- .../jmbus/transportlayer/TransportLayer.java | 144 +- .../jmbus/transportlayer/package-info.java | 18 +- .../wireless/AbstractWMBusConnection.java | 264 +- .../jmbus/wireless/HciMessageException.java | 32 +- .../jmbus/wireless/MessageReceiver.java | 104 +- .../wireless/VirtualWMBusMessageHelper.java | 64 +- .../jmbus/wireless/WMBusConnection.java | 344 +- .../jmbus/wireless/WMBusConnectionAmber.java | 444 +-- .../jmbus/wireless/WMBusConnectionImst.java | 738 ++--- .../wireless/WMBusConnectionRadioCrafts.java | 458 +-- .../openmuc/jmbus/wireless/WMBusListener.java | 78 +- .../openmuc/jmbus/wireless/WMBusMessage.java | 258 +- .../org/openmuc/jmbus/wireless/WMBusMode.java | 48 +- .../openmuc/jmbus/wireless/package-info.java | 24 +- .../main/java/org/openmuc/jrxtx/DataBits.java | 72 +- .../java/org/openmuc/jrxtx/FlowControl.java | 58 +- .../java/org/openmuc/jrxtx/JRxTxPort.java | 680 ++-- .../main/java/org/openmuc/jrxtx/Parity.java | 108 +- .../openmuc/jrxtx/PortNotFoundException.java | 40 +- .../java/org/openmuc/jrxtx/SerialPort.java | 344 +- .../org/openmuc/jrxtx/SerialPortBuilder.java | 284 +- .../openmuc/jrxtx/SerialPortException.java | 46 +- .../jrxtx/SerialPortTimeoutException.java | 50 +- .../main/java/org/openmuc/jrxtx/StopBits.java | 64 +- .../wmbus/device/AbstractWMBusTest.java | 108 +- .../binding/wmbus/device/RecordPredicate.java | 54 +- .../generic/GenericWMBusThingHandlerTest.java | 254 +- .../ItronConfigStatusDataParserTest.java | 62 +- .../ItronManufacturerDataParserTest.java | 82 +- .../device/techem/TechemDecoderTest.java | 466 +-- .../device/techem/TechemDiscoveryTest.java | 218 +- .../techem/TechemHandlerFactoryTest.java | 148 +- .../techem/predicate/FloatPredicate.java | 80 +- .../techem/predicate/IntegerPredicate.java | 82 +- .../techem/predicate/LocalDatePredicate.java | 92 +- .../techem/predicate/QuantityPredicate.java | 72 +- .../techem/predicate/RssiPredicate.java | 28 +- .../techem/predicate/StringPredicate.java | 80 +- .../internal/units/BaseUnitRegistryTest.java | 784 ++--- .../units/CompositeUnitRegistryTest.java | 46 +- .../ExtendedCompositeUnitRegistryTest.java | 118 +- .../internal/units/UnitsRegistryTest.java | 42 +- .../mbus/wireless/MapKeyStorageTest.java | 90 +- pom.xml | 193 +- 146 files changed, 14604 insertions(+), 14669 deletions(-) diff --git a/org.openhab.binding.wmbus.tools/pom.xml b/org.openhab.binding.wmbus.tools/pom.xml index ec7fbb1..8585848 100644 --- a/org.openhab.binding.wmbus.tools/pom.xml +++ b/org.openhab.binding.wmbus.tools/pom.xml @@ -1,75 +1,75 @@ - - 4.0.0 + + 4.0.0 - - org.openhab.binding - wmbus - 2.5.0-SNAPSHOT - + + org.openhab.addons.bundles + wmbus + 3.1.0-SNAPSHOT + - org.openhab.binding - org.openhab.binding.wmbus.tools + org.openhab.addons.bundles + org.openhab.addons.bundles.wmbus.tools - WMBus Binding Tools - - - false - + WMBus Binding Tools - - - org.openhab.binding - org.openhab.binding.wmbus - ${project.version} - + + false + - - com.google.guava - guava - 20.0 - provided - + + + org.openhab.addons.bundles + org.openhab.addons.bundles.wmbus + ${project.version} + - - org.openhab.core.bom - org.openhab.core.bom.compile - pom - provided - - - org.openhab.core.bom - org.openhab.core.bom.openhab-core - pom - provided - + + com.google.guava + guava + 20.0 + provided + - - junit - junit - 4.12 - test - - - org.assertj - assertj-core - 3.11.1 - test - - - org.slf4j - slf4j-simple - 1.7.2 - test - - - org.mockito - mockito-core - 2.23.0 - test - + + org.openhab.core.bom + org.openhab.core.bom.compile + pom + provided + + + org.openhab.core.bom + org.openhab.core.bom.openhab-core + pom + provided + - + + junit + junit + 4.12 + test + + + org.assertj + assertj-core + 3.11.1 + test + + + org.slf4j + slf4j-simple + 1.7.2 + test + + + org.mockito + mockito-core + 2.23.0 + test + + + diff --git a/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/CollectorServlet.java b/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/CollectorServlet.java index 855d77e..e2662ec 100644 --- a/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/CollectorServlet.java +++ b/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/CollectorServlet.java @@ -26,10 +26,10 @@ import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.IOUtils; -import org.eclipse.smarthome.core.util.HexUtils; import org.openhab.binding.wmbus.WMBusDevice; import org.openhab.binding.wmbus.handler.WMBusAdapter; import org.openhab.binding.wmbus.handler.WMBusMessageListener; +import org.openhab.core.util.HexUtils; import org.openmuc.jmbus.SecondaryAddress; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; diff --git a/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/InjectorServlet.java b/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/InjectorServlet.java index df803cf..ffd379b 100644 --- a/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/InjectorServlet.java +++ b/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/InjectorServlet.java @@ -8,14 +8,25 @@ */ package org.openhab.binding.wmbus.tools; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.*; +import java.util.stream.Collectors; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + import org.apache.commons.io.IOUtils; -import org.eclipse.smarthome.core.thing.Thing; -import org.eclipse.smarthome.core.thing.ThingRegistry; -import org.eclipse.smarthome.core.util.HexUtils; import org.openhab.binding.wmbus.WMBusBindingConstants; import org.openhab.binding.wmbus.WMBusDevice; import org.openhab.binding.wmbus.handler.WMBusAdapter; import org.openhab.binding.wmbus.tools.processor.*; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingRegistry; +import org.openhab.core.util.HexUtils; import org.openmuc.jmbus.DecodingException; import org.openmuc.jmbus.wireless.VirtualWMBusMessageHelper; import org.openmuc.jmbus.wireless.WMBusMessage; @@ -28,16 +39,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintWriter; -import java.util.*; -import java.util.stream.Collectors; - /** * Very basic servlet which allows to send a test frame to deployed binding. * @@ -109,8 +110,8 @@ private void inject(WMBusAdapter adapter, String frames, HttpServletRequest req, boolean stripCRC = Optional.ofNullable(req.getParameter("stripCRC")).map(value -> Boolean.TRUE).orElse(false); boolean calculateLength = Optional.ofNullable(req.getParameter("calculateLength")).map(value -> Boolean.TRUE) .orElse(false); - boolean recalculateLength = Optional.ofNullable(req.getParameter("recalculateLength")).map(value -> Boolean.TRUE) - .orElse(false); + boolean recalculateLength = Optional.ofNullable(req.getParameter("recalculateLength")) + .map(value -> Boolean.TRUE).orElse(false); List> processors = new ArrayList<>(); processors.add(new RssiProcessor(rssiIndex, rssiValue)); @@ -184,5 +185,4 @@ public void activate() throws ServletException, NamespaceException { public void deactivate() { httpService.unregister("/wmbus"); } - } diff --git a/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/LoggingMessageListener.java b/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/LoggingMessageListener.java index c2787f6..7cb5dfb 100644 --- a/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/LoggingMessageListener.java +++ b/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/LoggingMessageListener.java @@ -8,11 +8,10 @@ */ package org.openhab.binding.wmbus.tools; -import org.eclipse.smarthome.core.util.HexUtils; import org.openhab.binding.wmbus.WMBusDevice; -import org.openmuc.jmbus.wireless.WMBusMessage; import org.openhab.binding.wmbus.handler.WMBusAdapter; import org.openhab.binding.wmbus.handler.WMBusMessageListener; +import org.openhab.core.util.HexUtils; import org.osgi.service.component.annotations.Component; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,5 +39,4 @@ public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { private void log(WMBusDevice device) { logger.debug(HexUtils.bytesToHex(device.getOriginalMessage().asBlob())); } - } diff --git a/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/processor/CalculateLength.java b/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/processor/CalculateLength.java index 2979463..960dcf8 100644 --- a/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/processor/CalculateLength.java +++ b/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/processor/CalculateLength.java @@ -8,17 +8,16 @@ */ package org.openhab.binding.wmbus.tools.processor; -import org.openhab.binding.wmbus.tools.Processor; - import java.util.Map; -public class CalculateLength implements Processor { +import org.openhab.binding.wmbus.tools.Processor; - @Override - public String process(String frame, Map context) { - // remember of hex notation which doubles length - Integer len = frame.length() / 2; - return Integer.toHexString(len) + frame; - } +public class CalculateLength implements Processor { + @Override + public String process(String frame, Map context) { + // remember of hex notation which doubles length + Integer len = frame.length() / 2; + return Integer.toHexString(len) + frame; + } } diff --git a/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/processor/Processors.java b/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/processor/Processors.java index 4e1e297..1092dc5 100644 --- a/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/processor/Processors.java +++ b/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/processor/Processors.java @@ -8,11 +8,11 @@ */ package org.openhab.binding.wmbus.tools.processor; -import org.openhab.binding.wmbus.tools.Processor; - import java.util.List; import java.util.Map; +import org.openhab.binding.wmbus.tools.Processor; + /** * Helper type to orchestrate execution of several frame processors at once. * @@ -20,17 +20,16 @@ */ public class Processors { - public static T process(final T value, Map context, List> processors) { - if (processors.isEmpty()) { - return value; - } - - T result = value; - for (Processor processor : processors) { - result = processor.process(result, context); - } + public static T process(final T value, Map context, List> processors) { + if (processors.isEmpty()) { + return value; + } - return result; - } + T result = value; + for (Processor processor : processors) { + result = processor.process(result, context); + } + return result; + } } diff --git a/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/processor/RecalculateLength.java b/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/processor/RecalculateLength.java index 6f87e13..f1098ba 100644 --- a/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/processor/RecalculateLength.java +++ b/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/processor/RecalculateLength.java @@ -8,45 +8,44 @@ */ package org.openhab.binding.wmbus.tools.processor; -import org.openhab.binding.wmbus.tools.Processor; - import java.util.Map; +import org.openhab.binding.wmbus.tools.Processor; + public class RecalculateLength implements Processor { - @Override - public String process(String frame, Map context) { - int inputByteLength = frame.length() / 2; - - String newFrame = ""; - Integer numberOfBlocks; - int ciField = parseInt(frame, 20, 22); - - if (ciField == 0x7A){ - numberOfBlocks = (inputByteLength-14) / 16; - inputByteLength = 14 + numberOfBlocks*16; - newFrame += Integer.toHexString(inputByteLength); - newFrame += frame.substring(2, 26); - newFrame += Integer.toHexString(numberOfBlocks*16); - newFrame += frame.substring(28, (inputByteLength * 2) + 2); - return newFrame; + @Override + public String process(String frame, Map context) { + int inputByteLength = frame.length() / 2; + + String newFrame = ""; + Integer numberOfBlocks; + int ciField = parseInt(frame, 20, 22); + + if (ciField == 0x7A) { + numberOfBlocks = (inputByteLength - 14) / 16; + inputByteLength = 14 + numberOfBlocks * 16; + newFrame += Integer.toHexString(inputByteLength); + newFrame += frame.substring(2, 26); + newFrame += Integer.toHexString(numberOfBlocks * 16); + newFrame += frame.substring(28, (inputByteLength * 2) + 2); + return newFrame; + } + + if (ciField == 0x72) { + numberOfBlocks = (inputByteLength - 22) / 16; + inputByteLength = 22 + numberOfBlocks * 16; + newFrame += Integer.toHexString(inputByteLength); + newFrame += frame.substring(2, 42); + newFrame += Integer.toHexString(numberOfBlocks * 16); + newFrame += frame.substring(44, (inputByteLength * 2) + 2); + return newFrame; + } + + return newFrame; } - if (ciField == 0x72){ - numberOfBlocks = (inputByteLength-22) / 16; - inputByteLength = 22 + numberOfBlocks*16; - newFrame += Integer.toHexString(inputByteLength); - newFrame += frame.substring(2, 42); - newFrame += Integer.toHexString(numberOfBlocks*16); - newFrame += frame.substring(44, (inputByteLength * 2) + 2); - return newFrame; + private int parseInt(String frame, int startIndex, int endIndex) { + return Integer.parseInt(frame.substring(startIndex, endIndex), 16); } - - return newFrame; - } - - private int parseInt(String frame, int startIndex, int endIndex) { - return Integer.parseInt(frame.substring(startIndex, endIndex), 16); - } - } diff --git a/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/processor/RssiProcessor.java b/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/processor/RssiProcessor.java index db16a57..98a5875 100644 --- a/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/processor/RssiProcessor.java +++ b/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/processor/RssiProcessor.java @@ -8,33 +8,33 @@ */ package org.openhab.binding.wmbus.tools.processor; -import org.openhab.binding.wmbus.tools.Processor; - import java.util.Map; -public class RssiProcessor implements Processor { +import org.openhab.binding.wmbus.tools.Processor; - private final int rssiIndex; - private final int rssiValue; +public class RssiProcessor implements Processor { - public RssiProcessor(int rssiIndex, int rssiValue) { - this.rssiIndex = rssiIndex; - this.rssiValue = rssiValue; - } + private final int rssiIndex; + private final int rssiValue; - @Override - public String process(String frame, Map context) { - if (rssiIndex != 0) { - if (rssiIndex == 1) { // first byte starts from character at index 0. - context.put(RSSI, Integer.parseUnsignedInt(frame.substring(0, 2), 16)); - return frame.substring(2); - } else if (rssiIndex == -1) { - context.put(RSSI, Integer.parseUnsignedInt(frame.substring(frame.length() -2), 16)); - return frame.substring(0, frame.length() - 2); - } + public RssiProcessor(int rssiIndex, int rssiValue) { + this.rssiIndex = rssiIndex; + this.rssiValue = rssiValue; } - context.put(RSSI, rssiValue); - return frame; - } + @Override + public String process(String frame, Map context) { + if (rssiIndex != 0) { + if (rssiIndex == 1) { // first byte starts from character at index 0. + context.put(RSSI, Integer.parseUnsignedInt(frame.substring(0, 2), 16)); + return frame.substring(2); + } else if (rssiIndex == -1) { + context.put(RSSI, Integer.parseUnsignedInt(frame.substring(frame.length() - 2), 16)); + return frame.substring(0, frame.length() - 2); + } + } + + context.put(RSSI, rssiValue); + return frame; + } } diff --git a/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/processor/SkipCrcProcessor.java b/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/processor/SkipCrcProcessor.java index 357c963..b9299b1 100644 --- a/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/processor/SkipCrcProcessor.java +++ b/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/processor/SkipCrcProcessor.java @@ -8,49 +8,47 @@ */ package org.openhab.binding.wmbus.tools.processor; -import org.openhab.binding.wmbus.tools.Processor; - -import java.util.HashMap; import java.util.Map; +import org.openhab.binding.wmbus.tools.Processor; + public class SkipCrcProcessor implements Processor { - @Override - public String process(String frame, Map context) { - String strippedframe = ""; - int len = frame.length() / 2; // include all fields - - //int lengthField = Integer.decode(frame.substring(0, 2)); - int ciField = parseInt(frame, 22, 24); - boolean formatB = ciField == 0x72 || ciField == 0x7A; - - if (formatB) { - strippedframe += frame.substring(0, Math.min(18, frame.length() - 4)); - // block 1 + block 2 = 11 + ((16*n)+2) -- max 129 bytes - if (len <= 129) { - strippedframe += frame.substring(Math.min(22, frame.length()), frame.length() - 4); - } - // block 1 + block 2 + block 3= 11 + ((16*n)+2) + (16*n) -- bytes > 129 - else { - strippedframe += frame.substring(22, 256); - strippedframe += frame.substring(Math.min(260, frame.length()), frame.length() - 4); - } - } else { - // assume that we starts counting from C-field - strippedframe += frame.substring(0, Math.min(18, frame.length())); - int position = 22; - while (position < frame.length()) { - strippedframe += frame.substring(position, Math.min(position + 32, frame.length())); - position += 36; - } - strippedframe = strippedframe.substring(0,strippedframe.length()-4); + @Override + public String process(String frame, Map context) { + String strippedframe = ""; + int len = frame.length() / 2; // include all fields + + // int lengthField = Integer.decode(frame.substring(0, 2)); + int ciField = parseInt(frame, 22, 24); + boolean formatB = ciField == 0x72 || ciField == 0x7A; + + if (formatB) { + strippedframe += frame.substring(0, Math.min(18, frame.length() - 4)); + // block 1 + block 2 = 11 + ((16*n)+2) -- max 129 bytes + if (len <= 129) { + strippedframe += frame.substring(Math.min(22, frame.length()), frame.length() - 4); + } + // block 1 + block 2 + block 3= 11 + ((16*n)+2) + (16*n) -- bytes > 129 + else { + strippedframe += frame.substring(22, 256); + strippedframe += frame.substring(Math.min(260, frame.length()), frame.length() - 4); + } + } else { + // assume that we starts counting from C-field + strippedframe += frame.substring(0, Math.min(18, frame.length())); + int position = 22; + while (position < frame.length()) { + strippedframe += frame.substring(position, Math.min(position + 32, frame.length())); + position += 36; + } + strippedframe = strippedframe.substring(0, strippedframe.length() - 4); + } + + return strippedframe; } - return strippedframe; - } - - private int parseInt(String frame, int startIndex, int endIndex) { - return Integer.parseInt(frame.substring(startIndex, endIndex), 16); - } - + private int parseInt(String frame, int startIndex, int endIndex) { + return Integer.parseInt(frame.substring(startIndex, endIndex), 16); + } } diff --git a/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/processor/SkipProcessor.java b/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/processor/SkipProcessor.java index a9a6836..b37c030 100644 --- a/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/processor/SkipProcessor.java +++ b/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/processor/SkipProcessor.java @@ -8,21 +8,21 @@ */ package org.openhab.binding.wmbus.tools.processor; -import org.openhab.binding.wmbus.tools.Processor; - import java.util.Map; +import org.openhab.binding.wmbus.tools.Processor; + public class SkipProcessor implements Processor { - private final int amount; + private final int amount; - public SkipProcessor(int amount) { - this.amount = amount; - } + public SkipProcessor(int amount) { + this.amount = amount; + } - @Override - public String process(String frame, Map context) { - // one byte is 2 characters in hex representation - return frame.substring(amount * 2); - } + @Override + public String process(String frame, Map context) { + // one byte is 2 characters in hex representation + return frame.substring(amount * 2); + } } diff --git a/org.openhab.binding.wmbus.tools/src/test/java/org/openhab/binding/wmbus/tools/processor/RecalculateLengthTest.java b/org.openhab.binding.wmbus.tools/src/test/java/org/openhab/binding/wmbus/tools/processor/RecalculateLengthTest.java index 8f613c2..947e619 100644 --- a/org.openhab.binding.wmbus.tools/src/test/java/org/openhab/binding/wmbus/tools/processor/RecalculateLengthTest.java +++ b/org.openhab.binding.wmbus.tools/src/test/java/org/openhab/binding/wmbus/tools/processor/RecalculateLengthTest.java @@ -1,59 +1,54 @@ package org.openhab.binding.wmbus.tools.processor; -import org.eclipse.smarthome.core.util.HexUtils; -import org.junit.Test; -import org.openmuc.jmbus.DecodingException; -import org.openmuc.jmbus.wireless.VirtualWMBusMessageHelper; -import org.openmuc.jmbus.wireless.WMBusMessage; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.Collections; import java.util.HashMap; import java.util.Map; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - +import org.junit.Test; +import org.openhab.core.util.HexUtils; +import org.openmuc.jmbus.DecodingException; +import org.openmuc.jmbus.wireless.VirtualWMBusMessageHelper; +import org.openmuc.jmbus.wireless.WMBusMessage; public class RecalculateLengthTest { RecalculateLength recalculateLength = new RecalculateLength(); Map context = new HashMap<>(); - @Test + @Test public void testValidFrame() throws DecodingException { String frame = "2e44972682213893071A7AB80020A56BE69947E41B7346595CCA02F2BF0E62C906B80BD18240811FD4879DA1296D3F"; String expectedFrame = "2e44972682213893071A7AB80020A56BE69947E41B7346595CCA02F2BF0E62C906B80BD18240811FD4879DA1296D3F"; - assertThat(recalculateLength.process(frame, context)) - .isEqualTo(expectedFrame); + assertThat(recalculateLength.process(frame, context)).isEqualTo(expectedFrame); byte[] payload = HexUtils.hexToBytes(expectedFrame); WMBusMessage message = VirtualWMBusMessageHelper.decode(payload, 100, Collections.emptyMap()); - assertThatThrownBy(() -> message.getVariableDataResponse().decode()) - .isInstanceOf(DecodingException.class).hasMessageContaining("address key"); + assertThatThrownBy(() -> message.getVariableDataResponse().decode()).isInstanceOf(DecodingException.class) + .hasMessageContaining("address key"); } @Test public void testShortHeader() throws DecodingException { String frame = "3244972682213893071A7AB80040A56BE69947E41B7346595CCA02F2BF0E62C906B80BD18240811FD4879DA1296D3F4279385A"; String expectedFrame = "2e44972682213893071A7AB80020A56BE69947E41B7346595CCA02F2BF0E62C906B80BD18240811FD4879DA1296D3F"; - assertThat(recalculateLength.process(frame, context)) - .isEqualTo(expectedFrame); + assertThat(recalculateLength.process(frame, context)).isEqualTo(expectedFrame); byte[] payload = HexUtils.hexToBytes(expectedFrame); WMBusMessage message = VirtualWMBusMessageHelper.decode(payload, 100, Collections.emptyMap()); - assertThatThrownBy(() -> message.getVariableDataResponse().decode()) - .isInstanceOf(DecodingException.class).hasMessageContaining("address key"); + assertThatThrownBy(() -> message.getVariableDataResponse().decode()).isInstanceOf(DecodingException.class) + .hasMessageContaining("address key"); } @Test public void testLongHeader() throws DecodingException { String frame = "3244C5140401806003077261885616C51400075B0B90054CC7D04A56C6919495f6565704D1D9085134926D705D40D2EE699337"; String expectedFrame = "2644C5140401806003077261885616C51400075B0B10054CC7D04A56C6919495f6565704D1D908"; - assertThat(recalculateLength.process(frame, context)) - .isEqualTo(expectedFrame); + assertThat(recalculateLength.process(frame, context)).isEqualTo(expectedFrame); byte[] payload = HexUtils.hexToBytes(expectedFrame); WMBusMessage message = VirtualWMBusMessageHelper.decode(payload, 100, Collections.emptyMap()); System.out.println(message.getVariableDataResponse().getNumberOfEncryptedBlocks()); - assertThatThrownBy(() -> message.getVariableDataResponse().decode()) - .isInstanceOf(DecodingException.class).hasMessageContaining("secondary address"); + assertThatThrownBy(() -> message.getVariableDataResponse().decode()).isInstanceOf(DecodingException.class) + .hasMessageContaining("secondary address"); } - -} \ No newline at end of file +} diff --git a/org.openhab.binding.wmbus.tools/src/test/java/org/openhab/binding/wmbus/tools/processor/SkipCrcProcessorTest.java b/org.openhab.binding.wmbus.tools/src/test/java/org/openhab/binding/wmbus/tools/processor/SkipCrcProcessorTest.java index ef26bdd..811441c 100644 --- a/org.openhab.binding.wmbus.tools/src/test/java/org/openhab/binding/wmbus/tools/processor/SkipCrcProcessorTest.java +++ b/org.openhab.binding.wmbus.tools/src/test/java/org/openhab/binding/wmbus/tools/processor/SkipCrcProcessorTest.java @@ -2,39 +2,40 @@ import java.util.HashMap; import java.util.Map; -import junit.framework.TestCase; + import org.junit.Test; -public class SkipCrcProcessorTest extends TestCase { +import junit.framework.TestCase; - SkipCrcProcessor stripCrc = new SkipCrcProcessor(); - Map context = new HashMap<>(); +public class SkipCrcProcessorTest extends TestCase { - @Test - public void testFrameFormatB() { - String input = "11111111111111111100007A1111111111111111111111111111110000"; - String output = "1111111111111111117A111111111111111111111111111111"; - String result = stripCrc.process(input, context); + SkipCrcProcessor stripCrc = new SkipCrcProcessor(); + Map context = new HashMap<>(); - assertEquals(output, result); - } + @Test + public void testFrameFormatB() { + String input = "11111111111111111100007A1111111111111111111111111111110000"; + String output = "1111111111111111117A111111111111111111111111111111"; + String result = stripCrc.process(input, context); - @Test - public void testFrame1() { - String input = "111111111111111111000011111111111111111111111111110000"; - String output = "1111111111111111111111111111111111111111111111"; - String result = stripCrc.process(input, context); + assertEquals(output, result); + } - assertEquals(output, result); - } + @Test + public void testFrame1() { + String input = "111111111111111111000011111111111111111111111111110000"; + String output = "1111111111111111111111111111111111111111111111"; + String result = stripCrc.process(input, context); - @Test - public void testFrame3() { - String input = "11111111111111111100001111111111111111111111111111111100001111111111110000"; - String output = "11111111111111111111111111111111111111111111111111111111111111"; - String result = stripCrc.process(input, context); + assertEquals(output, result); + } - assertEquals(output, result); - } + @Test + public void testFrame3() { + String input = "11111111111111111100001111111111111111111111111111111100001111111111110000"; + String output = "11111111111111111111111111111111111111111111111111111111111111"; + String result = stripCrc.process(input, context); -} \ No newline at end of file + assertEquals(output, result); + } +} diff --git a/org.openhab.binding.wmbus/pom.xml b/org.openhab.binding.wmbus/pom.xml index 5c34712..1dec60c 100644 --- a/org.openhab.binding.wmbus/pom.xml +++ b/org.openhab.binding.wmbus/pom.xml @@ -1,52 +1,53 @@ - - - - 4.0.0 - - - org.openhab.addons.bundles - org.openhab.addons.reactor.bundles - 3.1.0-SNAPSHOT - - - org.openhab.binding.wmbus - - openHAB Add-ons :: Bundles :: wmbus Binding - - false - org.openhab.core.io.transport.serial - - - - junit - junit - 4.12 - test - - - org.assertj - assertj-core - 3.11.1 - test - - - org.slf4j - slf4j-simple - 1.7.2 - test - - - org.mockito - mockito-core - 2.23.0 - test - - - com.google.guava - guava - 20.0 - provided - - - + + + + 4.0.0 + + + org.openhab.addons.bundles + wmbus + 3.1.0-SNAPSHOT + + + org.openhab.addons.bundles + org.openhab.addons.bundles.wmbus + + WMBus Binding + + false + org.openhab.core.io.transport.serial + + + + junit + junit + 4.12 + test + + + org.assertj + assertj-core + 3.11.1 + test + + + org.slf4j + slf4j-simple + 1.7.2 + test + + + org.mockito + mockito-core + 2.23.0 + test + + + com.google.guava + guava + 20.0 + provided + + + diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/BindingConfiguration.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/BindingConfiguration.java index a6316c8..3b07cba 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/BindingConfiguration.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/BindingConfiguration.java @@ -1,25 +1,25 @@ -/** - * Copyright (c) 2010-2021 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.wmbus; - -/** - * Configuration of binding - defined as service so it can be injected into different places. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public interface BindingConfiguration { - - Long getTimeToLive(); - - Boolean getIncludeBridgeUID(); -} +/** + * Copyright (c) 2010-2021 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.wmbus; + +/** + * Configuration of binding - defined as service so it can be injected into different places. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public interface BindingConfiguration { + + Long getTimeToLive(); + + Boolean getIncludeBridgeUID(); +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/RecordType.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/RecordType.java index ea91e5a..be8055f 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/RecordType.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/RecordType.java @@ -1,105 +1,105 @@ -/** - * Copyright (c) 2010-2021 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.wmbus; - -import java.util.Arrays; - -import org.openhab.core.util.HexUtils; -import org.openmuc.jmbus.DataRecord; - -/** - * The {@link RecordType} class defines RecordType - * - * @author Hanno - Felix Wagner - Initial contribution - * @author Łukasz Dywicki - Hash/equality calculation and toString implementation. - */ - -public class RecordType { - - /** - * Constant for manufacturer data. - * - * Below combination of dib/vib is intentionally invalid. It is a mark for manufacturer specific data which can be - * appended as part of standard payload in the frame. - */ - public final static RecordType MANUFACTURER_DATA = new RecordType(new byte[] { 0x0, 0x0, 0x0, 0x0 }, - new byte[] { 0x0, 0x0, 0x0, 0x0 }); - - private final byte[] dib; - private final byte[] vib; - - public RecordType(byte[] dib, int vib) { - this(dib, new byte[] { (byte) vib }); - } - - public RecordType(int dib, byte[] vib) { - this(new byte[] { (byte) dib }, vib); - } - - public RecordType(byte[] dib, byte[] vib) { - this.dib = dib; - this.vib = vib; - } - - public RecordType(int dib, int vib) { - this(new byte[] { (byte) dib }, new byte[] { (byte) vib }); - } - - public byte[] getDib() { - return dib; - } - - public byte[] getVib() { - return vib; - } - - public boolean matches(DataRecord record) { - return Arrays.equals(record.getDib(), getDib()) && Arrays.equals(record.getVib(), getVib()); - } - - @Override - public String toString() { - return "RecordType DIB:" + HexUtils.bytesToHex(dib) + ", VIB:" + HexUtils.bytesToHex(vib); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + Arrays.hashCode(dib); - result = prime * result + Arrays.hashCode(vib); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - RecordType other = (RecordType) obj; - if (!Arrays.equals(dib, other.dib)) { - return false; - } - if (!Arrays.equals(vib, other.vib)) { - return false; - } - return true; - } -} +/** + * Copyright (c) 2010-2021 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.wmbus; + +import java.util.Arrays; + +import org.openhab.core.util.HexUtils; +import org.openmuc.jmbus.DataRecord; + +/** + * The {@link RecordType} class defines RecordType + * + * @author Hanno - Felix Wagner - Initial contribution + * @author Łukasz Dywicki - Hash/equality calculation and toString implementation. + */ + +public class RecordType { + + /** + * Constant for manufacturer data. + * + * Below combination of dib/vib is intentionally invalid. It is a mark for manufacturer specific data which can be + * appended as part of standard payload in the frame. + */ + public final static RecordType MANUFACTURER_DATA = new RecordType(new byte[] { 0x0, 0x0, 0x0, 0x0 }, + new byte[] { 0x0, 0x0, 0x0, 0x0 }); + + private final byte[] dib; + private final byte[] vib; + + public RecordType(byte[] dib, int vib) { + this(dib, new byte[] { (byte) vib }); + } + + public RecordType(int dib, byte[] vib) { + this(new byte[] { (byte) dib }, vib); + } + + public RecordType(byte[] dib, byte[] vib) { + this.dib = dib; + this.vib = vib; + } + + public RecordType(int dib, int vib) { + this(new byte[] { (byte) dib }, new byte[] { (byte) vib }); + } + + public byte[] getDib() { + return dib; + } + + public byte[] getVib() { + return vib; + } + + public boolean matches(DataRecord record) { + return Arrays.equals(record.getDib(), getDib()) && Arrays.equals(record.getVib(), getVib()); + } + + @Override + public String toString() { + return "RecordType DIB:" + HexUtils.bytesToHex(dib) + ", VIB:" + HexUtils.bytesToHex(vib); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(dib); + result = prime * result + Arrays.hashCode(vib); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + RecordType other = (RecordType) obj; + if (!Arrays.equals(dib, other.dib)) { + return false; + } + if (!Arrays.equals(vib, other.vib)) { + return false; + } + return true; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/UnitRegistry.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/UnitRegistry.java index 61829a8..bd43f1f 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/UnitRegistry.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/UnitRegistry.java @@ -1,47 +1,47 @@ -/** - * Copyright (c) 2010-2021 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.wmbus; - -import java.util.Optional; - -import javax.measure.Quantity; -import javax.measure.Unit; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openmuc.jmbus.DlmsUnit; - -/** - * Definition of unit registry which allows to provide mapping from wmbus types to javax.measure units. - * - * @author Łukasz Dywicki - Initial contribution. - */ -@NonNullByDefault -public interface UnitRegistry { - - /** - * Provides mapping from wmbus type to javax.measure type. - * - * @param wmbusType Data value type as per wmbus/IEC standard. - * @return Optional containing unit representing same value kind based on javax.measure types. - */ - Optional> lookup(@Nullable DlmsUnit wmbusType); - - /** - * Provides information of what kind of quantity given dlms unit is - ie. Power, Force. - * - * @param wmbusType Data value type as per wmbus/IEC standard. - * @return Optional containing quantity type according to unit mapping. - */ - Optional>> quantity(@Nullable DlmsUnit wmbusType); -} +/** + * Copyright (c) 2010-2021 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.wmbus; + +import java.util.Optional; + +import javax.measure.Quantity; +import javax.measure.Unit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openmuc.jmbus.DlmsUnit; + +/** + * Definition of unit registry which allows to provide mapping from wmbus types to javax.measure units. + * + * @author Łukasz Dywicki - Initial contribution. + */ +@NonNullByDefault +public interface UnitRegistry { + + /** + * Provides mapping from wmbus type to javax.measure type. + * + * @param wmbusType Data value type as per wmbus/IEC standard. + * @return Optional containing unit representing same value kind based on javax.measure types. + */ + Optional> lookup(@Nullable DlmsUnit wmbusType); + + /** + * Provides information of what kind of quantity given dlms unit is - ie. Power, Force. + * + * @param wmbusType Data value type as per wmbus/IEC standard. + * @return Optional containing quantity type according to unit mapping. + */ + Optional>> quantity(@Nullable DlmsUnit wmbusType); +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/WMBusBindingConstants.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/WMBusBindingConstants.java index 20e7171..808967b 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/WMBusBindingConstants.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/WMBusBindingConstants.java @@ -1,128 +1,128 @@ -/** - * Copyright (c) 2010-2021 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.wmbus; - -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; - -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.type.ChannelTypeUID; -import org.openmuc.jmbus.DeviceType; - -import com.google.common.collect.ImmutableSet; - -/** - * The {@link WMBusBindingConstants} class defines common constants, which are - * used across the whole binding. - * - * @author Hanno - Felix Wagner, Ernst Rohlicek, Roman Malyugin - Initial contribution - * @author Łűkasz Dywicki - meter thing type and surroundings - */ - -public class WMBusBindingConstants { - - public static final String BINDING_ID = "wmbus"; - public static final String THING_TYPE_NAME_BRIDGE = "wmbusbridge"; - public static final String THING_TYPE_NAME_VIRTUAL_BRIDGE = "wmbusvirtualbridge"; - public static final String THING_TYPE_NAME_METER = "meter"; - public static final String THING_TYPE_NAME_ENCRYPTED_METER = "encrypted_meter"; - - /** - * Time to live - by default 24 hours after which discovery result is discarded. - */ - public static final Long DEFAULT_TIME_TO_LIVE = TimeUnit.HOURS.toSeconds(24); - - // List all Thing Type UIDs, related to the WMBus Binding - public final static ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, THING_TYPE_NAME_BRIDGE); - public final static ThingTypeUID THING_TYPE_VIRTUAL_BRIDGE = new ThingTypeUID(BINDING_ID, - THING_TYPE_NAME_VIRTUAL_BRIDGE); - - public final static ThingTypeUID THING_TYPE_METER = new ThingTypeUID(BINDING_ID, THING_TYPE_NAME_METER); - public final static ThingTypeUID THING_TYPE_ENCRYPTED_METER = new ThingTypeUID(BINDING_ID, - THING_TYPE_NAME_ENCRYPTED_METER); - - public static final String CHANNEL_LAST_FRAME = "last_frame"; - public static final String CHANNEL_ERRORDATE = "error_date"; - public static final String CHANNEL_ERRORFLAGS = "error_flags"; - - public static final String CHANNEL_CURRENTPOWER = "current_power_w"; - public static final String CHANNEL_CURRENTENERGYTOTAL = "current_energy_total_kwh"; - public static final String CHANNEL_CURRENTVOLUMEFLOW = "current_volume_flow_m3h"; - public static final String CHANNEL_CURRENTVOLUMETOTAL = "current_volume_total_m3"; - public static final String CHANNEL_RETURNTEMPERATURE = "return_temperature"; - public static final String CHANNEL_TEMPERATUREDIFFERENCE = "temperature_difference"; - - public static final String CHANNEL_PREVIOUSREADING = "previous_reading"; - public static final String CHANNEL_PREVIOUSENERGYTOTAL = "previous_energy_total_kwh"; - public static final String CHANNEL_PREVIOUSDATE = "previous_date"; - - public final static ChannelTypeUID CHANNEL_LAST_FRAME_TYPE = new ChannelTypeUID(BINDING_ID, CHANNEL_LAST_FRAME); - - // add new devices here - public final static Set SUPPORTED_THING_TYPES_UIDS = ImmutableSet.of(THING_TYPE_BRIDGE, - THING_TYPE_VIRTUAL_BRIDGE, THING_TYPE_METER, THING_TYPE_ENCRYPTED_METER); - - // Bridge config properties - public static final String CONFKEY_STICK_MODEL = "stickModel"; - public static final String CONFKEY_INTERFACE_NAME = "serialDevice"; - public static final String CONFKEY_RADIO_MODE = "radioMode"; - public static final String CONFKEY_DATEFIELD_MODE = "dateFieldMode"; - public static final String CONFKEY_ENCRYPTION_KEYS = "encryptionKeys"; - public static final String CONFKEY_DEVICEID_FILTER = "deviceIDFilter"; - - // device config properties - public static final String PROPERTY_DEVICE_ADDRESS = "deviceAddress"; - public static final String PROPERTY_DEVICE_FREQUENCY_OF_UPDATES = "frequencyOfUpdates"; - public static final String PROPERTY_DEVICE_ENCRYPTION_KEY = "encryptionKey"; - // device property which says if we expected secure communication - public static final String PROPERTY_DEVICE_ENCRYPTED = "encrypted"; - - public static final String PROPERTY_WMBUS_MESSAGE = "wmBusMessage"; - - // Manufacturer options - public static final String MANUFACTURER_AMBER = "amber"; - public static final String MANUFACTURER_RADIO_CRAFTS = "rc"; - public static final String MANUFACTURER_IMST = "imst"; - - /** - * Default frequency of reports. This is at the same time value after which device is considered to be offline. - * Value in minutes. - */ - public static final Long DEFAULT_DEVICE_FREQUENCY_OF_UPDATES = 60l; - - /** - * A default encryption key. - */ - public static final byte[] DEFAULT_DEVICE_ENCRYPTION_KEY = new byte[0]; - - public static final Function DEVICE_TYPE_TRANSFORMATION = deviceType -> deviceType.name() - .toLowerCase().replace("_", " "); - - /** - * Generic device types which are supported by binding. - */ - public static final Set SUPPORTED_DEVICE_TYPES = ImmutableSet.of(DeviceType.OIL_METER, - DeviceType.ELECTRICITY_METER, DeviceType.GAS_METER, DeviceType.HEAT_METER, DeviceType.STEAM_METER, - DeviceType.WARM_WATER_METER, DeviceType.WATER_METER, DeviceType.HEAT_COST_ALLOCATOR, - DeviceType.COMPRESSED_AIR, DeviceType.COOLING_METER_OUTLET, DeviceType.COOLING_METER_INLET, - DeviceType.HEAT_METER_INLET, DeviceType.HEAT_COOLING_METER, DeviceType.CALORIFIC_VALUE, - DeviceType.HOT_WATER_METER, DeviceType.COLD_WATER_METER, DeviceType.DUAL_REGISTER_WATER_METER, - DeviceType.PRESSURE_METER, DeviceType.SMOKE_DETECTOR, DeviceType.ROOM_SENSOR_TEMP_HUM, - DeviceType.GAS_DETECTOR, DeviceType.BREAKER_ELEC, DeviceType.VALVE_GAS_OR_WATER, - DeviceType.WASTE_WATER_METER, DeviceType.RADIO_CONVERTER_SYSTEM_SIDE, - DeviceType.RADIO_CONVERTER_METER_SIDE); - - public static final String CONFKEY_BINDING_TIME_TO_LIVE = "timeToLive"; - public static final String CONFKEY_BINDING_INCLUDE_BRIDGE_UID = "includeBridgeUID"; -} +/** + * Copyright (c) 2010-2021 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.wmbus; + +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.type.ChannelTypeUID; +import org.openmuc.jmbus.DeviceType; + +import com.google.common.collect.ImmutableSet; + +/** + * The {@link WMBusBindingConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Hanno - Felix Wagner, Ernst Rohlicek, Roman Malyugin - Initial contribution + * @author Łűkasz Dywicki - meter thing type and surroundings + */ + +public class WMBusBindingConstants { + + public static final String BINDING_ID = "wmbus"; + public static final String THING_TYPE_NAME_BRIDGE = "wmbusbridge"; + public static final String THING_TYPE_NAME_VIRTUAL_BRIDGE = "wmbusvirtualbridge"; + public static final String THING_TYPE_NAME_METER = "meter"; + public static final String THING_TYPE_NAME_ENCRYPTED_METER = "encrypted_meter"; + + /** + * Time to live - by default 24 hours after which discovery result is discarded. + */ + public static final Long DEFAULT_TIME_TO_LIVE = TimeUnit.HOURS.toSeconds(24); + + // List all Thing Type UIDs, related to the WMBus Binding + public final static ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, THING_TYPE_NAME_BRIDGE); + public final static ThingTypeUID THING_TYPE_VIRTUAL_BRIDGE = new ThingTypeUID(BINDING_ID, + THING_TYPE_NAME_VIRTUAL_BRIDGE); + + public final static ThingTypeUID THING_TYPE_METER = new ThingTypeUID(BINDING_ID, THING_TYPE_NAME_METER); + public final static ThingTypeUID THING_TYPE_ENCRYPTED_METER = new ThingTypeUID(BINDING_ID, + THING_TYPE_NAME_ENCRYPTED_METER); + + public static final String CHANNEL_LAST_FRAME = "last_frame"; + public static final String CHANNEL_ERRORDATE = "error_date"; + public static final String CHANNEL_ERRORFLAGS = "error_flags"; + + public static final String CHANNEL_CURRENTPOWER = "current_power_w"; + public static final String CHANNEL_CURRENTENERGYTOTAL = "current_energy_total_kwh"; + public static final String CHANNEL_CURRENTVOLUMEFLOW = "current_volume_flow_m3h"; + public static final String CHANNEL_CURRENTVOLUMETOTAL = "current_volume_total_m3"; + public static final String CHANNEL_RETURNTEMPERATURE = "return_temperature"; + public static final String CHANNEL_TEMPERATUREDIFFERENCE = "temperature_difference"; + + public static final String CHANNEL_PREVIOUSREADING = "previous_reading"; + public static final String CHANNEL_PREVIOUSENERGYTOTAL = "previous_energy_total_kwh"; + public static final String CHANNEL_PREVIOUSDATE = "previous_date"; + + public final static ChannelTypeUID CHANNEL_LAST_FRAME_TYPE = new ChannelTypeUID(BINDING_ID, CHANNEL_LAST_FRAME); + + // add new devices here + public final static Set SUPPORTED_THING_TYPES_UIDS = ImmutableSet.of(THING_TYPE_BRIDGE, + THING_TYPE_VIRTUAL_BRIDGE, THING_TYPE_METER, THING_TYPE_ENCRYPTED_METER); + + // Bridge config properties + public static final String CONFKEY_STICK_MODEL = "stickModel"; + public static final String CONFKEY_INTERFACE_NAME = "serialDevice"; + public static final String CONFKEY_RADIO_MODE = "radioMode"; + public static final String CONFKEY_DATEFIELD_MODE = "dateFieldMode"; + public static final String CONFKEY_ENCRYPTION_KEYS = "encryptionKeys"; + public static final String CONFKEY_DEVICEID_FILTER = "deviceIDFilter"; + + // device config properties + public static final String PROPERTY_DEVICE_ADDRESS = "deviceAddress"; + public static final String PROPERTY_DEVICE_FREQUENCY_OF_UPDATES = "frequencyOfUpdates"; + public static final String PROPERTY_DEVICE_ENCRYPTION_KEY = "encryptionKey"; + // device property which says if we expected secure communication + public static final String PROPERTY_DEVICE_ENCRYPTED = "encrypted"; + + public static final String PROPERTY_WMBUS_MESSAGE = "wmBusMessage"; + + // Manufacturer options + public static final String MANUFACTURER_AMBER = "amber"; + public static final String MANUFACTURER_RADIO_CRAFTS = "rc"; + public static final String MANUFACTURER_IMST = "imst"; + + /** + * Default frequency of reports. This is at the same time value after which device is considered to be offline. + * Value in minutes. + */ + public static final Long DEFAULT_DEVICE_FREQUENCY_OF_UPDATES = 60l; + + /** + * A default encryption key. + */ + public static final byte[] DEFAULT_DEVICE_ENCRYPTION_KEY = new byte[0]; + + public static final Function DEVICE_TYPE_TRANSFORMATION = deviceType -> deviceType.name() + .toLowerCase().replace("_", " "); + + /** + * Generic device types which are supported by binding. + */ + public static final Set SUPPORTED_DEVICE_TYPES = ImmutableSet.of(DeviceType.OIL_METER, + DeviceType.ELECTRICITY_METER, DeviceType.GAS_METER, DeviceType.HEAT_METER, DeviceType.STEAM_METER, + DeviceType.WARM_WATER_METER, DeviceType.WATER_METER, DeviceType.HEAT_COST_ALLOCATOR, + DeviceType.COMPRESSED_AIR, DeviceType.COOLING_METER_OUTLET, DeviceType.COOLING_METER_INLET, + DeviceType.HEAT_METER_INLET, DeviceType.HEAT_COOLING_METER, DeviceType.CALORIFIC_VALUE, + DeviceType.HOT_WATER_METER, DeviceType.COLD_WATER_METER, DeviceType.DUAL_REGISTER_WATER_METER, + DeviceType.PRESSURE_METER, DeviceType.SMOKE_DETECTOR, DeviceType.ROOM_SENSOR_TEMP_HUM, + DeviceType.GAS_DETECTOR, DeviceType.BREAKER_ELEC, DeviceType.VALVE_GAS_OR_WATER, + DeviceType.WASTE_WATER_METER, DeviceType.RADIO_CONVERTER_SYSTEM_SIDE, + DeviceType.RADIO_CONVERTER_METER_SIDE); + + public static final String CONFKEY_BINDING_TIME_TO_LIVE = "timeToLive"; + public static final String CONFKEY_BINDING_INCLUDE_BRIDGE_UID = "includeBridgeUID"; +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/WMBusCompanyIdentifiers.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/WMBusCompanyIdentifiers.java index 0d716ec..9bbb7ec 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/WMBusCompanyIdentifiers.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/WMBusCompanyIdentifiers.java @@ -1,55 +1,55 @@ -/** - * Copyright (c) 2010-2021 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.wmbus; - -import java.util.HashMap; -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * Basic lookup table from 3 letter identifier code to full name of manufacturer. - * - * Captured from http://www.dlms.com/organization/flagmanufacturesids/index.html. - * Last update June 2018. - * - * @author Łukasz Dywicki - initial contribution. - */ -@NonNullByDefault -public class WMBusCompanyIdentifiers { - - private static final Map CIC = new HashMap<>(); - - static { - CIC.put("TCH", "TECHEM"); - CIC.put("QDS", "QUNDIS"); - CIC.put("KAM", "KAMSTRUP"); - CIC.put("ARF", "ADEUNIS"); - CIC.put("EFE", "ENGELMANN"); - } - - /** - * Returns the company name as a String - * - * @param manufacturer the WMBus manufacturer identifier - * @return The company name - */ - public static @Nullable String get(String manufacturer) { - if (manufacturer != null) { - return CIC.get(manufacturer); - } else { - return null; - } - } -} +/** + * Copyright (c) 2010-2021 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.wmbus; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * Basic lookup table from 3 letter identifier code to full name of manufacturer. + * + * Captured from http://www.dlms.com/organization/flagmanufacturesids/index.html. + * Last update June 2018. + * + * @author Łukasz Dywicki - initial contribution. + */ +@NonNullByDefault +public class WMBusCompanyIdentifiers { + + private static final Map CIC = new HashMap<>(); + + static { + CIC.put("TCH", "TECHEM"); + CIC.put("QDS", "QUNDIS"); + CIC.put("KAM", "KAMSTRUP"); + CIC.put("ARF", "ADEUNIS"); + CIC.put("EFE", "ENGELMANN"); + } + + /** + * Returns the company name as a String + * + * @param manufacturer the WMBus manufacturer identifier + * @return The company name + */ + public static @Nullable String get(String manufacturer) { + if (manufacturer != null) { + return CIC.get(manufacturer); + } else { + return null; + } + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/WMBusDevice.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/WMBusDevice.java index 601f82b..bddaddd 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/WMBusDevice.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/WMBusDevice.java @@ -1,119 +1,119 @@ -/** - * Copyright (c) 2010-2021 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.wmbus; - -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openhab.core.util.HexUtils; -import org.openmuc.jmbus.DataRecord; -import org.openmuc.jmbus.DecodingException; -import org.openmuc.jmbus.EncryptionMode; -import org.openmuc.jmbus.wireless.WMBusMessage; - -/** - * The {@link WMBusDevice} class defines WMBusDevice - * - * @author Hanno - Felix Wagner - Initial contribution - */ - -public class WMBusDevice { - - private final WMBusMessage originalMessage; - private final WMBusAdapter adapter; - - public WMBusDevice(WMBusMessage originalMessage, WMBusAdapter adapter) { - this.originalMessage = originalMessage; - this.adapter = adapter; - } - - public WMBusMessage getOriginalMessage() { - return originalMessage; - } - - public WMBusAdapter getAdapter() { - return adapter; - } - - public void decode() throws DecodingException { - originalMessage.getVariableDataResponse().decode(); - } - - public String getDeviceId() { - return originalMessage.getSecondaryAddress().getDeviceId().toString(); - } - - public DataRecord findRecord(RecordType recordType) { - if (RecordType.MANUFACTURER_DATA == recordType) { - return new ManufacturerData(originalMessage.getVariableDataResponse().getManufacturerData()); - } - - for (DataRecord record : originalMessage.getVariableDataResponse().getDataRecords()) { - if (recordType.matches(record)) { - return record; - } - } - return null; - } - - public DataRecord findRecord(byte[] dib, byte[] vib) { - return findRecord(new RecordType(dib, vib)); - } - - public boolean isEnrypted() { - return originalMessage.getVariableDataResponse().getEncryptionMode() != EncryptionMode.NONE; - } - - public String getDeviceAddress() { - return HexUtils.bytesToHex(originalMessage.getSecondaryAddress().asByteArray()); - } - - public String getDeviceType() { - return originalMessage.getControlField() + "" + originalMessage.getSecondaryAddress().getManufacturerId() + "" - + originalMessage.getSecondaryAddress().getVersion() + "" - + originalMessage.getSecondaryAddress().getDeviceType().getId(); - } - - public String getRawDeviceType() { - return originalMessage.getControlField() + "" + originalMessage.getSecondaryAddress().getManufacturerId() + "" - + originalMessage.getSecondaryAddress().getVersion() + "" + getOriginalDeviceTypeField(); - } - - public int getOriginalDeviceTypeField() { - byte[] addressArray = originalMessage.getSecondaryAddress().asByteArray(); - return addressArray[addressArray.length - 1] & 0xFF; - } - - @Override - public String toString() { - return originalMessage.getSecondaryAddress().toString(); - } -} - -class ManufacturerData extends DataRecord { - - private final byte[] rawData; - - ManufacturerData(byte[] rawData) { - this.rawData = rawData; - } - - @Override - public byte[] getDataValue() { - return getRawData(); - } - - @Override - public byte[] getRawData() { - return rawData; - } -} +/** + * Copyright (c) 2010-2021 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.wmbus; + +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openhab.core.util.HexUtils; +import org.openmuc.jmbus.DataRecord; +import org.openmuc.jmbus.DecodingException; +import org.openmuc.jmbus.EncryptionMode; +import org.openmuc.jmbus.wireless.WMBusMessage; + +/** + * The {@link WMBusDevice} class defines WMBusDevice + * + * @author Hanno - Felix Wagner - Initial contribution + */ + +public class WMBusDevice { + + private final WMBusMessage originalMessage; + private final WMBusAdapter adapter; + + public WMBusDevice(WMBusMessage originalMessage, WMBusAdapter adapter) { + this.originalMessage = originalMessage; + this.adapter = adapter; + } + + public WMBusMessage getOriginalMessage() { + return originalMessage; + } + + public WMBusAdapter getAdapter() { + return adapter; + } + + public void decode() throws DecodingException { + originalMessage.getVariableDataResponse().decode(); + } + + public String getDeviceId() { + return originalMessage.getSecondaryAddress().getDeviceId().toString(); + } + + public DataRecord findRecord(RecordType recordType) { + if (RecordType.MANUFACTURER_DATA == recordType) { + return new ManufacturerData(originalMessage.getVariableDataResponse().getManufacturerData()); + } + + for (DataRecord record : originalMessage.getVariableDataResponse().getDataRecords()) { + if (recordType.matches(record)) { + return record; + } + } + return null; + } + + public DataRecord findRecord(byte[] dib, byte[] vib) { + return findRecord(new RecordType(dib, vib)); + } + + public boolean isEnrypted() { + return originalMessage.getVariableDataResponse().getEncryptionMode() != EncryptionMode.NONE; + } + + public String getDeviceAddress() { + return HexUtils.bytesToHex(originalMessage.getSecondaryAddress().asByteArray()); + } + + public String getDeviceType() { + return originalMessage.getControlField() + "" + originalMessage.getSecondaryAddress().getManufacturerId() + "" + + originalMessage.getSecondaryAddress().getVersion() + "" + + originalMessage.getSecondaryAddress().getDeviceType().getId(); + } + + public String getRawDeviceType() { + return originalMessage.getControlField() + "" + originalMessage.getSecondaryAddress().getManufacturerId() + "" + + originalMessage.getSecondaryAddress().getVersion() + "" + getOriginalDeviceTypeField(); + } + + public int getOriginalDeviceTypeField() { + byte[] addressArray = originalMessage.getSecondaryAddress().asByteArray(); + return addressArray[addressArray.length - 1] & 0xFF; + } + + @Override + public String toString() { + return originalMessage.getSecondaryAddress().toString(); + } +} + +class ManufacturerData extends DataRecord { + + private final byte[] rawData; + + ManufacturerData(byte[] rawData) { + this.rawData = rawData; + } + + @Override + public byte[] getDataValue() { + return getRawData(); + } + + @Override + public byte[] getRawData() { + return rawData; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/DateFieldMode.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/DateFieldMode.java index 482cc72..42000dc 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/DateFieldMode.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/DateFieldMode.java @@ -1,27 +1,27 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.config; - -/** - * Setting describing possible modes for handling WMBus date/date time fields and their mapping back to openhab universe - * through binding code. - * - * @author Łukasz Dywicki - Initial contribution - */ -public enum DateFieldMode { - - FORMATTED_STRING, - UNIX_TIMESTAMP, - DATE_TIME - -} +/** + * Copyright (c) 2010-2021 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.wmbus.config; + +/** + * Setting describing possible modes for handling WMBus date/date time fields and their mapping back to openhab universe + * through binding code. + * + * @author Łukasz Dywicki - Initial contribution + */ +public enum DateFieldMode { + + FORMATTED_STRING, + UNIX_TIMESTAMP, + DATE_TIME + +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/StickModel.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/StickModel.java index cf1c155..645ec54 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/StickModel.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/StickModel.java @@ -1,28 +1,28 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.config; - -/** - * Radio stick model. - * - * @author Łukasz Dywicki - Initial contribution - */ -public enum StickModel { - - // be aware that these are coded lower case so they match stick configuration defined in XML - - amber, - rc, - imst - -} +/** + * Copyright (c) 2010-2021 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.wmbus.config; + +/** + * Radio stick model. + * + * @author Łukasz Dywicki - Initial contribution + */ +public enum StickModel { + + // be aware that these are coded lower case so they match stick configuration defined in XML + + amber, + rc, + imst + +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/WMBusBridgeConfig.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/WMBusBridgeConfig.java index be2ebbf..91e4778 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/WMBusBridgeConfig.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/WMBusBridgeConfig.java @@ -1,35 +1,35 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.config; - -/** - * Configuration of WM-Bus communication device. - * - * @author Łukasz Dywicki - Initial contribution - */ -public class WMBusBridgeConfig { - - public String encryptionKeys; - public String deviceIDFilter; - public DateFieldMode dateFieldMode = DateFieldMode.DATE_TIME; - - public int[] getDeviceIDFilter() { - String[] ids = deviceIDFilter.split(";"); - int[] idInts = new int[ids.length]; - for (int i = 0; i < ids.length; i++) { - String curID = ids[i]; - idInts[i] = Integer.parseInt(curID); - } - return idInts; - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.config; + +/** + * Configuration of WM-Bus communication device. + * + * @author Łukasz Dywicki - Initial contribution + */ +public class WMBusBridgeConfig { + + public String encryptionKeys; + public String deviceIDFilter; + public DateFieldMode dateFieldMode = DateFieldMode.DATE_TIME; + + public int[] getDeviceIDFilter() { + String[] ids = deviceIDFilter.split(";"); + int[] idInts = new int[ids.length]; + for (int i = 0; i < ids.length; i++) { + String curID = ids[i]; + idInts[i] = Integer.parseInt(curID); + } + return idInts; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/WMBusSerialBridgeConfig.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/WMBusSerialBridgeConfig.java index 9534d00..80ef68b 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/WMBusSerialBridgeConfig.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/config/WMBusSerialBridgeConfig.java @@ -1,27 +1,27 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.config; - -import org.openmuc.jmbus.wireless.WMBusMode; - -/** - * A specialized version of configuration of serial devices - USB sticks. - * - * @author Łukasz Dywicki - Initial contribution - */ -public class WMBusSerialBridgeConfig extends WMBusBridgeConfig { - - public StickModel stickModel; - public String serialDevice; - public WMBusMode radioMode; -} +/** + * Copyright (c) 2010-2021 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.wmbus.config; + +import org.openmuc.jmbus.wireless.WMBusMode; + +/** + * A specialized version of configuration of serial devices - USB sticks. + * + * @author Łukasz Dywicki - Initial contribution + */ +public class WMBusSerialBridgeConfig extends WMBusBridgeConfig { + + public StickModel stickModel; + public String serialDevice; + public WMBusMode radioMode; +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/AbstractWMBusDiscoveryParticipant.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/AbstractWMBusDiscoveryParticipant.java index b42b31d..d472971 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/AbstractWMBusDiscoveryParticipant.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/AbstractWMBusDiscoveryParticipant.java @@ -1,57 +1,57 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device; - -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.wmbus.BindingConfiguration; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.discovery.WMBusDiscoveryParticipant; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.ThingUID; - -/** - * Base class for discovery participants. - * - * @author Łukasz Dywicki - initial contribution - */ -public abstract class AbstractWMBusDiscoveryParticipant implements WMBusDiscoveryParticipant { - - private BindingConfiguration configuration; - - @Override - public @Nullable ThingUID getThingUID(WMBusDevice device) { - if (configuration.getIncludeBridgeUID()) { - return new ThingUID(getThingType(device), device.getAdapter().getUID(), getDeviceID(device)); - } else { - return new ThingUID(getThingType(device), getDeviceID(device)); - } - } - - private String getDeviceID(WMBusDevice device) { - return device.getDeviceId(); - } - - protected abstract ThingTypeUID getThingType(WMBusDevice device); - - public void setBindingConfiguration(BindingConfiguration configuration) { - this.configuration = configuration; - } - - public void unsetBindingConfiguration(BindingConfiguration configuration) { - this.configuration = null; - } - - protected Long getTimeToLive() { - return configuration.getTimeToLive(); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device; + +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.wmbus.BindingConfiguration; +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.discovery.WMBusDiscoveryParticipant; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; + +/** + * Base class for discovery participants. + * + * @author Łukasz Dywicki - initial contribution + */ +public abstract class AbstractWMBusDiscoveryParticipant implements WMBusDiscoveryParticipant { + + private BindingConfiguration configuration; + + @Override + public @Nullable ThingUID getThingUID(WMBusDevice device) { + if (configuration.getIncludeBridgeUID()) { + return new ThingUID(getThingType(device), device.getAdapter().getUID(), getDeviceID(device)); + } else { + return new ThingUID(getThingType(device), getDeviceID(device)); + } + } + + private String getDeviceID(WMBusDevice device) { + return device.getDeviceId(); + } + + protected abstract ThingTypeUID getThingType(WMBusDevice device); + + public void setBindingConfiguration(BindingConfiguration configuration) { + this.configuration = configuration; + } + + public void unsetBindingConfiguration(BindingConfiguration configuration) { + this.configuration = null; + } + + protected Long getTimeToLive() { + return configuration.getTimeToLive(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/Meter.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/Meter.java index a89e162..dacd573 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/Meter.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/Meter.java @@ -1,48 +1,48 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device; - -import java.util.Set; - -import org.openhab.core.thing.ThingTypeUID; - -/** - * The {@link Meter} class defines common Meter device - * - * @author Roman Malyugin - Initial contribution - */ - -public class Meter { - - protected Set supportedThingTypes; - protected ThingTypeUID thingType; - protected String thingTypeName; - protected String thingTypeId; - - public Set getSupportedThingTypes() { - return supportedThingTypes; - } - - public ThingTypeUID getThingType() { - return thingType; - } - - public String getThingTypeName() { - return thingTypeName; - } - - public String getThingTypeId() { - return thingTypeId; - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device; + +import java.util.Set; + +import org.openhab.core.thing.ThingTypeUID; + +/** + * The {@link Meter} class defines common Meter device + * + * @author Roman Malyugin - Initial contribution + */ + +public class Meter { + + protected Set supportedThingTypes; + protected ThingTypeUID thingType; + protected String thingTypeName; + protected String thingTypeId; + + public Set getSupportedThingTypes() { + return supportedThingTypes; + } + + public ThingTypeUID getThingType() { + return thingType; + } + + public String getThingTypeName() { + return thingTypeName; + } + + public String getThingTypeId() { + return thingTypeId; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/UnknownMeter.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/UnknownMeter.java index 7144bc9..515f003 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/UnknownMeter.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/UnknownMeter.java @@ -1,77 +1,77 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device; - -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNull; -import org.openhab.binding.wmbus.handler.WMBusDeviceHandler; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.types.Command; -import org.openhab.core.types.RefreshType; -import org.openhab.core.types.State; -import org.openhab.core.types.UnDefType; -import org.openhab.io.transport.mbus.wireless.KeyStorage; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Deactivate; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link UnknownMeter} class defines unknown abstract Meter device - * - * @author Roman Malyugin - Initial contribution - */ - -@Component(service = { UnknownMeter.class }) -public class UnknownMeter extends Meter { - - public static final Logger logger = LoggerFactory.getLogger(UnknownMeter.class); - - @Activate - protected void activate(Map properties) { - } - - @Deactivate - protected void deactivate() { - } - - public static class UnknownWMBusDeviceHandler extends WMBusDeviceHandler { - - public UnknownWMBusDeviceHandler(Thing thing, KeyStorage keyStorage) { - super(thing, keyStorage); - } - - @Override - public void handleCommand(@NonNull ChannelUID channelUID, @NonNull Command command) { - logger.trace("handleCommand(): (1/5) command for channel " + channelUID.toString() + " command: " - + command.toString()); - if (command == RefreshType.REFRESH) { - logger.trace("handleCommand(): (2/5) command.refreshtype == REFRESH"); - State newState = UnDefType.NULL; - if (wmbusDevice != null) { - logger.trace("handleCommand(): (3/5) deviceMessage != null"); - logger.trace("handleCommand(): (4/5): got channel id: " + channelUID.getId()); - logger.trace("handleCommand(): (5/5) assigning new state to channel '" - + channelUID.getId().toString() + "': " + newState.toString()); - updateState(channelUID.getId(), newState); - - } - - } - } - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNull; +import org.openhab.binding.wmbus.handler.WMBusDeviceHandler; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; +import org.openhab.io.transport.mbus.wireless.KeyStorage; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link UnknownMeter} class defines unknown abstract Meter device + * + * @author Roman Malyugin - Initial contribution + */ + +@Component(service = { UnknownMeter.class }) +public class UnknownMeter extends Meter { + + public static final Logger logger = LoggerFactory.getLogger(UnknownMeter.class); + + @Activate + protected void activate(Map properties) { + } + + @Deactivate + protected void deactivate() { + } + + public static class UnknownWMBusDeviceHandler extends WMBusDeviceHandler { + + public UnknownWMBusDeviceHandler(Thing thing, KeyStorage keyStorage) { + super(thing, keyStorage); + } + + @Override + public void handleCommand(@NonNull ChannelUID channelUID, @NonNull Command command) { + logger.trace("handleCommand(): (1/5) command for channel " + channelUID.toString() + " command: " + + command.toString()); + if (command == RefreshType.REFRESH) { + logger.trace("handleCommand(): (2/5) command.refreshtype == REFRESH"); + State newState = UnDefType.NULL; + if (wmbusDevice != null) { + logger.trace("handleCommand(): (3/5) deviceMessage != null"); + logger.trace("handleCommand(): (4/5): got channel id: " + channelUID.getId()); + logger.trace("handleCommand(): (5/5) assigning new state to channel '" + + channelUID.getId().toString() + "': " + newState.toString()); + updateState(channelUID.getId(), newState); + + } + + } + } + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/generic/DynamicWMBusThingHandler.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/generic/DynamicWMBusThingHandler.java index a1e7f1b..54bb964 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/generic/DynamicWMBusThingHandler.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/generic/DynamicWMBusThingHandler.java @@ -1,150 +1,150 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.generic; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.eclipse.jdt.annotation.NonNull; -import org.openhab.binding.wmbus.RecordType; -import org.openhab.binding.wmbus.UnitRegistry; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openhab.binding.wmbus.handler.WMBusDeviceHandler; -import org.openhab.binding.wmbus.internal.WMBusChannelTypeProvider; -import org.openhab.core.library.types.QuantityType; -import org.openhab.core.thing.Channel; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.binding.builder.ChannelBuilder; -import org.openhab.core.thing.binding.builder.ThingBuilder; -import org.openhab.core.thing.type.ChannelType; -import org.openhab.core.thing.type.ChannelTypeUID; -import org.openhab.core.thing.util.ThingHelper; -import org.openhab.core.types.Command; -import org.openhab.core.types.RefreshType; -import org.openhab.core.types.State; -import org.openhab.core.util.HexUtils; -import org.openhab.io.transport.mbus.wireless.KeyStorage; -import org.openmuc.jmbus.DataRecord; -import org.openmuc.jmbus.VariableDataStructure; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.collect.ImmutableMap; - -/** - * Universal dynamic handler which covers devices based on dib/vib and channel type mapping. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public class DynamicWMBusThingHandler extends WMBusDeviceHandler { - - private static final String CHANNEL_PROPERTY_VIB = "vib"; - - private static final String CHANNEL_PROPERTY_DIB = "dib"; - - private final Logger logger = LoggerFactory.getLogger(DynamicWMBusThingHandler.class); - - private final UnitRegistry unitRegistry; - private final WMBusChannelTypeProvider channelTypeProvider; - - public DynamicWMBusThingHandler(Thing thing, KeyStorage keyStorage, UnitRegistry unitRegistry, - WMBusChannelTypeProvider channelTypeProvider) { - super(thing, keyStorage); - this.unitRegistry = unitRegistry; - this.channelTypeProvider = channelTypeProvider; - } - - @Override - public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice receivedDevice) { - if (receivedDevice.getDeviceAddress().equals(deviceAddress)) { - VariableDataStructure response = receivedDevice.getOriginalMessage().getVariableDataResponse(); - - List channels = new ArrayList<>(); - for (DataRecord record : response.getDataRecords()) { - Optional typeId = WMBusChannelTypeProvider.getChannelType(record); - Optional channel = typeId.map(type -> thing.getChannel(type.getId())); - - if (typeId.isPresent() && !channel.isPresent()) { - Channel newChannel = createChannel(typeId.get(), record); - try { - ThingHelper.ensureUniqueChannels(channels, newChannel); - channels.add(newChannel); - } catch (IllegalArgumentException ex) { - logger.debug("Cannot create channel: {}", ex.getMessage()); - // TODO instead of dropping, rename the duplicate with a suffix like _1,_2 etc - } - } - } - - if (!channels.isEmpty()) { - ThingBuilder updatedThing = editThing().withChannels(channels); - updateThing(updatedThing.build()); - } - } - - super.onChangedWMBusDevice(adapter, receivedDevice); - } - - private Channel createChannel(ChannelTypeUID typeId, DataRecord record) { - ChannelType type = channelTypeProvider.getChannelType(typeId, null); - - ChannelBuilder channelBuilder = ChannelBuilder.create(new ChannelUID(thing.getUID(), typeId.getId()), - type.getItemType()); - - Map properties = ImmutableMap.of(CHANNEL_PROPERTY_DIB, HexUtils.bytesToHex(record.getDib()), - CHANNEL_PROPERTY_VIB, HexUtils.bytesToHex(record.getVib())); - - channelBuilder.withType(typeId).withProperties(properties).withLabel(type.getLabel()); - - String description = type.getDescription(); - if (description != null) { - channelBuilder.withDescription(description); - } - - return channelBuilder.build(); - } - - @Override - public void handleCommand(@NonNull ChannelUID channelUID, @NonNull Command command) { - logger.trace("Received command {} for channel {}", command, channelUID); - if (wmbusDevice != null && command == RefreshType.REFRESH) { - Optional> properties = Optional.ofNullable(thing.getChannel(channelUID.getId())) - .map(ch -> ch.getProperties()); - - Optional dib = properties.map(map -> map.get(CHANNEL_PROPERTY_DIB)).map(HexUtils::hexToBytes); - Optional vib = properties.map(map -> map.get(CHANNEL_PROPERTY_VIB)).map(HexUtils::hexToBytes); - - if (dib.isPresent() && vib.isPresent()) { - RecordType recordType = new RecordType(dib.get(), vib.get()); - DataRecord record = wmbusDevice.findRecord(recordType); - - if (record != null) { - State newState = unitRegistry.lookup(record.getUnit()) - .map(unit -> new QuantityType<>(record.getScaledDataValue(), unit)).map(State.class::cast) - .orElseGet(() -> convertRecordData(record)); - - logger.trace("Assigning new state {} to channel {}", newState, channelUID.getId()); - updateState(channelUID.getId(), newState); - } else { - logger.warn("Could not read value of record {} in received frame", recordType); - } - } else { - logger.warn("Unknown channel {}, not supported by {}", channelUID, thing); - } - } - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.generic; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNull; +import org.openhab.binding.wmbus.RecordType; +import org.openhab.binding.wmbus.UnitRegistry; +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openhab.binding.wmbus.handler.WMBusDeviceHandler; +import org.openhab.binding.wmbus.internal.WMBusChannelTypeProvider; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.binding.builder.ChannelBuilder; +import org.openhab.core.thing.binding.builder.ThingBuilder; +import org.openhab.core.thing.type.ChannelType; +import org.openhab.core.thing.type.ChannelTypeUID; +import org.openhab.core.thing.util.ThingHelper; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; +import org.openhab.core.util.HexUtils; +import org.openhab.io.transport.mbus.wireless.KeyStorage; +import org.openmuc.jmbus.DataRecord; +import org.openmuc.jmbus.VariableDataStructure; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableMap; + +/** + * Universal dynamic handler which covers devices based on dib/vib and channel type mapping. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public class DynamicWMBusThingHandler extends WMBusDeviceHandler { + + private static final String CHANNEL_PROPERTY_VIB = "vib"; + + private static final String CHANNEL_PROPERTY_DIB = "dib"; + + private final Logger logger = LoggerFactory.getLogger(DynamicWMBusThingHandler.class); + + private final UnitRegistry unitRegistry; + private final WMBusChannelTypeProvider channelTypeProvider; + + public DynamicWMBusThingHandler(Thing thing, KeyStorage keyStorage, UnitRegistry unitRegistry, + WMBusChannelTypeProvider channelTypeProvider) { + super(thing, keyStorage); + this.unitRegistry = unitRegistry; + this.channelTypeProvider = channelTypeProvider; + } + + @Override + public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice receivedDevice) { + if (receivedDevice.getDeviceAddress().equals(deviceAddress)) { + VariableDataStructure response = receivedDevice.getOriginalMessage().getVariableDataResponse(); + + List channels = new ArrayList<>(); + for (DataRecord record : response.getDataRecords()) { + Optional typeId = WMBusChannelTypeProvider.getChannelType(record); + Optional channel = typeId.map(type -> thing.getChannel(type.getId())); + + if (typeId.isPresent() && !channel.isPresent()) { + Channel newChannel = createChannel(typeId.get(), record); + try { + ThingHelper.ensureUniqueChannels(channels, newChannel); + channels.add(newChannel); + } catch (IllegalArgumentException ex) { + logger.debug("Cannot create channel: {}", ex.getMessage()); + // TODO instead of dropping, rename the duplicate with a suffix like _1,_2 etc + } + } + } + + if (!channels.isEmpty()) { + ThingBuilder updatedThing = editThing().withChannels(channels); + updateThing(updatedThing.build()); + } + } + + super.onChangedWMBusDevice(adapter, receivedDevice); + } + + private Channel createChannel(ChannelTypeUID typeId, DataRecord record) { + ChannelType type = channelTypeProvider.getChannelType(typeId, null); + + ChannelBuilder channelBuilder = ChannelBuilder.create(new ChannelUID(thing.getUID(), typeId.getId()), + type.getItemType()); + + Map properties = ImmutableMap.of(CHANNEL_PROPERTY_DIB, HexUtils.bytesToHex(record.getDib()), + CHANNEL_PROPERTY_VIB, HexUtils.bytesToHex(record.getVib())); + + channelBuilder.withType(typeId).withProperties(properties).withLabel(type.getLabel()); + + String description = type.getDescription(); + if (description != null) { + channelBuilder.withDescription(description); + } + + return channelBuilder.build(); + } + + @Override + public void handleCommand(@NonNull ChannelUID channelUID, @NonNull Command command) { + logger.trace("Received command {} for channel {}", command, channelUID); + if (wmbusDevice != null && command == RefreshType.REFRESH) { + Optional> properties = Optional.ofNullable(thing.getChannel(channelUID.getId())) + .map(ch -> ch.getProperties()); + + Optional dib = properties.map(map -> map.get(CHANNEL_PROPERTY_DIB)).map(HexUtils::hexToBytes); + Optional vib = properties.map(map -> map.get(CHANNEL_PROPERTY_VIB)).map(HexUtils::hexToBytes); + + if (dib.isPresent() && vib.isPresent()) { + RecordType recordType = new RecordType(dib.get(), vib.get()); + DataRecord record = wmbusDevice.findRecord(recordType); + + if (record != null) { + State newState = unitRegistry.lookup(record.getUnit()) + .map(unit -> new QuantityType<>(record.getScaledDataValue(), unit)).map(State.class::cast) + .orElseGet(() -> convertRecordData(record)); + + logger.trace("Assigning new state {} to channel {}", newState, channelUID.getId()); + updateState(channelUID.getId(), newState); + } else { + logger.warn("Could not read value of record {} in received frame", recordType); + } + } else { + logger.warn("Unknown channel {}, not supported by {}", channelUID, thing); + } + } + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/generic/GenericWMBusThingHandler.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/generic/GenericWMBusThingHandler.java index 88c8a8f..b9852ac 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/generic/GenericWMBusThingHandler.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/generic/GenericWMBusThingHandler.java @@ -1,77 +1,77 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.generic; - -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNull; -import org.openhab.binding.wmbus.RecordType; -import org.openhab.binding.wmbus.UnitRegistry; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.handler.WMBusDeviceHandler; -import org.openhab.core.library.types.QuantityType; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.types.Command; -import org.openhab.core.types.RefreshType; -import org.openhab.core.types.State; -import org.openhab.io.transport.mbus.wireless.KeyStorage; -import org.openmuc.jmbus.DataRecord; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Universal handler which covers all devices based on channel/record type mapping. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public class GenericWMBusThingHandler extends WMBusDeviceHandler { - - private final Logger logger = LoggerFactory.getLogger(GenericWMBusThingHandler.class); - - private final UnitRegistry unitRegistry; - private final Map channelMapping; - - protected GenericWMBusThingHandler(Thing thing, KeyStorage keyStorage, UnitRegistry unitRegistry, - Map channelMapping) { - super(thing, keyStorage); - this.unitRegistry = unitRegistry; - this.channelMapping = channelMapping; - } - - @Override - public void handleCommand(@NonNull ChannelUID channelUID, @NonNull Command command) { - logger.trace("Received command {} for channel {}", command, channelUID); - if (command == RefreshType.REFRESH) { - if (wmbusDevice != null) { - RecordType recordType = channelMapping.get(channelUID.getId()); - if (recordType != null) { - DataRecord record = wmbusDevice.findRecord(recordType); - - if (record != null) { - State newState = unitRegistry.lookup(record.getUnit()) - .map(unit -> new QuantityType<>(record.getScaledDataValue(), unit)) - .map(State.class::cast).orElseGet(() -> convertRecordData(record)); - - logger.trace("Assigning new state {} to channel {}", newState, channelUID.getId()); - updateState(channelUID.getId(), newState); - } else { - logger.warn("Could not read value of record {} in received frame", recordType); - } - } else { - logger.warn("Unown channel {}, not supported by {}", channelUID, thing); - } - } - } - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.generic; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNull; +import org.openhab.binding.wmbus.RecordType; +import org.openhab.binding.wmbus.UnitRegistry; +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.handler.WMBusDeviceHandler; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; +import org.openhab.io.transport.mbus.wireless.KeyStorage; +import org.openmuc.jmbus.DataRecord; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Universal handler which covers all devices based on channel/record type mapping. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public class GenericWMBusThingHandler extends WMBusDeviceHandler { + + private final Logger logger = LoggerFactory.getLogger(GenericWMBusThingHandler.class); + + private final UnitRegistry unitRegistry; + private final Map channelMapping; + + protected GenericWMBusThingHandler(Thing thing, KeyStorage keyStorage, UnitRegistry unitRegistry, + Map channelMapping) { + super(thing, keyStorage); + this.unitRegistry = unitRegistry; + this.channelMapping = channelMapping; + } + + @Override + public void handleCommand(@NonNull ChannelUID channelUID, @NonNull Command command) { + logger.trace("Received command {} for channel {}", command, channelUID); + if (command == RefreshType.REFRESH) { + if (wmbusDevice != null) { + RecordType recordType = channelMapping.get(channelUID.getId()); + if (recordType != null) { + DataRecord record = wmbusDevice.findRecord(recordType); + + if (record != null) { + State newState = unitRegistry.lookup(record.getUnit()) + .map(unit -> new QuantityType<>(record.getScaledDataValue(), unit)) + .map(State.class::cast).orElseGet(() -> convertRecordData(record)); + + logger.trace("Assigning new state {} to channel {}", newState, channelUID.getId()); + updateState(channelUID.getId(), newState); + } else { + logger.warn("Could not read value of record {} in received frame", recordType); + } + } else { + logger.warn("Unown channel {}, not supported by {}", channelUID, thing); + } + } + } + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronBindingConstants.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronBindingConstants.java index 0861a9f..b18fa21 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronBindingConstants.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronBindingConstants.java @@ -1,85 +1,85 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.itron; - -import java.util.Set; - -import org.openhab.binding.wmbus.WMBusBindingConstants; -import org.openhab.core.thing.ThingTypeUID; - -import com.google.common.collect.ImmutableSet; - -public interface ItronBindingConstants { - - String ITRON_SMOKE_DETECTOR = "itron_smoke_detector"; - - ThingTypeUID THING_TYPE_ITRON_SMOKE_DETECTOR = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, - ITRON_SMOKE_DETECTOR); - - Set SUPPORTED_THING_TYPES = ImmutableSet.of(THING_TYPE_ITRON_SMOKE_DETECTOR); - - String ITRON_MANUFACTURER_ID = "ITW"; - - String CHANNEL_CURRENT_DATE = "current_date"; - String CHANNEL_CURRENT_DATE_STRING = "current_date_string"; - String CHANNEL_CURRENT_DATE_NUMBER = "current_date_number"; - - String CHANNEL_STATUS_BILLING_DATE = "status_billing_date"; - String CHANNEL_STATUS_REMOVAL_OCCURRED = "status_removal_occurred"; - String CHANNEL_STATUS_PRODUCT_INSTALLED = "status_product_installed"; - String CHANNEL_STATUS_OPERATION_MODE = "status_operation_mode"; - String CHANNEL_STATUS_PERIMETER_INTRUSION_OCCURRED = "status_perimeter_intrusion_occurred"; - String CHANNEL_STATUS_SMOKE_INLET_BLOCKED_OCCURRED = "status_smoke_inlet_blocked_occurred"; - String CHANNEL_STATUS_OUT_OF_TEMP_RANGE_OCCURRED = "status_out_of_temp_range_occurred"; - String CHANNEL_STATUS_PRODUCT_CODE = "status_product_code"; - String CHANNEL_STATUS_BATTERY_LIFETIME = "status_battery_lifetime"; - // String CHANNEL_STATUS_PERIMETER_INTRUSION = "status_perimeter_intrusion"; - // String CHANNEL_STATUS_REMOVAL_ERROR = "status_removal_error"; - // String CHANNEL_STATUS_DATA_ENCRYPTED = "status_data_encrypted"; - - String CHANNEL_LAST_SMOKE_ALERT_START_DATE = "last_smoke_alert_start_date"; - String CHANNEL_LAST_SMOKE_ALERT_START_DATE_STRING = "last_smoke_alert_start_date_string"; - String CHANNEL_LAST_SMOKE_ALERT_START_DATE_NUMBER = "last_smoke_alert_start_date_number"; - String CHANNEL_LAST_SMOKE_ALERT_END_DATE = "last_smoke_alert_end_date"; - String CHANNEL_LAST_SMOKE_ALERT_END_DATE_STRING = "last_smoke_alert_end_date_string"; - String CHANNEL_LAST_SMOKE_ALERT_END_DATE_NUMBER = "last_smoke_alert_end_date_number"; - String CHANNEL_LAST_BEEPER_STOPPED_DURING_SMOKE_ALERT_DATE = "last_beeper_stopped_during_smoke_alert_date"; - String CHANNEL_LAST_BEEPER_STOPPED_DURING_SMOKE_ALERT_DATE_STRING = "last_beeper_stopped_during_smoke_alert_date_string"; - String CHANNEL_LAST_BEEPER_STOPPED_DURING_SMOKE_ALERT_DATE_NUMBER = "last_beeper_stopped_during_smoke_alert_date_number"; - - String CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_OCCURRED_DATE = "last_perimeter_intrusion_obstacle_occurred_date"; - String CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_OCCURRED_DATE_STRING = "last_perimeter_intrusion_obstacle_occurred_date_string"; - String CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_OCCURRED_DATE_NUMBER = "last_perimeter_intrusion_obstacle_occurred_date_number"; - String CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_REMOVED_DATE = "last_perimeter_intrusion_obstacle_removed_date"; - String CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_REMOVED_DATE_STRING = "last_perimeter_intrusion_obstacle_removed_date_string"; - String CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_REMOVED_DATE_NUMBER = "last_perimeter_intrusion_obstacle_removed_date_number"; - - String CHANNEL_LAST_SMOKE_INLET_BLOCKED_DATE = "last_smoke_inlet_blocked_date"; - String CHANNEL_LAST_SMOKE_INLET_BLOCKED_DATE_STRING = "last_smoke_inlet_blocked_date_string"; - String CHANNEL_LAST_SMOKE_INLET_BLOCKED_DATE_NUMBER = "last_smoke_inlet_blocked_date_number"; - String CHANNEL_LAST_SMOKE_INLET_BLOCKING_REMOVED_DATE = "last_smoke_inlet_blocking_removed_date"; - String CHANNEL_LAST_SMOKE_INLET_BLOCKING_REMOVED_DATE_STRING = "last_smoke_inlet_blocking_removed_date_string"; - String CHANNEL_LAST_SMOKE_INLET_BLOCKING_REMOVED_DATE_NUMBER = "last_smoke_inlet_blocking_removed_date_number"; - - String CHANNEL_LAST_TEMPERATURE_OUT_OF_RANGE_DATE = "last_temperature_out_of_range_date"; - String CHANNEL_LAST_TEMPERATURE_OUT_OF_RANGE_DATE_STRING = "last_temperature_out_of_range_date_string"; - String CHANNEL_LAST_TEMPERATURE_OUT_OF_RANGE_DATE_NUMBER = "last_temperature_out_of_range_date_number"; - - String CHANNEL_LAST_TEST_SWITCH_DATE = "last_test_switch_date"; - String CHANNEL_LAST_TEST_SWITCH_DATE_STRING = "last_test_switch_date_string"; - String CHANNEL_LAST_TEST_SWITCH_DATE_NUMBER = "last_test_switch_date_number"; - - String CHANNEL_NUMBER_OF_TEST_SWITCHES_OPERATED = "number_of_test_switches_operated"; - String CHANNEL_PERIMETER_INTRUSION_DAY_COUNTER_CUMULATED = "perimeter_intrusion_day_counter_cumulated"; - String CHANNEL_SMOKE_INLET_DAY_COUNTER_CUMULATED = "smoke_inlet_day_counter_cumulated"; -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.itron; + +import java.util.Set; + +import org.openhab.binding.wmbus.WMBusBindingConstants; +import org.openhab.core.thing.ThingTypeUID; + +import com.google.common.collect.ImmutableSet; + +public interface ItronBindingConstants { + + String ITRON_SMOKE_DETECTOR = "itron_smoke_detector"; + + ThingTypeUID THING_TYPE_ITRON_SMOKE_DETECTOR = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, + ITRON_SMOKE_DETECTOR); + + Set SUPPORTED_THING_TYPES = ImmutableSet.of(THING_TYPE_ITRON_SMOKE_DETECTOR); + + String ITRON_MANUFACTURER_ID = "ITW"; + + String CHANNEL_CURRENT_DATE = "current_date"; + String CHANNEL_CURRENT_DATE_STRING = "current_date_string"; + String CHANNEL_CURRENT_DATE_NUMBER = "current_date_number"; + + String CHANNEL_STATUS_BILLING_DATE = "status_billing_date"; + String CHANNEL_STATUS_REMOVAL_OCCURRED = "status_removal_occurred"; + String CHANNEL_STATUS_PRODUCT_INSTALLED = "status_product_installed"; + String CHANNEL_STATUS_OPERATION_MODE = "status_operation_mode"; + String CHANNEL_STATUS_PERIMETER_INTRUSION_OCCURRED = "status_perimeter_intrusion_occurred"; + String CHANNEL_STATUS_SMOKE_INLET_BLOCKED_OCCURRED = "status_smoke_inlet_blocked_occurred"; + String CHANNEL_STATUS_OUT_OF_TEMP_RANGE_OCCURRED = "status_out_of_temp_range_occurred"; + String CHANNEL_STATUS_PRODUCT_CODE = "status_product_code"; + String CHANNEL_STATUS_BATTERY_LIFETIME = "status_battery_lifetime"; + // String CHANNEL_STATUS_PERIMETER_INTRUSION = "status_perimeter_intrusion"; + // String CHANNEL_STATUS_REMOVAL_ERROR = "status_removal_error"; + // String CHANNEL_STATUS_DATA_ENCRYPTED = "status_data_encrypted"; + + String CHANNEL_LAST_SMOKE_ALERT_START_DATE = "last_smoke_alert_start_date"; + String CHANNEL_LAST_SMOKE_ALERT_START_DATE_STRING = "last_smoke_alert_start_date_string"; + String CHANNEL_LAST_SMOKE_ALERT_START_DATE_NUMBER = "last_smoke_alert_start_date_number"; + String CHANNEL_LAST_SMOKE_ALERT_END_DATE = "last_smoke_alert_end_date"; + String CHANNEL_LAST_SMOKE_ALERT_END_DATE_STRING = "last_smoke_alert_end_date_string"; + String CHANNEL_LAST_SMOKE_ALERT_END_DATE_NUMBER = "last_smoke_alert_end_date_number"; + String CHANNEL_LAST_BEEPER_STOPPED_DURING_SMOKE_ALERT_DATE = "last_beeper_stopped_during_smoke_alert_date"; + String CHANNEL_LAST_BEEPER_STOPPED_DURING_SMOKE_ALERT_DATE_STRING = "last_beeper_stopped_during_smoke_alert_date_string"; + String CHANNEL_LAST_BEEPER_STOPPED_DURING_SMOKE_ALERT_DATE_NUMBER = "last_beeper_stopped_during_smoke_alert_date_number"; + + String CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_OCCURRED_DATE = "last_perimeter_intrusion_obstacle_occurred_date"; + String CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_OCCURRED_DATE_STRING = "last_perimeter_intrusion_obstacle_occurred_date_string"; + String CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_OCCURRED_DATE_NUMBER = "last_perimeter_intrusion_obstacle_occurred_date_number"; + String CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_REMOVED_DATE = "last_perimeter_intrusion_obstacle_removed_date"; + String CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_REMOVED_DATE_STRING = "last_perimeter_intrusion_obstacle_removed_date_string"; + String CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_REMOVED_DATE_NUMBER = "last_perimeter_intrusion_obstacle_removed_date_number"; + + String CHANNEL_LAST_SMOKE_INLET_BLOCKED_DATE = "last_smoke_inlet_blocked_date"; + String CHANNEL_LAST_SMOKE_INLET_BLOCKED_DATE_STRING = "last_smoke_inlet_blocked_date_string"; + String CHANNEL_LAST_SMOKE_INLET_BLOCKED_DATE_NUMBER = "last_smoke_inlet_blocked_date_number"; + String CHANNEL_LAST_SMOKE_INLET_BLOCKING_REMOVED_DATE = "last_smoke_inlet_blocking_removed_date"; + String CHANNEL_LAST_SMOKE_INLET_BLOCKING_REMOVED_DATE_STRING = "last_smoke_inlet_blocking_removed_date_string"; + String CHANNEL_LAST_SMOKE_INLET_BLOCKING_REMOVED_DATE_NUMBER = "last_smoke_inlet_blocking_removed_date_number"; + + String CHANNEL_LAST_TEMPERATURE_OUT_OF_RANGE_DATE = "last_temperature_out_of_range_date"; + String CHANNEL_LAST_TEMPERATURE_OUT_OF_RANGE_DATE_STRING = "last_temperature_out_of_range_date_string"; + String CHANNEL_LAST_TEMPERATURE_OUT_OF_RANGE_DATE_NUMBER = "last_temperature_out_of_range_date_number"; + + String CHANNEL_LAST_TEST_SWITCH_DATE = "last_test_switch_date"; + String CHANNEL_LAST_TEST_SWITCH_DATE_STRING = "last_test_switch_date_string"; + String CHANNEL_LAST_TEST_SWITCH_DATE_NUMBER = "last_test_switch_date_number"; + + String CHANNEL_NUMBER_OF_TEST_SWITCHES_OPERATED = "number_of_test_switches_operated"; + String CHANNEL_PERIMETER_INTRUSION_DAY_COUNTER_CUMULATED = "perimeter_intrusion_day_counter_cumulated"; + String CHANNEL_SMOKE_INLET_DAY_COUNTER_CUMULATED = "smoke_inlet_day_counter_cumulated"; +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronConfigStatusDataParser.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronConfigStatusDataParser.java index 88e39f5..208e55b 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronConfigStatusDataParser.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronConfigStatusDataParser.java @@ -1,79 +1,79 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.itron; - -import java.util.BitSet; - -import org.openhab.binding.wmbus.device.techem.decoder.Buffer; - -public class ItronConfigStatusDataParser { - - private final Byte billing; - private final BitSet status; - private final Byte product; - private final Byte battery; - private final byte[] sdErrors; - private final Byte modemErrors; - private final Byte config; - - ItronConfigStatusDataParser(byte[] buffer) { - this(new Buffer(buffer)); - } - - ItronConfigStatusDataParser(Buffer buffer) { - this.billing = buffer.readByte(); - this.status = BitSet.valueOf(new byte[] { buffer.readByte() }); - this.product = buffer.readByte(); - this.battery = buffer.readByte(); - this.sdErrors = buffer.readBytes(2); - this.modemErrors = buffer.readByte(); - this.config = buffer.readByte(); - } - - public int getBillingDate() { - return billing.intValue(); - } - - public boolean isRemovalOccurred() { - return status.get(0); - } - - public boolean isProductInstalled() { - return status.get(1); - } - - public int getOperationMode() { - int value = status.toByteArray()[0]; - return value > 3 ? (value >> 2) ^ 4 : value >> 2; - } - - public boolean isPerimeterIntrusionOccurred() { - return status.get(4); - } - - public boolean isSmokeInletBlockedOccurred() { - return status.get(5); - } - - public boolean isOutOfRangeTemperatureOccurred() { - return status.get(6); - } - - public byte getProductCode() { - return product; - } - - public int getBatteryLifetime() { - return (battery & 0xFF) >> 1; - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.itron; + +import java.util.BitSet; + +import org.openhab.binding.wmbus.device.techem.decoder.Buffer; + +public class ItronConfigStatusDataParser { + + private final Byte billing; + private final BitSet status; + private final Byte product; + private final Byte battery; + private final byte[] sdErrors; + private final Byte modemErrors; + private final Byte config; + + ItronConfigStatusDataParser(byte[] buffer) { + this(new Buffer(buffer)); + } + + ItronConfigStatusDataParser(Buffer buffer) { + this.billing = buffer.readByte(); + this.status = BitSet.valueOf(new byte[] { buffer.readByte() }); + this.product = buffer.readByte(); + this.battery = buffer.readByte(); + this.sdErrors = buffer.readBytes(2); + this.modemErrors = buffer.readByte(); + this.config = buffer.readByte(); + } + + public int getBillingDate() { + return billing.intValue(); + } + + public boolean isRemovalOccurred() { + return status.get(0); + } + + public boolean isProductInstalled() { + return status.get(1); + } + + public int getOperationMode() { + int value = status.toByteArray()[0]; + return value > 3 ? (value >> 2) ^ 4 : value >> 2; + } + + public boolean isPerimeterIntrusionOccurred() { + return status.get(4); + } + + public boolean isSmokeInletBlockedOccurred() { + return status.get(5); + } + + public boolean isOutOfRangeTemperatureOccurred() { + return status.get(6); + } + + public byte getProductCode() { + return product; + } + + public int getBatteryLifetime() { + return (battery & 0xFF) >> 1; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronDiscoveryParticipant.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronDiscoveryParticipant.java index 2912dcb..2cd46e9 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronDiscoveryParticipant.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronDiscoveryParticipant.java @@ -1,111 +1,111 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.itron; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import org.eclipse.jdt.annotation.NonNull; -import org.openhab.binding.wmbus.BindingConfiguration; -import org.openhab.binding.wmbus.WMBusBindingConstants; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.discovery.WMBusDiscoveryParticipant; -import org.openhab.core.config.discovery.DiscoveryResult; -import org.openhab.core.config.discovery.DiscoveryResultBuilder; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.ThingUID; -import org.openmuc.jmbus.DeviceType; -import org.openmuc.jmbus.SecondaryAddress; -import org.openmuc.jmbus.wireless.WMBusMessage; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Reference; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Discovers itron devices and adds additional layer for decoding on top of generic WM-Bus devices. - * - * @author Łukasz Dywicki - initial contribution. - */ -@Component -public class ItronDiscoveryParticipant implements WMBusDiscoveryParticipant { - - private final Logger logger = LoggerFactory.getLogger(ItronDiscoveryParticipant.class); - - private BindingConfiguration configuration; - - @Override - public @NonNull Set getSupportedThingTypeUIDs() { - return ItronBindingConstants.SUPPORTED_THING_TYPES; - } - - @Override - public @NonNull ThingUID getThingUID(WMBusDevice device) { - ThingTypeUID type = ItronBindingConstants.THING_TYPE_ITRON_SMOKE_DETECTOR; - if (configuration.getIncludeBridgeUID()) { - return new ThingUID(type, device.getAdapter().getUID(), device.getDeviceId()); - } - return new ThingUID(type, device.getDeviceId()); - } - - @Override - public DiscoveryResult createResult(WMBusDevice device) { - if (isItronSmokeDetector(device)) { - String label = "Itron smoke detector #" + device.getDeviceId(); - - Map properties = new HashMap<>(); - properties.put(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS, device.getDeviceAddress()); - properties.put(Thing.PROPERTY_VENDOR, "Itron"); - properties.put(Thing.PROPERTY_SERIAL_NUMBER, device.getDeviceId()); - SecondaryAddress secondaryAddress = device.getOriginalMessage().getSecondaryAddress(); - properties.put(Thing.PROPERTY_MODEL_ID, secondaryAddress.getVersion()); - properties.put(WMBusBindingConstants.PROPERTY_DEVICE_ENCRYPTED, device.isEnrypted()); - @NonNull - ThingUID thingUID = getThingUID(device); - if (thingUID != null) - return DiscoveryResultBuilder.create(thingUID).withProperties(properties) - .withRepresentationProperty(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS).withLabel(label) - .withThingType(ItronBindingConstants.THING_TYPE_ITRON_SMOKE_DETECTOR) - .withBridge(device.getAdapter().getUID()).withLabel(label).withTTL(getTimeToLive()).build(); - - } - - return null; - } - - private boolean isItronSmokeDetector(WMBusDevice device) { - WMBusMessage message = device.getOriginalMessage(); - - if (!ItronBindingConstants.ITRON_MANUFACTURER_ID.equals(message.getSecondaryAddress().getManufacturerId()) - || message.getSecondaryAddress().getDeviceType() != DeviceType.SMOKE_DETECTOR) { - return false; - } - - return true; - } - - @Reference - public void setBindingConfiguration(BindingConfiguration configuration) { - this.configuration = configuration; - } - - public void unsetBindingConfiguration(BindingConfiguration configuration) { - this.configuration = null; - } - - private Long getTimeToLive() { - return configuration.getTimeToLive(); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.itron; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNull; +import org.openhab.binding.wmbus.BindingConfiguration; +import org.openhab.binding.wmbus.WMBusBindingConstants; +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.discovery.WMBusDiscoveryParticipant; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.openmuc.jmbus.DeviceType; +import org.openmuc.jmbus.SecondaryAddress; +import org.openmuc.jmbus.wireless.WMBusMessage; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Discovers itron devices and adds additional layer for decoding on top of generic WM-Bus devices. + * + * @author Łukasz Dywicki - initial contribution. + */ +@Component +public class ItronDiscoveryParticipant implements WMBusDiscoveryParticipant { + + private final Logger logger = LoggerFactory.getLogger(ItronDiscoveryParticipant.class); + + private BindingConfiguration configuration; + + @Override + public @NonNull Set getSupportedThingTypeUIDs() { + return ItronBindingConstants.SUPPORTED_THING_TYPES; + } + + @Override + public @NonNull ThingUID getThingUID(WMBusDevice device) { + ThingTypeUID type = ItronBindingConstants.THING_TYPE_ITRON_SMOKE_DETECTOR; + if (configuration.getIncludeBridgeUID()) { + return new ThingUID(type, device.getAdapter().getUID(), device.getDeviceId()); + } + return new ThingUID(type, device.getDeviceId()); + } + + @Override + public DiscoveryResult createResult(WMBusDevice device) { + if (isItronSmokeDetector(device)) { + String label = "Itron smoke detector #" + device.getDeviceId(); + + Map properties = new HashMap<>(); + properties.put(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS, device.getDeviceAddress()); + properties.put(Thing.PROPERTY_VENDOR, "Itron"); + properties.put(Thing.PROPERTY_SERIAL_NUMBER, device.getDeviceId()); + SecondaryAddress secondaryAddress = device.getOriginalMessage().getSecondaryAddress(); + properties.put(Thing.PROPERTY_MODEL_ID, secondaryAddress.getVersion()); + properties.put(WMBusBindingConstants.PROPERTY_DEVICE_ENCRYPTED, device.isEnrypted()); + @NonNull + ThingUID thingUID = getThingUID(device); + if (thingUID != null) + return DiscoveryResultBuilder.create(thingUID).withProperties(properties) + .withRepresentationProperty(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS).withLabel(label) + .withThingType(ItronBindingConstants.THING_TYPE_ITRON_SMOKE_DETECTOR) + .withBridge(device.getAdapter().getUID()).withLabel(label).withTTL(getTimeToLive()).build(); + + } + + return null; + } + + private boolean isItronSmokeDetector(WMBusDevice device) { + WMBusMessage message = device.getOriginalMessage(); + + if (!ItronBindingConstants.ITRON_MANUFACTURER_ID.equals(message.getSecondaryAddress().getManufacturerId()) + || message.getSecondaryAddress().getDeviceType() != DeviceType.SMOKE_DETECTOR) { + return false; + } + + return true; + } + + @Reference + public void setBindingConfiguration(BindingConfiguration configuration) { + this.configuration = configuration; + } + + public void unsetBindingConfiguration(BindingConfiguration configuration) { + this.configuration = null; + } + + private Long getTimeToLive() { + return configuration.getTimeToLive(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronHandlerFactory.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronHandlerFactory.java index 839f95b..ab74b7d 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronHandlerFactory.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronHandlerFactory.java @@ -1,77 +1,77 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.itron; - -import org.openhab.binding.wmbus.UnitRegistry; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.binding.BaseThingHandlerFactory; -import org.openhab.core.thing.binding.ThingHandler; -import org.openhab.core.thing.binding.ThingHandlerFactory; -import org.openhab.io.transport.mbus.wireless.FilteredKeyStorage; -import org.openhab.io.transport.mbus.wireless.KeyStorage; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Reference; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link ItronHandlerFactory} covers logic specific to Itron devices. - * - * @author Łukasz Dywicki - Initial contribution. - */ - -@Component(service = { ItronHandlerFactory.class, BaseThingHandlerFactory.class, ThingHandlerFactory.class }) -public class ItronHandlerFactory extends BaseThingHandlerFactory { - - // OpenHAB logger - private final Logger logger = LoggerFactory.getLogger(ItronHandlerFactory.class); - private KeyStorage keyStorage; - private UnitRegistry unitRegistry; - - @Override - public boolean supportsThingType(ThingTypeUID thingTypeUID) { - return ItronBindingConstants.SUPPORTED_THING_TYPES.contains(thingTypeUID); - } - - @Override - protected ThingHandler createHandler(Thing thing) { - ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - - if (thingTypeUID.equals(ItronBindingConstants.THING_TYPE_ITRON_SMOKE_DETECTOR)) { - logger.debug("Creating handler for Itron device {}", thing.getUID().getId()); - return new ItronSmokeDetectorHandler(thing, new FilteredKeyStorage(keyStorage, thing), unitRegistry); - } - - return null; - } - - @Reference - public void setKeyStorage(KeyStorage keyStorage) { - this.keyStorage = keyStorage; - } - - public void unsetKeyStorage(KeyStorage keyStorage) { - this.keyStorage = null; - } - - @Reference - protected void setUnitRegistry(UnitRegistry unitRegistry) { - this.unitRegistry = unitRegistry; - } - - protected void unsetUnitRegistry(UnitRegistry unitRegistry) { - this.unitRegistry = null; - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.itron; + +import org.openhab.binding.wmbus.UnitRegistry; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.BaseThingHandlerFactory; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.openhab.io.transport.mbus.wireless.FilteredKeyStorage; +import org.openhab.io.transport.mbus.wireless.KeyStorage; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link ItronHandlerFactory} covers logic specific to Itron devices. + * + * @author Łukasz Dywicki - Initial contribution. + */ + +@Component(service = { ItronHandlerFactory.class, BaseThingHandlerFactory.class, ThingHandlerFactory.class }) +public class ItronHandlerFactory extends BaseThingHandlerFactory { + + // OpenHAB logger + private final Logger logger = LoggerFactory.getLogger(ItronHandlerFactory.class); + private KeyStorage keyStorage; + private UnitRegistry unitRegistry; + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return ItronBindingConstants.SUPPORTED_THING_TYPES.contains(thingTypeUID); + } + + @Override + protected ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (thingTypeUID.equals(ItronBindingConstants.THING_TYPE_ITRON_SMOKE_DETECTOR)) { + logger.debug("Creating handler for Itron device {}", thing.getUID().getId()); + return new ItronSmokeDetectorHandler(thing, new FilteredKeyStorage(keyStorage, thing), unitRegistry); + } + + return null; + } + + @Reference + public void setKeyStorage(KeyStorage keyStorage) { + this.keyStorage = keyStorage; + } + + public void unsetKeyStorage(KeyStorage keyStorage) { + this.keyStorage = null; + } + + @Reference + protected void setUnitRegistry(UnitRegistry unitRegistry) { + this.unitRegistry = unitRegistry; + } + + protected void unsetUnitRegistry(UnitRegistry unitRegistry) { + this.unitRegistry = null; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronManufacturerDataParser.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronManufacturerDataParser.java index 9937d4e..ce2b3cc 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronManufacturerDataParser.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronManufacturerDataParser.java @@ -1,81 +1,81 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.itron; - -import java.time.DayOfWeek; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.Arrays; - -import org.openhab.binding.wmbus.device.techem.decoder.Buffer; - -public class ItronManufacturerDataParser { - - private final static byte[] EMPTY_SHORT_DATE_1 = new byte[] { 0x0, 0x0, 0x1, 0x1 }; - private final static byte[] EMPTY_SHORT_DATE_2 = new byte[] { 0x0, 0x0, 0x0, 0x0 }; - private final Buffer buffer; - - ItronManufacturerDataParser(Buffer buffer) { - this.buffer = buffer; - } - - public LocalDateTime readShortDateTime() { - return parseShortDateTime(buffer.readBytes(4)); - } - - public LocalDateTime readLongDateTime() { - return parseLongDateTime(buffer.readBytes(5)); - } - - private LocalDateTime parseShortDateTime(byte[] date) { - if (Arrays.equals(date, EMPTY_SHORT_DATE_1) || Arrays.equals(date, EMPTY_SHORT_DATE_2)) { - return null; - } - - int minute = date[0] & 0xFF >> 2; - - int yearLSB = (date[2] & 0xFF) >> 5; - int yearMSB = ((date[3] & 0xFF) & 0xF0) >> 1; - int hour = (date[1] & 0xFF >> 3); - int day = (date[2] & 0xFF >> 3); - int month = (date[3] & 0xFF >> 4); - - // ((0x2C & 0xF0) >> 0x1) | (0x7D >> 0x5) = 0x13 = 19d - int year = yearMSB | yearLSB; - - return LocalDateTime.of(2000 + year, month, day, hour, minute); - } - - private LocalDateTime parseLongDateTime(byte[] date) { - int second = (date[0] & 0xFF) >> 2; - int minute = (date[1] & 0xFF) >> 2; - int hour = (date[2] & 0xFF >> 3); - int day = (date[2] & 0xFF >> 4); - int month = (date[3] & 0xFF >> 4); - - int yearLSB = (date[3] & 0xFF) >> 5; - int yearMSB = ((date[4] & 0xFF) & 0xF0) >> 1; - - // ((0x2C & 0xF0) >> 0x1) | (0x7D >> 0x5) = 0x13 = 19d - int year = yearMSB | yearLSB; - - LocalTime time = LocalTime.of(hour, minute, second); - DayOfWeek dayOfWeek = DayOfWeek.of(day); - LocalDate today = LocalDate.now(); - LocalDate parsed = LocalDate.of(year, month, today.getDayOfMonth()); - - LocalTime timeWithDay = time.with(dayOfWeek); - return parsed.atStartOfDay().with(timeWithDay); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.itron; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Arrays; + +import org.openhab.binding.wmbus.device.techem.decoder.Buffer; + +public class ItronManufacturerDataParser { + + private final static byte[] EMPTY_SHORT_DATE_1 = new byte[] { 0x0, 0x0, 0x1, 0x1 }; + private final static byte[] EMPTY_SHORT_DATE_2 = new byte[] { 0x0, 0x0, 0x0, 0x0 }; + private final Buffer buffer; + + ItronManufacturerDataParser(Buffer buffer) { + this.buffer = buffer; + } + + public LocalDateTime readShortDateTime() { + return parseShortDateTime(buffer.readBytes(4)); + } + + public LocalDateTime readLongDateTime() { + return parseLongDateTime(buffer.readBytes(5)); + } + + private LocalDateTime parseShortDateTime(byte[] date) { + if (Arrays.equals(date, EMPTY_SHORT_DATE_1) || Arrays.equals(date, EMPTY_SHORT_DATE_2)) { + return null; + } + + int minute = date[0] & 0xFF >> 2; + + int yearLSB = (date[2] & 0xFF) >> 5; + int yearMSB = ((date[3] & 0xFF) & 0xF0) >> 1; + int hour = (date[1] & 0xFF >> 3); + int day = (date[2] & 0xFF >> 3); + int month = (date[3] & 0xFF >> 4); + + // ((0x2C & 0xF0) >> 0x1) | (0x7D >> 0x5) = 0x13 = 19d + int year = yearMSB | yearLSB; + + return LocalDateTime.of(2000 + year, month, day, hour, minute); + } + + private LocalDateTime parseLongDateTime(byte[] date) { + int second = (date[0] & 0xFF) >> 2; + int minute = (date[1] & 0xFF) >> 2; + int hour = (date[2] & 0xFF >> 3); + int day = (date[2] & 0xFF >> 4); + int month = (date[3] & 0xFF >> 4); + + int yearLSB = (date[3] & 0xFF) >> 5; + int yearMSB = ((date[4] & 0xFF) & 0xF0) >> 1; + + // ((0x2C & 0xF0) >> 0x1) | (0x7D >> 0x5) = 0x13 = 19d + int year = yearMSB | yearLSB; + + LocalTime time = LocalTime.of(hour, minute, second); + DayOfWeek dayOfWeek = DayOfWeek.of(day); + LocalDate today = LocalDate.now(); + LocalDate parsed = LocalDate.of(year, month, today.getDayOfMonth()); + + LocalTime timeWithDay = time.with(dayOfWeek); + return parsed.atStartOfDay().with(timeWithDay); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronSmokeDetectorHandler.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronSmokeDetectorHandler.java index 4585caa..97ab158 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronSmokeDetectorHandler.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/itron/ItronSmokeDetectorHandler.java @@ -1,177 +1,177 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.itron; - -import java.time.LocalDateTime; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNull; -import org.openhab.binding.wmbus.RecordType; -import org.openhab.binding.wmbus.UnitRegistry; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.device.generic.GenericWMBusThingHandler; -import org.openhab.binding.wmbus.device.techem.decoder.Buffer; -import org.openhab.core.library.types.DecimalType; -import org.openhab.core.library.types.OnOffType; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.types.Command; -import org.openhab.core.types.UnDefType; -import org.openhab.core.util.HexUtils; -import org.openhab.io.transport.mbus.wireless.KeyStorage; -import org.openmuc.jmbus.DataRecord; -import org.openmuc.jmbus.DecodingException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.primitives.Longs; - -public class ItronSmokeDetectorHandler extends GenericWMBusThingHandler { - - public static final RecordType CURRENT_DATE_06_6D = new RecordType(0x06, 0x6D); - public static final RecordType CONFIG_STATUS_07_7F = new RecordType(0x07, 0x7F); - - private final Logger logger = LoggerFactory.getLogger(ItronSmokeDetectorHandler.class); - private Map parsedFrame = new HashMap<>(); - - public ItronSmokeDetectorHandler(Thing thing, KeyStorage keyStorage, UnitRegistry unitRegistry) { - super(thing, keyStorage, unitRegistry, Collections.emptyMap()); - } - - @Override - protected WMBusDevice parseDevice(WMBusDevice device) throws DecodingException { - WMBusDevice parsedDevice = super.parseDevice(device); - - DataRecord record = device.findRecord(CONFIG_STATUS_07_7F); - if (record != null && record.getDataValueType() == DataRecord.DataValueType.LONG - && record.getDescription() == DataRecord.Description.MANUFACTURER_SPECIFIC) { - Long dataValue = (Long) record.getDataValue(); - ItronConfigStatusDataParser configStatus = new ItronConfigStatusDataParser(Longs.toByteArray(dataValue)); - - // first (MSB) byte with billing date - parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_BILLING_DATE, configStatus.getBillingDate()); - - // second byte with status codes - parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_REMOVAL_OCCURRED, configStatus.isRemovalOccurred()); - parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_PRODUCT_INSTALLED, configStatus.isProductInstalled()); - parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_OPERATION_MODE, configStatus.getOperationMode()); - parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_PERIMETER_INTRUSION_OCCURRED, - configStatus.isPerimeterIntrusionOccurred()); - parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_SMOKE_INLET_BLOCKED_OCCURRED, - configStatus.isSmokeInletBlockedOccurred()); - parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_OUT_OF_TEMP_RANGE_OCCURRED, - configStatus.isOutOfRangeTemperatureOccurred()); - - // third byte with error codes - parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_PRODUCT_CODE, - HexUtils.byteToHex(configStatus.getProductCode())); - - // fourth byte with battery lifetime - parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_BATTERY_LIFETIME, configStatus.getBatteryLifetime()); - } - - // fifth and sixth byte SD errors - // parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_PERIMETER_INTRUSION, configStatus.readBytes(2)); - - // 7tn byte is modem error codes - // parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_REMOVAL_ERROR, configStatus.readByte()); - - // 8tn byte is config byte - // parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_DATA_ENCRYPTED, configStatus.readByte()); - - byte[] manufacturerData = parsedDevice.getOriginalMessage().getVariableDataResponse().getManufacturerData(); - - Buffer buffer = new Buffer(manufacturerData); - ItronManufacturerDataParser parser = new ItronManufacturerDataParser(buffer); - - LocalDateTime eventDate = parser.readShortDateTime(); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_ALERT_START_DATE, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_ALERT_START_DATE_NUMBER, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_ALERT_START_DATE_STRING, eventDate); - - eventDate = parser.readShortDateTime(); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_ALERT_END_DATE, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_ALERT_END_DATE_NUMBER, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_ALERT_END_DATE_STRING, eventDate); - - eventDate = parser.readShortDateTime(); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_BEEPER_STOPPED_DURING_SMOKE_ALERT_DATE, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_BEEPER_STOPPED_DURING_SMOKE_ALERT_DATE_NUMBER, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_BEEPER_STOPPED_DURING_SMOKE_ALERT_DATE_STRING, eventDate); - - eventDate = parser.readShortDateTime(); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_OCCURRED_DATE, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_OCCURRED_DATE_NUMBER, - eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_OCCURRED_DATE_STRING, - eventDate); - - eventDate = parser.readShortDateTime(); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_REMOVED_DATE, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_REMOVED_DATE_NUMBER, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_REMOVED_DATE_STRING, eventDate); - - eventDate = parser.readShortDateTime(); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_INLET_BLOCKED_DATE, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_INLET_BLOCKED_DATE_NUMBER, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_INLET_BLOCKED_DATE_STRING, eventDate); - - eventDate = parser.readShortDateTime(); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_INLET_BLOCKING_REMOVED_DATE, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_INLET_BLOCKING_REMOVED_DATE_NUMBER, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_INLET_BLOCKING_REMOVED_DATE_STRING, eventDate); - - eventDate = parser.readShortDateTime(); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_TEMPERATURE_OUT_OF_RANGE_DATE, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_TEMPERATURE_OUT_OF_RANGE_DATE_NUMBER, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_TEMPERATURE_OUT_OF_RANGE_DATE_STRING, eventDate); - - eventDate = parser.readShortDateTime(); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_TEST_SWITCH_DATE, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_TEST_SWITCH_DATE_NUMBER, eventDate); - parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_TEST_SWITCH_DATE_STRING, eventDate); - - parsedFrame.put(ItronBindingConstants.CHANNEL_NUMBER_OF_TEST_SWITCHES_OPERATED, buffer.readShort()); - parsedFrame.put(ItronBindingConstants.CHANNEL_PERIMETER_INTRUSION_DAY_COUNTER_CUMULATED, buffer.readShort()); - parsedFrame.put(ItronBindingConstants.CHANNEL_SMOKE_INLET_DAY_COUNTER_CUMULATED, - buffer.available() < 2 ? buffer.readByte() : buffer.readShort()); - - return parsedDevice; - } - - @Override - public void handleCommand(@NonNull ChannelUID channelUID, @NonNull Command command) { - if (parsedFrame.containsKey(channelUID.getId())) { - // channel directly maps to manufacturer data appended to frame - logger.debug("Mapping custom smoke detector channel {} to manufacturer data", channelUID); - - Object value = parsedFrame.get(channelUID.getId()); - if (value == null) { - updateState(channelUID, UnDefType.NULL); - } else if (value instanceof LocalDateTime) { - updateState(channelUID, convertDate(value)); - } else if (value instanceof Number) { - updateState(channelUID, new DecimalType(((Number) value).floatValue())); - } else if (value instanceof Boolean) { - updateState(channelUID, ((boolean) value) ? OnOffType.ON : OnOffType.OFF); - } else { - logger.warn("Unsupported value type {}", value); - } - } else { - // try to do a lookup based on channel to record mapping - super.handleCommand(channelUID, command); - } - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.itron; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNull; +import org.openhab.binding.wmbus.RecordType; +import org.openhab.binding.wmbus.UnitRegistry; +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.device.generic.GenericWMBusThingHandler; +import org.openhab.binding.wmbus.device.techem.decoder.Buffer; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; +import org.openhab.core.types.UnDefType; +import org.openhab.core.util.HexUtils; +import org.openhab.io.transport.mbus.wireless.KeyStorage; +import org.openmuc.jmbus.DataRecord; +import org.openmuc.jmbus.DecodingException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.primitives.Longs; + +public class ItronSmokeDetectorHandler extends GenericWMBusThingHandler { + + public static final RecordType CURRENT_DATE_06_6D = new RecordType(0x06, 0x6D); + public static final RecordType CONFIG_STATUS_07_7F = new RecordType(0x07, 0x7F); + + private final Logger logger = LoggerFactory.getLogger(ItronSmokeDetectorHandler.class); + private Map parsedFrame = new HashMap<>(); + + public ItronSmokeDetectorHandler(Thing thing, KeyStorage keyStorage, UnitRegistry unitRegistry) { + super(thing, keyStorage, unitRegistry, Collections.emptyMap()); + } + + @Override + protected WMBusDevice parseDevice(WMBusDevice device) throws DecodingException { + WMBusDevice parsedDevice = super.parseDevice(device); + + DataRecord record = device.findRecord(CONFIG_STATUS_07_7F); + if (record != null && record.getDataValueType() == DataRecord.DataValueType.LONG + && record.getDescription() == DataRecord.Description.MANUFACTURER_SPECIFIC) { + Long dataValue = (Long) record.getDataValue(); + ItronConfigStatusDataParser configStatus = new ItronConfigStatusDataParser(Longs.toByteArray(dataValue)); + + // first (MSB) byte with billing date + parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_BILLING_DATE, configStatus.getBillingDate()); + + // second byte with status codes + parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_REMOVAL_OCCURRED, configStatus.isRemovalOccurred()); + parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_PRODUCT_INSTALLED, configStatus.isProductInstalled()); + parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_OPERATION_MODE, configStatus.getOperationMode()); + parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_PERIMETER_INTRUSION_OCCURRED, + configStatus.isPerimeterIntrusionOccurred()); + parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_SMOKE_INLET_BLOCKED_OCCURRED, + configStatus.isSmokeInletBlockedOccurred()); + parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_OUT_OF_TEMP_RANGE_OCCURRED, + configStatus.isOutOfRangeTemperatureOccurred()); + + // third byte with error codes + parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_PRODUCT_CODE, + HexUtils.byteToHex(configStatus.getProductCode())); + + // fourth byte with battery lifetime + parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_BATTERY_LIFETIME, configStatus.getBatteryLifetime()); + } + + // fifth and sixth byte SD errors + // parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_PERIMETER_INTRUSION, configStatus.readBytes(2)); + + // 7tn byte is modem error codes + // parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_REMOVAL_ERROR, configStatus.readByte()); + + // 8tn byte is config byte + // parsedFrame.put(ItronBindingConstants.CHANNEL_STATUS_DATA_ENCRYPTED, configStatus.readByte()); + + byte[] manufacturerData = parsedDevice.getOriginalMessage().getVariableDataResponse().getManufacturerData(); + + Buffer buffer = new Buffer(manufacturerData); + ItronManufacturerDataParser parser = new ItronManufacturerDataParser(buffer); + + LocalDateTime eventDate = parser.readShortDateTime(); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_ALERT_START_DATE, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_ALERT_START_DATE_NUMBER, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_ALERT_START_DATE_STRING, eventDate); + + eventDate = parser.readShortDateTime(); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_ALERT_END_DATE, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_ALERT_END_DATE_NUMBER, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_ALERT_END_DATE_STRING, eventDate); + + eventDate = parser.readShortDateTime(); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_BEEPER_STOPPED_DURING_SMOKE_ALERT_DATE, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_BEEPER_STOPPED_DURING_SMOKE_ALERT_DATE_NUMBER, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_BEEPER_STOPPED_DURING_SMOKE_ALERT_DATE_STRING, eventDate); + + eventDate = parser.readShortDateTime(); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_OCCURRED_DATE, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_OCCURRED_DATE_NUMBER, + eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_OCCURRED_DATE_STRING, + eventDate); + + eventDate = parser.readShortDateTime(); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_REMOVED_DATE, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_REMOVED_DATE_NUMBER, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_PERIMETER_INTRUSION_OBSTACLE_REMOVED_DATE_STRING, eventDate); + + eventDate = parser.readShortDateTime(); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_INLET_BLOCKED_DATE, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_INLET_BLOCKED_DATE_NUMBER, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_INLET_BLOCKED_DATE_STRING, eventDate); + + eventDate = parser.readShortDateTime(); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_INLET_BLOCKING_REMOVED_DATE, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_INLET_BLOCKING_REMOVED_DATE_NUMBER, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_SMOKE_INLET_BLOCKING_REMOVED_DATE_STRING, eventDate); + + eventDate = parser.readShortDateTime(); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_TEMPERATURE_OUT_OF_RANGE_DATE, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_TEMPERATURE_OUT_OF_RANGE_DATE_NUMBER, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_TEMPERATURE_OUT_OF_RANGE_DATE_STRING, eventDate); + + eventDate = parser.readShortDateTime(); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_TEST_SWITCH_DATE, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_TEST_SWITCH_DATE_NUMBER, eventDate); + parsedFrame.put(ItronBindingConstants.CHANNEL_LAST_TEST_SWITCH_DATE_STRING, eventDate); + + parsedFrame.put(ItronBindingConstants.CHANNEL_NUMBER_OF_TEST_SWITCHES_OPERATED, buffer.readShort()); + parsedFrame.put(ItronBindingConstants.CHANNEL_PERIMETER_INTRUSION_DAY_COUNTER_CUMULATED, buffer.readShort()); + parsedFrame.put(ItronBindingConstants.CHANNEL_SMOKE_INLET_DAY_COUNTER_CUMULATED, + buffer.available() < 2 ? buffer.readByte() : buffer.readShort()); + + return parsedDevice; + } + + @Override + public void handleCommand(@NonNull ChannelUID channelUID, @NonNull Command command) { + if (parsedFrame.containsKey(channelUID.getId())) { + // channel directly maps to manufacturer data appended to frame + logger.debug("Mapping custom smoke detector channel {} to manufacturer data", channelUID); + + Object value = parsedFrame.get(channelUID.getId()); + if (value == null) { + updateState(channelUID, UnDefType.NULL); + } else if (value instanceof LocalDateTime) { + updateState(channelUID, convertDate(value)); + } else if (value instanceof Number) { + updateState(channelUID, new DecimalType(((Number) value).floatValue())); + } else if (value instanceof Boolean) { + updateState(channelUID, ((boolean) value) ? OnOffType.ON : OnOffType.OFF); + } else { + logger.warn("Unsupported value type {}", value); + } + } else { + // try to do a lookup based on channel to record mapping + super.handleCommand(channelUID, command); + } + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/Record.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/Record.java index 5ddbf24..01b9b20 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/Record.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/Record.java @@ -1,65 +1,65 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.techem; - -public class Record { - - public enum Type { - CURRENT_VOLUME, - CURRENT_READING_DATE(true), - CURRENT_READING_DATE_SMOKE(true), - PAST_VOLUME, - PAST_READING_DATE(true), - ROOM_TEMPERATURE, - RADIATOR_TEMPERATURE, - RSSI, - ALMANAC, - STATUS, - COUNTER; - - private final boolean dateField; - - Type() { - this(false); - } - - Type(boolean dateField) { - this.dateField = dateField; - } - - public boolean isDate() { - return dateField; - } - } - - private final Type type; - private final T value; - - public Record(Type type, T value) { - this.type = type; - this.value = value; - } - - public final Type getType() { - return this.type; - } - - public final T getValue() { - return this.value; - } - - @Override - public String toString() { - return "Record [" + type + ", " + value + "]"; - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem; + +public class Record { + + public enum Type { + CURRENT_VOLUME, + CURRENT_READING_DATE(true), + CURRENT_READING_DATE_SMOKE(true), + PAST_VOLUME, + PAST_READING_DATE(true), + ROOM_TEMPERATURE, + RADIATOR_TEMPERATURE, + RSSI, + ALMANAC, + STATUS, + COUNTER; + + private final boolean dateField; + + Type() { + this(false); + } + + Type(boolean dateField) { + this.dateField = dateField; + } + + public boolean isDate() { + return dateField; + } + } + + private final Type type; + private final T value; + + public Record(Type type, T value) { + this.type = type; + this.value = value; + } + + public final Type getType() { + return this.type; + } + + public final T getValue() { + return this.value; + } + + @Override + public String toString() { + return "Record [" + type + ", " + value + "]"; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemBindingConstants.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemBindingConstants.java index 5ef4007..8d59818 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemBindingConstants.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemBindingConstants.java @@ -1,214 +1,214 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.techem; - -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import org.openhab.binding.wmbus.WMBusBindingConstants; -import org.openhab.binding.wmbus.device.techem.Record.Type; -import org.openhab.core.thing.ThingTypeUID; -import org.openmuc.jmbus.DeviceType; - -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; - -/** - * Subset of WMBus constants specific to Techem devices. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public interface TechemBindingConstants { - - String MANUFACTURER_ID = "TCH"; - - String CHANNEL_STATUS = "status"; - String CHANNEL_ALMANAC = "almanac"; - // temperatures - String CHANNEL_ROOMTEMPERATURE = "room_temperature"; - String CHANNEL_RADIATORTEMPERATURE = "radiator_temperature"; - // values - String CHANNEL_CURRENTREADING = "current_reading"; - String CHANNEL_LASTREADING = "last_reading"; - - // dates - String CHANNEL_LASTDATE_NUMBER = "last_date_number"; - String CHANNEL_LASTDATE_STRING = "last_date_string"; - String CHANNEL_LASTDATE = "last_date"; - String CHANNEL_CURRENTDATE_NUMBER = "current_date_number"; - String CHANNEL_CURRENTDATE_STRING = "current_date_string"; - String CHANNEL_CURRENTDATE = "current_date"; - String CHANNEL_RECEPTION = "reception"; - String CHANNEL_CURRENTDATE_SMOKE_NUMBER = "current_date_smoke_number"; - String CHANNEL_CURRENTDATE_SMOKE_STRING = "current_date_smoke_string"; - String CHANNEL_CURRENTDATE_SMOKE = "current_date_smoke"; - - // warm water version 0x70 -> 112, type 0x62 -> 98 - Variant _68TCH11298_6 = new Variant(0x70, 0x62, 0xA0, DeviceType.WARM_WATER_METER); - // cold water version 0x70 -> 112, type 0x72 -> 114 - Variant _68TCH112114_16 = new Variant(0x70, 0x72, 0xA0, DeviceType.COLD_WATER_METER); - // warm water version 0x74 -> 116, type 0x62 -> 98 - Variant _68TCH11698_6 = new Variant(0x74, 0x62, 0xA2, DeviceType.WARM_WATER_METER); - // cold water version 0x74 -> 116, type 0x72 -> 114 - Variant _68TCH116114_16 = new Variant(0x74, 0x72, 0xA2, DeviceType.COLD_WATER_METER); - // warm water version 0x95 -> 149, type 0x62 -> 98 - Variant _68TCH14998_6 = new Variant(0x95, 0x62, 0xA2, DeviceType.WARM_WATER_METER); - // cold water version 0x95 -> 149, type 0x72 -> 114 - Variant _68TCH149114_16 = new Variant(0x95, 0x72, 0xA2, DeviceType.COLD_WATER_METER); - - // heat version 0x22 -> v 34, type 0x43 -> 67 - Variant _68TCH3467_4 = new Variant(0x22, 0x43, 0xA2, DeviceType.HEAT_METER); - // heat version 0x39 -> v 57, type 0x43 -> 67 - Variant _68TCH5767_4 = new Variant(0x39, 0x43, 0xA2, DeviceType.HEAT_METER); - // heat version 0x71 -> v 113, type 0x43 -> 67 - Variant _68TCH11367_4_A0 = new Variant(0x71, 0x43, 0xA0, DeviceType.HEAT_METER); - Variant _68TCH11367_4_A2 = new Variant(0x71, 0x43, 0xA2, DeviceType.HEAT_METER); - - // heat version 0x57 -> v 87, type 0x44 -> 68 - Variant _68TCH8768_4 = new Variant(0x57, 0x44, 0xA2, DeviceType.HEAT_METER); - - // TODO Isn't this a heat meter? - // hkv version 0x45 -> 69, type 0x43 -> 67 - Variant _68TCH6967_8 = new Variant(0x45, 0x43, 0xA1, DeviceType.HEAT_COST_ALLOCATOR); - - // hkv version 0x61 -> 97, type ? - Variant _68TCH97255_8 = new Variant(0x61, DeviceType.RESERVED.getId(), 0xA2, DeviceType.HEAT_COST_ALLOCATOR); - // hkv version 0x64 -> 100, type 0x80 -> 128 - Variant _68TCH100128_8 = new Variant(0x64, 0x80, 0xA0, DeviceType.HEAT_COST_ALLOCATOR); - // hkv version 0x69 -> 105, type 0x80 -> 128 - Variant _68TCH105128_8 = new Variant(0x69, 0x80, 0xA0, DeviceType.HEAT_COST_ALLOCATOR); - // hkv version 0x94 -> 148, type 0x80 -> 128 - Variant _68TCH148128_8 = new Variant(0x94, 0x80, 0xA2, DeviceType.HEAT_COST_ALLOCATOR); - - // smoke detector version 0x76 -> 118, type 0xf0 -> 240 - Variant _68TCH118255_161_A0 = new Variant(0x76, 0xf0, 0xA0, DeviceType.SMOKE_DETECTOR); - Variant _68TCH118255_161_A1 = new Variant(0x76, 0xf0, 0xA1, DeviceType.SMOKE_DETECTOR); - - // water meters - String THING_TYPE_NAME_TECHEM_WARM_WATER_METER = "techem_wz62"; - String THING_TYPE_NAME_TECHEM_COLD_WATER_METER = "techem_wz72"; - - // heat meter - String THING_TYPE_NAME_TECHEM_HEAT_METER = "techem_wmz43"; - - // techem heat cost allocators (Heizkostenverteiler) - String THING_TYPE_NAME_TECHEM_HKV45 = "techem_hkv45"; - String THING_TYPE_NAME_TECHEM_HKV61 = "techem_hkv61"; - String THING_TYPE_NAME_TECHEM_HKV64 = "techem_hkv64"; - String THING_TYPE_NAME_TECHEM_HKV69 = "techem_hkv69"; - String THING_TYPE_NAME_TECHEM_HKV94 = "techem_hkv94"; - - // techem smoke detector - String THING_TYPE_NAME_TECHEM_SD76 = "techem_sd76"; - - ThingTypeUID THING_TYPE_TECHEM_WARM_WATER_METER = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, - THING_TYPE_NAME_TECHEM_WARM_WATER_METER); - ThingTypeUID THING_TYPE_TECHEM_COLD_WATER_METER = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, - THING_TYPE_NAME_TECHEM_COLD_WATER_METER); - - ThingTypeUID THING_TYPE_TECHEM_HEAT_METER = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, - THING_TYPE_NAME_TECHEM_HEAT_METER); - - ThingTypeUID THING_TYPE_TECHEM_HKV45 = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, - THING_TYPE_NAME_TECHEM_HKV45); - ThingTypeUID THING_TYPE_TECHEM_HKV61 = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, - THING_TYPE_NAME_TECHEM_HKV61); - ThingTypeUID THING_TYPE_TECHEM_HKV64 = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, - THING_TYPE_NAME_TECHEM_HKV64); - ThingTypeUID THING_TYPE_TECHEM_HKV69 = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, - THING_TYPE_NAME_TECHEM_HKV69); - ThingTypeUID THING_TYPE_TECHEM_HKV94 = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, - THING_TYPE_NAME_TECHEM_HKV94); - - ThingTypeUID THING_TYPE_TECHEM_SD76 = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, - THING_TYPE_NAME_TECHEM_SD76); - - // TODO remove this part once all deployments are migrated - // old device type, remained here as alias for hkv64 - String THING_TYPE_NAME_TECHEM_HKV = "techem_hkv"; - ThingTypeUID THING_TYPE_TECHEM_HKV = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, THING_TYPE_NAME_TECHEM_HKV); - - Map SUPPORTED_DEVICE_VARIANTS = ImmutableMap. builder() - .put(_68TCH11298_6, THING_TYPE_TECHEM_WARM_WATER_METER) // WZ 112_62 - .put(_68TCH112114_16, THING_TYPE_TECHEM_COLD_WATER_METER) // WZ 112_72 - .put(_68TCH11698_6, THING_TYPE_TECHEM_WARM_WATER_METER) // WZ 116_62 - .put(_68TCH116114_16, THING_TYPE_TECHEM_COLD_WATER_METER) // WZ 116_72 - .put(_68TCH14998_6, THING_TYPE_TECHEM_WARM_WATER_METER) // WZ 149_62 - .put(_68TCH149114_16, THING_TYPE_TECHEM_COLD_WATER_METER) // WZ 149_72 - .put(_68TCH3467_4, THING_TYPE_TECHEM_HEAT_METER) // WMZ 34_43 - .put(_68TCH5767_4, THING_TYPE_TECHEM_HEAT_METER) // WMZ 57_43 - .put(_68TCH11367_4_A0, THING_TYPE_TECHEM_HEAT_METER) // WMZ 113_43 with A0 encoding - .put(_68TCH11367_4_A2, THING_TYPE_TECHEM_HEAT_METER) // WMZ 113_43 with A2 encoding - .put(_68TCH8768_4, THING_TYPE_TECHEM_HEAT_METER) // WMZ 87_44 with A2 encoding - .put(_68TCH6967_8, THING_TYPE_TECHEM_HKV45) // HKV 45 - .put(_68TCH97255_8, THING_TYPE_TECHEM_HKV61) // HKV 61 - .put(_68TCH100128_8, THING_TYPE_TECHEM_HKV64) // HKV 64 - .put(_68TCH105128_8, THING_TYPE_TECHEM_HKV69) // HKV 69 - .put(_68TCH148128_8, THING_TYPE_TECHEM_HKV94) // HKV 94 - .put(_68TCH118255_161_A0, THING_TYPE_TECHEM_SD76) // SD 76 with A0 encoding - .put(_68TCH118255_161_A1, THING_TYPE_TECHEM_SD76) // SD 76 with A1 encoding - .build(); - - Set SUPPORTED_DEVICE_TYPES = ImmutableSet - .copyOf(SUPPORTED_DEVICE_VARIANTS.keySet().stream().map(Variant::getRawType).collect(Collectors.toSet())); - - Set SUPPORTED_THING_TYPES = ImmutableSet.copyOf(SUPPORTED_DEVICE_VARIANTS.values()); - - // List all channels - // general channels - Map TECHEM_METER_MAPPING = ImmutableMap. builder() - .put(CHANNEL_CURRENTREADING, Type.CURRENT_VOLUME) // current value - .put(CHANNEL_CURRENTDATE, Type.CURRENT_READING_DATE) // present date - .put(CHANNEL_CURRENTDATE_STRING, Type.CURRENT_READING_DATE) // present date - .put(CHANNEL_CURRENTDATE_NUMBER, Type.CURRENT_READING_DATE) // present date - .put(CHANNEL_LASTREADING, Type.PAST_VOLUME) // last billing value - .put(CHANNEL_LASTDATE, Type.PAST_READING_DATE) // past billing date - .put(CHANNEL_LASTDATE_STRING, Type.PAST_READING_DATE) // past billing date - .put(CHANNEL_LASTDATE_NUMBER, Type.PAST_READING_DATE) // past billing date - .put(CHANNEL_RECEPTION, Type.RSSI) // Received Signal Strength Indicator - .put(CHANNEL_ALMANAC, Type.ALMANAC) // bi-weekly history - .build(); - - Map HEAT_ALLOCATOR_MAPPING_69 = ImmutableMap. builder() - .putAll(TECHEM_METER_MAPPING) // inherit main HKV channel map - .put(CHANNEL_ROOMTEMPERATURE, Type.ROOM_TEMPERATURE) // room - .put(CHANNEL_RADIATORTEMPERATURE, Type.RADIATOR_TEMPERATURE) // radiator - .build(); - - // measurement readings - Map SMOKE_DETECTOR_MAPPING = ImmutableMap. builder() - .put(CHANNEL_STATUS, Type.STATUS) // present date - .put(CHANNEL_CURRENTDATE, Type.CURRENT_READING_DATE) // present date - .put(CHANNEL_CURRENTDATE_STRING, Type.CURRENT_READING_DATE) // present date - .put(CHANNEL_CURRENTDATE_NUMBER, Type.CURRENT_READING_DATE) // present date - .put(CHANNEL_CURRENTDATE_SMOKE, Type.CURRENT_READING_DATE_SMOKE) // smoke detector 2nd date - .put(CHANNEL_CURRENTDATE_SMOKE_STRING, Type.CURRENT_READING_DATE_SMOKE) // smoke detector 2nd date - .put(CHANNEL_CURRENTDATE_SMOKE_NUMBER, Type.CURRENT_READING_DATE_SMOKE) // smoke detector 2nd date - .build(); - - // channel mapping for thing types - Map> RECORD_MAP = ImmutableMap.> builder() - .put(THING_TYPE_TECHEM_WARM_WATER_METER, TECHEM_METER_MAPPING) // warm - .put(THING_TYPE_TECHEM_COLD_WATER_METER, TECHEM_METER_MAPPING) // cold - - .put(THING_TYPE_TECHEM_HEAT_METER, TECHEM_METER_MAPPING) // heat meter have same set of channels as heat - // cost allocator - .put(THING_TYPE_TECHEM_HKV45, TECHEM_METER_MAPPING) // basic HKV mapping - .put(THING_TYPE_TECHEM_HKV61, TECHEM_METER_MAPPING) // basic HKV mapping - .put(THING_TYPE_TECHEM_HKV64, TECHEM_METER_MAPPING) // again basic HKV mapping - .put(THING_TYPE_TECHEM_HKV69, HEAT_ALLOCATOR_MAPPING_69) // here we have two temperature channels - .put(THING_TYPE_TECHEM_HKV94, HEAT_ALLOCATOR_MAPPING_69) // try to decode 0x94 variant in same way as 0x69 - .put(THING_TYPE_TECHEM_SD76, SMOKE_DETECTOR_MAPPING) // v118 is smoke detector, experimental channels apply - .build(); -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem; + +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.openhab.binding.wmbus.WMBusBindingConstants; +import org.openhab.binding.wmbus.device.techem.Record.Type; +import org.openhab.core.thing.ThingTypeUID; +import org.openmuc.jmbus.DeviceType; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +/** + * Subset of WMBus constants specific to Techem devices. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public interface TechemBindingConstants { + + String MANUFACTURER_ID = "TCH"; + + String CHANNEL_STATUS = "status"; + String CHANNEL_ALMANAC = "almanac"; + // temperatures + String CHANNEL_ROOMTEMPERATURE = "room_temperature"; + String CHANNEL_RADIATORTEMPERATURE = "radiator_temperature"; + // values + String CHANNEL_CURRENTREADING = "current_reading"; + String CHANNEL_LASTREADING = "last_reading"; + + // dates + String CHANNEL_LASTDATE_NUMBER = "last_date_number"; + String CHANNEL_LASTDATE_STRING = "last_date_string"; + String CHANNEL_LASTDATE = "last_date"; + String CHANNEL_CURRENTDATE_NUMBER = "current_date_number"; + String CHANNEL_CURRENTDATE_STRING = "current_date_string"; + String CHANNEL_CURRENTDATE = "current_date"; + String CHANNEL_RECEPTION = "reception"; + String CHANNEL_CURRENTDATE_SMOKE_NUMBER = "current_date_smoke_number"; + String CHANNEL_CURRENTDATE_SMOKE_STRING = "current_date_smoke_string"; + String CHANNEL_CURRENTDATE_SMOKE = "current_date_smoke"; + + // warm water version 0x70 -> 112, type 0x62 -> 98 + Variant _68TCH11298_6 = new Variant(0x70, 0x62, 0xA0, DeviceType.WARM_WATER_METER); + // cold water version 0x70 -> 112, type 0x72 -> 114 + Variant _68TCH112114_16 = new Variant(0x70, 0x72, 0xA0, DeviceType.COLD_WATER_METER); + // warm water version 0x74 -> 116, type 0x62 -> 98 + Variant _68TCH11698_6 = new Variant(0x74, 0x62, 0xA2, DeviceType.WARM_WATER_METER); + // cold water version 0x74 -> 116, type 0x72 -> 114 + Variant _68TCH116114_16 = new Variant(0x74, 0x72, 0xA2, DeviceType.COLD_WATER_METER); + // warm water version 0x95 -> 149, type 0x62 -> 98 + Variant _68TCH14998_6 = new Variant(0x95, 0x62, 0xA2, DeviceType.WARM_WATER_METER); + // cold water version 0x95 -> 149, type 0x72 -> 114 + Variant _68TCH149114_16 = new Variant(0x95, 0x72, 0xA2, DeviceType.COLD_WATER_METER); + + // heat version 0x22 -> v 34, type 0x43 -> 67 + Variant _68TCH3467_4 = new Variant(0x22, 0x43, 0xA2, DeviceType.HEAT_METER); + // heat version 0x39 -> v 57, type 0x43 -> 67 + Variant _68TCH5767_4 = new Variant(0x39, 0x43, 0xA2, DeviceType.HEAT_METER); + // heat version 0x71 -> v 113, type 0x43 -> 67 + Variant _68TCH11367_4_A0 = new Variant(0x71, 0x43, 0xA0, DeviceType.HEAT_METER); + Variant _68TCH11367_4_A2 = new Variant(0x71, 0x43, 0xA2, DeviceType.HEAT_METER); + + // heat version 0x57 -> v 87, type 0x44 -> 68 + Variant _68TCH8768_4 = new Variant(0x57, 0x44, 0xA2, DeviceType.HEAT_METER); + + // TODO Isn't this a heat meter? + // hkv version 0x45 -> 69, type 0x43 -> 67 + Variant _68TCH6967_8 = new Variant(0x45, 0x43, 0xA1, DeviceType.HEAT_COST_ALLOCATOR); + + // hkv version 0x61 -> 97, type ? + Variant _68TCH97255_8 = new Variant(0x61, DeviceType.RESERVED.getId(), 0xA2, DeviceType.HEAT_COST_ALLOCATOR); + // hkv version 0x64 -> 100, type 0x80 -> 128 + Variant _68TCH100128_8 = new Variant(0x64, 0x80, 0xA0, DeviceType.HEAT_COST_ALLOCATOR); + // hkv version 0x69 -> 105, type 0x80 -> 128 + Variant _68TCH105128_8 = new Variant(0x69, 0x80, 0xA0, DeviceType.HEAT_COST_ALLOCATOR); + // hkv version 0x94 -> 148, type 0x80 -> 128 + Variant _68TCH148128_8 = new Variant(0x94, 0x80, 0xA2, DeviceType.HEAT_COST_ALLOCATOR); + + // smoke detector version 0x76 -> 118, type 0xf0 -> 240 + Variant _68TCH118255_161_A0 = new Variant(0x76, 0xf0, 0xA0, DeviceType.SMOKE_DETECTOR); + Variant _68TCH118255_161_A1 = new Variant(0x76, 0xf0, 0xA1, DeviceType.SMOKE_DETECTOR); + + // water meters + String THING_TYPE_NAME_TECHEM_WARM_WATER_METER = "techem_wz62"; + String THING_TYPE_NAME_TECHEM_COLD_WATER_METER = "techem_wz72"; + + // heat meter + String THING_TYPE_NAME_TECHEM_HEAT_METER = "techem_wmz43"; + + // techem heat cost allocators (Heizkostenverteiler) + String THING_TYPE_NAME_TECHEM_HKV45 = "techem_hkv45"; + String THING_TYPE_NAME_TECHEM_HKV61 = "techem_hkv61"; + String THING_TYPE_NAME_TECHEM_HKV64 = "techem_hkv64"; + String THING_TYPE_NAME_TECHEM_HKV69 = "techem_hkv69"; + String THING_TYPE_NAME_TECHEM_HKV94 = "techem_hkv94"; + + // techem smoke detector + String THING_TYPE_NAME_TECHEM_SD76 = "techem_sd76"; + + ThingTypeUID THING_TYPE_TECHEM_WARM_WATER_METER = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, + THING_TYPE_NAME_TECHEM_WARM_WATER_METER); + ThingTypeUID THING_TYPE_TECHEM_COLD_WATER_METER = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, + THING_TYPE_NAME_TECHEM_COLD_WATER_METER); + + ThingTypeUID THING_TYPE_TECHEM_HEAT_METER = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, + THING_TYPE_NAME_TECHEM_HEAT_METER); + + ThingTypeUID THING_TYPE_TECHEM_HKV45 = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, + THING_TYPE_NAME_TECHEM_HKV45); + ThingTypeUID THING_TYPE_TECHEM_HKV61 = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, + THING_TYPE_NAME_TECHEM_HKV61); + ThingTypeUID THING_TYPE_TECHEM_HKV64 = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, + THING_TYPE_NAME_TECHEM_HKV64); + ThingTypeUID THING_TYPE_TECHEM_HKV69 = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, + THING_TYPE_NAME_TECHEM_HKV69); + ThingTypeUID THING_TYPE_TECHEM_HKV94 = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, + THING_TYPE_NAME_TECHEM_HKV94); + + ThingTypeUID THING_TYPE_TECHEM_SD76 = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, + THING_TYPE_NAME_TECHEM_SD76); + + // TODO remove this part once all deployments are migrated + // old device type, remained here as alias for hkv64 + String THING_TYPE_NAME_TECHEM_HKV = "techem_hkv"; + ThingTypeUID THING_TYPE_TECHEM_HKV = new ThingTypeUID(WMBusBindingConstants.BINDING_ID, THING_TYPE_NAME_TECHEM_HKV); + + Map SUPPORTED_DEVICE_VARIANTS = ImmutableMap. builder() + .put(_68TCH11298_6, THING_TYPE_TECHEM_WARM_WATER_METER) // WZ 112_62 + .put(_68TCH112114_16, THING_TYPE_TECHEM_COLD_WATER_METER) // WZ 112_72 + .put(_68TCH11698_6, THING_TYPE_TECHEM_WARM_WATER_METER) // WZ 116_62 + .put(_68TCH116114_16, THING_TYPE_TECHEM_COLD_WATER_METER) // WZ 116_72 + .put(_68TCH14998_6, THING_TYPE_TECHEM_WARM_WATER_METER) // WZ 149_62 + .put(_68TCH149114_16, THING_TYPE_TECHEM_COLD_WATER_METER) // WZ 149_72 + .put(_68TCH3467_4, THING_TYPE_TECHEM_HEAT_METER) // WMZ 34_43 + .put(_68TCH5767_4, THING_TYPE_TECHEM_HEAT_METER) // WMZ 57_43 + .put(_68TCH11367_4_A0, THING_TYPE_TECHEM_HEAT_METER) // WMZ 113_43 with A0 encoding + .put(_68TCH11367_4_A2, THING_TYPE_TECHEM_HEAT_METER) // WMZ 113_43 with A2 encoding + .put(_68TCH8768_4, THING_TYPE_TECHEM_HEAT_METER) // WMZ 87_44 with A2 encoding + .put(_68TCH6967_8, THING_TYPE_TECHEM_HKV45) // HKV 45 + .put(_68TCH97255_8, THING_TYPE_TECHEM_HKV61) // HKV 61 + .put(_68TCH100128_8, THING_TYPE_TECHEM_HKV64) // HKV 64 + .put(_68TCH105128_8, THING_TYPE_TECHEM_HKV69) // HKV 69 + .put(_68TCH148128_8, THING_TYPE_TECHEM_HKV94) // HKV 94 + .put(_68TCH118255_161_A0, THING_TYPE_TECHEM_SD76) // SD 76 with A0 encoding + .put(_68TCH118255_161_A1, THING_TYPE_TECHEM_SD76) // SD 76 with A1 encoding + .build(); + + Set SUPPORTED_DEVICE_TYPES = ImmutableSet + .copyOf(SUPPORTED_DEVICE_VARIANTS.keySet().stream().map(Variant::getRawType).collect(Collectors.toSet())); + + Set SUPPORTED_THING_TYPES = ImmutableSet.copyOf(SUPPORTED_DEVICE_VARIANTS.values()); + + // List all channels + // general channels + Map TECHEM_METER_MAPPING = ImmutableMap. builder() + .put(CHANNEL_CURRENTREADING, Type.CURRENT_VOLUME) // current value + .put(CHANNEL_CURRENTDATE, Type.CURRENT_READING_DATE) // present date + .put(CHANNEL_CURRENTDATE_STRING, Type.CURRENT_READING_DATE) // present date + .put(CHANNEL_CURRENTDATE_NUMBER, Type.CURRENT_READING_DATE) // present date + .put(CHANNEL_LASTREADING, Type.PAST_VOLUME) // last billing value + .put(CHANNEL_LASTDATE, Type.PAST_READING_DATE) // past billing date + .put(CHANNEL_LASTDATE_STRING, Type.PAST_READING_DATE) // past billing date + .put(CHANNEL_LASTDATE_NUMBER, Type.PAST_READING_DATE) // past billing date + .put(CHANNEL_RECEPTION, Type.RSSI) // Received Signal Strength Indicator + .put(CHANNEL_ALMANAC, Type.ALMANAC) // bi-weekly history + .build(); + + Map HEAT_ALLOCATOR_MAPPING_69 = ImmutableMap. builder() + .putAll(TECHEM_METER_MAPPING) // inherit main HKV channel map + .put(CHANNEL_ROOMTEMPERATURE, Type.ROOM_TEMPERATURE) // room + .put(CHANNEL_RADIATORTEMPERATURE, Type.RADIATOR_TEMPERATURE) // radiator + .build(); + + // measurement readings + Map SMOKE_DETECTOR_MAPPING = ImmutableMap. builder() + .put(CHANNEL_STATUS, Type.STATUS) // present date + .put(CHANNEL_CURRENTDATE, Type.CURRENT_READING_DATE) // present date + .put(CHANNEL_CURRENTDATE_STRING, Type.CURRENT_READING_DATE) // present date + .put(CHANNEL_CURRENTDATE_NUMBER, Type.CURRENT_READING_DATE) // present date + .put(CHANNEL_CURRENTDATE_SMOKE, Type.CURRENT_READING_DATE_SMOKE) // smoke detector 2nd date + .put(CHANNEL_CURRENTDATE_SMOKE_STRING, Type.CURRENT_READING_DATE_SMOKE) // smoke detector 2nd date + .put(CHANNEL_CURRENTDATE_SMOKE_NUMBER, Type.CURRENT_READING_DATE_SMOKE) // smoke detector 2nd date + .build(); + + // channel mapping for thing types + Map> RECORD_MAP = ImmutableMap.> builder() + .put(THING_TYPE_TECHEM_WARM_WATER_METER, TECHEM_METER_MAPPING) // warm + .put(THING_TYPE_TECHEM_COLD_WATER_METER, TECHEM_METER_MAPPING) // cold + + .put(THING_TYPE_TECHEM_HEAT_METER, TECHEM_METER_MAPPING) // heat meter have same set of channels as heat + // cost allocator + .put(THING_TYPE_TECHEM_HKV45, TECHEM_METER_MAPPING) // basic HKV mapping + .put(THING_TYPE_TECHEM_HKV61, TECHEM_METER_MAPPING) // basic HKV mapping + .put(THING_TYPE_TECHEM_HKV64, TECHEM_METER_MAPPING) // again basic HKV mapping + .put(THING_TYPE_TECHEM_HKV69, HEAT_ALLOCATOR_MAPPING_69) // here we have two temperature channels + .put(THING_TYPE_TECHEM_HKV94, HEAT_ALLOCATOR_MAPPING_69) // try to decode 0x94 variant in same way as 0x69 + .put(THING_TYPE_TECHEM_SD76, SMOKE_DETECTOR_MAPPING) // v118 is smoke detector, experimental channels apply + .build(); +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemDevice.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemDevice.java index 7ee6f6e..6afb726 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemDevice.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemDevice.java @@ -1,62 +1,62 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.techem; - -import java.util.List; -import java.util.Optional; - -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.device.techem.Record.Type; -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openmuc.jmbus.DeviceType; -import org.openmuc.jmbus.wireless.WMBusMessage; - -/** - * The {@link TechemDevice} groups devices manufactured by Techem. - * - * @author Łukasz Dywicki - Initial contribution - */ -public class TechemDevice extends WMBusDevice { - - private final List> measurements; - private final Variant variant; - - protected TechemDevice(WMBusMessage originalMessage, WMBusAdapter adapter, Variant variant, - List> measurements) { - super(originalMessage, adapter); - this.variant = variant; - this.measurements = measurements; - } - - public final DeviceType getTechemDeviceType() { - return variant.getDesiredWMBusType(); - } - - public Variant getDeviceVariant() { - return variant; - } - - @Override - public String getDeviceType() { - return variant.getTechemType(); - } - - public List> getMeasurements() { - return measurements; - } - - public Optional> getRecord(Type type) { - return measurements.stream().filter(record -> record.getType().equals(type)).findFirst(); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem; + +import java.util.List; +import java.util.Optional; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.device.techem.Record.Type; +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openmuc.jmbus.DeviceType; +import org.openmuc.jmbus.wireless.WMBusMessage; + +/** + * The {@link TechemDevice} groups devices manufactured by Techem. + * + * @author Łukasz Dywicki - Initial contribution + */ +public class TechemDevice extends WMBusDevice { + + private final List> measurements; + private final Variant variant; + + protected TechemDevice(WMBusMessage originalMessage, WMBusAdapter adapter, Variant variant, + List> measurements) { + super(originalMessage, adapter); + this.variant = variant; + this.measurements = measurements; + } + + public final DeviceType getTechemDeviceType() { + return variant.getDesiredWMBusType(); + } + + public Variant getDeviceVariant() { + return variant; + } + + @Override + public String getDeviceType() { + return variant.getTechemType(); + } + + public List> getMeasurements() { + return measurements; + } + + public Optional> getRecord(Type type) { + return measurements.stream().filter(record -> record.getType().equals(type)).findFirst(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemDiscoveryParticipant.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemDiscoveryParticipant.java index 7ae6789..eecee90 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemDiscoveryParticipant.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemDiscoveryParticipant.java @@ -1,152 +1,152 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.techem; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.wmbus.BindingConfiguration; -import org.openhab.binding.wmbus.WMBusBindingConstants; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.device.techem.decoder.TechemFrameDecoder; -import org.openhab.binding.wmbus.discovery.WMBusDiscoveryParticipant; -import org.openhab.core.config.discovery.DiscoveryResult; -import org.openhab.core.config.discovery.DiscoveryResultBuilder; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.ThingUID; -import org.openmuc.jmbus.SecondaryAddress; -import org.openmuc.jmbus.wireless.WMBusMessage; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Reference; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Discovers techem devices and decodes records which are broadcasted by it. - * - * @author Łukasz Dywicki - extraction of logic from compound discovery service. - */ -@Component -public class TechemDiscoveryParticipant implements WMBusDiscoveryParticipant { - - private final Logger logger = LoggerFactory.getLogger(TechemDiscoveryParticipant.class); - - private TechemFrameDecoder techemFrameDecoder; - - private BindingConfiguration configuration; - - @Override - public @NonNull Set getSupportedThingTypeUIDs() { - return TechemBindingConstants.SUPPORTED_THING_TYPES; - } - - @Override - public @Nullable ThingUID getThingUID(WMBusDevice device) { - if (configuration.getIncludeBridgeUID()) { - return decodeDevice(device).map(this::getThingType) - .map(type -> new ThingUID(type, device.getAdapter().getUID(), device.getDeviceId())).orElse(null); - } else { - return decodeDevice(device).map(this::getThingType).map(type -> new ThingUID(type, device.getDeviceId())) - .orElse(null); - } - } - - protected @Nullable ThingUID getThingUID(TechemDevice device) { - if (configuration.getIncludeBridgeUID()) { - return Optional.ofNullable(getThingType(device)) - .map(type -> new ThingUID(type, device.getAdapter().getUID(), device.getDeviceId())).orElse(null); - } else { - return Optional.ofNullable(getThingType(device)).map(type -> new ThingUID(type, device.getDeviceId())) - .orElse(null); - } - } - - @Override - public DiscoveryResult createResult(WMBusDevice device) { - Optional decodeDevice = decodeDevice(device); - ThingUID thingUID = decodeDevice.map(this::getThingUID).orElse(null); - - if (thingUID != null) { - String deviceTypeLabel = decodeDevice.map(TechemDevice::getTechemDeviceType) - .map(WMBusBindingConstants.DEVICE_TYPE_TRANSFORMATION).orElse("Unknown"); - Variant deviceTypeTag = decodeDevice.map(TechemDevice::getDeviceVariant).orElseThrow( - () -> new IllegalArgumentException("Unmapped techem device " + device.getDeviceType())); - - String label = "Techem " + deviceTypeLabel + " #" + device.getDeviceId() + " (" - + deviceTypeTag.getTechemType() + ")"; - - Map properties = new HashMap<>(); - properties.put(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS, device.getDeviceAddress()); - properties.put(Thing.PROPERTY_VENDOR, "Techem"); - properties.put(Thing.PROPERTY_SERIAL_NUMBER, device.getDeviceId()); - SecondaryAddress secondaryAddress = device.getOriginalMessage().getSecondaryAddress(); - properties.put(Thing.PROPERTY_MODEL_ID, secondaryAddress.getVersion()); - - // Create the discovery result and add to the inbox - return DiscoveryResultBuilder.create(thingUID).withProperties(properties) - .withRepresentationProperty(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS).withLabel(label) - .withThingType(TechemBindingConstants.SUPPORTED_DEVICE_VARIANTS.get(deviceTypeTag)) - .withBridge(device.getAdapter().getUID()).withLabel(label).withTTL(getTimeToLive()).build(); - } - - return null; - } - - private final Optional decodeDevice(WMBusDevice device) { - WMBusMessage message = device.getOriginalMessage(); - - if (!"TCH".equals(message.getSecondaryAddress().getManufacturerId())) { - return Optional.empty(); - } - - if (!TechemBindingConstants.SUPPORTED_DEVICE_TYPES.contains(device.getRawDeviceType())) { - logger.trace("Found unsupported Techem device {}, omitting it from discovery results.", - device.getDeviceType()); - return Optional.empty(); - } - - logger.trace("Attempt to decode received Techem telegram"); - return Optional.ofNullable(techemFrameDecoder.decode(device)); - } - - private @Nullable ThingTypeUID getThingType(TechemDevice device) { - return TechemBindingConstants.SUPPORTED_DEVICE_VARIANTS.get(device.getDeviceVariant()); - } - - @Reference - public void setTechemFrameDecoder(TechemFrameDecoder techemFrameDecoder) { - this.techemFrameDecoder = techemFrameDecoder; - } - - public void unsetTechemFrameDecoder(TechemFrameDecoder techemFrameDecoder) { - this.techemFrameDecoder = null; - } - - @Reference - public void setBindingConfiguration(BindingConfiguration configuration) { - this.configuration = configuration; - } - - public void unsetBindingConfiguration(BindingConfiguration configuration) { - this.configuration = null; - } - - private Long getTimeToLive() { - return configuration.getTimeToLive(); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.wmbus.BindingConfiguration; +import org.openhab.binding.wmbus.WMBusBindingConstants; +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.device.techem.decoder.TechemFrameDecoder; +import org.openhab.binding.wmbus.discovery.WMBusDiscoveryParticipant; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.openmuc.jmbus.SecondaryAddress; +import org.openmuc.jmbus.wireless.WMBusMessage; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Discovers techem devices and decodes records which are broadcasted by it. + * + * @author Łukasz Dywicki - extraction of logic from compound discovery service. + */ +@Component +public class TechemDiscoveryParticipant implements WMBusDiscoveryParticipant { + + private final Logger logger = LoggerFactory.getLogger(TechemDiscoveryParticipant.class); + + private TechemFrameDecoder techemFrameDecoder; + + private BindingConfiguration configuration; + + @Override + public @NonNull Set getSupportedThingTypeUIDs() { + return TechemBindingConstants.SUPPORTED_THING_TYPES; + } + + @Override + public @Nullable ThingUID getThingUID(WMBusDevice device) { + if (configuration.getIncludeBridgeUID()) { + return decodeDevice(device).map(this::getThingType) + .map(type -> new ThingUID(type, device.getAdapter().getUID(), device.getDeviceId())).orElse(null); + } else { + return decodeDevice(device).map(this::getThingType).map(type -> new ThingUID(type, device.getDeviceId())) + .orElse(null); + } + } + + protected @Nullable ThingUID getThingUID(TechemDevice device) { + if (configuration.getIncludeBridgeUID()) { + return Optional.ofNullable(getThingType(device)) + .map(type -> new ThingUID(type, device.getAdapter().getUID(), device.getDeviceId())).orElse(null); + } else { + return Optional.ofNullable(getThingType(device)).map(type -> new ThingUID(type, device.getDeviceId())) + .orElse(null); + } + } + + @Override + public DiscoveryResult createResult(WMBusDevice device) { + Optional decodeDevice = decodeDevice(device); + ThingUID thingUID = decodeDevice.map(this::getThingUID).orElse(null); + + if (thingUID != null) { + String deviceTypeLabel = decodeDevice.map(TechemDevice::getTechemDeviceType) + .map(WMBusBindingConstants.DEVICE_TYPE_TRANSFORMATION).orElse("Unknown"); + Variant deviceTypeTag = decodeDevice.map(TechemDevice::getDeviceVariant).orElseThrow( + () -> new IllegalArgumentException("Unmapped techem device " + device.getDeviceType())); + + String label = "Techem " + deviceTypeLabel + " #" + device.getDeviceId() + " (" + + deviceTypeTag.getTechemType() + ")"; + + Map properties = new HashMap<>(); + properties.put(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS, device.getDeviceAddress()); + properties.put(Thing.PROPERTY_VENDOR, "Techem"); + properties.put(Thing.PROPERTY_SERIAL_NUMBER, device.getDeviceId()); + SecondaryAddress secondaryAddress = device.getOriginalMessage().getSecondaryAddress(); + properties.put(Thing.PROPERTY_MODEL_ID, secondaryAddress.getVersion()); + + // Create the discovery result and add to the inbox + return DiscoveryResultBuilder.create(thingUID).withProperties(properties) + .withRepresentationProperty(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS).withLabel(label) + .withThingType(TechemBindingConstants.SUPPORTED_DEVICE_VARIANTS.get(deviceTypeTag)) + .withBridge(device.getAdapter().getUID()).withLabel(label).withTTL(getTimeToLive()).build(); + } + + return null; + } + + private final Optional decodeDevice(WMBusDevice device) { + WMBusMessage message = device.getOriginalMessage(); + + if (!"TCH".equals(message.getSecondaryAddress().getManufacturerId())) { + return Optional.empty(); + } + + if (!TechemBindingConstants.SUPPORTED_DEVICE_TYPES.contains(device.getRawDeviceType())) { + logger.trace("Found unsupported Techem device {}, omitting it from discovery results.", + device.getDeviceType()); + return Optional.empty(); + } + + logger.trace("Attempt to decode received Techem telegram"); + return Optional.ofNullable(techemFrameDecoder.decode(device)); + } + + private @Nullable ThingTypeUID getThingType(TechemDevice device) { + return TechemBindingConstants.SUPPORTED_DEVICE_VARIANTS.get(device.getDeviceVariant()); + } + + @Reference + public void setTechemFrameDecoder(TechemFrameDecoder techemFrameDecoder) { + this.techemFrameDecoder = techemFrameDecoder; + } + + public void unsetTechemFrameDecoder(TechemFrameDecoder techemFrameDecoder) { + this.techemFrameDecoder = null; + } + + @Reference + public void setBindingConfiguration(BindingConfiguration configuration) { + this.configuration = configuration; + } + + public void unsetBindingConfiguration(BindingConfiguration configuration) { + this.configuration = null; + } + + private Long getTimeToLive() { + return configuration.getTimeToLive(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemHandlerFactory.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemHandlerFactory.java index f9d0c5a..a1e71e3 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemHandlerFactory.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemHandlerFactory.java @@ -1,102 +1,102 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.techem; - -import org.openhab.binding.wmbus.device.techem.decoder.TechemFrameDecoder; -import org.openhab.binding.wmbus.device.techem.handler.TechemMeterHandler; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.binding.BaseThingHandlerFactory; -import org.openhab.core.thing.binding.ThingHandler; -import org.openhab.core.thing.binding.ThingHandlerFactory; -import org.osgi.service.component.ComponentContext; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Deactivate; -import org.osgi.service.component.annotations.Reference; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link TechemHandlerFactory} covers logic specific to TechemH devices. - * - * @author Łukasz Dywicki - Initial contribution - */ - -@Component(service = { TechemHandlerFactory.class, BaseThingHandlerFactory.class, ThingHandlerFactory.class }) -public class TechemHandlerFactory extends BaseThingHandlerFactory { - - // OpenHAB logger - private final Logger logger = LoggerFactory.getLogger(TechemHandlerFactory.class); - - private TechemFrameDecoder techemFrameDecoder; - - public TechemHandlerFactory() { - logger.debug("Techem handler factory starting up."); - } - - @Override - public boolean supportsThingType(ThingTypeUID thingTypeUID) { - return TechemBindingConstants.SUPPORTED_THING_TYPES.contains(thingTypeUID); - } - - @Override - protected ThingHandler createHandler(Thing thing) { - ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - - if (thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_HKV45) - || thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_HKV61) - || thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_HKV64) - || thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_HKV69) - || thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_HKV94)) { - logger.debug("Creating handler for TechemDevice device {}", thing.getUID().getId()); - return new TechemMeterHandler<>(thing, TechemHeatCostAllocator.class, techemFrameDecoder); - } else if (thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_WARM_WATER_METER) - || thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_COLD_WATER_METER)) { - logger.debug("Creating handler for TechemDevice device {}", thing.getUID().getId()); - return new TechemMeterHandler<>(thing, TechemWaterMeter.class, techemFrameDecoder); - } else if (thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_SD76)) { - logger.debug("Creating handler for Techem Smoke Detector device {}", thing.getUID().getId()); - return new TechemMeterHandler<>(thing, TechemSmokeDetector.class, techemFrameDecoder); - } else if (thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_HEAT_METER)) { - logger.debug("Creating handler for TechemDevice device {}", thing.getUID().getId()); - return new TechemMeterHandler<>(thing, TechemHeatMeter.class, techemFrameDecoder); - } - - logger.warn("Unsupported thing type {}. TechemHandlerFactory can not handle {}", thingTypeUID, thing.getUID()); - - return null; - } - - @Override - @Activate - protected void activate(ComponentContext componentContext) { - super.activate(componentContext); - } - - @Override - @Deactivate - protected void deactivate(ComponentContext componentContext) { - super.deactivate(componentContext); - } - - @Reference - public void setTechemFrameDecoder(TechemFrameDecoder decoder) { - this.techemFrameDecoder = decoder; - } - - public void unsetTechemFrameDecoder(TechemFrameDecoder decoder) { - this.techemFrameDecoder = null; - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem; + +import org.openhab.binding.wmbus.device.techem.decoder.TechemFrameDecoder; +import org.openhab.binding.wmbus.device.techem.handler.TechemMeterHandler; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.BaseThingHandlerFactory; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link TechemHandlerFactory} covers logic specific to TechemH devices. + * + * @author Łukasz Dywicki - Initial contribution + */ + +@Component(service = { TechemHandlerFactory.class, BaseThingHandlerFactory.class, ThingHandlerFactory.class }) +public class TechemHandlerFactory extends BaseThingHandlerFactory { + + // OpenHAB logger + private final Logger logger = LoggerFactory.getLogger(TechemHandlerFactory.class); + + private TechemFrameDecoder techemFrameDecoder; + + public TechemHandlerFactory() { + logger.debug("Techem handler factory starting up."); + } + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return TechemBindingConstants.SUPPORTED_THING_TYPES.contains(thingTypeUID); + } + + @Override + protected ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_HKV45) + || thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_HKV61) + || thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_HKV64) + || thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_HKV69) + || thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_HKV94)) { + logger.debug("Creating handler for TechemDevice device {}", thing.getUID().getId()); + return new TechemMeterHandler<>(thing, TechemHeatCostAllocator.class, techemFrameDecoder); + } else if (thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_WARM_WATER_METER) + || thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_COLD_WATER_METER)) { + logger.debug("Creating handler for TechemDevice device {}", thing.getUID().getId()); + return new TechemMeterHandler<>(thing, TechemWaterMeter.class, techemFrameDecoder); + } else if (thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_SD76)) { + logger.debug("Creating handler for Techem Smoke Detector device {}", thing.getUID().getId()); + return new TechemMeterHandler<>(thing, TechemSmokeDetector.class, techemFrameDecoder); + } else if (thingTypeUID.equals(TechemBindingConstants.THING_TYPE_TECHEM_HEAT_METER)) { + logger.debug("Creating handler for TechemDevice device {}", thing.getUID().getId()); + return new TechemMeterHandler<>(thing, TechemHeatMeter.class, techemFrameDecoder); + } + + logger.warn("Unsupported thing type {}. TechemHandlerFactory can not handle {}", thingTypeUID, thing.getUID()); + + return null; + } + + @Override + @Activate + protected void activate(ComponentContext componentContext) { + super.activate(componentContext); + } + + @Override + @Deactivate + protected void deactivate(ComponentContext componentContext) { + super.deactivate(componentContext); + } + + @Reference + public void setTechemFrameDecoder(TechemFrameDecoder decoder) { + this.techemFrameDecoder = decoder; + } + + public void unsetTechemFrameDecoder(TechemFrameDecoder decoder) { + this.techemFrameDecoder = null; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemHeatCostAllocator.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemHeatCostAllocator.java index 4bd8210..c56938c 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemHeatCostAllocator.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemHeatCostAllocator.java @@ -1,32 +1,32 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.techem; - -import java.util.List; - -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openmuc.jmbus.wireless.WMBusMessage; - -/** - * The {@link TechemHeatCostAllocator} class covers heat cost allocator devices at very basic level. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public class TechemHeatCostAllocator extends TechemDevice { - - public TechemHeatCostAllocator(WMBusMessage originalMessage, WMBusAdapter adapter, Variant variant, - List> measures) { - super(originalMessage, adapter, variant, measures); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem; + +import java.util.List; + +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openmuc.jmbus.wireless.WMBusMessage; + +/** + * The {@link TechemHeatCostAllocator} class covers heat cost allocator devices at very basic level. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public class TechemHeatCostAllocator extends TechemDevice { + + public TechemHeatCostAllocator(WMBusMessage originalMessage, WMBusAdapter adapter, Variant variant, + List> measures) { + super(originalMessage, adapter, variant, measures); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemHeatMeter.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemHeatMeter.java index e1b413c..e750d86 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemHeatMeter.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemHeatMeter.java @@ -1,32 +1,32 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.techem; - -import java.util.List; - -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openmuc.jmbus.wireless.WMBusMessage; - -/** - * The {@link TechemHeatMeter} class covers heat meter devices at very basic level. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public class TechemHeatMeter extends TechemDevice { - - public TechemHeatMeter(WMBusMessage originalMessage, WMBusAdapter adapter, Variant variant, - List> measures) { - super(originalMessage, adapter, variant, measures); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem; + +import java.util.List; + +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openmuc.jmbus.wireless.WMBusMessage; + +/** + * The {@link TechemHeatMeter} class covers heat meter devices at very basic level. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public class TechemHeatMeter extends TechemDevice { + + public TechemHeatMeter(WMBusMessage originalMessage, WMBusAdapter adapter, Variant variant, + List> measures) { + super(originalMessage, adapter, variant, measures); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemSmokeDetector.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemSmokeDetector.java index 28f0632..a291391 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemSmokeDetector.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemSmokeDetector.java @@ -1,32 +1,32 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.techem; - -import java.util.List; - -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openmuc.jmbus.wireless.WMBusMessage; - -/** - * The {@link TechemSmokeDetector} class covers smoke detector devices at very basic level. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public class TechemSmokeDetector extends TechemDevice { - - public TechemSmokeDetector(WMBusMessage originalMessage, WMBusAdapter adapter, Variant variant, - List> records) { - super(originalMessage, adapter, variant, records); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem; + +import java.util.List; + +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openmuc.jmbus.wireless.WMBusMessage; + +/** + * The {@link TechemSmokeDetector} class covers smoke detector devices at very basic level. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public class TechemSmokeDetector extends TechemDevice { + + public TechemSmokeDetector(WMBusMessage originalMessage, WMBusAdapter adapter, Variant variant, + List> records) { + super(originalMessage, adapter, variant, records); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemUnknownDevice.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemUnknownDevice.java index c13dd18..a174e91 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemUnknownDevice.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemUnknownDevice.java @@ -1,31 +1,31 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.techem; - -import java.util.ArrayList; - -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openmuc.jmbus.wireless.WMBusMessage; - -/** - * The {@link TechemHeatCostAllocator} class covers heat cost allocator devices at very basic level. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public class TechemUnknownDevice extends TechemHeatCostAllocator { - - public TechemUnknownDevice(WMBusMessage originalMessage, WMBusAdapter adapter, Variant variant) { - super(originalMessage, adapter, variant, new ArrayList<>()); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem; + +import java.util.ArrayList; + +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openmuc.jmbus.wireless.WMBusMessage; + +/** + * The {@link TechemHeatCostAllocator} class covers heat cost allocator devices at very basic level. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public class TechemUnknownDevice extends TechemHeatCostAllocator { + + public TechemUnknownDevice(WMBusMessage originalMessage, WMBusAdapter adapter, Variant variant) { + super(originalMessage, adapter, variant, new ArrayList<>()); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemWaterMeter.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemWaterMeter.java index ca43756..1fa63e8 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemWaterMeter.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/TechemWaterMeter.java @@ -1,33 +1,33 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.techem; - -import java.util.List; - -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openmuc.jmbus.wireless.WMBusMessage; - -/** - * The {@link TechemWaterMeter} class covers basic water measurement devices. - * - * @author Łukasz Dywicki - Initial contribution. - */ - -public class TechemWaterMeter extends TechemDevice { - - public TechemWaterMeter(WMBusMessage originalMessage, WMBusAdapter adapter, Variant variant, - List> measures) { - super(originalMessage, adapter, variant, measures); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem; + +import java.util.List; + +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openmuc.jmbus.wireless.WMBusMessage; + +/** + * The {@link TechemWaterMeter} class covers basic water measurement devices. + * + * @author Łukasz Dywicki - Initial contribution. + */ + +public class TechemWaterMeter extends TechemDevice { + + public TechemWaterMeter(WMBusMessage originalMessage, WMBusAdapter adapter, Variant variant, + List> measures) { + super(originalMessage, adapter, variant, measures); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/Variant.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/Variant.java index 059819c..23d1626 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/Variant.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/Variant.java @@ -1,69 +1,69 @@ -package org.openhab.binding.wmbus.device.techem; - -import java.util.Objects; - -import org.openmuc.jmbus.DeviceType; - -public class Variant { - - public final int version; - public final int reportedType; - public final int coding; - public final DeviceType desiredType; - public final int matchingType; // since all unknown device types are converted to RESERVED/255 by jMBus - - public Variant(int version, int reportedType, int coding, DeviceType desiredType) { - this(version, reportedType, coding, desiredType, DeviceType.getInstance(reportedType)); - } - - public Variant(int version, int reportedType, int coding, DeviceType desiredType, DeviceType matchingType) { - this.version = version; - this.reportedType = reportedType; - this.coding = coding; - this.desiredType = desiredType; - this.matchingType = matchingType.getId(); - } - - public String getRawType() { - return "68" + TechemBindingConstants.MANUFACTURER_ID + version + "" + reportedType; - } - - public int getDesiredType() { - return desiredType.getId(); - } - - public DeviceType getDesiredWMBusType() { - return desiredType; - } - - public int getCoding() { - return coding; - } - - public String getTechemType() { - return getRawType() + desiredType; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof Variant)) { - return false; - } - Variant variant = (Variant) o; - return version == variant.version && reportedType == variant.reportedType && coding == variant.coding - && desiredType == variant.desiredType && matchingType == variant.matchingType; - } - - @Override - public int hashCode() { - return Objects.hash(version, reportedType, coding, desiredType, matchingType); - } - - @Override - public String toString() { - return getRawType() + "->" + getTechemType(); - } -} +package org.openhab.binding.wmbus.device.techem; + +import java.util.Objects; + +import org.openmuc.jmbus.DeviceType; + +public class Variant { + + public final int version; + public final int reportedType; + public final int coding; + public final DeviceType desiredType; + public final int matchingType; // since all unknown device types are converted to RESERVED/255 by jMBus + + public Variant(int version, int reportedType, int coding, DeviceType desiredType) { + this(version, reportedType, coding, desiredType, DeviceType.getInstance(reportedType)); + } + + public Variant(int version, int reportedType, int coding, DeviceType desiredType, DeviceType matchingType) { + this.version = version; + this.reportedType = reportedType; + this.coding = coding; + this.desiredType = desiredType; + this.matchingType = matchingType.getId(); + } + + public String getRawType() { + return "68" + TechemBindingConstants.MANUFACTURER_ID + version + "" + reportedType; + } + + public int getDesiredType() { + return desiredType.getId(); + } + + public DeviceType getDesiredWMBusType() { + return desiredType; + } + + public int getCoding() { + return coding; + } + + public String getTechemType() { + return getRawType() + desiredType; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Variant)) { + return false; + } + Variant variant = (Variant) o; + return version == variant.version && reportedType == variant.reportedType && coding == variant.coding + && desiredType == variant.desiredType && matchingType == variant.matchingType; + } + + @Override + public int hashCode() { + return Objects.hash(version, reportedType, coding, desiredType, matchingType); + } + + @Override + public String toString() { + return getRawType() + "->" + getTechemType(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/AbstractTechemFrameDecoder.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/AbstractTechemFrameDecoder.java index a873825..d565e82 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/AbstractTechemFrameDecoder.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/AbstractTechemFrameDecoder.java @@ -1,117 +1,117 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; - -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; -import java.util.function.Function; - -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.device.techem.TechemDevice; -import org.openhab.binding.wmbus.device.techem.Variant; -import org.openhab.core.util.HexUtils; -import org.openmuc.jmbus.SecondaryAddress; -import org.openmuc.jmbus.wireless.WMBusMessage; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -abstract class AbstractTechemFrameDecoder implements TechemFrameDecoder { - - private final Logger logger = LoggerFactory.getLogger(AbstractTechemFrameDecoder.class); - protected final Variant variant; - - protected final Function _SCALE_FACTOR_1_10th = value -> value / 10; - protected final Function _SCALE_FACTOR_1_100th = value -> value / 100; - - protected AbstractTechemFrameDecoder(Variant variant) { - this.variant = variant; - } - - @Override - public final boolean supports(String deviceVariant) { - boolean supports = variant.getRawType().equals(deviceVariant); - logger.debug("Does decoder {} support meter variant {}? {}", variant, deviceVariant, supports); - return supports; - } - - protected final byte[] read(byte[] buffer, int... indexes) { - byte[] value = new byte[indexes.length]; - for (int element = 0; element < indexes.length; element++) { - value[element] = buffer[indexes[element]]; - } - return value; - } - - protected final int parseBigEndianInt(byte[] buffer, int index) { - if (buffer.length < index + 1) { - return 0x00; - } - return parseBigEndianInt(buffer, index, index + 1); - } - - protected final int parseBigEndianInt(byte[] buffer, int lsb, int msb) { - byte[] value = read(buffer, lsb, msb); - return (value[0] & 0xFF) + ((value[1] & 0xFF) << 8); - } - - protected final float parseValue(byte[] buffer, int index, Function scale) { - float value = parseBigEndianInt(buffer, index); - - return scale.apply(value); - } - - protected final LocalDateTime parseLastDate(byte[] buffer, int index) { - int dateint = parseBigEndianInt(buffer, index); - - int day = (dateint >> 0) & 0x1F; - int month = (dateint >> 5) & 0x0F; - int year = (dateint >> 9) & 0x3F; - - LocalDateTime dateTime = LocalDateTime.of(2000 + year, month, day, 0, 0, 0, 0); - return dateTime.truncatedTo(ChronoUnit.DAYS); - } - - protected final LocalDateTime parseCurrentDate(byte[] buffer, int index) { - int dateint = parseBigEndianInt(buffer, index); - - int day = (dateint >> 4) & 0x1F; - int month = (dateint >> 9) & 0x0F; - - if (day <= 0) { - logger.trace("Detected invalid day number {} in byte representation: {}, changing to 1st day of month", day, - HexUtils.bytesToHex(read(buffer, index, index + 1))); - day = 1; - } - if (month <= 0) { - logger.trace("Detected invalid month number {} in byte representation: {}, changing to last month of year", - month, HexUtils.bytesToHex(read(buffer, index, index + 1))); - month = 12; - } - - LocalDateTime dateTime = LocalDateTime.now().withMonth(month).withDayOfMonth(day); - return dateTime.truncatedTo(ChronoUnit.SECONDS); - } - - protected final float parseTemperature(byte[] buffer, int index) { - return parseValue(buffer, index, _SCALE_FACTOR_1_100th); - } - - @Override - public T decode(WMBusDevice device) { - WMBusMessage message = device.getOriginalMessage(); - return decode(device, message.getSecondaryAddress(), message.asBlob()); - } - - // FIXME make this method abstract - protected abstract T decode(WMBusDevice device, SecondaryAddress address, byte[] buffer); -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.function.Function; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.device.techem.TechemDevice; +import org.openhab.binding.wmbus.device.techem.Variant; +import org.openhab.core.util.HexUtils; +import org.openmuc.jmbus.SecondaryAddress; +import org.openmuc.jmbus.wireless.WMBusMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +abstract class AbstractTechemFrameDecoder implements TechemFrameDecoder { + + private final Logger logger = LoggerFactory.getLogger(AbstractTechemFrameDecoder.class); + protected final Variant variant; + + protected final Function _SCALE_FACTOR_1_10th = value -> value / 10; + protected final Function _SCALE_FACTOR_1_100th = value -> value / 100; + + protected AbstractTechemFrameDecoder(Variant variant) { + this.variant = variant; + } + + @Override + public final boolean supports(String deviceVariant) { + boolean supports = variant.getRawType().equals(deviceVariant); + logger.debug("Does decoder {} support meter variant {}? {}", variant, deviceVariant, supports); + return supports; + } + + protected final byte[] read(byte[] buffer, int... indexes) { + byte[] value = new byte[indexes.length]; + for (int element = 0; element < indexes.length; element++) { + value[element] = buffer[indexes[element]]; + } + return value; + } + + protected final int parseBigEndianInt(byte[] buffer, int index) { + if (buffer.length < index + 1) { + return 0x00; + } + return parseBigEndianInt(buffer, index, index + 1); + } + + protected final int parseBigEndianInt(byte[] buffer, int lsb, int msb) { + byte[] value = read(buffer, lsb, msb); + return (value[0] & 0xFF) + ((value[1] & 0xFF) << 8); + } + + protected final float parseValue(byte[] buffer, int index, Function scale) { + float value = parseBigEndianInt(buffer, index); + + return scale.apply(value); + } + + protected final LocalDateTime parseLastDate(byte[] buffer, int index) { + int dateint = parseBigEndianInt(buffer, index); + + int day = (dateint >> 0) & 0x1F; + int month = (dateint >> 5) & 0x0F; + int year = (dateint >> 9) & 0x3F; + + LocalDateTime dateTime = LocalDateTime.of(2000 + year, month, day, 0, 0, 0, 0); + return dateTime.truncatedTo(ChronoUnit.DAYS); + } + + protected final LocalDateTime parseCurrentDate(byte[] buffer, int index) { + int dateint = parseBigEndianInt(buffer, index); + + int day = (dateint >> 4) & 0x1F; + int month = (dateint >> 9) & 0x0F; + + if (day <= 0) { + logger.trace("Detected invalid day number {} in byte representation: {}, changing to 1st day of month", day, + HexUtils.bytesToHex(read(buffer, index, index + 1))); + day = 1; + } + if (month <= 0) { + logger.trace("Detected invalid month number {} in byte representation: {}, changing to last month of year", + month, HexUtils.bytesToHex(read(buffer, index, index + 1))); + month = 12; + } + + LocalDateTime dateTime = LocalDateTime.now().withMonth(month).withDayOfMonth(day); + return dateTime.truncatedTo(ChronoUnit.SECONDS); + } + + protected final float parseTemperature(byte[] buffer, int index) { + return parseValue(buffer, index, _SCALE_FACTOR_1_100th); + } + + @Override + public T decode(WMBusDevice device) { + WMBusMessage message = device.getOriginalMessage(); + return decode(device, message.getSecondaryAddress(), message.asBlob()); + } + + // FIXME make this method abstract + protected abstract T decode(WMBusDevice device, SecondaryAddress address, byte[] buffer); +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/Buffer.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/Buffer.java index d529a65..a385198 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/Buffer.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/Buffer.java @@ -1,168 +1,168 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; - -import java.nio.BufferOverflowException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; -import java.util.function.Function; - -import org.openmuc.jmbus.SecondaryAddress; -import org.openmuc.jmbus.wireless.WMBusMessage; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Very basic wrapper around WMBus Message which turns it into readable sequence of bytes allowing to navigate over - * data part with automatic adjustment of reader index. - * - * @author Łukasz Dywicki - initial contribution. - */ -public class Buffer { - - protected final Logger logger = LoggerFactory.getLogger(Buffer.class); - protected final ByteBuffer buffer; - - public final static Function _SCALE_FACTOR_1_10th = value -> value / 10; - public final static Function _SCALE_FACTOR_1_100th = value -> value / 100; - - public Buffer(WMBusMessage message) { - this(message.asBlob(), message.getSecondaryAddress()); - } - - public Buffer(WMBusMessage message, SecondaryAddress secondaryAddress) { - this(message.asBlob(), secondaryAddress); - } - - public Buffer(byte[] buffer, SecondaryAddress secondaryAddress) { - this(buffer, secondaryAddress.asByteArray().length); - } - - public Buffer(byte[] buffer) { - this(buffer, 0); - } - - public Buffer(byte[] buffer, int offset) { - this.buffer = ByteBuffer.wrap(buffer); - skip(offset); - } - - public Buffer skip(int bytes) { - int position = buffer.position() + bytes; - if (position > buffer.limit()) { - throw new BufferOverflowException(); - } - - this.buffer.position(position); - return this; - } - - public byte readByte() { - return buffer.get(); - } - - public byte[] readBytes(int len) { - byte[] bytes = new byte[len]; - for (int index = 0; index < len; index++) { - bytes[index] = readByte(); - } - return bytes; - } - - public int readInt() { - int number = orderedRead(ByteOrder.LITTLE_ENDIAN, ByteBuffer::getInt); - return number >> 8; - } - - public short readShort() { - return orderedRead(ByteOrder.LITTLE_ENDIAN, ByteBuffer::getShort); - } - - public float readFloat() { - return readShort(); - } - - public LocalDateTime readPastDate() { - int dateint = readShort(); - - int day = dateint & 0x1F; - int month = (dateint >> 5) & 0x0F; - int year = (dateint >> 9) & 0x3F; - - return LocalDate.of(2000 + year, month, day).atStartOfDay(); - } - - public LocalDateTime readCurrentDate() { - int position = buffer.position(); - int dateint = readShort(); - - int day = (dateint >> 4) & 0x1F; - int month = (dateint >> 9) & 0x0F; - - if (day <= 0) { - logger.trace("Detected invalid day number {} in byte representation: {}, changing to 1st day of month", day, - String.format("%02X", buffer.get(position))); - day = 1; - } - if (month <= 0) { - logger.trace("Detected invalid month number {} in byte representation: {}, changing to last month of year", - month, String.format("%02X", buffer.get(position))); - month = 12; - } - - LocalDateTime dateTime = LocalDateTime.now().withMonth(month).withDayOfMonth(day); - return dateTime.truncatedTo(ChronoUnit.SECONDS); - } - - public String readHistory() { - StringBuilder history = new StringBuilder(); - while (buffer.position() + 1 < buffer.limit()) { - history.append((readByte() & 0xFF)).append(";"); - } - - return history.substring(0, history.length() - 1); - } - - public float readFloat(Function scale) { - return scale.apply(readFloat()); - } - - public int position() { - return buffer.position(); - } - - public int limit() { - return buffer.limit(); - } - - public int available() { - return limit() - position(); - } - - private final T orderedRead(ByteOrder readOrder, Function callback) { - ByteOrder byteOrder = buffer.order(); - if (!readOrder.equals(byteOrder)) { - try { - buffer.order(readOrder); - return callback.apply(buffer); - } finally { - buffer.order(byteOrder); - } - } - - return callback.apply(buffer); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; + +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.function.Function; + +import org.openmuc.jmbus.SecondaryAddress; +import org.openmuc.jmbus.wireless.WMBusMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Very basic wrapper around WMBus Message which turns it into readable sequence of bytes allowing to navigate over + * data part with automatic adjustment of reader index. + * + * @author Łukasz Dywicki - initial contribution. + */ +public class Buffer { + + protected final Logger logger = LoggerFactory.getLogger(Buffer.class); + protected final ByteBuffer buffer; + + public final static Function _SCALE_FACTOR_1_10th = value -> value / 10; + public final static Function _SCALE_FACTOR_1_100th = value -> value / 100; + + public Buffer(WMBusMessage message) { + this(message.asBlob(), message.getSecondaryAddress()); + } + + public Buffer(WMBusMessage message, SecondaryAddress secondaryAddress) { + this(message.asBlob(), secondaryAddress); + } + + public Buffer(byte[] buffer, SecondaryAddress secondaryAddress) { + this(buffer, secondaryAddress.asByteArray().length); + } + + public Buffer(byte[] buffer) { + this(buffer, 0); + } + + public Buffer(byte[] buffer, int offset) { + this.buffer = ByteBuffer.wrap(buffer); + skip(offset); + } + + public Buffer skip(int bytes) { + int position = buffer.position() + bytes; + if (position > buffer.limit()) { + throw new BufferOverflowException(); + } + + this.buffer.position(position); + return this; + } + + public byte readByte() { + return buffer.get(); + } + + public byte[] readBytes(int len) { + byte[] bytes = new byte[len]; + for (int index = 0; index < len; index++) { + bytes[index] = readByte(); + } + return bytes; + } + + public int readInt() { + int number = orderedRead(ByteOrder.LITTLE_ENDIAN, ByteBuffer::getInt); + return number >> 8; + } + + public short readShort() { + return orderedRead(ByteOrder.LITTLE_ENDIAN, ByteBuffer::getShort); + } + + public float readFloat() { + return readShort(); + } + + public LocalDateTime readPastDate() { + int dateint = readShort(); + + int day = dateint & 0x1F; + int month = (dateint >> 5) & 0x0F; + int year = (dateint >> 9) & 0x3F; + + return LocalDate.of(2000 + year, month, day).atStartOfDay(); + } + + public LocalDateTime readCurrentDate() { + int position = buffer.position(); + int dateint = readShort(); + + int day = (dateint >> 4) & 0x1F; + int month = (dateint >> 9) & 0x0F; + + if (day <= 0) { + logger.trace("Detected invalid day number {} in byte representation: {}, changing to 1st day of month", day, + String.format("%02X", buffer.get(position))); + day = 1; + } + if (month <= 0) { + logger.trace("Detected invalid month number {} in byte representation: {}, changing to last month of year", + month, String.format("%02X", buffer.get(position))); + month = 12; + } + + LocalDateTime dateTime = LocalDateTime.now().withMonth(month).withDayOfMonth(day); + return dateTime.truncatedTo(ChronoUnit.SECONDS); + } + + public String readHistory() { + StringBuilder history = new StringBuilder(); + while (buffer.position() + 1 < buffer.limit()) { + history.append((readByte() & 0xFF)).append(";"); + } + + return history.substring(0, history.length() - 1); + } + + public float readFloat(Function scale) { + return scale.apply(readFloat()); + } + + public int position() { + return buffer.position(); + } + + public int limit() { + return buffer.limit(); + } + + public int available() { + return limit() - position(); + } + + private final T orderedRead(ByteOrder readOrder, Function callback) { + ByteOrder byteOrder = buffer.order(); + if (!readOrder.equals(byteOrder)) { + try { + buffer.order(readOrder); + return callback.apply(buffer); + } finally { + buffer.order(byteOrder); + } + } + + return callback.apply(buffer); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/CompositeTechemFrameDecoder.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/CompositeTechemFrameDecoder.java index 49061e3..1b76176 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/CompositeTechemFrameDecoder.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/CompositeTechemFrameDecoder.java @@ -1,82 +1,82 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; - -import java.util.List; - -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.device.techem.TechemBindingConstants; -import org.openhab.binding.wmbus.device.techem.TechemDevice; -import org.osgi.service.component.annotations.Component; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.collect.ImmutableList; - -@Component -public class CompositeTechemFrameDecoder implements TechemFrameDecoder { - - private final Logger logger = LoggerFactory.getLogger(CompositeTechemFrameDecoder.class); - - private final List> decoders = ImmutableList.of(new TechemVersionFrameDecoderSelector(), - // warm water - new TechemWaterMeterFrameDecoder(TechemBindingConstants._68TCH11298_6, 1), - new TechemWaterMeterFrameDecoder(TechemBindingConstants._68TCH11698_6, 1), - new TechemWaterMeterFrameDecoder(TechemBindingConstants._68TCH14998_6, -1), - // cold water - new TechemWaterMeterFrameDecoder(TechemBindingConstants._68TCH112114_16, -1), - new TechemWaterMeterFrameDecoder(TechemBindingConstants._68TCH116114_16, -1), - new TechemWaterMeterFrameDecoder(TechemBindingConstants._68TCH149114_16, -1)); - - @Override - public boolean supports(String deviceVariant) { - for (TechemFrameDecoder decoder : decoders) { - if (decoder.supports(deviceVariant)) { - return true; - } - } - - return false; - } - - @Override - public TechemDevice decode(WMBusDevice device) { - if (device instanceof TechemDevice) { - return (TechemDevice) device; - } - TechemDevice result = null; - // TODO failing test: wrong water meter returned? - for (TechemFrameDecoder decoder : decoders) { - if (decoder.supports(device.getRawDeviceType())) { - // same variant might be supported by multiple decoders, but first one which gives decoded result wins - logger.debug("Found decoder capable of handling device {}: {}", device.getRawDeviceType(), - decoder.getClass().getName()); - result = decoder.decode(device); - if (result != null) { - logger.debug("Decoding result: {}, {}, {}", result, result.getRawDeviceType(), - result.getTechemDeviceType()); - break; - } else { - logger.debug("Decoding of frame failed, unsupported device variant"); - } - } - } - if (result != null) { - return result; - } - - logger.debug("Could not find decoder capable of handling device {}", device.getRawDeviceType()); - - return null; - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; + +import java.util.List; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.device.techem.TechemBindingConstants; +import org.openhab.binding.wmbus.device.techem.TechemDevice; +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableList; + +@Component +public class CompositeTechemFrameDecoder implements TechemFrameDecoder { + + private final Logger logger = LoggerFactory.getLogger(CompositeTechemFrameDecoder.class); + + private final List> decoders = ImmutableList.of(new TechemVersionFrameDecoderSelector(), + // warm water + new TechemWaterMeterFrameDecoder(TechemBindingConstants._68TCH11298_6, 1), + new TechemWaterMeterFrameDecoder(TechemBindingConstants._68TCH11698_6, 1), + new TechemWaterMeterFrameDecoder(TechemBindingConstants._68TCH14998_6, -1), + // cold water + new TechemWaterMeterFrameDecoder(TechemBindingConstants._68TCH112114_16, -1), + new TechemWaterMeterFrameDecoder(TechemBindingConstants._68TCH116114_16, -1), + new TechemWaterMeterFrameDecoder(TechemBindingConstants._68TCH149114_16, -1)); + + @Override + public boolean supports(String deviceVariant) { + for (TechemFrameDecoder decoder : decoders) { + if (decoder.supports(deviceVariant)) { + return true; + } + } + + return false; + } + + @Override + public TechemDevice decode(WMBusDevice device) { + if (device instanceof TechemDevice) { + return (TechemDevice) device; + } + TechemDevice result = null; + // TODO failing test: wrong water meter returned? + for (TechemFrameDecoder decoder : decoders) { + if (decoder.supports(device.getRawDeviceType())) { + // same variant might be supported by multiple decoders, but first one which gives decoded result wins + logger.debug("Found decoder capable of handling device {}: {}", device.getRawDeviceType(), + decoder.getClass().getName()); + result = decoder.decode(device); + if (result != null) { + logger.debug("Decoding result: {}, {}, {}", result, result.getRawDeviceType(), + result.getTechemDeviceType()); + break; + } else { + logger.debug("Decoding of frame failed, unsupported device variant"); + } + } + } + if (result != null) { + return result; + } + + logger.debug("Could not find decoder capable of handling device {}", device.getRawDeviceType()); + + return null; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/DebugBuffer.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/DebugBuffer.java index cf7bfc7..5c09502 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/DebugBuffer.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/DebugBuffer.java @@ -1,114 +1,114 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; - -import java.time.LocalDateTime; -import java.util.function.Function; - -import org.openmuc.jmbus.SecondaryAddress; -import org.openmuc.jmbus.wireless.WMBusMessage; - -/** - * Buffer implementation which dumps contents of frame during processing so it is easier to spot how payload is utilized - * by various parsers. - * - * @author Łukasz Dywicki - initial contribution. - */ -public class DebugBuffer extends Buffer { - - public DebugBuffer(WMBusMessage message) { - this(message.asBlob(), message.getSecondaryAddress()); - } - - public DebugBuffer(WMBusMessage message, SecondaryAddress secondaryAddress) { - this(message.asBlob(), secondaryAddress); - } - - public DebugBuffer(byte[] buffer, SecondaryAddress secondaryAddress) { - this(buffer, secondaryAddress.asByteArray().length); - } - - public DebugBuffer(byte[] buffer) { - this(buffer, 0); - } - - public DebugBuffer(byte[] buffer, int offset) { - super(buffer, offset); - logger.info("Skipped first {} bytes {}", offset, dump()); - } - - public DebugBuffer skip(int bytes) { - logger.info("Attempting to move reader index from {} by {} byte(s) {}", position(), bytes, dump()); - super.skip(bytes); - return this; - } - - public byte readByte() { - logger.info("Read byte {}", dump()); - return super.readByte(); - } - - public int readInt() { - logger.info("Read 3 byte integer from {}", dump()); - return super.readInt(); - } - - public short readShort() { - logger.info("Read 2 byte (short) from {}", dump()); - return super.readShort(); - } - - public float readFloat() { - logger.info("Read 2 byte (float) from {}", dump()); - return super.readFloat(); - } - - public LocalDateTime readPastDate() { - logger.info("Read 2 bytes (short) and turn int into data {}", dump()); - return super.readPastDate(); - } - - public LocalDateTime readCurrentDate() { - logger.info("Read 2 bytes (short) and turn int into data {}", dump()); - return super.readCurrentDate(); - } - - public float readFloat(Function scale) { - logger.info("Read 2 bytes (short) and turn int into float and rescale {}", dump()); - return super.readFloat(scale); - } - - private final String dump() { - byte[] array = buffer.array(); - - String output = "\nBuffer size: " + array.length + ", read index: " + buffer.position() + "\n"; - String middleLine = ""; - String lowerLine = ""; - for (int index = 0; index < array.length; index++) { - output += String.format("%02X", array[index]) + " "; - if (index < 10 && index != 0) { - lowerLine += " "; - } else { - lowerLine += " "; - } - lowerLine += index; - if (index == buffer.position()) { - middleLine += " ^ "; - } else { - middleLine += " "; - } - } - - return output + "\n" + lowerLine + "\n" + middleLine; - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; + +import java.time.LocalDateTime; +import java.util.function.Function; + +import org.openmuc.jmbus.SecondaryAddress; +import org.openmuc.jmbus.wireless.WMBusMessage; + +/** + * Buffer implementation which dumps contents of frame during processing so it is easier to spot how payload is utilized + * by various parsers. + * + * @author Łukasz Dywicki - initial contribution. + */ +public class DebugBuffer extends Buffer { + + public DebugBuffer(WMBusMessage message) { + this(message.asBlob(), message.getSecondaryAddress()); + } + + public DebugBuffer(WMBusMessage message, SecondaryAddress secondaryAddress) { + this(message.asBlob(), secondaryAddress); + } + + public DebugBuffer(byte[] buffer, SecondaryAddress secondaryAddress) { + this(buffer, secondaryAddress.asByteArray().length); + } + + public DebugBuffer(byte[] buffer) { + this(buffer, 0); + } + + public DebugBuffer(byte[] buffer, int offset) { + super(buffer, offset); + logger.info("Skipped first {} bytes {}", offset, dump()); + } + + public DebugBuffer skip(int bytes) { + logger.info("Attempting to move reader index from {} by {} byte(s) {}", position(), bytes, dump()); + super.skip(bytes); + return this; + } + + public byte readByte() { + logger.info("Read byte {}", dump()); + return super.readByte(); + } + + public int readInt() { + logger.info("Read 3 byte integer from {}", dump()); + return super.readInt(); + } + + public short readShort() { + logger.info("Read 2 byte (short) from {}", dump()); + return super.readShort(); + } + + public float readFloat() { + logger.info("Read 2 byte (float) from {}", dump()); + return super.readFloat(); + } + + public LocalDateTime readPastDate() { + logger.info("Read 2 bytes (short) and turn int into data {}", dump()); + return super.readPastDate(); + } + + public LocalDateTime readCurrentDate() { + logger.info("Read 2 bytes (short) and turn int into data {}", dump()); + return super.readCurrentDate(); + } + + public float readFloat(Function scale) { + logger.info("Read 2 bytes (short) and turn int into float and rescale {}", dump()); + return super.readFloat(scale); + } + + private final String dump() { + byte[] array = buffer.array(); + + String output = "\nBuffer size: " + array.length + ", read index: " + buffer.position() + "\n"; + String middleLine = ""; + String lowerLine = ""; + for (int index = 0; index < array.length; index++) { + output += String.format("%02X", array[index]) + " "; + if (index < 10 && index != 0) { + lowerLine += " "; + } else { + lowerLine += " "; + } + lowerLine += index; + if (index == buffer.position()) { + middleLine += " ^ "; + } else { + middleLine += " "; + } + } + + return output + "\n" + lowerLine + "\n" + middleLine; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemFrameDecoder.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemFrameDecoder.java index 99a76d0..87bc574 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemFrameDecoder.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemFrameDecoder.java @@ -1,23 +1,23 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; - -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.device.techem.TechemDevice; - -public interface TechemFrameDecoder { - - boolean supports(String deviceVariant); - - T decode(WMBusDevice device); -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.device.techem.TechemDevice; + +public interface TechemFrameDecoder { + + boolean supports(String deviceVariant); + + T decode(WMBusDevice device); +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemHKVFrameDecoder.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemHKVFrameDecoder.java index 5bbf69f..2d54252 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemHKVFrameDecoder.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemHKVFrameDecoder.java @@ -1,65 +1,65 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; - -import java.util.ArrayList; -import java.util.List; - -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.device.techem.Record; -import org.openhab.binding.wmbus.device.techem.TechemHeatCostAllocator; -import org.openhab.binding.wmbus.device.techem.TechemUnknownDevice; -import org.openhab.binding.wmbus.device.techem.Variant; -import org.openmuc.jmbus.DeviceType; -import org.openmuc.jmbus.SecondaryAddress; - -class TechemHKVFrameDecoder extends AbstractTechemFrameDecoder { - - protected final boolean reportsTemperature; - - TechemHKVFrameDecoder(Variant variant) { - this(variant, false); - } - - TechemHKVFrameDecoder(Variant variant, boolean temperature) { - super(variant); - this.reportsTemperature = temperature; - } - - @Override - protected TechemHeatCostAllocator decode(WMBusDevice device, SecondaryAddress address, byte[] buffer) { - Buffer buff = new Buffer(device.getOriginalMessage(), address); - - int coding = buff.skip(2).readByte() & 0xFF; - if (variant.getCoding() == coding) { - - List> records = new ArrayList<>(); - records.add(new Record<>(Record.Type.STATUS, ((Byte) buff.readByte()).intValue())); - records.add(new Record<>(Record.Type.PAST_READING_DATE, buff.readPastDate())); - records.add(new Record<>(Record.Type.PAST_VOLUME, (float) buff.readShort())); - records.add(new Record<>(Record.Type.CURRENT_READING_DATE, buff.readCurrentDate())); - records.add(new Record<>(Record.Type.CURRENT_VOLUME, (float) buff.readShort())); - records.add(new Record<>(Record.Type.RSSI, device.getOriginalMessage().getRssi())); - records.add(new Record<>(Record.Type.ALMANAC, "")); - - return new TechemHeatCostAllocator(device.getOriginalMessage(), device.getAdapter(), variant, records); - } - - if (coding == 0xA3) { - return new TechemUnknownDevice(device.getOriginalMessage(), device.getAdapter(), - new Variant(variant.version, variant.reportedType, coding, DeviceType.UNKNOWN)); - } - - return null; - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; + +import java.util.ArrayList; +import java.util.List; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.device.techem.Record; +import org.openhab.binding.wmbus.device.techem.TechemHeatCostAllocator; +import org.openhab.binding.wmbus.device.techem.TechemUnknownDevice; +import org.openhab.binding.wmbus.device.techem.Variant; +import org.openmuc.jmbus.DeviceType; +import org.openmuc.jmbus.SecondaryAddress; + +class TechemHKVFrameDecoder extends AbstractTechemFrameDecoder { + + protected final boolean reportsTemperature; + + TechemHKVFrameDecoder(Variant variant) { + this(variant, false); + } + + TechemHKVFrameDecoder(Variant variant, boolean temperature) { + super(variant); + this.reportsTemperature = temperature; + } + + @Override + protected TechemHeatCostAllocator decode(WMBusDevice device, SecondaryAddress address, byte[] buffer) { + Buffer buff = new Buffer(device.getOriginalMessage(), address); + + int coding = buff.skip(2).readByte() & 0xFF; + if (variant.getCoding() == coding) { + + List> records = new ArrayList<>(); + records.add(new Record<>(Record.Type.STATUS, ((Byte) buff.readByte()).intValue())); + records.add(new Record<>(Record.Type.PAST_READING_DATE, buff.readPastDate())); + records.add(new Record<>(Record.Type.PAST_VOLUME, (float) buff.readShort())); + records.add(new Record<>(Record.Type.CURRENT_READING_DATE, buff.readCurrentDate())); + records.add(new Record<>(Record.Type.CURRENT_VOLUME, (float) buff.readShort())); + records.add(new Record<>(Record.Type.RSSI, device.getOriginalMessage().getRssi())); + records.add(new Record<>(Record.Type.ALMANAC, "")); + + return new TechemHeatCostAllocator(device.getOriginalMessage(), device.getAdapter(), variant, records); + } + + if (coding == 0xA3) { + return new TechemUnknownDevice(device.getOriginalMessage(), device.getAdapter(), + new Variant(variant.version, variant.reportedType, coding, DeviceType.UNKNOWN)); + } + + return null; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemHKVRoomTempFrameDecoder.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemHKVRoomTempFrameDecoder.java index 55f46ad..c460981 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemHKVRoomTempFrameDecoder.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemHKVRoomTempFrameDecoder.java @@ -1,79 +1,79 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; - -import java.util.ArrayList; -import java.util.List; - -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.device.techem.Record; -import org.openhab.binding.wmbus.device.techem.TechemHeatCostAllocator; -import org.openhab.binding.wmbus.device.techem.TechemUnknownDevice; -import org.openhab.binding.wmbus.device.techem.Variant; -import org.openhab.core.library.unit.SIUnits; -import org.openmuc.jmbus.DeviceType; -import org.openmuc.jmbus.SecondaryAddress; - -import tec.uom.se.quantity.Quantities; - -class TechemHKVRoomTempFrameDecoder extends AbstractTechemFrameDecoder { - - private final int currentReadingOffset; - private final int historyOffset; - - TechemHKVRoomTempFrameDecoder(Variant variant) { - this(variant, 0, 0); - } - - TechemHKVRoomTempFrameDecoder(Variant variant, int currentReadingOffset, int historyOffset) { - super(variant); - this.currentReadingOffset = currentReadingOffset; - this.historyOffset = historyOffset; - } - - @Override - protected TechemHeatCostAllocator decode(WMBusDevice device, SecondaryAddress address, byte[] buffer) { - Buffer buff = new Buffer(device.getOriginalMessage(), address); - - int coding = buff.skip(2).readByte() & 0xFF; - if (variant.getCoding() == coding) { - - List> records = new ArrayList<>(); - records.add(new Record<>(Record.Type.STATUS, ((Byte) buff.readByte()).intValue())); - records.add(new Record<>(Record.Type.PAST_READING_DATE, buff.readPastDate())); - records.add(new Record<>(Record.Type.PAST_VOLUME, (float) buff.readShort())); - records.add(new Record<>(Record.Type.CURRENT_READING_DATE, buff.readCurrentDate())); - records.add(new Record<>(Record.Type.CURRENT_VOLUME, (float) buff.skip(currentReadingOffset).readShort())); - records.add(new Record<>(Record.Type.RSSI, device.getOriginalMessage().getRssi())); - - float temp1 = buff.readFloat(Buffer._SCALE_FACTOR_1_100th); - float temp2 = buff.readFloat(Buffer._SCALE_FACTOR_1_100th); - records.add(new Record<>(Record.Type.ROOM_TEMPERATURE, Quantities.getQuantity(temp1, SIUnits.CELSIUS))); - records.add(new Record<>(Record.Type.RADIATOR_TEMPERATURE, Quantities.getQuantity(temp2, SIUnits.CELSIUS))); - - records.add(new Record<>(Record.Type.COUNTER, buff.readByte() & 0xF0)); - - buff.skip(historyOffset); - records.add(new Record<>(Record.Type.ALMANAC, buff.readHistory())); - - return new TechemHeatCostAllocator(device.getOriginalMessage(), device.getAdapter(), variant, records); - } - - if (coding == 0xA3) { - return new TechemUnknownDevice(device.getOriginalMessage(), device.getAdapter(), - new Variant(variant.version, variant.reportedType, coding, DeviceType.UNKNOWN)); - } - - return null; - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; + +import java.util.ArrayList; +import java.util.List; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.device.techem.Record; +import org.openhab.binding.wmbus.device.techem.TechemHeatCostAllocator; +import org.openhab.binding.wmbus.device.techem.TechemUnknownDevice; +import org.openhab.binding.wmbus.device.techem.Variant; +import org.openhab.core.library.unit.SIUnits; +import org.openmuc.jmbus.DeviceType; +import org.openmuc.jmbus.SecondaryAddress; + +import tec.uom.se.quantity.Quantities; + +class TechemHKVRoomTempFrameDecoder extends AbstractTechemFrameDecoder { + + private final int currentReadingOffset; + private final int historyOffset; + + TechemHKVRoomTempFrameDecoder(Variant variant) { + this(variant, 0, 0); + } + + TechemHKVRoomTempFrameDecoder(Variant variant, int currentReadingOffset, int historyOffset) { + super(variant); + this.currentReadingOffset = currentReadingOffset; + this.historyOffset = historyOffset; + } + + @Override + protected TechemHeatCostAllocator decode(WMBusDevice device, SecondaryAddress address, byte[] buffer) { + Buffer buff = new Buffer(device.getOriginalMessage(), address); + + int coding = buff.skip(2).readByte() & 0xFF; + if (variant.getCoding() == coding) { + + List> records = new ArrayList<>(); + records.add(new Record<>(Record.Type.STATUS, ((Byte) buff.readByte()).intValue())); + records.add(new Record<>(Record.Type.PAST_READING_DATE, buff.readPastDate())); + records.add(new Record<>(Record.Type.PAST_VOLUME, (float) buff.readShort())); + records.add(new Record<>(Record.Type.CURRENT_READING_DATE, buff.readCurrentDate())); + records.add(new Record<>(Record.Type.CURRENT_VOLUME, (float) buff.skip(currentReadingOffset).readShort())); + records.add(new Record<>(Record.Type.RSSI, device.getOriginalMessage().getRssi())); + + float temp1 = buff.readFloat(Buffer._SCALE_FACTOR_1_100th); + float temp2 = buff.readFloat(Buffer._SCALE_FACTOR_1_100th); + records.add(new Record<>(Record.Type.ROOM_TEMPERATURE, Quantities.getQuantity(temp1, SIUnits.CELSIUS))); + records.add(new Record<>(Record.Type.RADIATOR_TEMPERATURE, Quantities.getQuantity(temp2, SIUnits.CELSIUS))); + + records.add(new Record<>(Record.Type.COUNTER, buff.readByte() & 0xF0)); + + buff.skip(historyOffset); + records.add(new Record<>(Record.Type.ALMANAC, buff.readHistory())); + + return new TechemHeatCostAllocator(device.getOriginalMessage(), device.getAdapter(), variant, records); + } + + if (coding == 0xA3) { + return new TechemUnknownDevice(device.getOriginalMessage(), device.getAdapter(), + new Variant(variant.version, variant.reportedType, coding, DeviceType.UNKNOWN)); + } + + return null; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemHeatMeterFrameDecoder.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemHeatMeterFrameDecoder.java index c99dc48..b8e4cae 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemHeatMeterFrameDecoder.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemHeatMeterFrameDecoder.java @@ -1,83 +1,83 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; - -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.List; - -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.device.techem.Record; -import org.openhab.binding.wmbus.device.techem.TechemHeatMeter; -import org.openhab.binding.wmbus.device.techem.Variant; -import org.openmuc.jmbus.SecondaryAddress; - -// TODO adjust after finding test frame -class TechemHeatMeterFrameDecoder extends AbstractTechemFrameDecoder { - - private final Variant[] variants; - - TechemHeatMeterFrameDecoder(Variant... variants) { - super(variants[0]); - this.variants = variants; - } - - @Override - protected TechemHeatMeter decode(WMBusDevice device, SecondaryAddress address, byte[] buffer) { - int offset = address.asByteArray().length + 2; - int coding = buffer[offset] & 0xFF; - - for (Variant variant : variants) { - if (variant.getCoding() == coding) { - LocalDateTime lastReading = parseLastDate(buffer, offset + 2); - float lastValue = parseLastPeriod(buffer, offset + 4); - LocalDateTime currentDate = parseCurrentDate(buffer, offset + 6); - float currentValue = parseActualPeriod(buffer, offset + 8); - - List> records = new ArrayList<>(); - records.add(new Record<>(Record.Type.CURRENT_READING_DATE, currentDate)); - records.add(new Record<>(Record.Type.CURRENT_VOLUME, currentValue)); - records.add(new Record<>(Record.Type.PAST_VOLUME, lastValue)); - records.add(new Record<>(Record.Type.PAST_READING_DATE, lastReading)); - records.add(new Record<>(Record.Type.RSSI, device.getOriginalMessage().getRssi())); - - return new TechemHeatMeter(device.getOriginalMessage(), device.getAdapter(), variant, records); - } - } - - return null; - } - - private float parseLastPeriod(byte[] buffer, int index) { - byte[] value = read(buffer, index, index + 1, index + 2); - - return (value[2] & 0xFF) + ((value[1] & 0xFF) << 8) + ((value[0] & 0xFF) << 16); - } - - private float parseActualPeriod(byte[] buffer, int index) { - byte[] value = read(buffer, index, index + 1, index + 2); - - return (value[2] & 0xFF) + ((value[1] & 0xFF) << 8) + ((value[0] & 0xFF) << 16); - } - - protected LocalDateTime parseActualDate(byte[] buffer, int dayIndex, int monthIndex) { - int dateint = parseBigEndianInt(buffer, dayIndex); - - int day = (dateint >> 7) & 0x1F; - int month = (buffer[monthIndex] >> 3) & 0x0F; - - LocalDateTime dateTime = LocalDateTime.now().withMonth(month).withDayOfMonth(day); - return dateTime.truncatedTo(ChronoUnit.SECONDS); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.List; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.device.techem.Record; +import org.openhab.binding.wmbus.device.techem.TechemHeatMeter; +import org.openhab.binding.wmbus.device.techem.Variant; +import org.openmuc.jmbus.SecondaryAddress; + +// TODO adjust after finding test frame +class TechemHeatMeterFrameDecoder extends AbstractTechemFrameDecoder { + + private final Variant[] variants; + + TechemHeatMeterFrameDecoder(Variant... variants) { + super(variants[0]); + this.variants = variants; + } + + @Override + protected TechemHeatMeter decode(WMBusDevice device, SecondaryAddress address, byte[] buffer) { + int offset = address.asByteArray().length + 2; + int coding = buffer[offset] & 0xFF; + + for (Variant variant : variants) { + if (variant.getCoding() == coding) { + LocalDateTime lastReading = parseLastDate(buffer, offset + 2); + float lastValue = parseLastPeriod(buffer, offset + 4); + LocalDateTime currentDate = parseCurrentDate(buffer, offset + 6); + float currentValue = parseActualPeriod(buffer, offset + 8); + + List> records = new ArrayList<>(); + records.add(new Record<>(Record.Type.CURRENT_READING_DATE, currentDate)); + records.add(new Record<>(Record.Type.CURRENT_VOLUME, currentValue)); + records.add(new Record<>(Record.Type.PAST_VOLUME, lastValue)); + records.add(new Record<>(Record.Type.PAST_READING_DATE, lastReading)); + records.add(new Record<>(Record.Type.RSSI, device.getOriginalMessage().getRssi())); + + return new TechemHeatMeter(device.getOriginalMessage(), device.getAdapter(), variant, records); + } + } + + return null; + } + + private float parseLastPeriod(byte[] buffer, int index) { + byte[] value = read(buffer, index, index + 1, index + 2); + + return (value[2] & 0xFF) + ((value[1] & 0xFF) << 8) + ((value[0] & 0xFF) << 16); + } + + private float parseActualPeriod(byte[] buffer, int index) { + byte[] value = read(buffer, index, index + 1, index + 2); + + return (value[2] & 0xFF) + ((value[1] & 0xFF) << 8) + ((value[0] & 0xFF) << 16); + } + + protected LocalDateTime parseActualDate(byte[] buffer, int dayIndex, int monthIndex) { + int dateint = parseBigEndianInt(buffer, dayIndex); + + int day = (dateint >> 7) & 0x1F; + int month = (buffer[monthIndex] >> 3) & 0x0F; + + LocalDateTime dateTime = LocalDateTime.now().withMonth(month).withDayOfMonth(day); + return dateTime.truncatedTo(ChronoUnit.SECONDS); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemSmokeDetectorFrameDecoder.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemSmokeDetectorFrameDecoder.java index 86b31f6..d02edf0 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemSmokeDetectorFrameDecoder.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemSmokeDetectorFrameDecoder.java @@ -1,54 +1,54 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; - -import java.util.ArrayList; -import java.util.List; - -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.device.techem.Record; -import org.openhab.binding.wmbus.device.techem.TechemSmokeDetector; -import org.openhab.binding.wmbus.device.techem.Variant; -import org.openmuc.jmbus.SecondaryAddress; - -class TechemSmokeDetectorFrameDecoder extends AbstractTechemFrameDecoder { - - private final Variant[] variants; - - TechemSmokeDetectorFrameDecoder(Variant... variants) { - super(variants[0]); - this.variants = variants; - } - - @Override - protected TechemSmokeDetector decode(WMBusDevice device, SecondaryAddress address, byte[] buffer) { - int offset = address.asByteArray().length + 2; // 2 first bytes of data is CRC - Buffer buff = new Buffer(device.getOriginalMessage(), address); - int coding = buff.skip(2).readByte() & 0xFF; - - for (Variant variant : variants) { - if (variant.getCoding() == coding) { - List> records = new ArrayList<>(); - records.add(new Record<>(Record.Type.STATUS, ((Byte) buff.readByte()).intValue())); - records.add(new Record<>(Record.Type.CURRENT_READING_DATE, buff.skip(2).readCurrentDate())); - buff.skip(2); - records.add(new Record<>(Record.Type.CURRENT_READING_DATE_SMOKE, buff.readPastDate())); - records.add(new Record<>(Record.Type.RSSI, device.getOriginalMessage().getRssi())); - - return new TechemSmokeDetector(device.getOriginalMessage(), device.getAdapter(), variant, records); - } - } - - return null; - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; + +import java.util.ArrayList; +import java.util.List; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.device.techem.Record; +import org.openhab.binding.wmbus.device.techem.TechemSmokeDetector; +import org.openhab.binding.wmbus.device.techem.Variant; +import org.openmuc.jmbus.SecondaryAddress; + +class TechemSmokeDetectorFrameDecoder extends AbstractTechemFrameDecoder { + + private final Variant[] variants; + + TechemSmokeDetectorFrameDecoder(Variant... variants) { + super(variants[0]); + this.variants = variants; + } + + @Override + protected TechemSmokeDetector decode(WMBusDevice device, SecondaryAddress address, byte[] buffer) { + int offset = address.asByteArray().length + 2; // 2 first bytes of data is CRC + Buffer buff = new Buffer(device.getOriginalMessage(), address); + int coding = buff.skip(2).readByte() & 0xFF; + + for (Variant variant : variants) { + if (variant.getCoding() == coding) { + List> records = new ArrayList<>(); + records.add(new Record<>(Record.Type.STATUS, ((Byte) buff.readByte()).intValue())); + records.add(new Record<>(Record.Type.CURRENT_READING_DATE, buff.skip(2).readCurrentDate())); + buff.skip(2); + records.add(new Record<>(Record.Type.CURRENT_READING_DATE_SMOKE, buff.readPastDate())); + records.add(new Record<>(Record.Type.RSSI, device.getOriginalMessage().getRssi())); + + return new TechemSmokeDetector(device.getOriginalMessage(), device.getAdapter(), variant, records); + } + } + + return null; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemVariantFrameDecoderSelector.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemVariantFrameDecoderSelector.java index 40639c8..974bb12 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemVariantFrameDecoderSelector.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemVariantFrameDecoderSelector.java @@ -1,71 +1,71 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; - -import java.util.Map; - -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.device.techem.TechemDevice; -import org.openmuc.jmbus.SecondaryAddress; - -import com.google.common.collect.ImmutableMap; - -/** - * Frame decoder which passes actual work to delegate after confirming matching device variant. - * - * Since Techem devices tend to use very similar wmbus identification and most of them reports themselves as RESERVED we - * need to determine its type based on device version. This decoder reads byte at fixed, given position and then try to - * match given decoders with it. - * Since standard leaves space for vendor specific device types this type is an attempt to sort it in a way which does - * not interfere with actual frame decoding logic. - * Tag offset is calculated from end of address field which contains manufacturer, device id, device version and device - * type. Value -1 passed to constructor indicates that variant determination is based on device version while value 0 - * points to last byte in address which is device type. - * - * @author Łukasz Dywicki - initial contribution - */ -class TechemVariantFrameDecoderSelector implements TechemFrameDecoder { - - private final Map> decoders; - private final int tagOffset; - protected static final int BASED_ON_VERSION = -1; - protected static final int BASED_ON_DEVICETYPE = 0; - - protected TechemVariantFrameDecoderSelector(int tagOffset, ImmutableMap> codecMap) { - this.tagOffset = tagOffset; - this.decoders = codecMap; - } - - @Override - public TechemDevice decode(WMBusDevice device) { - SecondaryAddress address = device.getOriginalMessage().getSecondaryAddress(); - byte[] addressArray = address.asByteArray(); - int tagIndex = addressArray.length + tagOffset - 1; - - if (addressArray.length <= tagIndex) { - return null; - } - - Byte tag = addressArray[tagIndex]; - if (decoders.containsKey(tag)) { - return decoders.get(tag).decode(device); - } - - return null; - } - - @Override - public boolean supports(String deviceVariant) { - return decoders.values().stream().filter(decoder -> decoder.supports(deviceVariant)).findFirst().isPresent(); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; + +import java.util.Map; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.device.techem.TechemDevice; +import org.openmuc.jmbus.SecondaryAddress; + +import com.google.common.collect.ImmutableMap; + +/** + * Frame decoder which passes actual work to delegate after confirming matching device variant. + * + * Since Techem devices tend to use very similar wmbus identification and most of them reports themselves as RESERVED we + * need to determine its type based on device version. This decoder reads byte at fixed, given position and then try to + * match given decoders with it. + * Since standard leaves space for vendor specific device types this type is an attempt to sort it in a way which does + * not interfere with actual frame decoding logic. + * Tag offset is calculated from end of address field which contains manufacturer, device id, device version and device + * type. Value -1 passed to constructor indicates that variant determination is based on device version while value 0 + * points to last byte in address which is device type. + * + * @author Łukasz Dywicki - initial contribution + */ +class TechemVariantFrameDecoderSelector implements TechemFrameDecoder { + + private final Map> decoders; + private final int tagOffset; + protected static final int BASED_ON_VERSION = -1; + protected static final int BASED_ON_DEVICETYPE = 0; + + protected TechemVariantFrameDecoderSelector(int tagOffset, ImmutableMap> codecMap) { + this.tagOffset = tagOffset; + this.decoders = codecMap; + } + + @Override + public TechemDevice decode(WMBusDevice device) { + SecondaryAddress address = device.getOriginalMessage().getSecondaryAddress(); + byte[] addressArray = address.asByteArray(); + int tagIndex = addressArray.length + tagOffset - 1; + + if (addressArray.length <= tagIndex) { + return null; + } + + Byte tag = addressArray[tagIndex]; + if (decoders.containsKey(tag)) { + return decoders.get(tag).decode(device); + } + + return null; + } + + @Override + public boolean supports(String deviceVariant) { + return decoders.values().stream().filter(decoder -> decoder.supports(deviceVariant)).findFirst().isPresent(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemVersionFrameDecoderSelector.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemVersionFrameDecoderSelector.java index ed1c2db..a7ac7bb 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemVersionFrameDecoderSelector.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemVersionFrameDecoderSelector.java @@ -1,41 +1,41 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; - -import static org.openhab.binding.wmbus.device.techem.TechemBindingConstants.*; - -import com.google.common.collect.ImmutableMap; - -class TechemVersionFrameDecoderSelector extends TechemVariantFrameDecoderSelector { - - private static final ImmutableMap> CODEC_MAP = ImmutableMap - .> builder() - .put(Byte.valueOf((byte) 0x45), new TechemHKVFrameDecoder(_68TCH6967_8)) - .put(Byte.valueOf((byte) 0x61), new TechemHKVFrameDecoder(_68TCH97255_8)) - .put(Byte.valueOf((byte) 0x64), new TechemHKVFrameDecoder(_68TCH100128_8)) - .put(Byte.valueOf((byte) 0x69), new TechemHKVRoomTempFrameDecoder(_68TCH105128_8, 0, 1)) - .put(Byte.valueOf((byte) 0x94), new TechemHKVRoomTempFrameDecoder(_68TCH148128_8, 1, 1)) - .put(Byte.valueOf((byte) 0x76), - new TechemSmokeDetectorFrameDecoder(_68TCH118255_161_A0, _68TCH118255_161_A1)) - - .put(Byte.valueOf((byte) 0x22), new TechemHeatMeterFrameDecoder(_68TCH3467_4)) - .put(Byte.valueOf((byte) 0x39), new TechemHeatMeterFrameDecoder(_68TCH5767_4)) - .put(Byte.valueOf((byte) 0x71), new TechemHeatMeterFrameDecoder(_68TCH11367_4_A0, _68TCH11367_4_A2)) - .put(Byte.valueOf((byte) 0x57), new TechemHeatMeterFrameDecoder(_68TCH8768_4)) - - .build(); - - TechemVersionFrameDecoderSelector() { - super(BASED_ON_VERSION, CODEC_MAP); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; + +import static org.openhab.binding.wmbus.device.techem.TechemBindingConstants.*; + +import com.google.common.collect.ImmutableMap; + +class TechemVersionFrameDecoderSelector extends TechemVariantFrameDecoderSelector { + + private static final ImmutableMap> CODEC_MAP = ImmutableMap + .> builder() + .put(Byte.valueOf((byte) 0x45), new TechemHKVFrameDecoder(_68TCH6967_8)) + .put(Byte.valueOf((byte) 0x61), new TechemHKVFrameDecoder(_68TCH97255_8)) + .put(Byte.valueOf((byte) 0x64), new TechemHKVFrameDecoder(_68TCH100128_8)) + .put(Byte.valueOf((byte) 0x69), new TechemHKVRoomTempFrameDecoder(_68TCH105128_8, 0, 1)) + .put(Byte.valueOf((byte) 0x94), new TechemHKVRoomTempFrameDecoder(_68TCH148128_8, 1, 1)) + .put(Byte.valueOf((byte) 0x76), + new TechemSmokeDetectorFrameDecoder(_68TCH118255_161_A0, _68TCH118255_161_A1)) + + .put(Byte.valueOf((byte) 0x22), new TechemHeatMeterFrameDecoder(_68TCH3467_4)) + .put(Byte.valueOf((byte) 0x39), new TechemHeatMeterFrameDecoder(_68TCH5767_4)) + .put(Byte.valueOf((byte) 0x71), new TechemHeatMeterFrameDecoder(_68TCH11367_4_A0, _68TCH11367_4_A2)) + .put(Byte.valueOf((byte) 0x57), new TechemHeatMeterFrameDecoder(_68TCH8768_4)) + + .build(); + + TechemVersionFrameDecoderSelector() { + super(BASED_ON_VERSION, CODEC_MAP); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemWaterMeterFrameDecoder.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemWaterMeterFrameDecoder.java index eee9e2f..143c28d 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemWaterMeterFrameDecoder.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/decoder/TechemWaterMeterFrameDecoder.java @@ -1,72 +1,72 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; - -import java.util.ArrayList; -import java.util.List; - -import javax.measure.Unit; -import javax.measure.quantity.Volume; - -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.device.techem.Record; -import org.openhab.binding.wmbus.device.techem.TechemWaterMeter; -import org.openhab.binding.wmbus.device.techem.Variant; -import org.openmuc.jmbus.SecondaryAddress; - -import tec.uom.se.quantity.Quantities; -import tec.uom.se.unit.Units; - -class TechemWaterMeterFrameDecoder extends AbstractTechemFrameDecoder { - - /** - * Offset of counter byte, if set to -1 means that there is no counter and history to be read. - */ - private final int counterByteOffset; - - TechemWaterMeterFrameDecoder(Variant variant, int counterByteOffset) { - super(variant); - this.counterByteOffset = counterByteOffset; - } - - @Override - protected TechemWaterMeter decode(WMBusDevice device, SecondaryAddress address, byte[] buffer) { - Buffer buff = new Buffer(device.getOriginalMessage(), address); - - int coding = buff.skip(2).readByte() & 0xFF; - - if (coding == variant.getCoding()) { - Unit unit = Units.CUBIC_METRE; - - List> records = new ArrayList<>(); - records.add(new Record<>(Record.Type.STATUS, ((Byte) buff.readByte()).intValue())); - records.add(new Record<>(Record.Type.PAST_READING_DATE, buff.readPastDate())); - float pastVolume = buff.readFloat(Buffer._SCALE_FACTOR_1_10th); - records.add(new Record<>(Record.Type.PAST_VOLUME, Quantities.getQuantity(pastVolume, unit))); - records.add(new Record<>(Record.Type.CURRENT_READING_DATE, buff.readCurrentDate())); - float number = buff.readFloat(Buffer._SCALE_FACTOR_1_10th); - records.add(new Record<>(Record.Type.CURRENT_VOLUME, Quantities.getQuantity(number, unit))); - records.add(new Record<>(Record.Type.RSSI, device.getOriginalMessage().getRssi())); - - if (counterByteOffset >= 0) { - int counter = buff.skip(counterByteOffset).readByte() & 0xFF; - records.add(new Record<>(Record.Type.COUNTER, counter)); - records.add(new Record<>(Record.Type.ALMANAC, buff.readHistory())); - } - - return new TechemWaterMeter(device.getOriginalMessage(), device.getAdapter(), variant, records); - } - - return null; - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem.decoder; + +import java.util.ArrayList; +import java.util.List; + +import javax.measure.Unit; +import javax.measure.quantity.Volume; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.device.techem.Record; +import org.openhab.binding.wmbus.device.techem.TechemWaterMeter; +import org.openhab.binding.wmbus.device.techem.Variant; +import org.openmuc.jmbus.SecondaryAddress; + +import tec.uom.se.quantity.Quantities; +import tec.uom.se.unit.Units; + +class TechemWaterMeterFrameDecoder extends AbstractTechemFrameDecoder { + + /** + * Offset of counter byte, if set to -1 means that there is no counter and history to be read. + */ + private final int counterByteOffset; + + TechemWaterMeterFrameDecoder(Variant variant, int counterByteOffset) { + super(variant); + this.counterByteOffset = counterByteOffset; + } + + @Override + protected TechemWaterMeter decode(WMBusDevice device, SecondaryAddress address, byte[] buffer) { + Buffer buff = new Buffer(device.getOriginalMessage(), address); + + int coding = buff.skip(2).readByte() & 0xFF; + + if (coding == variant.getCoding()) { + Unit unit = Units.CUBIC_METRE; + + List> records = new ArrayList<>(); + records.add(new Record<>(Record.Type.STATUS, ((Byte) buff.readByte()).intValue())); + records.add(new Record<>(Record.Type.PAST_READING_DATE, buff.readPastDate())); + float pastVolume = buff.readFloat(Buffer._SCALE_FACTOR_1_10th); + records.add(new Record<>(Record.Type.PAST_VOLUME, Quantities.getQuantity(pastVolume, unit))); + records.add(new Record<>(Record.Type.CURRENT_READING_DATE, buff.readCurrentDate())); + float number = buff.readFloat(Buffer._SCALE_FACTOR_1_10th); + records.add(new Record<>(Record.Type.CURRENT_VOLUME, Quantities.getQuantity(number, unit))); + records.add(new Record<>(Record.Type.RSSI, device.getOriginalMessage().getRssi())); + + if (counterByteOffset >= 0) { + int counter = buff.skip(counterByteOffset).readByte() & 0xFF; + records.add(new Record<>(Record.Type.COUNTER, counter)); + records.add(new Record<>(Record.Type.ALMANAC, buff.readHistory())); + } + + return new TechemWaterMeter(device.getOriginalMessage(), device.getAdapter(), variant, records); + } + + return null; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/handler/TechemMeterHandler.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/handler/TechemMeterHandler.java index 8b079f8..e5a95fb 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/handler/TechemMeterHandler.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/device/techem/handler/TechemMeterHandler.java @@ -1,155 +1,155 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.device.techem.handler; - -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.Map; -import java.util.Optional; - -import javax.measure.Quantity; - -import org.eclipse.jdt.annotation.NonNull; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.config.DateFieldMode; -import org.openhab.binding.wmbus.device.techem.Record; -import org.openhab.binding.wmbus.device.techem.Record.Type; -import org.openhab.binding.wmbus.device.techem.TechemBindingConstants; -import org.openhab.binding.wmbus.device.techem.TechemDevice; -import org.openhab.binding.wmbus.device.techem.decoder.TechemFrameDecoder; -import org.openhab.binding.wmbus.handler.WMBusDeviceHandler; -import org.openhab.core.library.CoreItemFactory; -import org.openhab.core.library.types.DateTimeType; -import org.openhab.core.library.types.DecimalType; -import org.openhab.core.library.types.QuantityType; -import org.openhab.core.thing.Channel; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.types.Command; -import org.openhab.core.types.RefreshType; -import org.openhab.core.types.State; -import org.openmuc.jmbus.DecodingException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link TechemMeterHandler} holds logic related to retrieval (actually mapping) of data reported by various techem - * devices. - * - * @author Łukasz Dywicki - Initial contribution - */ - -public class TechemMeterHandler extends WMBusDeviceHandler { - - private final Logger logger = LoggerFactory.getLogger(TechemMeterHandler.class); - - private final Class type; - private final TechemFrameDecoder decoder; - private final Map channelMapping; - - public TechemMeterHandler(Thing thing, Class type, TechemFrameDecoder decoder) { - this(thing, type, decoder, fetchMapping(thing.getThingTypeUID())); - } - - protected TechemMeterHandler(Thing thing, Class type, TechemFrameDecoder decoder, - Map channelMapping) { - super(thing); - this.type = type; - this.decoder = decoder; - this.channelMapping = channelMapping; - } - - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - logger.trace("handleCommand {} for channel {}", command.toString(), channelUID.toString()); - if (command == RefreshType.REFRESH) { - if (wmbusDevice != null) { - Type recordType = channelMapping.get(channelUID.getId()); - if (recordType != null) { - Optional> record = wmbusDevice.getRecord(recordType); - - if (recordType.isDate()) { - String acceptedType = ""; - Channel channel = getThing().getChannel(channelUID.getId()); - if (channel != null) { - acceptedType = channel.getAcceptedItemType(); - } - if (CoreItemFactory.DATETIME.equals(acceptedType) - && DateFieldMode.DATE_TIME == getDateFieldMode()) { - record.map(measurement -> map(measurement, measurement.getValue())) - .ifPresent(state -> updateState(channelUID.getId(), state)); - } else if (CoreItemFactory.STRING.equals(acceptedType) - && DateFieldMode.FORMATTED_STRING == getDateFieldMode()) { - record.map(measurement -> map(measurement, measurement.getValue())) - .ifPresent(state -> updateState(channelUID.getId(), state)); - } else if (CoreItemFactory.NUMBER.equals(acceptedType) - && DateFieldMode.UNIX_TIMESTAMP == getDateFieldMode()) { - record.map(measurement -> map(measurement, measurement.getValue())) - .ifPresent(state -> updateState(channelUID.getId(), state)); - } else { - logger.info( - "Ignoring update of channel {}, it is date field with no proper mapping available.", - channelUID); - } - } else { - record.map(measurement -> map(measurement, measurement.getValue())) - .ifPresent(state -> updateState(channelUID.getId(), state)); - } - - if (!record.isPresent()) { - logger.warn("Could not read value of record {} in received frame", recordType); - } - } else { - logger.warn("Unown channel {}, not supported by {}", channelUID, thing.getUID()); - } - } - } - } - - private State map(Record measurement, Object value) { - State returnvalue = null; // maybe Undef would be better? - if (value instanceof Quantity) { - Quantity quantity = (Quantity) value; - returnvalue = new QuantityType<>(quantity.getValue(), quantity.getUnit()); - } else if (value instanceof Integer) { - returnvalue = new DecimalType(((Integer) value).floatValue()); - } else if (value instanceof Double) { - returnvalue = new DecimalType(((Double) value).floatValue()); - } else if (value instanceof Float) { - returnvalue = new DecimalType(((Float) value).floatValue()); - } else if (value instanceof LocalDateTime) { - returnvalue = convertDate( - new DateTimeType(ZonedDateTime.of((LocalDateTime) value, ZoneId.systemDefault()))); - } else if (value instanceof ZonedDateTime) { - returnvalue = convertDate(new DateTimeType((ZonedDateTime) value)); - } - - return returnvalue; - } - - @Override - protected T parseDevice(WMBusDevice device) throws DecodingException { - TechemDevice decodedDevice = decoder.decode(device); - if (type.isInstance(decodedDevice)) { - return type.cast(decodedDevice); - } - return null; - } - - private static Map fetchMapping(@NonNull ThingTypeUID thingTypeUID) { - return TechemBindingConstants.RECORD_MAP.get(thingTypeUID); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.device.techem.handler; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Map; +import java.util.Optional; + +import javax.measure.Quantity; + +import org.eclipse.jdt.annotation.NonNull; +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.config.DateFieldMode; +import org.openhab.binding.wmbus.device.techem.Record; +import org.openhab.binding.wmbus.device.techem.Record.Type; +import org.openhab.binding.wmbus.device.techem.TechemBindingConstants; +import org.openhab.binding.wmbus.device.techem.TechemDevice; +import org.openhab.binding.wmbus.device.techem.decoder.TechemFrameDecoder; +import org.openhab.binding.wmbus.handler.WMBusDeviceHandler; +import org.openhab.core.library.CoreItemFactory; +import org.openhab.core.library.types.DateTimeType; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; +import org.openmuc.jmbus.DecodingException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link TechemMeterHandler} holds logic related to retrieval (actually mapping) of data reported by various techem + * devices. + * + * @author Łukasz Dywicki - Initial contribution + */ + +public class TechemMeterHandler extends WMBusDeviceHandler { + + private final Logger logger = LoggerFactory.getLogger(TechemMeterHandler.class); + + private final Class type; + private final TechemFrameDecoder decoder; + private final Map channelMapping; + + public TechemMeterHandler(Thing thing, Class type, TechemFrameDecoder decoder) { + this(thing, type, decoder, fetchMapping(thing.getThingTypeUID())); + } + + protected TechemMeterHandler(Thing thing, Class type, TechemFrameDecoder decoder, + Map channelMapping) { + super(thing); + this.type = type; + this.decoder = decoder; + this.channelMapping = channelMapping; + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + logger.trace("handleCommand {} for channel {}", command.toString(), channelUID.toString()); + if (command == RefreshType.REFRESH) { + if (wmbusDevice != null) { + Type recordType = channelMapping.get(channelUID.getId()); + if (recordType != null) { + Optional> record = wmbusDevice.getRecord(recordType); + + if (recordType.isDate()) { + String acceptedType = ""; + Channel channel = getThing().getChannel(channelUID.getId()); + if (channel != null) { + acceptedType = channel.getAcceptedItemType(); + } + if (CoreItemFactory.DATETIME.equals(acceptedType) + && DateFieldMode.DATE_TIME == getDateFieldMode()) { + record.map(measurement -> map(measurement, measurement.getValue())) + .ifPresent(state -> updateState(channelUID.getId(), state)); + } else if (CoreItemFactory.STRING.equals(acceptedType) + && DateFieldMode.FORMATTED_STRING == getDateFieldMode()) { + record.map(measurement -> map(measurement, measurement.getValue())) + .ifPresent(state -> updateState(channelUID.getId(), state)); + } else if (CoreItemFactory.NUMBER.equals(acceptedType) + && DateFieldMode.UNIX_TIMESTAMP == getDateFieldMode()) { + record.map(measurement -> map(measurement, measurement.getValue())) + .ifPresent(state -> updateState(channelUID.getId(), state)); + } else { + logger.info( + "Ignoring update of channel {}, it is date field with no proper mapping available.", + channelUID); + } + } else { + record.map(measurement -> map(measurement, measurement.getValue())) + .ifPresent(state -> updateState(channelUID.getId(), state)); + } + + if (!record.isPresent()) { + logger.warn("Could not read value of record {} in received frame", recordType); + } + } else { + logger.warn("Unown channel {}, not supported by {}", channelUID, thing.getUID()); + } + } + } + } + + private State map(Record measurement, Object value) { + State returnvalue = null; // maybe Undef would be better? + if (value instanceof Quantity) { + Quantity quantity = (Quantity) value; + returnvalue = new QuantityType<>(quantity.getValue(), quantity.getUnit()); + } else if (value instanceof Integer) { + returnvalue = new DecimalType(((Integer) value).floatValue()); + } else if (value instanceof Double) { + returnvalue = new DecimalType(((Double) value).floatValue()); + } else if (value instanceof Float) { + returnvalue = new DecimalType(((Float) value).floatValue()); + } else if (value instanceof LocalDateTime) { + returnvalue = convertDate( + new DateTimeType(ZonedDateTime.of((LocalDateTime) value, ZoneId.systemDefault()))); + } else if (value instanceof ZonedDateTime) { + returnvalue = convertDate(new DateTimeType((ZonedDateTime) value)); + } + + return returnvalue; + } + + @Override + protected T parseDevice(WMBusDevice device) throws DecodingException { + TechemDevice decodedDevice = decoder.decode(device); + if (type.isInstance(decodedDevice)) { + return type.cast(decodedDevice); + } + return null; + } + + private static Map fetchMapping(@NonNull ThingTypeUID thingTypeUID) { + return TechemBindingConstants.RECORD_MAP.get(thingTypeUID); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/discovery/CompositeMessageListener.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/discovery/CompositeMessageListener.java index 10c8461..6b2fa16 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/discovery/CompositeMessageListener.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/discovery/CompositeMessageListener.java @@ -1,60 +1,60 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.discovery; - -import java.util.LinkedHashSet; -import java.util.Set; - -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openhab.binding.wmbus.handler.WMBusMessageListener; - -/** - * Listener backed by given set of other listeners. - * - * @author Łukasz Dywicki - initial contribution - */ -public class CompositeMessageListener implements WMBusMessageListener { - - private final Set listeners; - - public CompositeMessageListener() { - this(new LinkedHashSet<>()); - } - - public CompositeMessageListener(Set listeners) { - this.listeners = listeners; - } - - @Override - public void onNewWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { - for (WMBusMessageListener listener : listeners) { - listener.onNewWMBusDevice(adapter, device); - } - } - - @Override - public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { - for (WMBusMessageListener listener : listeners) { - listener.onChangedWMBusDevice(adapter, device); - } - } - - public void addMessageListener(WMBusMessageListener listener) { - this.listeners.add(listener); - } - - public void removeMessageListener(WMBusMessageListener listener) { - this.listeners.add(listener); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.discovery; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openhab.binding.wmbus.handler.WMBusMessageListener; + +/** + * Listener backed by given set of other listeners. + * + * @author Łukasz Dywicki - initial contribution + */ +public class CompositeMessageListener implements WMBusMessageListener { + + private final Set listeners; + + public CompositeMessageListener() { + this(new LinkedHashSet<>()); + } + + public CompositeMessageListener(Set listeners) { + this.listeners = listeners; + } + + @Override + public void onNewWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { + for (WMBusMessageListener listener : listeners) { + listener.onNewWMBusDevice(adapter, device); + } + } + + @Override + public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { + for (WMBusMessageListener listener : listeners) { + listener.onChangedWMBusDevice(adapter, device); + } + } + + public void addMessageListener(WMBusMessageListener listener) { + this.listeners.add(listener); + } + + public void removeMessageListener(WMBusMessageListener listener) { + this.listeners.add(listener); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/discovery/DebugMessageListener.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/discovery/DebugMessageListener.java index e42a130..9b6b0fe 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/discovery/DebugMessageListener.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/discovery/DebugMessageListener.java @@ -1,71 +1,71 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.discovery; - -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openhab.binding.wmbus.handler.WMBusMessageListener; -import org.openhab.core.util.HexUtils; -import org.openmuc.jmbus.DataRecord; -import org.openmuc.jmbus.DecodingException; -import org.openmuc.jmbus.EncryptionMode; -import org.openmuc.jmbus.SecondaryAddress; -import org.openmuc.jmbus.VariableDataStructure; -import org.osgi.service.component.annotations.Component; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@Component(immediate = true) -public class DebugMessageListener implements WMBusMessageListener { - - private final Logger logger = LoggerFactory.getLogger(DebugMessageListener.class); - - @Override - public void onNewWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { - log(device); - } - - @Override - public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { - log(device); - } - - private void log(WMBusDevice device) { - SecondaryAddress secondaryAddress = device.getOriginalMessage().getSecondaryAddress(); - - if (!"TCH".equals(secondaryAddress.getManufacturerId())) { - - try { - VariableDataStructure vdr = device.getOriginalMessage().getVariableDataResponse(); - - if (vdr.getEncryptionMode() == EncryptionMode.NONE) { - vdr.decode(); - - logger.debug( - "Received telegram ({}): access number: {}, status: {}, encryption mode: {}, number of encrypted blocks: {}", - secondaryAddress, vdr.getAccessNumber(), vdr.getStatus(), vdr.getEncryptionMode(), - vdr.getNumberOfEncryptedBlocks()); - logger.debug("Message in hex: {}", HexUtils.bytesToHex(device.getOriginalMessage().asBlob())); - - for (DataRecord record : vdr.getDataRecords()) { - logger.debug("> record: {}", record.toString()); - } - } else { - logger.debug("Encrypted block {}", vdr); - } - } catch (DecodingException e) { - logger.debug("Could not decode frame ({})", secondaryAddress, e.getMessage()); - } - } - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.discovery; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openhab.binding.wmbus.handler.WMBusMessageListener; +import org.openhab.core.util.HexUtils; +import org.openmuc.jmbus.DataRecord; +import org.openmuc.jmbus.DecodingException; +import org.openmuc.jmbus.EncryptionMode; +import org.openmuc.jmbus.SecondaryAddress; +import org.openmuc.jmbus.VariableDataStructure; +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component(immediate = true) +public class DebugMessageListener implements WMBusMessageListener { + + private final Logger logger = LoggerFactory.getLogger(DebugMessageListener.class); + + @Override + public void onNewWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { + log(device); + } + + @Override + public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { + log(device); + } + + private void log(WMBusDevice device) { + SecondaryAddress secondaryAddress = device.getOriginalMessage().getSecondaryAddress(); + + if (!"TCH".equals(secondaryAddress.getManufacturerId())) { + + try { + VariableDataStructure vdr = device.getOriginalMessage().getVariableDataResponse(); + + if (vdr.getEncryptionMode() == EncryptionMode.NONE) { + vdr.decode(); + + logger.debug( + "Received telegram ({}): access number: {}, status: {}, encryption mode: {}, number of encrypted blocks: {}", + secondaryAddress, vdr.getAccessNumber(), vdr.getStatus(), vdr.getEncryptionMode(), + vdr.getNumberOfEncryptedBlocks()); + logger.debug("Message in hex: {}", HexUtils.bytesToHex(device.getOriginalMessage().asBlob())); + + for (DataRecord record : vdr.getDataRecords()) { + logger.debug("> record: {}", record.toString()); + } + } else { + logger.debug("Encrypted block {}", vdr); + } + } catch (DecodingException e) { + logger.debug("Could not decode frame ({})", secondaryAddress, e.getMessage()); + } + } + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/discovery/WMBusDiscoveryParticipant.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/discovery/WMBusDiscoveryParticipant.java index 91ff9c9..25ef488 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/discovery/WMBusDiscoveryParticipant.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/discovery/WMBusDiscoveryParticipant.java @@ -1,58 +1,58 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.discovery; - -import java.util.Set; - -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.core.config.discovery.DiscoveryResult; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.ThingUID; - -/** - * Discovery participant which is responsible for identification of WMBus device. - * - * Realizations of this interface might return empty results when they do not know device in order to let other - * participants do their job. - * - * @author Łukasz Dywicki - initial contribution. - */ -public interface WMBusDiscoveryParticipant { - - /** - * Defines the list of thing types that this participant can identify - * - * @return a set of thing type UIDs for which results can be created - */ - Set getSupportedThingTypeUIDs(); - - /** - * Creates a discovery result for a WMBus device - * - * @param device the WMbus device found on the network - * @return the according discovery result or null, if device is not - * supported by this participant - */ - @Nullable - DiscoveryResult createResult(WMBusDevice device); - - /** - * Returns the thing UID for a WMBus device - * - * @param device the WMBus device - * @return a thing UID or null, if the device is not supported by this participant - */ - @Nullable - public ThingUID getThingUID(WMBusDevice device); -} +/** + * Copyright (c) 2010-2021 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.wmbus.discovery; + +import java.util.Set; + +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; + +/** + * Discovery participant which is responsible for identification of WMBus device. + * + * Realizations of this interface might return empty results when they do not know device in order to let other + * participants do their job. + * + * @author Łukasz Dywicki - initial contribution. + */ +public interface WMBusDiscoveryParticipant { + + /** + * Defines the list of thing types that this participant can identify + * + * @return a set of thing type UIDs for which results can be created + */ + Set getSupportedThingTypeUIDs(); + + /** + * Creates a discovery result for a WMBus device + * + * @param device the WMbus device found on the network + * @return the according discovery result or null, if device is not + * supported by this participant + */ + @Nullable + DiscoveryResult createResult(WMBusDevice device); + + /** + * Returns the thing UID for a WMBus device + * + * @param device the WMBus device + * @return a thing UID or null, if the device is not supported by this participant + */ + @Nullable + public ThingUID getThingUID(WMBusDevice device); +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/VirtualWMBusBridgeHandler.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/VirtualWMBusBridgeHandler.java index 2d5e5b6..06b4c7a 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/VirtualWMBusBridgeHandler.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/VirtualWMBusBridgeHandler.java @@ -1,87 +1,87 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.handler; - -import static org.openhab.binding.wmbus.WMBusBindingConstants.THING_TYPE_VIRTUAL_BRIDGE; - -import java.util.Collection; -import java.util.Collections; -import java.util.Set; - -import org.openhab.binding.wmbus.config.WMBusBridgeConfig; -import org.openhab.binding.wmbus.internal.WMBusReceiver; -import org.openhab.core.config.core.status.ConfigStatusMessage; -import org.openhab.core.thing.Bridge; -import org.openhab.core.thing.ThingStatus; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.io.transport.mbus.wireless.KeyStorage; - -/** - * The {@link VirtualWMBusBridgeHandler} class defines This class represents the WMBus bridge which - * does not ship any real connection . - * - * @author Hanno - Felix Wagner - Initial contribution - * @author Łukasz Dywicki - Bringing back functionality via separate handler - */ -public class VirtualWMBusBridgeHandler extends WMBusBridgeHandlerBase { - - public final static Set SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_VIRTUAL_BRIDGE); - - public VirtualWMBusBridgeHandler(Bridge bridge, KeyStorage keyStorage) { - super(bridge, keyStorage); - } - - @Override - public Collection getConfigStatus() { - return Collections.emptyList(); // all good, otherwise add some messages - } - - /** - * Connects to the WMBus radio module and updates bridge status. - * - * @see org.openhab.core.thing.binding.BaseThingHandler#initialize() - */ - @Override - public void initialize() { - logger.debug("WMBusBridgeHandler: initialize()"); - - updateStatus(ThingStatus.UNKNOWN); - wmbusReceiver = new WMBusReceiver(this); - - WMBusBridgeConfig config = getConfigAs(WMBusBridgeConfig.class); - if (config.deviceIDFilter == null || config.deviceIDFilter.trim().isEmpty()) { - logger.debug("Device ID filter is empty."); - } else { - wmbusReceiver.setFilterIDs(config.getDeviceIDFilter()); - } - - // success - logger.debug("WMBusBridgeHandler: Initialization done! Setting bridge online"); - updateStatus(ThingStatus.ONLINE); - } - - @Override - public void dispose() { - super.dispose(); - - if (wmbusReceiver != null) { - wmbusReceiver = null; - } - } - - public void reset() { - wmbusReceiver = null; - initialize(); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.handler; + +import static org.openhab.binding.wmbus.WMBusBindingConstants.THING_TYPE_VIRTUAL_BRIDGE; + +import java.util.Collection; +import java.util.Collections; +import java.util.Set; + +import org.openhab.binding.wmbus.config.WMBusBridgeConfig; +import org.openhab.binding.wmbus.internal.WMBusReceiver; +import org.openhab.core.config.core.status.ConfigStatusMessage; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.io.transport.mbus.wireless.KeyStorage; + +/** + * The {@link VirtualWMBusBridgeHandler} class defines This class represents the WMBus bridge which + * does not ship any real connection . + * + * @author Hanno - Felix Wagner - Initial contribution + * @author Łukasz Dywicki - Bringing back functionality via separate handler + */ +public class VirtualWMBusBridgeHandler extends WMBusBridgeHandlerBase { + + public final static Set SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_VIRTUAL_BRIDGE); + + public VirtualWMBusBridgeHandler(Bridge bridge, KeyStorage keyStorage) { + super(bridge, keyStorage); + } + + @Override + public Collection getConfigStatus() { + return Collections.emptyList(); // all good, otherwise add some messages + } + + /** + * Connects to the WMBus radio module and updates bridge status. + * + * @see org.openhab.core.thing.binding.BaseThingHandler#initialize() + */ + @Override + public void initialize() { + logger.debug("WMBusBridgeHandler: initialize()"); + + updateStatus(ThingStatus.UNKNOWN); + wmbusReceiver = new WMBusReceiver(this); + + WMBusBridgeConfig config = getConfigAs(WMBusBridgeConfig.class); + if (config.deviceIDFilter == null || config.deviceIDFilter.trim().isEmpty()) { + logger.debug("Device ID filter is empty."); + } else { + wmbusReceiver.setFilterIDs(config.getDeviceIDFilter()); + } + + // success + logger.debug("WMBusBridgeHandler: Initialization done! Setting bridge online"); + updateStatus(ThingStatus.ONLINE); + } + + @Override + public void dispose() { + super.dispose(); + + if (wmbusReceiver != null) { + wmbusReceiver = null; + } + } + + public void reset() { + wmbusReceiver = null; + initialize(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusAdapter.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusAdapter.java index 11847b8..b5d69ab 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusAdapter.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusAdapter.java @@ -1,36 +1,36 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.handler; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.config.DateFieldMode; -import org.openhab.core.common.registry.Identifiable; -import org.openhab.core.thing.ThingUID; - -/** - * Representation of WMBus device which is holds a link to radio device. - *

    - * Main purpose of this interface is to cut off hard dependency on {@link WMBusBridgeHandler}. - * - * @author Łukasz Dywicki - */ -@NonNullByDefault -public interface WMBusAdapter extends Identifiable { - - void processMessage(WMBusDevice device); - - void reset(); - - DateFieldMode getDateFieldMode(); -} +/** + * Copyright (c) 2010-2021 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.wmbus.handler; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.config.DateFieldMode; +import org.openhab.core.common.registry.Identifiable; +import org.openhab.core.thing.ThingUID; + +/** + * Representation of WMBus device which is holds a link to radio device. + *

    + * Main purpose of this interface is to cut off hard dependency on {@link WMBusBridgeHandler}. + * + * @author Łukasz Dywicki + */ +@NonNullByDefault +public interface WMBusAdapter extends Identifiable { + + void processMessage(WMBusDevice device); + + void reset(); + + DateFieldMode getDateFieldMode(); +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusBridgeHandler.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusBridgeHandler.java index c7b7394..6e8f700 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusBridgeHandler.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusBridgeHandler.java @@ -1,208 +1,208 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.handler; - -import static org.openhab.binding.wmbus.WMBusBindingConstants.*; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - -import org.openhab.binding.wmbus.config.StickModel; -import org.openhab.binding.wmbus.config.WMBusSerialBridgeConfig; -import org.openhab.binding.wmbus.internal.WMBusReceiver; -import org.openhab.core.config.core.status.ConfigStatusMessage; -import org.openhab.core.thing.Bridge; -import org.openhab.core.thing.ThingStatus; -import org.openhab.core.thing.ThingStatusDetail; -import org.openhab.io.transport.mbus.wireless.KeyStorage; -import org.openmuc.jmbus.wireless.WMBusConnection; -import org.openmuc.jmbus.wireless.WMBusConnection.WMBusManufacturer; -import org.openmuc.jmbus.wireless.WMBusConnection.WMBusSerialBuilder; -import org.openmuc.jmbus.wireless.WMBusMode; - -/** - * The {@link WMBusBridgeHandler} class defines This class represents the WMBus bridge and handles general events for - * the whole group of WMBus devices. - * - * @author Hanno - Felix Wagner - Initial contribution - */ - -public class WMBusBridgeHandler extends WMBusBridgeHandlerBase { - - private ScheduledFuture initFuture; - private WMBusConnection wmbusConnection; - - public WMBusBridgeHandler(Bridge bridge, KeyStorage keyStorage) { - super(bridge, keyStorage); - } - - @Override - public Collection getConfigStatus() { - List messages = new ArrayList<>(); - WMBusSerialBridgeConfig config = getConfigAs(WMBusSerialBridgeConfig.class); - - // check stick model - if (config.stickModel == null) { - messages.add(ConfigStatusMessage.Builder.error(CONFKEY_STICK_MODEL) - .withMessageKeySuffix(CONFKEY_STICK_MODEL).build()); - } - // check serial device name - if (config.serialDevice == null) { - messages.add(ConfigStatusMessage.Builder.error(CONFKEY_INTERFACE_NAME) - .withMessageKeySuffix(CONFKEY_INTERFACE_NAME).build()); - } - // check radio mode - if (config.radioMode == null) { - messages.add(ConfigStatusMessage.Builder.error(CONFKEY_RADIO_MODE).withMessageKeySuffix(CONFKEY_RADIO_MODE) - .build()); - } - - return messages; - } - - /** - * Connects to the WMBus radio module and updates bridge status. - * - * @see org.openhab.core.thing.binding.BaseThingHandler#initialize() - */ - @Override - public void initialize() { - logger.debug("WMBusBridgeHandler: initialize()"); - - WMBusSerialBridgeConfig config = getConfigAs(WMBusSerialBridgeConfig.class); - updateStatus(ThingStatus.UNKNOWN); - initFuture = scheduler.schedule(() -> { - // set up WMBus receiver = handler for radio telegrams - if (wmbusReceiver == null) { - StickModel stickModel = config.stickModel; - String interfaceName = config.serialDevice; - WMBusMode radioMode = config.radioMode; - - // connect to the radio module / open WMBus connection - logger.debug("Opening wmbus stick {} serial port {} in mode {}", stickModel, interfaceName, radioMode); - - WMBusManufacturer wmBusManufacturer = parseManufacturer(stickModel.name().toUpperCase()); - if (wmBusManufacturer == null) { - logger.error("Cannot open WMBus device. Unknown manufacturer given: " + stickModel - + ". Expected 'amber' or 'imst' or 'rc'."); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, - "Cannot open WMBus device. Unknown manufacturer given: " + stickModel - + ". Expected 'amber' or 'imst' or 'rc'."); - return; - } - logger.debug("Building new connection"); - - wmbusReceiver = new WMBusReceiver(this); - - if (config.deviceIDFilter == null || config.deviceIDFilter.trim().isEmpty()) { - logger.debug("Device ID filter is empty."); - } else { - wmbusReceiver.setFilterIDs(config.getDeviceIDFilter()); - } - - WMBusSerialBuilder connectionBuilder = new WMBusSerialBuilder(wmBusManufacturer, wmbusReceiver, - interfaceName); - - logger.debug("Setting WMBus radio mode to {}", radioMode.toString()); - connectionBuilder.setMode(radioMode); - connectionBuilder.setTimeout(1000); // infinite - // connectionBuilder.setTimeout(0); // infinite - - try { - logger.debug("Building/opening connection"); - logger.debug( - "NOTE: if initialization does not progress from here, check systemd journal for Execptions -- probably native lib still loaded by another ClassLoader = previous version or instance of WMBus binding -> restart OpenHAB"); - if (wmbusConnection != null) { - logger.debug("Connection already set, closing old"); - wmbusConnection.close(); - wmbusConnection = null; - } - wmbusConnection = connectionBuilder.build(); - } catch (IOException e) { - logger.error("Cannot open WMBus device. Connection builder returned: " + e.getMessage()); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, - "Cannot open WMBus device. Connection builder returned: " + e.getMessage()); - return; - } - - logger.debug("Connected to WMBus serial port"); - - // close WMBus connection on shutdown - logger.trace("Setting shutdown hook"); - Runtime.getRuntime().addShutdownHook(new Thread() { - @Override - public void run() { - if (wmbusConnection != null) { - try { - logger.debug("Closing connection to WMBus radio device"); - wmbusConnection.close(); - } catch (IOException e) { - logger.error("Cannot close connection to WMBus radio module: " + e.getMessage()); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, - "Cannot close connection to WMBus radio module: " + e.getMessage()); - return; - } - } - } - }); - - // success - logger.debug("WMBusBridgeHandler: Initialization done! Setting bridge online"); - updateStatus(ThingStatus.ONLINE); - } - }, 0, TimeUnit.SECONDS); - } - - private static WMBusManufacturer parseManufacturer(String manufacturer) { - switch (manufacturer.toLowerCase()) { - case MANUFACTURER_AMBER: - return WMBusManufacturer.AMBER; - case MANUFACTURER_RADIO_CRAFTS: - return WMBusManufacturer.RADIO_CRAFTS; - case MANUFACTURER_IMST: - return WMBusManufacturer.IMST; - default: - return null; - } - } - - @Override - public void dispose() { - super.dispose(); - logger.debug("WMBus bridge Handler disposed."); - - if (wmbusConnection != null) { - logger.debug("Close serial device connection"); - try { - wmbusConnection.close(); - } catch (IOException e) { - logger.error("An exception occurred while closing the wmbusConnection", e); - } - wmbusConnection = null; - } - - if (wmbusReceiver != null) { - wmbusReceiver = null; - } - - if (initFuture != null) { - initFuture.cancel(true); - initFuture = null; - } - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.handler; + +import static org.openhab.binding.wmbus.WMBusBindingConstants.*; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.openhab.binding.wmbus.config.StickModel; +import org.openhab.binding.wmbus.config.WMBusSerialBridgeConfig; +import org.openhab.binding.wmbus.internal.WMBusReceiver; +import org.openhab.core.config.core.status.ConfigStatusMessage; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.io.transport.mbus.wireless.KeyStorage; +import org.openmuc.jmbus.wireless.WMBusConnection; +import org.openmuc.jmbus.wireless.WMBusConnection.WMBusManufacturer; +import org.openmuc.jmbus.wireless.WMBusConnection.WMBusSerialBuilder; +import org.openmuc.jmbus.wireless.WMBusMode; + +/** + * The {@link WMBusBridgeHandler} class defines This class represents the WMBus bridge and handles general events for + * the whole group of WMBus devices. + * + * @author Hanno - Felix Wagner - Initial contribution + */ + +public class WMBusBridgeHandler extends WMBusBridgeHandlerBase { + + private ScheduledFuture initFuture; + private WMBusConnection wmbusConnection; + + public WMBusBridgeHandler(Bridge bridge, KeyStorage keyStorage) { + super(bridge, keyStorage); + } + + @Override + public Collection getConfigStatus() { + List messages = new ArrayList<>(); + WMBusSerialBridgeConfig config = getConfigAs(WMBusSerialBridgeConfig.class); + + // check stick model + if (config.stickModel == null) { + messages.add(ConfigStatusMessage.Builder.error(CONFKEY_STICK_MODEL) + .withMessageKeySuffix(CONFKEY_STICK_MODEL).build()); + } + // check serial device name + if (config.serialDevice == null) { + messages.add(ConfigStatusMessage.Builder.error(CONFKEY_INTERFACE_NAME) + .withMessageKeySuffix(CONFKEY_INTERFACE_NAME).build()); + } + // check radio mode + if (config.radioMode == null) { + messages.add(ConfigStatusMessage.Builder.error(CONFKEY_RADIO_MODE).withMessageKeySuffix(CONFKEY_RADIO_MODE) + .build()); + } + + return messages; + } + + /** + * Connects to the WMBus radio module and updates bridge status. + * + * @see org.openhab.core.thing.binding.BaseThingHandler#initialize() + */ + @Override + public void initialize() { + logger.debug("WMBusBridgeHandler: initialize()"); + + WMBusSerialBridgeConfig config = getConfigAs(WMBusSerialBridgeConfig.class); + updateStatus(ThingStatus.UNKNOWN); + initFuture = scheduler.schedule(() -> { + // set up WMBus receiver = handler for radio telegrams + if (wmbusReceiver == null) { + StickModel stickModel = config.stickModel; + String interfaceName = config.serialDevice; + WMBusMode radioMode = config.radioMode; + + // connect to the radio module / open WMBus connection + logger.debug("Opening wmbus stick {} serial port {} in mode {}", stickModel, interfaceName, radioMode); + + WMBusManufacturer wmBusManufacturer = parseManufacturer(stickModel.name().toUpperCase()); + if (wmBusManufacturer == null) { + logger.error("Cannot open WMBus device. Unknown manufacturer given: " + stickModel + + ". Expected 'amber' or 'imst' or 'rc'."); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, + "Cannot open WMBus device. Unknown manufacturer given: " + stickModel + + ". Expected 'amber' or 'imst' or 'rc'."); + return; + } + logger.debug("Building new connection"); + + wmbusReceiver = new WMBusReceiver(this); + + if (config.deviceIDFilter == null || config.deviceIDFilter.trim().isEmpty()) { + logger.debug("Device ID filter is empty."); + } else { + wmbusReceiver.setFilterIDs(config.getDeviceIDFilter()); + } + + WMBusSerialBuilder connectionBuilder = new WMBusSerialBuilder(wmBusManufacturer, wmbusReceiver, + interfaceName); + + logger.debug("Setting WMBus radio mode to {}", radioMode.toString()); + connectionBuilder.setMode(radioMode); + connectionBuilder.setTimeout(1000); // infinite + // connectionBuilder.setTimeout(0); // infinite + + try { + logger.debug("Building/opening connection"); + logger.debug( + "NOTE: if initialization does not progress from here, check systemd journal for Execptions -- probably native lib still loaded by another ClassLoader = previous version or instance of WMBus binding -> restart OpenHAB"); + if (wmbusConnection != null) { + logger.debug("Connection already set, closing old"); + wmbusConnection.close(); + wmbusConnection = null; + } + wmbusConnection = connectionBuilder.build(); + } catch (IOException e) { + logger.error("Cannot open WMBus device. Connection builder returned: " + e.getMessage()); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, + "Cannot open WMBus device. Connection builder returned: " + e.getMessage()); + return; + } + + logger.debug("Connected to WMBus serial port"); + + // close WMBus connection on shutdown + logger.trace("Setting shutdown hook"); + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + if (wmbusConnection != null) { + try { + logger.debug("Closing connection to WMBus radio device"); + wmbusConnection.close(); + } catch (IOException e) { + logger.error("Cannot close connection to WMBus radio module: " + e.getMessage()); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, + "Cannot close connection to WMBus radio module: " + e.getMessage()); + return; + } + } + } + }); + + // success + logger.debug("WMBusBridgeHandler: Initialization done! Setting bridge online"); + updateStatus(ThingStatus.ONLINE); + } + }, 0, TimeUnit.SECONDS); + } + + private static WMBusManufacturer parseManufacturer(String manufacturer) { + switch (manufacturer.toLowerCase()) { + case MANUFACTURER_AMBER: + return WMBusManufacturer.AMBER; + case MANUFACTURER_RADIO_CRAFTS: + return WMBusManufacturer.RADIO_CRAFTS; + case MANUFACTURER_IMST: + return WMBusManufacturer.IMST; + default: + return null; + } + } + + @Override + public void dispose() { + super.dispose(); + logger.debug("WMBus bridge Handler disposed."); + + if (wmbusConnection != null) { + logger.debug("Close serial device connection"); + try { + wmbusConnection.close(); + } catch (IOException e) { + logger.error("An exception occurred while closing the wmbusConnection", e); + } + wmbusConnection = null; + } + + if (wmbusReceiver != null) { + wmbusReceiver = null; + } + + if (initFuture != null) { + initFuture.cancel(true); + initFuture = null; + } + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusBridgeHandlerBase.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusBridgeHandlerBase.java index 9e423f1..668b870 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusBridgeHandlerBase.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusBridgeHandlerBase.java @@ -1,280 +1,280 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.handler; - -import static org.openhab.binding.wmbus.WMBusBindingConstants.*; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; - -import org.eclipse.jdt.annotation.NonNull; -import org.openhab.binding.wmbus.WMBusBindingConstants; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.config.DateFieldMode; -import org.openhab.binding.wmbus.config.WMBusBridgeConfig; -import org.openhab.binding.wmbus.internal.WMBusReceiver; -import org.openhab.core.common.ThreadPoolManager; -import org.openhab.core.library.types.StringType; -import org.openhab.core.thing.Bridge; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingUID; -import org.openhab.core.thing.binding.ConfigStatusBridgeHandler; -import org.openhab.core.thing.binding.ThingHandler; -import org.openhab.core.types.Command; -import org.openhab.core.util.HexUtils; -import org.openhab.io.transport.mbus.wireless.KeyStorage; -import org.openmuc.jmbus.DecodingException; -import org.openmuc.jmbus.wireless.VirtualWMBusMessageHelper; -import org.openmuc.jmbus.wireless.WMBusMessage; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link WMBusBridgeHandlerBase} class defines base operations which are common for all bridge - * handlers such as device and key association. - * - * @author Łukasz Dywicki - Initial contribution, extracted from {@link WMBusBridgeHandler}. - */ -public abstract class WMBusBridgeHandlerBase extends ConfigStatusBridgeHandler implements WMBusAdapter { - - private static final ScheduledExecutorService SCHEDULER = ThreadPoolManager.getScheduledPool("wmbus"); - - private static final String DEVICE_STATE_ADDED = "added"; - private static final String DEVICE_STATE_CHANGED = "changed"; - - protected final Logger logger = LoggerFactory.getLogger(getClass()); - - private final KeyStorage keyStorage; - private final Map knownDevices = new ConcurrentHashMap<>(); - private final Set> handlers = Collections.synchronizedSet(new HashSet<>()); - private final List wmBusMessageListeners = new CopyOnWriteArrayList<>(); - protected WMBusReceiver wmbusReceiver; - private ScheduledFuture statusFuture; - private AtomicBoolean updateFrames = new AtomicBoolean(false); - - public WMBusBridgeHandlerBase(Bridge bridge, KeyStorage keyStorage) { - super(bridge); - this.keyStorage = keyStorage; - this.statusFuture = SCHEDULER.scheduleAtFixedRate(new StatusRunnable(handlers), 60, 60, TimeUnit.SECONDS); - } - - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - // judging from the hue bridge, this seems to be not needed...? - logger.warn("Unexpected command for bridge. Parameters are channelUID={} and command={}", channelUID, command); - } - - @Override - public void dispose() { - if (statusFuture != null) { - statusFuture.cancel(true); - statusFuture = null; - } - } - - public boolean registerWMBusMessageListener(WMBusMessageListener wmBusMessageListener) { - if (wmBusMessageListener == null) { - return false; - } - - logger.trace("register listener: Adding"); - boolean result = wmBusMessageListeners.add(wmBusMessageListener); - logger.trace("register listener: Success"); - if (result) { - // inform the listener initially about all devices and their states - for (WMBusDevice device : knownDevices.values()) { - wmBusMessageListener.onNewWMBusDevice(this, device); - } - } - return result; - } - - /** - * Iterate through wmBusMessageListeners and notify them about a newly received message. - * - * @param device - */ - private void notifyWMBusMessageListeners(final WMBusDevice device, final String type) { - logger.trace("bridge: notify message listeners: sending to all"); - WMBusDevice decrypt = decrypt(device); - - // below we append thing handlers which are configured for given device address - ArrayList listeners = new ArrayList<>(this.wmBusMessageListeners); - handlers.stream().filter(h -> device.getDeviceAddress().equals(h.getDeviceAddress())) - .collect(Collectors.toCollection(() -> listeners)); - - for (WMBusMessageListener wmBusMessageListener : listeners) { - try { - switch (type) { - case DEVICE_STATE_ADDED: { - wmBusMessageListener.onNewWMBusDevice(this, decrypt); - break; - } - case DEVICE_STATE_CHANGED: { - wmBusMessageListener.onChangedWMBusDevice(this, decrypt); - break; - } - default: { - throw new IllegalArgumentException( - "Could not notify wmBusMessageListeners for unknown event type " + type); - } - } - } catch (Exception e) { - logger.error("An exception occurred while notifying the WMBusMessageListener", e); - } - } - - logger.trace("bridge: notify message listeners: return"); - } - - /** - * Because we do not add encryption keys to connection and they are propagated from connection down to received - * frame and its parsing logic we need to inject encryption keys after message is received and before its first use - * to avoid troubles. - * Yes, we do it manually because jmbus does not offer any API/SPI for that. - * - * @param device Incoming frame. - * @return Decrypted frame or original (unencrypted) frame when parsing fails. - */ - protected WMBusDevice decrypt(WMBusDevice device) { - try { - device.decode(); - } catch (DecodingException parseException) { - if (parseException.getMessage().startsWith("Unable to decode encrypted payload")) { - try { - WMBusMessage message = VirtualWMBusMessageHelper.decode(device.getOriginalMessage().asBlob(), - device.getOriginalMessage().getRssi(), keyStorage.toMap()); - message.getVariableDataResponse().decode(); - logger.info("Message from {} successfully decrypted, forwarding it to receivers", - device.getDeviceAddress()); - return new WMBusDevice(message, this); - } catch (DecodingException decodingException) { - logger.info( - "Could not decode frame, probably we still miss encryption key, forwarding frame in original form. {}, {}, {}", - decodingException.getMessage(), device.getOriginalMessage().toString(), - keyStorage.toMap().toString()); - } catch (NoClassDefFoundError decodingException) { - logger.info( - "Could not decode frame, probably we still miss encryption key, forwarding frame in original form. {}", - decodingException.getMessage()); - } - } else if (parseException.getMessage().startsWith("Manufacturer specific CI:") - || parseException.getMessage().startsWith("Unable to decode message with this CI Field")) { - logger.debug("Found frame with manufacturer specific encoding, forwarding for futher processing."); - } else { - logger.debug("Unexpected error while parsing frame, forwarding frame in original form", parseException); - } - } - return device; - } - - @Override - public void processMessage(WMBusDevice device) { - if (updateFrames.get()) { - StringType frame = StringType.valueOf(HexUtils.bytesToHex(device.getOriginalMessage().asBlob())); - getCallback().stateUpdated(new ChannelUID(getUID(), WMBusBindingConstants.CHANNEL_LAST_FRAME), frame); - } - logger.trace("bridge: processMessage begin"); - - String deviceAddress = device.getDeviceAddress(); - String deviceState = DEVICE_STATE_ADDED; - if (knownDevices.containsKey(deviceAddress)) { - deviceState = DEVICE_STATE_CHANGED; - } - knownDevices.put(deviceAddress, device); - logger.trace("bridge processMessage: notifying listeners"); - notifyWMBusMessageListeners(device, deviceState); - logger.trace("bridge: processMessage end"); - } - - public WMBusDevice getDeviceByAddress(String deviceAddress) { - logger.trace("bridge: get device by address: " + deviceAddress); - if (knownDevices.containsKey(deviceAddress)) { - logger.trace("bridge: found device"); - } else { - logger.trace("bridge: device not found"); - } - return knownDevices.get(deviceAddress); - } - - @Override - public ThingUID getUID() { - return getThing().getUID(); - } - - @Override - public void channelLinked(ChannelUID channelUID) { - if (CHANNEL_LAST_FRAME.equals(channelUID.getId())) { - updateFrames.set(true); - } - } - - @Override - public void channelUnlinked(ChannelUID channelUID) { - if (CHANNEL_LAST_FRAME.equals(channelUID.getId())) { - updateFrames.set(false); - } - } - - @Override - public void childHandlerInitialized(@NonNull ThingHandler childHandler, @NonNull Thing childThing) { - if (childHandler instanceof WMBusDeviceHandler) { - handlers.add((WMBusDeviceHandler) childHandler); - } - } - - @Override - public void childHandlerDisposed(@NonNull ThingHandler childHandler, @NonNull Thing childThing) { - if (childHandler instanceof WMBusDeviceHandler) { - handlers.remove(childHandler); - } - } - - public void reset() { - wmbusReceiver = null; - initialize(); - } - - @Override - public DateFieldMode getDateFieldMode() { - return Optional.ofNullable(getConfigAs(WMBusBridgeConfig.class)).map(cfg -> cfg.dateFieldMode) - .orElse(DateFieldMode.DATE_TIME); - } - - static class StatusRunnable implements Runnable { - - private final Set> handlers; - - public StatusRunnable(Set> handlers) { - this.handlers = handlers; - } - - @Override - public void run() { - handlers.stream().forEach(WMBusDeviceHandler::checkStatus); - } - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.handler; + +import static org.openhab.binding.wmbus.WMBusBindingConstants.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNull; +import org.openhab.binding.wmbus.WMBusBindingConstants; +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.config.DateFieldMode; +import org.openhab.binding.wmbus.config.WMBusBridgeConfig; +import org.openhab.binding.wmbus.internal.WMBusReceiver; +import org.openhab.core.common.ThreadPoolManager; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.ConfigStatusBridgeHandler; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.types.Command; +import org.openhab.core.util.HexUtils; +import org.openhab.io.transport.mbus.wireless.KeyStorage; +import org.openmuc.jmbus.DecodingException; +import org.openmuc.jmbus.wireless.VirtualWMBusMessageHelper; +import org.openmuc.jmbus.wireless.WMBusMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link WMBusBridgeHandlerBase} class defines base operations which are common for all bridge + * handlers such as device and key association. + * + * @author Łukasz Dywicki - Initial contribution, extracted from {@link WMBusBridgeHandler}. + */ +public abstract class WMBusBridgeHandlerBase extends ConfigStatusBridgeHandler implements WMBusAdapter { + + private static final ScheduledExecutorService SCHEDULER = ThreadPoolManager.getScheduledPool("wmbus"); + + private static final String DEVICE_STATE_ADDED = "added"; + private static final String DEVICE_STATE_CHANGED = "changed"; + + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + private final KeyStorage keyStorage; + private final Map knownDevices = new ConcurrentHashMap<>(); + private final Set> handlers = Collections.synchronizedSet(new HashSet<>()); + private final List wmBusMessageListeners = new CopyOnWriteArrayList<>(); + protected WMBusReceiver wmbusReceiver; + private ScheduledFuture statusFuture; + private AtomicBoolean updateFrames = new AtomicBoolean(false); + + public WMBusBridgeHandlerBase(Bridge bridge, KeyStorage keyStorage) { + super(bridge); + this.keyStorage = keyStorage; + this.statusFuture = SCHEDULER.scheduleAtFixedRate(new StatusRunnable(handlers), 60, 60, TimeUnit.SECONDS); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + // judging from the hue bridge, this seems to be not needed...? + logger.warn("Unexpected command for bridge. Parameters are channelUID={} and command={}", channelUID, command); + } + + @Override + public void dispose() { + if (statusFuture != null) { + statusFuture.cancel(true); + statusFuture = null; + } + } + + public boolean registerWMBusMessageListener(WMBusMessageListener wmBusMessageListener) { + if (wmBusMessageListener == null) { + return false; + } + + logger.trace("register listener: Adding"); + boolean result = wmBusMessageListeners.add(wmBusMessageListener); + logger.trace("register listener: Success"); + if (result) { + // inform the listener initially about all devices and their states + for (WMBusDevice device : knownDevices.values()) { + wmBusMessageListener.onNewWMBusDevice(this, device); + } + } + return result; + } + + /** + * Iterate through wmBusMessageListeners and notify them about a newly received message. + * + * @param device + */ + private void notifyWMBusMessageListeners(final WMBusDevice device, final String type) { + logger.trace("bridge: notify message listeners: sending to all"); + WMBusDevice decrypt = decrypt(device); + + // below we append thing handlers which are configured for given device address + ArrayList listeners = new ArrayList<>(this.wmBusMessageListeners); + handlers.stream().filter(h -> device.getDeviceAddress().equals(h.getDeviceAddress())) + .collect(Collectors.toCollection(() -> listeners)); + + for (WMBusMessageListener wmBusMessageListener : listeners) { + try { + switch (type) { + case DEVICE_STATE_ADDED: { + wmBusMessageListener.onNewWMBusDevice(this, decrypt); + break; + } + case DEVICE_STATE_CHANGED: { + wmBusMessageListener.onChangedWMBusDevice(this, decrypt); + break; + } + default: { + throw new IllegalArgumentException( + "Could not notify wmBusMessageListeners for unknown event type " + type); + } + } + } catch (Exception e) { + logger.error("An exception occurred while notifying the WMBusMessageListener", e); + } + } + + logger.trace("bridge: notify message listeners: return"); + } + + /** + * Because we do not add encryption keys to connection and they are propagated from connection down to received + * frame and its parsing logic we need to inject encryption keys after message is received and before its first use + * to avoid troubles. + * Yes, we do it manually because jmbus does not offer any API/SPI for that. + * + * @param device Incoming frame. + * @return Decrypted frame or original (unencrypted) frame when parsing fails. + */ + protected WMBusDevice decrypt(WMBusDevice device) { + try { + device.decode(); + } catch (DecodingException parseException) { + if (parseException.getMessage().startsWith("Unable to decode encrypted payload")) { + try { + WMBusMessage message = VirtualWMBusMessageHelper.decode(device.getOriginalMessage().asBlob(), + device.getOriginalMessage().getRssi(), keyStorage.toMap()); + message.getVariableDataResponse().decode(); + logger.info("Message from {} successfully decrypted, forwarding it to receivers", + device.getDeviceAddress()); + return new WMBusDevice(message, this); + } catch (DecodingException decodingException) { + logger.info( + "Could not decode frame, probably we still miss encryption key, forwarding frame in original form. {}, {}, {}", + decodingException.getMessage(), device.getOriginalMessage().toString(), + keyStorage.toMap().toString()); + } catch (NoClassDefFoundError decodingException) { + logger.info( + "Could not decode frame, probably we still miss encryption key, forwarding frame in original form. {}", + decodingException.getMessage()); + } + } else if (parseException.getMessage().startsWith("Manufacturer specific CI:") + || parseException.getMessage().startsWith("Unable to decode message with this CI Field")) { + logger.debug("Found frame with manufacturer specific encoding, forwarding for futher processing."); + } else { + logger.debug("Unexpected error while parsing frame, forwarding frame in original form", parseException); + } + } + return device; + } + + @Override + public void processMessage(WMBusDevice device) { + if (updateFrames.get()) { + StringType frame = StringType.valueOf(HexUtils.bytesToHex(device.getOriginalMessage().asBlob())); + getCallback().stateUpdated(new ChannelUID(getUID(), WMBusBindingConstants.CHANNEL_LAST_FRAME), frame); + } + logger.trace("bridge: processMessage begin"); + + String deviceAddress = device.getDeviceAddress(); + String deviceState = DEVICE_STATE_ADDED; + if (knownDevices.containsKey(deviceAddress)) { + deviceState = DEVICE_STATE_CHANGED; + } + knownDevices.put(deviceAddress, device); + logger.trace("bridge processMessage: notifying listeners"); + notifyWMBusMessageListeners(device, deviceState); + logger.trace("bridge: processMessage end"); + } + + public WMBusDevice getDeviceByAddress(String deviceAddress) { + logger.trace("bridge: get device by address: " + deviceAddress); + if (knownDevices.containsKey(deviceAddress)) { + logger.trace("bridge: found device"); + } else { + logger.trace("bridge: device not found"); + } + return knownDevices.get(deviceAddress); + } + + @Override + public ThingUID getUID() { + return getThing().getUID(); + } + + @Override + public void channelLinked(ChannelUID channelUID) { + if (CHANNEL_LAST_FRAME.equals(channelUID.getId())) { + updateFrames.set(true); + } + } + + @Override + public void channelUnlinked(ChannelUID channelUID) { + if (CHANNEL_LAST_FRAME.equals(channelUID.getId())) { + updateFrames.set(false); + } + } + + @Override + public void childHandlerInitialized(@NonNull ThingHandler childHandler, @NonNull Thing childThing) { + if (childHandler instanceof WMBusDeviceHandler) { + handlers.add((WMBusDeviceHandler) childHandler); + } + } + + @Override + public void childHandlerDisposed(@NonNull ThingHandler childHandler, @NonNull Thing childThing) { + if (childHandler instanceof WMBusDeviceHandler) { + handlers.remove(childHandler); + } + } + + public void reset() { + wmbusReceiver = null; + initialize(); + } + + @Override + public DateFieldMode getDateFieldMode() { + return Optional.ofNullable(getConfigAs(WMBusBridgeConfig.class)).map(cfg -> cfg.dateFieldMode) + .orElse(DateFieldMode.DATE_TIME); + } + + static class StatusRunnable implements Runnable { + + private final Set> handlers; + + public StatusRunnable(Set> handlers) { + this.handlers = handlers; + } + + @Override + public void run() { + handlers.stream().forEach(WMBusDeviceHandler::checkStatus); + } + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusDeviceHandler.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusDeviceHandler.java index 35d7ec1..0711f23 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusDeviceHandler.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusDeviceHandler.java @@ -1,314 +1,314 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.handler; - -import static org.openhab.binding.wmbus.WMBusBindingConstants.*; - -import java.math.BigDecimal; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.time.temporal.ChronoUnit; -import java.util.Date; -import java.util.Optional; -import java.util.concurrent.TimeUnit; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.wmbus.WMBusBindingConstants; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.config.DateFieldMode; -import org.openhab.binding.wmbus.internal.WMBusException; -import org.openhab.core.config.core.Configuration; -import org.openhab.core.library.types.DateTimeType; -import org.openhab.core.library.types.DecimalType; -import org.openhab.core.library.types.StringType; -import org.openhab.core.thing.Bridge; -import org.openhab.core.thing.Channel; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingStatus; -import org.openhab.core.thing.ThingStatusDetail; -import org.openhab.core.thing.binding.BaseThingHandler; -import org.openhab.core.thing.binding.ThingHandler; -import org.openhab.core.types.RefreshType; -import org.openhab.core.types.State; -import org.openhab.core.util.HexUtils; -import org.openhab.io.transport.mbus.wireless.KeyStorage; -import org.openhab.io.transport.mbus.wireless.MapKeyStorage; -import org.openmuc.jmbus.DataRecord; -import org.openmuc.jmbus.DecodingException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link WMBusDeviceHandler} class defines abstract WMBusDeviceHandler - * - * @author Hanno - Felix Wagner - Initial contribution - * @author Łukasz Dywicki - Added possibility to customize message parsing. - */ - -public abstract class WMBusDeviceHandler extends BaseThingHandler - implements WMBusMessageListener { - - private final Logger logger = LoggerFactory.getLogger(WMBusDeviceHandler.class); - private final KeyStorage keyStorage; - - protected String deviceAddress; - private WMBusBridgeHandlerBase bridgeHandler; - protected T wmbusDevice; - protected Long lastUpdate; - private Long frequencyOfUpdates = WMBusBindingConstants.DEFAULT_DEVICE_FREQUENCY_OF_UPDATES; - private ThingStatus status; - - protected WMBusDeviceHandler(Thing thing) { - this(thing, new MapKeyStorage()); - } - - public WMBusDeviceHandler(Thing thing, KeyStorage keyStorage) { - super(thing); - this.keyStorage = keyStorage; - logger.debug("Created new handler for thing {}", thing.getUID()); - } - - // entry point - gets device here - @Override - public void onNewWMBusDevice(WMBusAdapter adapter, WMBusDevice wmBusDevice) { - logger.trace("onNewWMBusDevice(): is it me?"); - if (wmBusDevice.getDeviceAddress().equals(deviceAddress)) { - logger.trace("onNewWMBusDevice(): yes it's me"); - logger.trace("onNewWMBusDevice(): calling onChangedWMBusDevice()"); - onChangedWMBusDevice(adapter, wmBusDevice); - } - logger.trace("onNewWMBusDevice(): no"); - } - - @Override - public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice receivedDevice) { - logger.trace("onChangedWMBusDevice(): is it me?"); - if (receivedDevice.getDeviceAddress().equals(deviceAddress)) { - logger.trace("onChangedWMBusDevice(): yes"); - // in between the good messages, there are messages with invalid values -> filter these out - if (!checkMessage(receivedDevice)) { - logger.trace("onChangedWMBusDevice(): this is a malformed message, ignoring this message"); - } else { - try { - wmbusDevice = parseDevice(receivedDevice); - logger.trace("onChangedWMBusDevice(): updating status to online"); - updateStatus(ThingStatus.ONLINE); - - if (wmbusDevice != null) { - logger.trace("onChangedWMBusDevice(): inform all channels to refresh"); - triggerRefresh(); - } - } catch (DecodingException e) { - // FIXME decoding exception should not be necessary over time - logger.debug("onChangedWMBusDevice(): could not decode frame {} because {}. Detail: {}", - HexUtils.bytesToHex(receivedDevice.getOriginalMessage().asBlob()), e.getMessage(), - receivedDevice.getOriginalMessage()); - } - } - } else { - logger.trace("onChangedWMBusDevice(): no"); - } - logger.trace("onChangedWMBusDevice(): return"); - } - - @Override - protected void updateStatus(@NonNull ThingStatus status, @NonNull ThingStatusDetail statusDetail, - @Nullable String description) { - super.updateStatus(status, statusDetail, description); - this.status = status; - } - - protected void triggerRefresh() { - lastUpdate = System.currentTimeMillis(); - - for (Channel curChan : getThing().getChannels()) { - handleCommand(curChan.getUID(), RefreshType.REFRESH); - } - } - - protected State convertRecordData(DataRecord record) { - - switch (record.getDataValueType()) { - case LONG: - case DOUBLE: - case BCD: - return new DecimalType(record.getScaledDataValue()); - case DATE: - return convertDate(record.getDataValue()); - case STRING: - case NONE: - return new StringType(record.getDataValue().toString()); - } - return null; - } - - protected DateFieldMode getDateFieldMode() { - return getBridgeHandler().getDateFieldMode(); - } - - protected State convertDate(Object input) { - DateTimeType value = null; - if (input instanceof Date) { - Date date = (Date) input; - ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); - zonedDateTime.truncatedTo(ChronoUnit.SECONDS); // throw away millisecond value to avoid, eg. _previous_date - // changed from 2018-02-28T00:00:00.353+0100 to - // 2018-02-28T00:00:00.159+0100 - value = new DateTimeType(zonedDateTime); - } - if (input instanceof DateTimeType) { - value = (DateTimeType) input; - } - - if (value == null) { - return null; - } - - switch (getDateFieldMode()) { - case FORMATTED_STRING: - return new StringType(value.format(null)); - case UNIX_TIMESTAMP: - return new DecimalType(value.getZonedDateTime().toEpochSecond()); - default: - return value; - } - } - - @Override - public void initialize() { - logger.debug("Initializing handler."); - updateStatus(ThingStatus.UNKNOWN); - - Configuration config = getConfig(); - deviceAddress = (String) config.getProperties().get(PROPERTY_DEVICE_ADDRESS); - - if (deviceAddress == null || deviceAddress.trim().isEmpty()) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, - "Missing device address, please update."); - return; - } - - try { - wmbusDevice = getDevice(); - if (wmbusDevice != null) { - initialize(wmbusDevice); - } - } catch (WMBusException e) { - logger.error("Could not obtain Wireless M-Bus device information", e); - } - - Long updateFrequency = Optional.of(config.getProperties()) - .map(cfg -> cfg.get(PROPERTY_DEVICE_FREQUENCY_OF_UPDATES)) // - .filter(BigDecimal.class::isInstance) // - .map(BigDecimal.class::cast) // - .map(BigDecimal::longValue) // - .orElse(DEFAULT_DEVICE_FREQUENCY_OF_UPDATES); - this.frequencyOfUpdates = TimeUnit.MINUTES.toMillis(updateFrequency); - - if (Boolean.valueOf(thing.getProperties().get(PROPERTY_DEVICE_ENCRYPTED))) { - Optional encryptionKey = Optional.of(config.getProperties()) // - .map(cfg -> cfg.get(PROPERTY_DEVICE_ENCRYPTION_KEY)) // - .filter(String.class::isInstance) // - .map(String.class::cast) // - .map(HexUtils::hexToBytes); - if (!encryptionKey.isPresent()) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, - "Please provide encryption key to read device communication."); - } - encryptionKey.ifPresent(key -> keyStorage.registerKey(HexUtils.hexToBytes(deviceAddress), key)); - } - } - - protected void initialize(T device) { - updateStatus(ThingStatus.ONLINE); - } - - @Override - public void dispose() { - logger.debug("Disposing handler."); - this.deviceAddress = null; - this.wmbusDevice = null; - } - - protected synchronized WMBusBridgeHandlerBase getBridgeHandler() { - logger.trace("getBridgeHandler() begin"); - if (bridgeHandler == null) { - Bridge bridge = getBridge(); - if (bridge == null) { - return null; - } - ThingHandler handler = bridge.getHandler(); - if (handler instanceof WMBusBridgeHandlerBase) { - bridgeHandler = (WMBusBridgeHandlerBase) handler; - } else { - return null; - } - } - logger.trace("getBridgeHandler() returning bridgehandler"); - return bridgeHandler; - } - - protected T getDevice() throws WMBusException { - logger.trace("getDevice() begin"); - WMBusBridgeHandlerBase bridgeHandler = getBridgeHandler(); - if (bridgeHandler == null) { - logger.debug("Device handler is not linked with bridge, skipping call"); - return null; - } - - logger.trace("Lookup known devices by address {}", deviceAddress); - WMBusDevice device = bridgeHandler.getDeviceByAddress(deviceAddress); - if (device != null) { - logger.trace("Found device matching given addresss {}, {}", deviceAddress, device); - try { - return parseDevice(device); - } catch (DecodingException e) { - logger.trace("Unable to parse received message {}", device, e); - return null; - } - } - - logger.trace("Couldn't find device matching address {}", deviceAddress); - return null; - } - - boolean checkMessage(WMBusDevice receivedDevice) { - return true; - } - - public void checkStatus() { - // status check is relevant only if device is considered to be online - we determine if it should be marked - // offline - if (this.status != ThingStatus.ONLINE) { - return; - } - - long currentTime = System.currentTimeMillis(); - if (lastUpdate == null || lastUpdate + frequencyOfUpdates <= currentTime) { - logger.info("WMBus device was not seen since {}, marking it as offline", new Date(lastUpdate)); - updateStatus(ThingStatus.OFFLINE); - } - } - - @SuppressWarnings("unchecked") - protected T parseDevice(WMBusDevice device) throws DecodingException { - device.decode(); - return (T) device; - } - - public String getDeviceAddress() { - return deviceAddress; - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.handler; + +import static org.openhab.binding.wmbus.WMBusBindingConstants.*; + +import java.math.BigDecimal; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Date; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.wmbus.WMBusBindingConstants; +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.config.DateFieldMode; +import org.openhab.binding.wmbus.internal.WMBusException; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.library.types.DateTimeType; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; +import org.openhab.core.util.HexUtils; +import org.openhab.io.transport.mbus.wireless.KeyStorage; +import org.openhab.io.transport.mbus.wireless.MapKeyStorage; +import org.openmuc.jmbus.DataRecord; +import org.openmuc.jmbus.DecodingException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link WMBusDeviceHandler} class defines abstract WMBusDeviceHandler + * + * @author Hanno - Felix Wagner - Initial contribution + * @author Łukasz Dywicki - Added possibility to customize message parsing. + */ + +public abstract class WMBusDeviceHandler extends BaseThingHandler + implements WMBusMessageListener { + + private final Logger logger = LoggerFactory.getLogger(WMBusDeviceHandler.class); + private final KeyStorage keyStorage; + + protected String deviceAddress; + private WMBusBridgeHandlerBase bridgeHandler; + protected T wmbusDevice; + protected Long lastUpdate; + private Long frequencyOfUpdates = WMBusBindingConstants.DEFAULT_DEVICE_FREQUENCY_OF_UPDATES; + private ThingStatus status; + + protected WMBusDeviceHandler(Thing thing) { + this(thing, new MapKeyStorage()); + } + + public WMBusDeviceHandler(Thing thing, KeyStorage keyStorage) { + super(thing); + this.keyStorage = keyStorage; + logger.debug("Created new handler for thing {}", thing.getUID()); + } + + // entry point - gets device here + @Override + public void onNewWMBusDevice(WMBusAdapter adapter, WMBusDevice wmBusDevice) { + logger.trace("onNewWMBusDevice(): is it me?"); + if (wmBusDevice.getDeviceAddress().equals(deviceAddress)) { + logger.trace("onNewWMBusDevice(): yes it's me"); + logger.trace("onNewWMBusDevice(): calling onChangedWMBusDevice()"); + onChangedWMBusDevice(adapter, wmBusDevice); + } + logger.trace("onNewWMBusDevice(): no"); + } + + @Override + public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice receivedDevice) { + logger.trace("onChangedWMBusDevice(): is it me?"); + if (receivedDevice.getDeviceAddress().equals(deviceAddress)) { + logger.trace("onChangedWMBusDevice(): yes"); + // in between the good messages, there are messages with invalid values -> filter these out + if (!checkMessage(receivedDevice)) { + logger.trace("onChangedWMBusDevice(): this is a malformed message, ignoring this message"); + } else { + try { + wmbusDevice = parseDevice(receivedDevice); + logger.trace("onChangedWMBusDevice(): updating status to online"); + updateStatus(ThingStatus.ONLINE); + + if (wmbusDevice != null) { + logger.trace("onChangedWMBusDevice(): inform all channels to refresh"); + triggerRefresh(); + } + } catch (DecodingException e) { + // FIXME decoding exception should not be necessary over time + logger.debug("onChangedWMBusDevice(): could not decode frame {} because {}. Detail: {}", + HexUtils.bytesToHex(receivedDevice.getOriginalMessage().asBlob()), e.getMessage(), + receivedDevice.getOriginalMessage()); + } + } + } else { + logger.trace("onChangedWMBusDevice(): no"); + } + logger.trace("onChangedWMBusDevice(): return"); + } + + @Override + protected void updateStatus(@NonNull ThingStatus status, @NonNull ThingStatusDetail statusDetail, + @Nullable String description) { + super.updateStatus(status, statusDetail, description); + this.status = status; + } + + protected void triggerRefresh() { + lastUpdate = System.currentTimeMillis(); + + for (Channel curChan : getThing().getChannels()) { + handleCommand(curChan.getUID(), RefreshType.REFRESH); + } + } + + protected State convertRecordData(DataRecord record) { + + switch (record.getDataValueType()) { + case LONG: + case DOUBLE: + case BCD: + return new DecimalType(record.getScaledDataValue()); + case DATE: + return convertDate(record.getDataValue()); + case STRING: + case NONE: + return new StringType(record.getDataValue().toString()); + } + return null; + } + + protected DateFieldMode getDateFieldMode() { + return getBridgeHandler().getDateFieldMode(); + } + + protected State convertDate(Object input) { + DateTimeType value = null; + if (input instanceof Date) { + Date date = (Date) input; + ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); + zonedDateTime.truncatedTo(ChronoUnit.SECONDS); // throw away millisecond value to avoid, eg. _previous_date + // changed from 2018-02-28T00:00:00.353+0100 to + // 2018-02-28T00:00:00.159+0100 + value = new DateTimeType(zonedDateTime); + } + if (input instanceof DateTimeType) { + value = (DateTimeType) input; + } + + if (value == null) { + return null; + } + + switch (getDateFieldMode()) { + case FORMATTED_STRING: + return new StringType(value.format(null)); + case UNIX_TIMESTAMP: + return new DecimalType(value.getZonedDateTime().toEpochSecond()); + default: + return value; + } + } + + @Override + public void initialize() { + logger.debug("Initializing handler."); + updateStatus(ThingStatus.UNKNOWN); + + Configuration config = getConfig(); + deviceAddress = (String) config.getProperties().get(PROPERTY_DEVICE_ADDRESS); + + if (deviceAddress == null || deviceAddress.trim().isEmpty()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, + "Missing device address, please update."); + return; + } + + try { + wmbusDevice = getDevice(); + if (wmbusDevice != null) { + initialize(wmbusDevice); + } + } catch (WMBusException e) { + logger.error("Could not obtain Wireless M-Bus device information", e); + } + + Long updateFrequency = Optional.of(config.getProperties()) + .map(cfg -> cfg.get(PROPERTY_DEVICE_FREQUENCY_OF_UPDATES)) // + .filter(BigDecimal.class::isInstance) // + .map(BigDecimal.class::cast) // + .map(BigDecimal::longValue) // + .orElse(DEFAULT_DEVICE_FREQUENCY_OF_UPDATES); + this.frequencyOfUpdates = TimeUnit.MINUTES.toMillis(updateFrequency); + + if (Boolean.valueOf(thing.getProperties().get(PROPERTY_DEVICE_ENCRYPTED))) { + Optional encryptionKey = Optional.of(config.getProperties()) // + .map(cfg -> cfg.get(PROPERTY_DEVICE_ENCRYPTION_KEY)) // + .filter(String.class::isInstance) // + .map(String.class::cast) // + .map(HexUtils::hexToBytes); + if (!encryptionKey.isPresent()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, + "Please provide encryption key to read device communication."); + } + encryptionKey.ifPresent(key -> keyStorage.registerKey(HexUtils.hexToBytes(deviceAddress), key)); + } + } + + protected void initialize(T device) { + updateStatus(ThingStatus.ONLINE); + } + + @Override + public void dispose() { + logger.debug("Disposing handler."); + this.deviceAddress = null; + this.wmbusDevice = null; + } + + protected synchronized WMBusBridgeHandlerBase getBridgeHandler() { + logger.trace("getBridgeHandler() begin"); + if (bridgeHandler == null) { + Bridge bridge = getBridge(); + if (bridge == null) { + return null; + } + ThingHandler handler = bridge.getHandler(); + if (handler instanceof WMBusBridgeHandlerBase) { + bridgeHandler = (WMBusBridgeHandlerBase) handler; + } else { + return null; + } + } + logger.trace("getBridgeHandler() returning bridgehandler"); + return bridgeHandler; + } + + protected T getDevice() throws WMBusException { + logger.trace("getDevice() begin"); + WMBusBridgeHandlerBase bridgeHandler = getBridgeHandler(); + if (bridgeHandler == null) { + logger.debug("Device handler is not linked with bridge, skipping call"); + return null; + } + + logger.trace("Lookup known devices by address {}", deviceAddress); + WMBusDevice device = bridgeHandler.getDeviceByAddress(deviceAddress); + if (device != null) { + logger.trace("Found device matching given addresss {}, {}", deviceAddress, device); + try { + return parseDevice(device); + } catch (DecodingException e) { + logger.trace("Unable to parse received message {}", device, e); + return null; + } + } + + logger.trace("Couldn't find device matching address {}", deviceAddress); + return null; + } + + boolean checkMessage(WMBusDevice receivedDevice) { + return true; + } + + public void checkStatus() { + // status check is relevant only if device is considered to be online - we determine if it should be marked + // offline + if (this.status != ThingStatus.ONLINE) { + return; + } + + long currentTime = System.currentTimeMillis(); + if (lastUpdate == null || lastUpdate + frequencyOfUpdates <= currentTime) { + logger.info("WMBus device was not seen since {}, marking it as offline", new Date(lastUpdate)); + updateStatus(ThingStatus.OFFLINE); + } + } + + @SuppressWarnings("unchecked") + protected T parseDevice(WMBusDevice device) throws DecodingException { + device.decode(); + return (T) device; + } + + public String getDeviceAddress() { + return deviceAddress; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusMessageListener.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusMessageListener.java index 6c73791..7dea0f9 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusMessageListener.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/handler/WMBusMessageListener.java @@ -1,39 +1,39 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.handler; - -import org.openhab.binding.wmbus.WMBusDevice; - -/** - * The {@link WMBusMessageListener} class defines interface WMBusMessageListener - * - * @author Hanno - Felix Wagner - Initial contribution - */ - -public interface WMBusMessageListener { - - /** - * - * @param adapter Adapter which received message. - * @param device The message which was received. - */ - public void onNewWMBusDevice(WMBusAdapter adapter, WMBusDevice device); - - /** - * - * @param adapter WMBusAdapter adapter who discovered change, - * @param device The message which was received. - */ - public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice device); -} +/** + * Copyright (c) 2010-2021 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.wmbus.handler; + +import org.openhab.binding.wmbus.WMBusDevice; + +/** + * The {@link WMBusMessageListener} class defines interface WMBusMessageListener + * + * @author Hanno - Felix Wagner - Initial contribution + */ + +public interface WMBusMessageListener { + + /** + * + * @param adapter Adapter which received message. + * @param device The message which was received. + */ + public void onNewWMBusDevice(WMBusAdapter adapter, WMBusDevice device); + + /** + * + * @param adapter WMBusAdapter adapter who discovered change, + * @param device The message which was received. + */ + public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice device); +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/DynamicBindingConfiguration.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/DynamicBindingConfiguration.java index 052cd89..cbbf3d7 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/DynamicBindingConfiguration.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/DynamicBindingConfiguration.java @@ -1,94 +1,94 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.internal; - -import java.util.Map; - -import org.openhab.binding.wmbus.BindingConfiguration; -import org.openhab.binding.wmbus.WMBusBindingConstants; -import org.osgi.service.component.ComponentContext; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Modified; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Implementation of configuration which rely on OSGi configuration admin and keep updating of time to live once its - * set. - * - * @author Łukasz Dywicki - Initial contribution. - */ -@Component(service = BindingConfiguration.class, configurationPid = "binding.wmbus") -public class DynamicBindingConfiguration implements BindingConfiguration { - - private final Logger logger = LoggerFactory.getLogger(DynamicBindingConfiguration.class); - private Long timeToLive = WMBusBindingConstants.DEFAULT_TIME_TO_LIVE; - private Boolean includeBridgeUID = true; - - @Activate - public void activate(ComponentContext context) { - setTimeToLive(context.getProperties().get(WMBusBindingConstants.CONFKEY_BINDING_TIME_TO_LIVE)); - setIncludeBridgeUID(context.getProperties().get(WMBusBindingConstants.CONFKEY_BINDING_INCLUDE_BRIDGE_UID)); - } - - private void setTimeToLive(Object value) { - if (value == null) { - logger.debug("Setting up time to live to default value"); - this.timeToLive = WMBusBindingConstants.DEFAULT_TIME_TO_LIVE; - return; - } - - logger.debug("Setting up time to live to new value {}", value); - if (value instanceof Long) { - this.timeToLive = (Long) value; - } - - if (value instanceof String) { - this.timeToLive = Long.parseLong((String) value); - } - } - - @Override - public Long getTimeToLive() { - return timeToLive; - } - - private void setIncludeBridgeUID(Object value) { - if (value == null) { - logger.debug("Setting up includeBridgeUID to default value"); - this.includeBridgeUID = true; - return; - } - - logger.debug("Setting up includeBridgeUID to new value {}", value); - if (value instanceof Boolean) { - this.includeBridgeUID = (Boolean) value; - } - - if (value instanceof String) { - this.includeBridgeUID = Boolean.parseBoolean((String) value); - } - } - - @Override - public Boolean getIncludeBridgeUID() { - return includeBridgeUID; - } - - @Modified - void updated(Map configuration) { - setTimeToLive(configuration.get(WMBusBindingConstants.CONFKEY_BINDING_TIME_TO_LIVE)); - setIncludeBridgeUID(configuration.get(WMBusBindingConstants.CONFKEY_BINDING_INCLUDE_BRIDGE_UID)); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.internal; + +import java.util.Map; + +import org.openhab.binding.wmbus.BindingConfiguration; +import org.openhab.binding.wmbus.WMBusBindingConstants; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Modified; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implementation of configuration which rely on OSGi configuration admin and keep updating of time to live once its + * set. + * + * @author Łukasz Dywicki - Initial contribution. + */ +@Component(service = BindingConfiguration.class, configurationPid = "binding.wmbus") +public class DynamicBindingConfiguration implements BindingConfiguration { + + private final Logger logger = LoggerFactory.getLogger(DynamicBindingConfiguration.class); + private Long timeToLive = WMBusBindingConstants.DEFAULT_TIME_TO_LIVE; + private Boolean includeBridgeUID = true; + + @Activate + public void activate(ComponentContext context) { + setTimeToLive(context.getProperties().get(WMBusBindingConstants.CONFKEY_BINDING_TIME_TO_LIVE)); + setIncludeBridgeUID(context.getProperties().get(WMBusBindingConstants.CONFKEY_BINDING_INCLUDE_BRIDGE_UID)); + } + + private void setTimeToLive(Object value) { + if (value == null) { + logger.debug("Setting up time to live to default value"); + this.timeToLive = WMBusBindingConstants.DEFAULT_TIME_TO_LIVE; + return; + } + + logger.debug("Setting up time to live to new value {}", value); + if (value instanceof Long) { + this.timeToLive = (Long) value; + } + + if (value instanceof String) { + this.timeToLive = Long.parseLong((String) value); + } + } + + @Override + public Long getTimeToLive() { + return timeToLive; + } + + private void setIncludeBridgeUID(Object value) { + if (value == null) { + logger.debug("Setting up includeBridgeUID to default value"); + this.includeBridgeUID = true; + return; + } + + logger.debug("Setting up includeBridgeUID to new value {}", value); + if (value instanceof Boolean) { + this.includeBridgeUID = (Boolean) value; + } + + if (value instanceof String) { + this.includeBridgeUID = Boolean.parseBoolean((String) value); + } + } + + @Override + public Boolean getIncludeBridgeUID() { + return includeBridgeUID; + } + + @Modified + void updated(Map configuration) { + setTimeToLive(configuration.get(WMBusBindingConstants.CONFKEY_BINDING_TIME_TO_LIVE)); + setIncludeBridgeUID(configuration.get(WMBusBindingConstants.CONFKEY_BINDING_INCLUDE_BRIDGE_UID)); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/HexConverter.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/HexConverter.java index c595c25..49e8ebc 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/HexConverter.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/HexConverter.java @@ -1,34 +1,34 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.internal; - -/** - * The {@link HexConverter} class defines HexConverter - * - * @author Hanno - Felix Wagner - Initial contribution - */ - -class HexConverter { - private final static char[] hexArray = "0123456789ABCDEF".toCharArray(); - - static String bytesToHex(byte[] bytes) { - char[] hexChars = new char[bytes.length * 2]; - for (int j = 0; j < bytes.length; j++) { - int v = bytes[j] & 0xFF; - hexChars[j * 2] = hexArray[v >>> 4]; - hexChars[j * 2 + 1] = hexArray[v & 0x0F]; - } - return new String(hexChars); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.internal; + +/** + * The {@link HexConverter} class defines HexConverter + * + * @author Hanno - Felix Wagner - Initial contribution + */ + +class HexConverter { + private final static char[] hexArray = "0123456789ABCDEF".toCharArray(); + + static String bytesToHex(byte[] bytes) { + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusChannelTypeProvider.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusChannelTypeProvider.java index 19b51ce..ade8071 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusChannelTypeProvider.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusChannelTypeProvider.java @@ -1,252 +1,252 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.internal; - -import java.util.Collection; -import java.util.Collections; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import javax.measure.Unit; - -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.wmbus.UnitRegistry; -import org.openhab.binding.wmbus.WMBusBindingConstants; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.config.DateFieldMode; -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openhab.binding.wmbus.handler.WMBusMessageListener; -import org.openhab.core.library.CoreItemFactory; -import org.openhab.core.thing.type.ChannelKind; -import org.openhab.core.thing.type.ChannelType; -import org.openhab.core.thing.type.ChannelTypeBuilder; -import org.openhab.core.thing.type.ChannelTypeProvider; -import org.openhab.core.thing.type.ChannelTypeUID; -import org.openhab.core.types.EventDescription; -import org.openhab.core.types.StateDescription; -import org.openhab.core.types.StateDescriptionFragmentBuilder; -import org.openhab.core.util.HexUtils; -import org.openmuc.jmbus.DataRecord; -import org.openmuc.jmbus.DataRecord.DataValueType; -import org.openmuc.jmbus.DataRecord.Description; -import org.openmuc.jmbus.DataRecord.FunctionField; -import org.openmuc.jmbus.DlmsUnit; -import org.openmuc.jmbus.VariableDataStructure; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Reference; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Dynamic channel type provider which uses received wmbus frames to create channel types. - * - * Because wmbus frames contain variable data part which have many mutations it is not possible to create a static - * configuration which would cover all combinations. Fields which are multipliers are: - * - dib (inst/min/max/error val) - * - vib (subunit, tariff, storage number, vif) - * - * While most of devices uses just small subset of values its not possible to predict all variations of above. - * - * @author Łukasz Dywicki - Initial contribution. - */ -@Component(service = { ChannelTypeProvider.class, WMBusMessageListener.class, WMBusChannelTypeProvider.class }) -public class WMBusChannelTypeProvider implements ChannelTypeProvider, WMBusMessageListener { - - private final Logger logger = LoggerFactory.getLogger(WMBusChannelTypeProvider.class); - - private final Map wmbusChannelMap = new ConcurrentHashMap<>(); - private UnitRegistry unitRegistry; - - @Override - public Collection getChannelTypes(@Nullable Locale locale) { - return wmbusChannelMap.values(); - } - - @Override - public @Nullable ChannelType getChannelType(ChannelTypeUID channelTypeUID, @Nullable Locale locale) { - return wmbusChannelMap.values().stream().filter(channelType -> channelType.getUID().equals(channelTypeUID)) - .findFirst().orElse(null); - } - - @Override - public void onNewWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { - calculateChannelTypes(device); - } - - @Override - public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { - calculateChannelTypes(device); - } - - private void calculateChannelTypes(WMBusDevice device) { - VariableDataStructure response = device.getOriginalMessage().getVariableDataResponse(); - DateFieldMode dateFieldMode = device.getAdapter().getDateFieldMode(); - - for (DataRecord record : response.getDataRecords()) { - Optional channelTypeUID = getChannelType(record); - if (channelTypeUID.isPresent()) { - ChannelTypeUID typeUID = channelTypeUID.get(); - if (!wmbusChannelMap.containsKey(typeUID.getId())) { - Optional> unit = unitRegistry.lookup(record.getUnit()); - String label = getFunction(record.getFunctionField()) + " "; - label += record.getDescription().name().toLowerCase().replace("_", " "); - if (record.getTariff() != 0) { - label += " tariff " + record.getTariff(); - } - if (record.getStorageNumber() != 0) { - label += " storage " + record.getStorageNumber(); - } - - logger.info("Calculating new channel type {} for record {}", channelTypeUID, record); - - Optional itemType = getItemType(record.getDataValueType(), record.getUnit(), dateFieldMode); - ChannelKind kind = ChannelKind.STATE; - String description = getDescription(record); - String category = ""; - Set tags = Collections.emptySet(); - StateDescription state = getStateDescription(record.getDataValueType(), record.getDescription(), - unit, dateFieldMode); - EventDescription event = null; - ChannelTypeBuilder channelTypeBuilder; - ChannelType channelType = ChannelTypeBuilder.state(channelTypeUID.get(), label, itemType.get()) - .isAdvanced(false).withDescription(description).withCategory(category) - .build();/* - * new ChannelType(typeUID, false, itemType.get(), kind, label, - * description, - * category, tags, state, null, event, null, null) - */ - - wmbusChannelMap.put(typeUID.getId(), channelType); - } - } - } - } - - private StateDescription getStateDescription(DataValueType type, Description description, - Optional> mappedUnit, DateFieldMode dateFieldMode) { - - boolean number; - if (type == DataValueType.BCD || type == DataValueType.DOUBLE || type == DataValueType.LONG) { - number = true; - } else { - number = false; - } - - boolean date = type == DataValueType.DATE; - - String pattern = mappedUnit.map(unit -> formatUnit(false, number, date, dateFieldMode)) - .orElseGet(() -> formatUnit(true, number, date, dateFieldMode)); - StateDescriptionFragmentBuilder stateFragment = StateDescriptionFragmentBuilder.create().withPattern(pattern) - .withReadOnly(true); - return stateFragment.build() - .toStateDescription()/* new StateDescription(null, null, null, pattern, true, null) */; - } - - private String formatUnit(boolean unitless, boolean number, boolean date, DateFieldMode dateFieldMode) { - if (number) { - if (unitless) { - return "%.2f"; - } else { - return "%.2f %unit%"; - } - } - - if (date) { - switch (dateFieldMode) { - case UNIX_TIMESTAMP: - return "%d"; - case DATE_TIME: - return ""; - // default in this case is string - } - } - - return "%s"; - } - - private String getDescription(DataRecord record) { - String description = record.getDescription().name().replace("_", " ").toLowerCase(); - String function = getFunction(record.getFunctionField()); - return function + " value of " + description + " registry. Storage " + record.getStorageNumber() + ", tarif " - + record.getTariff() + ". Emmited under DIB " + HexUtils.bytesToHex(record.getDib()) + " and VIB:" - + HexUtils.bytesToHex(record.getVib()); - } - - private String getFunction(FunctionField function) { - switch (function) { - case ERROR_VAL: - return "Error"; - case INST_VAL: - return "Present"; - case MAX_VAL: - return "Maximum"; - case MIN_VAL: - return "Minimum"; - - } - - return "Unknown"; - } - - private Optional getItemType(DataValueType dataValueType, DlmsUnit dlmsUnit, DateFieldMode dateFieldMode) { - switch (dataValueType) { - case BCD: - case DOUBLE: - case LONG: - String quantity = unitRegistry.quantity(dlmsUnit).map(Class::getSimpleName) - .map(quantityName -> ":" + quantityName).orElse(""); - return Optional.of(CoreItemFactory.NUMBER + quantity); - case DATE: - switch (dateFieldMode) { - case FORMATTED_STRING: - return Optional.of(CoreItemFactory.STRING); - case UNIX_TIMESTAMP: - return Optional.of(CoreItemFactory.NUMBER); - default: - return Optional.of(CoreItemFactory.DATETIME); - } - case STRING: - return Optional.of(CoreItemFactory.STRING); - } - - return Optional.of(CoreItemFactory.STRING); - } - - @Reference - protected void setUnitRegistry(UnitRegistry unitRegistry) { - this.unitRegistry = unitRegistry; - } - - protected void unsetUnitRegistry(UnitRegistry unitRegistry) { - this.unitRegistry = null; - } - - public final static Optional getChannelId(DataRecord record) { - if (record.getDescription() == Description.RESERVED || record.getDescription() == Description.NOT_SUPPORTED - || record.getDescription() == Description.MANUFACTURER_SPECIFIC) { - return Optional.empty(); - } - - String dib = HexUtils.bytesToHex(record.getDib()); - String vib = HexUtils.bytesToHex(record.getVib()); - - return Optional.of(record.getDescription().name().toLowerCase() + "_" + dib + "_" + vib); - } - - public final static Optional getChannelType(DataRecord record) { - return getChannelId(record).map(id -> new ChannelTypeUID(WMBusBindingConstants.BINDING_ID, id)); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.internal; + +import java.util.Collection; +import java.util.Collections; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import javax.measure.Unit; + +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.wmbus.UnitRegistry; +import org.openhab.binding.wmbus.WMBusBindingConstants; +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.config.DateFieldMode; +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openhab.binding.wmbus.handler.WMBusMessageListener; +import org.openhab.core.library.CoreItemFactory; +import org.openhab.core.thing.type.ChannelKind; +import org.openhab.core.thing.type.ChannelType; +import org.openhab.core.thing.type.ChannelTypeBuilder; +import org.openhab.core.thing.type.ChannelTypeProvider; +import org.openhab.core.thing.type.ChannelTypeUID; +import org.openhab.core.types.EventDescription; +import org.openhab.core.types.StateDescription; +import org.openhab.core.types.StateDescriptionFragmentBuilder; +import org.openhab.core.util.HexUtils; +import org.openmuc.jmbus.DataRecord; +import org.openmuc.jmbus.DataRecord.DataValueType; +import org.openmuc.jmbus.DataRecord.Description; +import org.openmuc.jmbus.DataRecord.FunctionField; +import org.openmuc.jmbus.DlmsUnit; +import org.openmuc.jmbus.VariableDataStructure; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Dynamic channel type provider which uses received wmbus frames to create channel types. + * + * Because wmbus frames contain variable data part which have many mutations it is not possible to create a static + * configuration which would cover all combinations. Fields which are multipliers are: + * - dib (inst/min/max/error val) + * - vib (subunit, tariff, storage number, vif) + * + * While most of devices uses just small subset of values its not possible to predict all variations of above. + * + * @author Łukasz Dywicki - Initial contribution. + */ +@Component(service = { ChannelTypeProvider.class, WMBusMessageListener.class, WMBusChannelTypeProvider.class }) +public class WMBusChannelTypeProvider implements ChannelTypeProvider, WMBusMessageListener { + + private final Logger logger = LoggerFactory.getLogger(WMBusChannelTypeProvider.class); + + private final Map wmbusChannelMap = new ConcurrentHashMap<>(); + private UnitRegistry unitRegistry; + + @Override + public Collection getChannelTypes(@Nullable Locale locale) { + return wmbusChannelMap.values(); + } + + @Override + public @Nullable ChannelType getChannelType(ChannelTypeUID channelTypeUID, @Nullable Locale locale) { + return wmbusChannelMap.values().stream().filter(channelType -> channelType.getUID().equals(channelTypeUID)) + .findFirst().orElse(null); + } + + @Override + public void onNewWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { + calculateChannelTypes(device); + } + + @Override + public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { + calculateChannelTypes(device); + } + + private void calculateChannelTypes(WMBusDevice device) { + VariableDataStructure response = device.getOriginalMessage().getVariableDataResponse(); + DateFieldMode dateFieldMode = device.getAdapter().getDateFieldMode(); + + for (DataRecord record : response.getDataRecords()) { + Optional channelTypeUID = getChannelType(record); + if (channelTypeUID.isPresent()) { + ChannelTypeUID typeUID = channelTypeUID.get(); + if (!wmbusChannelMap.containsKey(typeUID.getId())) { + Optional> unit = unitRegistry.lookup(record.getUnit()); + String label = getFunction(record.getFunctionField()) + " "; + label += record.getDescription().name().toLowerCase().replace("_", " "); + if (record.getTariff() != 0) { + label += " tariff " + record.getTariff(); + } + if (record.getStorageNumber() != 0) { + label += " storage " + record.getStorageNumber(); + } + + logger.info("Calculating new channel type {} for record {}", channelTypeUID, record); + + Optional itemType = getItemType(record.getDataValueType(), record.getUnit(), dateFieldMode); + ChannelKind kind = ChannelKind.STATE; + String description = getDescription(record); + String category = ""; + Set tags = Collections.emptySet(); + StateDescription state = getStateDescription(record.getDataValueType(), record.getDescription(), + unit, dateFieldMode); + EventDescription event = null; + ChannelTypeBuilder channelTypeBuilder; + ChannelType channelType = ChannelTypeBuilder.state(channelTypeUID.get(), label, itemType.get()) + .isAdvanced(false).withDescription(description).withCategory(category) + .build();/* + * new ChannelType(typeUID, false, itemType.get(), kind, label, + * description, + * category, tags, state, null, event, null, null) + */ + + wmbusChannelMap.put(typeUID.getId(), channelType); + } + } + } + } + + private StateDescription getStateDescription(DataValueType type, Description description, + Optional> mappedUnit, DateFieldMode dateFieldMode) { + + boolean number; + if (type == DataValueType.BCD || type == DataValueType.DOUBLE || type == DataValueType.LONG) { + number = true; + } else { + number = false; + } + + boolean date = type == DataValueType.DATE; + + String pattern = mappedUnit.map(unit -> formatUnit(false, number, date, dateFieldMode)) + .orElseGet(() -> formatUnit(true, number, date, dateFieldMode)); + StateDescriptionFragmentBuilder stateFragment = StateDescriptionFragmentBuilder.create().withPattern(pattern) + .withReadOnly(true); + return stateFragment.build() + .toStateDescription()/* new StateDescription(null, null, null, pattern, true, null) */; + } + + private String formatUnit(boolean unitless, boolean number, boolean date, DateFieldMode dateFieldMode) { + if (number) { + if (unitless) { + return "%.2f"; + } else { + return "%.2f %unit%"; + } + } + + if (date) { + switch (dateFieldMode) { + case UNIX_TIMESTAMP: + return "%d"; + case DATE_TIME: + return ""; + // default in this case is string + } + } + + return "%s"; + } + + private String getDescription(DataRecord record) { + String description = record.getDescription().name().replace("_", " ").toLowerCase(); + String function = getFunction(record.getFunctionField()); + return function + " value of " + description + " registry. Storage " + record.getStorageNumber() + ", tarif " + + record.getTariff() + ". Emmited under DIB " + HexUtils.bytesToHex(record.getDib()) + " and VIB:" + + HexUtils.bytesToHex(record.getVib()); + } + + private String getFunction(FunctionField function) { + switch (function) { + case ERROR_VAL: + return "Error"; + case INST_VAL: + return "Present"; + case MAX_VAL: + return "Maximum"; + case MIN_VAL: + return "Minimum"; + + } + + return "Unknown"; + } + + private Optional getItemType(DataValueType dataValueType, DlmsUnit dlmsUnit, DateFieldMode dateFieldMode) { + switch (dataValueType) { + case BCD: + case DOUBLE: + case LONG: + String quantity = unitRegistry.quantity(dlmsUnit).map(Class::getSimpleName) + .map(quantityName -> ":" + quantityName).orElse(""); + return Optional.of(CoreItemFactory.NUMBER + quantity); + case DATE: + switch (dateFieldMode) { + case FORMATTED_STRING: + return Optional.of(CoreItemFactory.STRING); + case UNIX_TIMESTAMP: + return Optional.of(CoreItemFactory.NUMBER); + default: + return Optional.of(CoreItemFactory.DATETIME); + } + case STRING: + return Optional.of(CoreItemFactory.STRING); + } + + return Optional.of(CoreItemFactory.STRING); + } + + @Reference + protected void setUnitRegistry(UnitRegistry unitRegistry) { + this.unitRegistry = unitRegistry; + } + + protected void unsetUnitRegistry(UnitRegistry unitRegistry) { + this.unitRegistry = null; + } + + public final static Optional getChannelId(DataRecord record) { + if (record.getDescription() == Description.RESERVED || record.getDescription() == Description.NOT_SUPPORTED + || record.getDescription() == Description.MANUFACTURER_SPECIFIC) { + return Optional.empty(); + } + + String dib = HexUtils.bytesToHex(record.getDib()); + String vib = HexUtils.bytesToHex(record.getVib()); + + return Optional.of(record.getDescription().name().toLowerCase() + "_" + dib + "_" + vib); + } + + public final static Optional getChannelType(DataRecord record) { + return getChannelId(record).map(id -> new ChannelTypeUID(WMBusBindingConstants.BINDING_ID, id)); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusException.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusException.java index ba5c171..a57a5bb 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusException.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusException.java @@ -1,27 +1,27 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.internal; - -/** - * The {@link WMBusException} class defines WMBusException - * - * @author Roman Malyugin - Initial contribution - */ - -public class WMBusException extends Exception { - - public WMBusException(String string) { - super(string); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.internal; + +/** + * The {@link WMBusException} class defines WMBusException + * + * @author Roman Malyugin - Initial contribution + */ + +public class WMBusException extends Exception { + + public WMBusException(String string) { + super(string); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusHandlerFactory.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusHandlerFactory.java index c6d249b..2225a25 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusHandlerFactory.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusHandlerFactory.java @@ -1,146 +1,146 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.internal; - -import org.openhab.binding.wmbus.UnitRegistry; -import org.openhab.binding.wmbus.WMBusBindingConstants; -import org.openhab.binding.wmbus.device.UnknownMeter.UnknownWMBusDeviceHandler; -import org.openhab.binding.wmbus.device.generic.DynamicWMBusThingHandler; -import org.openhab.binding.wmbus.discovery.CompositeMessageListener; -import org.openhab.binding.wmbus.handler.VirtualWMBusBridgeHandler; -import org.openhab.binding.wmbus.handler.WMBusBridgeHandler; -import org.openhab.binding.wmbus.handler.WMBusMessageListener; -import org.openhab.core.thing.Bridge; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.binding.BaseThingHandlerFactory; -import org.openhab.core.thing.binding.ThingHandler; -import org.openhab.core.thing.binding.ThingHandlerFactory; -import org.openhab.io.transport.mbus.wireless.FilteredKeyStorage; -import org.openhab.io.transport.mbus.wireless.KeyStorage; -import org.osgi.service.component.ComponentContext; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Deactivate; -import org.osgi.service.component.annotations.Reference; -import org.osgi.service.component.annotations.ReferenceCardinality; -import org.osgi.service.component.annotations.ReferencePolicy; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link WMBusHandlerFactory} class defines WMBusHandlerFactory. This class is the main entry point of the binding. - * - * @author Hanno - Felix Wagner - Roman Malyugin - Initial contribution - */ - -@Component(service = { WMBusHandlerFactory.class, BaseThingHandlerFactory.class, ThingHandlerFactory.class }) -public class WMBusHandlerFactory extends BaseThingHandlerFactory { - - // OpenHAB logger - private final Logger logger = LoggerFactory.getLogger(WMBusHandlerFactory.class); - - private final CompositeMessageListener messageListener = new CompositeMessageListener(); - - private KeyStorage keyStorage; - private UnitRegistry unitRegistry; - private WMBusChannelTypeProvider channelTypeProvider; - - public WMBusHandlerFactory() { - logger.debug("wmbus handler factory is starting up."); - } - - @Override - public boolean supportsThingType(ThingTypeUID thingTypeUID) { - return WMBusBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); - } - - @Override - protected ThingHandler createHandler(Thing thing) { - ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - - if (thing instanceof Bridge) { - if (thingTypeUID.equals(WMBusBindingConstants.THING_TYPE_BRIDGE)) { - logger.debug("Creating handler for WMBus bridge."); - WMBusBridgeHandler handler = new WMBusBridgeHandler((Bridge) thing, keyStorage); - handler.registerWMBusMessageListener(messageListener); - return handler; - } else if (thingTypeUID.equals(WMBusBindingConstants.THING_TYPE_VIRTUAL_BRIDGE)) { - logger.debug("Creating handler for virtual WMBus bridge."); - VirtualWMBusBridgeHandler handler = new VirtualWMBusBridgeHandler((Bridge) thing, keyStorage); - handler.registerWMBusMessageListener(messageListener); - return handler; - } - } - - // add new devices here - if (thingTypeUID.equals(WMBusBindingConstants.THING_TYPE_METER) - || thingTypeUID.equals(WMBusBindingConstants.THING_TYPE_ENCRYPTED_METER)) { - logger.debug("Creating standard wmbus handler."); - return new DynamicWMBusThingHandler<>(thing, new FilteredKeyStorage(keyStorage, thing), unitRegistry, - channelTypeProvider); - } else { - logger.debug("Creating (handler for) Unknown device."); - return new UnknownWMBusDeviceHandler(thing, new FilteredKeyStorage(keyStorage, thing)); - } - } - - @Override - @Activate - protected void activate(ComponentContext componentContext) { - super.activate(componentContext); - } - - @Override - @Deactivate - protected void deactivate(ComponentContext componentContext) { - super.deactivate(componentContext); - } - - @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) - public void registerWMBusMessageListener(WMBusMessageListener wmBusMessageListener) { - messageListener.addMessageListener(wmBusMessageListener); - } - - public void unregisterWMBusMessageListener(WMBusMessageListener wmBusMessageListener) { - messageListener.removeMessageListener(wmBusMessageListener); - } - - @Reference - public void setKeyStorage(KeyStorage keyStorage) { - this.keyStorage = keyStorage; - } - - public void unsetKeyStorage(KeyStorage keyStorage) { - this.keyStorage = null; - } - - @Reference - protected void setUnitRegistry(UnitRegistry unitRegistry) { - this.unitRegistry = unitRegistry; - } - - protected void unsetUnitRegistry(UnitRegistry unitRegistry) { - this.unitRegistry = null; - } - - @Reference - protected void setChannelTypeProvider(WMBusChannelTypeProvider channelTypeProvider) { - this.channelTypeProvider = channelTypeProvider; - } - - protected void unsetChannelTypeProvider(WMBusChannelTypeProvider channelTypeProvider) { - this.channelTypeProvider = null; - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.internal; + +import org.openhab.binding.wmbus.UnitRegistry; +import org.openhab.binding.wmbus.WMBusBindingConstants; +import org.openhab.binding.wmbus.device.UnknownMeter.UnknownWMBusDeviceHandler; +import org.openhab.binding.wmbus.device.generic.DynamicWMBusThingHandler; +import org.openhab.binding.wmbus.discovery.CompositeMessageListener; +import org.openhab.binding.wmbus.handler.VirtualWMBusBridgeHandler; +import org.openhab.binding.wmbus.handler.WMBusBridgeHandler; +import org.openhab.binding.wmbus.handler.WMBusMessageListener; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.BaseThingHandlerFactory; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.openhab.io.transport.mbus.wireless.FilteredKeyStorage; +import org.openhab.io.transport.mbus.wireless.KeyStorage; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link WMBusHandlerFactory} class defines WMBusHandlerFactory. This class is the main entry point of the binding. + * + * @author Hanno - Felix Wagner - Roman Malyugin - Initial contribution + */ + +@Component(service = { WMBusHandlerFactory.class, BaseThingHandlerFactory.class, ThingHandlerFactory.class }) +public class WMBusHandlerFactory extends BaseThingHandlerFactory { + + // OpenHAB logger + private final Logger logger = LoggerFactory.getLogger(WMBusHandlerFactory.class); + + private final CompositeMessageListener messageListener = new CompositeMessageListener(); + + private KeyStorage keyStorage; + private UnitRegistry unitRegistry; + private WMBusChannelTypeProvider channelTypeProvider; + + public WMBusHandlerFactory() { + logger.debug("wmbus handler factory is starting up."); + } + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return WMBusBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + @Override + protected ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (thing instanceof Bridge) { + if (thingTypeUID.equals(WMBusBindingConstants.THING_TYPE_BRIDGE)) { + logger.debug("Creating handler for WMBus bridge."); + WMBusBridgeHandler handler = new WMBusBridgeHandler((Bridge) thing, keyStorage); + handler.registerWMBusMessageListener(messageListener); + return handler; + } else if (thingTypeUID.equals(WMBusBindingConstants.THING_TYPE_VIRTUAL_BRIDGE)) { + logger.debug("Creating handler for virtual WMBus bridge."); + VirtualWMBusBridgeHandler handler = new VirtualWMBusBridgeHandler((Bridge) thing, keyStorage); + handler.registerWMBusMessageListener(messageListener); + return handler; + } + } + + // add new devices here + if (thingTypeUID.equals(WMBusBindingConstants.THING_TYPE_METER) + || thingTypeUID.equals(WMBusBindingConstants.THING_TYPE_ENCRYPTED_METER)) { + logger.debug("Creating standard wmbus handler."); + return new DynamicWMBusThingHandler<>(thing, new FilteredKeyStorage(keyStorage, thing), unitRegistry, + channelTypeProvider); + } else { + logger.debug("Creating (handler for) Unknown device."); + return new UnknownWMBusDeviceHandler(thing, new FilteredKeyStorage(keyStorage, thing)); + } + } + + @Override + @Activate + protected void activate(ComponentContext componentContext) { + super.activate(componentContext); + } + + @Override + @Deactivate + protected void deactivate(ComponentContext componentContext) { + super.deactivate(componentContext); + } + + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + public void registerWMBusMessageListener(WMBusMessageListener wmBusMessageListener) { + messageListener.addMessageListener(wmBusMessageListener); + } + + public void unregisterWMBusMessageListener(WMBusMessageListener wmBusMessageListener) { + messageListener.removeMessageListener(wmBusMessageListener); + } + + @Reference + public void setKeyStorage(KeyStorage keyStorage) { + this.keyStorage = keyStorage; + } + + public void unsetKeyStorage(KeyStorage keyStorage) { + this.keyStorage = null; + } + + @Reference + protected void setUnitRegistry(UnitRegistry unitRegistry) { + this.unitRegistry = unitRegistry; + } + + protected void unsetUnitRegistry(UnitRegistry unitRegistry) { + this.unitRegistry = null; + } + + @Reference + protected void setChannelTypeProvider(WMBusChannelTypeProvider channelTypeProvider) { + this.channelTypeProvider = channelTypeProvider; + } + + protected void unsetChannelTypeProvider(WMBusChannelTypeProvider channelTypeProvider) { + this.channelTypeProvider = null; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusReceiver.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusReceiver.java index 9e64411..54aebf0 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusReceiver.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/WMBusReceiver.java @@ -1,99 +1,99 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.internal; - -import java.io.IOException; -import java.util.Arrays; - -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openhab.core.util.HexUtils; -import org.openmuc.jmbus.wireless.WMBusListener; -import org.openmuc.jmbus.wireless.WMBusMessage; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link WMBusReceiver} class defines WMBusReceiver. Keeps connection with the WMBus radio module and forwards - * - * @author Hanno - Felix Wagner - Roman Malyugin - Initial contribution - */ - -public class WMBusReceiver implements WMBusListener { - - int[] filterIDs = new int[] {}; - - private final WMBusAdapter wmBusBridgeHandler; - - private final Logger logger = LoggerFactory.getLogger(WMBusReceiver.class); - - public WMBusReceiver(WMBusAdapter wmBusBridgeHandler) { - this.wmBusBridgeHandler = wmBusBridgeHandler; - } - - public int[] getFilterIDs() { - logger.debug("getFilterIDs(): got {}", Arrays.toString(filterIDs)); - return filterIDs; - } - - public void setFilterIDs(int[] filterIDs) { - logger.debug("setFilterIDs() to {}", Arrays.toString(filterIDs)); - this.filterIDs = filterIDs; - } - - boolean filterMatch(int inQuestion) { - logger.trace("filterMatch(): are we interested in device {}?", Integer.toString(inQuestion)); - if (filterIDs.length == 0) { - logger.trace("filterMatch(): length is zero -> yes"); - return true; - } - for (int i = 0; i < filterIDs.length; i++) { - if (filterIDs[i] == inQuestion) { - logger.debug("filterMatch(): found the device -> yes"); - return true; - } - } - logger.trace("filterMatch(): not found"); - return false; - } - - /* - * Handle incoming WMBus message from radio module. - * - * @see org.openmuc.jmbus.WMBusListener#newMessage(org.openmuc.jmbus.WMBusMessage) - */ - @Override - public void newMessage(WMBusMessage message) { - logger.trace("Received WMBus message"); - if (filterMatch(message.getSecondaryAddress().getDeviceId().intValue())) { - WMBusDevice device = new WMBusDevice(message, wmBusBridgeHandler); - - logger.trace("Forwarding message to further processing: {}", HexUtils.bytesToHex(message.asBlob())); - wmBusBridgeHandler.processMessage(device); - } else { - logger.trace("Unmatched message received: {}", HexUtils.bytesToHex(message.asBlob())); - } - } - - @Override - public void discardedBytes(byte[] bytes) { - logger.debug("Bytes discarded by radio module: {}", HexConverter.bytesToHex(bytes)); - } - - @Override - public void stoppedListening(IOException e) { - wmBusBridgeHandler.reset(); - logger.warn("Stopped listening for new messages. Reason: {}", e); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.internal; + +import java.io.IOException; +import java.util.Arrays; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openhab.core.util.HexUtils; +import org.openmuc.jmbus.wireless.WMBusListener; +import org.openmuc.jmbus.wireless.WMBusMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link WMBusReceiver} class defines WMBusReceiver. Keeps connection with the WMBus radio module and forwards + * + * @author Hanno - Felix Wagner - Roman Malyugin - Initial contribution + */ + +public class WMBusReceiver implements WMBusListener { + + int[] filterIDs = new int[] {}; + + private final WMBusAdapter wmBusBridgeHandler; + + private final Logger logger = LoggerFactory.getLogger(WMBusReceiver.class); + + public WMBusReceiver(WMBusAdapter wmBusBridgeHandler) { + this.wmBusBridgeHandler = wmBusBridgeHandler; + } + + public int[] getFilterIDs() { + logger.debug("getFilterIDs(): got {}", Arrays.toString(filterIDs)); + return filterIDs; + } + + public void setFilterIDs(int[] filterIDs) { + logger.debug("setFilterIDs() to {}", Arrays.toString(filterIDs)); + this.filterIDs = filterIDs; + } + + boolean filterMatch(int inQuestion) { + logger.trace("filterMatch(): are we interested in device {}?", Integer.toString(inQuestion)); + if (filterIDs.length == 0) { + logger.trace("filterMatch(): length is zero -> yes"); + return true; + } + for (int i = 0; i < filterIDs.length; i++) { + if (filterIDs[i] == inQuestion) { + logger.debug("filterMatch(): found the device -> yes"); + return true; + } + } + logger.trace("filterMatch(): not found"); + return false; + } + + /* + * Handle incoming WMBus message from radio module. + * + * @see org.openmuc.jmbus.WMBusListener#newMessage(org.openmuc.jmbus.WMBusMessage) + */ + @Override + public void newMessage(WMBusMessage message) { + logger.trace("Received WMBus message"); + if (filterMatch(message.getSecondaryAddress().getDeviceId().intValue())) { + WMBusDevice device = new WMBusDevice(message, wmBusBridgeHandler); + + logger.trace("Forwarding message to further processing: {}", HexUtils.bytesToHex(message.asBlob())); + wmBusBridgeHandler.processMessage(device); + } else { + logger.trace("Unmatched message received: {}", HexUtils.bytesToHex(message.asBlob())); + } + } + + @Override + public void discardedBytes(byte[] bytes) { + logger.debug("Bytes discarded by radio module: {}", HexConverter.bytesToHex(bytes)); + } + + @Override + public void stoppedListening(IOException e) { + wmBusBridgeHandler.reset(); + logger.warn("Stopped listening for new messages. Reason: {}", e); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/discovery/WMBusDiscoveryService.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/discovery/WMBusDiscoveryService.java index 4ca9a90..c4fd1e8 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/discovery/WMBusDiscoveryService.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/discovery/WMBusDiscoveryService.java @@ -1,154 +1,154 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.internal.discovery; - -import static org.openhab.binding.wmbus.WMBusBindingConstants.*; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openhab.binding.wmbus.handler.WMBusBridgeHandler; -import org.openhab.binding.wmbus.handler.WMBusMessageListener; -import org.openhab.core.config.discovery.AbstractDiscoveryService; -import org.openhab.core.config.discovery.DiscoveryResult; -import org.openhab.core.config.discovery.DiscoveryResultBuilder; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.ThingUID; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link WMBusDiscoveryService2} class defines WMBusDiscoveryService2 - * - * FIXME: Left for backward compatibility verification. - * - * @author Hanno - Felix Wagner - Initial contribution - */ - -public class WMBusDiscoveryService extends AbstractDiscoveryService implements WMBusMessageListener { - - private final Logger logger = LoggerFactory.getLogger(WMBusDiscoveryService.class); - - // add new devices here; these IDs here are generated in getThingTypeUID() - // basically: control field (0x44 = dec. 68) + manufacturer ID (3 characters) + device version (as output by test - // program) + device type from jMBus DeviceType class (eg. HEAT_METER = 0x04 = 4) - // you can get these values using the diagnostics radio message printer included with the JMBus library - private Map typeToWMBUSIdMap; - - private WMBusBridgeHandler bridgeHandler; - - public WMBusDiscoveryService(Set supportedThingTypes, WMBusBridgeHandler bridgeHandler, - Map types) { - super(supportedThingTypes, 1); - this.bridgeHandler = bridgeHandler; - this.typeToWMBUSIdMap = types; - } - - public WMBusDiscoveryService(Set supportedThingTypes, int timeout, - boolean backgroundDiscoveryEnabledByDefault) throws IllegalArgumentException { - super(supportedThingTypes, timeout, backgroundDiscoveryEnabledByDefault); - } - - @Override - // add new devices there - public Set getSupportedThingTypes() { - logger.trace("getSupportedThingTypes(): currently *implemented* are {}", super.getSupportedThingTypes()); - return super.getSupportedThingTypes(); - } - - @Override - protected void startScan() { - // do nothing since there is no active scan possible at the moment, only receiving - logger.debug("startScan(): unimplemented - devices will be added upon reception of telegrams from them"); - } - - @Override - public void onNewWMBusDevice(WMBusAdapter adapter, WMBusDevice wmBusDevice) { - logger.debug( - "onnewWMBusDevice(): new device " + wmBusDevice.getOriginalMessage().getSecondaryAddress().toString()); - onWMBusMessageReceivedInternal(wmBusDevice); - } - - private void onWMBusMessageReceivedInternal(WMBusDevice wmBusDevice) { - logger.trace("msgreceivedInternal() begin"); - // try to find this device in the list of supported devices - ThingUID thingUID = getThingUID(wmBusDevice); - - if (thingUID != null) { - logger.trace("msgReceivedInternal(): device is known"); - // device known -> create discovery result - ThingUID bridgeUID = bridgeHandler.getThing().getUID(); - Map properties = new HashMap<>(1); - properties.put(PROPERTY_DEVICE_ADDRESS, wmBusDevice.getDeviceId().toString()); - - DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties) - .withRepresentationProperty(wmBusDevice.getDeviceId().toString()).withBridge(bridgeUID) - .withLabel("WMBus device #" + wmBusDevice.getDeviceId() + " (" + getTypeID(wmBusDevice) + ")") - .build(); - logger.trace("msgReceivedInternal(): notifying OpenHAB of new thing"); - thingDiscovered(discoveryResult); - } else { - // device unknown -> log message - logger.debug( - "discovered unsupported WMBus device with our type ID {} of WMBus type '{}' with secondary address {}", - getTypeID(wmBusDevice), wmBusDevice.getOriginalMessage().getSecondaryAddress().getDeviceType(), - wmBusDevice.getOriginalMessage().getSecondaryAddress().toString()); - } - } - - // checks if this device is of the supported kind -> if yes, will be discovered - private ThingUID getThingUID(WMBusDevice wmBusDevice) { - logger.trace("getThingUID begin"); - ThingUID bridgeUID = bridgeHandler.getThing().getUID(); - ThingTypeUID thingTypeUID = getThingTypeUID(wmBusDevice); - - if (thingTypeUID != null && getSupportedThingTypes().contains(thingTypeUID)) { - logger.trace("getThingUID have bridgeUID " + bridgeUID.toString()); - logger.trace("getThingUID have thingTypeUID " + thingTypeUID.toString()); - return new ThingUID(thingTypeUID, bridgeUID, wmBusDevice.getDeviceId() + ""); - } else { - logger.debug("get ThingUID found no supported device"); - return null; - } - } - - private ThingTypeUID getThingTypeUID(WMBusDevice wmBusDevice) { - String typeIdString = getTypeID(wmBusDevice); - logger.trace("getThingTypeUID(): This device has typeID " + typeIdString + " -- supported device types are " - + Arrays.toString(typeToWMBUSIdMap.keySet().toArray())); - String thingTypeId = typeToWMBUSIdMap.get(typeIdString); - return thingTypeId != null ? new ThingTypeUID(BINDING_ID, thingTypeId) : null; - } - - // this ID is needed to add new devices - private String getTypeID(WMBusDevice wmBusDevice) { - return wmBusDevice.getOriginalMessage().getControlField() + "" - + wmBusDevice.getOriginalMessage().getSecondaryAddress().getManufacturerId() + "" - + wmBusDevice.getOriginalMessage().getSecondaryAddress().getVersion() + "" - + wmBusDevice.getOriginalMessage().getSecondaryAddress().getDeviceType().getId(); - } - - public void activate() { - bridgeHandler.registerWMBusMessageListener(this); - } - - @Override - public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice wmBusDevice) { - // nothing to do - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.internal.discovery; + +import static org.openhab.binding.wmbus.WMBusBindingConstants.*; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openhab.binding.wmbus.handler.WMBusBridgeHandler; +import org.openhab.binding.wmbus.handler.WMBusMessageListener; +import org.openhab.core.config.discovery.AbstractDiscoveryService; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link WMBusDiscoveryService2} class defines WMBusDiscoveryService2 + * + * FIXME: Left for backward compatibility verification. + * + * @author Hanno - Felix Wagner - Initial contribution + */ + +public class WMBusDiscoveryService extends AbstractDiscoveryService implements WMBusMessageListener { + + private final Logger logger = LoggerFactory.getLogger(WMBusDiscoveryService.class); + + // add new devices here; these IDs here are generated in getThingTypeUID() + // basically: control field (0x44 = dec. 68) + manufacturer ID (3 characters) + device version (as output by test + // program) + device type from jMBus DeviceType class (eg. HEAT_METER = 0x04 = 4) + // you can get these values using the diagnostics radio message printer included with the JMBus library + private Map typeToWMBUSIdMap; + + private WMBusBridgeHandler bridgeHandler; + + public WMBusDiscoveryService(Set supportedThingTypes, WMBusBridgeHandler bridgeHandler, + Map types) { + super(supportedThingTypes, 1); + this.bridgeHandler = bridgeHandler; + this.typeToWMBUSIdMap = types; + } + + public WMBusDiscoveryService(Set supportedThingTypes, int timeout, + boolean backgroundDiscoveryEnabledByDefault) throws IllegalArgumentException { + super(supportedThingTypes, timeout, backgroundDiscoveryEnabledByDefault); + } + + @Override + // add new devices there + public Set getSupportedThingTypes() { + logger.trace("getSupportedThingTypes(): currently *implemented* are {}", super.getSupportedThingTypes()); + return super.getSupportedThingTypes(); + } + + @Override + protected void startScan() { + // do nothing since there is no active scan possible at the moment, only receiving + logger.debug("startScan(): unimplemented - devices will be added upon reception of telegrams from them"); + } + + @Override + public void onNewWMBusDevice(WMBusAdapter adapter, WMBusDevice wmBusDevice) { + logger.debug( + "onnewWMBusDevice(): new device " + wmBusDevice.getOriginalMessage().getSecondaryAddress().toString()); + onWMBusMessageReceivedInternal(wmBusDevice); + } + + private void onWMBusMessageReceivedInternal(WMBusDevice wmBusDevice) { + logger.trace("msgreceivedInternal() begin"); + // try to find this device in the list of supported devices + ThingUID thingUID = getThingUID(wmBusDevice); + + if (thingUID != null) { + logger.trace("msgReceivedInternal(): device is known"); + // device known -> create discovery result + ThingUID bridgeUID = bridgeHandler.getThing().getUID(); + Map properties = new HashMap<>(1); + properties.put(PROPERTY_DEVICE_ADDRESS, wmBusDevice.getDeviceId().toString()); + + DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties) + .withRepresentationProperty(wmBusDevice.getDeviceId().toString()).withBridge(bridgeUID) + .withLabel("WMBus device #" + wmBusDevice.getDeviceId() + " (" + getTypeID(wmBusDevice) + ")") + .build(); + logger.trace("msgReceivedInternal(): notifying OpenHAB of new thing"); + thingDiscovered(discoveryResult); + } else { + // device unknown -> log message + logger.debug( + "discovered unsupported WMBus device with our type ID {} of WMBus type '{}' with secondary address {}", + getTypeID(wmBusDevice), wmBusDevice.getOriginalMessage().getSecondaryAddress().getDeviceType(), + wmBusDevice.getOriginalMessage().getSecondaryAddress().toString()); + } + } + + // checks if this device is of the supported kind -> if yes, will be discovered + private ThingUID getThingUID(WMBusDevice wmBusDevice) { + logger.trace("getThingUID begin"); + ThingUID bridgeUID = bridgeHandler.getThing().getUID(); + ThingTypeUID thingTypeUID = getThingTypeUID(wmBusDevice); + + if (thingTypeUID != null && getSupportedThingTypes().contains(thingTypeUID)) { + logger.trace("getThingUID have bridgeUID " + bridgeUID.toString()); + logger.trace("getThingUID have thingTypeUID " + thingTypeUID.toString()); + return new ThingUID(thingTypeUID, bridgeUID, wmBusDevice.getDeviceId() + ""); + } else { + logger.debug("get ThingUID found no supported device"); + return null; + } + } + + private ThingTypeUID getThingTypeUID(WMBusDevice wmBusDevice) { + String typeIdString = getTypeID(wmBusDevice); + logger.trace("getThingTypeUID(): This device has typeID " + typeIdString + " -- supported device types are " + + Arrays.toString(typeToWMBUSIdMap.keySet().toArray())); + String thingTypeId = typeToWMBUSIdMap.get(typeIdString); + return thingTypeId != null ? new ThingTypeUID(BINDING_ID, thingTypeId) : null; + } + + // this ID is needed to add new devices + private String getTypeID(WMBusDevice wmBusDevice) { + return wmBusDevice.getOriginalMessage().getControlField() + "" + + wmBusDevice.getOriginalMessage().getSecondaryAddress().getManufacturerId() + "" + + wmBusDevice.getOriginalMessage().getSecondaryAddress().getVersion() + "" + + wmBusDevice.getOriginalMessage().getSecondaryAddress().getDeviceType().getId(); + } + + public void activate() { + bridgeHandler.registerWMBusMessageListener(this); + } + + @Override + public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice wmBusDevice) { + // nothing to do + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/discovery/WMBusDiscoveryService2.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/discovery/WMBusDiscoveryService2.java index ef219bc..e5d0a92 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/discovery/WMBusDiscoveryService2.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/discovery/WMBusDiscoveryService2.java @@ -1,205 +1,205 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.internal.discovery; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArraySet; - -import org.openhab.binding.wmbus.BindingConfiguration; -import org.openhab.binding.wmbus.WMBusBindingConstants; -import org.openhab.binding.wmbus.WMBusCompanyIdentifiers; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.discovery.WMBusDiscoveryParticipant; -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openhab.binding.wmbus.handler.WMBusMessageListener; -import org.openhab.core.config.discovery.AbstractDiscoveryService; -import org.openhab.core.config.discovery.DiscoveryResult; -import org.openhab.core.config.discovery.DiscoveryResultBuilder; -import org.openhab.core.config.discovery.DiscoveryService; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.ThingUID; -import org.openmuc.jmbus.SecondaryAddress; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Deactivate; -import org.osgi.service.component.annotations.Modified; -import org.osgi.service.component.annotations.Reference; -import org.osgi.service.component.annotations.ReferenceCardinality; -import org.osgi.service.component.annotations.ReferencePolicy; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link WMBusDiscoveryService2} handles searching for WMBus devices. - * - * @author Łukasz Dywicki - initial Contribution - */ -@Component(immediate = true, service = { DiscoveryService.class, - WMBusMessageListener.class }, configurationPid = "discovery.wmbus") -public class WMBusDiscoveryService2 extends AbstractDiscoveryService implements WMBusMessageListener { - - private final Logger logger = LoggerFactory.getLogger(WMBusDiscoveryService2.class); - - private static final int SEARCH_TIME = 0; - - private final Set participants = new CopyOnWriteArraySet<>(); - private final Set supportedThingTypes = new CopyOnWriteArraySet<>(); - - private BindingConfiguration configuration; - - public WMBusDiscoveryService2() { - super(SEARCH_TIME); - supportedThingTypes.add(WMBusBindingConstants.THING_TYPE_METER); - } - - @Override - public boolean isBackgroundDiscoveryEnabled() { - return true; - } - - @Override - @Activate - protected void activate(Map configProperties) { - logger.debug("Activating WMBus discovery service"); - super.activate(configProperties); - startScan(); - } - - @Override - @Modified - protected void modified(Map configProperties) { - super.modified(configProperties); - } - - @Override - @Deactivate - public void deactivate() { - logger.debug("Deactivating WMBus discovery service"); - } - - @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) - protected void addWMBusDiscoveryParticipant(WMBusDiscoveryParticipant participant) { - this.participants.add(participant); - supportedThingTypes.addAll(participant.getSupportedThingTypeUIDs()); - } - - protected void removeWMBusDiscoveryParticipant(WMBusDiscoveryParticipant participant) { - supportedThingTypes.removeAll(participant.getSupportedThingTypeUIDs()); - this.participants.remove(participant); - } - - @Override - public Set getSupportedThingTypes() { - return supportedThingTypes; - } - - @Override - public void startScan() { - } - - @Override - public void stopScan() { - removeOlderResults(getTimestampOfLastScan()); - } - - @Override - public void onNewWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { - deviceDiscovered(adapter, device); - } - - @Override - public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { - } - - private void deviceDiscovered(WMBusAdapter adapter, WMBusDevice device) { - for (WMBusDiscoveryParticipant participant : participants) { - try { - DiscoveryResult result = participant.createResult(device); - if (result != null) { - thingDiscovered(result); - return; - } - } catch (Exception e) { - logger.error("Participant '{}' threw an exception.", participant.getClass().getName(), e); - } - } - - // Here we have an additional section which discard devices of unknown type. - // Some of standard WMBus devices are not meant to be read by this binding. - // If earlier discovery participants didn't handle properly device we need to narrow scope to meters, - // valves and other standard accessory excluding repeaters and other special things. - SecondaryAddress secondaryAddress = device.getOriginalMessage().getSecondaryAddress(); - if (/* - * !WMBusBindingConstants.SUPPORTED_DEVICE_TYPES.contains(secondaryAddress.getDeviceType()) - * || - */ !device.getDeviceId().matches("^[a-zA-Z0-9]+$")) { - logger.info("Discarded discovery of device {} which is unsupported by binding: {}", device.getDeviceType(), - secondaryAddress); - return; - } - - // We did not find a thing type for this device, so let's treat it as a generic one - String label = "WMBus device: " + secondaryAddress.getDeviceType().name().toLowerCase().replace("_", " ") + " #" - + device.getDeviceId() + " (" + device.getDeviceType() + ")"; - - Map properties = new HashMap<>(); - properties.put(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS, device.getDeviceAddress()); - properties.put(Thing.PROPERTY_SERIAL_NUMBER, device.getDeviceId()); - properties.put(Thing.PROPERTY_MODEL_ID, secondaryAddress.getVersion()); - - String manufacturer = WMBusCompanyIdentifiers.get(secondaryAddress.getManufacturerId()); - if (manufacturer != null) { - properties.put(Thing.PROPERTY_VENDOR, manufacturer); - label += " (" + manufacturer + ")"; - } - - ThingTypeUID typeUID; - if (device.isEnrypted()) { - typeUID = WMBusBindingConstants.THING_TYPE_ENCRYPTED_METER; - properties.put(WMBusBindingConstants.PROPERTY_DEVICE_ENCRYPTED, true); - } else { - typeUID = WMBusBindingConstants.THING_TYPE_METER; - properties.put(WMBusBindingConstants.PROPERTY_DEVICE_ENCRYPTED, false); - } - - ThingUID thingUID; - if (configuration.getIncludeBridgeUID()) { - thingUID = new ThingUID(typeUID, device.getAdapter().getUID(), device.getDeviceAddress()); - } else { - thingUID = new ThingUID(typeUID, device.getDeviceAddress()); - } - // logger.error("adapter '{}' thingUID '{}'", adapter.getUID(), thingUID); - // Create the discovery result and add to the inbox - DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties) - .withRepresentationProperty(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS).withBridge(adapter.getUID()) - .withThingType(typeUID).withLabel(label).withTTL(getTimeToLive()).build(); - - thingDiscovered(discoveryResult); - } - - @Reference - public void setBindingConfiguration(BindingConfiguration configuration) { - this.configuration = configuration; - } - - public void unsetBindingConfiguration(BindingConfiguration configuration) { - this.configuration = null; - } - - private Long getTimeToLive() { - return configuration.getTimeToLive(); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.internal.discovery; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +import org.openhab.binding.wmbus.BindingConfiguration; +import org.openhab.binding.wmbus.WMBusBindingConstants; +import org.openhab.binding.wmbus.WMBusCompanyIdentifiers; +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.discovery.WMBusDiscoveryParticipant; +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openhab.binding.wmbus.handler.WMBusMessageListener; +import org.openhab.core.config.discovery.AbstractDiscoveryService; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.config.discovery.DiscoveryService; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.openmuc.jmbus.SecondaryAddress; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Modified; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link WMBusDiscoveryService2} handles searching for WMBus devices. + * + * @author Łukasz Dywicki - initial Contribution + */ +@Component(immediate = true, service = { DiscoveryService.class, + WMBusMessageListener.class }, configurationPid = "discovery.wmbus") +public class WMBusDiscoveryService2 extends AbstractDiscoveryService implements WMBusMessageListener { + + private final Logger logger = LoggerFactory.getLogger(WMBusDiscoveryService2.class); + + private static final int SEARCH_TIME = 0; + + private final Set participants = new CopyOnWriteArraySet<>(); + private final Set supportedThingTypes = new CopyOnWriteArraySet<>(); + + private BindingConfiguration configuration; + + public WMBusDiscoveryService2() { + super(SEARCH_TIME); + supportedThingTypes.add(WMBusBindingConstants.THING_TYPE_METER); + } + + @Override + public boolean isBackgroundDiscoveryEnabled() { + return true; + } + + @Override + @Activate + protected void activate(Map configProperties) { + logger.debug("Activating WMBus discovery service"); + super.activate(configProperties); + startScan(); + } + + @Override + @Modified + protected void modified(Map configProperties) { + super.modified(configProperties); + } + + @Override + @Deactivate + public void deactivate() { + logger.debug("Deactivating WMBus discovery service"); + } + + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + protected void addWMBusDiscoveryParticipant(WMBusDiscoveryParticipant participant) { + this.participants.add(participant); + supportedThingTypes.addAll(participant.getSupportedThingTypeUIDs()); + } + + protected void removeWMBusDiscoveryParticipant(WMBusDiscoveryParticipant participant) { + supportedThingTypes.removeAll(participant.getSupportedThingTypeUIDs()); + this.participants.remove(participant); + } + + @Override + public Set getSupportedThingTypes() { + return supportedThingTypes; + } + + @Override + public void startScan() { + } + + @Override + public void stopScan() { + removeOlderResults(getTimestampOfLastScan()); + } + + @Override + public void onNewWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { + deviceDiscovered(adapter, device); + } + + @Override + public void onChangedWMBusDevice(WMBusAdapter adapter, WMBusDevice device) { + } + + private void deviceDiscovered(WMBusAdapter adapter, WMBusDevice device) { + for (WMBusDiscoveryParticipant participant : participants) { + try { + DiscoveryResult result = participant.createResult(device); + if (result != null) { + thingDiscovered(result); + return; + } + } catch (Exception e) { + logger.error("Participant '{}' threw an exception.", participant.getClass().getName(), e); + } + } + + // Here we have an additional section which discard devices of unknown type. + // Some of standard WMBus devices are not meant to be read by this binding. + // If earlier discovery participants didn't handle properly device we need to narrow scope to meters, + // valves and other standard accessory excluding repeaters and other special things. + SecondaryAddress secondaryAddress = device.getOriginalMessage().getSecondaryAddress(); + if (/* + * !WMBusBindingConstants.SUPPORTED_DEVICE_TYPES.contains(secondaryAddress.getDeviceType()) + * || + */ !device.getDeviceId().matches("^[a-zA-Z0-9]+$")) { + logger.info("Discarded discovery of device {} which is unsupported by binding: {}", device.getDeviceType(), + secondaryAddress); + return; + } + + // We did not find a thing type for this device, so let's treat it as a generic one + String label = "WMBus device: " + secondaryAddress.getDeviceType().name().toLowerCase().replace("_", " ") + " #" + + device.getDeviceId() + " (" + device.getDeviceType() + ")"; + + Map properties = new HashMap<>(); + properties.put(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS, device.getDeviceAddress()); + properties.put(Thing.PROPERTY_SERIAL_NUMBER, device.getDeviceId()); + properties.put(Thing.PROPERTY_MODEL_ID, secondaryAddress.getVersion()); + + String manufacturer = WMBusCompanyIdentifiers.get(secondaryAddress.getManufacturerId()); + if (manufacturer != null) { + properties.put(Thing.PROPERTY_VENDOR, manufacturer); + label += " (" + manufacturer + ")"; + } + + ThingTypeUID typeUID; + if (device.isEnrypted()) { + typeUID = WMBusBindingConstants.THING_TYPE_ENCRYPTED_METER; + properties.put(WMBusBindingConstants.PROPERTY_DEVICE_ENCRYPTED, true); + } else { + typeUID = WMBusBindingConstants.THING_TYPE_METER; + properties.put(WMBusBindingConstants.PROPERTY_DEVICE_ENCRYPTED, false); + } + + ThingUID thingUID; + if (configuration.getIncludeBridgeUID()) { + thingUID = new ThingUID(typeUID, device.getAdapter().getUID(), device.getDeviceAddress()); + } else { + thingUID = new ThingUID(typeUID, device.getDeviceAddress()); + } + // logger.error("adapter '{}' thingUID '{}'", adapter.getUID(), thingUID); + // Create the discovery result and add to the inbox + DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties) + .withRepresentationProperty(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS).withBridge(adapter.getUID()) + .withThingType(typeUID).withLabel(label).withTTL(getTimeToLive()).build(); + + thingDiscovered(discoveryResult); + } + + @Reference + public void setBindingConfiguration(BindingConfiguration configuration) { + this.configuration = configuration; + } + + public void unsetBindingConfiguration(BindingConfiguration configuration) { + this.configuration = null; + } + + private Long getTimeToLive() { + return configuration.getTimeToLive(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/units/CompositeUnitRegistry.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/units/CompositeUnitRegistry.java index 6dd4fc8..aae27f3 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/units/CompositeUnitRegistry.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/units/CompositeUnitRegistry.java @@ -1,100 +1,100 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.internal.units; - -import java.util.Arrays; -import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Stream; - -import javax.measure.Quantity; -import javax.measure.Unit; - -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.wmbus.UnitRegistry; -import org.openmuc.jmbus.DlmsUnit; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Reference; -import org.osgi.service.component.annotations.ReferenceCardinality; - -/** - * An unit registry intended to aggregate several other registers to mask actual lookup operation. - * - * By default this registry is started up with {@link UnitsRegistry} which covers standard DLMS-SI/Imperial - * units known to - * framework. However some DLMS units are not supported and very specific to narrow fields which might not be added any - * time soon. For this reason we leave an extensions for future cases if there is a device we desperately want, but its - * dlms measurements units are not supported. - * - * @author Łukasz Dywicki - Initial contribution. - */ -@Component(property = { "composite=true" }) -public class CompositeUnitRegistry implements UnitRegistry { - - private final Set registers = new LinkedHashSet<>(); - - public CompositeUnitRegistry() { - this(new UnitsRegistry()); - } - - CompositeUnitRegistry(UnitRegistry... registers) { - this(Arrays.asList(registers)); - } - - CompositeUnitRegistry(Collection initial) { - this.registers.addAll(initial); - } - - @Override - public Optional> lookup(DlmsUnit wmbusType) { - return registers.stream() // - .flatMap(registry -> get(registry, wmbusType)) // - .findFirst(); - } - - @Override - public Optional>> quantity(@Nullable DlmsUnit wmbusType) { - return registers.stream() // - .flatMap(registry -> getQuantity(registry, wmbusType)) // - .findFirst(); - } - - private Stream> get(UnitRegistry registry, DlmsUnit wmbusType) { - Optional> lookup = registry.lookup(wmbusType); - - if (lookup.isPresent()) { - return Stream.of(lookup.get()); - } - return Stream.empty(); - } - - private Stream>> getQuantity(UnitRegistry registry, @Nullable DlmsUnit wmbusType) { - Optional>> lookup = registry.quantity(wmbusType); - - if (lookup.isPresent()) { - return Stream.of(lookup.get()); - } - return Stream.empty(); - } - - @Reference(cardinality = ReferenceCardinality.MULTIPLE, target = "(!(composite=true))") - protected void setUnitRegistry(UnitRegistry registry) { - this.registers.add(registry); - } - - protected void unsetUnitRegistry(UnitRegistry registry) { - this.registers.remove(registry); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.internal.units; + +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; + +import javax.measure.Quantity; +import javax.measure.Unit; + +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.wmbus.UnitRegistry; +import org.openmuc.jmbus.DlmsUnit; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; + +/** + * An unit registry intended to aggregate several other registers to mask actual lookup operation. + * + * By default this registry is started up with {@link UnitsRegistry} which covers standard DLMS-SI/Imperial + * units known to + * framework. However some DLMS units are not supported and very specific to narrow fields which might not be added any + * time soon. For this reason we leave an extensions for future cases if there is a device we desperately want, but its + * dlms measurements units are not supported. + * + * @author Łukasz Dywicki - Initial contribution. + */ +@Component(property = { "composite=true" }) +public class CompositeUnitRegistry implements UnitRegistry { + + private final Set registers = new LinkedHashSet<>(); + + public CompositeUnitRegistry() { + this(new UnitsRegistry()); + } + + CompositeUnitRegistry(UnitRegistry... registers) { + this(Arrays.asList(registers)); + } + + CompositeUnitRegistry(Collection initial) { + this.registers.addAll(initial); + } + + @Override + public Optional> lookup(DlmsUnit wmbusType) { + return registers.stream() // + .flatMap(registry -> get(registry, wmbusType)) // + .findFirst(); + } + + @Override + public Optional>> quantity(@Nullable DlmsUnit wmbusType) { + return registers.stream() // + .flatMap(registry -> getQuantity(registry, wmbusType)) // + .findFirst(); + } + + private Stream> get(UnitRegistry registry, DlmsUnit wmbusType) { + Optional> lookup = registry.lookup(wmbusType); + + if (lookup.isPresent()) { + return Stream.of(lookup.get()); + } + return Stream.empty(); + } + + private Stream>> getQuantity(UnitRegistry registry, @Nullable DlmsUnit wmbusType) { + Optional>> lookup = registry.quantity(wmbusType); + + if (lookup.isPresent()) { + return Stream.of(lookup.get()); + } + return Stream.empty(); + } + + @Reference(cardinality = ReferenceCardinality.MULTIPLE, target = "(!(composite=true))") + protected void setUnitRegistry(UnitRegistry registry) { + this.registers.add(registry); + } + + protected void unsetUnitRegistry(UnitRegistry registry) { + this.registers.remove(registry); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/units/UnitsRegistry.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/units/UnitsRegistry.java index 3c91437..ce4de1c 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/units/UnitsRegistry.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/binding/wmbus/internal/units/UnitsRegistry.java @@ -1,361 +1,361 @@ -/** - * Copyright (c) 2010-2021 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.wmbus.internal.units; - -import java.util.Optional; - -import javax.measure.Quantity; -import javax.measure.Unit; -import javax.measure.quantity.Angle; -import javax.measure.quantity.Dimensionless; -import javax.measure.quantity.ElectricCapacitance; -import javax.measure.quantity.ElectricCharge; -import javax.measure.quantity.ElectricCurrent; -import javax.measure.quantity.ElectricInductance; -import javax.measure.quantity.ElectricPotential; -import javax.measure.quantity.ElectricResistance; -import javax.measure.quantity.Energy; -import javax.measure.quantity.Force; -import javax.measure.quantity.Frequency; -import javax.measure.quantity.Length; -import javax.measure.quantity.MagneticFlux; -import javax.measure.quantity.MagneticFluxDensity; -import javax.measure.quantity.Mass; -import javax.measure.quantity.Power; -import javax.measure.quantity.Pressure; -import javax.measure.quantity.Speed; -import javax.measure.quantity.Temperature; -import javax.measure.quantity.Time; -import javax.measure.quantity.Volume; - -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.wmbus.UnitRegistry; -import org.openhab.core.library.unit.ImperialUnits; -import org.openhab.core.library.unit.SIUnits; -import org.openhab.core.library.unit.Units; -import org.openmuc.jmbus.DlmsUnit; - -/** - * Lookup table between wmbus and smart home units which contains default mapping of units. - * - * Since there are many units which are not covered by Eclipse SmartHome there are empty case - * statements. These are left for future to gets filled in once support for them is present. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public class UnitsRegistry implements UnitRegistry { - - @Override - public Optional> lookup(DlmsUnit wmbusType) { - if (wmbusType == null) { - return Optional.empty(); - } - - switch (wmbusType) { - case AMPERE: - return Optional.of(Units.AMPERE); - case AMPERE_HOUR: - break; - case AMPERE_PER_METRE: - break; - case AMPERE_SQUARED_HOURS: - break; - case AMPERE_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE: - break; - case APPARENT_ENERGY_METER_CONSTANT_OR_PULSE_VALUE: - break; - case BAR: - // Not present in ESH 0.9 / 0.10.0.oh230 - // return Optional.of(Units.BAR); - break; - case CALORIFIC_VALUE: - break; - case COULOMB: - return Optional.of(Units.COULOMB); - case COUNT: - break; - case CUBIC_FEET: - return Optional.of(ImperialUnits.CUBIC_FOOT); - case CUBIC_METRE: - case CUBIC_METRE_CORRECTED: - return Optional.of(SIUnits.CUBIC_METRE); - case CUBIC_METRE_PER_DAY: - case CUBIC_METRE_PER_DAY_CORRECTED: - case CUBIC_METRE_PER_HOUR: - case CUBIC_METRE_PER_HOUR_CORRECTED: - case CUBIC_METRE_PER_MINUTE: - case CUBIC_METRE_PER_SECOND: - // there is no support for VolumetricFlowRate yet. - return Optional.empty(); - case CURRENCY: - break; - case DAY: - return Optional.of(Units.DAY); - case DEGREE: - break; - case DEGREE_CELSIUS: - return Optional.of(SIUnits.CELSIUS); - case DEGREE_FAHRENHEIT: - break; - case ENERGY_PER_VOLUME: - break; - case FARAD: - return Optional.of(Units.FARAD); - case HENRY: - return Optional.of(Units.HENRY); - case HERTZ: - return Optional.of(Units.HERTZ); - case HOUR: - return Optional.of(Units.HOUR); - case JOULE: - return Optional.of(Units.JOULE); - case JOULE_PER_HOUR: - break; - case KELVIN: - return Optional.of(Units.KELVIN); - case KILOGRAM: - return Optional.of(SIUnits.KILOGRAM); - case KILOGRAM_PER_HOUR: - break; - case KILOGRAM_PER_SECOND: - break; - case LITRE: - return Optional.of(Units.LITRE); - case MASS_DENSITY: - break; - case METER_CONSTANT_OR_PULSE_VALUE: - break; - case METRE: - return Optional.of(SIUnits.METRE); - case METRE_PER_SECOND: - return Optional.of(Units.METRE_PER_SECOND); - case MIN: - return Optional.of(Units.MINUTE); - case MOLE_PERCENT: - break; - case MONTH: - return Optional.of(Units.FARAD); - case NEWTON: - return Optional.of(Units.NEWTON); - case NEWTONMETER: - break; - case OHM: - return Optional.of(Units.OHM); - case OHM_METRE: - break; - case OTHER_UNIT: - break; - case PASCAL: - return Optional.of(SIUnits.PASCAL); - case PASCAL_SECOND: - break; - case PERCENTAGE: - break; - case REACTIVE_ENERGY_METER_CONSTANT_OR_PULSE_VALUE: - break; - case RESERVED: - break; - case SECOND: - return Optional.of(Units.SECOND); - case SIGNAL_STRENGTH: - break; - case SPECIFIC_ENERGY: - break; - case TESLA: - return Optional.of(Units.TESLA); - case US_GALLON: - break; - case US_GALLON_PER_HOUR: - break; - case US_GALLON_PER_MINUTE: - break; - case VAR: - break; - case VAR_HOUR: - break; - case VOLT: - return Optional.of(Units.VOLT); - case VOLT_AMPERE: - break; - case VOLT_AMPERE_HOUR: - break; - case VOLT_PER_METRE: - break; - case VOLT_SQUARED_HOURS: - break; - case VOLT_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE: - break; - case WATT: - return Optional.of(Units.WATT); - case WATT_HOUR: - return Optional.of(Units.WATT_HOUR); - case WEBER: - return Optional.of(Units.WEBER); - case WEEK: - return Optional.of(Units.WEEK); - case YEAR: - return Optional.of(Units.YEAR); - default: - break; - } - - return Optional.empty(); - } - - @Override - public Optional>> quantity(@Nullable DlmsUnit wmbusType) { - - if (wmbusType == null) { - return Optional.empty(); - } - - switch (wmbusType) { - case AMPERE: - return Optional.of(ElectricCurrent.class); - case AMPERE_HOUR: - break; - case AMPERE_PER_METRE: - break; - case AMPERE_SQUARED_HOURS: - break; - case AMPERE_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE: - break; - case APPARENT_ENERGY_METER_CONSTANT_OR_PULSE_VALUE: - break; - case BAR: - return Optional.of(Pressure.class); - case CALORIFIC_VALUE: - break; - case COULOMB: - return Optional.of(ElectricCharge.class); - case COUNT: - break; - case CUBIC_FEET: - case CUBIC_METRE: - case CUBIC_METRE_CORRECTED: - return Optional.of(Volume.class); - case CUBIC_METRE_PER_DAY: - case CUBIC_METRE_PER_DAY_CORRECTED: - case CUBIC_METRE_PER_HOUR: - case CUBIC_METRE_PER_HOUR_CORRECTED: - case CUBIC_METRE_PER_MINUTE: - case CUBIC_METRE_PER_SECOND: - // VolumetricFlow - return Optional.empty(); - case CURRENCY: - break; - case DEGREE: - return Optional.of(Angle.class); - case DEGREE_CELSIUS: - case DEGREE_FAHRENHEIT: - case KELVIN: - return Optional.of(Temperature.class); - case ENERGY_PER_VOLUME: - break; - case FARAD: - return Optional.of(ElectricCapacitance.class); - case HENRY: - return Optional.of(ElectricInductance.class); - case HERTZ: - return Optional.of(Frequency.class); - case JOULE: - return Optional.of(Energy.class); - case JOULE_PER_HOUR: - break; - case KILOGRAM: - return Optional.of(Mass.class); - case KILOGRAM_PER_HOUR: - return Optional.of(Speed.class); - case KILOGRAM_PER_SECOND: - break; - case LITRE: - return Optional.of(Volume.class); - case MASS_DENSITY: - return Optional.empty(); - case METER_CONSTANT_OR_PULSE_VALUE: - break; - case METRE: - return Optional.of(Length.class); - case METRE_PER_SECOND: - return Optional.of(Speed.class); - case MOLE_PERCENT: - break; - case NEWTON: - return Optional.of(Force.class); - case NEWTONMETER: - break; - case OHM: - return Optional.of(ElectricResistance.class); - case OHM_METRE: - break; - case OTHER_UNIT: - break; - case PASCAL: - return Optional.of(Pressure.class); - case PASCAL_SECOND: - break; - case PERCENTAGE: - return Optional.of(Dimensionless.class); - case REACTIVE_ENERGY_METER_CONSTANT_OR_PULSE_VALUE: - break; - case RESERVED: - break; - case SIGNAL_STRENGTH: - break; - case SPECIFIC_ENERGY: - break; - case TESLA: - return Optional.of(MagneticFluxDensity.class); - case US_GALLON: - break; - case US_GALLON_PER_HOUR: - case US_GALLON_PER_MINUTE: - // VolumetricFlow - break; - case VAR: - break; - case VAR_HOUR: - break; - case VOLT: - return Optional.of(ElectricPotential.class); - case VOLT_AMPERE: - break; - case VOLT_AMPERE_HOUR: - break; - case VOLT_PER_METRE: - break; - case VOLT_SQUARED_HOURS: - break; - case VOLT_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE: - break; - case WATT: - return Optional.of(Power.class); - case WATT_HOUR: - return Optional.of(Energy.class); - case WEBER: - return Optional.of(MagneticFlux.class); - case SECOND: - case MIN: - case HOUR: - case DAY: - case WEEK: - case MONTH: - case YEAR: - return Optional.of(Time.class); - default: - break; - } - - return Optional.empty(); - } -} +/** + * Copyright (c) 2010-2021 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.wmbus.internal.units; + +import java.util.Optional; + +import javax.measure.Quantity; +import javax.measure.Unit; +import javax.measure.quantity.Angle; +import javax.measure.quantity.Dimensionless; +import javax.measure.quantity.ElectricCapacitance; +import javax.measure.quantity.ElectricCharge; +import javax.measure.quantity.ElectricCurrent; +import javax.measure.quantity.ElectricInductance; +import javax.measure.quantity.ElectricPotential; +import javax.measure.quantity.ElectricResistance; +import javax.measure.quantity.Energy; +import javax.measure.quantity.Force; +import javax.measure.quantity.Frequency; +import javax.measure.quantity.Length; +import javax.measure.quantity.MagneticFlux; +import javax.measure.quantity.MagneticFluxDensity; +import javax.measure.quantity.Mass; +import javax.measure.quantity.Power; +import javax.measure.quantity.Pressure; +import javax.measure.quantity.Speed; +import javax.measure.quantity.Temperature; +import javax.measure.quantity.Time; +import javax.measure.quantity.Volume; + +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.wmbus.UnitRegistry; +import org.openhab.core.library.unit.ImperialUnits; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.library.unit.Units; +import org.openmuc.jmbus.DlmsUnit; + +/** + * Lookup table between wmbus and smart home units which contains default mapping of units. + * + * Since there are many units which are not covered by Eclipse SmartHome there are empty case + * statements. These are left for future to gets filled in once support for them is present. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public class UnitsRegistry implements UnitRegistry { + + @Override + public Optional> lookup(DlmsUnit wmbusType) { + if (wmbusType == null) { + return Optional.empty(); + } + + switch (wmbusType) { + case AMPERE: + return Optional.of(Units.AMPERE); + case AMPERE_HOUR: + break; + case AMPERE_PER_METRE: + break; + case AMPERE_SQUARED_HOURS: + break; + case AMPERE_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE: + break; + case APPARENT_ENERGY_METER_CONSTANT_OR_PULSE_VALUE: + break; + case BAR: + // Not present in ESH 0.9 / 0.10.0.oh230 + // return Optional.of(Units.BAR); + break; + case CALORIFIC_VALUE: + break; + case COULOMB: + return Optional.of(Units.COULOMB); + case COUNT: + break; + case CUBIC_FEET: + return Optional.of(ImperialUnits.CUBIC_FOOT); + case CUBIC_METRE: + case CUBIC_METRE_CORRECTED: + return Optional.of(SIUnits.CUBIC_METRE); + case CUBIC_METRE_PER_DAY: + case CUBIC_METRE_PER_DAY_CORRECTED: + case CUBIC_METRE_PER_HOUR: + case CUBIC_METRE_PER_HOUR_CORRECTED: + case CUBIC_METRE_PER_MINUTE: + case CUBIC_METRE_PER_SECOND: + // there is no support for VolumetricFlowRate yet. + return Optional.empty(); + case CURRENCY: + break; + case DAY: + return Optional.of(Units.DAY); + case DEGREE: + break; + case DEGREE_CELSIUS: + return Optional.of(SIUnits.CELSIUS); + case DEGREE_FAHRENHEIT: + break; + case ENERGY_PER_VOLUME: + break; + case FARAD: + return Optional.of(Units.FARAD); + case HENRY: + return Optional.of(Units.HENRY); + case HERTZ: + return Optional.of(Units.HERTZ); + case HOUR: + return Optional.of(Units.HOUR); + case JOULE: + return Optional.of(Units.JOULE); + case JOULE_PER_HOUR: + break; + case KELVIN: + return Optional.of(Units.KELVIN); + case KILOGRAM: + return Optional.of(SIUnits.KILOGRAM); + case KILOGRAM_PER_HOUR: + break; + case KILOGRAM_PER_SECOND: + break; + case LITRE: + return Optional.of(Units.LITRE); + case MASS_DENSITY: + break; + case METER_CONSTANT_OR_PULSE_VALUE: + break; + case METRE: + return Optional.of(SIUnits.METRE); + case METRE_PER_SECOND: + return Optional.of(Units.METRE_PER_SECOND); + case MIN: + return Optional.of(Units.MINUTE); + case MOLE_PERCENT: + break; + case MONTH: + return Optional.of(Units.FARAD); + case NEWTON: + return Optional.of(Units.NEWTON); + case NEWTONMETER: + break; + case OHM: + return Optional.of(Units.OHM); + case OHM_METRE: + break; + case OTHER_UNIT: + break; + case PASCAL: + return Optional.of(SIUnits.PASCAL); + case PASCAL_SECOND: + break; + case PERCENTAGE: + break; + case REACTIVE_ENERGY_METER_CONSTANT_OR_PULSE_VALUE: + break; + case RESERVED: + break; + case SECOND: + return Optional.of(Units.SECOND); + case SIGNAL_STRENGTH: + break; + case SPECIFIC_ENERGY: + break; + case TESLA: + return Optional.of(Units.TESLA); + case US_GALLON: + break; + case US_GALLON_PER_HOUR: + break; + case US_GALLON_PER_MINUTE: + break; + case VAR: + break; + case VAR_HOUR: + break; + case VOLT: + return Optional.of(Units.VOLT); + case VOLT_AMPERE: + break; + case VOLT_AMPERE_HOUR: + break; + case VOLT_PER_METRE: + break; + case VOLT_SQUARED_HOURS: + break; + case VOLT_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE: + break; + case WATT: + return Optional.of(Units.WATT); + case WATT_HOUR: + return Optional.of(Units.WATT_HOUR); + case WEBER: + return Optional.of(Units.WEBER); + case WEEK: + return Optional.of(Units.WEEK); + case YEAR: + return Optional.of(Units.YEAR); + default: + break; + } + + return Optional.empty(); + } + + @Override + public Optional>> quantity(@Nullable DlmsUnit wmbusType) { + + if (wmbusType == null) { + return Optional.empty(); + } + + switch (wmbusType) { + case AMPERE: + return Optional.of(ElectricCurrent.class); + case AMPERE_HOUR: + break; + case AMPERE_PER_METRE: + break; + case AMPERE_SQUARED_HOURS: + break; + case AMPERE_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE: + break; + case APPARENT_ENERGY_METER_CONSTANT_OR_PULSE_VALUE: + break; + case BAR: + return Optional.of(Pressure.class); + case CALORIFIC_VALUE: + break; + case COULOMB: + return Optional.of(ElectricCharge.class); + case COUNT: + break; + case CUBIC_FEET: + case CUBIC_METRE: + case CUBIC_METRE_CORRECTED: + return Optional.of(Volume.class); + case CUBIC_METRE_PER_DAY: + case CUBIC_METRE_PER_DAY_CORRECTED: + case CUBIC_METRE_PER_HOUR: + case CUBIC_METRE_PER_HOUR_CORRECTED: + case CUBIC_METRE_PER_MINUTE: + case CUBIC_METRE_PER_SECOND: + // VolumetricFlow + return Optional.empty(); + case CURRENCY: + break; + case DEGREE: + return Optional.of(Angle.class); + case DEGREE_CELSIUS: + case DEGREE_FAHRENHEIT: + case KELVIN: + return Optional.of(Temperature.class); + case ENERGY_PER_VOLUME: + break; + case FARAD: + return Optional.of(ElectricCapacitance.class); + case HENRY: + return Optional.of(ElectricInductance.class); + case HERTZ: + return Optional.of(Frequency.class); + case JOULE: + return Optional.of(Energy.class); + case JOULE_PER_HOUR: + break; + case KILOGRAM: + return Optional.of(Mass.class); + case KILOGRAM_PER_HOUR: + return Optional.of(Speed.class); + case KILOGRAM_PER_SECOND: + break; + case LITRE: + return Optional.of(Volume.class); + case MASS_DENSITY: + return Optional.empty(); + case METER_CONSTANT_OR_PULSE_VALUE: + break; + case METRE: + return Optional.of(Length.class); + case METRE_PER_SECOND: + return Optional.of(Speed.class); + case MOLE_PERCENT: + break; + case NEWTON: + return Optional.of(Force.class); + case NEWTONMETER: + break; + case OHM: + return Optional.of(ElectricResistance.class); + case OHM_METRE: + break; + case OTHER_UNIT: + break; + case PASCAL: + return Optional.of(Pressure.class); + case PASCAL_SECOND: + break; + case PERCENTAGE: + return Optional.of(Dimensionless.class); + case REACTIVE_ENERGY_METER_CONSTANT_OR_PULSE_VALUE: + break; + case RESERVED: + break; + case SIGNAL_STRENGTH: + break; + case SPECIFIC_ENERGY: + break; + case TESLA: + return Optional.of(MagneticFluxDensity.class); + case US_GALLON: + break; + case US_GALLON_PER_HOUR: + case US_GALLON_PER_MINUTE: + // VolumetricFlow + break; + case VAR: + break; + case VAR_HOUR: + break; + case VOLT: + return Optional.of(ElectricPotential.class); + case VOLT_AMPERE: + break; + case VOLT_AMPERE_HOUR: + break; + case VOLT_PER_METRE: + break; + case VOLT_SQUARED_HOURS: + break; + case VOLT_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE: + break; + case WATT: + return Optional.of(Power.class); + case WATT_HOUR: + return Optional.of(Energy.class); + case WEBER: + return Optional.of(MagneticFlux.class); + case SECOND: + case MIN: + case HOUR: + case DAY: + case WEEK: + case MONTH: + case YEAR: + return Optional.of(Time.class); + default: + break; + } + + return Optional.empty(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/FilteredKeyStorage.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/FilteredKeyStorage.java index e6fae27..e12d792 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/FilteredKeyStorage.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/FilteredKeyStorage.java @@ -1,66 +1,66 @@ -/** - * Copyright (c) 2010-2021 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.io.transport.mbus.wireless; - -import java.util.Arrays; -import java.util.Collections; -import java.util.Map; -import java.util.Optional; - -import org.openhab.binding.wmbus.WMBusBindingConstants; -import org.openhab.core.thing.Thing; -import org.openhab.core.util.HexUtils; -import org.openmuc.jmbus.SecondaryAddress; - -/** - * Filtering implementation of {@link KeyStorage} - permits operation on single address and no other. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public class FilteredKeyStorage implements KeyStorage { - - private final KeyStorage delegate; - private final byte[] address; - - public FilteredKeyStorage(KeyStorage delegate, Thing thing) { - this.delegate = delegate; - this.address = Optional.ofNullable(thing.getConfiguration()) - .map(cfg -> cfg.get(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS)).map(Object::toString) - .map(HexUtils::hexToBytes).orElse(new byte[0]); - } - - @Override - public Optional lookupKey(byte[] address) { - if (Arrays.equals(this.address, address)) { - return delegate.lookupKey(address); - } - return Optional.empty(); - } - - @Override - public void registerKey(byte[] address, byte[] key) { - if (Arrays.equals(this.address, address)) { - delegate.registerKey(address, key); - } - } - - @Override - public Map toMap() { - return lookupKey(address).map(key -> Collections.singletonMap(createKey(address), key)) - .orElse(Collections.emptyMap()); - } - - private SecondaryAddress createKey(byte[] address) { - return SecondaryAddress.newFromWMBusLlHeader(address, 0); - } -} +/** + * Copyright (c) 2010-2021 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.io.transport.mbus.wireless; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; + +import org.openhab.binding.wmbus.WMBusBindingConstants; +import org.openhab.core.thing.Thing; +import org.openhab.core.util.HexUtils; +import org.openmuc.jmbus.SecondaryAddress; + +/** + * Filtering implementation of {@link KeyStorage} - permits operation on single address and no other. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public class FilteredKeyStorage implements KeyStorage { + + private final KeyStorage delegate; + private final byte[] address; + + public FilteredKeyStorage(KeyStorage delegate, Thing thing) { + this.delegate = delegate; + this.address = Optional.ofNullable(thing.getConfiguration()) + .map(cfg -> cfg.get(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS)).map(Object::toString) + .map(HexUtils::hexToBytes).orElse(new byte[0]); + } + + @Override + public Optional lookupKey(byte[] address) { + if (Arrays.equals(this.address, address)) { + return delegate.lookupKey(address); + } + return Optional.empty(); + } + + @Override + public void registerKey(byte[] address, byte[] key) { + if (Arrays.equals(this.address, address)) { + delegate.registerKey(address, key); + } + } + + @Override + public Map toMap() { + return lookupKey(address).map(key -> Collections.singletonMap(createKey(address), key)) + .orElse(Collections.emptyMap()); + } + + private SecondaryAddress createKey(byte[] address) { + return SecondaryAddress.newFromWMBusLlHeader(address, 0); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/KeyStorage.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/KeyStorage.java index 64a261f..fe15c62 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/KeyStorage.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/KeyStorage.java @@ -1,33 +1,33 @@ -/** - * Copyright (c) 2010-2021 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.io.transport.mbus.wireless; - -import java.util.Map; -import java.util.Optional; - -import org.openmuc.jmbus.SecondaryAddress; - -/** - * A simple abstraction over wmbus transport layer which allows to work with message encryption keys without knowing - * anything at all about jmbus library, its dependencies and wmbus in general. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public interface KeyStorage { - - Optional lookupKey(byte[] address); - - void registerKey(byte[] address, byte[] key); - - Map toMap(); -} +/** + * Copyright (c) 2010-2021 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.io.transport.mbus.wireless; + +import java.util.Map; +import java.util.Optional; + +import org.openmuc.jmbus.SecondaryAddress; + +/** + * A simple abstraction over wmbus transport layer which allows to work with message encryption keys without knowing + * anything at all about jmbus library, its dependencies and wmbus in general. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public interface KeyStorage { + + Optional lookupKey(byte[] address); + + void registerKey(byte[] address, byte[] key); + + Map toMap(); +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/MapKeyStorage.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/MapKeyStorage.java index 04a1572..38f8022 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/MapKeyStorage.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/MapKeyStorage.java @@ -1,51 +1,51 @@ -/** - * Copyright (c) 2010-2021 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.io.transport.mbus.wireless; - -import java.util.Collections; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; - -import org.openmuc.jmbus.SecondaryAddress; -import org.osgi.service.component.annotations.Component; - -/** - * Simplistic implementation of {@link KeyStorage} backed by Map. - * - * @author Łukasz Dywicki - Initial contribution. - */ -@Component -public class MapKeyStorage implements KeyStorage { - - private final Map keyMap = new ConcurrentHashMap<>(); - - @Override - public Optional lookupKey(byte[] address) { - return Optional.ofNullable(keyMap.get(createKey(address))); - } - - @Override - public void registerKey(byte[] address, byte[] key) { - keyMap.put(createKey(address), key); - } - - @Override - public Map toMap() { - return Collections.unmodifiableMap(keyMap); - } - - private SecondaryAddress createKey(byte[] address) { - return SecondaryAddress.newFromWMBusLlHeader(address, 0); - } -} +/** + * Copyright (c) 2010-2021 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.io.transport.mbus.wireless; + +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +import org.openmuc.jmbus.SecondaryAddress; +import org.osgi.service.component.annotations.Component; + +/** + * Simplistic implementation of {@link KeyStorage} backed by Map. + * + * @author Łukasz Dywicki - Initial contribution. + */ +@Component +public class MapKeyStorage implements KeyStorage { + + private final Map keyMap = new ConcurrentHashMap<>(); + + @Override + public Optional lookupKey(byte[] address) { + return Optional.ofNullable(keyMap.get(createKey(address))); + } + + @Override + public void registerKey(byte[] address, byte[] key) { + keyMap.put(createKey(address), key); + } + + @Override + public Map toMap() { + return Collections.unmodifiableMap(keyMap); + } + + private SecondaryAddress createKey(byte[] address) { + return SecondaryAddress.newFromWMBusLlHeader(address, 0); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/AesCrypt.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/AesCrypt.java index 226fc43..658b7fc 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/AesCrypt.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/AesCrypt.java @@ -1,68 +1,68 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus; - -import java.security.GeneralSecurityException; -import java.security.NoSuchAlgorithmException; -import java.security.spec.AlgorithmParameterSpec; -import java.util.Arrays; - -import javax.crypto.Cipher; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; - -class AesCrypt { - private final byte[] key; - private final byte[] iv; - - protected final SecretKeySpec skeySpec; - protected final AlgorithmParameterSpec paramSpec; - protected Cipher cipher; - - public static AesCrypt newAesCrypt(byte[] key, byte[] iv) throws DecodingException { - return new AesCrypt(key, iv, "AES/CBC/NoPadding"); - } - - public static AesCrypt newAesCtrCrypt(byte[] key, byte[] iv) throws DecodingException { - return new AesCrypt(key, iv, "AES/CTR/NoPadding"); - } - - private AesCrypt(byte[] key, byte[] iv, String cipherName) throws DecodingException { - try { - this.cipher = Cipher.getInstance(cipherName); - } catch (NoSuchPaddingException | NoSuchAlgorithmException e) { - throw new DecodingException(e); - } - - this.key = Arrays.copyOf(key, key.length); - this.iv = Arrays.copyOf(iv, iv.length); - this.skeySpec = new SecretKeySpec(this.key, "AES"); - this.paramSpec = new IvParameterSpec(this.iv); - } - - public byte[] encrypt(byte[] rawData, int length) throws GeneralSecurityException { - byte[] tempData = Arrays.copyOf(rawData, length); - - this.cipher.init(Cipher.ENCRYPT_MODE, skeySpec, paramSpec); - return this.cipher.doFinal(tempData); - } - - public byte[] decrypt(byte[] rawData, int length) throws DecodingException { - byte[] encrypted = rawData; - - if (length != 0) { - encrypted = Arrays.copyOf(rawData, length); - } - - try { - cipher.init(Cipher.DECRYPT_MODE, skeySpec, paramSpec); - return cipher.doFinal(encrypted); - } catch (GeneralSecurityException e) { - throw new DecodingException(e); - } - } -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus; + +import java.security.GeneralSecurityException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.AlgorithmParameterSpec; +import java.util.Arrays; + +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +class AesCrypt { + private final byte[] key; + private final byte[] iv; + + protected final SecretKeySpec skeySpec; + protected final AlgorithmParameterSpec paramSpec; + protected Cipher cipher; + + public static AesCrypt newAesCrypt(byte[] key, byte[] iv) throws DecodingException { + return new AesCrypt(key, iv, "AES/CBC/NoPadding"); + } + + public static AesCrypt newAesCtrCrypt(byte[] key, byte[] iv) throws DecodingException { + return new AesCrypt(key, iv, "AES/CTR/NoPadding"); + } + + private AesCrypt(byte[] key, byte[] iv, String cipherName) throws DecodingException { + try { + this.cipher = Cipher.getInstance(cipherName); + } catch (NoSuchPaddingException | NoSuchAlgorithmException e) { + throw new DecodingException(e); + } + + this.key = Arrays.copyOf(key, key.length); + this.iv = Arrays.copyOf(iv, iv.length); + this.skeySpec = new SecretKeySpec(this.key, "AES"); + this.paramSpec = new IvParameterSpec(this.iv); + } + + public byte[] encrypt(byte[] rawData, int length) throws GeneralSecurityException { + byte[] tempData = Arrays.copyOf(rawData, length); + + this.cipher.init(Cipher.ENCRYPT_MODE, skeySpec, paramSpec); + return this.cipher.doFinal(tempData); + } + + public byte[] decrypt(byte[] rawData, int length) throws DecodingException { + byte[] encrypted = rawData; + + if (length != 0) { + encrypted = Arrays.copyOf(rawData, length); + } + + try { + cipher.init(Cipher.DECRYPT_MODE, skeySpec, paramSpec); + return cipher.doFinal(encrypted); + } catch (GeneralSecurityException e) { + throw new DecodingException(e); + } + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/Bcd.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/Bcd.java index b1316f9..e7691cc 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/Bcd.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/Bcd.java @@ -1,91 +1,91 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus; - -/** - * This class represents a binary-coded decimal (BCD) number as defined by the M-Bus standard. The class provides - * methods to convert the BCD to other types such as double, int or String. - */ -public class Bcd extends Number { - - private static final long serialVersionUID = 790515601507532939L; - private final byte[] value; - - /** - * Constructs a Bcd from the given bytes. The constructed Bcd will use the given byte array for - * internal storage of its value. It is therefore recommended not to change the byte array after construction. - * - * @param bcdBytes - * the byte array to be used for construction of the Bcd. - */ - public Bcd(byte[] bcdBytes) { - this.value = bcdBytes; - } - - public byte[] getBytes() { - return value; - } - - @Override - public String toString() { - byte[] bytes = new byte[value.length * 2]; - int c = 0; - - if ((value[value.length - 1] & 0xf0) == 0xf0) { - bytes[c++] = 0x2d; - } else { - bytes[c++] = (byte) (((value[value.length - 1] >> 4) & 0x0f) + 48); - } - - bytes[c++] = (byte) ((value[value.length - 1] & 0x0f) + 48); - - for (int i = value.length - 2; i >= 0; i--) { - bytes[c++] = (byte) (((value[i] >> 4) & 0x0f) + 48); - bytes[c++] = (byte) ((value[i] & 0x0f) + 48); - } - - return new String(bytes); - } - - @Override - public double doubleValue() { - return longValue(); - } - - @Override - public float floatValue() { - return longValue(); - } - - @Override - public int intValue() { - return (int) longValue(); - } - - @Override - public long longValue() { - long result = 0l; - long factor = 1l; - - for (int i = 0; i < (value.length - 1); i++) { - result += (value[i] & 0x0f) * factor; - factor = factor * 10l; - result += ((value[i] >> 4) & 0x0f) * factor; - factor = factor * 10l; - } - - result += (value[value.length - 1] & 0x0f) * factor; - factor = factor * 10l; - - if ((value[value.length - 1] & 0xf0) == 0xf0) { - result = result * -1; - } else { - result += ((value[value.length - 1] >> 4) & 0x0f) * factor; - } - - return result; - } -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus; + +/** + * This class represents a binary-coded decimal (BCD) number as defined by the M-Bus standard. The class provides + * methods to convert the BCD to other types such as double, int or String. + */ +public class Bcd extends Number { + + private static final long serialVersionUID = 790515601507532939L; + private final byte[] value; + + /** + * Constructs a Bcd from the given bytes. The constructed Bcd will use the given byte array for + * internal storage of its value. It is therefore recommended not to change the byte array after construction. + * + * @param bcdBytes + * the byte array to be used for construction of the Bcd. + */ + public Bcd(byte[] bcdBytes) { + this.value = bcdBytes; + } + + public byte[] getBytes() { + return value; + } + + @Override + public String toString() { + byte[] bytes = new byte[value.length * 2]; + int c = 0; + + if ((value[value.length - 1] & 0xf0) == 0xf0) { + bytes[c++] = 0x2d; + } else { + bytes[c++] = (byte) (((value[value.length - 1] >> 4) & 0x0f) + 48); + } + + bytes[c++] = (byte) ((value[value.length - 1] & 0x0f) + 48); + + for (int i = value.length - 2; i >= 0; i--) { + bytes[c++] = (byte) (((value[i] >> 4) & 0x0f) + 48); + bytes[c++] = (byte) ((value[i] & 0x0f) + 48); + } + + return new String(bytes); + } + + @Override + public double doubleValue() { + return longValue(); + } + + @Override + public float floatValue() { + return longValue(); + } + + @Override + public int intValue() { + return (int) longValue(); + } + + @Override + public long longValue() { + long result = 0l; + long factor = 1l; + + for (int i = 0; i < (value.length - 1); i++) { + result += (value[i] & 0x0f) * factor; + factor = factor * 10l; + result += ((value[i] >> 4) & 0x0f) * factor; + factor = factor * 10l; + } + + result += (value[value.length - 1] & 0x0f) * factor; + factor = factor * 10l; + + if ((value[value.length - 1] & 0xf0) == 0xf0) { + result = result * -1; + } else { + result += ((value[value.length - 1] >> 4) & 0x0f) * factor; + } + + return result; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/CRC16.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/CRC16.java index 379092d..5ff5e73 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/CRC16.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/CRC16.java @@ -1,56 +1,56 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus; - -import java.nio.ByteBuffer; - -/** - * 16 bit cyclic redundancy check implementation. - */ -class CRC16 { - - private static byte[] computeCrc(byte[] bytes, int poly, int initialValue, int xorValue) { - int i; - int crcVal = initialValue; - byte[] crc = new byte[2]; - - for (byte b : bytes) { - for (i = 0x80; i != 0; i >>= 1) { - if ((crcVal & 0x8000) != 0) { - crcVal = (crcVal << 1) ^ poly; - } else { - crcVal = crcVal << 1; - } - if ((b & i) != 0) { - crcVal ^= poly; - } - } - } - - byte[] tmpCrc = ByteBuffer.allocate(4).putInt(Integer.reverseBytes(crcVal & 0xffff ^ xorValue)).array(); - crc[0] = tmpCrc[0]; - crc[1] = tmpCrc[1]; - - return crc; - } - - /** - * Computes the CRC16 according EN13757. - * - * @param bytes - * the data to be checked. - * @return the CRC16 result. - */ - public static byte[] calculateCrc16(byte[] bytes) { - return computeCrc(bytes, 0x3D65, 0x0000, 0xFFFF); - } - - /** - * Do not let this class be instantiated. - */ - private CRC16() { - } -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus; + +import java.nio.ByteBuffer; + +/** + * 16 bit cyclic redundancy check implementation. + */ +class CRC16 { + + private static byte[] computeCrc(byte[] bytes, int poly, int initialValue, int xorValue) { + int i; + int crcVal = initialValue; + byte[] crc = new byte[2]; + + for (byte b : bytes) { + for (i = 0x80; i != 0; i >>= 1) { + if ((crcVal & 0x8000) != 0) { + crcVal = (crcVal << 1) ^ poly; + } else { + crcVal = crcVal << 1; + } + if ((b & i) != 0) { + crcVal ^= poly; + } + } + } + + byte[] tmpCrc = ByteBuffer.allocate(4).putInt(Integer.reverseBytes(crcVal & 0xffff ^ xorValue)).array(); + crc[0] = tmpCrc[0]; + crc[1] = tmpCrc[1]; + + return crc; + } + + /** + * Computes the CRC16 according EN13757. + * + * @param bytes + * the data to be checked. + * @return the CRC16 result. + */ + public static byte[] calculateCrc16(byte[] bytes) { + return computeCrc(bytes, 0x3D65, 0x0000, 0xFFFF); + } + + /** + * Do not let this class be instantiated. + */ + private CRC16() { + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DataRecord.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DataRecord.java index afa4cec..1c6ac65 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DataRecord.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DataRecord.java @@ -1,1381 +1,1381 @@ -/** - * Copyright (c) 2010-2021 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.openmuc.jmbus; - -import static javax.xml.bind.DatatypeConverter.printHexBinary; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; -import java.util.Calendar; - -/** - * Representation of a data record (sometimes called variable data block). - * - * A data record is the basic data entity of the M-Bus application layer. A variable data structure contains a list of - * data records. Each data record represents a single data point. A data record consists of three fields: The data - * information block (DIB), the value information block (VIB) and the data field. - * - * The DIB codes the following parameters: - *

      - *
    • Storage number - a meter can have several storages e.g. to store historical time series data. The storage number - * 0 signals an actual value.
    • - *
    • Function - data can have the following four function types: instantaneous value, max value, min value, value - * during error state.
    • - *
    • Length and coding of the data field.
    • - *
    • Tariff - indicates the tariff number of this data field. The data of tariff 0 is usually the sum of all other - * tariffs.
    • - *
    • Subunit - can be used by a slave to distinguish several subunits of the metering device
    • - *
    - * - * The VIB codes the following parameters: - *
      - *
    • Description - the meaning of the data value (e.g. "Energy", "Volume" etc.)
    • - *
    • Unit - the unit of the data value.
    • - *
    • Multiplier - a factor by which the data value coded in the data field has to be multiplied with. - * getScaledDataValue() returns the result of the data value multiplied with the multiplier.
    • - *
    - * - */ -public class DataRecord { - - /** - * The data value type - * - */ - public enum DataValueType { - LONG, - DOUBLE, - DATE, - STRING, - BCD, - NONE; - } - - /** - * Function coded in the DIB - * - */ - public enum FunctionField { - /** - * instantaneous value - */ - INST_VAL, - /** - * maximum value - */ - MAX_VAL, - /** - * minimum value - */ - MIN_VAL, - /** - * value during error state - */ - ERROR_VAL; - } - - /** - * Data description stored in the VIB - * - */ - public enum Description { - ENERGY, - VOLUME, - MASS, - ON_TIME, - OPERATING_TIME, - POWER, - VOLUME_FLOW, - VOLUME_FLOW_EXT, - MASS_FLOW, - FLOW_TEMPERATURE, - RETURN_TEMPERATURE, - TEMPERATURE_DIFFERENCE, - EXTERNAL_TEMPERATURE, - PRESSURE, - DATE, - DATE_TIME, - VOLTAGE, - CURRENT, - AVERAGING_DURATION, - ACTUALITY_DURATION, - FABRICATION_NO, - MODEL_VERSION, - PARAMETER_SET_ID, - HARDWARE_VERSION, - FIRMWARE_VERSION, - ERROR_FLAGS, - CUSTOMER, - RESERVED, - OPERATING_TIME_BATTERY, - HCA, - REACTIVE_ENERGY, - TEMPERATURE_LIMIT, - MAX_POWER, - REACTIVE_POWER, - REL_HUMIDITY, - FREQUENCY, - PHASE, - EXTENDED_IDENTIFICATION, - ADDRESS, - NOT_SUPPORTED, - MANUFACTURER_SPECIFIC, - FUTURE_VALUE, - USER_DEFINED, - APPARENT_ENERGY, - CUSTOMER_LOCATION, - ACCSESS_CODE_OPERATOR, - ACCSESS_CODE_USER, - PASSWORD, - ACCSESS_CODE_SYSTEM_DEVELOPER, - OTHER_SOFTWARE_VERSION, - ACCSESS_CODE_SYSTEM_OPERATOR, - ERROR_MASK, - SECURITY_KEY, - DIGITAL_INPUT, - BAUDRATE, - DIGITAL_OUTPUT, - RESPONSE_DELAY_TIME, - RETRY, - FIRST_STORAGE_NUMBER_CYCLIC, - REMOTE_CONTROL, - LAST_STORAGE_NUMBER_CYCLIC, - SIZE_STORAGE_BLOCK, - STORAGE_INTERVALL, - TARIF_START, - DURATION_LAST_READOUT, - TIME_POINT, - TARIF_DURATION, - OPERATOR_SPECIFIC_DATA, - TARIF_PERIOD, - NUMBER_STOPS, - LAST_CUMULATION_DURATION, - SPECIAL_SUPPLIER_INFORMATION, - PARAMETER_ACTIVATION_STATE, - CONTROL_SIGNAL, - WEEK_NUMBER, - DAY_OF_WEEK, - REMAINING_BATTERY_LIFE_TIME, - TIME_POINT_DAY_CHANGE, - CUMULATION_COUNTER, - RESET_COUNTER; - } - - // // Data Information Block that contains a DIF and optionally up to 10 DIFEs - private byte[] dib; - // // Value Information Block that contains a VIF and optionally up to 10 VIFEs - private byte[] vib; - - private Object dataValue; - private DataValueType dataValueType; - - // DIB fields: - private FunctionField functionField; - - private long storageNumber; // max is 41 bits - private int tariff; // max 20 bits - private short subunit; // max 10 bits - - // VIB fields: - private Description description; - private String userDefinedDescription; - private int multiplierExponent = 0; - private DlmsUnit unit; - - private boolean dateTypeF = false; - private boolean dateTypeG = false; - - int dataLength; - - byte[] rawData; - - public byte[] getRawData() { - return rawData; - } - - int decode(byte[] buffer, int offset, int length) throws DecodingException { - int i = offset; - - decodeDib(buffer, i); - - int dataField = buffer[i] & 0x0f; - dataLength = dataField; - storageNumber = (buffer[i] & 0x40) >> 6; - - subunit = 0; - tariff = 0; - - int numDife = 0; - while ((buffer[i++] & 0x80) == 0x80) { - subunit += (((buffer[i] & 0x40) >> 6) << numDife); - tariff += ((buffer[i] & 0x30) >> 4) << (numDife * 2); - storageNumber += ((buffer[i] & 0x0f) << ((numDife * 4) + 1)); - numDife++; - } - - multiplierExponent = 0; - - unit = null; - - dib = Arrays.copyOfRange(buffer, offset, i); - - // decode VIB - - int vif = buffer[i++] & 0xff; - - boolean decodeFurtherVifs = false; - - if (vif == 0xfb) { - decodeAlternateExtendedVif(buffer[i]); - if ((buffer[i] & 0x80) == 0x80) { - decodeFurtherVifs = true; - } - i++; - } else if ((vif & 0x7f) == 0x7c) { - i += decodeUserDefinedVif(buffer, i); - if ((vif & 0x80) == 0x80) { - decodeFurtherVifs = true; - } - } else if (vif == 0xfd) { - decodeMainExtendedVif(buffer[i]); - if ((buffer[i] & 0x80) == 0x80) { - decodeFurtherVifs = true; - } - i++; - } else { - decodeMainVif(vif); - if ((vif & 0x80) == 0x80) { - decodeFurtherVifs = true; - } - } - - if (decodeFurtherVifs) { - while ((buffer[i++] & 0x80) == 0x80) { - // TODO these vifes should not be ignored! - } - } - - vib = Arrays.copyOfRange(buffer, offset + dib.length, i); - - switch (dataField) { - case 0x00: - case 0x08: /* no data - selection for readout request */ - dataValue = null; - dataValueType = DataValueType.NONE; - break; - case 0x01: /* INT8 */ - dataValue = Long.valueOf(buffer[i++]); - dataValueType = DataValueType.LONG; - break; - case 0x02: /* INT16 */ - if (dateTypeG) { - int day = (0x1f) & buffer[i]; - int year1 = ((0xe0) & buffer[i++]) >> 5; - int month = (0x0f) & buffer[i]; - int year2 = ((0xf0) & buffer[i++]) >> 1; - int year = (2000 + year1 + year2); - - Calendar calendar = Calendar.getInstance(); - - calendar.set(year, month - 1, day, 0, 0, 0); - - dataValue = calendar.getTime(); - dataValueType = DataValueType.DATE; - } else { - dataValue = Long.valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8)); - dataValueType = DataValueType.LONG; - } - break; - case 0x03: /* INT24 */ - if ((buffer[i + 2] & 0x80) == 0x80) { - // negative - dataValue = Long.valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) - | ((buffer[i++] & 0xff) << 16) | 0xff << 24); - } else { - dataValue = Long - .valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) | ((buffer[i++] & 0xff) << 16)); - } - dataValueType = DataValueType.LONG; - break; - case 0x04: /* INT32 */ - if (dateTypeF) { - int min = (buffer[i++] & 0x3f); - int hour = (buffer[i] & 0x1f); - int yearh = (0x60 & buffer[i++]) >> 5; - int day = (buffer[i] & 0x1f); - int year1 = (0xe0 & buffer[i++]) >> 5; - int mon = (buffer[i] & 0x0f); - int year2 = (0xf0 & buffer[i++]) >> 1; - - if (yearh == 0) { - yearh = 1; - } - - int year = 1900 + 100 * yearh + year1 + year2; - - Calendar calendar = Calendar.getInstance(); - - calendar.set(year, mon - 1, day, hour, min, 0); - - dataValue = calendar.getTime(); - dataValueType = DataValueType.DATE; - } else { - dataValue = Long.valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) - | ((buffer[i++] & 0xff) << 16) | ((buffer[i++] & 0xff) << 24)); - dataValueType = DataValueType.LONG; - } - break; - case 0x05: /* FLOAT32 */ - Float doubleDatavalue = ByteBuffer.wrap(buffer, i, 4).order(ByteOrder.LITTLE_ENDIAN).getFloat(); - i += 4; - dataValue = Double.valueOf(doubleDatavalue); - dataValueType = DataValueType.DOUBLE; - break; - case 0x06: /* INT48 */ - if ((buffer[i + 5] & 0x80) == 0x80) { - // negative - dataValue = Long - .valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) | ((buffer[i++] & 0xff) << 16) - | ((buffer[i++] & 0xff) << 24) | (((long) buffer[i++] & 0xff) << 32) - | (((long) buffer[i++] & 0xff) << 40) | (0xffl << 48) | (0xffl << 56)); - } else { - dataValue = Long.valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) - | ((buffer[i++] & 0xff) << 16) | ((buffer[i++] & 0xff) << 24) - | (((long) buffer[i++] & 0xff) << 32) | (((long) buffer[i++] & 0xff) << 40)); - } - dataValueType = DataValueType.LONG; - break; - case 0x07: /* INT64 */ - dataValue = Long.valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) - | ((buffer[i++] & 0xff) << 16) | ((buffer[i++] & 0xff) << 24) - | (((long) buffer[i++] & 0xff) << 32) | (((long) buffer[i++] & 0xff) << 40) - | (((long) buffer[i++] & 0xff) << 48) | (((long) buffer[i++] & 0xff) << 56)); - dataValueType = DataValueType.LONG; - break; - case 0x09: - i = setBCD(buffer, i, 1); - break; - case 0x0a: - i = setBCD(buffer, i, 2); - break; - case 0x0b: - i = setBCD(buffer, i, 3); - break; - case 0x0c: - i = setBCD(buffer, i, 4); - break; - case 0x0e: - i = setBCD(buffer, i, 6); - break; - case 0x0d: - - int variableLength = buffer[i++] & 0xff; - int dataLength0x0d; - - if (variableLength < 0xc0) { - dataLength0x0d = variableLength; - } else if ((variableLength >= 0xc0) && (variableLength <= 0xc9)) { - dataLength0x0d = 2 * (variableLength - 0xc0); - } else if ((variableLength >= 0xd0) && (variableLength <= 0xd9)) { - dataLength0x0d = 2 * (variableLength - 0xd0); - } else if ((variableLength >= 0xe0) && (variableLength <= 0xef)) { - dataLength0x0d = variableLength - 0xe0; - } else if (variableLength == 0xf8) { - dataLength0x0d = 4; - } else { - throw new DecodingException("Unsupported LVAR Field: " + variableLength); - } - - // TODO check this: - // if (variableLength >= 0xc0) { - // throw new DecodingException("Variable length (LVAR) field >= 0xc0: " + variableLength); - // } - - byte[] rawData = new byte[dataLength0x0d]; - - for (int j = 0; j < dataLength0x0d; j++) { - rawData[j] = buffer[i + dataLength0x0d - 1 - j]; - } - i += dataLength0x0d; - - this.rawData = rawData; - dataValue = new String(rawData); - dataValueType = DataValueType.STRING; - break; - default: - String msg = String.format("Unknown Data Field in DIF: %02X.", dataField); - throw new DecodingException(msg); - } - - return i; - } - - private int setBCD(byte[] buffer, int i, int j) { - dataValue = new Bcd(Arrays.copyOfRange(buffer, i, i + j)); - dataValueType = DataValueType.BCD; - return i + j; - } - - private void decodeDib(byte[] buffer, int i) { - int ff = ((buffer[i] & 0x30) >> 4); - switch (ff) { - case 0: - functionField = FunctionField.INST_VAL; - break; - case 1: - functionField = FunctionField.MAX_VAL; - break; - case 2: - functionField = FunctionField.MIN_VAL; - break; - case 3: - functionField = FunctionField.ERROR_VAL; - break; - default: - this.functionField = null; - } - } - - int encode(byte[] buffer, int offset) { - - int i = offset; - - System.arraycopy(dib, 0, buffer, i, dib.length); - - i += dib.length; - - System.arraycopy(vib, 0, buffer, i, vib.length); - - i += vib.length; - - return i - offset; - } - - /** - * Returns a byte array containing the DIB (i.e. the DIF and the DIFEs) contained in the data record. - * - * @return a byte array containing the DIB - */ - public byte[] getDib() { - return dib; - } - - /** - * Returns a byte array containing the VIB (i.e. the VIF and the VIFEs) contained in the data record. - * - * @return a byte array containing the VIB - */ - public byte[] getVib() { - return vib; - } - - /** - * Returns the decoded data field of the data record as an Object. The Object is of one of the four types Long, - * Double, String or Date depending on information coded in the DIB/VIB. The DataType can be checked using - * getDataValueType(). - * - * @return the data value - */ - public Object getDataValue() { - return dataValue; - } - - public DataValueType getDataValueType() { - return dataValueType; - } - - /** - * Returns the data (value) multiplied by the multiplier as a Double. If the data is not a number than null is - * returned. - * - * @return the data (value) multiplied by the multiplier as a Double - */ - public Double getScaledDataValue() { - try { - return ((Number) dataValue).doubleValue() * Math.pow(10, multiplierExponent); - } catch (ClassCastException e) { - return null; - } - } - - public FunctionField getFunctionField() { - return functionField; - } - - public long getStorageNumber() { - return storageNumber; - } - - public int getTariff() { - return tariff; - } - - public short getSubunit() { - return subunit; - } - - public Description getDescription() { - return description; - } - - public String getUserDefinedDescription() { - if (description == Description.USER_DEFINED) { - return userDefinedDescription; - } else { - return description.toString(); - } - } - - /** - * The multiplier is coded in the VIF. Is always a power of 10. This function returns the exponent. The base is - * always 10. - * - * @return the exponent of the multiplier. - */ - public int getMultiplierExponent() { - return multiplierExponent; - } - - public DlmsUnit getUnit() { - return unit; - } - - private void decodeTimeUnit(int vif) { - if ((vif & 0x02) == 0) { - if ((vif & 0x01) == 0) { - unit = DlmsUnit.SECOND; - } else { - unit = DlmsUnit.MIN; - } - } else { - if ((vif & 0x01) == 0) { - unit = DlmsUnit.HOUR; - } else { - unit = DlmsUnit.DAY; - } - } - } - - private int decodeUserDefinedVif(byte[] buffer, int offset) throws DecodingException { - - int length = buffer[offset]; - StringBuilder sb = new StringBuilder(); - for (int i = offset + length; i > offset; i--) { - sb.append((char) buffer[i]); - } - - description = Description.USER_DEFINED; - userDefinedDescription = sb.toString(); - - return length + 1; - } - - private void decodeMainVif(int vif) { - description = Description.NOT_SUPPORTED; - - if ((vif & 0x40) == 0) { - decodeE0(vif); - } else { - decodeE1(vif); - - } - } - - private void decodeE1(int vif) { - // E1 - if ((vif & 0x20) == 0) { - decodeE10(vif); - } else { - decodeE11(vif); - } - } - - private void decodeE11(int vif) { - // E11 - if ((vif & 0x10) == 0) { - decodeE110(vif); - } else { - decodeE111(vif); - } - } - - private void decodeE111(int vif) { - // E111 - if ((vif & 0x08) == 0) { - // E111 0 - if ((vif & 0x04) == 0) { - description = Description.AVERAGING_DURATION; - } else { - description = Description.ACTUALITY_DURATION; - } - decodeTimeUnit(vif); - } else { - // E111 1 - if ((vif & 0x04) == 0) { - // E111 10 - if ((vif & 0x02) == 0) { - // E111 100 - if ((vif & 0x01) == 0) { - // E111 1000 - description = Description.FABRICATION_NO; - } else { - // E111 1001 - description = Description.EXTENDED_IDENTIFICATION; - } - } else { - // E111 101 - if ((vif & 0x01) == 0) { - description = Description.ADDRESS; - } else { - // E111 1011 - // Codes used with extension indicator 0xFB (table 29 of DIN EN 13757-3:2011) - throw new IllegalArgumentException( - "Trying to decode a mainVIF even though it is an alternate extended vif"); - } - } - } else { - // E111 11 - if ((vif & 0x02) == 0) { - // E111 110 - if ((vif & 0x01) == 0) { - // E111 1100 - // Extension indicator 0xFC: VIF is given in following string - description = Description.NOT_SUPPORTED; - } else { - // E111 1101 - // Extension indicator 0xFD: main VIFE-code extension table (table 28 of DIN EN - // 13757-3:2011) - throw new IllegalArgumentException( - "Trying to decode a mainVIF even though it is a main extended vif"); - - } - } else { - // E111 111 - if ((vif & 0x01) == 0) { - // E111 1110 - description = Description.FUTURE_VALUE; - } else { - // E111 1111 - description = Description.MANUFACTURER_SPECIFIC; - } - } - } - } - } - - private void decodeE110(int vif) { - // E110 - if ((vif & 0x08) == 0) { - // E110 0 - if ((vif & 0x04) == 0) { - // E110 00 - description = Description.TEMPERATURE_DIFFERENCE; - multiplierExponent = (vif & 0x03) - 3; - unit = DlmsUnit.KELVIN; - } else { - // E110 01 - description = Description.EXTERNAL_TEMPERATURE; - multiplierExponent = (vif & 0x03) - 3; - unit = DlmsUnit.DEGREE_CELSIUS; - } - } else { - // E110 1 - if ((vif & 0x04) == 0) { - // E110 10 - description = Description.PRESSURE; - multiplierExponent = (vif & 0x03) - 3; - unit = DlmsUnit.BAR; - } else { - // E110 11 - if ((vif & 0x02) == 0) { - // E110 110 - if ((vif & 0x01) == 0) { - // E110 1100 - description = Description.DATE; - dateTypeG = true; - } else { - // E110 1101 - description = Description.DATE_TIME; - dateTypeF = true; - } - } else { - // E110 111 - if ((vif & 0x01) == 0) { - // E110 1110 - description = Description.HCA; - unit = DlmsUnit.RESERVED; - } else { - description = Description.NOT_SUPPORTED; - } - - } - - } - } - } - - private void decodeE10(int vif) { - // E10 - if ((vif & 0x10) == 0) { - // E100 - if ((vif & 0x08) == 0) { - // E100 0 - description = Description.VOLUME_FLOW_EXT; - multiplierExponent = (vif & 0x07) - 7; - unit = DlmsUnit.CUBIC_METRE_PER_MINUTE; - } else { - // E100 1 - description = Description.VOLUME_FLOW_EXT; - multiplierExponent = (vif & 0x07) - 9; - unit = DlmsUnit.CUBIC_METRE_PER_SECOND; - } - } else { - // E101 - if ((vif & 0x08) == 0) { - // E101 0 - description = Description.MASS_FLOW; - multiplierExponent = (vif & 0x07) - 3; - unit = DlmsUnit.KILOGRAM_PER_HOUR; - } else { - // E101 1 - if ((vif & 0x04) == 0) { - // E101 10 - description = Description.FLOW_TEMPERATURE; - multiplierExponent = (vif & 0x03) - 3; - unit = DlmsUnit.DEGREE_CELSIUS; - } else { - // E101 11 - description = Description.RETURN_TEMPERATURE; - multiplierExponent = (vif & 0x03) - 3; - unit = DlmsUnit.DEGREE_CELSIUS; - } - } - } - } - - private void decodeE0(int vif) { - // E0 - if ((vif & 0x20) == 0) { - decodeE00(vif); - } else { - decode01(vif); - } - } - - private void decode01(int vif) { - // E01 - if ((vif & 0x10) == 0) { - // E010 - if ((vif & 0x08) == 0) { - // E010 0 - if ((vif & 0x04) == 0) { - // E010 00 - description = Description.ON_TIME; - } else { - // E010 01 - description = Description.OPERATING_TIME; - } - decodeTimeUnit(vif); - } else { - // E010 1 - description = Description.POWER; - multiplierExponent = (vif & 0x07) - 3; - unit = DlmsUnit.WATT; - } - } else { - // E011 - if ((vif & 0x08) == 0) { - // E011 0 - description = Description.POWER; - multiplierExponent = vif & 0x07; - unit = DlmsUnit.JOULE_PER_HOUR; - } else { - // E011 1 - description = Description.VOLUME_FLOW; - multiplierExponent = (vif & 0x07) - 6; - unit = DlmsUnit.CUBIC_METRE_PER_HOUR; - } - } - } - - private void decodeE00(int vif) { - // E00 - if ((vif & 0x10) == 0) { - // E000 - if ((vif & 0x08) == 0) { - // E000 0 - description = Description.ENERGY; - multiplierExponent = (vif & 0x07) - 3; - unit = DlmsUnit.WATT_HOUR; - } else { - // E000 1 - description = Description.ENERGY; - multiplierExponent = vif & 0x07; - unit = DlmsUnit.JOULE; - } - } else { - // E001 - if ((vif & 0x08) == 0) { - // E001 0 - description = Description.VOLUME; - multiplierExponent = (vif & 0x07) - 6; - unit = DlmsUnit.CUBIC_METRE; - } else { - // E001 1 - description = Description.MASS; - multiplierExponent = (vif & 0x07) - 3; - unit = DlmsUnit.KILOGRAM; - } - } - } - - // implements table 28 of DIN EN 13757-3:2013 - private void decodeMainExtendedVif(byte vif) throws DecodingException { - if ((vif & 0x7f) == 0x0b) { // E000 1011 - description = Description.PARAMETER_SET_ID; - } else if ((vif & 0x7f) == 0x0c) { // E000 1100 - description = Description.MODEL_VERSION; - } else if ((vif & 0x7f) == 0x0d) { // E000 1101 - description = Description.HARDWARE_VERSION; - } else if ((vif & 0x7f) == 0x0e) { // E000 1110 - description = Description.FIRMWARE_VERSION; - } else if ((vif & 0x7f) == 0x0f) { // E000 1111 - description = Description.OTHER_SOFTWARE_VERSION; - } else if ((vif & 0x7f) == 0x10) { // E001 0000 - description = Description.CUSTOMER_LOCATION; - } else if ((vif & 0x7f) == 0x11) { // E001 0001 - description = Description.CUSTOMER; - } else if ((vif & 0x7f) == 0x12) { // E001 0010 - description = Description.ACCSESS_CODE_USER; - } else if ((vif & 0x7f) == 0x13) { // E001 0011 - description = Description.ACCSESS_CODE_OPERATOR; - } else if ((vif & 0x7f) == 0x14) { // E001 0100 - description = Description.ACCSESS_CODE_SYSTEM_OPERATOR; - } else if ((vif & 0x7f) == 0x15) { // E001 0101 - description = Description.ACCSESS_CODE_SYSTEM_DEVELOPER; - } else if ((vif & 0x7f) == 0x16) { // E001 0110 - description = Description.PASSWORD; - } else if ((vif & 0x7f) == 0x17) { // E001 0111 - description = Description.ERROR_FLAGS; - } else if ((vif & 0x7f) == 0x18) { // E001 1000 - description = Description.ERROR_MASK; - } else if ((vif & 0x7f) == 0x19) { // E001 1001 - description = Description.SECURITY_KEY; - } else if ((vif & 0x7f) == 0x1a) { // E001 1010 - description = Description.DIGITAL_OUTPUT; - } else if ((vif & 0x7f) == 0x1b) { // E001 1011 - description = Description.DIGITAL_INPUT; - } else if ((vif & 0x7f) == 0x1c) { // E001 1100 - description = Description.BAUDRATE; - } else if ((vif & 0x7f) == 0x1d) { // E001 1101 - description = Description.RESPONSE_DELAY_TIME; - } else if ((vif & 0x7f) == 0x1e) { // E001 1110 - description = Description.RETRY; - } else if ((vif & 0x7f) == 0x1f) { // E001 1111 - description = Description.REMOTE_CONTROL; - } else if ((vif & 0x7f) == 0x20) { // E010 0000 - description = Description.FIRST_STORAGE_NUMBER_CYCLIC; - } else if ((vif & 0x7f) == 0x21) { // E010 0001 - description = Description.LAST_STORAGE_NUMBER_CYCLIC; - } else if ((vif & 0x7f) == 0x22) { // E010 0010 - description = Description.SIZE_STORAGE_BLOCK; - } else if ((vif & 0x7f) == 0x23) { // E010 0011 - description = Description.RESERVED; - } else if ((vif & 0x7c) == 0x24) { // E010 01nn - description = Description.STORAGE_INTERVALL; - this.unit = unitFor(vif); - } else if ((vif & 0x7f) == 0x28) { // E010 1000 - description = Description.STORAGE_INTERVALL; - unit = DlmsUnit.MONTH; - } else if ((vif & 0x7f) == 0x29) { // E010 1001 - description = Description.STORAGE_INTERVALL; - unit = DlmsUnit.YEAR; - } else if ((vif & 0x7f) == 0x2a) { // E010 1010 - description = Description.OPERATOR_SPECIFIC_DATA; - } else if ((vif & 0x7f) == 0x2b) { // E010 1011 - description = Description.TIME_POINT; - unit = DlmsUnit.SECOND; - } else if ((vif & 0x7c) == 0x2c) { // E010 11nn - description = Description.DURATION_LAST_READOUT; - this.unit = unitFor(vif); - } else if ((vif & 0x7c) == 0x30) { // E011 00nn - description = Description.TARIF_DURATION; - switch (vif & 0x03) { - case 0: // E011 0000 - description = Description.NOT_SUPPORTED; // TODO: TARIF_START (Date/Time) - break; - default: - this.unit = unitFor(vif); - } - } else if ((vif & 0x7c) == 0x34) { // E011 01nn - description = Description.TARIF_PERIOD; - this.unit = unitFor(vif); - } else if ((vif & 0x7f) == 0x38) { // E011 1000 - description = Description.TARIF_PERIOD; - unit = DlmsUnit.MONTH; - } else if ((vif & 0x7f) == 0x39) { // E011 1001 - description = Description.TARIF_PERIOD; - unit = DlmsUnit.YEAR; - } else if ((vif & 0x70) == 0x40) { // E100 0000 - description = Description.VOLTAGE; - multiplierExponent = (vif & 0x0f) - 9; - unit = DlmsUnit.VOLT; - } else if ((vif & 0x70) == 0x50) { // E101 0000 - description = Description.CURRENT; - multiplierExponent = (vif & 0x0f) - 12; - unit = DlmsUnit.AMPERE; - } else if ((vif & 0x7f) == 0x60) { // E110 0000 - description = Description.RESET_COUNTER; - } else if ((vif & 0x7f) == 0x61) { // E110 0001 - description = Description.CUMULATION_COUNTER; - } else if ((vif & 0x7f) == 0x62) { // E110 0010 - description = Description.CONTROL_SIGNAL; - } else if ((vif & 0x7f) == 0x63) { // E110 0011 - description = Description.DAY_OF_WEEK; // 1 = Monday; 7 = Sunday; 0 = all Days - } else if ((vif & 0x7f) == 0x64) { // E110 0100 - description = Description.WEEK_NUMBER; - } else if ((vif & 0x7f) == 0x65) { // E110 0101 - description = Description.TIME_POINT_DAY_CHANGE; - } else if ((vif & 0x7f) == 0x66) { // E110 0110 - description = Description.PARAMETER_ACTIVATION_STATE; - } else if ((vif & 0x7f) == 0x67) { // E110 0111 - description = Description.SPECIAL_SUPPLIER_INFORMATION; - } else if ((vif & 0x7c) == 0x68) { // E110 10nn - description = Description.LAST_CUMULATION_DURATION; - this.unit = unitBiggerFor(vif); - } else if ((vif & 0x7c) == 0x6c) { // E110 11nn - description = Description.OPERATING_TIME_BATTERY; - this.unit = unitBiggerFor(vif); - } else if ((vif & 0x7f) == 0x70) { // E111 0000 - description = Description.NOT_SUPPORTED; // TODO: BATTERY_CHANGE_DATE_TIME - } else if ((vif & 0x7f) == 0x71) { // E111 0001 - description = Description.NOT_SUPPORTED; // TODO: RF_LEVEL dBm - } else if ((vif & 0x7f) == 0x72) { // E111 0010 - description = Description.NOT_SUPPORTED; // TODO: DAYLIGHT_SAVING (begin, ending, deviation) - } else if ((vif & 0x7f) == 0x73) { // E111 0011 - description = Description.NOT_SUPPORTED; // TODO: Listening window management data type L - } else if ((vif & 0x7f) == 0x74) { // E111 0100 - description = Description.REMAINING_BATTERY_LIFE_TIME; - unit = DlmsUnit.DAY; - } else if ((vif & 0x7f) == 0x75) { // E111 0101 - description = Description.NUMBER_STOPS; - } else if ((vif & 0x7f) == 0x76) { // E111 0110 - description = Description.MANUFACTURER_SPECIFIC; - } else if ((vif & 0x7f) >= 0x77) { // E111 0111 - E111 1111 - description = Description.RESERVED; - } else { - description = Description.NOT_SUPPORTED; - } - } - - private static DlmsUnit unitBiggerFor(byte vif) throws DecodingException { - int u = vif & 0x03; - switch (u) { - case 0: // E110 1100 - return DlmsUnit.HOUR; - case 1: // E110 1101 - return DlmsUnit.DAY; - case 2: // E110 1110 - return DlmsUnit.MONTH; - case 3: // E110 1111 - return DlmsUnit.YEAR; - default: - throw new DecodingException(String.format("Unknown unit 0x%02X.", u)); - } - } - - private static DlmsUnit unitFor(byte vif) throws DecodingException { - int u = vif & 0x03; - switch (u) { - case 0: // E010 1100 - return DlmsUnit.SECOND; - case 1: // E010 1101 - return DlmsUnit.MIN; - case 2: // E010 1110 - return DlmsUnit.HOUR; - case 3: // E010 1111 - return DlmsUnit.DAY; - default: - throw new DecodingException(String.format("Unknown unit 0x%02X.", u)); - } - } - - // implements table 29 of DIN EN 13757-3:2011 - private void decodeAlternateExtendedVif(byte vif) { - description = Description.NOT_SUPPORTED; // default value - - if ((vif & 0x40) == 0) { - // E0 - if ((vif & 0x20) == 0) { - // E00 - if ((vif & 0x10) == 0) { - // E000 - if ((vif & 0x08) == 0) { - // E000 0 - if ((vif & 0x04) == 0) { - // E000 00 - if ((vif & 0x02) == 0) { - // E000 000 - description = Description.ENERGY; - multiplierExponent = 5 + (vif & 0x01); - unit = DlmsUnit.WATT_HOUR; - } else { - // E000 001 - description = Description.REACTIVE_ENERGY; - multiplierExponent = 3 + (vif & 0x01); - unit = DlmsUnit.VAR_HOUR; - } - - } else { - // E000 01 - if ((vif & 0x02) == 0) { - // E000 010 - description = Description.APPARENT_ENERGY; - multiplierExponent = 3 + (vif & 0x01); - unit = DlmsUnit.VOLT_AMPERE_HOUR; - } else { - // E000 011 - description = Description.NOT_SUPPORTED; - } - } - } else { - // E000 1 - if ((vif & 0x04) == 0) { - // E000 10 - if ((vif & 0x02) == 0) { - // E000 100 - description = Description.ENERGY; - multiplierExponent = 8 + (vif & 0x01); - unit = DlmsUnit.JOULE; - } else { - // E000 101 - description = Description.NOT_SUPPORTED; - } - - } else { - // E000 11 - description = Description.ENERGY; - multiplierExponent = 5 + (vif & 0x03); - unit = DlmsUnit.CALORIFIC_VALUE; - } - } - } else { - // E001 - if ((vif & 0x08) == 0) { - // E001 0 - if ((vif & 0x04) == 0) { - // E001 00 - if ((vif & 0x02) == 0) { - // E001 000 - description = Description.VOLUME; - multiplierExponent = 2 + (vif & 0x01); - unit = DlmsUnit.CUBIC_METRE; - } else { - // E001 001 - description = Description.NOT_SUPPORTED; - } - } else { - // E001 01 - description = Description.REACTIVE_POWER; - multiplierExponent = (vif & 0x03); - unit = DlmsUnit.VAR; - } - } else { - // E001 1 - if ((vif & 0x04) == 0) { - // E001 10 - if ((vif & 0x02) == 0) { - // E001 100 - description = Description.MASS; - multiplierExponent = 5 + (vif & 0x01); - unit = DlmsUnit.KILOGRAM; - } else { - // E001 101 - description = Description.REL_HUMIDITY; - multiplierExponent = -1 + (vif & 0x01); - unit = DlmsUnit.PERCENTAGE; - } - - } else { - // E001 11 - description = Description.NOT_SUPPORTED; - } - } - - } - } else { - // E01 - if ((vif & 0x10) == 0) { - // E010 - if ((vif & 0x08) == 0) { - // E010 0 - if ((vif & 0x04) == 0) { - // E010 00 - if ((vif & 0x02) == 0) { - // E010 000 - if ((vif & 0x01) == 0) { - // E010 0000 - description = Description.VOLUME; - multiplierExponent = 0; - unit = DlmsUnit.CUBIC_FEET; - } else { - // E010 0001 - description = Description.VOLUME; - multiplierExponent = -1; - unit = DlmsUnit.CUBIC_FEET; - } - } else { - // E010 001 - // outdated value ! - description = Description.VOLUME; - multiplierExponent = -1 + (vif & 0x01); - unit = DlmsUnit.US_GALLON; - } - } else { - // E010 01 - if ((vif & 0x02) == 0) { - // E010 010 - if ((vif & 0x01) == 0) { - // E010 0100 - // outdated value ! - description = Description.VOLUME_FLOW; - multiplierExponent = -3; - unit = DlmsUnit.US_GALLON_PER_MINUTE; - } else { - // E010 0101 - // outdated value ! - description = Description.VOLUME_FLOW; - multiplierExponent = 0; - unit = DlmsUnit.US_GALLON_PER_MINUTE; - } - } else { - // E010 011 - if ((vif & 0x01) == 0) { - // E010 0110 - // outdated value ! - description = Description.VOLUME_FLOW; - multiplierExponent = 0; - unit = DlmsUnit.US_GALLON_PER_HOUR; - } else { - // E010 0111 - description = Description.NOT_SUPPORTED; - } - } - - } - } else { - // E010 1 - if ((vif & 0x04) == 0) { - // E010 10 - if ((vif & 0x02) == 0) { - // E010 100 - description = Description.POWER; - multiplierExponent = 5 + (vif & 0x01); - unit = DlmsUnit.WATT; - } else { - if ((vif & 0x01) == 0) { - // E010 1010 - description = Description.PHASE; - multiplierExponent = -1; // is -1 or 0 correct ?? - unit = DlmsUnit.DEGREE; - } - // TODO same - // else { - // // E010 1011 - // description = Description.PHASE; - // multiplierExponent = -1; // is -1 or 0 correct ?? - // unit = DlmsUnit.DEGREE; - // } - } - } else { - // E010 11 - description = Description.FREQUENCY; - multiplierExponent = -3 + (vif & 0x03); - unit = DlmsUnit.HERTZ; - } - } - } else { - // E011 - if ((vif & 0x08) == 0) { - // E011 0 - if ((vif & 0x04) == 0) { - // E011 00 - if ((vif & 0x02) == 0) { - // E011 000 - description = Description.POWER; - multiplierExponent = 8 + (vif & 0x01); - unit = DlmsUnit.JOULE_PER_HOUR; - } else { - // E011 001 - description = Description.NOT_SUPPORTED; - } - } else { - // E011 01 - description = Description.APPARENT_ENERGY; - multiplierExponent = (vif & 0x03); - unit = DlmsUnit.VOLT_AMPERE; - } - } else { - // E011 1 - description = Description.NOT_SUPPORTED; - } - } - } - } else { - // E1 - if ((vif & 0x20) == 0) { - // E10 - if ((vif & 0x10) == 0) { - // E100 - description = Description.NOT_SUPPORTED; - } else { - // E101 - if ((vif & 0x08) == 0) { - // E101 0 - description = Description.NOT_SUPPORTED; - } else { - // E101 1 - if ((vif & 0x04) == 0) { - // E101 10 - // outdated value ! - description = Description.FLOW_TEMPERATURE; - multiplierExponent = (vif & 0x03) - 3; - unit = DlmsUnit.DEGREE_FAHRENHEIT; - } else { - // E101 11 - // outdated value ! - description = Description.RETURN_TEMPERATURE; - multiplierExponent = (vif & 0x03) - 3; - unit = DlmsUnit.DEGREE_FAHRENHEIT; - } - } - } - } else { - // E11 - if ((vif & 0x10) == 0) { - // E110 - if ((vif & 0x08) == 0) { - // E110 0 - if ((vif & 0x04) == 0) { - // E110 00 - // outdated value ! - description = Description.TEMPERATURE_DIFFERENCE; - multiplierExponent = (vif & 0x03) - 3; - unit = DlmsUnit.DEGREE_FAHRENHEIT; - } else { - // E110 01 - // outdated value ! - description = Description.FLOW_TEMPERATURE; - multiplierExponent = (vif & 0x03) - 3; - unit = DlmsUnit.DEGREE_FAHRENHEIT; - } - } else { - // E110 1 - description = Description.NOT_SUPPORTED; - } - } else { - // E111 - if ((vif & 0x08) == 0) { - // E111 0 - if ((vif & 0x04) == 0) { - // E111 00 - // outdated value ! - description = Description.TEMPERATURE_LIMIT; - multiplierExponent = (vif & 0x03) - 3; - unit = DlmsUnit.DEGREE_FAHRENHEIT; - } else { - // E111 01 - description = Description.TEMPERATURE_LIMIT; - multiplierExponent = (vif & 0x03) - 3; - unit = DlmsUnit.DEGREE_CELSIUS; - } - } else { - // E111 1 - description = Description.MAX_POWER; - multiplierExponent = (vif & 0x07) - 3; - unit = DlmsUnit.WATT; - } - } - } - - } - } - - @Override - public String toString() { - - StringBuilder builder = new StringBuilder().append("DIB:").append(printHexBinary(dib)).append(", VIB:") - .append(printHexBinary(vib)).append(" -> descr:").append(description); - - if (description == Description.USER_DEFINED) { - builder.append(" :").append(getUserDefinedDescription()); - } - builder.append(", function:").append(functionField); - - if (storageNumber > 0) { - builder.append(", storage:").append(storageNumber); - } - - if (tariff > 0) { - builder.append(", tariff:").append(tariff); - } - - if (subunit > 0) { - builder.append(", subunit:").append(subunit); - } - - final String valuePlacHolder = ", value:"; - final String scaledValueString = ", scaled value:"; - - switch (dataValueType) { - case DATE: - case STRING: - builder.append(valuePlacHolder).append((dataValue).toString()); - break; - case DOUBLE: - builder.append(scaledValueString).append(getScaledDataValue()); - break; - case LONG: - if (multiplierExponent == 0) { - builder.append(valuePlacHolder).append(dataValue); - } else { - builder.append(scaledValueString).append(getScaledDataValue()); - } - break; - case BCD: - if (multiplierExponent == 0) { - builder.append(valuePlacHolder).append((dataValue).toString()); - } else { - builder.append(scaledValueString).append(getScaledDataValue()); - } - break; - case NONE: - builder.append(", value:NONE"); - break; - } - - if (unit != null) { - builder.append(", unit:").append(unit); - if (!unit.getUnit().isEmpty()) { - builder.append(", ").append(unit.getUnit()); - } - } - - return builder.toString(); - } - - public int getDataLength() { - return dataLength; - } -} +/** + * Copyright (c) 2010-2021 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.openmuc.jmbus; + +import static javax.xml.bind.DatatypeConverter.printHexBinary; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.Calendar; + +/** + * Representation of a data record (sometimes called variable data block). + * + * A data record is the basic data entity of the M-Bus application layer. A variable data structure contains a list of + * data records. Each data record represents a single data point. A data record consists of three fields: The data + * information block (DIB), the value information block (VIB) and the data field. + * + * The DIB codes the following parameters: + *
      + *
    • Storage number - a meter can have several storages e.g. to store historical time series data. The storage number + * 0 signals an actual value.
    • + *
    • Function - data can have the following four function types: instantaneous value, max value, min value, value + * during error state.
    • + *
    • Length and coding of the data field.
    • + *
    • Tariff - indicates the tariff number of this data field. The data of tariff 0 is usually the sum of all other + * tariffs.
    • + *
    • Subunit - can be used by a slave to distinguish several subunits of the metering device
    • + *
    + * + * The VIB codes the following parameters: + *
      + *
    • Description - the meaning of the data value (e.g. "Energy", "Volume" etc.)
    • + *
    • Unit - the unit of the data value.
    • + *
    • Multiplier - a factor by which the data value coded in the data field has to be multiplied with. + * getScaledDataValue() returns the result of the data value multiplied with the multiplier.
    • + *
    + * + */ +public class DataRecord { + + /** + * The data value type + * + */ + public enum DataValueType { + LONG, + DOUBLE, + DATE, + STRING, + BCD, + NONE; + } + + /** + * Function coded in the DIB + * + */ + public enum FunctionField { + /** + * instantaneous value + */ + INST_VAL, + /** + * maximum value + */ + MAX_VAL, + /** + * minimum value + */ + MIN_VAL, + /** + * value during error state + */ + ERROR_VAL; + } + + /** + * Data description stored in the VIB + * + */ + public enum Description { + ENERGY, + VOLUME, + MASS, + ON_TIME, + OPERATING_TIME, + POWER, + VOLUME_FLOW, + VOLUME_FLOW_EXT, + MASS_FLOW, + FLOW_TEMPERATURE, + RETURN_TEMPERATURE, + TEMPERATURE_DIFFERENCE, + EXTERNAL_TEMPERATURE, + PRESSURE, + DATE, + DATE_TIME, + VOLTAGE, + CURRENT, + AVERAGING_DURATION, + ACTUALITY_DURATION, + FABRICATION_NO, + MODEL_VERSION, + PARAMETER_SET_ID, + HARDWARE_VERSION, + FIRMWARE_VERSION, + ERROR_FLAGS, + CUSTOMER, + RESERVED, + OPERATING_TIME_BATTERY, + HCA, + REACTIVE_ENERGY, + TEMPERATURE_LIMIT, + MAX_POWER, + REACTIVE_POWER, + REL_HUMIDITY, + FREQUENCY, + PHASE, + EXTENDED_IDENTIFICATION, + ADDRESS, + NOT_SUPPORTED, + MANUFACTURER_SPECIFIC, + FUTURE_VALUE, + USER_DEFINED, + APPARENT_ENERGY, + CUSTOMER_LOCATION, + ACCSESS_CODE_OPERATOR, + ACCSESS_CODE_USER, + PASSWORD, + ACCSESS_CODE_SYSTEM_DEVELOPER, + OTHER_SOFTWARE_VERSION, + ACCSESS_CODE_SYSTEM_OPERATOR, + ERROR_MASK, + SECURITY_KEY, + DIGITAL_INPUT, + BAUDRATE, + DIGITAL_OUTPUT, + RESPONSE_DELAY_TIME, + RETRY, + FIRST_STORAGE_NUMBER_CYCLIC, + REMOTE_CONTROL, + LAST_STORAGE_NUMBER_CYCLIC, + SIZE_STORAGE_BLOCK, + STORAGE_INTERVALL, + TARIF_START, + DURATION_LAST_READOUT, + TIME_POINT, + TARIF_DURATION, + OPERATOR_SPECIFIC_DATA, + TARIF_PERIOD, + NUMBER_STOPS, + LAST_CUMULATION_DURATION, + SPECIAL_SUPPLIER_INFORMATION, + PARAMETER_ACTIVATION_STATE, + CONTROL_SIGNAL, + WEEK_NUMBER, + DAY_OF_WEEK, + REMAINING_BATTERY_LIFE_TIME, + TIME_POINT_DAY_CHANGE, + CUMULATION_COUNTER, + RESET_COUNTER; + } + + // // Data Information Block that contains a DIF and optionally up to 10 DIFEs + private byte[] dib; + // // Value Information Block that contains a VIF and optionally up to 10 VIFEs + private byte[] vib; + + private Object dataValue; + private DataValueType dataValueType; + + // DIB fields: + private FunctionField functionField; + + private long storageNumber; // max is 41 bits + private int tariff; // max 20 bits + private short subunit; // max 10 bits + + // VIB fields: + private Description description; + private String userDefinedDescription; + private int multiplierExponent = 0; + private DlmsUnit unit; + + private boolean dateTypeF = false; + private boolean dateTypeG = false; + + int dataLength; + + byte[] rawData; + + public byte[] getRawData() { + return rawData; + } + + int decode(byte[] buffer, int offset, int length) throws DecodingException { + int i = offset; + + decodeDib(buffer, i); + + int dataField = buffer[i] & 0x0f; + dataLength = dataField; + storageNumber = (buffer[i] & 0x40) >> 6; + + subunit = 0; + tariff = 0; + + int numDife = 0; + while ((buffer[i++] & 0x80) == 0x80) { + subunit += (((buffer[i] & 0x40) >> 6) << numDife); + tariff += ((buffer[i] & 0x30) >> 4) << (numDife * 2); + storageNumber += ((buffer[i] & 0x0f) << ((numDife * 4) + 1)); + numDife++; + } + + multiplierExponent = 0; + + unit = null; + + dib = Arrays.copyOfRange(buffer, offset, i); + + // decode VIB + + int vif = buffer[i++] & 0xff; + + boolean decodeFurtherVifs = false; + + if (vif == 0xfb) { + decodeAlternateExtendedVif(buffer[i]); + if ((buffer[i] & 0x80) == 0x80) { + decodeFurtherVifs = true; + } + i++; + } else if ((vif & 0x7f) == 0x7c) { + i += decodeUserDefinedVif(buffer, i); + if ((vif & 0x80) == 0x80) { + decodeFurtherVifs = true; + } + } else if (vif == 0xfd) { + decodeMainExtendedVif(buffer[i]); + if ((buffer[i] & 0x80) == 0x80) { + decodeFurtherVifs = true; + } + i++; + } else { + decodeMainVif(vif); + if ((vif & 0x80) == 0x80) { + decodeFurtherVifs = true; + } + } + + if (decodeFurtherVifs) { + while ((buffer[i++] & 0x80) == 0x80) { + // TODO these vifes should not be ignored! + } + } + + vib = Arrays.copyOfRange(buffer, offset + dib.length, i); + + switch (dataField) { + case 0x00: + case 0x08: /* no data - selection for readout request */ + dataValue = null; + dataValueType = DataValueType.NONE; + break; + case 0x01: /* INT8 */ + dataValue = Long.valueOf(buffer[i++]); + dataValueType = DataValueType.LONG; + break; + case 0x02: /* INT16 */ + if (dateTypeG) { + int day = (0x1f) & buffer[i]; + int year1 = ((0xe0) & buffer[i++]) >> 5; + int month = (0x0f) & buffer[i]; + int year2 = ((0xf0) & buffer[i++]) >> 1; + int year = (2000 + year1 + year2); + + Calendar calendar = Calendar.getInstance(); + + calendar.set(year, month - 1, day, 0, 0, 0); + + dataValue = calendar.getTime(); + dataValueType = DataValueType.DATE; + } else { + dataValue = Long.valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8)); + dataValueType = DataValueType.LONG; + } + break; + case 0x03: /* INT24 */ + if ((buffer[i + 2] & 0x80) == 0x80) { + // negative + dataValue = Long.valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) + | ((buffer[i++] & 0xff) << 16) | 0xff << 24); + } else { + dataValue = Long + .valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) | ((buffer[i++] & 0xff) << 16)); + } + dataValueType = DataValueType.LONG; + break; + case 0x04: /* INT32 */ + if (dateTypeF) { + int min = (buffer[i++] & 0x3f); + int hour = (buffer[i] & 0x1f); + int yearh = (0x60 & buffer[i++]) >> 5; + int day = (buffer[i] & 0x1f); + int year1 = (0xe0 & buffer[i++]) >> 5; + int mon = (buffer[i] & 0x0f); + int year2 = (0xf0 & buffer[i++]) >> 1; + + if (yearh == 0) { + yearh = 1; + } + + int year = 1900 + 100 * yearh + year1 + year2; + + Calendar calendar = Calendar.getInstance(); + + calendar.set(year, mon - 1, day, hour, min, 0); + + dataValue = calendar.getTime(); + dataValueType = DataValueType.DATE; + } else { + dataValue = Long.valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) + | ((buffer[i++] & 0xff) << 16) | ((buffer[i++] & 0xff) << 24)); + dataValueType = DataValueType.LONG; + } + break; + case 0x05: /* FLOAT32 */ + Float doubleDatavalue = ByteBuffer.wrap(buffer, i, 4).order(ByteOrder.LITTLE_ENDIAN).getFloat(); + i += 4; + dataValue = Double.valueOf(doubleDatavalue); + dataValueType = DataValueType.DOUBLE; + break; + case 0x06: /* INT48 */ + if ((buffer[i + 5] & 0x80) == 0x80) { + // negative + dataValue = Long + .valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) | ((buffer[i++] & 0xff) << 16) + | ((buffer[i++] & 0xff) << 24) | (((long) buffer[i++] & 0xff) << 32) + | (((long) buffer[i++] & 0xff) << 40) | (0xffl << 48) | (0xffl << 56)); + } else { + dataValue = Long.valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) + | ((buffer[i++] & 0xff) << 16) | ((buffer[i++] & 0xff) << 24) + | (((long) buffer[i++] & 0xff) << 32) | (((long) buffer[i++] & 0xff) << 40)); + } + dataValueType = DataValueType.LONG; + break; + case 0x07: /* INT64 */ + dataValue = Long.valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) + | ((buffer[i++] & 0xff) << 16) | ((buffer[i++] & 0xff) << 24) + | (((long) buffer[i++] & 0xff) << 32) | (((long) buffer[i++] & 0xff) << 40) + | (((long) buffer[i++] & 0xff) << 48) | (((long) buffer[i++] & 0xff) << 56)); + dataValueType = DataValueType.LONG; + break; + case 0x09: + i = setBCD(buffer, i, 1); + break; + case 0x0a: + i = setBCD(buffer, i, 2); + break; + case 0x0b: + i = setBCD(buffer, i, 3); + break; + case 0x0c: + i = setBCD(buffer, i, 4); + break; + case 0x0e: + i = setBCD(buffer, i, 6); + break; + case 0x0d: + + int variableLength = buffer[i++] & 0xff; + int dataLength0x0d; + + if (variableLength < 0xc0) { + dataLength0x0d = variableLength; + } else if ((variableLength >= 0xc0) && (variableLength <= 0xc9)) { + dataLength0x0d = 2 * (variableLength - 0xc0); + } else if ((variableLength >= 0xd0) && (variableLength <= 0xd9)) { + dataLength0x0d = 2 * (variableLength - 0xd0); + } else if ((variableLength >= 0xe0) && (variableLength <= 0xef)) { + dataLength0x0d = variableLength - 0xe0; + } else if (variableLength == 0xf8) { + dataLength0x0d = 4; + } else { + throw new DecodingException("Unsupported LVAR Field: " + variableLength); + } + + // TODO check this: + // if (variableLength >= 0xc0) { + // throw new DecodingException("Variable length (LVAR) field >= 0xc0: " + variableLength); + // } + + byte[] rawData = new byte[dataLength0x0d]; + + for (int j = 0; j < dataLength0x0d; j++) { + rawData[j] = buffer[i + dataLength0x0d - 1 - j]; + } + i += dataLength0x0d; + + this.rawData = rawData; + dataValue = new String(rawData); + dataValueType = DataValueType.STRING; + break; + default: + String msg = String.format("Unknown Data Field in DIF: %02X.", dataField); + throw new DecodingException(msg); + } + + return i; + } + + private int setBCD(byte[] buffer, int i, int j) { + dataValue = new Bcd(Arrays.copyOfRange(buffer, i, i + j)); + dataValueType = DataValueType.BCD; + return i + j; + } + + private void decodeDib(byte[] buffer, int i) { + int ff = ((buffer[i] & 0x30) >> 4); + switch (ff) { + case 0: + functionField = FunctionField.INST_VAL; + break; + case 1: + functionField = FunctionField.MAX_VAL; + break; + case 2: + functionField = FunctionField.MIN_VAL; + break; + case 3: + functionField = FunctionField.ERROR_VAL; + break; + default: + this.functionField = null; + } + } + + int encode(byte[] buffer, int offset) { + + int i = offset; + + System.arraycopy(dib, 0, buffer, i, dib.length); + + i += dib.length; + + System.arraycopy(vib, 0, buffer, i, vib.length); + + i += vib.length; + + return i - offset; + } + + /** + * Returns a byte array containing the DIB (i.e. the DIF and the DIFEs) contained in the data record. + * + * @return a byte array containing the DIB + */ + public byte[] getDib() { + return dib; + } + + /** + * Returns a byte array containing the VIB (i.e. the VIF and the VIFEs) contained in the data record. + * + * @return a byte array containing the VIB + */ + public byte[] getVib() { + return vib; + } + + /** + * Returns the decoded data field of the data record as an Object. The Object is of one of the four types Long, + * Double, String or Date depending on information coded in the DIB/VIB. The DataType can be checked using + * getDataValueType(). + * + * @return the data value + */ + public Object getDataValue() { + return dataValue; + } + + public DataValueType getDataValueType() { + return dataValueType; + } + + /** + * Returns the data (value) multiplied by the multiplier as a Double. If the data is not a number than null is + * returned. + * + * @return the data (value) multiplied by the multiplier as a Double + */ + public Double getScaledDataValue() { + try { + return ((Number) dataValue).doubleValue() * Math.pow(10, multiplierExponent); + } catch (ClassCastException e) { + return null; + } + } + + public FunctionField getFunctionField() { + return functionField; + } + + public long getStorageNumber() { + return storageNumber; + } + + public int getTariff() { + return tariff; + } + + public short getSubunit() { + return subunit; + } + + public Description getDescription() { + return description; + } + + public String getUserDefinedDescription() { + if (description == Description.USER_DEFINED) { + return userDefinedDescription; + } else { + return description.toString(); + } + } + + /** + * The multiplier is coded in the VIF. Is always a power of 10. This function returns the exponent. The base is + * always 10. + * + * @return the exponent of the multiplier. + */ + public int getMultiplierExponent() { + return multiplierExponent; + } + + public DlmsUnit getUnit() { + return unit; + } + + private void decodeTimeUnit(int vif) { + if ((vif & 0x02) == 0) { + if ((vif & 0x01) == 0) { + unit = DlmsUnit.SECOND; + } else { + unit = DlmsUnit.MIN; + } + } else { + if ((vif & 0x01) == 0) { + unit = DlmsUnit.HOUR; + } else { + unit = DlmsUnit.DAY; + } + } + } + + private int decodeUserDefinedVif(byte[] buffer, int offset) throws DecodingException { + + int length = buffer[offset]; + StringBuilder sb = new StringBuilder(); + for (int i = offset + length; i > offset; i--) { + sb.append((char) buffer[i]); + } + + description = Description.USER_DEFINED; + userDefinedDescription = sb.toString(); + + return length + 1; + } + + private void decodeMainVif(int vif) { + description = Description.NOT_SUPPORTED; + + if ((vif & 0x40) == 0) { + decodeE0(vif); + } else { + decodeE1(vif); + + } + } + + private void decodeE1(int vif) { + // E1 + if ((vif & 0x20) == 0) { + decodeE10(vif); + } else { + decodeE11(vif); + } + } + + private void decodeE11(int vif) { + // E11 + if ((vif & 0x10) == 0) { + decodeE110(vif); + } else { + decodeE111(vif); + } + } + + private void decodeE111(int vif) { + // E111 + if ((vif & 0x08) == 0) { + // E111 0 + if ((vif & 0x04) == 0) { + description = Description.AVERAGING_DURATION; + } else { + description = Description.ACTUALITY_DURATION; + } + decodeTimeUnit(vif); + } else { + // E111 1 + if ((vif & 0x04) == 0) { + // E111 10 + if ((vif & 0x02) == 0) { + // E111 100 + if ((vif & 0x01) == 0) { + // E111 1000 + description = Description.FABRICATION_NO; + } else { + // E111 1001 + description = Description.EXTENDED_IDENTIFICATION; + } + } else { + // E111 101 + if ((vif & 0x01) == 0) { + description = Description.ADDRESS; + } else { + // E111 1011 + // Codes used with extension indicator 0xFB (table 29 of DIN EN 13757-3:2011) + throw new IllegalArgumentException( + "Trying to decode a mainVIF even though it is an alternate extended vif"); + } + } + } else { + // E111 11 + if ((vif & 0x02) == 0) { + // E111 110 + if ((vif & 0x01) == 0) { + // E111 1100 + // Extension indicator 0xFC: VIF is given in following string + description = Description.NOT_SUPPORTED; + } else { + // E111 1101 + // Extension indicator 0xFD: main VIFE-code extension table (table 28 of DIN EN + // 13757-3:2011) + throw new IllegalArgumentException( + "Trying to decode a mainVIF even though it is a main extended vif"); + + } + } else { + // E111 111 + if ((vif & 0x01) == 0) { + // E111 1110 + description = Description.FUTURE_VALUE; + } else { + // E111 1111 + description = Description.MANUFACTURER_SPECIFIC; + } + } + } + } + } + + private void decodeE110(int vif) { + // E110 + if ((vif & 0x08) == 0) { + // E110 0 + if ((vif & 0x04) == 0) { + // E110 00 + description = Description.TEMPERATURE_DIFFERENCE; + multiplierExponent = (vif & 0x03) - 3; + unit = DlmsUnit.KELVIN; + } else { + // E110 01 + description = Description.EXTERNAL_TEMPERATURE; + multiplierExponent = (vif & 0x03) - 3; + unit = DlmsUnit.DEGREE_CELSIUS; + } + } else { + // E110 1 + if ((vif & 0x04) == 0) { + // E110 10 + description = Description.PRESSURE; + multiplierExponent = (vif & 0x03) - 3; + unit = DlmsUnit.BAR; + } else { + // E110 11 + if ((vif & 0x02) == 0) { + // E110 110 + if ((vif & 0x01) == 0) { + // E110 1100 + description = Description.DATE; + dateTypeG = true; + } else { + // E110 1101 + description = Description.DATE_TIME; + dateTypeF = true; + } + } else { + // E110 111 + if ((vif & 0x01) == 0) { + // E110 1110 + description = Description.HCA; + unit = DlmsUnit.RESERVED; + } else { + description = Description.NOT_SUPPORTED; + } + + } + + } + } + } + + private void decodeE10(int vif) { + // E10 + if ((vif & 0x10) == 0) { + // E100 + if ((vif & 0x08) == 0) { + // E100 0 + description = Description.VOLUME_FLOW_EXT; + multiplierExponent = (vif & 0x07) - 7; + unit = DlmsUnit.CUBIC_METRE_PER_MINUTE; + } else { + // E100 1 + description = Description.VOLUME_FLOW_EXT; + multiplierExponent = (vif & 0x07) - 9; + unit = DlmsUnit.CUBIC_METRE_PER_SECOND; + } + } else { + // E101 + if ((vif & 0x08) == 0) { + // E101 0 + description = Description.MASS_FLOW; + multiplierExponent = (vif & 0x07) - 3; + unit = DlmsUnit.KILOGRAM_PER_HOUR; + } else { + // E101 1 + if ((vif & 0x04) == 0) { + // E101 10 + description = Description.FLOW_TEMPERATURE; + multiplierExponent = (vif & 0x03) - 3; + unit = DlmsUnit.DEGREE_CELSIUS; + } else { + // E101 11 + description = Description.RETURN_TEMPERATURE; + multiplierExponent = (vif & 0x03) - 3; + unit = DlmsUnit.DEGREE_CELSIUS; + } + } + } + } + + private void decodeE0(int vif) { + // E0 + if ((vif & 0x20) == 0) { + decodeE00(vif); + } else { + decode01(vif); + } + } + + private void decode01(int vif) { + // E01 + if ((vif & 0x10) == 0) { + // E010 + if ((vif & 0x08) == 0) { + // E010 0 + if ((vif & 0x04) == 0) { + // E010 00 + description = Description.ON_TIME; + } else { + // E010 01 + description = Description.OPERATING_TIME; + } + decodeTimeUnit(vif); + } else { + // E010 1 + description = Description.POWER; + multiplierExponent = (vif & 0x07) - 3; + unit = DlmsUnit.WATT; + } + } else { + // E011 + if ((vif & 0x08) == 0) { + // E011 0 + description = Description.POWER; + multiplierExponent = vif & 0x07; + unit = DlmsUnit.JOULE_PER_HOUR; + } else { + // E011 1 + description = Description.VOLUME_FLOW; + multiplierExponent = (vif & 0x07) - 6; + unit = DlmsUnit.CUBIC_METRE_PER_HOUR; + } + } + } + + private void decodeE00(int vif) { + // E00 + if ((vif & 0x10) == 0) { + // E000 + if ((vif & 0x08) == 0) { + // E000 0 + description = Description.ENERGY; + multiplierExponent = (vif & 0x07) - 3; + unit = DlmsUnit.WATT_HOUR; + } else { + // E000 1 + description = Description.ENERGY; + multiplierExponent = vif & 0x07; + unit = DlmsUnit.JOULE; + } + } else { + // E001 + if ((vif & 0x08) == 0) { + // E001 0 + description = Description.VOLUME; + multiplierExponent = (vif & 0x07) - 6; + unit = DlmsUnit.CUBIC_METRE; + } else { + // E001 1 + description = Description.MASS; + multiplierExponent = (vif & 0x07) - 3; + unit = DlmsUnit.KILOGRAM; + } + } + } + + // implements table 28 of DIN EN 13757-3:2013 + private void decodeMainExtendedVif(byte vif) throws DecodingException { + if ((vif & 0x7f) == 0x0b) { // E000 1011 + description = Description.PARAMETER_SET_ID; + } else if ((vif & 0x7f) == 0x0c) { // E000 1100 + description = Description.MODEL_VERSION; + } else if ((vif & 0x7f) == 0x0d) { // E000 1101 + description = Description.HARDWARE_VERSION; + } else if ((vif & 0x7f) == 0x0e) { // E000 1110 + description = Description.FIRMWARE_VERSION; + } else if ((vif & 0x7f) == 0x0f) { // E000 1111 + description = Description.OTHER_SOFTWARE_VERSION; + } else if ((vif & 0x7f) == 0x10) { // E001 0000 + description = Description.CUSTOMER_LOCATION; + } else if ((vif & 0x7f) == 0x11) { // E001 0001 + description = Description.CUSTOMER; + } else if ((vif & 0x7f) == 0x12) { // E001 0010 + description = Description.ACCSESS_CODE_USER; + } else if ((vif & 0x7f) == 0x13) { // E001 0011 + description = Description.ACCSESS_CODE_OPERATOR; + } else if ((vif & 0x7f) == 0x14) { // E001 0100 + description = Description.ACCSESS_CODE_SYSTEM_OPERATOR; + } else if ((vif & 0x7f) == 0x15) { // E001 0101 + description = Description.ACCSESS_CODE_SYSTEM_DEVELOPER; + } else if ((vif & 0x7f) == 0x16) { // E001 0110 + description = Description.PASSWORD; + } else if ((vif & 0x7f) == 0x17) { // E001 0111 + description = Description.ERROR_FLAGS; + } else if ((vif & 0x7f) == 0x18) { // E001 1000 + description = Description.ERROR_MASK; + } else if ((vif & 0x7f) == 0x19) { // E001 1001 + description = Description.SECURITY_KEY; + } else if ((vif & 0x7f) == 0x1a) { // E001 1010 + description = Description.DIGITAL_OUTPUT; + } else if ((vif & 0x7f) == 0x1b) { // E001 1011 + description = Description.DIGITAL_INPUT; + } else if ((vif & 0x7f) == 0x1c) { // E001 1100 + description = Description.BAUDRATE; + } else if ((vif & 0x7f) == 0x1d) { // E001 1101 + description = Description.RESPONSE_DELAY_TIME; + } else if ((vif & 0x7f) == 0x1e) { // E001 1110 + description = Description.RETRY; + } else if ((vif & 0x7f) == 0x1f) { // E001 1111 + description = Description.REMOTE_CONTROL; + } else if ((vif & 0x7f) == 0x20) { // E010 0000 + description = Description.FIRST_STORAGE_NUMBER_CYCLIC; + } else if ((vif & 0x7f) == 0x21) { // E010 0001 + description = Description.LAST_STORAGE_NUMBER_CYCLIC; + } else if ((vif & 0x7f) == 0x22) { // E010 0010 + description = Description.SIZE_STORAGE_BLOCK; + } else if ((vif & 0x7f) == 0x23) { // E010 0011 + description = Description.RESERVED; + } else if ((vif & 0x7c) == 0x24) { // E010 01nn + description = Description.STORAGE_INTERVALL; + this.unit = unitFor(vif); + } else if ((vif & 0x7f) == 0x28) { // E010 1000 + description = Description.STORAGE_INTERVALL; + unit = DlmsUnit.MONTH; + } else if ((vif & 0x7f) == 0x29) { // E010 1001 + description = Description.STORAGE_INTERVALL; + unit = DlmsUnit.YEAR; + } else if ((vif & 0x7f) == 0x2a) { // E010 1010 + description = Description.OPERATOR_SPECIFIC_DATA; + } else if ((vif & 0x7f) == 0x2b) { // E010 1011 + description = Description.TIME_POINT; + unit = DlmsUnit.SECOND; + } else if ((vif & 0x7c) == 0x2c) { // E010 11nn + description = Description.DURATION_LAST_READOUT; + this.unit = unitFor(vif); + } else if ((vif & 0x7c) == 0x30) { // E011 00nn + description = Description.TARIF_DURATION; + switch (vif & 0x03) { + case 0: // E011 0000 + description = Description.NOT_SUPPORTED; // TODO: TARIF_START (Date/Time) + break; + default: + this.unit = unitFor(vif); + } + } else if ((vif & 0x7c) == 0x34) { // E011 01nn + description = Description.TARIF_PERIOD; + this.unit = unitFor(vif); + } else if ((vif & 0x7f) == 0x38) { // E011 1000 + description = Description.TARIF_PERIOD; + unit = DlmsUnit.MONTH; + } else if ((vif & 0x7f) == 0x39) { // E011 1001 + description = Description.TARIF_PERIOD; + unit = DlmsUnit.YEAR; + } else if ((vif & 0x70) == 0x40) { // E100 0000 + description = Description.VOLTAGE; + multiplierExponent = (vif & 0x0f) - 9; + unit = DlmsUnit.VOLT; + } else if ((vif & 0x70) == 0x50) { // E101 0000 + description = Description.CURRENT; + multiplierExponent = (vif & 0x0f) - 12; + unit = DlmsUnit.AMPERE; + } else if ((vif & 0x7f) == 0x60) { // E110 0000 + description = Description.RESET_COUNTER; + } else if ((vif & 0x7f) == 0x61) { // E110 0001 + description = Description.CUMULATION_COUNTER; + } else if ((vif & 0x7f) == 0x62) { // E110 0010 + description = Description.CONTROL_SIGNAL; + } else if ((vif & 0x7f) == 0x63) { // E110 0011 + description = Description.DAY_OF_WEEK; // 1 = Monday; 7 = Sunday; 0 = all Days + } else if ((vif & 0x7f) == 0x64) { // E110 0100 + description = Description.WEEK_NUMBER; + } else if ((vif & 0x7f) == 0x65) { // E110 0101 + description = Description.TIME_POINT_DAY_CHANGE; + } else if ((vif & 0x7f) == 0x66) { // E110 0110 + description = Description.PARAMETER_ACTIVATION_STATE; + } else if ((vif & 0x7f) == 0x67) { // E110 0111 + description = Description.SPECIAL_SUPPLIER_INFORMATION; + } else if ((vif & 0x7c) == 0x68) { // E110 10nn + description = Description.LAST_CUMULATION_DURATION; + this.unit = unitBiggerFor(vif); + } else if ((vif & 0x7c) == 0x6c) { // E110 11nn + description = Description.OPERATING_TIME_BATTERY; + this.unit = unitBiggerFor(vif); + } else if ((vif & 0x7f) == 0x70) { // E111 0000 + description = Description.NOT_SUPPORTED; // TODO: BATTERY_CHANGE_DATE_TIME + } else if ((vif & 0x7f) == 0x71) { // E111 0001 + description = Description.NOT_SUPPORTED; // TODO: RF_LEVEL dBm + } else if ((vif & 0x7f) == 0x72) { // E111 0010 + description = Description.NOT_SUPPORTED; // TODO: DAYLIGHT_SAVING (begin, ending, deviation) + } else if ((vif & 0x7f) == 0x73) { // E111 0011 + description = Description.NOT_SUPPORTED; // TODO: Listening window management data type L + } else if ((vif & 0x7f) == 0x74) { // E111 0100 + description = Description.REMAINING_BATTERY_LIFE_TIME; + unit = DlmsUnit.DAY; + } else if ((vif & 0x7f) == 0x75) { // E111 0101 + description = Description.NUMBER_STOPS; + } else if ((vif & 0x7f) == 0x76) { // E111 0110 + description = Description.MANUFACTURER_SPECIFIC; + } else if ((vif & 0x7f) >= 0x77) { // E111 0111 - E111 1111 + description = Description.RESERVED; + } else { + description = Description.NOT_SUPPORTED; + } + } + + private static DlmsUnit unitBiggerFor(byte vif) throws DecodingException { + int u = vif & 0x03; + switch (u) { + case 0: // E110 1100 + return DlmsUnit.HOUR; + case 1: // E110 1101 + return DlmsUnit.DAY; + case 2: // E110 1110 + return DlmsUnit.MONTH; + case 3: // E110 1111 + return DlmsUnit.YEAR; + default: + throw new DecodingException(String.format("Unknown unit 0x%02X.", u)); + } + } + + private static DlmsUnit unitFor(byte vif) throws DecodingException { + int u = vif & 0x03; + switch (u) { + case 0: // E010 1100 + return DlmsUnit.SECOND; + case 1: // E010 1101 + return DlmsUnit.MIN; + case 2: // E010 1110 + return DlmsUnit.HOUR; + case 3: // E010 1111 + return DlmsUnit.DAY; + default: + throw new DecodingException(String.format("Unknown unit 0x%02X.", u)); + } + } + + // implements table 29 of DIN EN 13757-3:2011 + private void decodeAlternateExtendedVif(byte vif) { + description = Description.NOT_SUPPORTED; // default value + + if ((vif & 0x40) == 0) { + // E0 + if ((vif & 0x20) == 0) { + // E00 + if ((vif & 0x10) == 0) { + // E000 + if ((vif & 0x08) == 0) { + // E000 0 + if ((vif & 0x04) == 0) { + // E000 00 + if ((vif & 0x02) == 0) { + // E000 000 + description = Description.ENERGY; + multiplierExponent = 5 + (vif & 0x01); + unit = DlmsUnit.WATT_HOUR; + } else { + // E000 001 + description = Description.REACTIVE_ENERGY; + multiplierExponent = 3 + (vif & 0x01); + unit = DlmsUnit.VAR_HOUR; + } + + } else { + // E000 01 + if ((vif & 0x02) == 0) { + // E000 010 + description = Description.APPARENT_ENERGY; + multiplierExponent = 3 + (vif & 0x01); + unit = DlmsUnit.VOLT_AMPERE_HOUR; + } else { + // E000 011 + description = Description.NOT_SUPPORTED; + } + } + } else { + // E000 1 + if ((vif & 0x04) == 0) { + // E000 10 + if ((vif & 0x02) == 0) { + // E000 100 + description = Description.ENERGY; + multiplierExponent = 8 + (vif & 0x01); + unit = DlmsUnit.JOULE; + } else { + // E000 101 + description = Description.NOT_SUPPORTED; + } + + } else { + // E000 11 + description = Description.ENERGY; + multiplierExponent = 5 + (vif & 0x03); + unit = DlmsUnit.CALORIFIC_VALUE; + } + } + } else { + // E001 + if ((vif & 0x08) == 0) { + // E001 0 + if ((vif & 0x04) == 0) { + // E001 00 + if ((vif & 0x02) == 0) { + // E001 000 + description = Description.VOLUME; + multiplierExponent = 2 + (vif & 0x01); + unit = DlmsUnit.CUBIC_METRE; + } else { + // E001 001 + description = Description.NOT_SUPPORTED; + } + } else { + // E001 01 + description = Description.REACTIVE_POWER; + multiplierExponent = (vif & 0x03); + unit = DlmsUnit.VAR; + } + } else { + // E001 1 + if ((vif & 0x04) == 0) { + // E001 10 + if ((vif & 0x02) == 0) { + // E001 100 + description = Description.MASS; + multiplierExponent = 5 + (vif & 0x01); + unit = DlmsUnit.KILOGRAM; + } else { + // E001 101 + description = Description.REL_HUMIDITY; + multiplierExponent = -1 + (vif & 0x01); + unit = DlmsUnit.PERCENTAGE; + } + + } else { + // E001 11 + description = Description.NOT_SUPPORTED; + } + } + + } + } else { + // E01 + if ((vif & 0x10) == 0) { + // E010 + if ((vif & 0x08) == 0) { + // E010 0 + if ((vif & 0x04) == 0) { + // E010 00 + if ((vif & 0x02) == 0) { + // E010 000 + if ((vif & 0x01) == 0) { + // E010 0000 + description = Description.VOLUME; + multiplierExponent = 0; + unit = DlmsUnit.CUBIC_FEET; + } else { + // E010 0001 + description = Description.VOLUME; + multiplierExponent = -1; + unit = DlmsUnit.CUBIC_FEET; + } + } else { + // E010 001 + // outdated value ! + description = Description.VOLUME; + multiplierExponent = -1 + (vif & 0x01); + unit = DlmsUnit.US_GALLON; + } + } else { + // E010 01 + if ((vif & 0x02) == 0) { + // E010 010 + if ((vif & 0x01) == 0) { + // E010 0100 + // outdated value ! + description = Description.VOLUME_FLOW; + multiplierExponent = -3; + unit = DlmsUnit.US_GALLON_PER_MINUTE; + } else { + // E010 0101 + // outdated value ! + description = Description.VOLUME_FLOW; + multiplierExponent = 0; + unit = DlmsUnit.US_GALLON_PER_MINUTE; + } + } else { + // E010 011 + if ((vif & 0x01) == 0) { + // E010 0110 + // outdated value ! + description = Description.VOLUME_FLOW; + multiplierExponent = 0; + unit = DlmsUnit.US_GALLON_PER_HOUR; + } else { + // E010 0111 + description = Description.NOT_SUPPORTED; + } + } + + } + } else { + // E010 1 + if ((vif & 0x04) == 0) { + // E010 10 + if ((vif & 0x02) == 0) { + // E010 100 + description = Description.POWER; + multiplierExponent = 5 + (vif & 0x01); + unit = DlmsUnit.WATT; + } else { + if ((vif & 0x01) == 0) { + // E010 1010 + description = Description.PHASE; + multiplierExponent = -1; // is -1 or 0 correct ?? + unit = DlmsUnit.DEGREE; + } + // TODO same + // else { + // // E010 1011 + // description = Description.PHASE; + // multiplierExponent = -1; // is -1 or 0 correct ?? + // unit = DlmsUnit.DEGREE; + // } + } + } else { + // E010 11 + description = Description.FREQUENCY; + multiplierExponent = -3 + (vif & 0x03); + unit = DlmsUnit.HERTZ; + } + } + } else { + // E011 + if ((vif & 0x08) == 0) { + // E011 0 + if ((vif & 0x04) == 0) { + // E011 00 + if ((vif & 0x02) == 0) { + // E011 000 + description = Description.POWER; + multiplierExponent = 8 + (vif & 0x01); + unit = DlmsUnit.JOULE_PER_HOUR; + } else { + // E011 001 + description = Description.NOT_SUPPORTED; + } + } else { + // E011 01 + description = Description.APPARENT_ENERGY; + multiplierExponent = (vif & 0x03); + unit = DlmsUnit.VOLT_AMPERE; + } + } else { + // E011 1 + description = Description.NOT_SUPPORTED; + } + } + } + } else { + // E1 + if ((vif & 0x20) == 0) { + // E10 + if ((vif & 0x10) == 0) { + // E100 + description = Description.NOT_SUPPORTED; + } else { + // E101 + if ((vif & 0x08) == 0) { + // E101 0 + description = Description.NOT_SUPPORTED; + } else { + // E101 1 + if ((vif & 0x04) == 0) { + // E101 10 + // outdated value ! + description = Description.FLOW_TEMPERATURE; + multiplierExponent = (vif & 0x03) - 3; + unit = DlmsUnit.DEGREE_FAHRENHEIT; + } else { + // E101 11 + // outdated value ! + description = Description.RETURN_TEMPERATURE; + multiplierExponent = (vif & 0x03) - 3; + unit = DlmsUnit.DEGREE_FAHRENHEIT; + } + } + } + } else { + // E11 + if ((vif & 0x10) == 0) { + // E110 + if ((vif & 0x08) == 0) { + // E110 0 + if ((vif & 0x04) == 0) { + // E110 00 + // outdated value ! + description = Description.TEMPERATURE_DIFFERENCE; + multiplierExponent = (vif & 0x03) - 3; + unit = DlmsUnit.DEGREE_FAHRENHEIT; + } else { + // E110 01 + // outdated value ! + description = Description.FLOW_TEMPERATURE; + multiplierExponent = (vif & 0x03) - 3; + unit = DlmsUnit.DEGREE_FAHRENHEIT; + } + } else { + // E110 1 + description = Description.NOT_SUPPORTED; + } + } else { + // E111 + if ((vif & 0x08) == 0) { + // E111 0 + if ((vif & 0x04) == 0) { + // E111 00 + // outdated value ! + description = Description.TEMPERATURE_LIMIT; + multiplierExponent = (vif & 0x03) - 3; + unit = DlmsUnit.DEGREE_FAHRENHEIT; + } else { + // E111 01 + description = Description.TEMPERATURE_LIMIT; + multiplierExponent = (vif & 0x03) - 3; + unit = DlmsUnit.DEGREE_CELSIUS; + } + } else { + // E111 1 + description = Description.MAX_POWER; + multiplierExponent = (vif & 0x07) - 3; + unit = DlmsUnit.WATT; + } + } + } + + } + } + + @Override + public String toString() { + + StringBuilder builder = new StringBuilder().append("DIB:").append(printHexBinary(dib)).append(", VIB:") + .append(printHexBinary(vib)).append(" -> descr:").append(description); + + if (description == Description.USER_DEFINED) { + builder.append(" :").append(getUserDefinedDescription()); + } + builder.append(", function:").append(functionField); + + if (storageNumber > 0) { + builder.append(", storage:").append(storageNumber); + } + + if (tariff > 0) { + builder.append(", tariff:").append(tariff); + } + + if (subunit > 0) { + builder.append(", subunit:").append(subunit); + } + + final String valuePlacHolder = ", value:"; + final String scaledValueString = ", scaled value:"; + + switch (dataValueType) { + case DATE: + case STRING: + builder.append(valuePlacHolder).append((dataValue).toString()); + break; + case DOUBLE: + builder.append(scaledValueString).append(getScaledDataValue()); + break; + case LONG: + if (multiplierExponent == 0) { + builder.append(valuePlacHolder).append(dataValue); + } else { + builder.append(scaledValueString).append(getScaledDataValue()); + } + break; + case BCD: + if (multiplierExponent == 0) { + builder.append(valuePlacHolder).append((dataValue).toString()); + } else { + builder.append(scaledValueString).append(getScaledDataValue()); + } + break; + case NONE: + builder.append(", value:NONE"); + break; + } + + if (unit != null) { + builder.append(", unit:").append(unit); + if (!unit.getUnit().isEmpty()) { + builder.append(", ").append(unit.getUnit()); + } + } + + return builder.toString(); + } + + public int getDataLength() { + return dataLength; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DecodingException.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DecodingException.java index 5dc0544..b7b3aa6 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DecodingException.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DecodingException.java @@ -1,26 +1,26 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus; - -/** - * Signals that a M-Bus message could not be decoded. - */ -public class DecodingException extends Exception { - - private static final long serialVersionUID = 1735527302166708223L; - - public DecodingException(String msg) { - super(msg); - } - - public DecodingException(Throwable cause) { - super(cause); - } - - public DecodingException(String msg, Throwable cause) { - super(msg, cause); - } -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus; + +/** + * Signals that a M-Bus message could not be decoded. + */ +public class DecodingException extends Exception { + + private static final long serialVersionUID = 1735527302166708223L; + + public DecodingException(String msg) { + super(msg); + } + + public DecodingException(Throwable cause) { + super(cause); + } + + public DecodingException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DeviceType.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DeviceType.java index c69aa81..1ba052f 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DeviceType.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DeviceType.java @@ -1,122 +1,122 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -package org.openmuc.jmbus; - -import java.util.HashMap; -import java.util.Map; - -/** - * The device type that is part of the data header of a Variable Data Response. - */ -public enum DeviceType { - OTHER(0x00), - OIL_METER(0x01), - ELECTRICITY_METER(0x02), - GAS_METER(0x03), - HEAT_METER(0x04), - STEAM_METER(0x05), - WARM_WATER_METER(0x06), - WATER_METER(0x07), - HEAT_COST_ALLOCATOR(0x08), - COMPRESSED_AIR(0x09), - COOLING_METER_OUTLET(0x0a), - COOLING_METER_INLET(0x0b), - HEAT_METER_INLET(0x0c), - HEAT_COOLING_METER(0x0d), - BUS_SYSTEM_COMPONENT(0x0e), - UNKNOWN(0x0f), - RESERVED_FOR_METER_16(0x10), - RESERVED_FOR_METER_17(0x11), - RESERVED_FOR_METER_18(0x12), - RESERVED_FOR_METER_19(0x13), - CALORIFIC_VALUE(0x14), - HOT_WATER_METER(0x15), - COLD_WATER_METER(0x16), - DUAL_REGISTER_WATER_METER(0x17), - PRESSURE_METER(0x18), - AD_CONVERTER(0x19), - SMOKE_DETECTOR(0x1a), - ROOM_SENSOR_TEMP_HUM(0x1b), - GAS_DETECTOR(0x1c), - RESERVED_FOR_SENSOR_0X1D(0x1d), - RESERVED_FOR_SENSOR_0X1E(0x1e), - RESERVED_FOR_SENSOR_0X1F(0x1f), - BREAKER_ELEC(0x20), - VALVE_GAS_OR_WATER(0x21), - RESERVED_FOR_SWITCHING_DEVICE_0X22(0x22), - RESERVED_FOR_SWITCHING_DEVICE_0X23(0x23), - RESERVED_FOR_SWITCHING_DEVICE_0X24(0x24), - CUSTOMER_UNIT_DISPLAY_DEVICE(0x25), - RESERVED_FOR_CUSTOMER_UNIT_0X26(0x26), - RESERVED_FOR_CUSTOMER_UNIT_0X27(0x27), - WASTE_WATER_METER(0x28), - GARBAGE(0x29), - RESERVED_FOR_CO2(0x2a), - RESERVED_FOR_ENV_METER_0X2B(0x2b), - RESERVED_FOR_ENV_METER_0X2C(0x2c), - RESERVED_FOR_ENV_METER_0X2D(0x2d), - RESERVED_FOR_ENV_METER_0X2E(0x2e), - RESERVED_FOR_ENV_METER_0X2F(0x2f), - RESERVED_FOR_SYSTEM_DEVICES_0X30(0x30), - COM_CONTROLLER(0x31), - UNIDIRECTION_REPEATER(0x32), - BIDIRECTION_REPEATER(0x33), - RESERVED_FOR_SYSTEM_DEVICES_0X34(0x34), - RESERVED_FOR_SYSTEM_DEVICES_0X35(0x35), - RADIO_CONVERTER_SYSTEM_SIDE(0x36), - RADIO_CONVERTER_METER_SIDE(0x37), - RESERVED_FOR_SYSTEM_DEVICES_0X38(0x38), - RESERVED_FOR_SYSTEM_DEVICES_0X39(0x39), - RESERVED_FOR_SYSTEM_DEVICES_0X3A(0x3a), - RESERVED_FOR_SYSTEM_DEVICES_0X3B(0x3b), - RESERVED_FOR_SYSTEM_DEVICES_0X3C(0x3c), - RESERVED_FOR_SYSTEM_DEVICES_0X3D(0x3d), - RESERVED_FOR_SYSTEM_DEVICES_0X3E(0x3e), - RESERVED_FOR_SYSTEM_DEVICES_0X3F(0x3f), - RESERVED(0xff); - - private final int id; - - private static final Map idMap = new HashMap<>(); - - static { - for (DeviceType enumInstance : DeviceType.values()) { - if (idMap.put(enumInstance.getId(), enumInstance) != null) { - throw new IllegalArgumentException("duplicate ID: " + enumInstance.getId()); - } - } - } - - private DeviceType(int id) { - this.id = id; - } - - /** - * Returns the ID of this DeviceType. - * - * @return the ID - */ - public int getId() { - return id; - } - - /** - * Returns the DeviceType that corresponds to the given ID. Returns DeviceType.RESERVED if no DeviceType with the - * given ID exists. - * - * @param id - * the ID - * @return the DeviceType that corresponds to the given ID - */ - public static DeviceType getInstance(int id) { - DeviceType enumInstance = idMap.get(id); - if (enumInstance == null) { - enumInstance = DeviceType.RESERVED; - } - return enumInstance; - } -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.openmuc.jmbus; + +import java.util.HashMap; +import java.util.Map; + +/** + * The device type that is part of the data header of a Variable Data Response. + */ +public enum DeviceType { + OTHER(0x00), + OIL_METER(0x01), + ELECTRICITY_METER(0x02), + GAS_METER(0x03), + HEAT_METER(0x04), + STEAM_METER(0x05), + WARM_WATER_METER(0x06), + WATER_METER(0x07), + HEAT_COST_ALLOCATOR(0x08), + COMPRESSED_AIR(0x09), + COOLING_METER_OUTLET(0x0a), + COOLING_METER_INLET(0x0b), + HEAT_METER_INLET(0x0c), + HEAT_COOLING_METER(0x0d), + BUS_SYSTEM_COMPONENT(0x0e), + UNKNOWN(0x0f), + RESERVED_FOR_METER_16(0x10), + RESERVED_FOR_METER_17(0x11), + RESERVED_FOR_METER_18(0x12), + RESERVED_FOR_METER_19(0x13), + CALORIFIC_VALUE(0x14), + HOT_WATER_METER(0x15), + COLD_WATER_METER(0x16), + DUAL_REGISTER_WATER_METER(0x17), + PRESSURE_METER(0x18), + AD_CONVERTER(0x19), + SMOKE_DETECTOR(0x1a), + ROOM_SENSOR_TEMP_HUM(0x1b), + GAS_DETECTOR(0x1c), + RESERVED_FOR_SENSOR_0X1D(0x1d), + RESERVED_FOR_SENSOR_0X1E(0x1e), + RESERVED_FOR_SENSOR_0X1F(0x1f), + BREAKER_ELEC(0x20), + VALVE_GAS_OR_WATER(0x21), + RESERVED_FOR_SWITCHING_DEVICE_0X22(0x22), + RESERVED_FOR_SWITCHING_DEVICE_0X23(0x23), + RESERVED_FOR_SWITCHING_DEVICE_0X24(0x24), + CUSTOMER_UNIT_DISPLAY_DEVICE(0x25), + RESERVED_FOR_CUSTOMER_UNIT_0X26(0x26), + RESERVED_FOR_CUSTOMER_UNIT_0X27(0x27), + WASTE_WATER_METER(0x28), + GARBAGE(0x29), + RESERVED_FOR_CO2(0x2a), + RESERVED_FOR_ENV_METER_0X2B(0x2b), + RESERVED_FOR_ENV_METER_0X2C(0x2c), + RESERVED_FOR_ENV_METER_0X2D(0x2d), + RESERVED_FOR_ENV_METER_0X2E(0x2e), + RESERVED_FOR_ENV_METER_0X2F(0x2f), + RESERVED_FOR_SYSTEM_DEVICES_0X30(0x30), + COM_CONTROLLER(0x31), + UNIDIRECTION_REPEATER(0x32), + BIDIRECTION_REPEATER(0x33), + RESERVED_FOR_SYSTEM_DEVICES_0X34(0x34), + RESERVED_FOR_SYSTEM_DEVICES_0X35(0x35), + RADIO_CONVERTER_SYSTEM_SIDE(0x36), + RADIO_CONVERTER_METER_SIDE(0x37), + RESERVED_FOR_SYSTEM_DEVICES_0X38(0x38), + RESERVED_FOR_SYSTEM_DEVICES_0X39(0x39), + RESERVED_FOR_SYSTEM_DEVICES_0X3A(0x3a), + RESERVED_FOR_SYSTEM_DEVICES_0X3B(0x3b), + RESERVED_FOR_SYSTEM_DEVICES_0X3C(0x3c), + RESERVED_FOR_SYSTEM_DEVICES_0X3D(0x3d), + RESERVED_FOR_SYSTEM_DEVICES_0X3E(0x3e), + RESERVED_FOR_SYSTEM_DEVICES_0X3F(0x3f), + RESERVED(0xff); + + private final int id; + + private static final Map idMap = new HashMap<>(); + + static { + for (DeviceType enumInstance : DeviceType.values()) { + if (idMap.put(enumInstance.getId(), enumInstance) != null) { + throw new IllegalArgumentException("duplicate ID: " + enumInstance.getId()); + } + } + } + + private DeviceType(int id) { + this.id = id; + } + + /** + * Returns the ID of this DeviceType. + * + * @return the ID + */ + public int getId() { + return id; + } + + /** + * Returns the DeviceType that corresponds to the given ID. Returns DeviceType.RESERVED if no DeviceType with the + * given ID exists. + * + * @param id + * the ID + * @return the DeviceType that corresponds to the given ID + */ + public static DeviceType getInstance(int id) { + DeviceType enumInstance = idMap.get(id); + if (enumInstance == null) { + enumInstance = DeviceType.RESERVED; + } + return enumInstance; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DlmsUnit.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DlmsUnit.java index eed1598..3efbf97 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DlmsUnit.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DlmsUnit.java @@ -1,146 +1,146 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus; - -import java.util.HashMap; -import java.util.Map; - -/** - * The units as defined in IEC 62056-6-2. Some units not defined in IEC 62056-6-2 but needed by M-Bus were added. - */ -public enum DlmsUnit { - // can be found in IEC 62056-6-2 2013 Capture 5.2.2 - YEAR(1, "a"), - MONTH(2, "mo"), - WEEK(3, "wk"), - DAY(4, "d"), - HOUR(5, "h"), - MIN(6, "min"), - SECOND(7, "s"), - DEGREE(8, "°"), - DEGREE_CELSIUS(9, "°C"), - CURRENCY(10, ""), - METRE(11, "m"), - METRE_PER_SECOND(12, "m/s"), - CUBIC_METRE(13, "m³"), - CUBIC_METRE_CORRECTED(14, "m³"), - CUBIC_METRE_PER_HOUR(15, "m³/h"), - CUBIC_METRE_PER_HOUR_CORRECTED(16, "m³/h"), - CUBIC_METRE_PER_DAY(17, "m³/d"), - CUBIC_METRE_PER_DAY_CORRECTED(18, "m³/d"), - LITRE(19, "l"), - KILOGRAM(20, "kg"), - NEWTON(21, "N"), - NEWTONMETER(22, "n"), - PASCAL(23, "Nm"), - BAR(24, "bar"), - JOULE(25, "J"), - JOULE_PER_HOUR(26, "J/h"), - WATT(27, "W"), - VOLT_AMPERE(28, "VA"), - VAR(29, "var"), - WATT_HOUR(30, "Wh"), - VOLT_AMPERE_HOUR(31, "VAh"), - VAR_HOUR(32, "varh"), - AMPERE(33, "A"), - COULOMB(34, "C"), - VOLT(35, "V"), - VOLT_PER_METRE(36, "V/m"), - FARAD(37, "F"), - OHM(38, "Ohm"), - OHM_METRE(39, "Ohm m²/m"), - WEBER(40, "Wb"), - TESLA(41, "T"), - AMPERE_PER_METRE(42, "A/m"), - HENRY(43, "H"), - HERTZ(44, "Hz"), - ACTIVE_ENERGY_METER_CONSTANT_OR_PULSE_VALUE(45, "1/(Wh)"), - REACTIVE_ENERGY_METER_CONSTANT_OR_PULSE_VALUE(46, "1/(varh)"), - APPARENT_ENERGY_METER_CONSTANT_OR_PULSE_VALUE(47, "1(VAh)"), - VOLT_SQUARED_HOURS(48, "V²h"), - AMPERE_SQUARED_HOURS(49, "A²h"), - KILOGRAM_PER_SECOND(50, "kg/s"), - KELVIN(52, "S"), - VOLT_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE(53, "K"), - AMPERE_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE(54, "1/(V²h)"), - METER_CONSTANT_OR_PULSE_VALUE(55, "1/(A²h)"), - PERCENTAGE(56, "%"), - AMPERE_HOUR(57, "Ah"), - - ENERGY_PER_VOLUME(60, "Wh/m³"), - CALORIFIC_VALUE(61, "J/m³"), - MOLE_PERCENT(62, "Mol %"), - MASS_DENSITY(63, "g/m³"), - PASCAL_SECOND(64, "Pa s"), - SPECIFIC_ENERGY(65, "J/kg"), - - SIGNAL_STRENGTH(70, "dBm"), - - RESERVED(253, ""), - OTHER_UNIT(254, ""), - COUNT(255, ""), - // not mentioned in 62056, added for MBus: - CUBIC_METRE_PER_SECOND(150, "m³/s"), - CUBIC_METRE_PER_MINUTE(151, "m³/min"), - KILOGRAM_PER_HOUR(152, "kg/h"), - CUBIC_FEET(153, "cft"), - US_GALLON(154, "Impl. gal."), - US_GALLON_PER_MINUTE(155, "Impl. gal./min"), - US_GALLON_PER_HOUR(156, "Impl. gal./h"), - DEGREE_FAHRENHEIT(157, "°F"); - - private final int id; - private final String unit; - - private static final Map idMap = new HashMap<>(); - - static { - for (DlmsUnit enumInstance : DlmsUnit.values()) { - if (idMap.put(enumInstance.getId(), enumInstance) != null) { - throw new IllegalArgumentException("duplicate ID: " + enumInstance.getId()); - } - } - } - - private DlmsUnit(int id, String unit) { - this.id = id; - this.unit = unit; - } - - /** - * Returns the ID of this DlmsUnit. - * - * @return the ID - */ - public int getId() { - return id; - } - - /** - * Returns the unit sign of this DlmsUnit. - * - * @return the ID - */ - public String getUnit() { - return unit; - } - - /** - * Returns the DlmsUnit that corresponds to the given ID. Returns DlmsUnit.RESERVED if no DlmsUnit with the given ID - * exists. - * - * @param id - * the ID - * @return the DlmsUnit that corresponds to the given ID - */ - public static DlmsUnit getInstance(int id) { - DlmsUnit enumInstance = idMap.get(id); - if (enumInstance == null) { - enumInstance = DlmsUnit.RESERVED; - } - return enumInstance; - } -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus; + +import java.util.HashMap; +import java.util.Map; + +/** + * The units as defined in IEC 62056-6-2. Some units not defined in IEC 62056-6-2 but needed by M-Bus were added. + */ +public enum DlmsUnit { + // can be found in IEC 62056-6-2 2013 Capture 5.2.2 + YEAR(1, "a"), + MONTH(2, "mo"), + WEEK(3, "wk"), + DAY(4, "d"), + HOUR(5, "h"), + MIN(6, "min"), + SECOND(7, "s"), + DEGREE(8, "°"), + DEGREE_CELSIUS(9, "°C"), + CURRENCY(10, ""), + METRE(11, "m"), + METRE_PER_SECOND(12, "m/s"), + CUBIC_METRE(13, "m³"), + CUBIC_METRE_CORRECTED(14, "m³"), + CUBIC_METRE_PER_HOUR(15, "m³/h"), + CUBIC_METRE_PER_HOUR_CORRECTED(16, "m³/h"), + CUBIC_METRE_PER_DAY(17, "m³/d"), + CUBIC_METRE_PER_DAY_CORRECTED(18, "m³/d"), + LITRE(19, "l"), + KILOGRAM(20, "kg"), + NEWTON(21, "N"), + NEWTONMETER(22, "n"), + PASCAL(23, "Nm"), + BAR(24, "bar"), + JOULE(25, "J"), + JOULE_PER_HOUR(26, "J/h"), + WATT(27, "W"), + VOLT_AMPERE(28, "VA"), + VAR(29, "var"), + WATT_HOUR(30, "Wh"), + VOLT_AMPERE_HOUR(31, "VAh"), + VAR_HOUR(32, "varh"), + AMPERE(33, "A"), + COULOMB(34, "C"), + VOLT(35, "V"), + VOLT_PER_METRE(36, "V/m"), + FARAD(37, "F"), + OHM(38, "Ohm"), + OHM_METRE(39, "Ohm m²/m"), + WEBER(40, "Wb"), + TESLA(41, "T"), + AMPERE_PER_METRE(42, "A/m"), + HENRY(43, "H"), + HERTZ(44, "Hz"), + ACTIVE_ENERGY_METER_CONSTANT_OR_PULSE_VALUE(45, "1/(Wh)"), + REACTIVE_ENERGY_METER_CONSTANT_OR_PULSE_VALUE(46, "1/(varh)"), + APPARENT_ENERGY_METER_CONSTANT_OR_PULSE_VALUE(47, "1(VAh)"), + VOLT_SQUARED_HOURS(48, "V²h"), + AMPERE_SQUARED_HOURS(49, "A²h"), + KILOGRAM_PER_SECOND(50, "kg/s"), + KELVIN(52, "S"), + VOLT_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE(53, "K"), + AMPERE_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE(54, "1/(V²h)"), + METER_CONSTANT_OR_PULSE_VALUE(55, "1/(A²h)"), + PERCENTAGE(56, "%"), + AMPERE_HOUR(57, "Ah"), + + ENERGY_PER_VOLUME(60, "Wh/m³"), + CALORIFIC_VALUE(61, "J/m³"), + MOLE_PERCENT(62, "Mol %"), + MASS_DENSITY(63, "g/m³"), + PASCAL_SECOND(64, "Pa s"), + SPECIFIC_ENERGY(65, "J/kg"), + + SIGNAL_STRENGTH(70, "dBm"), + + RESERVED(253, ""), + OTHER_UNIT(254, ""), + COUNT(255, ""), + // not mentioned in 62056, added for MBus: + CUBIC_METRE_PER_SECOND(150, "m³/s"), + CUBIC_METRE_PER_MINUTE(151, "m³/min"), + KILOGRAM_PER_HOUR(152, "kg/h"), + CUBIC_FEET(153, "cft"), + US_GALLON(154, "Impl. gal."), + US_GALLON_PER_MINUTE(155, "Impl. gal./min"), + US_GALLON_PER_HOUR(156, "Impl. gal./h"), + DEGREE_FAHRENHEIT(157, "°F"); + + private final int id; + private final String unit; + + private static final Map idMap = new HashMap<>(); + + static { + for (DlmsUnit enumInstance : DlmsUnit.values()) { + if (idMap.put(enumInstance.getId(), enumInstance) != null) { + throw new IllegalArgumentException("duplicate ID: " + enumInstance.getId()); + } + } + } + + private DlmsUnit(int id, String unit) { + this.id = id; + this.unit = unit; + } + + /** + * Returns the ID of this DlmsUnit. + * + * @return the ID + */ + public int getId() { + return id; + } + + /** + * Returns the unit sign of this DlmsUnit. + * + * @return the ID + */ + public String getUnit() { + return unit; + } + + /** + * Returns the DlmsUnit that corresponds to the given ID. Returns DlmsUnit.RESERVED if no DlmsUnit with the given ID + * exists. + * + * @param id + * the ID + * @return the DlmsUnit that corresponds to the given ID + */ + public static DlmsUnit getInstance(int id) { + DlmsUnit enumInstance = idMap.get(id); + if (enumInstance == null) { + enumInstance = DlmsUnit.RESERVED; + } + return enumInstance; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/EncryptionMode.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/EncryptionMode.java index ab856f9..626464a 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/EncryptionMode.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/EncryptionMode.java @@ -1,99 +1,99 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus; - -import java.util.HashMap; -import java.util.Map; - -/** - * The encryption modes. - */ -public enum EncryptionMode { - /** - * No encryption. - */ - NONE(0), - /** - * AES with Counter Mode (CTR) noPadding and IV. - */ - AES_128(1), - /** - * DES with Cipher Block Chaining Mode (CBC).
    - * Not supported yet. - */ - DES_CBC(2), - /** - * DES with Cipher Block Chaining Mode (CBC) and Initial Vector.
    - * Not supported yet. - */ - DES_CBC_IV(3), - RESERVED_04(4), - /** - * AES with Cipher Block Chaining Mode (CBC) and Initial Vector. - */ - AES_CBC_IV(5), - RESERVED_06(6), - /** - * AES 128 with Cipher Block Chaining Mode (CBC) and dynamic key and Initial Vector with 0.
    - * TR-03109-1 Anlage Feinspezifikation Drahtlose LMN Schnittstelle-Teil2
    - * Not supported yet. - */ - AES_CBC_IV_0(7), - RESERVED_08(8), - RESERVED_09(9), - RESERVED_10(10), - RESERVED_11(11), - RESERVED_12(12), - /** - * TLS 1.2
    - * TR-03109-1 Anlage Feinspezifikation Drahtlose LMN Schnittstelle-Teil2
    - * Not supported yet. - */ - TLS(13), - RESERVED_14(14), - RESERVED_15(15); - - private final int id; - - private static final Map idMap = new HashMap<>(); - - static { - for (EncryptionMode enumInstance : EncryptionMode.values()) { - if (idMap.put(enumInstance.getId(), enumInstance) != null) { - throw new IllegalArgumentException("duplicate ID: " + enumInstance.getId()); - } - } - } - - private EncryptionMode(int id) { - this.id = id; - } - - /** - * Returns the ID of this EncryptionMode. - * - * @return the ID - */ - public int getId() { - return id; - } - - /** - * Returns the EncryptionMode that corresponds to the given ID. Throws an IllegalArgumentException if no - * EncryptionMode with the given ID exists. - * - * @param id - * the ID - * @return the EncryptionMode that corresponds to the given ID - */ - public static EncryptionMode getInstance(int id) { - EncryptionMode enumInstance = idMap.get(id); - if (enumInstance == null) { - throw new IllegalArgumentException("invalid ID: " + id); - } - return enumInstance; - } -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus; + +import java.util.HashMap; +import java.util.Map; + +/** + * The encryption modes. + */ +public enum EncryptionMode { + /** + * No encryption. + */ + NONE(0), + /** + * AES with Counter Mode (CTR) noPadding and IV. + */ + AES_128(1), + /** + * DES with Cipher Block Chaining Mode (CBC).
    + * Not supported yet. + */ + DES_CBC(2), + /** + * DES with Cipher Block Chaining Mode (CBC) and Initial Vector.
    + * Not supported yet. + */ + DES_CBC_IV(3), + RESERVED_04(4), + /** + * AES with Cipher Block Chaining Mode (CBC) and Initial Vector. + */ + AES_CBC_IV(5), + RESERVED_06(6), + /** + * AES 128 with Cipher Block Chaining Mode (CBC) and dynamic key and Initial Vector with 0.
    + * TR-03109-1 Anlage Feinspezifikation Drahtlose LMN Schnittstelle-Teil2
    + * Not supported yet. + */ + AES_CBC_IV_0(7), + RESERVED_08(8), + RESERVED_09(9), + RESERVED_10(10), + RESERVED_11(11), + RESERVED_12(12), + /** + * TLS 1.2
    + * TR-03109-1 Anlage Feinspezifikation Drahtlose LMN Schnittstelle-Teil2
    + * Not supported yet. + */ + TLS(13), + RESERVED_14(14), + RESERVED_15(15); + + private final int id; + + private static final Map idMap = new HashMap<>(); + + static { + for (EncryptionMode enumInstance : EncryptionMode.values()) { + if (idMap.put(enumInstance.getId(), enumInstance) != null) { + throw new IllegalArgumentException("duplicate ID: " + enumInstance.getId()); + } + } + } + + private EncryptionMode(int id) { + this.id = id; + } + + /** + * Returns the ID of this EncryptionMode. + * + * @return the ID + */ + public int getId() { + return id; + } + + /** + * Returns the EncryptionMode that corresponds to the given ID. Throws an IllegalArgumentException if no + * EncryptionMode with the given ID exists. + * + * @param id + * the ID + * @return the EncryptionMode that corresponds to the given ID + */ + public static EncryptionMode getInstance(int id) { + EncryptionMode enumInstance = idMap.get(id); + if (enumInstance == null) { + throw new IllegalArgumentException("invalid ID: " + id); + } + return enumInstance; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/MBusConnection.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/MBusConnection.java index a0cd165..d2d329e 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/MBusConnection.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/MBusConnection.java @@ -1,522 +1,522 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.InterruptedIOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; -import java.util.List; - -import org.openmuc.jmbus.MBusMessage.MessageType; -import org.openmuc.jmbus.VerboseMessage.MessageDirection; -import org.openmuc.jmbus.transportlayer.SerialBuilder; -import org.openmuc.jmbus.transportlayer.TcpBuilder; -import org.openmuc.jmbus.transportlayer.TransportLayer; - -/** - * M-Bus Application Layer connection. - *

    - * Use this access point to communicate using the M-Bus wired protocol. - *

    - * - * @see MBusConnection#newSerialBuilder(String) - * @see MBusConnection#newTcpBuilder(String, int) - */ -public class MBusConnection implements AutoCloseable { - - // 261 is the maximum size of a long frame - private static final int MAX_MESSAGE_SIZE = 261; - - private final byte[] outputBuffer = new byte[MAX_MESSAGE_SIZE]; - - private final byte[] dataRecordsAsBytes = new byte[MAX_MESSAGE_SIZE]; - - private final boolean[] frameCountBits; - - private DataOutputStream os; - private DataInputStream is; - - private SecondaryAddress secondaryAddress; - - private VerboseMessageListener verboseMessageListener; - - private final TransportLayer transportLayer; - - /** - * Creates an M-Bus Service Access Point that is used to read meters. - * - * @param transportLayer - * Underlying transport layer - * @see MBusConnection#open() - */ - private MBusConnection(TransportLayer transportLayer) { - this.transportLayer = transportLayer; - - // set all frame bits to true - this.frameCountBits = new boolean[254]; - for (int i = 0; i < frameCountBits.length; i++) { - frameCountBits[i] = true; - } - } - - private void open() throws IOException { - try { - this.transportLayer.open(); - } catch (IOException e) { - this.transportLayer.close(); - throw e; - } - - this.os = transportLayer.getOutputStream(); - this.is = transportLayer.getInputStream(); - } - - /** - * Closes the service access point. - */ - @Override - public void close() { - transportLayer.close(); - } - - /** - * Sets the verbose mode on if a implementation of debugMessageListener has been set. - * - * @param verboseMessageListener - * Implementation of debugMessageListener - */ - public void setVerboseMessageListener(VerboseMessageListener verboseMessageListener) { - this.verboseMessageListener = verboseMessageListener; - } - - /** - * Scans for secondary addresses and returns all detected devices in a list and if SecondaryAddressListener not null - * to the listen listener. - * - * @param wildcardMask - * a wildcard mask for masking - * @param secondaryAddressListener - * listener to get scan messages and scanned secondary address just at time.
    - * If null, all detected address will only returned if finished. - * - * @return a list of secondary addresses of all detected devices - * @throws IOException - * if any kind of error (including timeout) occurs while writing to the remote device. Note that the - * connection is not closed when an IOException is thrown. - */ - public List scan(String wildcardMask, SecondaryAddressListener secondaryAddressListener) - throws IOException { - - if (wildcardMask == null || wildcardMask.isEmpty()) { - wildcardMask = "ffffffff"; - } - - return ScanSecondaryAddress.scan(this, wildcardMask, secondaryAddressListener); - } - - /** - * Reads a meter using primary addressing. Sends a data request (REQ_UD2) to the remote device and returns the - * variable data structure from the received RSP_UD frame. - * - * @param primaryAddress - * the primary address of the meter to read. For secondary address use 0xfd. - * @return the variable data structure from the received RSP_UD frame - * @throws InterruptedIOException - * if no response at all (not even a single byte) was received from the meter within the timeout span. - * @throws IOException - * if any kind of error (including timeout) occurs while trying to read the remote device. Note that the - * connection is not closed when an IOException is thrown. - * @throws InterruptedIOException - * if no response at all (not even a single byte) was received from the meter within the timeout span. - */ - public VariableDataStructure read(int primaryAddress) throws IOException, InterruptedIOException { - if (transportLayer.isClosed()) { - throw new IllegalStateException("Port is not open."); - } - - if (frameCountBits[primaryAddress]) { - sendShortMessage(primaryAddress, 0x7b); - frameCountBits[primaryAddress] = false; - } else { - sendShortMessage(primaryAddress, 0x5b); - frameCountBits[primaryAddress] = true; - } - - MBusMessage mBusMessage = receiveMessage(); - - if (mBusMessage.getMessageType() != MessageType.RSP_UD) { - throw new IOException( - "Received wrong kind of message. Expected RSP_UD but got: " + mBusMessage.getMessageType()); - } - - if (mBusMessage.getAddressField() != primaryAddress) { - // throw new IOException("Received RSP_UD message with unexpected address field. Expected " + primaryAddress - // + " but received " + mBusMessage.getAddressField()); - } - - try { - mBusMessage.getVariableDataResponse().decode(); - } catch (DecodingException e) { - throw new IOException("Error decoding incoming RSP_UD message.", e); - } - - return mBusMessage.getVariableDataResponse(); - } - - /** - * Sends a long message with individual parameters. Used for messages which arn't not predefined in - * {@link MBusConnection}.
    - * If no response message is expected, set hasResponse to false. Returns null if hasResponse is - * false - * - * @param primaryAddr - * the primary address of the meter to read. For secondary address use 0xfd. - * @param controlField - * control field (C Field) has the size of 1 byte. - * @param ci - * control information field (CI Field) has the size of 1 byte. - * @param data - * the data to sends to the meter. - * @param responseExpected - * set this flag to false if no response is expected else true. If false - * returns null - * @return returns null if boolean hasResponse is false.
    - * returns the {@link MBusMessage} if a message received. - * @throws IOException - * if any kind of error (including timeout) occurs while trying to send to or read the remote device. - * Note that the connection is not closed when an IOException is thrown. - */ - public MBusMessage sendLongMessage(int primaryAddr, int controlField, int ci, byte[] data, boolean responseExpected) - throws IOException { - MBusMessage mBusMessage = null; - - sendLongMessage(primaryAddr, controlField, ci, data.length, data); - - if (responseExpected) { - mBusMessage = receiveMessage(); - } - return mBusMessage; - } - - /** - * Sends a short message with individual parameters. Used for messages which arn't not predefined in - * {@link MBusConnection}.
    - * For normal readout use {@link MBusConnection#read(int)}.
    - * If no response message is expected, set hasResponse to false. Returns null if hasResponse is - * false - * - * @param primaryAddr - * the primary address of the meter to read. For secondary address use 0xfd. - * @param cmd - * the command to send to the meter. - * @param responseExpected - * set this flag to false if no response is expected else true. If false - * returns null - * @return returns null if boolean hasResponse is false.
    - * returns the {@link MBusMessage} if a message received. - * @throws IOException - * f any kind of error (including timeout) occurs while trying to send to or read the remote device. - * Note that the connection is not closed when an IOException is thrown. - */ - public MBusMessage sendShortMessage(int primaryAddr, int cmd, boolean responseExpected) throws IOException { - MBusMessage mBusMessage = null; - - sendShortMessage(primaryAddr, cmd); - - if (responseExpected) { - mBusMessage = receiveMessage(); - } - return mBusMessage; - } - - /** - * Writes to a meter using primary addressing. Sends a data send (SND_UD) to the remote device and returns a true if - * slave sends a 0x7e else false - * - * @param primaryAddress - * the primary address of the meter to write. For secondary address use 0xfd. - * @param data - * the data to sends to the meter. - * @throws IOException - * if any kind of error (including timeout) occurs while writing to the remote device. Note that the - * connection is not closed when an IOException is thrown. - * @throws InterruptedIOException - * if no response at all (not even a single byte) was received from the meter within the timeout span. - */ - public void write(int primaryAddress, byte[] data) throws IOException, InterruptedIOException { - if (data == null) { - data = new byte[0]; - } - - sendLongMessage(primaryAddress, 0x73, 0x51, data.length, data); - MBusMessage mBusMessage = receiveMessage(); - - if (mBusMessage.getMessageType() != MessageType.SINGLE_CHARACTER) { - throw new IOException("Unable to select component."); - } - } - - /** - * Selects the meter with the specified secondary address. After this the meter can be read on primary address 0xfd. - * - * @param secondaryAddress - * the secondary address of the meter to select. - * @throws IOException - * if any kind of error (including timeout) occurs while trying to read the remote device. Note that the - * connection is not closed when an IOException is thrown. - * @throws InterruptedIOException - * if no response at all (not even a single byte) was received from the meter within the timeout span. - */ - public void selectComponent(SecondaryAddress secondaryAddress) throws IOException, InterruptedIOException { - this.secondaryAddress = secondaryAddress; - componentSelection(false); - } - - /** - * Deselects the previously selected meter. - * - * @throws IOException - * if any kind of error (including timeout) occurs while trying to read the remote device. Note that the - * connection is not closed when an IOException is thrown. - * @throws InterruptedIOException - * if no response at all (not even a single byte) was received from the meter within the timeout span. - */ - public void deselectComponent() throws IOException, InterruptedIOException { - if (secondaryAddress == null) { - return; - } - componentSelection(true); - secondaryAddress = null; - } - - /** - * Selection of wanted records. - * - * @param primaryAddress - * primary address of the slave - * @param dataRecords - * data record to select - * @throws IOException - * if any kind of error (including timeout) occurs while trying to read the remote device. Note that the - * connection is not closed when an IOException is thrown. - * @throws InterruptedIOException - * if no response at all (not even a single byte) was received from the meter within the timeout span. - */ - public void selectForReadout(int primaryAddress, List dataRecords) - throws IOException, InterruptedIOException { - int i = 0; - for (DataRecord dataRecord : dataRecords) { - i += dataRecord.encode(dataRecordsAsBytes, i); - } - sendLongMessage(primaryAddress, 0x53, 0x51, i, dataRecordsAsBytes); - MBusMessage mBusMessage = receiveMessage(); - - if (mBusMessage.getMessageType() != MessageType.SINGLE_CHARACTER) { - throw new IOException("unable to select component"); - } - } - - /** - * Sends a application reset to the slave with specified primary address. - * - * @param primaryAddress - * primary address of the slave - * @throws IOException - * if any kind of error (including timeout) occurs while trying to read the remote device. Note that the - * connection is not closed when an IOException is thrown. - * @throws InterruptedIOException - * if no response at all (not even a single byte) was received from the meter within the timeout span. - */ - public void resetReadout(int primaryAddress) throws IOException, InterruptedIOException { - sendLongMessage(primaryAddress, 0x53, 0x50, 0, new byte[] {}); - MBusMessage mBusMessage = receiveMessage(); - - if (mBusMessage.getMessageType() != MessageType.SINGLE_CHARACTER) { - throw new IOException("Unable to reset application."); - } - } - - /** - * Sends a SND_NKE message to reset the FCB (frame counter bit). - * - * @param primaryAddress - * the primary address of the meter to reset. - * @throws InterruptedIOException - * if the slave does not answer with an 0xe5 message within the configured timeout span. - * @throws IOException - * if an error occurs during the reset process. - * @throws InterruptedIOException - * if the slave does not answer with an 0xe5 message within the configured timeout span. - */ - public void linkReset(int primaryAddress) throws IOException, InterruptedIOException { - sendShortMessage(primaryAddress, 0x40); - MBusMessage mBusMessage = receiveMessage(); - - if (mBusMessage.getMessageType() != MessageType.SINGLE_CHARACTER) { - throw new IOException("Unable to reset link."); - } - - frameCountBits[primaryAddress] = true; - } - - private void componentSelection(boolean deselect) throws IOException, InterruptedIOException { - byte[] ba = secondaryAddressAsBa(); - - // send select/deselect - if (deselect) { - sendLongMessage(0xfd, 0x53, 0x56, 8, ba); - } else { - sendLongMessage(0xfd, 0x53, 0x52, 8, ba); - } - - MBusMessage mBusMessage = receiveMessage(); - - if (mBusMessage.getMessageType() != MessageType.SINGLE_CHARACTER) { - throw new IOException("unable to select component"); - } - } - - private byte[] secondaryAddressAsBa() { - byte[] ba = new byte[8]; - - ((ByteBuffer) ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).put(secondaryAddress.asByteArray()) - .position(0)).get(ba, 0, 8); - return ba; - } - - private void sendShortMessage(int slaveAddr, int cmd) throws IOException { - synchronized (os) { - outputBuffer[0] = 0x10; - outputBuffer[1] = (byte) (cmd); - outputBuffer[2] = (byte) (slaveAddr); - outputBuffer[3] = (byte) (cmd + slaveAddr); - outputBuffer[4] = 0x16; - - verboseMessage(MessageDirection.SEND, outputBuffer, 0, 5); - - os.write(outputBuffer, 0, 5); - } - } - - void sendLongMessage(int slaveAddr, int controlField, int ci, int length, byte[] data) throws IOException { - synchronized (os) { - outputBuffer[0] = 0x68; - outputBuffer[1] = (byte) (length + 3); - outputBuffer[2] = (byte) (length + 3); - outputBuffer[3] = 0x68; - outputBuffer[4] = (byte) controlField; - outputBuffer[5] = (byte) slaveAddr; - outputBuffer[6] = (byte) ci; - - for (int i = 0; i < length; i++) { - outputBuffer[7 + i] = data[i]; - } - - outputBuffer[length + 7] = computeChecksum(length, outputBuffer); - - outputBuffer[length + 8] = 0x16; - - verboseMessage(MessageDirection.SEND, outputBuffer, 0, length + 9); - - os.write(outputBuffer, 0, length + 9); - } - } - - private static byte computeChecksum(int length, byte[] oBuffer) { - int checksum = 0; - for (int j = 4; j < (length + 7); j++) { - checksum += oBuffer[j]; - } - return (byte) (checksum & 0xff); - } - - MBusMessage receiveMessage() throws IOException { - byte[] receivedBytes; - - int b0 = is.read(); - if (b0 == 0xe5) { - // messageLength = 1; - receivedBytes = new byte[] { (byte) b0 }; - } else if ((b0 & 0xff) == 0x68) { - int b1 = is.readByte() & 0xff; - - /** - * The L field gives the quantity of the user data inputs plus 3 (for C,A,CI). - */ - int messageLength = b1 + 6; - - receivedBytes = new byte[messageLength]; - receivedBytes[0] = (byte) b0; - receivedBytes[1] = (byte) b1; - - int lenRead = messageLength - 2; - - is.readFully(receivedBytes, 2, lenRead); - } else { - throw new IOException(String.format("Received unknown message: %02X", b0)); - } - - verboseMessage(MessageDirection.RECEIVE, receivedBytes, 0, receivedBytes.length); - - return MBusMessage.decode(receivedBytes, receivedBytes.length); - } - - private void verboseMessage(MessageDirection direction, byte[] array, int from, int to) { - if (this.verboseMessageListener != null) { - byte[] message = Arrays.copyOfRange(array, from, to); - - VerboseMessage debugMessage = new VerboseMessage(direction, message); - this.verboseMessageListener.newVerboseMessage(debugMessage); - } - } - - public static MBusTcpBuilder newTcpBuilder(String hostAddress, int port) { - return new MBusTcpBuilder(hostAddress, port); - } - - public static class MBusTcpBuilder extends TcpBuilder { - - protected MBusTcpBuilder(String hostAddress, int port) { - super(hostAddress, port); - } - - @Override - public MBusConnection build() throws IOException { - MBusConnection mBusConnection = new MBusConnection(buildTransportLayer()); - mBusConnection.open(); - return mBusConnection; - } - } - - /** - * Create a new builder to connect to a serial. - * - * @param serialPortName - * the serial port. e.g. /dev/ttyS0. - * @return a new connection builder. - */ - public static MBusSerialBuilder newSerialBuilder(String serialPortName) { - return new MBusSerialBuilder(serialPortName); - } - - public static class MBusSerialBuilder extends SerialBuilder { - - protected MBusSerialBuilder(String serialPortName) { - super(serialPortName); - } - - @Override - public MBusConnection build() throws IOException { - MBusConnection mBusConnection = new MBusConnection(buildTransportLayer()); - mBusConnection.open(); - return mBusConnection; - } - } -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.List; + +import org.openmuc.jmbus.MBusMessage.MessageType; +import org.openmuc.jmbus.VerboseMessage.MessageDirection; +import org.openmuc.jmbus.transportlayer.SerialBuilder; +import org.openmuc.jmbus.transportlayer.TcpBuilder; +import org.openmuc.jmbus.transportlayer.TransportLayer; + +/** + * M-Bus Application Layer connection. + *

    + * Use this access point to communicate using the M-Bus wired protocol. + *

    + * + * @see MBusConnection#newSerialBuilder(String) + * @see MBusConnection#newTcpBuilder(String, int) + */ +public class MBusConnection implements AutoCloseable { + + // 261 is the maximum size of a long frame + private static final int MAX_MESSAGE_SIZE = 261; + + private final byte[] outputBuffer = new byte[MAX_MESSAGE_SIZE]; + + private final byte[] dataRecordsAsBytes = new byte[MAX_MESSAGE_SIZE]; + + private final boolean[] frameCountBits; + + private DataOutputStream os; + private DataInputStream is; + + private SecondaryAddress secondaryAddress; + + private VerboseMessageListener verboseMessageListener; + + private final TransportLayer transportLayer; + + /** + * Creates an M-Bus Service Access Point that is used to read meters. + * + * @param transportLayer + * Underlying transport layer + * @see MBusConnection#open() + */ + private MBusConnection(TransportLayer transportLayer) { + this.transportLayer = transportLayer; + + // set all frame bits to true + this.frameCountBits = new boolean[254]; + for (int i = 0; i < frameCountBits.length; i++) { + frameCountBits[i] = true; + } + } + + private void open() throws IOException { + try { + this.transportLayer.open(); + } catch (IOException e) { + this.transportLayer.close(); + throw e; + } + + this.os = transportLayer.getOutputStream(); + this.is = transportLayer.getInputStream(); + } + + /** + * Closes the service access point. + */ + @Override + public void close() { + transportLayer.close(); + } + + /** + * Sets the verbose mode on if a implementation of debugMessageListener has been set. + * + * @param verboseMessageListener + * Implementation of debugMessageListener + */ + public void setVerboseMessageListener(VerboseMessageListener verboseMessageListener) { + this.verboseMessageListener = verboseMessageListener; + } + + /** + * Scans for secondary addresses and returns all detected devices in a list and if SecondaryAddressListener not null + * to the listen listener. + * + * @param wildcardMask + * a wildcard mask for masking + * @param secondaryAddressListener + * listener to get scan messages and scanned secondary address just at time.
    + * If null, all detected address will only returned if finished. + * + * @return a list of secondary addresses of all detected devices + * @throws IOException + * if any kind of error (including timeout) occurs while writing to the remote device. Note that the + * connection is not closed when an IOException is thrown. + */ + public List scan(String wildcardMask, SecondaryAddressListener secondaryAddressListener) + throws IOException { + + if (wildcardMask == null || wildcardMask.isEmpty()) { + wildcardMask = "ffffffff"; + } + + return ScanSecondaryAddress.scan(this, wildcardMask, secondaryAddressListener); + } + + /** + * Reads a meter using primary addressing. Sends a data request (REQ_UD2) to the remote device and returns the + * variable data structure from the received RSP_UD frame. + * + * @param primaryAddress + * the primary address of the meter to read. For secondary address use 0xfd. + * @return the variable data structure from the received RSP_UD frame + * @throws InterruptedIOException + * if no response at all (not even a single byte) was received from the meter within the timeout span. + * @throws IOException + * if any kind of error (including timeout) occurs while trying to read the remote device. Note that the + * connection is not closed when an IOException is thrown. + * @throws InterruptedIOException + * if no response at all (not even a single byte) was received from the meter within the timeout span. + */ + public VariableDataStructure read(int primaryAddress) throws IOException, InterruptedIOException { + if (transportLayer.isClosed()) { + throw new IllegalStateException("Port is not open."); + } + + if (frameCountBits[primaryAddress]) { + sendShortMessage(primaryAddress, 0x7b); + frameCountBits[primaryAddress] = false; + } else { + sendShortMessage(primaryAddress, 0x5b); + frameCountBits[primaryAddress] = true; + } + + MBusMessage mBusMessage = receiveMessage(); + + if (mBusMessage.getMessageType() != MessageType.RSP_UD) { + throw new IOException( + "Received wrong kind of message. Expected RSP_UD but got: " + mBusMessage.getMessageType()); + } + + if (mBusMessage.getAddressField() != primaryAddress) { + // throw new IOException("Received RSP_UD message with unexpected address field. Expected " + primaryAddress + // + " but received " + mBusMessage.getAddressField()); + } + + try { + mBusMessage.getVariableDataResponse().decode(); + } catch (DecodingException e) { + throw new IOException("Error decoding incoming RSP_UD message.", e); + } + + return mBusMessage.getVariableDataResponse(); + } + + /** + * Sends a long message with individual parameters. Used for messages which arn't not predefined in + * {@link MBusConnection}.
    + * If no response message is expected, set hasResponse to false. Returns null if hasResponse is + * false + * + * @param primaryAddr + * the primary address of the meter to read. For secondary address use 0xfd. + * @param controlField + * control field (C Field) has the size of 1 byte. + * @param ci + * control information field (CI Field) has the size of 1 byte. + * @param data + * the data to sends to the meter. + * @param responseExpected + * set this flag to false if no response is expected else true. If false + * returns null + * @return returns null if boolean hasResponse is false.
    + * returns the {@link MBusMessage} if a message received. + * @throws IOException + * if any kind of error (including timeout) occurs while trying to send to or read the remote device. + * Note that the connection is not closed when an IOException is thrown. + */ + public MBusMessage sendLongMessage(int primaryAddr, int controlField, int ci, byte[] data, boolean responseExpected) + throws IOException { + MBusMessage mBusMessage = null; + + sendLongMessage(primaryAddr, controlField, ci, data.length, data); + + if (responseExpected) { + mBusMessage = receiveMessage(); + } + return mBusMessage; + } + + /** + * Sends a short message with individual parameters. Used for messages which arn't not predefined in + * {@link MBusConnection}.
    + * For normal readout use {@link MBusConnection#read(int)}.
    + * If no response message is expected, set hasResponse to false. Returns null if hasResponse is + * false + * + * @param primaryAddr + * the primary address of the meter to read. For secondary address use 0xfd. + * @param cmd + * the command to send to the meter. + * @param responseExpected + * set this flag to false if no response is expected else true. If false + * returns null + * @return returns null if boolean hasResponse is false.
    + * returns the {@link MBusMessage} if a message received. + * @throws IOException + * f any kind of error (including timeout) occurs while trying to send to or read the remote device. + * Note that the connection is not closed when an IOException is thrown. + */ + public MBusMessage sendShortMessage(int primaryAddr, int cmd, boolean responseExpected) throws IOException { + MBusMessage mBusMessage = null; + + sendShortMessage(primaryAddr, cmd); + + if (responseExpected) { + mBusMessage = receiveMessage(); + } + return mBusMessage; + } + + /** + * Writes to a meter using primary addressing. Sends a data send (SND_UD) to the remote device and returns a true if + * slave sends a 0x7e else false + * + * @param primaryAddress + * the primary address of the meter to write. For secondary address use 0xfd. + * @param data + * the data to sends to the meter. + * @throws IOException + * if any kind of error (including timeout) occurs while writing to the remote device. Note that the + * connection is not closed when an IOException is thrown. + * @throws InterruptedIOException + * if no response at all (not even a single byte) was received from the meter within the timeout span. + */ + public void write(int primaryAddress, byte[] data) throws IOException, InterruptedIOException { + if (data == null) { + data = new byte[0]; + } + + sendLongMessage(primaryAddress, 0x73, 0x51, data.length, data); + MBusMessage mBusMessage = receiveMessage(); + + if (mBusMessage.getMessageType() != MessageType.SINGLE_CHARACTER) { + throw new IOException("Unable to select component."); + } + } + + /** + * Selects the meter with the specified secondary address. After this the meter can be read on primary address 0xfd. + * + * @param secondaryAddress + * the secondary address of the meter to select. + * @throws IOException + * if any kind of error (including timeout) occurs while trying to read the remote device. Note that the + * connection is not closed when an IOException is thrown. + * @throws InterruptedIOException + * if no response at all (not even a single byte) was received from the meter within the timeout span. + */ + public void selectComponent(SecondaryAddress secondaryAddress) throws IOException, InterruptedIOException { + this.secondaryAddress = secondaryAddress; + componentSelection(false); + } + + /** + * Deselects the previously selected meter. + * + * @throws IOException + * if any kind of error (including timeout) occurs while trying to read the remote device. Note that the + * connection is not closed when an IOException is thrown. + * @throws InterruptedIOException + * if no response at all (not even a single byte) was received from the meter within the timeout span. + */ + public void deselectComponent() throws IOException, InterruptedIOException { + if (secondaryAddress == null) { + return; + } + componentSelection(true); + secondaryAddress = null; + } + + /** + * Selection of wanted records. + * + * @param primaryAddress + * primary address of the slave + * @param dataRecords + * data record to select + * @throws IOException + * if any kind of error (including timeout) occurs while trying to read the remote device. Note that the + * connection is not closed when an IOException is thrown. + * @throws InterruptedIOException + * if no response at all (not even a single byte) was received from the meter within the timeout span. + */ + public void selectForReadout(int primaryAddress, List dataRecords) + throws IOException, InterruptedIOException { + int i = 0; + for (DataRecord dataRecord : dataRecords) { + i += dataRecord.encode(dataRecordsAsBytes, i); + } + sendLongMessage(primaryAddress, 0x53, 0x51, i, dataRecordsAsBytes); + MBusMessage mBusMessage = receiveMessage(); + + if (mBusMessage.getMessageType() != MessageType.SINGLE_CHARACTER) { + throw new IOException("unable to select component"); + } + } + + /** + * Sends a application reset to the slave with specified primary address. + * + * @param primaryAddress + * primary address of the slave + * @throws IOException + * if any kind of error (including timeout) occurs while trying to read the remote device. Note that the + * connection is not closed when an IOException is thrown. + * @throws InterruptedIOException + * if no response at all (not even a single byte) was received from the meter within the timeout span. + */ + public void resetReadout(int primaryAddress) throws IOException, InterruptedIOException { + sendLongMessage(primaryAddress, 0x53, 0x50, 0, new byte[] {}); + MBusMessage mBusMessage = receiveMessage(); + + if (mBusMessage.getMessageType() != MessageType.SINGLE_CHARACTER) { + throw new IOException("Unable to reset application."); + } + } + + /** + * Sends a SND_NKE message to reset the FCB (frame counter bit). + * + * @param primaryAddress + * the primary address of the meter to reset. + * @throws InterruptedIOException + * if the slave does not answer with an 0xe5 message within the configured timeout span. + * @throws IOException + * if an error occurs during the reset process. + * @throws InterruptedIOException + * if the slave does not answer with an 0xe5 message within the configured timeout span. + */ + public void linkReset(int primaryAddress) throws IOException, InterruptedIOException { + sendShortMessage(primaryAddress, 0x40); + MBusMessage mBusMessage = receiveMessage(); + + if (mBusMessage.getMessageType() != MessageType.SINGLE_CHARACTER) { + throw new IOException("Unable to reset link."); + } + + frameCountBits[primaryAddress] = true; + } + + private void componentSelection(boolean deselect) throws IOException, InterruptedIOException { + byte[] ba = secondaryAddressAsBa(); + + // send select/deselect + if (deselect) { + sendLongMessage(0xfd, 0x53, 0x56, 8, ba); + } else { + sendLongMessage(0xfd, 0x53, 0x52, 8, ba); + } + + MBusMessage mBusMessage = receiveMessage(); + + if (mBusMessage.getMessageType() != MessageType.SINGLE_CHARACTER) { + throw new IOException("unable to select component"); + } + } + + private byte[] secondaryAddressAsBa() { + byte[] ba = new byte[8]; + + ((ByteBuffer) ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).put(secondaryAddress.asByteArray()) + .position(0)).get(ba, 0, 8); + return ba; + } + + private void sendShortMessage(int slaveAddr, int cmd) throws IOException { + synchronized (os) { + outputBuffer[0] = 0x10; + outputBuffer[1] = (byte) (cmd); + outputBuffer[2] = (byte) (slaveAddr); + outputBuffer[3] = (byte) (cmd + slaveAddr); + outputBuffer[4] = 0x16; + + verboseMessage(MessageDirection.SEND, outputBuffer, 0, 5); + + os.write(outputBuffer, 0, 5); + } + } + + void sendLongMessage(int slaveAddr, int controlField, int ci, int length, byte[] data) throws IOException { + synchronized (os) { + outputBuffer[0] = 0x68; + outputBuffer[1] = (byte) (length + 3); + outputBuffer[2] = (byte) (length + 3); + outputBuffer[3] = 0x68; + outputBuffer[4] = (byte) controlField; + outputBuffer[5] = (byte) slaveAddr; + outputBuffer[6] = (byte) ci; + + for (int i = 0; i < length; i++) { + outputBuffer[7 + i] = data[i]; + } + + outputBuffer[length + 7] = computeChecksum(length, outputBuffer); + + outputBuffer[length + 8] = 0x16; + + verboseMessage(MessageDirection.SEND, outputBuffer, 0, length + 9); + + os.write(outputBuffer, 0, length + 9); + } + } + + private static byte computeChecksum(int length, byte[] oBuffer) { + int checksum = 0; + for (int j = 4; j < (length + 7); j++) { + checksum += oBuffer[j]; + } + return (byte) (checksum & 0xff); + } + + MBusMessage receiveMessage() throws IOException { + byte[] receivedBytes; + + int b0 = is.read(); + if (b0 == 0xe5) { + // messageLength = 1; + receivedBytes = new byte[] { (byte) b0 }; + } else if ((b0 & 0xff) == 0x68) { + int b1 = is.readByte() & 0xff; + + /** + * The L field gives the quantity of the user data inputs plus 3 (for C,A,CI). + */ + int messageLength = b1 + 6; + + receivedBytes = new byte[messageLength]; + receivedBytes[0] = (byte) b0; + receivedBytes[1] = (byte) b1; + + int lenRead = messageLength - 2; + + is.readFully(receivedBytes, 2, lenRead); + } else { + throw new IOException(String.format("Received unknown message: %02X", b0)); + } + + verboseMessage(MessageDirection.RECEIVE, receivedBytes, 0, receivedBytes.length); + + return MBusMessage.decode(receivedBytes, receivedBytes.length); + } + + private void verboseMessage(MessageDirection direction, byte[] array, int from, int to) { + if (this.verboseMessageListener != null) { + byte[] message = Arrays.copyOfRange(array, from, to); + + VerboseMessage debugMessage = new VerboseMessage(direction, message); + this.verboseMessageListener.newVerboseMessage(debugMessage); + } + } + + public static MBusTcpBuilder newTcpBuilder(String hostAddress, int port) { + return new MBusTcpBuilder(hostAddress, port); + } + + public static class MBusTcpBuilder extends TcpBuilder { + + protected MBusTcpBuilder(String hostAddress, int port) { + super(hostAddress, port); + } + + @Override + public MBusConnection build() throws IOException { + MBusConnection mBusConnection = new MBusConnection(buildTransportLayer()); + mBusConnection.open(); + return mBusConnection; + } + } + + /** + * Create a new builder to connect to a serial. + * + * @param serialPortName + * the serial port. e.g. /dev/ttyS0. + * @return a new connection builder. + */ + public static MBusSerialBuilder newSerialBuilder(String serialPortName) { + return new MBusSerialBuilder(serialPortName); + } + + public static class MBusSerialBuilder extends SerialBuilder { + + protected MBusSerialBuilder(String serialPortName) { + super(serialPortName); + } + + @Override + public MBusConnection build() throws IOException { + MBusConnection mBusConnection = new MBusConnection(buildTransportLayer()); + mBusConnection.open(); + return mBusConnection; + } + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/MBusMessage.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/MBusMessage.java index 367e800..161001c 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/MBusMessage.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/MBusMessage.java @@ -1,131 +1,131 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus; - -import java.io.IOException; - -/** - * - * Represents a wired M-Bus link layer message according to EN 13757-2. The messages are in format class FT 1.2 - * according to IEC 60870-5-2. - * - * If the M-Bus message is of frame type Long Frame it contains user data and it contains the following fields: - *
      - *
    • Length (1 byte) -
    • - *
    • Control field (1 byte) -
    • - *
    • Address field (1 byte) -
    • - *
    • CI field (1 byte) -
    • - *
    • The APDU (Variable Data Response) -
    • - *
    - */ -public class MBusMessage { - - private static final int RSP_UD_HEADER_LENGTH = 6; - - public enum MessageType { - // the other message types (e.g. SND_NKE, REQ_UD2) cannot be sent from slave to master and are therefore - // omitted. - SINGLE_CHARACTER(0xE5), - RSP_UD(0x68); - - private static final MessageType[] VALUES = values(); - private final int value; - - private MessageType(int value) { - this.value = value; - } - - private static MessageType messageTypeFor(byte value) throws IOException { - int vAsint = value & 0xff; - for (MessageType messageType : VALUES) { - if (vAsint == messageType.value) { - return messageType; - } - } - throw new IOException(String.format("Unexpected first frame byte: 0x%02X.", value)); - } - } - - private final MessageType messageType; - private final int addressField; - private final VariableDataStructure variableDataStructure; - - private MBusMessage(MessageType messageType, int addressField, VariableDataStructure variableDataStructure) { - this.messageType = messageType; - this.addressField = addressField; - this.variableDataStructure = variableDataStructure; - } - - public static MBusMessage decode(byte[] buffer, int length) throws IOException { - MessageType messageType = MessageType.messageTypeFor(buffer[0]); - int addressField; - VariableDataStructure variableDataStructure; - - switch (messageType) { - case SINGLE_CHARACTER: - addressField = 0; - variableDataStructure = null; - break; - case RSP_UD: - int messageLength = getLongFrameMessageLength(buffer, length); - checkLongFrameFields(buffer); - addressField = buffer[5] & 0xff; - variableDataStructure = new VariableDataStructure(buffer, RSP_UD_HEADER_LENGTH, messageLength, null, - null); - break; - default: - // should not occur. - throw new RuntimeException("Case not supported " + messageType); - } - - return new MBusMessage(messageType, addressField, variableDataStructure); - } - - private static void checkLongFrameFields(byte[] buffer) throws IOException { - if (buffer[1] != buffer[2]) { - throw new IOException("Length fields are not identical in long frame!"); - } - - if (buffer[3] != MessageType.RSP_UD.value) { - throw new IOException("Fourth byte of long frame was not 0x68."); - } - - int controlField = buffer[4] & 0xff; - - if ((controlField & 0xcf) != 0x08) { - throw new IOException(String.format("Unexpected control field value: 0x%02X.", controlField)); - } - } - - private static int getLongFrameMessageLength(byte[] buffer, int length) throws IOException { - int messageLength = buffer[1] & 0xff; - - if (messageLength != length - RSP_UD_HEADER_LENGTH) { - throw new IOException("Wrong length field in frame header does not match the buffer length. Length field: " - + messageLength + ", buffer length: " + length + " !"); - } - return messageLength; - } - - public int getAddressField() { - return addressField; - } - - public MessageType getMessageType() { - return messageType; - } - - public VariableDataStructure getVariableDataResponse() { - return variableDataStructure; - } - - @Override - public String toString() { - return new StringBuilder().append("message type: ").append(messageType).append("\naddress field: ") - .append(addressField & 0xff).append("\nVariable Data Structure:\n").append(variableDataStructure) - .toString(); - } -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus; + +import java.io.IOException; + +/** + * + * Represents a wired M-Bus link layer message according to EN 13757-2. The messages are in format class FT 1.2 + * according to IEC 60870-5-2. + * + * If the M-Bus message is of frame type Long Frame it contains user data and it contains the following fields: + *
      + *
    • Length (1 byte) -
    • + *
    • Control field (1 byte) -
    • + *
    • Address field (1 byte) -
    • + *
    • CI field (1 byte) -
    • + *
    • The APDU (Variable Data Response) -
    • + *
    + */ +public class MBusMessage { + + private static final int RSP_UD_HEADER_LENGTH = 6; + + public enum MessageType { + // the other message types (e.g. SND_NKE, REQ_UD2) cannot be sent from slave to master and are therefore + // omitted. + SINGLE_CHARACTER(0xE5), + RSP_UD(0x68); + + private static final MessageType[] VALUES = values(); + private final int value; + + private MessageType(int value) { + this.value = value; + } + + private static MessageType messageTypeFor(byte value) throws IOException { + int vAsint = value & 0xff; + for (MessageType messageType : VALUES) { + if (vAsint == messageType.value) { + return messageType; + } + } + throw new IOException(String.format("Unexpected first frame byte: 0x%02X.", value)); + } + } + + private final MessageType messageType; + private final int addressField; + private final VariableDataStructure variableDataStructure; + + private MBusMessage(MessageType messageType, int addressField, VariableDataStructure variableDataStructure) { + this.messageType = messageType; + this.addressField = addressField; + this.variableDataStructure = variableDataStructure; + } + + public static MBusMessage decode(byte[] buffer, int length) throws IOException { + MessageType messageType = MessageType.messageTypeFor(buffer[0]); + int addressField; + VariableDataStructure variableDataStructure; + + switch (messageType) { + case SINGLE_CHARACTER: + addressField = 0; + variableDataStructure = null; + break; + case RSP_UD: + int messageLength = getLongFrameMessageLength(buffer, length); + checkLongFrameFields(buffer); + addressField = buffer[5] & 0xff; + variableDataStructure = new VariableDataStructure(buffer, RSP_UD_HEADER_LENGTH, messageLength, null, + null); + break; + default: + // should not occur. + throw new RuntimeException("Case not supported " + messageType); + } + + return new MBusMessage(messageType, addressField, variableDataStructure); + } + + private static void checkLongFrameFields(byte[] buffer) throws IOException { + if (buffer[1] != buffer[2]) { + throw new IOException("Length fields are not identical in long frame!"); + } + + if (buffer[3] != MessageType.RSP_UD.value) { + throw new IOException("Fourth byte of long frame was not 0x68."); + } + + int controlField = buffer[4] & 0xff; + + if ((controlField & 0xcf) != 0x08) { + throw new IOException(String.format("Unexpected control field value: 0x%02X.", controlField)); + } + } + + private static int getLongFrameMessageLength(byte[] buffer, int length) throws IOException { + int messageLength = buffer[1] & 0xff; + + if (messageLength != length - RSP_UD_HEADER_LENGTH) { + throw new IOException("Wrong length field in frame header does not match the buffer length. Length field: " + + messageLength + ", buffer length: " + length + " !"); + } + return messageLength; + } + + public int getAddressField() { + return addressField; + } + + public MessageType getMessageType() { + return messageType; + } + + public VariableDataStructure getVariableDataResponse() { + return variableDataStructure; + } + + @Override + public String toString() { + return new StringBuilder().append("message type: ").append(messageType).append("\naddress field: ") + .append(addressField & 0xff).append("\nVariable Data Structure:\n").append(variableDataStructure) + .toString(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/ScanSecondaryAddress.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/ScanSecondaryAddress.java index 5bfd194..84e04f6 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/ScanSecondaryAddress.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/ScanSecondaryAddress.java @@ -1,218 +1,218 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus; - -import static javax.xml.bind.DatatypeConverter.printHexBinary; - -import java.io.IOException; -import java.io.InterruptedIOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.text.MessageFormat; -import java.util.LinkedList; -import java.util.List; - -import org.openmuc.jmbus.MBusMessage.MessageType; - -class ScanSecondaryAddress { - - private static final int MAX_LENGTH = 16; - private static int pos = 0; - private static byte[] value = new byte[MAX_LENGTH]; - - public static List scan(MBusConnection mBusConnection, String wildcardMask, - SecondaryAddressListener secondaryAddressListener) throws IOException { - - List secondaryAddresses = new LinkedList<>(); - - boolean stop = false; - boolean collision = false; - - wildcardMask = flipString(wildcardMask); - wildcardMask += "ffffffff"; - - for (int i = 0; i < MAX_LENGTH; ++i) { - value[i] = Byte.parseByte(wildcardMask.substring(i, i + 1), 16); - } - - pos = wildcardMask.indexOf('f'); - if (pos == 8 || pos < 0) { - pos = 7; - } - value[pos] = 0; - - while (!stop) { - String msg = MessageFormat.format("scan with wildcard: {0}", printHexBinary(toSendByteArray(value))); - notifyScanMsg(secondaryAddressListener, msg); - - SecondaryAddress secondaryAddessesWildCard = SecondaryAddress.newFromLongHeader(toSendByteArray(value), 0); - SecondaryAddress readSecondaryAddress = null; - - if (scanSelection(mBusConnection, secondaryAddessesWildCard)) { - - try { - readSecondaryAddress = mBusConnection.read(0xfd).getSecondaryAddress(); - - } catch (InterruptedIOException e) { - notifyScanMsg(secondaryAddressListener, "Read (REQ_UD2) TimeoutException"); - collision = false; - } catch (IOException e) { - notifyScanMsg(secondaryAddressListener, "Read (REQ_UD2) IOException / Collision"); - collision = true; - } - - if (collision) { - if (pos < 7) { - ++pos; - value[pos] = 0; - } else { - stop = handler(); - } - collision = false; - } else { - if (readSecondaryAddress != null) { - String message = "Detected Device:\n" + readSecondaryAddress.toString(); - notifyScanMsg(secondaryAddressListener, message); - secondaryAddresses.add(readSecondaryAddress); - if (secondaryAddressListener != null) { - secondaryAddressListener.newDeviceFound(readSecondaryAddress); - } - stop = handler(); - } else { - - notifyScanMsg(secondaryAddressListener, - "Problem to decode secondary address. Perhaps a collision."); - if (pos < 7) { - ++pos; - value[pos] = 0; - } else { - stop = handler(); - } - collision = false; - } - } - } else { - stop = handler(); - } - } - if (mBusConnection != null) { - mBusConnection.close(); - } - return secondaryAddresses; - } - - /** - * Scans if any device response to the given wildcard. - * - * @param mBusConnection - * object to the open mbus connection - * - * @param wildcard - * secondary address wildcard e.g. f1ffffffffffffff - * @return true if any device responsed else false - * @throws IOException - */ - private static boolean scanSelection(MBusConnection mBusConnection, SecondaryAddress wildcard) throws IOException { - ByteBuffer bf = ByteBuffer.allocate(8); - byte[] ba = new byte[8]; - - bf.order(ByteOrder.LITTLE_ENDIAN); - - bf.put(wildcard.asByteArray()); - - bf.position(0); - bf.get(ba, 0, 8); - - mBusConnection.sendLongMessage(0xfd, 0x53, 0x52, 8, ba); - - try { - MBusMessage mBusMessage = mBusConnection.receiveMessage(); - - return mBusMessage.getMessageType() == MessageType.SINGLE_CHARACTER; - } catch (InterruptedIOException e) { - return false; - } catch (IOException e) { - return true; - } - } - - private static void notifyScanMsg(SecondaryAddressListener secondaryAddressListener, String message) { - if (secondaryAddressListener != null) { - secondaryAddressListener.newScanMessage(message); - } - } - - private static boolean handler() { - boolean stop; - - ++value[pos]; - - if (value[pos] < 10) { - stop = false; - } else { - if (pos > 0) { - --pos; - ++value[pos]; - setFValue(); - - while (value[pos] > 10) { - --pos; - ++value[pos]; - setFValue(); - } - stop = false; - } else { - stop = true; - } - } - - return stop; - } - - private static void setFValue() { - for (int i = pos + 1; i < 8; ++i) { - value[i] = 0xf; - } - } - - private static byte[] toSendByteArray(byte[] value) { - byte[] sendByteArray = new byte[8]; - - for (int i = 0; i < MAX_LENGTH; ++i) { - - if (i % 2 > 0) { - sendByteArray[i / 2] |= value[i] << 4; - } else { - sendByteArray[i / 2] |= value[i]; - } - } - return sendByteArray; - } - - /** - * Flips character pairs.
    - * from 01253fffffffffff to 1052f3ffffffffff - * - * @param value - * a string value like 01253fffffffffff - * @return a fliped string value. - */ - private static String flipString(String value) { - StringBuilder flipped = new StringBuilder(); - - for (int i = 0; i < value.length(); i += 2) { - flipped.append(value.charAt(i + 1)); - flipped.append(value.charAt(i)); - } - return flipped.toString(); - } - - /** - * Don't let anyone instantiate this class. - */ - private ScanSecondaryAddress() { - } -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus; + +import static javax.xml.bind.DatatypeConverter.printHexBinary; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.text.MessageFormat; +import java.util.LinkedList; +import java.util.List; + +import org.openmuc.jmbus.MBusMessage.MessageType; + +class ScanSecondaryAddress { + + private static final int MAX_LENGTH = 16; + private static int pos = 0; + private static byte[] value = new byte[MAX_LENGTH]; + + public static List scan(MBusConnection mBusConnection, String wildcardMask, + SecondaryAddressListener secondaryAddressListener) throws IOException { + + List secondaryAddresses = new LinkedList<>(); + + boolean stop = false; + boolean collision = false; + + wildcardMask = flipString(wildcardMask); + wildcardMask += "ffffffff"; + + for (int i = 0; i < MAX_LENGTH; ++i) { + value[i] = Byte.parseByte(wildcardMask.substring(i, i + 1), 16); + } + + pos = wildcardMask.indexOf('f'); + if (pos == 8 || pos < 0) { + pos = 7; + } + value[pos] = 0; + + while (!stop) { + String msg = MessageFormat.format("scan with wildcard: {0}", printHexBinary(toSendByteArray(value))); + notifyScanMsg(secondaryAddressListener, msg); + + SecondaryAddress secondaryAddessesWildCard = SecondaryAddress.newFromLongHeader(toSendByteArray(value), 0); + SecondaryAddress readSecondaryAddress = null; + + if (scanSelection(mBusConnection, secondaryAddessesWildCard)) { + + try { + readSecondaryAddress = mBusConnection.read(0xfd).getSecondaryAddress(); + + } catch (InterruptedIOException e) { + notifyScanMsg(secondaryAddressListener, "Read (REQ_UD2) TimeoutException"); + collision = false; + } catch (IOException e) { + notifyScanMsg(secondaryAddressListener, "Read (REQ_UD2) IOException / Collision"); + collision = true; + } + + if (collision) { + if (pos < 7) { + ++pos; + value[pos] = 0; + } else { + stop = handler(); + } + collision = false; + } else { + if (readSecondaryAddress != null) { + String message = "Detected Device:\n" + readSecondaryAddress.toString(); + notifyScanMsg(secondaryAddressListener, message); + secondaryAddresses.add(readSecondaryAddress); + if (secondaryAddressListener != null) { + secondaryAddressListener.newDeviceFound(readSecondaryAddress); + } + stop = handler(); + } else { + + notifyScanMsg(secondaryAddressListener, + "Problem to decode secondary address. Perhaps a collision."); + if (pos < 7) { + ++pos; + value[pos] = 0; + } else { + stop = handler(); + } + collision = false; + } + } + } else { + stop = handler(); + } + } + if (mBusConnection != null) { + mBusConnection.close(); + } + return secondaryAddresses; + } + + /** + * Scans if any device response to the given wildcard. + * + * @param mBusConnection + * object to the open mbus connection + * + * @param wildcard + * secondary address wildcard e.g. f1ffffffffffffff + * @return true if any device responsed else false + * @throws IOException + */ + private static boolean scanSelection(MBusConnection mBusConnection, SecondaryAddress wildcard) throws IOException { + ByteBuffer bf = ByteBuffer.allocate(8); + byte[] ba = new byte[8]; + + bf.order(ByteOrder.LITTLE_ENDIAN); + + bf.put(wildcard.asByteArray()); + + bf.position(0); + bf.get(ba, 0, 8); + + mBusConnection.sendLongMessage(0xfd, 0x53, 0x52, 8, ba); + + try { + MBusMessage mBusMessage = mBusConnection.receiveMessage(); + + return mBusMessage.getMessageType() == MessageType.SINGLE_CHARACTER; + } catch (InterruptedIOException e) { + return false; + } catch (IOException e) { + return true; + } + } + + private static void notifyScanMsg(SecondaryAddressListener secondaryAddressListener, String message) { + if (secondaryAddressListener != null) { + secondaryAddressListener.newScanMessage(message); + } + } + + private static boolean handler() { + boolean stop; + + ++value[pos]; + + if (value[pos] < 10) { + stop = false; + } else { + if (pos > 0) { + --pos; + ++value[pos]; + setFValue(); + + while (value[pos] > 10) { + --pos; + ++value[pos]; + setFValue(); + } + stop = false; + } else { + stop = true; + } + } + + return stop; + } + + private static void setFValue() { + for (int i = pos + 1; i < 8; ++i) { + value[i] = 0xf; + } + } + + private static byte[] toSendByteArray(byte[] value) { + byte[] sendByteArray = new byte[8]; + + for (int i = 0; i < MAX_LENGTH; ++i) { + + if (i % 2 > 0) { + sendByteArray[i / 2] |= value[i] << 4; + } else { + sendByteArray[i / 2] |= value[i]; + } + } + return sendByteArray; + } + + /** + * Flips character pairs.
    + * from 01253fffffffffff to 1052f3ffffffffff + * + * @param value + * a string value like 01253fffffffffff + * @return a fliped string value. + */ + private static String flipString(String value) { + StringBuilder flipped = new StringBuilder(); + + for (int i = 0; i < value.length(); i += 2) { + flipped.append(value.charAt(i + 1)); + flipped.append(value.charAt(i)); + } + return flipped.toString(); + } + + /** + * Don't let anyone instantiate this class. + */ + private ScanSecondaryAddress() { + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/SecondaryAddress.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/SecondaryAddress.java index 86f41cf..33e75d3 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/SecondaryAddress.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/SecondaryAddress.java @@ -1,224 +1,224 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; - -import javax.xml.bind.DatatypeConverter; - -/** - * This class represents a secondary address. Use the static initializer to initialize the - */ -public class SecondaryAddress implements Comparable { - - private static final int SECONDARY_ADDRESS_LENGTH = 8; - - private static final int ID_NUMBER_LENGTH = 4; - - private final String manufacturerId; - private final Bcd deviceId; - private final int version; - private final DeviceType deviceType; - private final byte[] bytes; - private final int hashCode; - private final boolean isLongHeader; - - /** - * Instantiate a new secondary address within a long header. - * - * @param buffer - * the byte buffer. - * @param offset - * the offset. - * @return a new secondary address. - */ - public static SecondaryAddress newFromLongHeader(byte[] buffer, int offset) { - return new SecondaryAddress(buffer, offset, true); - } - - /** - * Instantiate a new secondary address within a wireless M-Bus link layer header. - * - * @param buffer - * the byte buffer. - * @param offset - * the offset. - * @return a new secondary address. - */ - public static SecondaryAddress newFromWMBusLlHeader(byte[] buffer, int offset) { - return new SecondaryAddress(buffer, offset, false); - } - - /** - * Instantiate a new secondary address for a manufacturer ID. - * - * @param idNumber - * ID number. - * @param manufactureId - * manufacturer ID. - * @param version - * the version. - * @param media - * the media. - * @return a new secondary address. - * @throws NumberFormatException - * if the idNumber is not long enough. - */ - public static SecondaryAddress newFromManufactureId(byte[] idNumber, String manufactureId, byte version, byte media) - throws NumberFormatException { - if (idNumber.length != ID_NUMBER_LENGTH) { - throw new NumberFormatException("Wrong length of ID. Length must be " + ID_NUMBER_LENGTH + " byte."); - } - - byte[] mfId = encodeManufacturerId(manufactureId); - byte[] buffer = ByteBuffer.allocate(idNumber.length + mfId.length + 1 + 1).put(idNumber).put(mfId).put(version) - .put(media).array(); - return new SecondaryAddress(buffer, 0, true); - } - - /** - * The {@link SecondaryAddress} as byte array. - * - * @return the byte array (octet string) representation. - */ - public byte[] asByteArray() { - return bytes; - } - - /** - * Get the manufacturer ID. - * - * @return the ID. - */ - public String getManufacturerId() { - return manufacturerId; - } - - /** - * Returns the device ID. This is secondary address of the device. - * - * @return the device ID - */ - public Bcd getDeviceId() { - return deviceId; - } - - /** - * Returns the device type (e.g. gas, water etc.) - * - * @return the device type - */ - public DeviceType getDeviceType() { - return deviceType; - } - - /** - * Get the version. - * - * @return the version. - */ - public int getVersion() { - return version; - } - - public boolean isLongHeader() { - return isLongHeader; - } - - @Override - public String toString() { - return new StringBuilder().append("manufacturer ID: ").append(manufacturerId).append(", device ID: ") - .append(deviceId).append(", device version: ").append(version).append(", device type: ") - .append(deviceType).append(", as bytes: ").append(DatatypeConverter.printHexBinary(bytes)).toString(); - } - - @Override - public int hashCode() { - return this.hashCode; - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof SecondaryAddress)) { - return false; - } - - SecondaryAddress other = (SecondaryAddress) obj; - - return Arrays.equals(this.bytes, other.bytes); - } - - @Override - public int compareTo(SecondaryAddress sa) { - return Integer.compare(hashCode(), sa.hashCode()); - } - - private SecondaryAddress(byte[] buffer, int offset, boolean longHeader) { - this.bytes = Arrays.copyOfRange(buffer, offset, offset + SECONDARY_ADDRESS_LENGTH); - - this.hashCode = Arrays.hashCode(this.bytes); - this.isLongHeader = longHeader; - - try (ByteArrayInputStream is = new ByteArrayInputStream(this.bytes)) { - if (longHeader) { - this.deviceId = decodeDeviceId(is); - this.manufacturerId = decodeManufacturerId(is); - } else { - this.manufacturerId = decodeManufacturerId(is); - this.deviceId = decodeDeviceId(is); - } - this.version = is.read() & 0xff; - this.deviceType = DeviceType.getInstance(is.read() & 0xff); - } catch (IOException e) { - // should not occur - throw new RuntimeException(e); - } - } - - private static String decodeManufacturerId(ByteArrayInputStream is) { - int manufacturerIdAsInt = (is.read() & 0xff) + (is.read() << 8); - char c = (char) ((manufacturerIdAsInt & 0x1f) + 64); - manufacturerIdAsInt = (manufacturerIdAsInt >> 5); - char c1 = (char) ((manufacturerIdAsInt & 0x1f) + 64); - manufacturerIdAsInt = (manufacturerIdAsInt >> 5); - char c2 = (char) ((manufacturerIdAsInt & 0x1f) + 64); - - return new StringBuilder().append(c2).append(c1).append(c).toString(); - } - - private static byte[] encodeManufacturerId(String manufactureId) { - if (manufactureId.length() != 3) { - return new byte[] { 0, 0 }; - } - - manufactureId = manufactureId.toUpperCase(); - char[] manufactureIdArray = manufactureId.toCharArray(); - int manufacturerIdAsInt = (manufactureIdArray[0] - 64) * 32 * 32; - manufacturerIdAsInt += (manufactureIdArray[1] - 64) * 32; - manufacturerIdAsInt += (manufactureIdArray[2] - 64); - - ByteBuffer buf = ByteBuffer.allocate(2); - buf.order(ByteOrder.LITTLE_ENDIAN); - buf.putShort((short) manufacturerIdAsInt); - - return buf.array(); - } - - private static Bcd decodeDeviceId(ByteArrayInputStream is) throws IOException { - int msgSize = 4; - byte[] idArray = new byte[msgSize]; - int actual = is.read(idArray); - - if (msgSize != actual) { - throw new IOException("Failed to read BCD data. Data missing."); - } - return new Bcd(idArray); - } -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; + +import javax.xml.bind.DatatypeConverter; + +/** + * This class represents a secondary address. Use the static initializer to initialize the + */ +public class SecondaryAddress implements Comparable { + + private static final int SECONDARY_ADDRESS_LENGTH = 8; + + private static final int ID_NUMBER_LENGTH = 4; + + private final String manufacturerId; + private final Bcd deviceId; + private final int version; + private final DeviceType deviceType; + private final byte[] bytes; + private final int hashCode; + private final boolean isLongHeader; + + /** + * Instantiate a new secondary address within a long header. + * + * @param buffer + * the byte buffer. + * @param offset + * the offset. + * @return a new secondary address. + */ + public static SecondaryAddress newFromLongHeader(byte[] buffer, int offset) { + return new SecondaryAddress(buffer, offset, true); + } + + /** + * Instantiate a new secondary address within a wireless M-Bus link layer header. + * + * @param buffer + * the byte buffer. + * @param offset + * the offset. + * @return a new secondary address. + */ + public static SecondaryAddress newFromWMBusLlHeader(byte[] buffer, int offset) { + return new SecondaryAddress(buffer, offset, false); + } + + /** + * Instantiate a new secondary address for a manufacturer ID. + * + * @param idNumber + * ID number. + * @param manufactureId + * manufacturer ID. + * @param version + * the version. + * @param media + * the media. + * @return a new secondary address. + * @throws NumberFormatException + * if the idNumber is not long enough. + */ + public static SecondaryAddress newFromManufactureId(byte[] idNumber, String manufactureId, byte version, byte media) + throws NumberFormatException { + if (idNumber.length != ID_NUMBER_LENGTH) { + throw new NumberFormatException("Wrong length of ID. Length must be " + ID_NUMBER_LENGTH + " byte."); + } + + byte[] mfId = encodeManufacturerId(manufactureId); + byte[] buffer = ByteBuffer.allocate(idNumber.length + mfId.length + 1 + 1).put(idNumber).put(mfId).put(version) + .put(media).array(); + return new SecondaryAddress(buffer, 0, true); + } + + /** + * The {@link SecondaryAddress} as byte array. + * + * @return the byte array (octet string) representation. + */ + public byte[] asByteArray() { + return bytes; + } + + /** + * Get the manufacturer ID. + * + * @return the ID. + */ + public String getManufacturerId() { + return manufacturerId; + } + + /** + * Returns the device ID. This is secondary address of the device. + * + * @return the device ID + */ + public Bcd getDeviceId() { + return deviceId; + } + + /** + * Returns the device type (e.g. gas, water etc.) + * + * @return the device type + */ + public DeviceType getDeviceType() { + return deviceType; + } + + /** + * Get the version. + * + * @return the version. + */ + public int getVersion() { + return version; + } + + public boolean isLongHeader() { + return isLongHeader; + } + + @Override + public String toString() { + return new StringBuilder().append("manufacturer ID: ").append(manufacturerId).append(", device ID: ") + .append(deviceId).append(", device version: ").append(version).append(", device type: ") + .append(deviceType).append(", as bytes: ").append(DatatypeConverter.printHexBinary(bytes)).toString(); + } + + @Override + public int hashCode() { + return this.hashCode; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof SecondaryAddress)) { + return false; + } + + SecondaryAddress other = (SecondaryAddress) obj; + + return Arrays.equals(this.bytes, other.bytes); + } + + @Override + public int compareTo(SecondaryAddress sa) { + return Integer.compare(hashCode(), sa.hashCode()); + } + + private SecondaryAddress(byte[] buffer, int offset, boolean longHeader) { + this.bytes = Arrays.copyOfRange(buffer, offset, offset + SECONDARY_ADDRESS_LENGTH); + + this.hashCode = Arrays.hashCode(this.bytes); + this.isLongHeader = longHeader; + + try (ByteArrayInputStream is = new ByteArrayInputStream(this.bytes)) { + if (longHeader) { + this.deviceId = decodeDeviceId(is); + this.manufacturerId = decodeManufacturerId(is); + } else { + this.manufacturerId = decodeManufacturerId(is); + this.deviceId = decodeDeviceId(is); + } + this.version = is.read() & 0xff; + this.deviceType = DeviceType.getInstance(is.read() & 0xff); + } catch (IOException e) { + // should not occur + throw new RuntimeException(e); + } + } + + private static String decodeManufacturerId(ByteArrayInputStream is) { + int manufacturerIdAsInt = (is.read() & 0xff) + (is.read() << 8); + char c = (char) ((manufacturerIdAsInt & 0x1f) + 64); + manufacturerIdAsInt = (manufacturerIdAsInt >> 5); + char c1 = (char) ((manufacturerIdAsInt & 0x1f) + 64); + manufacturerIdAsInt = (manufacturerIdAsInt >> 5); + char c2 = (char) ((manufacturerIdAsInt & 0x1f) + 64); + + return new StringBuilder().append(c2).append(c1).append(c).toString(); + } + + private static byte[] encodeManufacturerId(String manufactureId) { + if (manufactureId.length() != 3) { + return new byte[] { 0, 0 }; + } + + manufactureId = manufactureId.toUpperCase(); + char[] manufactureIdArray = manufactureId.toCharArray(); + int manufacturerIdAsInt = (manufactureIdArray[0] - 64) * 32 * 32; + manufacturerIdAsInt += (manufactureIdArray[1] - 64) * 32; + manufacturerIdAsInt += (manufactureIdArray[2] - 64); + + ByteBuffer buf = ByteBuffer.allocate(2); + buf.order(ByteOrder.LITTLE_ENDIAN); + buf.putShort((short) manufacturerIdAsInt); + + return buf.array(); + } + + private static Bcd decodeDeviceId(ByteArrayInputStream is) throws IOException { + int msgSize = 4; + byte[] idArray = new byte[msgSize]; + int actual = is.read(idArray); + + if (msgSize != actual) { + throw new IOException("Failed to read BCD data. Data missing."); + } + return new Bcd(idArray); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/SecondaryAddressListener.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/SecondaryAddressListener.java index 3c9409c..5309804 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/SecondaryAddressListener.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/SecondaryAddressListener.java @@ -1,30 +1,30 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus; - -import java.util.EventListener; - -/** - * Listener to get secondary address scan message e.g. for console tools and to get messages. - */ -public interface SecondaryAddressListener extends EventListener { - - /** - * New scan message. - * - * @param message - * messages from scan secondary address - */ - void newScanMessage(String message); - - /** - * New device found. - * - * @param secondaryAddress - * secondary address of detected device - */ - void newDeviceFound(SecondaryAddress secondaryAddress); -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus; + +import java.util.EventListener; + +/** + * Listener to get secondary address scan message e.g. for console tools and to get messages. + */ +public interface SecondaryAddressListener extends EventListener { + + /** + * New scan message. + * + * @param message + * messages from scan secondary address + */ + void newScanMessage(String message); + + /** + * New device found. + * + * @param secondaryAddress + * secondary address of detected device + */ + void newDeviceFound(SecondaryAddress secondaryAddress); +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/VariableDataStructure.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/VariableDataStructure.java index e6cbbdc..f2ce2c8 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/VariableDataStructure.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/VariableDataStructure.java @@ -1,449 +1,449 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.text.MessageFormat; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import javax.xml.bind.DatatypeConverter; - -/** - * Representation of the data transmitted in RESP-UD (M-Bus) and SND-NR (wM-Bus) messages. - * - * @see #decode() - */ -public class VariableDataStructure { - - private static final ConcurrentHashMap> deviceHistory = new ConcurrentHashMap<>(); - - private final byte[] buffer; - private final int offset; - private final int length; - private byte[] header = new byte[0]; - private final SecondaryAddress linkLayerSecondaryAddress; - private final Map keyMap; - - private SecondaryAddress secondaryAddress; - private int accessNumber; - private int status; - - /* Extended Link Layer (ELL) (0x8d) specific */ - private byte communicationControl; - private byte[] sessionNumber; - /* End of ELL specific */ - - private EncryptionMode encryptionMode; - private int numberOfEncryptedBlocks; - private byte[] manufacturerData = new byte[0]; - private byte[] vdr = new byte[0]; - private boolean moreRecordsFollow = false; - - private boolean decoded = false; - - private List dataRecords; - - public VariableDataStructure(byte[] buffer, int offset, int length, SecondaryAddress linkLayerSecondaryAddress, - Map keyMap) { - this.buffer = buffer; - this.offset = offset; - this.length = length; - this.linkLayerSecondaryAddress = linkLayerSecondaryAddress; - this.keyMap = keyMap; - this.dataRecords = new LinkedList<>(); - } - - /** - * This method is used to - * - * @throws DecodingException - */ - public void decode() throws DecodingException { - if (!decoded) { - try { - int ciField = readUnsignedByte(buffer, offset); - - switch (ciField) { - case 0x72: - decodeLongHeaderData(); - break; - case 0x78: /* no header */ - encryptionMode = EncryptionMode.NONE; - decodeDataRecords(buffer, offset + 1, length - 1); - break; - case 0x7a: /* short header */ - decodeWithShortHeader(); - break; - case 0x8d: /* ELL */ - decodeExtendedLinkLayer(buffer, offset + 1); // 6 bytes header + CRC - header = Arrays.copyOfRange(buffer, offset, offset + 7); // don't include CRC - vdr = new byte[length - 7]; - System.arraycopy(buffer, offset + 7, vdr, 0, length - 7); - if (encryptionMode.equals(EncryptionMode.AES_128)) { - decryptMessage(getKey()); - } - - if ((vdr[2] & 0xff) == 0x78) { - decodeDataRecords(vdr, 3, length - 10); - } else if ((vdr[2] & 0xff) == 0x79) { - decodeShortFrame(vdr, 3, length - 10); - } - break; - case 0x33: - String msg = String.format( - "Received telegram with CI 0x33. Decoding not implemented. Device Serial: %s, Manufacturer: %s.", - linkLayerSecondaryAddress.getDeviceId().toString(), - linkLayerSecondaryAddress.getManufacturerId()); - throw new DecodingException(msg); - default: - String strFormat = "Unable to decode message with this CI Field: 0x%02X."; - if ((ciField >= 0xA0) && (ciField <= 0xB7)) { - strFormat = "Manufacturer specific CI: 0x%02X."; - } - - throw new DecodingException(String.format(strFormat, ciField)); - } - } catch (RuntimeException e) { - throw new DecodingException(e); - } - - decoded = true; - } - } - - private void decodeWithShortHeader() throws DecodingException { - decodeShortHeader(buffer, offset + 1); - if (encryptionMode == EncryptionMode.NONE) { - decodeDataRecords(buffer, offset + 5, length - 5); - } else if (encryptionMode == EncryptionMode.AES_CBC_IV) { - decryptAesCbcIv(buffer, offset + 5, numberOfEncryptedBlocks * 16); - } else { - throw new DecodingException("Unsupported encryption mode used: " + encryptionMode); - } - } - - private void decryptAesCbcIv(byte[] buffer, int offset, int encryptedDataLength) throws DecodingException { - final int len = length - 5; - vdr = new byte[len]; - System.arraycopy(buffer, offset, vdr, 0, encryptedDataLength); - - byte[] key = keyMap.get(linkLayerSecondaryAddress); - if (key == null) { - String msg = MessageFormat.format( - "Unable to decode encrypted payload. \nSecondary address key was not registered: \n{0}", - linkLayerSecondaryAddress); - throw new DecodingException(msg); - } - - decodeDataRecords(decryptMessage(key), 0, len); - } - - private void decodeLongHeaderData() throws DecodingException { - final int headerLength = 13; - header = Arrays.copyOfRange(buffer, offset, offset + headerLength); - - secondaryAddress = SecondaryAddress.newFromLongHeader(buffer, offset + 1); - - decodeShortHeader(buffer, offset + 1 + 8); - - vdr = new byte[length - headerLength]; - System.arraycopy(buffer, offset + headerLength, vdr, 0, length - headerLength); - - if (encryptionMode == EncryptionMode.AES_CBC_IV) { - decryptMessage(getKey()); - } else if (encryptionMode != EncryptionMode.NONE) { - throw new DecodingException("Unsupported encryption mode used: " + encryptionMode); - } - decodeDataRecords(vdr, 0, length - headerLength); - } - - public SecondaryAddress getSecondaryAddress() { - return secondaryAddress; - } - - public int getAccessNumber() { - return accessNumber; - } - - public EncryptionMode getEncryptionMode() { - return encryptionMode; - } - - public byte[] getManufacturerData() { - return manufacturerData; - } - - public int getNumberOfEncryptedBlocks() { - return numberOfEncryptedBlocks; - } - - public int getStatus() { - return status; - } - - public List getDataRecords() { - return dataRecords; - } - - public boolean moreRecordsFollow() { - return moreRecordsFollow; - } - - private void decodeExtendedLinkLayer(byte[] buffer, int offset) { - int i = offset; - - communicationControl = buffer[i++]; - accessNumber = buffer[i++]; - sessionNumber = new byte[] { buffer[i++], buffer[i++], buffer[i++], buffer[i++] }; - encryptionMode = EncryptionMode.getInstance(sessionNumber[3] >> 5); - byte[] checksum = new byte[] { buffer[i++], buffer[i++] }; - - byte[] crc = CRC16.calculateCrc16(Arrays.copyOfRange(buffer, i, buffer.length - 1)); - if (checksum[0] == crc[0] && checksum[1] == crc[1]) { - encryptionMode = EncryptionMode.NONE; - } - } - - private void decodeShortHeader(byte[] buffer, int offset) { - int i = offset; - - accessNumber = readUnsignedByte(buffer, i++); - status = readUnsignedByte(buffer, i++); - numberOfEncryptedBlocks = (buffer[i++] & 0xf0) >> 4; - encryptionMode = EncryptionMode.getInstance(buffer[i++] & 0x0f); - - if (msgIsNotEnc(buffer, i)) { - encryptionMode = EncryptionMode.NONE; - } - } - - private static boolean msgIsNotEnc(byte[] buffer, int i) { - byte b = buffer[i]; - byte b2 = buffer[i + 1]; - return b == (byte) 0x2f && b2 == (byte) 0x02; - } - - private static int readUnsignedByte(byte[] msg, int i) { - return msg[i] & 0xff; - } - - public byte[] getHeader() { - return this.header; - } - - private void decodeDataRecords(byte[] buffer, int offset, int length) throws DecodingException { - int i = offset; - - while (i < offset + length - 2) { - - if ((buffer[i] & 0xef) == 0x0f) { - // manufacturer specific data - - moreRecordsFollow = (buffer[i] & 0x10) == 0x10; - - manufacturerData = Arrays.copyOfRange(buffer, i + 1, offset + length - 2); - return; - } - - if (buffer[i] == 0x2f) { - // this is a fill byte because some encryption mechanisms need multiples of 8 bytes to encode data - i++; - continue; - } - - DataRecord dataRecord = new DataRecord(); - i = dataRecord.decode(buffer, i, length); - - dataRecords.add(dataRecord); - } - - if (linkLayerSecondaryAddress != null) { - deviceHistory.put(linkLayerSecondaryAddress, dataRecords); - } - } - - private void decodeShortFrame(byte[] data, int offset, int length) throws DecodingException { - if (!deviceHistory.containsKey(linkLayerSecondaryAddress)) { - deviceHistory.put(linkLayerSecondaryAddress, new LinkedList()); - } - - ByteBuffer buf = ByteBuffer.wrap(data, offset, length); - buf.order(ByteOrder.nativeOrder()); - - // skip checksum data - buf.position(4); - - this.dataRecords = deviceHistory.get(linkLayerSecondaryAddress); - - ListIterator iter = this.dataRecords.listIterator(); - while (iter.hasNext()) { - DataRecord dr = iter.next(); - try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { - os.write(dr.getDib()); - os.write(dr.getVib()); - - int tempOffset = dr.getDib().length + dr.getVib().length; - - // This might not be right - int dataLegth = tempOffset + dr.getDataLength(); - byte[] b = new byte[dataLegth - tempOffset]; - buf.get(b); - os.write(b); - - DataRecord newDataRecord = new DataRecord(); - newDataRecord.decode(os.toByteArray(), 0, dataLegth); - iter.set(newDataRecord); - } catch (IOException e) { - // ignore - } - - } - } - - public byte[] decryptMessage(byte[] key) throws DecodingException { - - if (encryptionMode == EncryptionMode.NONE) { - return vdr; - } - - if (key == null) { - throw new DecodingException("AES key for given address not specified."); - } - - final int len = numberOfEncryptedBlocks * 16; - - if (len > vdr.length) { - throw new DecodingException("Number of encrypted exceeds payload size!"); - } - - switch (encryptionMode) { - case AES_CBC_IV: - decryptAesCbcIv(key, len); - break; - case AES_128: - decryptAes128(key, len); - break; - default: - throw new DecodingException("Unsupported encryption mode: " + encryptionMode); - } - - return vdr; - } - - private void decryptAes128(byte[] key, final int len) throws DecodingException { - byte[] iv = createIvKamstrup(); - byte[] result = AesCrypt.newAesCtrCrypt(key, iv).decrypt(vdr, len); - - byte[] crc = CRC16.calculateCrc16(Arrays.copyOfRange(result, 2, result.length)); - - if (result[0] != crc[0] || result[1] != crc[1]) { - throw new DecodingException(newDecyptionExceptionMsg()); - } - vdr = result; - } - - private void decryptAesCbcIv(byte[] key, final int len) throws DecodingException { - byte[] iv = createIv(); - byte[] result = AesCrypt.newAesCrypt(key, iv).decrypt(this.vdr, len); - if (!(result[0] == 0x2f && result[1] == 0x2f)) { - throw new DecodingException(newDecyptionExceptionMsg()); - } - System.arraycopy(result, 0, vdr, 0, len); - } - - private String newDecyptionExceptionMsg() { - String deviceId = linkLayerSecondaryAddress.getDeviceId().toString(); - String manId = linkLayerSecondaryAddress.getManufacturerId(); - return String.format("%s - %s - Decryption unsuccessful! Wrong AES/CTR Key?", deviceId, manId); - } - - private byte[] createIv() { - byte[] iv = new byte[16]; - byte[] saBytes = linkLayerSecondaryAddress.asByteArray(); - - if (linkLayerSecondaryAddress.isLongHeader()) { - System.arraycopy(saBytes, 0, iv, 4, 2); // Manufacture - System.arraycopy(saBytes, 2, iv, 0, 4); // Identification - System.arraycopy(saBytes, 6, iv, 6, 2); // Version and Device Type - } else { - System.arraycopy(saBytes, 0, iv, 0, 8); - } - - for (int i = 8; i < iv.length; i++) { - iv[i] = (byte) accessNumber; - } - - return iv; - } - - private byte[] createIvKamstrup() throws DecodingException { - try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { - - os.write(linkLayerSecondaryAddress.asByteArray(), 0, 8); - /* set hop count to 0 in case a repeater is used */ - os.write(communicationControl & ~(1 << 4)); - os.write(sessionNumber); - os.write(new byte[3]); // 3 * 0x00 - - return os.toByteArray(); - } catch (IOException e) { - throw new DecodingException("Unable to create initial vector for decryption.", e); - } - } - - private byte[] getKey() throws DecodingException { - byte[] key = keyMap.get(linkLayerSecondaryAddress); - if (key != null) { - return key; - } - - String msg = "Unable to decode encrypted payload because no key for the following secondary address was registered: " - + linkLayerSecondaryAddress; - throw new DecodingException(msg); - } - - @Override - public String toString() { - if (!decoded) { - int from = offset; - int to = from + length; - String hexString = DatatypeConverter.printHexBinary(Arrays.copyOfRange(buffer, from, to)); - return MessageFormat.format("VariableDataResponse has not been decoded. Bytes:\n{0}", hexString); - } - - StringBuilder builder = new StringBuilder(); - - if (secondaryAddress != null) { - builder.append("Secondary address: {").append(secondaryAddress).append("}\n"); - } - - builder.append("Short Header: {Access No.: ").append(accessNumber).append(", status: ").append(status) - .append(", encryption mode: ").append(encryptionMode).append(", number of encrypted blocks: ") - .append(numberOfEncryptedBlocks).append("}"); - - for (DataRecord dataRecord : dataRecords) { - builder.append("\n").append(dataRecord.toString()); - } - - if (manufacturerData.length != 0) { - String manDaraHexStr = DatatypeConverter.printHexBinary(manufacturerData); - builder.append("\nManufacturer specific bytes:\n").append(manDaraHexStr); - } - - if (moreRecordsFollow) { - builder.append("\nMore records follow ..."); - } - return builder.toString(); - } -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.xml.bind.DatatypeConverter; + +/** + * Representation of the data transmitted in RESP-UD (M-Bus) and SND-NR (wM-Bus) messages. + * + * @see #decode() + */ +public class VariableDataStructure { + + private static final ConcurrentHashMap> deviceHistory = new ConcurrentHashMap<>(); + + private final byte[] buffer; + private final int offset; + private final int length; + private byte[] header = new byte[0]; + private final SecondaryAddress linkLayerSecondaryAddress; + private final Map keyMap; + + private SecondaryAddress secondaryAddress; + private int accessNumber; + private int status; + + /* Extended Link Layer (ELL) (0x8d) specific */ + private byte communicationControl; + private byte[] sessionNumber; + /* End of ELL specific */ + + private EncryptionMode encryptionMode; + private int numberOfEncryptedBlocks; + private byte[] manufacturerData = new byte[0]; + private byte[] vdr = new byte[0]; + private boolean moreRecordsFollow = false; + + private boolean decoded = false; + + private List dataRecords; + + public VariableDataStructure(byte[] buffer, int offset, int length, SecondaryAddress linkLayerSecondaryAddress, + Map keyMap) { + this.buffer = buffer; + this.offset = offset; + this.length = length; + this.linkLayerSecondaryAddress = linkLayerSecondaryAddress; + this.keyMap = keyMap; + this.dataRecords = new LinkedList<>(); + } + + /** + * This method is used to + * + * @throws DecodingException + */ + public void decode() throws DecodingException { + if (!decoded) { + try { + int ciField = readUnsignedByte(buffer, offset); + + switch (ciField) { + case 0x72: + decodeLongHeaderData(); + break; + case 0x78: /* no header */ + encryptionMode = EncryptionMode.NONE; + decodeDataRecords(buffer, offset + 1, length - 1); + break; + case 0x7a: /* short header */ + decodeWithShortHeader(); + break; + case 0x8d: /* ELL */ + decodeExtendedLinkLayer(buffer, offset + 1); // 6 bytes header + CRC + header = Arrays.copyOfRange(buffer, offset, offset + 7); // don't include CRC + vdr = new byte[length - 7]; + System.arraycopy(buffer, offset + 7, vdr, 0, length - 7); + if (encryptionMode.equals(EncryptionMode.AES_128)) { + decryptMessage(getKey()); + } + + if ((vdr[2] & 0xff) == 0x78) { + decodeDataRecords(vdr, 3, length - 10); + } else if ((vdr[2] & 0xff) == 0x79) { + decodeShortFrame(vdr, 3, length - 10); + } + break; + case 0x33: + String msg = String.format( + "Received telegram with CI 0x33. Decoding not implemented. Device Serial: %s, Manufacturer: %s.", + linkLayerSecondaryAddress.getDeviceId().toString(), + linkLayerSecondaryAddress.getManufacturerId()); + throw new DecodingException(msg); + default: + String strFormat = "Unable to decode message with this CI Field: 0x%02X."; + if ((ciField >= 0xA0) && (ciField <= 0xB7)) { + strFormat = "Manufacturer specific CI: 0x%02X."; + } + + throw new DecodingException(String.format(strFormat, ciField)); + } + } catch (RuntimeException e) { + throw new DecodingException(e); + } + + decoded = true; + } + } + + private void decodeWithShortHeader() throws DecodingException { + decodeShortHeader(buffer, offset + 1); + if (encryptionMode == EncryptionMode.NONE) { + decodeDataRecords(buffer, offset + 5, length - 5); + } else if (encryptionMode == EncryptionMode.AES_CBC_IV) { + decryptAesCbcIv(buffer, offset + 5, numberOfEncryptedBlocks * 16); + } else { + throw new DecodingException("Unsupported encryption mode used: " + encryptionMode); + } + } + + private void decryptAesCbcIv(byte[] buffer, int offset, int encryptedDataLength) throws DecodingException { + final int len = length - 5; + vdr = new byte[len]; + System.arraycopy(buffer, offset, vdr, 0, encryptedDataLength); + + byte[] key = keyMap.get(linkLayerSecondaryAddress); + if (key == null) { + String msg = MessageFormat.format( + "Unable to decode encrypted payload. \nSecondary address key was not registered: \n{0}", + linkLayerSecondaryAddress); + throw new DecodingException(msg); + } + + decodeDataRecords(decryptMessage(key), 0, len); + } + + private void decodeLongHeaderData() throws DecodingException { + final int headerLength = 13; + header = Arrays.copyOfRange(buffer, offset, offset + headerLength); + + secondaryAddress = SecondaryAddress.newFromLongHeader(buffer, offset + 1); + + decodeShortHeader(buffer, offset + 1 + 8); + + vdr = new byte[length - headerLength]; + System.arraycopy(buffer, offset + headerLength, vdr, 0, length - headerLength); + + if (encryptionMode == EncryptionMode.AES_CBC_IV) { + decryptMessage(getKey()); + } else if (encryptionMode != EncryptionMode.NONE) { + throw new DecodingException("Unsupported encryption mode used: " + encryptionMode); + } + decodeDataRecords(vdr, 0, length - headerLength); + } + + public SecondaryAddress getSecondaryAddress() { + return secondaryAddress; + } + + public int getAccessNumber() { + return accessNumber; + } + + public EncryptionMode getEncryptionMode() { + return encryptionMode; + } + + public byte[] getManufacturerData() { + return manufacturerData; + } + + public int getNumberOfEncryptedBlocks() { + return numberOfEncryptedBlocks; + } + + public int getStatus() { + return status; + } + + public List getDataRecords() { + return dataRecords; + } + + public boolean moreRecordsFollow() { + return moreRecordsFollow; + } + + private void decodeExtendedLinkLayer(byte[] buffer, int offset) { + int i = offset; + + communicationControl = buffer[i++]; + accessNumber = buffer[i++]; + sessionNumber = new byte[] { buffer[i++], buffer[i++], buffer[i++], buffer[i++] }; + encryptionMode = EncryptionMode.getInstance(sessionNumber[3] >> 5); + byte[] checksum = new byte[] { buffer[i++], buffer[i++] }; + + byte[] crc = CRC16.calculateCrc16(Arrays.copyOfRange(buffer, i, buffer.length - 1)); + if (checksum[0] == crc[0] && checksum[1] == crc[1]) { + encryptionMode = EncryptionMode.NONE; + } + } + + private void decodeShortHeader(byte[] buffer, int offset) { + int i = offset; + + accessNumber = readUnsignedByte(buffer, i++); + status = readUnsignedByte(buffer, i++); + numberOfEncryptedBlocks = (buffer[i++] & 0xf0) >> 4; + encryptionMode = EncryptionMode.getInstance(buffer[i++] & 0x0f); + + if (msgIsNotEnc(buffer, i)) { + encryptionMode = EncryptionMode.NONE; + } + } + + private static boolean msgIsNotEnc(byte[] buffer, int i) { + byte b = buffer[i]; + byte b2 = buffer[i + 1]; + return b == (byte) 0x2f && b2 == (byte) 0x02; + } + + private static int readUnsignedByte(byte[] msg, int i) { + return msg[i] & 0xff; + } + + public byte[] getHeader() { + return this.header; + } + + private void decodeDataRecords(byte[] buffer, int offset, int length) throws DecodingException { + int i = offset; + + while (i < offset + length - 2) { + + if ((buffer[i] & 0xef) == 0x0f) { + // manufacturer specific data + + moreRecordsFollow = (buffer[i] & 0x10) == 0x10; + + manufacturerData = Arrays.copyOfRange(buffer, i + 1, offset + length - 2); + return; + } + + if (buffer[i] == 0x2f) { + // this is a fill byte because some encryption mechanisms need multiples of 8 bytes to encode data + i++; + continue; + } + + DataRecord dataRecord = new DataRecord(); + i = dataRecord.decode(buffer, i, length); + + dataRecords.add(dataRecord); + } + + if (linkLayerSecondaryAddress != null) { + deviceHistory.put(linkLayerSecondaryAddress, dataRecords); + } + } + + private void decodeShortFrame(byte[] data, int offset, int length) throws DecodingException { + if (!deviceHistory.containsKey(linkLayerSecondaryAddress)) { + deviceHistory.put(linkLayerSecondaryAddress, new LinkedList()); + } + + ByteBuffer buf = ByteBuffer.wrap(data, offset, length); + buf.order(ByteOrder.nativeOrder()); + + // skip checksum data + buf.position(4); + + this.dataRecords = deviceHistory.get(linkLayerSecondaryAddress); + + ListIterator iter = this.dataRecords.listIterator(); + while (iter.hasNext()) { + DataRecord dr = iter.next(); + try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { + os.write(dr.getDib()); + os.write(dr.getVib()); + + int tempOffset = dr.getDib().length + dr.getVib().length; + + // This might not be right + int dataLegth = tempOffset + dr.getDataLength(); + byte[] b = new byte[dataLegth - tempOffset]; + buf.get(b); + os.write(b); + + DataRecord newDataRecord = new DataRecord(); + newDataRecord.decode(os.toByteArray(), 0, dataLegth); + iter.set(newDataRecord); + } catch (IOException e) { + // ignore + } + + } + } + + public byte[] decryptMessage(byte[] key) throws DecodingException { + + if (encryptionMode == EncryptionMode.NONE) { + return vdr; + } + + if (key == null) { + throw new DecodingException("AES key for given address not specified."); + } + + final int len = numberOfEncryptedBlocks * 16; + + if (len > vdr.length) { + throw new DecodingException("Number of encrypted exceeds payload size!"); + } + + switch (encryptionMode) { + case AES_CBC_IV: + decryptAesCbcIv(key, len); + break; + case AES_128: + decryptAes128(key, len); + break; + default: + throw new DecodingException("Unsupported encryption mode: " + encryptionMode); + } + + return vdr; + } + + private void decryptAes128(byte[] key, final int len) throws DecodingException { + byte[] iv = createIvKamstrup(); + byte[] result = AesCrypt.newAesCtrCrypt(key, iv).decrypt(vdr, len); + + byte[] crc = CRC16.calculateCrc16(Arrays.copyOfRange(result, 2, result.length)); + + if (result[0] != crc[0] || result[1] != crc[1]) { + throw new DecodingException(newDecyptionExceptionMsg()); + } + vdr = result; + } + + private void decryptAesCbcIv(byte[] key, final int len) throws DecodingException { + byte[] iv = createIv(); + byte[] result = AesCrypt.newAesCrypt(key, iv).decrypt(this.vdr, len); + if (!(result[0] == 0x2f && result[1] == 0x2f)) { + throw new DecodingException(newDecyptionExceptionMsg()); + } + System.arraycopy(result, 0, vdr, 0, len); + } + + private String newDecyptionExceptionMsg() { + String deviceId = linkLayerSecondaryAddress.getDeviceId().toString(); + String manId = linkLayerSecondaryAddress.getManufacturerId(); + return String.format("%s - %s - Decryption unsuccessful! Wrong AES/CTR Key?", deviceId, manId); + } + + private byte[] createIv() { + byte[] iv = new byte[16]; + byte[] saBytes = linkLayerSecondaryAddress.asByteArray(); + + if (linkLayerSecondaryAddress.isLongHeader()) { + System.arraycopy(saBytes, 0, iv, 4, 2); // Manufacture + System.arraycopy(saBytes, 2, iv, 0, 4); // Identification + System.arraycopy(saBytes, 6, iv, 6, 2); // Version and Device Type + } else { + System.arraycopy(saBytes, 0, iv, 0, 8); + } + + for (int i = 8; i < iv.length; i++) { + iv[i] = (byte) accessNumber; + } + + return iv; + } + + private byte[] createIvKamstrup() throws DecodingException { + try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { + + os.write(linkLayerSecondaryAddress.asByteArray(), 0, 8); + /* set hop count to 0 in case a repeater is used */ + os.write(communicationControl & ~(1 << 4)); + os.write(sessionNumber); + os.write(new byte[3]); // 3 * 0x00 + + return os.toByteArray(); + } catch (IOException e) { + throw new DecodingException("Unable to create initial vector for decryption.", e); + } + } + + private byte[] getKey() throws DecodingException { + byte[] key = keyMap.get(linkLayerSecondaryAddress); + if (key != null) { + return key; + } + + String msg = "Unable to decode encrypted payload because no key for the following secondary address was registered: " + + linkLayerSecondaryAddress; + throw new DecodingException(msg); + } + + @Override + public String toString() { + if (!decoded) { + int from = offset; + int to = from + length; + String hexString = DatatypeConverter.printHexBinary(Arrays.copyOfRange(buffer, from, to)); + return MessageFormat.format("VariableDataResponse has not been decoded. Bytes:\n{0}", hexString); + } + + StringBuilder builder = new StringBuilder(); + + if (secondaryAddress != null) { + builder.append("Secondary address: {").append(secondaryAddress).append("}\n"); + } + + builder.append("Short Header: {Access No.: ").append(accessNumber).append(", status: ").append(status) + .append(", encryption mode: ").append(encryptionMode).append(", number of encrypted blocks: ") + .append(numberOfEncryptedBlocks).append("}"); + + for (DataRecord dataRecord : dataRecords) { + builder.append("\n").append(dataRecord.toString()); + } + + if (manufacturerData.length != 0) { + String manDaraHexStr = DatatypeConverter.printHexBinary(manufacturerData); + builder.append("\nManufacturer specific bytes:\n").append(manDaraHexStr); + } + + if (moreRecordsFollow) { + builder.append("\nMore records follow ..."); + } + return builder.toString(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/VerboseMessage.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/VerboseMessage.java index 31b4387..958c77f 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/VerboseMessage.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/VerboseMessage.java @@ -1,48 +1,48 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus; - -/** - * This class represents a verbose message. This may be useful to debug a connection. - * - * @see VerboseMessageListener - */ -public class VerboseMessage { - - private final MessageDirection messageDirection; - private final byte[] message; - - VerboseMessage(MessageDirection messageDirection, byte[] message) { - this.messageDirection = messageDirection; - this.message = message; - } - - /** - * Get the message data. - * - * @return an octet string. - */ - public byte[] getMessage() { - return message; - } - - /** - * Get the direction of the message. - * - * @return the message direction. - */ - public MessageDirection getMessageDirection() { - return messageDirection; - } - - /** - * The direction of message. - */ - public enum MessageDirection { - SEND, - RECEIVE; - } -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus; + +/** + * This class represents a verbose message. This may be useful to debug a connection. + * + * @see VerboseMessageListener + */ +public class VerboseMessage { + + private final MessageDirection messageDirection; + private final byte[] message; + + VerboseMessage(MessageDirection messageDirection, byte[] message) { + this.messageDirection = messageDirection; + this.message = message; + } + + /** + * Get the message data. + * + * @return an octet string. + */ + public byte[] getMessage() { + return message; + } + + /** + * Get the direction of the message. + * + * @return the message direction. + */ + public MessageDirection getMessageDirection() { + return messageDirection; + } + + /** + * The direction of message. + */ + public enum MessageDirection { + SEND, + RECEIVE; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/VerboseMessageListener.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/VerboseMessageListener.java index 60254b9..8c6d03d 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/VerboseMessageListener.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/VerboseMessageListener.java @@ -1,20 +1,20 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus; - -import java.util.EventListener; - -/** - * - * @see MBusConnection#setVerboseMessageListener(VerboseMessageListener) - */ -public interface VerboseMessageListener extends EventListener { - /** - * - * @param debugMessage - */ - void newVerboseMessage(VerboseMessage debugMessage); -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus; + +import java.util.EventListener; + +/** + * + * @see MBusConnection#setVerboseMessageListener(VerboseMessageListener) + */ +public interface VerboseMessageListener extends EventListener { + /** + * + * @param debugMessage + */ + void newVerboseMessage(VerboseMessage debugMessage); +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/package-info.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/package-info.java index 14376e6..a69ebc0 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/package-info.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/package-info.java @@ -1,11 +1,11 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -/** - * Main jmbus package. - * - * @see org.openmuc.jmbus.MBusConnection - */ -package org.openmuc.jmbus; +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/** + * Main jmbus package. + * + * @see org.openmuc.jmbus.MBusConnection + */ +package org.openmuc.jmbus; diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/Builder.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/Builder.java index 887e435..70c2b78 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/Builder.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/Builder.java @@ -1,64 +1,64 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus.transportlayer; - -import java.io.IOException; - -/** - * A abstract builder to an active M-Bus connection. - * - * @param - * the resulting connection after calling {@link #build()}. - * @param - * the inheriting builder type. - */ -public abstract class Builder> { - - private int timeout; - - protected Builder() { - this.timeout = 500; - } - - /** - * Set the connection timeout. The timeout must be ≥ zero. A timeout of zero is interpreted as infinite timeout, - * - * @param timeout - * a timeout in milliseconds. - * @return the builder itself. - */ - public B setTimeout(int timeout) { - this.timeout = timeout; - return self(); - } - - int getTimeout() { - return timeout; - } - - @SuppressWarnings("unchecked") - protected B self() { - return (B) this; - } - - /** - * Build the TransportLayer with the given settings. - * - * @return TransportLayer to connect to the M-Bus device - * @throws IOException - * if an I/O exception occurred. - */ - protected abstract TransportLayer buildTransportLayer() throws IOException; - - /** - * This return an active M-Bus connection. - * - * @return a new active M-Bus connection. - * @throws IOException - * if an I/O exception occurred opening the connection to the remote device. - */ - public abstract C build() throws IOException; -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.transportlayer; + +import java.io.IOException; + +/** + * A abstract builder to an active M-Bus connection. + * + * @param + * the resulting connection after calling {@link #build()}. + * @param + * the inheriting builder type. + */ +public abstract class Builder> { + + private int timeout; + + protected Builder() { + this.timeout = 500; + } + + /** + * Set the connection timeout. The timeout must be ≥ zero. A timeout of zero is interpreted as infinite timeout, + * + * @param timeout + * a timeout in milliseconds. + * @return the builder itself. + */ + public B setTimeout(int timeout) { + this.timeout = timeout; + return self(); + } + + int getTimeout() { + return timeout; + } + + @SuppressWarnings("unchecked") + protected B self() { + return (B) this; + } + + /** + * Build the TransportLayer with the given settings. + * + * @return TransportLayer to connect to the M-Bus device + * @throws IOException + * if an I/O exception occurred. + */ + protected abstract TransportLayer buildTransportLayer() throws IOException; + + /** + * This return an active M-Bus connection. + * + * @return a new active M-Bus connection. + * @throws IOException + * if an I/O exception occurred opening the connection to the remote device. + */ + public abstract C build() throws IOException; +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/SerialBuilder.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/SerialBuilder.java index 0b49c4d..5658071 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/SerialBuilder.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/SerialBuilder.java @@ -1,109 +1,109 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus.transportlayer; - -import org.openmuc.jrxtx.DataBits; -import org.openmuc.jrxtx.Parity; -import org.openmuc.jrxtx.SerialPortBuilder; -import org.openmuc.jrxtx.StopBits; - -/** - * Connection builder for serial connections. - */ -public abstract class SerialBuilder> extends Builder { - - private String serialPortName; - private int baudrate; - - private DataBits dataBits; - private StopBits stopBits; - private Parity parity; - - /** - * Constructor of the Serial Settings Builder, for connecting M-Bus devices over serial connections like RS232 and - * RS485. With default settings. - * - * @param serialPortName - * examples for serial port identifiers are on Linux "/dev/ttyS0" or "/dev/ttyUSB0" and on Windows "COM1" - **/ - protected SerialBuilder(String serialPortName) { - this.serialPortName = serialPortName; - - this.baudrate = 2400; - this.dataBits = DataBits.DATABITS_8; - this.stopBits = StopBits.STOPBITS_1; - this.parity = Parity.EVEN; - } - - /** - * Sets the baudrate of the device - * - * @param baudrate - * the baud rate to use. - * @return the builder itself - */ - public S setBaudrate(int baudrate) { - this.baudrate = baudrate; - return self(); - } - - /** - * Sets the serial port name of the device - * - * @param serialPortName - * examples for serial port identifiers are on Linux {@code "/dev/ttyS0"} or {@code "/dev/ttyUSB0"} and - * on Windows {@code "COM1"}. - * @return the builder itself. - */ - public S setSerialPortName(String serialPortName) { - this.serialPortName = serialPortName; - return self(); - } - - /** - * Sets the number of DataBits, default is {@link DataBits#DATABITS_8}. - * - * @param dataBits - * the new number of databits. - * @return the builder itself. - */ - public S setDataBits(DataBits dataBits) { - this.dataBits = dataBits; - return self(); - } - - /** - * Sets the stop bits, default is 1 - * - * @param stopBits - * Possible values are 1, 1.5 or 2 - * @return the builder itself - */ - public S setStopBits(StopBits stopBits) { - this.stopBits = stopBits; - return self(); - } - - /** - * Sets the parity, default is NONE - * - * @param parity - * Possible values are NONE, EVEN, ODD, SPACE or MARK. - * @return the builder itself - */ - public S setParity(Parity parity) { - this.parity = parity; - return self(); - } - - @Override - protected TransportLayer buildTransportLayer() { - SerialPortBuilder serialPortBuilder = SerialPortBuilder.newBuilder(serialPortName).setBaudRate(baudrate) - .setDataBits(dataBits).setStopBits(stopBits).setStopBits(stopBits).setParity(parity); - - return new SerialLayer(getTimeout(), serialPortBuilder); - } -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.transportlayer; + +import org.openmuc.jrxtx.DataBits; +import org.openmuc.jrxtx.Parity; +import org.openmuc.jrxtx.SerialPortBuilder; +import org.openmuc.jrxtx.StopBits; + +/** + * Connection builder for serial connections. + */ +public abstract class SerialBuilder> extends Builder { + + private String serialPortName; + private int baudrate; + + private DataBits dataBits; + private StopBits stopBits; + private Parity parity; + + /** + * Constructor of the Serial Settings Builder, for connecting M-Bus devices over serial connections like RS232 and + * RS485. With default settings. + * + * @param serialPortName + * examples for serial port identifiers are on Linux "/dev/ttyS0" or "/dev/ttyUSB0" and on Windows "COM1" + **/ + protected SerialBuilder(String serialPortName) { + this.serialPortName = serialPortName; + + this.baudrate = 2400; + this.dataBits = DataBits.DATABITS_8; + this.stopBits = StopBits.STOPBITS_1; + this.parity = Parity.EVEN; + } + + /** + * Sets the baudrate of the device + * + * @param baudrate + * the baud rate to use. + * @return the builder itself + */ + public S setBaudrate(int baudrate) { + this.baudrate = baudrate; + return self(); + } + + /** + * Sets the serial port name of the device + * + * @param serialPortName + * examples for serial port identifiers are on Linux {@code "/dev/ttyS0"} or {@code "/dev/ttyUSB0"} and + * on Windows {@code "COM1"}. + * @return the builder itself. + */ + public S setSerialPortName(String serialPortName) { + this.serialPortName = serialPortName; + return self(); + } + + /** + * Sets the number of DataBits, default is {@link DataBits#DATABITS_8}. + * + * @param dataBits + * the new number of databits. + * @return the builder itself. + */ + public S setDataBits(DataBits dataBits) { + this.dataBits = dataBits; + return self(); + } + + /** + * Sets the stop bits, default is 1 + * + * @param stopBits + * Possible values are 1, 1.5 or 2 + * @return the builder itself + */ + public S setStopBits(StopBits stopBits) { + this.stopBits = stopBits; + return self(); + } + + /** + * Sets the parity, default is NONE + * + * @param parity + * Possible values are NONE, EVEN, ODD, SPACE or MARK. + * @return the builder itself + */ + public S setParity(Parity parity) { + this.parity = parity; + return self(); + } + + @Override + protected TransportLayer buildTransportLayer() { + SerialPortBuilder serialPortBuilder = SerialPortBuilder.newBuilder(serialPortName).setBaudRate(baudrate) + .setDataBits(dataBits).setStopBits(stopBits).setStopBits(stopBits).setParity(parity); + + return new SerialLayer(getTimeout(), serialPortBuilder); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/SerialLayer.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/SerialLayer.java index fb4c2a7..5fc272c 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/SerialLayer.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/SerialLayer.java @@ -1,73 +1,73 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus.transportlayer; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - -import org.openmuc.jrxtx.SerialPort; -import org.openmuc.jrxtx.SerialPortBuilder; - -class SerialLayer implements TransportLayer { - private final SerialPortBuilder serialPortBuilder; - private final int timeout; - - private DataOutputStream os; - private DataInputStream is; - private SerialPort serialPort; - - public SerialLayer(int timeout, SerialPortBuilder serialPortBuilder) { - this.serialPortBuilder = serialPortBuilder; - this.timeout = timeout; - } - - @Override - public void open() throws IOException { - serialPort = serialPortBuilder.build(); - serialPort.setSerialPortTimeout(timeout); - - os = new DataOutputStream(serialPort.getOutputStream()); - is = new DataInputStream(serialPort.getInputStream()); - } - - @Override - public DataOutputStream getOutputStream() { - return os; - } - - @Override - public DataInputStream getInputStream() { - return is; - } - - @Override - public void close() { - if (serialPort == null || serialPort.isClosed()) { - return; - } - try { - serialPort.close(); - } catch (IOException e) { - // ignore - } - } - - @Override - public boolean isClosed() { - return serialPort == null; - } - - @Override - public void setTimeout(int timeout) throws IOException { - serialPort.setSerialPortTimeout(timeout); - } - - @Override - public int getTimeout() { - return serialPort.getSerialPortTimeout(); - } -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.transportlayer; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import org.openmuc.jrxtx.SerialPort; +import org.openmuc.jrxtx.SerialPortBuilder; + +class SerialLayer implements TransportLayer { + private final SerialPortBuilder serialPortBuilder; + private final int timeout; + + private DataOutputStream os; + private DataInputStream is; + private SerialPort serialPort; + + public SerialLayer(int timeout, SerialPortBuilder serialPortBuilder) { + this.serialPortBuilder = serialPortBuilder; + this.timeout = timeout; + } + + @Override + public void open() throws IOException { + serialPort = serialPortBuilder.build(); + serialPort.setSerialPortTimeout(timeout); + + os = new DataOutputStream(serialPort.getOutputStream()); + is = new DataInputStream(serialPort.getInputStream()); + } + + @Override + public DataOutputStream getOutputStream() { + return os; + } + + @Override + public DataInputStream getInputStream() { + return is; + } + + @Override + public void close() { + if (serialPort == null || serialPort.isClosed()) { + return; + } + try { + serialPort.close(); + } catch (IOException e) { + // ignore + } + } + + @Override + public boolean isClosed() { + return serialPort == null; + } + + @Override + public void setTimeout(int timeout) throws IOException { + serialPort.setSerialPortTimeout(timeout); + } + + @Override + public int getTimeout() { + return serialPort.getSerialPortTimeout(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/TcpBuilder.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/TcpBuilder.java index 28240ce..8a7c6cb 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/TcpBuilder.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/TcpBuilder.java @@ -1,73 +1,73 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus.transportlayer; - -/** - * Connection builder for TCP connections.
    - * M-Bus over TCP has the same data like M-Bus over Serial connection, only the transport layer and below differs. - */ -public abstract class TcpBuilder> extends Builder { - - private String hostAddress; - private int port; - private int connectionTimeout = 10000; // 10 s - - /** - * Constructor of the TCP/IP Settings Builder, for connecting M-Bus devices over TCP/IP. - * - * @param hostAddress - * examples for IP host address port are "127.0.0.1, localhost, ..." - * @param port - * the TCP port of the host - **/ - protected TcpBuilder(String hostAddress, int port) { - setHostAddress(hostAddress); - setPort(port); - } - - /** - * Sets the TCP port of this communication - * - * @param port - * the TCP port of the host - * @return the builder itself - * - **/ - public S setPort(int port) { - this.port = port; - return self(); - } - - /** - * Sets the IP host address of the device - * - * @param hostAddress - * examples for IP host address port are "127.0.0.1, localhost, ..." - * @return the builder itself - */ - public S setHostAddress(String hostAddress) { - this.hostAddress = hostAddress; - return self(); - } - - /** - * Sets the TCP connection timeout. - * - * @param connectionTimeout - * the TCP connection timeout - * @return the builder itself - * - **/ - public S setConnectionTimeout(int connectionTimeout) { - this.connectionTimeout = connectionTimeout; - return self(); - } - - @Override - protected TransportLayer buildTransportLayer() { - return new TcpLayer(hostAddress, port, connectionTimeout, getTimeout()); - } -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.transportlayer; + +/** + * Connection builder for TCP connections.
    + * M-Bus over TCP has the same data like M-Bus over Serial connection, only the transport layer and below differs. + */ +public abstract class TcpBuilder> extends Builder { + + private String hostAddress; + private int port; + private int connectionTimeout = 10000; // 10 s + + /** + * Constructor of the TCP/IP Settings Builder, for connecting M-Bus devices over TCP/IP. + * + * @param hostAddress + * examples for IP host address port are "127.0.0.1, localhost, ..." + * @param port + * the TCP port of the host + **/ + protected TcpBuilder(String hostAddress, int port) { + setHostAddress(hostAddress); + setPort(port); + } + + /** + * Sets the TCP port of this communication + * + * @param port + * the TCP port of the host + * @return the builder itself + * + **/ + public S setPort(int port) { + this.port = port; + return self(); + } + + /** + * Sets the IP host address of the device + * + * @param hostAddress + * examples for IP host address port are "127.0.0.1, localhost, ..." + * @return the builder itself + */ + public S setHostAddress(String hostAddress) { + this.hostAddress = hostAddress; + return self(); + } + + /** + * Sets the TCP connection timeout. + * + * @param connectionTimeout + * the TCP connection timeout + * @return the builder itself + * + **/ + public S setConnectionTimeout(int connectionTimeout) { + this.connectionTimeout = connectionTimeout; + return self(); + } + + @Override + protected TransportLayer buildTransportLayer() { + return new TcpLayer(hostAddress, port, connectionTimeout, getTimeout()); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/TcpLayer.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/TcpLayer.java index a8c4d0e..d447ec1 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/TcpLayer.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/TcpLayer.java @@ -1,98 +1,98 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus.transportlayer; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.SocketAddress; -import java.text.MessageFormat; - -class TcpLayer implements TransportLayer { - private final String hostAddress; - private final int port; - private final int timeout; - private final int connectionTimeout; - - private Socket client; - private DataOutputStream os; - private DataInputStream is; - - TcpLayer(String hostAddress, int port, int connectionTimeout, int timeout) { - this.hostAddress = hostAddress; - this.port = port; - this.timeout = timeout; - this.connectionTimeout = connectionTimeout; - } - - @Override - public void open() throws IOException { - InetAddress hostname = InetAddress.getByName(hostAddress); - - try { - SocketAddress socketAddress = new InetSocketAddress(hostname, port); - this.client = new Socket(); - this.client.connect(socketAddress, connectionTimeout); - this.client.setSoTimeout(timeout); - } catch (IOException e) { - String msg = MessageFormat.format("Connecting to {0}:{1} failed.", hostname, port); - throw new IOException(msg, e); - } - - initialiseIOStreams(); - } - - private void initialiseIOStreams() throws IOException { - try { - this.os = new DataOutputStream(client.getOutputStream()); - this.is = new DataInputStream(client.getInputStream()); - } catch (IOException e) { - close(); - throw new IOException("Error getting output or input stream from TCP connection.", e); - } - } - - @Override - public void close() { - if (client == null || client.isClosed()) { - return; - } - try { - client.close(); - } catch (IOException e) { - // ignore this here - } - client = null; - } - - @Override - public DataOutputStream getOutputStream() { - return os; - } - - @Override - public DataInputStream getInputStream() { - return is; - } - - @Override - public boolean isClosed() { - return client.isClosed(); - } - - @Override - public void setTimeout(int timeout) throws IOException { - client.setSoTimeout(timeout); - } - - @Override - public int getTimeout() throws IOException { - return client.getSoTimeout(); - } -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.transportlayer; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.text.MessageFormat; + +class TcpLayer implements TransportLayer { + private final String hostAddress; + private final int port; + private final int timeout; + private final int connectionTimeout; + + private Socket client; + private DataOutputStream os; + private DataInputStream is; + + TcpLayer(String hostAddress, int port, int connectionTimeout, int timeout) { + this.hostAddress = hostAddress; + this.port = port; + this.timeout = timeout; + this.connectionTimeout = connectionTimeout; + } + + @Override + public void open() throws IOException { + InetAddress hostname = InetAddress.getByName(hostAddress); + + try { + SocketAddress socketAddress = new InetSocketAddress(hostname, port); + this.client = new Socket(); + this.client.connect(socketAddress, connectionTimeout); + this.client.setSoTimeout(timeout); + } catch (IOException e) { + String msg = MessageFormat.format("Connecting to {0}:{1} failed.", hostname, port); + throw new IOException(msg, e); + } + + initialiseIOStreams(); + } + + private void initialiseIOStreams() throws IOException { + try { + this.os = new DataOutputStream(client.getOutputStream()); + this.is = new DataInputStream(client.getInputStream()); + } catch (IOException e) { + close(); + throw new IOException("Error getting output or input stream from TCP connection.", e); + } + } + + @Override + public void close() { + if (client == null || client.isClosed()) { + return; + } + try { + client.close(); + } catch (IOException e) { + // ignore this here + } + client = null; + } + + @Override + public DataOutputStream getOutputStream() { + return os; + } + + @Override + public DataInputStream getInputStream() { + return is; + } + + @Override + public boolean isClosed() { + return client.isClosed(); + } + + @Override + public void setTimeout(int timeout) throws IOException { + client.setSoTimeout(timeout); + } + + @Override + public int getTimeout() throws IOException { + return client.getSoTimeout(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/TransportLayer.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/TransportLayer.java index fc9776a..33e7866 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/TransportLayer.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/TransportLayer.java @@ -1,72 +1,72 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus.transportlayer; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; - -/** - * The MBus transport layer interface. - */ -public interface TransportLayer extends AutoCloseable { - - /** - * Opens the transport layer. The layer needs to be opened before attempting to read a device. - * - * @throws IOException - * if an I/O error occurs while opening. - * - */ - void open() throws IOException; - - /** - * Closes the transport layer. - */ - @Override - void close(); - - /** - * Get the output stream of the layer. - * - * @return the output stream. - */ - DataOutputStream getOutputStream(); - - /** - * Get the input stream of the layer. - * - * @return the input stream. - */ - DataInputStream getInputStream(); - - /** - * Check if the layer is open. - * - * @return {@code true} if the layer is closed. - */ - boolean isClosed(); - - /** - * Set the response timeout. - * - * @param timeout - * the timeout in MILLIS. - * @throws IOException - * if an I/O error occurs. - */ - void setTimeout(int timeout) throws IOException; - - /** - * Get the response timeout in MILLIS. - * - * @return the response timeout in MILLIS. - * - * @throws IOException - * if an I/O error occurs. - */ - int getTimeout() throws IOException; -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.transportlayer; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * The MBus transport layer interface. + */ +public interface TransportLayer extends AutoCloseable { + + /** + * Opens the transport layer. The layer needs to be opened before attempting to read a device. + * + * @throws IOException + * if an I/O error occurs while opening. + * + */ + void open() throws IOException; + + /** + * Closes the transport layer. + */ + @Override + void close(); + + /** + * Get the output stream of the layer. + * + * @return the output stream. + */ + DataOutputStream getOutputStream(); + + /** + * Get the input stream of the layer. + * + * @return the input stream. + */ + DataInputStream getInputStream(); + + /** + * Check if the layer is open. + * + * @return {@code true} if the layer is closed. + */ + boolean isClosed(); + + /** + * Set the response timeout. + * + * @param timeout + * the timeout in MILLIS. + * @throws IOException + * if an I/O error occurs. + */ + void setTimeout(int timeout) throws IOException; + + /** + * Get the response timeout in MILLIS. + * + * @return the response timeout in MILLIS. + * + * @throws IOException + * if an I/O error occurs. + */ + int getTimeout() throws IOException; +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/package-info.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/package-info.java index 02a656a..1f30e2f 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/package-info.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/package-info.java @@ -1,9 +1,9 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -/** - * This package contains the transport layers and their builders. - */ -package org.openmuc.jmbus.transportlayer; +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/** + * This package contains the transport layers and their builders. + */ +package org.openmuc.jmbus.transportlayer; diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/AbstractWMBusConnection.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/AbstractWMBusConnection.java index 7554611..c755aed 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/AbstractWMBusConnection.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/AbstractWMBusConnection.java @@ -1,132 +1,132 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus.wireless; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.InterruptedIOException; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import org.openmuc.jmbus.SecondaryAddress; -import org.openmuc.jmbus.transportlayer.TransportLayer; - -abstract class AbstractWMBusConnection implements WMBusConnection { - - private static final int ACK = 0x3E; - - protected static final int BUFFER_LENGTH = 1000; - protected static final int MESSAGE_FRAGEMENT_TIMEOUT = 1000; - - private TransportLayer transportLayer; - - private final WMBusMode mode; - private final WMBusListener listener; - - final Map keyMap = new HashMap<>(); - - private volatile boolean closed; - private final ExecutorService receiverService; - - protected AbstractWMBusConnection(WMBusMode mode, WMBusListener listener, TransportLayer tl) { - this.listener = listener; - this.mode = mode; - this.transportLayer = tl; - - this.closed = true; - this.receiverService = Executors.newSingleThreadExecutor(); - } - - @Override - public final void close() { - if (this.transportLayer == null || this.closed) { - // nothing to do - return; - } - - try { - this.receiverService.shutdown(); - this.transportLayer.close(); - } finally { - this.transportLayer = null; - this.closed = true; - } - } - - @Override - public final void addKey(SecondaryAddress address, byte[] key) { - this.keyMap.put(address, key); - } - - @Override - public final void removeKey(SecondaryAddress address) { - this.keyMap.remove(address); - } - - public final void open() throws IOException { - if (!closed) { - return; - } - try { - transportLayer.open(); - - initializeWirelessTransceiver(mode); - - } catch (IOException e) { - transportLayer.close(); - - throw e; - } - this.receiverService.execute(newMessageReceiver(this.transportLayer, this.listener)); - - this.closed = false; - } - - protected abstract MessageReceiver newMessageReceiver(TransportLayer transportLayer, WMBusListener listener); - - protected abstract void initializeWirelessTransceiver(WMBusMode mode) throws IOException; - - protected boolean isClosed() { - return closed; - } - - protected DataInputStream getInputStream() { - return this.transportLayer.getInputStream(); - } - - protected DataOutputStream getOutputStream() { - return this.transportLayer.getOutputStream(); - } - - protected long discardNoise() throws IOException { - DataInputStream inputStream = getInputStream(); - - if (inputStream.available() == 0) { - return 0; - } - - transportLayer.setTimeout(MESSAGE_FRAGEMENT_TIMEOUT); - try { - return inputStream.skip(500); - - } catch (InterruptedIOException e) { - // ignore - return 0; - } - } - - protected void waitForAck() throws IOException { - transportLayer.setTimeout(MESSAGE_FRAGEMENT_TIMEOUT); - - int b = getInputStream().read(); - if (b != ACK) { - throw new IOException(String.format("Did not receive ACK. Received 0x%02X instead.", b)); - } - } -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.wireless; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.openmuc.jmbus.SecondaryAddress; +import org.openmuc.jmbus.transportlayer.TransportLayer; + +abstract class AbstractWMBusConnection implements WMBusConnection { + + private static final int ACK = 0x3E; + + protected static final int BUFFER_LENGTH = 1000; + protected static final int MESSAGE_FRAGEMENT_TIMEOUT = 1000; + + private TransportLayer transportLayer; + + private final WMBusMode mode; + private final WMBusListener listener; + + final Map keyMap = new HashMap<>(); + + private volatile boolean closed; + private final ExecutorService receiverService; + + protected AbstractWMBusConnection(WMBusMode mode, WMBusListener listener, TransportLayer tl) { + this.listener = listener; + this.mode = mode; + this.transportLayer = tl; + + this.closed = true; + this.receiverService = Executors.newSingleThreadExecutor(); + } + + @Override + public final void close() { + if (this.transportLayer == null || this.closed) { + // nothing to do + return; + } + + try { + this.receiverService.shutdown(); + this.transportLayer.close(); + } finally { + this.transportLayer = null; + this.closed = true; + } + } + + @Override + public final void addKey(SecondaryAddress address, byte[] key) { + this.keyMap.put(address, key); + } + + @Override + public final void removeKey(SecondaryAddress address) { + this.keyMap.remove(address); + } + + public final void open() throws IOException { + if (!closed) { + return; + } + try { + transportLayer.open(); + + initializeWirelessTransceiver(mode); + + } catch (IOException e) { + transportLayer.close(); + + throw e; + } + this.receiverService.execute(newMessageReceiver(this.transportLayer, this.listener)); + + this.closed = false; + } + + protected abstract MessageReceiver newMessageReceiver(TransportLayer transportLayer, WMBusListener listener); + + protected abstract void initializeWirelessTransceiver(WMBusMode mode) throws IOException; + + protected boolean isClosed() { + return closed; + } + + protected DataInputStream getInputStream() { + return this.transportLayer.getInputStream(); + } + + protected DataOutputStream getOutputStream() { + return this.transportLayer.getOutputStream(); + } + + protected long discardNoise() throws IOException { + DataInputStream inputStream = getInputStream(); + + if (inputStream.available() == 0) { + return 0; + } + + transportLayer.setTimeout(MESSAGE_FRAGEMENT_TIMEOUT); + try { + return inputStream.skip(500); + + } catch (InterruptedIOException e) { + // ignore + return 0; + } + } + + protected void waitForAck() throws IOException { + transportLayer.setTimeout(MESSAGE_FRAGEMENT_TIMEOUT); + + int b = getInputStream().read(); + if (b != ACK) { + throw new IOException(String.format("Did not receive ACK. Received 0x%02X instead.", b)); + } + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/HciMessageException.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/HciMessageException.java index d9d99c8..89175ba 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/HciMessageException.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/HciMessageException.java @@ -1,16 +1,16 @@ -package org.openmuc.jmbus.wireless; - -import java.io.IOException; - -class HciMessageException extends IOException { - - private final byte[] data; - - public HciMessageException(byte[] data) { - this.data = data; - } - - public byte[] getData() { - return data; - } -} +package org.openmuc.jmbus.wireless; + +import java.io.IOException; + +class HciMessageException extends IOException { + + private final byte[] data; + + public HciMessageException(byte[] data) { + this.data = data; + } + + public byte[] getData() { + return data; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/MessageReceiver.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/MessageReceiver.java index 32d9d01..70acec5 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/MessageReceiver.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/MessageReceiver.java @@ -1,52 +1,52 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus.wireless; - -import java.io.IOException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -abstract class MessageReceiver implements Runnable { - - private final ExecutorService executor; - private final WMBusListener listener; - - public MessageReceiver(WMBusListener listener) { - this.listener = listener; - this.executor = Executors.newSingleThreadExecutor(); - } - - protected void shutdown() { - this.executor.shutdown(); - } - - protected void notifyStoppedListening(final IOException ioException) { - executor.execute(new Runnable() { - @Override - public void run() { - listener.stoppedListening(ioException); - } - }); - } - - protected void notifyNewMessage(final WMBusMessage wmBusMessage) { - executor.execute(new Runnable() { - @Override - public void run() { - listener.newMessage(wmBusMessage); - } - }); - } - - protected void notifyDiscarded(final byte[] discardedBytes) { - executor.execute(new Runnable() { - @Override - public void run() { - listener.discardedBytes(discardedBytes); - } - }); - } -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.wireless; + +import java.io.IOException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +abstract class MessageReceiver implements Runnable { + + private final ExecutorService executor; + private final WMBusListener listener; + + public MessageReceiver(WMBusListener listener) { + this.listener = listener; + this.executor = Executors.newSingleThreadExecutor(); + } + + protected void shutdown() { + this.executor.shutdown(); + } + + protected void notifyStoppedListening(final IOException ioException) { + executor.execute(new Runnable() { + @Override + public void run() { + listener.stoppedListening(ioException); + } + }); + } + + protected void notifyNewMessage(final WMBusMessage wmBusMessage) { + executor.execute(new Runnable() { + @Override + public void run() { + listener.newMessage(wmBusMessage); + } + }); + } + + protected void notifyDiscarded(final byte[] discardedBytes) { + executor.execute(new Runnable() { + @Override + public void run() { + listener.discardedBytes(discardedBytes); + } + }); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/VirtualWMBusMessageHelper.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/VirtualWMBusMessageHelper.java index 76709ab..001769c 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/VirtualWMBusMessageHelper.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/VirtualWMBusMessageHelper.java @@ -1,32 +1,32 @@ -/** - * Copyright (c) 2010-2021 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.openmuc.jmbus.wireless; - -import java.util.Map; - -import org.openmuc.jmbus.DecodingException; -import org.openmuc.jmbus.SecondaryAddress; - -/** - * The {@link VirtualWMBusMessageHelper} class defines VirtualWMBusMessageHelper - * - * @author Roman Malyugin - Initial contribution - */ -public class VirtualWMBusMessageHelper { - - public static WMBusMessage decode(byte[] buffer, Integer signalStrengthInDBm, Map keyMap) - throws DecodingException { - return WMBusMessage.decode(buffer, signalStrengthInDBm, keyMap); - } -} +/** + * Copyright (c) 2010-2021 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.openmuc.jmbus.wireless; + +import java.util.Map; + +import org.openmuc.jmbus.DecodingException; +import org.openmuc.jmbus.SecondaryAddress; + +/** + * The {@link VirtualWMBusMessageHelper} class defines VirtualWMBusMessageHelper + * + * @author Roman Malyugin - Initial contribution + */ +public class VirtualWMBusMessageHelper { + + public static WMBusMessage decode(byte[] buffer, Integer signalStrengthInDBm, Map keyMap) + throws DecodingException { + return WMBusMessage.decode(buffer, signalStrengthInDBm, keyMap); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnection.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnection.java index a41a376..b5de981 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnection.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnection.java @@ -1,172 +1,172 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus.wireless; - -import java.io.IOException; -import java.text.MessageFormat; - -import org.openmuc.jmbus.SecondaryAddress; -import org.openmuc.jmbus.transportlayer.SerialBuilder; -import org.openmuc.jmbus.transportlayer.TcpBuilder; -import org.openmuc.jmbus.transportlayer.TransportLayer; -import org.openmuc.jrxtx.DataBits; -import org.openmuc.jrxtx.Parity; -import org.openmuc.jrxtx.StopBits; - -/** - * A Wireless Mbus Connection. - * - * @see #addKey(SecondaryAddress, byte[]) - */ -public interface WMBusConnection extends AutoCloseable { - - /** - * Closes the service access point. - */ - @Override - void close() throws IOException; - - /** - * Stores a pair of secondary address and cryptographic key. The stored keys are automatically used to decrypt - * messages when a wireless M-Bus message is been decoded. - * - * @param address - * the secondary address. - * @param key - * the cryptographic key. - * - * @see #removeKey(SecondaryAddress) - */ - void addKey(SecondaryAddress address, byte[] key); - - /** - * Removes the stored key for the given secondary address. - * - * @param address - * the secondary address for which to remove the stored key. - * - * @see #addKey(SecondaryAddress, byte[]) - */ - void removeKey(SecondaryAddress address); - - class WMBusSerialBuilder extends SerialBuilder { - - private final Builder builder; - - public WMBusSerialBuilder(WMBusManufacturer wmBusManufacturer, WMBusListener listener, String serialPortName) { - super(serialPortName); - builder = new Builder(wmBusManufacturer, listener); - - switch (wmBusManufacturer) { - case RADIO_CRAFTS: - setBaudrate(19200); - break; - case AMBER: - setBaudrate(9600); - break; - case IMST: - setBaudrate(57600); - break; - default: - // should not occur - throw new RuntimeException( - MessageFormat.format("Error unknown manufacturer {0}.", wmBusManufacturer)); - } - setStopBits(StopBits.STOPBITS_1).setParity(Parity.NONE).setDataBits(DataBits.DATABITS_8); - } - - public WMBusSerialBuilder setMode(WMBusMode mode) { - builder.mode = mode; - return self(); - } - - public WMBusSerialBuilder setWmBusManufacturer(WMBusManufacturer wmBusManufacturer) { - builder.wmBusManufacturer = wmBusManufacturer; - return self(); - } - - public WMBusSerialBuilder setListener(WMBusListener connectionListener) { - builder.listener = connectionListener; - return self(); - } - - @Override - public WMBusConnection build() throws IOException { - return builder.build(buildTransportLayer()); - } - } - - class WMBusTcpBuilder extends TcpBuilder { - - private final Builder builder; - - public WMBusTcpBuilder(WMBusManufacturer wmBusManufacturer, WMBusListener listener, String hostAddress, - int port) { - super(hostAddress, port); - builder = new Builder(wmBusManufacturer, listener); - } - - public WMBusTcpBuilder setMode(WMBusMode mode) { - builder.mode = mode; - return self(); - } - - public WMBusTcpBuilder setWmBusManufacturer(WMBusManufacturer wmBusManufacturer) { - builder.wmBusManufacturer = wmBusManufacturer; - return self(); - } - - public WMBusTcpBuilder setListener(WMBusListener connectionListener) { - builder.listener = connectionListener; - return self(); - } - - @Override - public WMBusConnection build() throws IOException { - return builder.build(buildTransportLayer()); - } - } - - class Builder { - - private WMBusManufacturer wmBusManufacturer; - private WMBusMode mode; - private WMBusListener listener; - - Builder(WMBusManufacturer wmBusManufacturer, WMBusListener listener) { - this.listener = listener; - this.wmBusManufacturer = wmBusManufacturer; - this.mode = WMBusMode.T; - } - - WMBusConnection build(TransportLayer transportLayer) throws IOException { - AbstractWMBusConnection wmBusConnection; - switch (this.wmBusManufacturer) { - case AMBER: - wmBusConnection = new WMBusConnectionAmber(this.mode, this.listener, transportLayer); - break; - case IMST: - wmBusConnection = new WMBusConnectionImst(this.mode, this.listener, transportLayer); - break; - case RADIO_CRAFTS: - wmBusConnection = new WMBusConnectionRadioCrafts(this.mode, this.listener, transportLayer); - break; - default: - // should not occur. - throw new RuntimeException("Unknown Manufacturer."); - } - - wmBusConnection.open(); - return wmBusConnection; - } - } - - public enum WMBusManufacturer { - AMBER, - IMST, - RADIO_CRAFTS - } -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.wireless; + +import java.io.IOException; +import java.text.MessageFormat; + +import org.openmuc.jmbus.SecondaryAddress; +import org.openmuc.jmbus.transportlayer.SerialBuilder; +import org.openmuc.jmbus.transportlayer.TcpBuilder; +import org.openmuc.jmbus.transportlayer.TransportLayer; +import org.openmuc.jrxtx.DataBits; +import org.openmuc.jrxtx.Parity; +import org.openmuc.jrxtx.StopBits; + +/** + * A Wireless Mbus Connection. + * + * @see #addKey(SecondaryAddress, byte[]) + */ +public interface WMBusConnection extends AutoCloseable { + + /** + * Closes the service access point. + */ + @Override + void close() throws IOException; + + /** + * Stores a pair of secondary address and cryptographic key. The stored keys are automatically used to decrypt + * messages when a wireless M-Bus message is been decoded. + * + * @param address + * the secondary address. + * @param key + * the cryptographic key. + * + * @see #removeKey(SecondaryAddress) + */ + void addKey(SecondaryAddress address, byte[] key); + + /** + * Removes the stored key for the given secondary address. + * + * @param address + * the secondary address for which to remove the stored key. + * + * @see #addKey(SecondaryAddress, byte[]) + */ + void removeKey(SecondaryAddress address); + + class WMBusSerialBuilder extends SerialBuilder { + + private final Builder builder; + + public WMBusSerialBuilder(WMBusManufacturer wmBusManufacturer, WMBusListener listener, String serialPortName) { + super(serialPortName); + builder = new Builder(wmBusManufacturer, listener); + + switch (wmBusManufacturer) { + case RADIO_CRAFTS: + setBaudrate(19200); + break; + case AMBER: + setBaudrate(9600); + break; + case IMST: + setBaudrate(57600); + break; + default: + // should not occur + throw new RuntimeException( + MessageFormat.format("Error unknown manufacturer {0}.", wmBusManufacturer)); + } + setStopBits(StopBits.STOPBITS_1).setParity(Parity.NONE).setDataBits(DataBits.DATABITS_8); + } + + public WMBusSerialBuilder setMode(WMBusMode mode) { + builder.mode = mode; + return self(); + } + + public WMBusSerialBuilder setWmBusManufacturer(WMBusManufacturer wmBusManufacturer) { + builder.wmBusManufacturer = wmBusManufacturer; + return self(); + } + + public WMBusSerialBuilder setListener(WMBusListener connectionListener) { + builder.listener = connectionListener; + return self(); + } + + @Override + public WMBusConnection build() throws IOException { + return builder.build(buildTransportLayer()); + } + } + + class WMBusTcpBuilder extends TcpBuilder { + + private final Builder builder; + + public WMBusTcpBuilder(WMBusManufacturer wmBusManufacturer, WMBusListener listener, String hostAddress, + int port) { + super(hostAddress, port); + builder = new Builder(wmBusManufacturer, listener); + } + + public WMBusTcpBuilder setMode(WMBusMode mode) { + builder.mode = mode; + return self(); + } + + public WMBusTcpBuilder setWmBusManufacturer(WMBusManufacturer wmBusManufacturer) { + builder.wmBusManufacturer = wmBusManufacturer; + return self(); + } + + public WMBusTcpBuilder setListener(WMBusListener connectionListener) { + builder.listener = connectionListener; + return self(); + } + + @Override + public WMBusConnection build() throws IOException { + return builder.build(buildTransportLayer()); + } + } + + class Builder { + + private WMBusManufacturer wmBusManufacturer; + private WMBusMode mode; + private WMBusListener listener; + + Builder(WMBusManufacturer wmBusManufacturer, WMBusListener listener) { + this.listener = listener; + this.wmBusManufacturer = wmBusManufacturer; + this.mode = WMBusMode.T; + } + + WMBusConnection build(TransportLayer transportLayer) throws IOException { + AbstractWMBusConnection wmBusConnection; + switch (this.wmBusManufacturer) { + case AMBER: + wmBusConnection = new WMBusConnectionAmber(this.mode, this.listener, transportLayer); + break; + case IMST: + wmBusConnection = new WMBusConnectionImst(this.mode, this.listener, transportLayer); + break; + case RADIO_CRAFTS: + wmBusConnection = new WMBusConnectionRadioCrafts(this.mode, this.listener, transportLayer); + break; + default: + // should not occur. + throw new RuntimeException("Unknown Manufacturer."); + } + + wmBusConnection.open(); + return wmBusConnection; + } + } + + public enum WMBusManufacturer { + AMBER, + IMST, + RADIO_CRAFTS + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionAmber.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionAmber.java index c41ec1f..b24fd10 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionAmber.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionAmber.java @@ -1,222 +1,222 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus.wireless; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.InterruptedIOException; -import java.nio.ByteBuffer; -import java.text.MessageFormat; -import java.util.Arrays; - -import org.openmuc.jmbus.DecodingException; -import org.openmuc.jmbus.transportlayer.TransportLayer; - -/** - * Was tested with the Amber 8426M Wireless M-Bus stick. - */ -class WMBusConnectionAmber extends AbstractWMBusConnection { - - private class MessageReceiverImpl extends MessageReceiver { - - private static final int MBUS_BL_CONTROL = 0x44; - - private int discardCount = 0; - private final TransportLayer transportLayer; - - public MessageReceiverImpl(TransportLayer transportLayer, WMBusListener listener) { - super(listener); - this.transportLayer = transportLayer; - } - - @Override - public void run() { - - try { - - while (!isClosed()) { - task(); - } - - } catch (final IOException e) { - if (isClosed()) { - return; - } - super.notifyStoppedListening(e); - - } finally { - close(); - super.shutdown(); - } - } - - private void task() throws IOException { - - ByteBuffer discardBuffer = ByteBuffer.allocate(100); - - int b0, b1; - DataInputStream is = getInputStream(); - while (true) { - try { - this.transportLayer.setTimeout(0); - b0 = is.read(); - this.transportLayer.setTimeout(MESSAGE_FRAGEMENT_TIMEOUT); - b1 = is.read(); - - if ((b1 ^ MBUS_BL_CONTROL) == 0) { - break; - } - - if (discardBuffer.capacity() - discardBuffer.position() < 2) { - discard(discardBuffer.array(), 0, discardBuffer.position()); - discardBuffer.clear(); - } - discardBuffer.put((byte) b0); - discardBuffer.put((byte) b1); - } catch (InterruptedIOException e) { - continue; - } - } - - int len = (b0 & 0xff) + 1; - byte[] data = new byte[2 + len]; - - data[0] = (byte) b0; - data[1] = (byte) b1; - - int readLength = len - 2; - int actualLength = is.read(data, 2, readLength); - - if (readLength != actualLength) { - discard(data, 0, actualLength); - return; - } - - notifyListener(data); - - if (discardBuffer.position() > 0) { - discard(discardBuffer.array(), 0, discardBuffer.position()); - } - } - - private void notifyListener(final byte[] data) { - int rssi = data[data.length - 1] & 0xff; - final Integer signalStrengthInDBm; - int rssiOffset = 74; - if (rssi >= 128) { - signalStrengthInDBm = ((rssi - 256) / 2) - rssiOffset; - } else { - signalStrengthInDBm = (rssi / 2) - rssiOffset; - } - - data[0] = (byte) (data[0] - 1); - - try { - super.notifyNewMessage(WMBusMessage.decode(data, signalStrengthInDBm, keyMap)); - } catch (DecodingException e) { - super.notifyDiscarded(data); - } - } - - private void discard(byte[] data, int offset, int length) { - discardCount++; - final byte[] discardedBytes = Arrays.copyOfRange(data, offset, offset + length); - - super.notifyDiscarded(discardedBytes); - - if (discardCount >= 5) { - try { - reset(); - } catch (IOException e) { - // ignoring reset errors here.. - } - discardCount = 0; - } - } - } - - public WMBusConnectionAmber(WMBusMode mode, WMBusListener listener, TransportLayer tl) { - super(mode, listener, tl); - } - - @Override - protected MessageReceiver newMessageReceiver(TransportLayer transportLayer, WMBusListener listener) { - return new MessageReceiverImpl(transportLayer, listener); - } - - /** - * @param mode - * - the wMBus mode to be used for transmission - * @throws IOException - */ - @Override - protected void initializeWirelessTransceiver(WMBusMode mode) throws IOException { - switch (mode) { - case S: - amberSetReg((byte) 0x46, (byte) 0x03); - break; - case T: - amberSetReg((byte) 0x46, (byte) 0x08); // T2-OTHER (correct for receiving station in T mode) - break; - case C: - amberSetReg((byte) 0x46, (byte) 0x0e); // C2-OTHER - break; - default: - String message = MessageFormat.format("wMBUS Mode ''{0}'' is not supported", mode.toString()); - throw new IOException(message); - } - amberSetReg((byte) 0x45, (byte) 0x01); // Enable attaching RSSI to message - } - - /** - * Writes a {@code CMD_SET_REQ} to the Amber module. - * - * @param cmd - * register address of the Amber module. - * @param data - * new value(s) for this register address(es). - * @throws IOException - * if an error occurred, while writing the command. - */ - private void writeCommand(byte cmd, byte[] data) throws IOException { - DataOutputStream os = getOutputStream(); - - byte[] header = ByteBuffer.allocate(3).put((byte) 0xFF).put(cmd).put((byte) data.length).array(); - - os.write(header); - os.write(data); - - byte checksum = computeCheckSum(data, computeCheckSum(header, (byte) 0)); - - os.write(checksum); - } - - private void amberSetReg(byte reg, byte value) throws IOException { - byte[] data = { reg, 0x01, value }; - - writeCommand((byte) 0x09, data); - - discardNoise(); - } - - /** - * Writes a reset command to the Amber module - * - * @throws IOException - * if the reset command failed. - */ - private void reset() throws IOException { - writeCommand((byte) 0x05, new byte[] {}); - } - - private static byte computeCheckSum(byte[] data, byte checksum) { - for (byte element : data) { - checksum = (byte) (checksum ^ element); - } - return checksum; - } -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.wireless; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.nio.ByteBuffer; +import java.text.MessageFormat; +import java.util.Arrays; + +import org.openmuc.jmbus.DecodingException; +import org.openmuc.jmbus.transportlayer.TransportLayer; + +/** + * Was tested with the Amber 8426M Wireless M-Bus stick. + */ +class WMBusConnectionAmber extends AbstractWMBusConnection { + + private class MessageReceiverImpl extends MessageReceiver { + + private static final int MBUS_BL_CONTROL = 0x44; + + private int discardCount = 0; + private final TransportLayer transportLayer; + + public MessageReceiverImpl(TransportLayer transportLayer, WMBusListener listener) { + super(listener); + this.transportLayer = transportLayer; + } + + @Override + public void run() { + + try { + + while (!isClosed()) { + task(); + } + + } catch (final IOException e) { + if (isClosed()) { + return; + } + super.notifyStoppedListening(e); + + } finally { + close(); + super.shutdown(); + } + } + + private void task() throws IOException { + + ByteBuffer discardBuffer = ByteBuffer.allocate(100); + + int b0, b1; + DataInputStream is = getInputStream(); + while (true) { + try { + this.transportLayer.setTimeout(0); + b0 = is.read(); + this.transportLayer.setTimeout(MESSAGE_FRAGEMENT_TIMEOUT); + b1 = is.read(); + + if ((b1 ^ MBUS_BL_CONTROL) == 0) { + break; + } + + if (discardBuffer.capacity() - discardBuffer.position() < 2) { + discard(discardBuffer.array(), 0, discardBuffer.position()); + discardBuffer.clear(); + } + discardBuffer.put((byte) b0); + discardBuffer.put((byte) b1); + } catch (InterruptedIOException e) { + continue; + } + } + + int len = (b0 & 0xff) + 1; + byte[] data = new byte[2 + len]; + + data[0] = (byte) b0; + data[1] = (byte) b1; + + int readLength = len - 2; + int actualLength = is.read(data, 2, readLength); + + if (readLength != actualLength) { + discard(data, 0, actualLength); + return; + } + + notifyListener(data); + + if (discardBuffer.position() > 0) { + discard(discardBuffer.array(), 0, discardBuffer.position()); + } + } + + private void notifyListener(final byte[] data) { + int rssi = data[data.length - 1] & 0xff; + final Integer signalStrengthInDBm; + int rssiOffset = 74; + if (rssi >= 128) { + signalStrengthInDBm = ((rssi - 256) / 2) - rssiOffset; + } else { + signalStrengthInDBm = (rssi / 2) - rssiOffset; + } + + data[0] = (byte) (data[0] - 1); + + try { + super.notifyNewMessage(WMBusMessage.decode(data, signalStrengthInDBm, keyMap)); + } catch (DecodingException e) { + super.notifyDiscarded(data); + } + } + + private void discard(byte[] data, int offset, int length) { + discardCount++; + final byte[] discardedBytes = Arrays.copyOfRange(data, offset, offset + length); + + super.notifyDiscarded(discardedBytes); + + if (discardCount >= 5) { + try { + reset(); + } catch (IOException e) { + // ignoring reset errors here.. + } + discardCount = 0; + } + } + } + + public WMBusConnectionAmber(WMBusMode mode, WMBusListener listener, TransportLayer tl) { + super(mode, listener, tl); + } + + @Override + protected MessageReceiver newMessageReceiver(TransportLayer transportLayer, WMBusListener listener) { + return new MessageReceiverImpl(transportLayer, listener); + } + + /** + * @param mode + * - the wMBus mode to be used for transmission + * @throws IOException + */ + @Override + protected void initializeWirelessTransceiver(WMBusMode mode) throws IOException { + switch (mode) { + case S: + amberSetReg((byte) 0x46, (byte) 0x03); + break; + case T: + amberSetReg((byte) 0x46, (byte) 0x08); // T2-OTHER (correct for receiving station in T mode) + break; + case C: + amberSetReg((byte) 0x46, (byte) 0x0e); // C2-OTHER + break; + default: + String message = MessageFormat.format("wMBUS Mode ''{0}'' is not supported", mode.toString()); + throw new IOException(message); + } + amberSetReg((byte) 0x45, (byte) 0x01); // Enable attaching RSSI to message + } + + /** + * Writes a {@code CMD_SET_REQ} to the Amber module. + * + * @param cmd + * register address of the Amber module. + * @param data + * new value(s) for this register address(es). + * @throws IOException + * if an error occurred, while writing the command. + */ + private void writeCommand(byte cmd, byte[] data) throws IOException { + DataOutputStream os = getOutputStream(); + + byte[] header = ByteBuffer.allocate(3).put((byte) 0xFF).put(cmd).put((byte) data.length).array(); + + os.write(header); + os.write(data); + + byte checksum = computeCheckSum(data, computeCheckSum(header, (byte) 0)); + + os.write(checksum); + } + + private void amberSetReg(byte reg, byte value) throws IOException { + byte[] data = { reg, 0x01, value }; + + writeCommand((byte) 0x09, data); + + discardNoise(); + } + + /** + * Writes a reset command to the Amber module + * + * @throws IOException + * if the reset command failed. + */ + private void reset() throws IOException { + writeCommand((byte) 0x05, new byte[] {}); + } + + private static byte computeCheckSum(byte[] data, byte checksum) { + for (byte element : data) { + checksum = (byte) (checksum ^ element); + } + return checksum; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionImst.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionImst.java index bb7a01e..4c1f0c0 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionImst.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionImst.java @@ -1,369 +1,369 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus.wireless; - -import java.io.DataInputStream; -import java.io.IOException; -import java.io.InterruptedIOException; -import java.nio.ByteBuffer; -import java.text.MessageFormat; -import java.util.Arrays; - -import javax.xml.bind.DatatypeConverter; - -import org.openmuc.jmbus.DecodingException; -import org.openmuc.jmbus.transportlayer.TransportLayer; - -/** - * Was tested with the IMST iM871A-USB Wireless M-Bus stick.
    - */ -class WMBusConnectionImst extends AbstractWMBusConnection { - - private class MessageReceiverImpl extends MessageReceiver { - - private static final byte MBUS_BL_CONTROL = 0x44; - private final TransportLayer transportLayer; - - public MessageReceiverImpl(TransportLayer transportLayer, WMBusListener listener) { - super(listener); - this.transportLayer = transportLayer; - } - - @Override - public void run() { - try { - - while (!isClosed()) { - try { - task(); - } catch (InterruptedIOException e) { - // ignore a timeout.. - } catch (HciMessageException e) { - super.notifyDiscarded(e.getData()); - } - } - - } catch (IOException e) { - if (isClosed()) { - return; - } - - super.notifyStoppedListening(e); - } finally { - close(); - super.shutdown(); - } - } - - private void task() throws IOException { - HciMessage hciMessage = readHciMsg(); - - final byte[] wmbusMessage = hciMessage.getPayload(); - final int signalStrengthInDBm = hciMessage.getRSSI(); - try { - super.notifyNewMessage(WMBusMessage.decode(wmbusMessage, signalStrengthInDBm, keyMap)); - } catch (DecodingException e) { - super.notifyDiscarded(wmbusMessage); - } - } - - private HciMessage readHciMsg() throws IOException { - while (true) { - HciMessage hciMessage = HciMessage.decode(this.transportLayer); - - if (hciMessage.getPayload().length <= 1) { - continue; - } - - if (hciMessage.getPayload()[1] == MBUS_BL_CONTROL) { - return hciMessage; - } else { - discard(hciMessage); - } - } - } - - private void discard(HciMessage hciMessage) { - super.notifyDiscarded(hciMessage.payload); - } - } - - public WMBusConnectionImst(WMBusMode mode, WMBusListener listener, TransportLayer tl) { - super(mode, listener, tl); - } - - @Override - protected MessageReceiver newMessageReceiver(TransportLayer transportLayer, WMBusListener listener) { - return new MessageReceiverImpl(transportLayer, listener); - } - - @Override - protected void initializeWirelessTransceiver(WMBusMode mode) throws IOException { - - byte[] payload = ByteBuffer.allocate(6).put((byte) 0x00) // NVM Flag: change configuration only temporary - .put((byte) 0x03) // IIFlag 1: Bit 0 Device Mode and Bit 1 Radio Mode - .put((byte) 0x00) // Device Mode: Meter - .put(linkRadioModeFor(mode)) // Link/Radio Mode - .put((byte) 0x10) // IIFlag 2: Bit 4 : Auto RSSI Attachment - .put((byte) 0x01) // Rx-Timestamp attached for each received Radio message - .array(); - - writeCommand(Const.DEVMGMT_ID, Const.DEVMGMT_MSG_SET_CONFIG_REQ, payload); - } - - private static byte linkRadioModeFor(WMBusMode mode) throws IOException { - switch (mode) { - case S: - return 0x01; // Link/Radio Mode: S1-m - case T: - return 0x04; // Link/Radio Mode: T2 - case C: - return 0x08; // Link/Radio Mode: C2 with telegram format A (C2 + format B = 0x09) - default: - String msg = MessageFormat.format("wMBUS Mode ''{0}'' is not supported", mode.toString()); - throw new IOException(msg); - } - } - - private boolean writeCommand(byte endpointId, byte msgId, byte[] payload) { - byte controlField = 0; - int lengthPayloadAll = payload.length & 0xFF; - double numOfPackagesTemp = (double) lengthPayloadAll / Const.MAX_SINGLE_PAYLOAD_SIZE; - - int numOfPackages = (int) numOfPackagesTemp; - int comma = (int) (numOfPackagesTemp - numOfPackages) * 100; - if (numOfPackages == 0 || comma != 0) { - ++numOfPackages; - } - - if (numOfPackages > Const.MAX_PACKAGES) { - return false; - } - - for (int i = 0; i < numOfPackages; ++i) { - int payloadSendLength = Const.MAX_SINGLE_PAYLOAD_SIZE; - if (numOfPackages - i == 1) { - payloadSendLength = lengthPayloadAll - (numOfPackages - 1) * Const.MAX_SINGLE_PAYLOAD_SIZE; - } - byte[] payloadSend = new byte[payloadSendLength]; - System.arraycopy(payload, i * payloadSendLength, payloadSend, 0, payloadSendLength); - - byte controlField_EndpointField = (byte) ((controlField << 4) | endpointId & 0xff); - byte[] hciHeader = { Const.START_OF_FRAME, controlField_EndpointField, msgId, (byte) payloadSendLength }; - - byte[] hciMessage = new byte[Const.HCI_HEADER_LENGTH + payloadSendLength]; - System.arraycopy(hciHeader, 0, hciMessage, 0, hciHeader.length); - System.arraycopy(payloadSend, 0, hciMessage, hciHeader.length, payloadSend.length); - - try { - getOutputStream().write(hciMessage); - } catch (IOException e) { - return false; - } - } - return true; - } - - /** - * Writes a reset command to the IMST module - */ - public void reset() { - writeCommand(Const.DEVMGMT_MSG_FACTORY_RESET_REQ, Const.DEVMGMT_ID, new byte[0]); - } - - /** - * IMST constants packages - */ - class Const { - public static final byte START_OF_FRAME = (byte) 0xA5; - // A5 01 03 - public static final int MAX_PACKAGES = 255; - public static final int MAX_SINGLE_PAYLOAD_SIZE = 255; - public static final int HCI_HEADER_LENGTH = 4; - - // ControlField - public static final byte RESERVED = 0x00; // 0b0000 - public static final byte TIMESTAMP_ATTACHED = 0x02; // 0b0010 - public static final byte RSSI_ATTACHED = 0x04; // 0b0100 - public static final byte CRC16_ATTACHED = 0x08; // 0b1000 (FCS) - - // List of Endpoint Identifier - public static final byte DEVMGMT_ID = 0x01; - public static final byte RADIOLINK_ID = 0x02; - public static final byte RADIOLINKTEST_ID = 0x03; - public static final byte HWTEST_ID = 0x04; - - // Device Management MessageIdentifier - public static final byte DEVMGMT_MSG_PING_REQ = 0x01; - public static final byte DEVMGMT_MSG_PING_RSP = 0x02; - public static final byte DEVMGMT_MSG_SET_CONFIG_REQ = 0x03; - public static final byte DEVMGMT_MSG_SET_CONFIG_RSP = 0x04; - public static final byte DEVMGMT_MSG_GET_CONFIG_REQ = 0x05; - public static final byte DEVMGMT_MSG_GET_CONFIG_RSP = 0x06; - public static final byte DEVMGMT_MSG_RESET_REQ = 0x07; - public static final byte DEVMGMT_MSG_RESET_RSP = 0x08; - public static final byte DEVMGMT_MSG_FACTORY_RESET_REQ = 0x09; - public static final byte DEVMGMT_MSG_FACTORY_RESET_RSP = 0x0A; - public static final byte DEVMGMT_MSG_GET_OPMODE_REQ = 0x0B; - public static final byte DEVMGMT_MSG_GET_OPMODE_RSP = 0x0C; - public static final byte DEVMGMT_MSG_SET_OPMODE_REQ = 0x0D; - public static final byte DEVMGMT_MSG_SET_OPMODE_RSP = 0x0E; - public static final byte DEVMGMT_MSG_GET_DEVICEINFO_REQ = 0x0F; - public static final byte DEVMGMT_MSG_GET_DEVICEINFO_RSP = 0x10; - public static final byte DEVMGMT_MSG_GET_SYSSTATUS_REQ = 0x11; - public static final byte DEVMGMT_MSG_GET_SYSSTATUS_RSP = 0x12; - public static final byte DEVMGMT_MSG_GET_FWINFO_REQ = 0x13; - public static final byte DEVMGMT_MSG_GET_FWINFO_RSP = 0x14; - public static final byte DEVMGMT_MSG_GET_RTC_REQ = 0x19; - public static final byte DEVMGMT_MSG_GET_RTC_RSP = 0x1A; - public static final byte DEVMGMT_MSG_SET_RTC_REQ = 0x1B; - public static final byte DEVMGMT_MSG_SET_RTC_RSP = 0x1C; - public static final byte DEVMGMT_MSG_ENTER_LPM_REQ = 0x1D; - public static final byte DEVMGMT_MSG_ENTER_LPM_RSP = 0x1E; - public static final byte DEVMGMT_MSG_SET_AES_ENCKEY_REQ = 0x21; - public static final byte DEVMGMT_MSG_SET_AES_ENCKEY_RSP = 0x22; - public static final byte DEVMGMT_MSG_ENABLE_AES_ENCKEY_REQ = 0x23; - public static final byte DEVMGMT_MSG_ENABLE_AES_ENCKEY_RSP = 0x24; - public static final byte DEVMGMT_MSG_SET_AES_DECKEY_RSP_0X25 = 0x25; - public static final byte DEVMGMT_MSG_SET_AES_DECKEY_RSP_0X26 = 0x26; - public static final byte DEVMGMT_MSG_AES_DEC_ERROR_IND = 0x27; - - // Radio Link Message Identifier - public static final byte RADIOLINK_MSG_WMBUSMSG_REQ = 0x01; - public static final byte RADIOLINK_MSG_WMBUSMSG_RSP = 0x02; - public static final byte RADIOLINK_MSG_WMBUSMSG_IND = 0x03; - public static final byte RADIOLINK_MSG_DATA_REQ = 0x04; - public static final byte RADIOLINK_MSG_DATA_RSP = 0x05; - } - - /** - *
  • HCI Message - *
      - *
    • StartOfFrame 8 Bit: 0xA5 - *
    • MsgHeader 24 Bit: - *
        - *
      • ControlField 4 Bit: - *
          - *
        • 0000b Reserved - *
        • 0010b Time Stamp Field attached - *
        • 0100b RSSI Field attached - *
        • 1000b CRC16 Field attached - *
        - *
      • EndPoint ID 4 Bit: Identifies a logical message endpoint which groups several messages. - *
      • Msg ID Field 8 Bit: Identifies the message type. - *
      • LengthFiled 8 Bit: Number of bytes in the payload. If null no payload. - *
      - *
    • PayloadField n * 8 Bit: wMBus Message - *
    • Time Stamp (optional): 32 Bit Timestamp of the RTC - *
    • RSSI (optional) 8 Bit: Receive Signal Strength Indicator - *
    • FCS (optional) 16 Bit: CRC from Control Field up to last byte of Payload, Time Stamp or RSSI Field.
    • - *
    - * - */ - private static class HciMessage { - - private final byte controlField;; - private final byte endpointID; - private final byte msgId; - private final int length; - - private final byte[] payload; - private final int timeStamp; - private final int rSSI; - private final int fCS; - - private HciMessage(byte controlField, byte endpointID, byte msgId, int length, byte[] payload, int timeStamp, - int rSSI, int fCS) { - this.controlField = controlField; - this.endpointID = endpointID; - this.msgId = msgId; - this.length = length; - this.payload = payload; - this.timeStamp = timeStamp; - this.rSSI = rSSI; - this.fCS = fCS; - } - - public static HciMessage decode(TransportLayer transportLayer) throws IOException { - DataInputStream is = transportLayer.getInputStream(); - byte b0, b1; - - transportLayer.setTimeout(0); - b0 = is.readByte(); - if (b0 != Const.START_OF_FRAME) { - String msg = String.format("First byte does not start with %02X.", Const.START_OF_FRAME); - throw new IOException(msg); - } - - transportLayer.setTimeout(MESSAGE_FRAGEMENT_TIMEOUT); - // this may time out. - b1 = is.readByte(); - - byte controlField = (byte) ((b1 >> 4) & 0x0F); - byte endpointId = (byte) (b1 & 0x0F); - - byte msgId = is.readByte(); - int length = is.readUnsignedByte(); - - byte[] payload = readPayload(is, length); - - int timeStamp = 0; - if ((controlField & Const.TIMESTAMP_ATTACHED) == Const.TIMESTAMP_ATTACHED) { - timeStamp = is.readInt(); - } - - int rSSI = 0; - if ((controlField & Const.RSSI_ATTACHED) == Const.RSSI_ATTACHED) { - double b = -100.0 - (4000.0 / 150.0); - double m = 80.0 / 150.0; - rSSI = (int) (m * is.readUnsignedByte() + b); - } - - int fCS = 0; - if ((controlField & Const.CRC16_ATTACHED) == Const.CRC16_ATTACHED) { - fCS = is.readUnsignedShort(); - } - - return new HciMessage(controlField, endpointId, msgId, length, payload, timeStamp, rSSI, fCS); - } - - private static byte[] readPayload(DataInputStream is, final int length) throws IOException { - byte[] payload = new byte[length + 1]; - payload[0] = (byte) length; - - int readLength = is.read(payload, 1, length); - - if (readLength != length) { - byte[] data = Arrays.copyOfRange(payload, 1, 1 + readLength); - throw new HciMessageException(data); - } - - return payload; - } - - @Override - public String toString() { - return new StringBuilder().append("Control Field: ").append(byteAsHexString(controlField)) - .append("\nEndpointID: ").append(byteAsHexString(endpointID)).append("\nMsg ID: ") - .append(byteAsHexString(msgId)).append("\nLength: ").append(length) - .append("\nTimestamp: ").append(timeStamp).append("\nRSSI: ").append(rSSI) - .append("\nFCS: ").append(fCS).append("\nPayload:\n") - .append(DatatypeConverter.printHexBinary(payload)).toString(); - } - - private static String byteAsHexString(byte b) { - return String.format("%02X", b); - } - - public byte[] getPayload() { - return payload; - } - - public int getRSSI() { - return rSSI; - } - } -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.wireless; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.nio.ByteBuffer; +import java.text.MessageFormat; +import java.util.Arrays; + +import javax.xml.bind.DatatypeConverter; + +import org.openmuc.jmbus.DecodingException; +import org.openmuc.jmbus.transportlayer.TransportLayer; + +/** + * Was tested with the IMST iM871A-USB Wireless M-Bus stick.
    + */ +class WMBusConnectionImst extends AbstractWMBusConnection { + + private class MessageReceiverImpl extends MessageReceiver { + + private static final byte MBUS_BL_CONTROL = 0x44; + private final TransportLayer transportLayer; + + public MessageReceiverImpl(TransportLayer transportLayer, WMBusListener listener) { + super(listener); + this.transportLayer = transportLayer; + } + + @Override + public void run() { + try { + + while (!isClosed()) { + try { + task(); + } catch (InterruptedIOException e) { + // ignore a timeout.. + } catch (HciMessageException e) { + super.notifyDiscarded(e.getData()); + } + } + + } catch (IOException e) { + if (isClosed()) { + return; + } + + super.notifyStoppedListening(e); + } finally { + close(); + super.shutdown(); + } + } + + private void task() throws IOException { + HciMessage hciMessage = readHciMsg(); + + final byte[] wmbusMessage = hciMessage.getPayload(); + final int signalStrengthInDBm = hciMessage.getRSSI(); + try { + super.notifyNewMessage(WMBusMessage.decode(wmbusMessage, signalStrengthInDBm, keyMap)); + } catch (DecodingException e) { + super.notifyDiscarded(wmbusMessage); + } + } + + private HciMessage readHciMsg() throws IOException { + while (true) { + HciMessage hciMessage = HciMessage.decode(this.transportLayer); + + if (hciMessage.getPayload().length <= 1) { + continue; + } + + if (hciMessage.getPayload()[1] == MBUS_BL_CONTROL) { + return hciMessage; + } else { + discard(hciMessage); + } + } + } + + private void discard(HciMessage hciMessage) { + super.notifyDiscarded(hciMessage.payload); + } + } + + public WMBusConnectionImst(WMBusMode mode, WMBusListener listener, TransportLayer tl) { + super(mode, listener, tl); + } + + @Override + protected MessageReceiver newMessageReceiver(TransportLayer transportLayer, WMBusListener listener) { + return new MessageReceiverImpl(transportLayer, listener); + } + + @Override + protected void initializeWirelessTransceiver(WMBusMode mode) throws IOException { + + byte[] payload = ByteBuffer.allocate(6).put((byte) 0x00) // NVM Flag: change configuration only temporary + .put((byte) 0x03) // IIFlag 1: Bit 0 Device Mode and Bit 1 Radio Mode + .put((byte) 0x00) // Device Mode: Meter + .put(linkRadioModeFor(mode)) // Link/Radio Mode + .put((byte) 0x10) // IIFlag 2: Bit 4 : Auto RSSI Attachment + .put((byte) 0x01) // Rx-Timestamp attached for each received Radio message + .array(); + + writeCommand(Const.DEVMGMT_ID, Const.DEVMGMT_MSG_SET_CONFIG_REQ, payload); + } + + private static byte linkRadioModeFor(WMBusMode mode) throws IOException { + switch (mode) { + case S: + return 0x01; // Link/Radio Mode: S1-m + case T: + return 0x04; // Link/Radio Mode: T2 + case C: + return 0x08; // Link/Radio Mode: C2 with telegram format A (C2 + format B = 0x09) + default: + String msg = MessageFormat.format("wMBUS Mode ''{0}'' is not supported", mode.toString()); + throw new IOException(msg); + } + } + + private boolean writeCommand(byte endpointId, byte msgId, byte[] payload) { + byte controlField = 0; + int lengthPayloadAll = payload.length & 0xFF; + double numOfPackagesTemp = (double) lengthPayloadAll / Const.MAX_SINGLE_PAYLOAD_SIZE; + + int numOfPackages = (int) numOfPackagesTemp; + int comma = (int) (numOfPackagesTemp - numOfPackages) * 100; + if (numOfPackages == 0 || comma != 0) { + ++numOfPackages; + } + + if (numOfPackages > Const.MAX_PACKAGES) { + return false; + } + + for (int i = 0; i < numOfPackages; ++i) { + int payloadSendLength = Const.MAX_SINGLE_PAYLOAD_SIZE; + if (numOfPackages - i == 1) { + payloadSendLength = lengthPayloadAll - (numOfPackages - 1) * Const.MAX_SINGLE_PAYLOAD_SIZE; + } + byte[] payloadSend = new byte[payloadSendLength]; + System.arraycopy(payload, i * payloadSendLength, payloadSend, 0, payloadSendLength); + + byte controlField_EndpointField = (byte) ((controlField << 4) | endpointId & 0xff); + byte[] hciHeader = { Const.START_OF_FRAME, controlField_EndpointField, msgId, (byte) payloadSendLength }; + + byte[] hciMessage = new byte[Const.HCI_HEADER_LENGTH + payloadSendLength]; + System.arraycopy(hciHeader, 0, hciMessage, 0, hciHeader.length); + System.arraycopy(payloadSend, 0, hciMessage, hciHeader.length, payloadSend.length); + + try { + getOutputStream().write(hciMessage); + } catch (IOException e) { + return false; + } + } + return true; + } + + /** + * Writes a reset command to the IMST module + */ + public void reset() { + writeCommand(Const.DEVMGMT_MSG_FACTORY_RESET_REQ, Const.DEVMGMT_ID, new byte[0]); + } + + /** + * IMST constants packages + */ + class Const { + public static final byte START_OF_FRAME = (byte) 0xA5; + // A5 01 03 + public static final int MAX_PACKAGES = 255; + public static final int MAX_SINGLE_PAYLOAD_SIZE = 255; + public static final int HCI_HEADER_LENGTH = 4; + + // ControlField + public static final byte RESERVED = 0x00; // 0b0000 + public static final byte TIMESTAMP_ATTACHED = 0x02; // 0b0010 + public static final byte RSSI_ATTACHED = 0x04; // 0b0100 + public static final byte CRC16_ATTACHED = 0x08; // 0b1000 (FCS) + + // List of Endpoint Identifier + public static final byte DEVMGMT_ID = 0x01; + public static final byte RADIOLINK_ID = 0x02; + public static final byte RADIOLINKTEST_ID = 0x03; + public static final byte HWTEST_ID = 0x04; + + // Device Management MessageIdentifier + public static final byte DEVMGMT_MSG_PING_REQ = 0x01; + public static final byte DEVMGMT_MSG_PING_RSP = 0x02; + public static final byte DEVMGMT_MSG_SET_CONFIG_REQ = 0x03; + public static final byte DEVMGMT_MSG_SET_CONFIG_RSP = 0x04; + public static final byte DEVMGMT_MSG_GET_CONFIG_REQ = 0x05; + public static final byte DEVMGMT_MSG_GET_CONFIG_RSP = 0x06; + public static final byte DEVMGMT_MSG_RESET_REQ = 0x07; + public static final byte DEVMGMT_MSG_RESET_RSP = 0x08; + public static final byte DEVMGMT_MSG_FACTORY_RESET_REQ = 0x09; + public static final byte DEVMGMT_MSG_FACTORY_RESET_RSP = 0x0A; + public static final byte DEVMGMT_MSG_GET_OPMODE_REQ = 0x0B; + public static final byte DEVMGMT_MSG_GET_OPMODE_RSP = 0x0C; + public static final byte DEVMGMT_MSG_SET_OPMODE_REQ = 0x0D; + public static final byte DEVMGMT_MSG_SET_OPMODE_RSP = 0x0E; + public static final byte DEVMGMT_MSG_GET_DEVICEINFO_REQ = 0x0F; + public static final byte DEVMGMT_MSG_GET_DEVICEINFO_RSP = 0x10; + public static final byte DEVMGMT_MSG_GET_SYSSTATUS_REQ = 0x11; + public static final byte DEVMGMT_MSG_GET_SYSSTATUS_RSP = 0x12; + public static final byte DEVMGMT_MSG_GET_FWINFO_REQ = 0x13; + public static final byte DEVMGMT_MSG_GET_FWINFO_RSP = 0x14; + public static final byte DEVMGMT_MSG_GET_RTC_REQ = 0x19; + public static final byte DEVMGMT_MSG_GET_RTC_RSP = 0x1A; + public static final byte DEVMGMT_MSG_SET_RTC_REQ = 0x1B; + public static final byte DEVMGMT_MSG_SET_RTC_RSP = 0x1C; + public static final byte DEVMGMT_MSG_ENTER_LPM_REQ = 0x1D; + public static final byte DEVMGMT_MSG_ENTER_LPM_RSP = 0x1E; + public static final byte DEVMGMT_MSG_SET_AES_ENCKEY_REQ = 0x21; + public static final byte DEVMGMT_MSG_SET_AES_ENCKEY_RSP = 0x22; + public static final byte DEVMGMT_MSG_ENABLE_AES_ENCKEY_REQ = 0x23; + public static final byte DEVMGMT_MSG_ENABLE_AES_ENCKEY_RSP = 0x24; + public static final byte DEVMGMT_MSG_SET_AES_DECKEY_RSP_0X25 = 0x25; + public static final byte DEVMGMT_MSG_SET_AES_DECKEY_RSP_0X26 = 0x26; + public static final byte DEVMGMT_MSG_AES_DEC_ERROR_IND = 0x27; + + // Radio Link Message Identifier + public static final byte RADIOLINK_MSG_WMBUSMSG_REQ = 0x01; + public static final byte RADIOLINK_MSG_WMBUSMSG_RSP = 0x02; + public static final byte RADIOLINK_MSG_WMBUSMSG_IND = 0x03; + public static final byte RADIOLINK_MSG_DATA_REQ = 0x04; + public static final byte RADIOLINK_MSG_DATA_RSP = 0x05; + } + + /** + *
  • HCI Message + *
      + *
    • StartOfFrame 8 Bit: 0xA5 + *
    • MsgHeader 24 Bit: + *
        + *
      • ControlField 4 Bit: + *
          + *
        • 0000b Reserved + *
        • 0010b Time Stamp Field attached + *
        • 0100b RSSI Field attached + *
        • 1000b CRC16 Field attached + *
        + *
      • EndPoint ID 4 Bit: Identifies a logical message endpoint which groups several messages. + *
      • Msg ID Field 8 Bit: Identifies the message type. + *
      • LengthFiled 8 Bit: Number of bytes in the payload. If null no payload. + *
      + *
    • PayloadField n * 8 Bit: wMBus Message + *
    • Time Stamp (optional): 32 Bit Timestamp of the RTC + *
    • RSSI (optional) 8 Bit: Receive Signal Strength Indicator + *
    • FCS (optional) 16 Bit: CRC from Control Field up to last byte of Payload, Time Stamp or RSSI Field.
    • + *
    + * + */ + private static class HciMessage { + + private final byte controlField;; + private final byte endpointID; + private final byte msgId; + private final int length; + + private final byte[] payload; + private final int timeStamp; + private final int rSSI; + private final int fCS; + + private HciMessage(byte controlField, byte endpointID, byte msgId, int length, byte[] payload, int timeStamp, + int rSSI, int fCS) { + this.controlField = controlField; + this.endpointID = endpointID; + this.msgId = msgId; + this.length = length; + this.payload = payload; + this.timeStamp = timeStamp; + this.rSSI = rSSI; + this.fCS = fCS; + } + + public static HciMessage decode(TransportLayer transportLayer) throws IOException { + DataInputStream is = transportLayer.getInputStream(); + byte b0, b1; + + transportLayer.setTimeout(0); + b0 = is.readByte(); + if (b0 != Const.START_OF_FRAME) { + String msg = String.format("First byte does not start with %02X.", Const.START_OF_FRAME); + throw new IOException(msg); + } + + transportLayer.setTimeout(MESSAGE_FRAGEMENT_TIMEOUT); + // this may time out. + b1 = is.readByte(); + + byte controlField = (byte) ((b1 >> 4) & 0x0F); + byte endpointId = (byte) (b1 & 0x0F); + + byte msgId = is.readByte(); + int length = is.readUnsignedByte(); + + byte[] payload = readPayload(is, length); + + int timeStamp = 0; + if ((controlField & Const.TIMESTAMP_ATTACHED) == Const.TIMESTAMP_ATTACHED) { + timeStamp = is.readInt(); + } + + int rSSI = 0; + if ((controlField & Const.RSSI_ATTACHED) == Const.RSSI_ATTACHED) { + double b = -100.0 - (4000.0 / 150.0); + double m = 80.0 / 150.0; + rSSI = (int) (m * is.readUnsignedByte() + b); + } + + int fCS = 0; + if ((controlField & Const.CRC16_ATTACHED) == Const.CRC16_ATTACHED) { + fCS = is.readUnsignedShort(); + } + + return new HciMessage(controlField, endpointId, msgId, length, payload, timeStamp, rSSI, fCS); + } + + private static byte[] readPayload(DataInputStream is, final int length) throws IOException { + byte[] payload = new byte[length + 1]; + payload[0] = (byte) length; + + int readLength = is.read(payload, 1, length); + + if (readLength != length) { + byte[] data = Arrays.copyOfRange(payload, 1, 1 + readLength); + throw new HciMessageException(data); + } + + return payload; + } + + @Override + public String toString() { + return new StringBuilder().append("Control Field: ").append(byteAsHexString(controlField)) + .append("\nEndpointID: ").append(byteAsHexString(endpointID)).append("\nMsg ID: ") + .append(byteAsHexString(msgId)).append("\nLength: ").append(length) + .append("\nTimestamp: ").append(timeStamp).append("\nRSSI: ").append(rSSI) + .append("\nFCS: ").append(fCS).append("\nPayload:\n") + .append(DatatypeConverter.printHexBinary(payload)).toString(); + } + + private static String byteAsHexString(byte b) { + return String.format("%02X", b); + } + + public byte[] getPayload() { + return payload; + } + + public int getRSSI() { + return rSSI; + } + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionRadioCrafts.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionRadioCrafts.java index 6237bbf..5cdb7e5 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionRadioCrafts.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionRadioCrafts.java @@ -1,229 +1,229 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus.wireless; - -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.InterruptedIOException; -import java.text.MessageFormat; -import java.util.Arrays; - -import org.openmuc.jmbus.DecodingException; -import org.openmuc.jmbus.transportlayer.TransportLayer; - -/* - * Radio Craft W-MBUS frame: - * - * @formatter:off - * +---+----+-----------+ - * | L | CI | APPL_DATA | - * +---+----+-----------+ - * ~ L is the length (not including the length byte itself) - * ~ CI is the Control Information byte - * @formatter:on - */ -class WMBusConnectionRadioCrafts extends AbstractWMBusConnection { - - private class MessageReceiverImpl extends MessageReceiver { - - /** - * Indicates message from primary station, function send/no reply (SND -N - */ - private static final byte CONTROL_BYTE = 0x44; - private final TransportLayer transportLayer; - - private final byte[] discardBuffer = new byte[BUFFER_LENGTH]; - private int bufferPointer = 0; - - public MessageReceiverImpl(TransportLayer transportLayer, WMBusListener listener) { - super(listener); - this.transportLayer = transportLayer; - } - - @Override - public void run() { - try { - - while (!isClosed()) { - - byte[] messageData = initMessageData(); - - int len = messageData.length - 2; - handleData(messageData, len); - - } - } catch (final IOException e) { - if (!isClosed()) { - super.notifyStoppedListening(e); - } - - } finally { - close(); - super.shutdown(); - } - } - - private void handleData(byte[] messageData, int len) throws IOException { - try { - int numReadBytes = getInputStream().read(messageData, 2, len); - - if (len == numReadBytes) { - notifyListener(messageData); - } else { - discard(messageData, 0, numReadBytes + 2); - } - } catch (InterruptedIOException e) { - discard(messageData, 0, 2); - } - } - - private byte[] initMessageData() throws IOException { - byte b0, b1; - while (true) { - this.transportLayer.setTimeout(0); - b0 = getInputStream().readByte(); - this.transportLayer.setTimeout(MESSAGE_FRAGEMENT_TIMEOUT); - - try { - // this may time out. - b1 = getInputStream().readByte(); - } catch (InterruptedIOException e) { - continue; - } - - if (b1 == CONTROL_BYTE) { - break; - } - - discardBuffer[bufferPointer++] = b0; - discardBuffer[bufferPointer++] = b1; - - if (bufferPointer - 2 >= discardBuffer.length) { - discard(discardBuffer, 0, bufferPointer); - bufferPointer = 0; - } - - } - - int messageLength = b0 & 0xff; - - final byte[] messageData = new byte[messageLength + 1]; - messageData[0] = b0; - messageData[1] = b1; - - return messageData; - } - - private void notifyListener(final byte[] messageBytes) { - messageBytes[0] = (byte) (messageBytes[0] - 1); - int rssi = messageBytes[messageBytes.length - 1] & 0xff; - - final int signalStrengthInDBm = (rssi * -1) / 2; - try { - super.notifyNewMessage(WMBusMessage.decode(messageBytes, signalStrengthInDBm, keyMap)); - } catch (DecodingException e) { - super.notifyDiscarded(messageBytes); - } - } - - private void discard(byte[] buffer, int offset, int length) { - final byte[] discardedBytes = Arrays.copyOfRange(buffer, offset, offset + length); - - super.notifyDiscarded(discardedBytes); - } - } - - public WMBusConnectionRadioCrafts(WMBusMode mode, WMBusListener listener, TransportLayer tl) { - super(mode, listener, tl); - } - - @Override - protected MessageReceiver newMessageReceiver(TransportLayer transportLayer, WMBusListener listener) { - return new MessageReceiverImpl(transportLayer, listener); - } - - /** - * @param mode - * - the wMBus mode to be used for transmission - * @throws IOException - */ - @Override - protected void initializeWirelessTransceiver(WMBusMode mode) throws IOException { - // enter config mode - sendByteInConfigMode(0x00); - - DataOutputStream os = getOutputStream(); - int modeFlag = getModeFlag(mode); - - init(os, modeFlag); - - // /* Set Auto Answer Register */ - // sendByteInConfigMode(0x41); - // sendByteInConfigMode(0xff); - - // leave config mode - os.write(0x58); - os.flush(); - } - - private int getModeFlag(WMBusMode mode) throws IOException { - int modeFlag; - - switch (mode) { - case C: - modeFlag = 0x04; - break; - case S: - modeFlag = 0x00; - break; - case T: - modeFlag = 0x02; - break; - default: - String msg = MessageFormat.format("wMBUS Mode ''{0}'' is not supported", mode.toString()); - throw new IOException(msg); - } - return modeFlag; - } - - private void init(DataOutputStream os, int modeFlag) throws IOException { - requestSetMode(os, modeFlag); - - requestSetMasterMode(os); - - requestRssiInformation(os); - } - - private void requestSetMode(DataOutputStream os, int modeFlag) throws IOException { - sendRequest(os, 0x03, modeFlag); - } - - private void requestSetMasterMode(DataOutputStream os) throws IOException { - sendRequest(os, 0x12, 0x01); - } - - private void requestRssiInformation(DataOutputStream os) throws IOException { - sendRequest(os, 0x05, 0x01); - } - - private void sendRequest(DataOutputStream os, int b0, int b1) throws IOException { - sendByteInConfigMode(0x4d); - os.write(b0); - os.write(b1); - sendByteInConfigMode(0xff); - } - - private void sendByteInConfigMode(int b) throws IOException { - - discardNoise(); - - DataOutputStream os = getOutputStream(); - os.write(b); - os.flush(); - - waitForAck(); - } -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.wireless; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.text.MessageFormat; +import java.util.Arrays; + +import org.openmuc.jmbus.DecodingException; +import org.openmuc.jmbus.transportlayer.TransportLayer; + +/* + * Radio Craft W-MBUS frame: + * + * @formatter:off + * +---+----+-----------+ + * | L | CI | APPL_DATA | + * +---+----+-----------+ + * ~ L is the length (not including the length byte itself) + * ~ CI is the Control Information byte + * @formatter:on + */ +class WMBusConnectionRadioCrafts extends AbstractWMBusConnection { + + private class MessageReceiverImpl extends MessageReceiver { + + /** + * Indicates message from primary station, function send/no reply (SND -N + */ + private static final byte CONTROL_BYTE = 0x44; + private final TransportLayer transportLayer; + + private final byte[] discardBuffer = new byte[BUFFER_LENGTH]; + private int bufferPointer = 0; + + public MessageReceiverImpl(TransportLayer transportLayer, WMBusListener listener) { + super(listener); + this.transportLayer = transportLayer; + } + + @Override + public void run() { + try { + + while (!isClosed()) { + + byte[] messageData = initMessageData(); + + int len = messageData.length - 2; + handleData(messageData, len); + + } + } catch (final IOException e) { + if (!isClosed()) { + super.notifyStoppedListening(e); + } + + } finally { + close(); + super.shutdown(); + } + } + + private void handleData(byte[] messageData, int len) throws IOException { + try { + int numReadBytes = getInputStream().read(messageData, 2, len); + + if (len == numReadBytes) { + notifyListener(messageData); + } else { + discard(messageData, 0, numReadBytes + 2); + } + } catch (InterruptedIOException e) { + discard(messageData, 0, 2); + } + } + + private byte[] initMessageData() throws IOException { + byte b0, b1; + while (true) { + this.transportLayer.setTimeout(0); + b0 = getInputStream().readByte(); + this.transportLayer.setTimeout(MESSAGE_FRAGEMENT_TIMEOUT); + + try { + // this may time out. + b1 = getInputStream().readByte(); + } catch (InterruptedIOException e) { + continue; + } + + if (b1 == CONTROL_BYTE) { + break; + } + + discardBuffer[bufferPointer++] = b0; + discardBuffer[bufferPointer++] = b1; + + if (bufferPointer - 2 >= discardBuffer.length) { + discard(discardBuffer, 0, bufferPointer); + bufferPointer = 0; + } + + } + + int messageLength = b0 & 0xff; + + final byte[] messageData = new byte[messageLength + 1]; + messageData[0] = b0; + messageData[1] = b1; + + return messageData; + } + + private void notifyListener(final byte[] messageBytes) { + messageBytes[0] = (byte) (messageBytes[0] - 1); + int rssi = messageBytes[messageBytes.length - 1] & 0xff; + + final int signalStrengthInDBm = (rssi * -1) / 2; + try { + super.notifyNewMessage(WMBusMessage.decode(messageBytes, signalStrengthInDBm, keyMap)); + } catch (DecodingException e) { + super.notifyDiscarded(messageBytes); + } + } + + private void discard(byte[] buffer, int offset, int length) { + final byte[] discardedBytes = Arrays.copyOfRange(buffer, offset, offset + length); + + super.notifyDiscarded(discardedBytes); + } + } + + public WMBusConnectionRadioCrafts(WMBusMode mode, WMBusListener listener, TransportLayer tl) { + super(mode, listener, tl); + } + + @Override + protected MessageReceiver newMessageReceiver(TransportLayer transportLayer, WMBusListener listener) { + return new MessageReceiverImpl(transportLayer, listener); + } + + /** + * @param mode + * - the wMBus mode to be used for transmission + * @throws IOException + */ + @Override + protected void initializeWirelessTransceiver(WMBusMode mode) throws IOException { + // enter config mode + sendByteInConfigMode(0x00); + + DataOutputStream os = getOutputStream(); + int modeFlag = getModeFlag(mode); + + init(os, modeFlag); + + // /* Set Auto Answer Register */ + // sendByteInConfigMode(0x41); + // sendByteInConfigMode(0xff); + + // leave config mode + os.write(0x58); + os.flush(); + } + + private int getModeFlag(WMBusMode mode) throws IOException { + int modeFlag; + + switch (mode) { + case C: + modeFlag = 0x04; + break; + case S: + modeFlag = 0x00; + break; + case T: + modeFlag = 0x02; + break; + default: + String msg = MessageFormat.format("wMBUS Mode ''{0}'' is not supported", mode.toString()); + throw new IOException(msg); + } + return modeFlag; + } + + private void init(DataOutputStream os, int modeFlag) throws IOException { + requestSetMode(os, modeFlag); + + requestSetMasterMode(os); + + requestRssiInformation(os); + } + + private void requestSetMode(DataOutputStream os, int modeFlag) throws IOException { + sendRequest(os, 0x03, modeFlag); + } + + private void requestSetMasterMode(DataOutputStream os) throws IOException { + sendRequest(os, 0x12, 0x01); + } + + private void requestRssiInformation(DataOutputStream os) throws IOException { + sendRequest(os, 0x05, 0x01); + } + + private void sendRequest(DataOutputStream os, int b0, int b1) throws IOException { + sendByteInConfigMode(0x4d); + os.write(b0); + os.write(b1); + sendByteInConfigMode(0xff); + } + + private void sendByteInConfigMode(int b) throws IOException { + + discardNoise(); + + DataOutputStream os = getOutputStream(); + os.write(b); + os.flush(); + + waitForAck(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusListener.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusListener.java index 1a8d1b1..b274f26 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusListener.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusListener.java @@ -1,39 +1,39 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus.wireless; - -import java.io.IOException; -import java.util.EventListener; - -/** - * The wireless M-Bus event/new message listener interface. - */ -public interface WMBusListener extends EventListener { - - /** - * Received a new wireless M-Bus message. - * - * @param message - * the message. - */ - void newMessage(WMBusMessage message); - - /** - * Callback, when noisy data has been discarded. - * - * @param bytes - * the data which has been discarded. - */ - void discardedBytes(byte[] bytes); - - /** - * Callback, if the connection has been interrupted. - * - * @param cause - * the cause of the interruption. - */ - void stoppedListening(IOException cause); -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.wireless; + +import java.io.IOException; +import java.util.EventListener; + +/** + * The wireless M-Bus event/new message listener interface. + */ +public interface WMBusListener extends EventListener { + + /** + * Received a new wireless M-Bus message. + * + * @param message + * the message. + */ + void newMessage(WMBusMessage message); + + /** + * Callback, when noisy data has been discarded. + * + * @param bytes + * the data which has been discarded. + */ + void discardedBytes(byte[] bytes); + + /** + * Callback, if the connection has been interrupted. + * + * @param cause + * the cause of the interruption. + */ + void stoppedListening(IOException cause); +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusMessage.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusMessage.java index 41e7e0e..8b99cb1 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusMessage.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusMessage.java @@ -1,129 +1,129 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus.wireless; - -import java.text.MessageFormat; -import java.util.Map; - -import org.openmuc.jmbus.DecodingException; -import org.openmuc.jmbus.SecondaryAddress; -import org.openmuc.jmbus.VariableDataStructure; - -/** - * Represents a wireless M-Bus link layer message without the CRC checksum. - * - * {@link WMBusMessage} is structured as follows: - *
      - *
    • Length (1 byte) - the length (number of bytes) of the complete message without the length byte and the CRC bytes. - *
    • - *
    • Control field (1 byte) - defines the frame type. 0x44 signifies an SND-NR (send no request) message that is sent - * by meters in S1 mode.
    • - *
    • Secondary address (8 bytes) - the secondary address consists of: - *
        - *
      • Manufacturer ID (2 bytes) -
      • - *
      • Address (6 bytes) - consists of - *
          - *
        • Device ID (4 bytes) -
        • - *
        • Version (1 byte) -
        • - *
        • Device type (1 byte) -
        • - *
        - *
      • - *
      - *
    • - *
    - */ -public class WMBusMessage { - - private final Integer signalStrengthInDBm; - - private final byte[] buffer; - private final int controlField; - private final SecondaryAddress secondaryAddress; - private final VariableDataStructure vdr; - - private WMBusMessage(Integer signalStrengthInDBm, byte[] buffer, int controlField, - SecondaryAddress secondaryAddress, VariableDataStructure vdr) { - this.signalStrengthInDBm = signalStrengthInDBm; - this.buffer = buffer; - this.controlField = controlField; - this.secondaryAddress = secondaryAddress; - this.vdr = vdr; - } - - /* - * Only decodes the wireless M-Bus message itself. - */ - static WMBusMessage decode(byte[] buffer, Integer signalStrengthInDBm, Map keyMap) - throws DecodingException { - int length = buffer[0] & 0xff; - - if (length > (buffer.length - 1)) { - String msg = MessageFormat.format( - "Byte buffer has only a length of {0} while the specified length field is {1}.", buffer.length, - length); - throw new DecodingException(msg); - } - - int controlField = buffer[1] & 0xff; - SecondaryAddress secondaryAddress = SecondaryAddress.newFromWMBusLlHeader(buffer, 2); - VariableDataStructure vdr = new VariableDataStructure(buffer, 10, length - 9, secondaryAddress, keyMap); - - return new WMBusMessage(signalStrengthInDBm, buffer, controlField, secondaryAddress, vdr); - } - - /** - * Get the message as binary large object (byte array). - * - * @return the byte array representation of the message. - */ - public byte[] asBlob() { - return buffer; - } - - public int getControlField() { - return controlField; - } - - /** - * Get the secondary address. - * - * @return the secondary address. - */ - public SecondaryAddress getSecondaryAddress() { - return secondaryAddress; - } - - /** - * Get the variable data structure of the message. - * - * @return the variable data structure. - */ - public VariableDataStructure getVariableDataResponse() { - return vdr; - } - - /** - * Returns the received signal string indication (RSSI) in dBm. - * - * @return the RSSI. - */ - public Integer getRssi() { - return signalStrengthInDBm; - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - - if (signalStrengthInDBm != null) { - builder.append("Message was received with signal strength: ").append(signalStrengthInDBm).append("dBm\n"); - } - - return builder.append("control field: ").append(String.format("0x%02X", controlField)) - .append("\nSecondary Address -> ").append(secondaryAddress).append("\nVariable Data Response:\n") - .append(vdr).toString(); - } -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.wireless; + +import java.text.MessageFormat; +import java.util.Map; + +import org.openmuc.jmbus.DecodingException; +import org.openmuc.jmbus.SecondaryAddress; +import org.openmuc.jmbus.VariableDataStructure; + +/** + * Represents a wireless M-Bus link layer message without the CRC checksum. + * + * {@link WMBusMessage} is structured as follows: + *
      + *
    • Length (1 byte) - the length (number of bytes) of the complete message without the length byte and the CRC bytes. + *
    • + *
    • Control field (1 byte) - defines the frame type. 0x44 signifies an SND-NR (send no request) message that is sent + * by meters in S1 mode.
    • + *
    • Secondary address (8 bytes) - the secondary address consists of: + *
        + *
      • Manufacturer ID (2 bytes) -
      • + *
      • Address (6 bytes) - consists of + *
          + *
        • Device ID (4 bytes) -
        • + *
        • Version (1 byte) -
        • + *
        • Device type (1 byte) -
        • + *
        + *
      • + *
      + *
    • + *
    + */ +public class WMBusMessage { + + private final Integer signalStrengthInDBm; + + private final byte[] buffer; + private final int controlField; + private final SecondaryAddress secondaryAddress; + private final VariableDataStructure vdr; + + private WMBusMessage(Integer signalStrengthInDBm, byte[] buffer, int controlField, + SecondaryAddress secondaryAddress, VariableDataStructure vdr) { + this.signalStrengthInDBm = signalStrengthInDBm; + this.buffer = buffer; + this.controlField = controlField; + this.secondaryAddress = secondaryAddress; + this.vdr = vdr; + } + + /* + * Only decodes the wireless M-Bus message itself. + */ + static WMBusMessage decode(byte[] buffer, Integer signalStrengthInDBm, Map keyMap) + throws DecodingException { + int length = buffer[0] & 0xff; + + if (length > (buffer.length - 1)) { + String msg = MessageFormat.format( + "Byte buffer has only a length of {0} while the specified length field is {1}.", buffer.length, + length); + throw new DecodingException(msg); + } + + int controlField = buffer[1] & 0xff; + SecondaryAddress secondaryAddress = SecondaryAddress.newFromWMBusLlHeader(buffer, 2); + VariableDataStructure vdr = new VariableDataStructure(buffer, 10, length - 9, secondaryAddress, keyMap); + + return new WMBusMessage(signalStrengthInDBm, buffer, controlField, secondaryAddress, vdr); + } + + /** + * Get the message as binary large object (byte array). + * + * @return the byte array representation of the message. + */ + public byte[] asBlob() { + return buffer; + } + + public int getControlField() { + return controlField; + } + + /** + * Get the secondary address. + * + * @return the secondary address. + */ + public SecondaryAddress getSecondaryAddress() { + return secondaryAddress; + } + + /** + * Get the variable data structure of the message. + * + * @return the variable data structure. + */ + public VariableDataStructure getVariableDataResponse() { + return vdr; + } + + /** + * Returns the received signal string indication (RSSI) in dBm. + * + * @return the RSSI. + */ + public Integer getRssi() { + return signalStrengthInDBm; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + + if (signalStrengthInDBm != null) { + builder.append("Message was received with signal strength: ").append(signalStrengthInDBm).append("dBm\n"); + } + + return builder.append("control field: ").append(String.format("0x%02X", controlField)) + .append("\nSecondary Address -> ").append(secondaryAddress).append("\nVariable Data Response:\n") + .append(vdr).toString(); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusMode.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusMode.java index fdc1597..943aab3 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusMode.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusMode.java @@ -1,24 +1,24 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -package org.openmuc.jmbus.wireless; - -/** - * The wireless M-Bus modes. - */ -public enum WMBusMode { - /** - * Compact (868.95 MHz). Combination of range (S) and battery efficiency (T). Suitable for frequent sending. - */ - C, - /** - * Frequent (868.95 MHz). Meter sends data several times/day. - */ - T, - /** - * Stationary (868.3 MHz). Meter sends data few times/day. - */ - S; -} +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.openmuc.jmbus.wireless; + +/** + * The wireless M-Bus modes. + */ +public enum WMBusMode { + /** + * Compact (868.95 MHz). Combination of range (S) and battery efficiency (T). Suitable for frequent sending. + */ + C, + /** + * Frequent (868.95 MHz). Meter sends data several times/day. + */ + T, + /** + * Stationary (868.3 MHz). Meter sends data few times/day. + */ + S; +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/package-info.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/package-info.java index b5bedb3..a613d45 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/package-info.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/package-info.java @@ -1,12 +1,12 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -/** - * This package contain relevant classes to receive wireless M-Bus messages. - * - * @see org.openmuc.jmbus.wireless.WMBusConnection.WMBusSerialBuilder - * @see org.openmuc.jmbus.wireless.WMBusConnection - */ -package org.openmuc.jmbus.wireless; +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +/** + * This package contain relevant classes to receive wireless M-Bus messages. + * + * @see org.openmuc.jmbus.wireless.WMBusConnection.WMBusSerialBuilder + * @see org.openmuc.jmbus.wireless.WMBusConnection + */ +package org.openmuc.jmbus.wireless; diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/DataBits.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/DataBits.java index 53550d3..87be3bc 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/DataBits.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/DataBits.java @@ -1,36 +1,36 @@ -package org.openmuc.jrxtx; - -import org.openhab.core.io.transport.serial.SerialPort; - -/** - * The data bits. - */ -@SuppressWarnings("deprecation") -public enum DataBits { - /** - * 5 data bits will be used for each character. - */ - DATABITS_5(SerialPort.DATABITS_5), - /** - * 6 data bits will be used for each character. - */ - DATABITS_6(SerialPort.DATABITS_6), - /** - * 8 data bits will be used for each character. - */ - DATABITS_7(SerialPort.DATABITS_7), - /** - * 8 data bits will be used for each character. - */ - DATABITS_8(SerialPort.DATABITS_8),; - - private int odlValue; - - private DataBits(int oldValue) { - this.odlValue = oldValue; - } - - int getOldValue() { - return this.odlValue; - } -} +package org.openmuc.jrxtx; + +import org.openhab.core.io.transport.serial.SerialPort; + +/** + * The data bits. + */ +@SuppressWarnings("deprecation") +public enum DataBits { + /** + * 5 data bits will be used for each character. + */ + DATABITS_5(SerialPort.DATABITS_5), + /** + * 6 data bits will be used for each character. + */ + DATABITS_6(SerialPort.DATABITS_6), + /** + * 8 data bits will be used for each character. + */ + DATABITS_7(SerialPort.DATABITS_7), + /** + * 8 data bits will be used for each character. + */ + DATABITS_8(SerialPort.DATABITS_8),; + + private int odlValue; + + private DataBits(int oldValue) { + this.odlValue = oldValue; + } + + int getOldValue() { + return this.odlValue; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/FlowControl.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/FlowControl.java index 6f303e9..8e00c8a 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/FlowControl.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/FlowControl.java @@ -1,29 +1,29 @@ -package org.openmuc.jrxtx; - -/** - * The flow control. - * - * @see SerialPort#setFlowControl(FlowControl) - * @see SerialPortBuilder#setFlowControl(FlowControl) - */ -public enum FlowControl { - /** - * No flow control. - */ - NONE, - - /** - * Hardware flow control on input and output (RTS/CTS). - * - *

    - * Sets RFR (ready for receiving) formally known as RTS and the CTS (clear to send) flag. - *

    - */ - RTS_CTS, - - /** - * Software flow control on input and output. - */ - XON_XOFF - -} +package org.openmuc.jrxtx; + +/** + * The flow control. + * + * @see SerialPort#setFlowControl(FlowControl) + * @see SerialPortBuilder#setFlowControl(FlowControl) + */ +public enum FlowControl { + /** + * No flow control. + */ + NONE, + + /** + * Hardware flow control on input and output (RTS/CTS). + * + *

    + * Sets RFR (ready for receiving) formally known as RTS and the CTS (clear to send) flag. + *

    + */ + RTS_CTS, + + /** + * Software flow control on input and output. + */ + XON_XOFF + +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/JRxTxPort.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/JRxTxPort.java index 4c3318f..396cf5a 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/JRxTxPort.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/JRxTxPort.java @@ -1,340 +1,340 @@ -package org.openmuc.jrxtx; - -import static java.text.MessageFormat.format; -import static org.openhab.core.io.transport.serial.SerialPort.*; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import org.openhab.core.io.transport.serial.PortInUseException; -import org.openhab.core.io.transport.serial.SerialPortIdentifier; -import org.openhab.core.io.transport.serial.SerialPortManager; -import org.openhab.core.io.transport.serial.UnsupportedCommOperationException; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.osgi.util.tracker.ServiceTracker; - -/** - * This class is a workaround: - * jrxtx library includes gnu.io.* classes and j62056.jar is depending on that. As we are - * using nrjavaserial as an implementation of gnu.io, we can't go that way! - * -> As a workaround I shaded the {@link JRxTxPort} class which is used by j62056.jar and modified it to - * work with any implementation of {@link SerialPort} (formerly it was just working with the implementation - * gnu.io.RXTXPort). - * - * @author MatthiasS - * - */ -class JRxTxPort implements org.openmuc.jrxtx.SerialPort { - - private volatile boolean closed; - - private org.openhab.core.io.transport.serial.SerialPort rxtxPort; - - private SerialInputStream serialIs; - private SerialOutputStream serial0s; - - private String portName; - - private DataBits dataBits; - - private Parity parity; - - private StopBits stopBits; - - private int baudRate; - - private int serialPortTimeout; - - private FlowControl flowControl; - - public static JRxTxPort openSerialPort(String portName, int baudRate, Parity parity, DataBits dataBits, - StopBits stopBits, FlowControl flowControl) throws IOException { - try { - BundleContext bundleContext = FrameworkUtil.getBundle(JRxTxPort.class).getBundleContext(); - ServiceTracker serviceTracker = new ServiceTracker<>(bundleContext, - SerialPortManager.class, null); - serviceTracker.open(); - SerialPortManager serialPortManager = serviceTracker.getService(); - - SerialPortIdentifier serialPortIdentifier = serialPortManager.getIdentifier(portName); - if (serialPortIdentifier == null) { - String errMessage = format("Serial port {0} not found or port is busy.", portName); - throw new PortNotFoundException(errMessage); - } - org.openhab.core.io.transport.serial.SerialPort comPort = serialPortIdentifier.open("meterreader", 0); - // if (!(comPort instanceof RXTXPort)) { - // throw new SerialPortException("Unable to open the serial port. Port is not RXTX."); - // } - - try { - comPort.setSerialPortParams(baudRate, dataBits.getOldValue(), stopBits.getOldValue(), - parity.getOldValue()); - - setFlowControl(flowControl, comPort); - } catch (UnsupportedCommOperationException e) { - String message = format("Unable to apply config on serial port.\n{0}", e.getMessage()); - throw new SerialPortException(message); - } - - return new JRxTxPort(comPort, portName, baudRate, parity, dataBits, stopBits, flowControl); - // } catch (NoSuchPortException e) { - // String errMessage = format("Serial port {0} not found or port is busy.", portName); - // throw new PortNotFoundException(errMessage); - } catch (PortInUseException e) { - String errMessage = format("Serial port {0} is already in use.", portName); - throw new PortNotFoundException(errMessage); - // } catch (UnsupportedCommOperationException e1) { - // throw new IOException(e1); - } - } - - private static void setFlowControl(FlowControl flowControl, - org.openhab.core.io.transport.serial.SerialPort rxtxPort) throws IOException { - try { - switch (flowControl) { - case RTS_CTS: - rxtxPort.setFlowControlMode(FLOWCONTROL_RTSCTS_IN | FLOWCONTROL_RTSCTS_OUT); - break; - case XON_XOFF: - rxtxPort.setFlowControlMode(FLOWCONTROL_XONXOFF_IN | FLOWCONTROL_XONXOFF_OUT); - - break; - - case NONE: - default: - rxtxPort.setFlowControlMode(FLOWCONTROL_NONE); - break; - } - } catch (UnsupportedCommOperationException e) { - throw new IOException("Failed to set FlowControl mode", e); - } - } - - private JRxTxPort(org.openhab.core.io.transport.serial.SerialPort comPort, String portName, int baudRate, - Parity parity, DataBits dataBits, StopBits stopBits, FlowControl flowControl) throws IOException { - this.rxtxPort = comPort; - this.portName = portName; - this.baudRate = baudRate; - this.parity = parity; - this.dataBits = dataBits; - this.stopBits = stopBits; - this.flowControl = flowControl; - - this.closed = false; - - this.serial0s = new SerialOutputStream(this.rxtxPort.getOutputStream()); - this.serialIs = new SerialInputStream(); - } - - @Override - public InputStream getInputStream() throws IOException { - if (isClosed()) { - throw new SerialPortException("Serial port is closed"); - } - return this.serialIs; - } - - @Override - public OutputStream getOutputStream() throws IOException { - if (isClosed()) { - throw new SerialPortException("Serial port is closed"); - } - - return this.serial0s; - } - - @Override - public synchronized void close() throws IOException { - if (isClosed()) { - return; - } - - try { - this.serial0s.closeStream(); - this.serialIs.closeStream(); - this.rxtxPort.close(); - this.serial0s = null; - this.serialIs = null; - this.rxtxPort = null; - } finally { - this.closed = true; - } - } - - @Override - public boolean isClosed() { - return this.closed; - } - - private class SerialInputStream extends InputStream { - private static final long SLEEP_TIME = 10L; // sleep appropriate time - - @Override - public synchronized int read() throws IOException { - long elapsedTime = 0; - - InputStream serialInputStream = rxtxPort.getInputStream(); - do { - if (serialInputStream.available() > 0) { - return serialInputStream.read(); - } - try { - Thread.sleep(SLEEP_TIME); - elapsedTime += SLEEP_TIME; - } catch (InterruptedException e) { - // ignore - } - - if (isClosed()) { - throw new SerialPortException("Serial port has been closed."); - } - } while (getSerialPortTimeout() == 0 || elapsedTime <= getSerialPortTimeout()); - - throw new SerialPortTimeoutException(); - } - - @Override - public int available() throws IOException { - return rxtxPort.getInputStream().available(); - } - - private void closeStream() throws IOException { - rxtxPort.getInputStream().close(); - } - - @Override - public void close() throws IOException { - JRxTxPort.this.close(); - } - } - - private class SerialOutputStream extends OutputStream { - - private OutputStream serialOutputStream; - - public SerialOutputStream(OutputStream serialOutputStream) { - this.serialOutputStream = serialOutputStream; - } - - @Override - public void write(int b) throws IOException { - checkIfOpen(); - - this.serialOutputStream.write(b); - } - - private void checkIfOpen() throws SerialPortException { - if (isClosed()) { - throw new SerialPortException("Port has been closed."); - } - } - - @Override - public void write(byte[] b, int off, int len) throws IOException { - checkIfOpen(); - this.serialOutputStream.write(b, off, len); - } - - @Override - public void write(byte[] b) throws IOException { - checkIfOpen(); - this.serialOutputStream.write(b); - } - - @Override - public void flush() throws IOException { - checkIfOpen(); - this.serialOutputStream.flush(); - } - - private void closeStream() throws IOException { - this.serialOutputStream.close(); - } - - @Override - public void close() throws IOException { - JRxTxPort.this.close(); - } - } - - @Override - public String getPortName() { - return this.portName; - } - - @Override - public DataBits getDataBits() { - return this.dataBits; - } - - @Override - public void setDataBits(DataBits dataBits) throws IOException { - this.dataBits = dataBits; - updateWrappedPort(); - } - - @Override - public Parity getParity() { - return this.parity; - } - - @Override - public void setParity(Parity parity) throws IOException { - this.parity = parity; - updateWrappedPort(); - } - - @Override - public StopBits getStopBits() { - return this.stopBits; - } - - @Override - public void setStopBits(StopBits stopBits) throws IOException { - this.stopBits = stopBits; - updateWrappedPort(); - } - - @Override - public int getBaudRate() { - return this.baudRate; - } - - @Override - public void setBaudRate(int baudRate) throws IOException { - this.baudRate = baudRate; - updateWrappedPort(); - } - - private void updateWrappedPort() throws IOException { - try { - this.rxtxPort.setSerialPortParams(this.baudRate, this.dataBits.getOldValue(), this.stopBits.getOldValue(), - this.parity.getOldValue()); - } catch (UnsupportedCommOperationException e) { - throw new IOException(e.getMessage()); - } - } - - @Override - public int getSerialPortTimeout() { - return this.serialPortTimeout; - } - - @Override - public void setSerialPortTimeout(int serialPortTimeout) throws IOException { - this.serialPortTimeout = serialPortTimeout; - } - - @Override - public void setFlowControl(FlowControl flowControl) throws IOException { - setFlowControl(flowControl, this.rxtxPort); - this.flowControl = flowControl; - } - - @Override - public FlowControl getFlowControl() { - return this.flowControl; - } -} +package org.openmuc.jrxtx; + +import static java.text.MessageFormat.format; +import static org.openhab.core.io.transport.serial.SerialPort.*; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.openhab.core.io.transport.serial.PortInUseException; +import org.openhab.core.io.transport.serial.SerialPortIdentifier; +import org.openhab.core.io.transport.serial.SerialPortManager; +import org.openhab.core.io.transport.serial.UnsupportedCommOperationException; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.util.tracker.ServiceTracker; + +/** + * This class is a workaround: + * jrxtx library includes gnu.io.* classes and j62056.jar is depending on that. As we are + * using nrjavaserial as an implementation of gnu.io, we can't go that way! + * -> As a workaround I shaded the {@link JRxTxPort} class which is used by j62056.jar and modified it to + * work with any implementation of {@link SerialPort} (formerly it was just working with the implementation + * gnu.io.RXTXPort). + * + * @author MatthiasS + * + */ +class JRxTxPort implements org.openmuc.jrxtx.SerialPort { + + private volatile boolean closed; + + private org.openhab.core.io.transport.serial.SerialPort rxtxPort; + + private SerialInputStream serialIs; + private SerialOutputStream serial0s; + + private String portName; + + private DataBits dataBits; + + private Parity parity; + + private StopBits stopBits; + + private int baudRate; + + private int serialPortTimeout; + + private FlowControl flowControl; + + public static JRxTxPort openSerialPort(String portName, int baudRate, Parity parity, DataBits dataBits, + StopBits stopBits, FlowControl flowControl) throws IOException { + try { + BundleContext bundleContext = FrameworkUtil.getBundle(JRxTxPort.class).getBundleContext(); + ServiceTracker serviceTracker = new ServiceTracker<>(bundleContext, + SerialPortManager.class, null); + serviceTracker.open(); + SerialPortManager serialPortManager = serviceTracker.getService(); + + SerialPortIdentifier serialPortIdentifier = serialPortManager.getIdentifier(portName); + if (serialPortIdentifier == null) { + String errMessage = format("Serial port {0} not found or port is busy.", portName); + throw new PortNotFoundException(errMessage); + } + org.openhab.core.io.transport.serial.SerialPort comPort = serialPortIdentifier.open("meterreader", 0); + // if (!(comPort instanceof RXTXPort)) { + // throw new SerialPortException("Unable to open the serial port. Port is not RXTX."); + // } + + try { + comPort.setSerialPortParams(baudRate, dataBits.getOldValue(), stopBits.getOldValue(), + parity.getOldValue()); + + setFlowControl(flowControl, comPort); + } catch (UnsupportedCommOperationException e) { + String message = format("Unable to apply config on serial port.\n{0}", e.getMessage()); + throw new SerialPortException(message); + } + + return new JRxTxPort(comPort, portName, baudRate, parity, dataBits, stopBits, flowControl); + // } catch (NoSuchPortException e) { + // String errMessage = format("Serial port {0} not found or port is busy.", portName); + // throw new PortNotFoundException(errMessage); + } catch (PortInUseException e) { + String errMessage = format("Serial port {0} is already in use.", portName); + throw new PortNotFoundException(errMessage); + // } catch (UnsupportedCommOperationException e1) { + // throw new IOException(e1); + } + } + + private static void setFlowControl(FlowControl flowControl, + org.openhab.core.io.transport.serial.SerialPort rxtxPort) throws IOException { + try { + switch (flowControl) { + case RTS_CTS: + rxtxPort.setFlowControlMode(FLOWCONTROL_RTSCTS_IN | FLOWCONTROL_RTSCTS_OUT); + break; + case XON_XOFF: + rxtxPort.setFlowControlMode(FLOWCONTROL_XONXOFF_IN | FLOWCONTROL_XONXOFF_OUT); + + break; + + case NONE: + default: + rxtxPort.setFlowControlMode(FLOWCONTROL_NONE); + break; + } + } catch (UnsupportedCommOperationException e) { + throw new IOException("Failed to set FlowControl mode", e); + } + } + + private JRxTxPort(org.openhab.core.io.transport.serial.SerialPort comPort, String portName, int baudRate, + Parity parity, DataBits dataBits, StopBits stopBits, FlowControl flowControl) throws IOException { + this.rxtxPort = comPort; + this.portName = portName; + this.baudRate = baudRate; + this.parity = parity; + this.dataBits = dataBits; + this.stopBits = stopBits; + this.flowControl = flowControl; + + this.closed = false; + + this.serial0s = new SerialOutputStream(this.rxtxPort.getOutputStream()); + this.serialIs = new SerialInputStream(); + } + + @Override + public InputStream getInputStream() throws IOException { + if (isClosed()) { + throw new SerialPortException("Serial port is closed"); + } + return this.serialIs; + } + + @Override + public OutputStream getOutputStream() throws IOException { + if (isClosed()) { + throw new SerialPortException("Serial port is closed"); + } + + return this.serial0s; + } + + @Override + public synchronized void close() throws IOException { + if (isClosed()) { + return; + } + + try { + this.serial0s.closeStream(); + this.serialIs.closeStream(); + this.rxtxPort.close(); + this.serial0s = null; + this.serialIs = null; + this.rxtxPort = null; + } finally { + this.closed = true; + } + } + + @Override + public boolean isClosed() { + return this.closed; + } + + private class SerialInputStream extends InputStream { + private static final long SLEEP_TIME = 10L; // sleep appropriate time + + @Override + public synchronized int read() throws IOException { + long elapsedTime = 0; + + InputStream serialInputStream = rxtxPort.getInputStream(); + do { + if (serialInputStream.available() > 0) { + return serialInputStream.read(); + } + try { + Thread.sleep(SLEEP_TIME); + elapsedTime += SLEEP_TIME; + } catch (InterruptedException e) { + // ignore + } + + if (isClosed()) { + throw new SerialPortException("Serial port has been closed."); + } + } while (getSerialPortTimeout() == 0 || elapsedTime <= getSerialPortTimeout()); + + throw new SerialPortTimeoutException(); + } + + @Override + public int available() throws IOException { + return rxtxPort.getInputStream().available(); + } + + private void closeStream() throws IOException { + rxtxPort.getInputStream().close(); + } + + @Override + public void close() throws IOException { + JRxTxPort.this.close(); + } + } + + private class SerialOutputStream extends OutputStream { + + private OutputStream serialOutputStream; + + public SerialOutputStream(OutputStream serialOutputStream) { + this.serialOutputStream = serialOutputStream; + } + + @Override + public void write(int b) throws IOException { + checkIfOpen(); + + this.serialOutputStream.write(b); + } + + private void checkIfOpen() throws SerialPortException { + if (isClosed()) { + throw new SerialPortException("Port has been closed."); + } + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + checkIfOpen(); + this.serialOutputStream.write(b, off, len); + } + + @Override + public void write(byte[] b) throws IOException { + checkIfOpen(); + this.serialOutputStream.write(b); + } + + @Override + public void flush() throws IOException { + checkIfOpen(); + this.serialOutputStream.flush(); + } + + private void closeStream() throws IOException { + this.serialOutputStream.close(); + } + + @Override + public void close() throws IOException { + JRxTxPort.this.close(); + } + } + + @Override + public String getPortName() { + return this.portName; + } + + @Override + public DataBits getDataBits() { + return this.dataBits; + } + + @Override + public void setDataBits(DataBits dataBits) throws IOException { + this.dataBits = dataBits; + updateWrappedPort(); + } + + @Override + public Parity getParity() { + return this.parity; + } + + @Override + public void setParity(Parity parity) throws IOException { + this.parity = parity; + updateWrappedPort(); + } + + @Override + public StopBits getStopBits() { + return this.stopBits; + } + + @Override + public void setStopBits(StopBits stopBits) throws IOException { + this.stopBits = stopBits; + updateWrappedPort(); + } + + @Override + public int getBaudRate() { + return this.baudRate; + } + + @Override + public void setBaudRate(int baudRate) throws IOException { + this.baudRate = baudRate; + updateWrappedPort(); + } + + private void updateWrappedPort() throws IOException { + try { + this.rxtxPort.setSerialPortParams(this.baudRate, this.dataBits.getOldValue(), this.stopBits.getOldValue(), + this.parity.getOldValue()); + } catch (UnsupportedCommOperationException e) { + throw new IOException(e.getMessage()); + } + } + + @Override + public int getSerialPortTimeout() { + return this.serialPortTimeout; + } + + @Override + public void setSerialPortTimeout(int serialPortTimeout) throws IOException { + this.serialPortTimeout = serialPortTimeout; + } + + @Override + public void setFlowControl(FlowControl flowControl) throws IOException { + setFlowControl(flowControl, this.rxtxPort); + this.flowControl = flowControl; + } + + @Override + public FlowControl getFlowControl() { + return this.flowControl; + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/Parity.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/Parity.java index 3c1dce8..a2541b8 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/Parity.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/Parity.java @@ -1,54 +1,54 @@ -package org.openmuc.jrxtx; - -import org.openhab.core.io.transport.serial.SerialPort; - -/** - * The parity. - */ -@SuppressWarnings("deprecation") -public enum Parity { - /** - * No parity bit will be sent with each data character at all. - */ - NONE(SerialPort.PARITY_NONE), - /** - * An odd parity bit will be sent with each data character. I.e. will be set to 1 if the data character contains an - * even number of bits set to 1. - */ - ODD(SerialPort.PARITY_ODD), - /** - * An even parity bit will be sent with each data character. I.e. will be set to 1 if the data character contains an - * odd number of bits set to 1. - */ - EVEN(SerialPort.PARITY_EVEN), - /** - * A mark parity bit (i.e. always 1) will be sent with each data character. - */ - MARK(SerialPort.PARITY_MARK), - /** - * A space parity bit (i.e. always 0) will be sent with each data character - */ - SPACE(4),; - - private static final Parity[] VALUES = values(); - private int odlValue; - - private Parity(int oldValue) { - this.odlValue = oldValue; - } - - int getOldValue() { - return this.odlValue; - } - - static Parity forValue(int parity) { - for (Parity p : VALUES) { - if (p.odlValue == parity) { - return p; - } - } - - // should not occur - throw new RuntimeException("Error."); - } -} +package org.openmuc.jrxtx; + +import org.openhab.core.io.transport.serial.SerialPort; + +/** + * The parity. + */ +@SuppressWarnings("deprecation") +public enum Parity { + /** + * No parity bit will be sent with each data character at all. + */ + NONE(SerialPort.PARITY_NONE), + /** + * An odd parity bit will be sent with each data character. I.e. will be set to 1 if the data character contains an + * even number of bits set to 1. + */ + ODD(SerialPort.PARITY_ODD), + /** + * An even parity bit will be sent with each data character. I.e. will be set to 1 if the data character contains an + * odd number of bits set to 1. + */ + EVEN(SerialPort.PARITY_EVEN), + /** + * A mark parity bit (i.e. always 1) will be sent with each data character. + */ + MARK(SerialPort.PARITY_MARK), + /** + * A space parity bit (i.e. always 0) will be sent with each data character + */ + SPACE(4),; + + private static final Parity[] VALUES = values(); + private int odlValue; + + private Parity(int oldValue) { + this.odlValue = oldValue; + } + + int getOldValue() { + return this.odlValue; + } + + static Parity forValue(int parity) { + for (Parity p : VALUES) { + if (p.odlValue == parity) { + return p; + } + } + + // should not occur + throw new RuntimeException("Error."); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/PortNotFoundException.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/PortNotFoundException.java index 28176da..4d7c7eb 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/PortNotFoundException.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/PortNotFoundException.java @@ -1,20 +1,20 @@ -package org.openmuc.jrxtx; - -/** - * Signals that the provided serial port name provided via {@link SerialPortBuilder#newBuilder(String)}, - * {@link SerialPortBuilder#setPortName(String)} doesn't exist on the host system. - */ -public class PortNotFoundException extends SerialPortException { - - private static final long serialVersionUID = 2766015292714524756L; - - /** - * Constructs a new PortNotFoundException with the specified detail message. - * - * @param message - * the detail message. - */ - public PortNotFoundException(String message) { - super(message); - } -} +package org.openmuc.jrxtx; + +/** + * Signals that the provided serial port name provided via {@link SerialPortBuilder#newBuilder(String)}, + * {@link SerialPortBuilder#setPortName(String)} doesn't exist on the host system. + */ +public class PortNotFoundException extends SerialPortException { + + private static final long serialVersionUID = 2766015292714524756L; + + /** + * Constructs a new PortNotFoundException with the specified detail message. + * + * @param message + * the detail message. + */ + public PortNotFoundException(String message) { + super(message); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPort.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPort.java index 49f33f2..7732ab7 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPort.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPort.java @@ -1,172 +1,172 @@ -package org.openmuc.jrxtx; - -import java.io.Closeable; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * Serial port for communication using UARTs. Can be used for communication protocols such as RS-232 and RS-485. - *

    - * A SerialPort is created using {@link SerialPortBuilder}. Once closed it cannot be opened again but has to be - * recreated. - */ -public interface SerialPort extends Closeable { - - /** - * Returns the input stream for this serial port. - *

    - * Closing the returned InputStream will close the associated serial port. - * - * @return the InputStream object that can be used to read from the port. - * @throws IOException - * if an I/O error occurred - */ - InputStream getInputStream() throws IOException; - - /** - * Returns the output stream for this serial port. - * - * @return the OutputStream object that can be used to write to the port. - * @throws IOException - * if an I/O error occurred. - */ - OutputStream getOutputStream() throws IOException; - - /** - * Closes the serial port. - *

    - * Also closes the associated input and output streams. - * - * @throws IOException - * if an I/O error occurred. - */ - void close() throws IOException; - - /** - * Returns whether the serial port is currently open and available for communication. - * - * @return true if the serial port is closed. - */ - boolean isClosed(); - - /** - * Get the name of the serial port. - * - * @return the serial port name. - */ - String getPortName(); - - /** - * Get the current data bits config. - * - * @return the dataBits the data bits. - */ - DataBits getDataBits(); - - /** - * Set the data bits. - * - * @param dataBits - * the new dataBits. - * @throws IOException - * if an I/O exception occurred when setting the new data bits.. - */ - void setDataBits(DataBits dataBits) throws IOException; - - /** - * Get the parity. - * - * @return the new parity. - */ - Parity getParity(); - - /** - * Set the new parity. - * - * @param parity - * the new parity. - * @throws IOException - * if an I/O exception occurred when setting the new parity. - */ - void setParity(Parity parity) throws IOException; - - /** - * Get the current stop bits settings. - * - * @return the stopBits the stop bits. - */ - StopBits getStopBits(); - - /** - * Set the stop bits. - * - * @param stopBits - * the stopBits to set - * @throws IOException - * if an I/O exception occurred when setting the new stop bits. - */ - void setStopBits(StopBits stopBits) throws IOException; - - /** - * @return the baudRate setting. - * - * @see #setBaudRate(int) - */ - int getBaudRate(); - - /** - * Sets the baud rate of the system. - * - * @param baudRate - * the new baud rate. - * @throws IOException - * if an I/O exception occurred when setting the new baud rate. - * - * @see #getBaudRate() - */ - void setBaudRate(int baudRate) throws IOException; - - /** - * Returns setting for serial port timeout. 0 returns implies that the option is disabled (i.e., - * timeout of infinity). - * - * @return the serialPortTimeout. - * - * @see #setSerialPortTimeout(int) - */ - int getSerialPortTimeout(); - - /** - * Enable/disable serial port timeout with the specified timeout, in milliseconds. With this option set to a - * non-zero timeout, a read() call on the InputStream associated with this serial port will block for only this - * amount of time. If the timeout expires, a org.openmuc.jrxtx.SerialPortTimeoutExcepption is raised, though the - * serial port is still valid. The option must be enabled prior to entering the blocking operation to have effect. - * The timeout must be > 0. A timeout of zero is interpreted as an infinite timeout. - * - * @param serialPortTimeout - * the specified timeout, in milliseconds. - * @throws IOException - * if there is an error in the underlying protocol. - * - * @see #getSerialPortTimeout() - */ - void setSerialPortTimeout(int serialPortTimeout) throws IOException; - - /** - * Set the flow control type. - * - * @param flowControl - * the flow control. - * @throws IOException - * if an I/O exception occurred when setting the new baud rate. - */ - void setFlowControl(FlowControl flowControl) throws IOException; - - /** - * Get the current flow control settings. - * - * @return the flow control. - */ - FlowControl getFlowControl(); -} +package org.openmuc.jrxtx; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Serial port for communication using UARTs. Can be used for communication protocols such as RS-232 and RS-485. + *

    + * A SerialPort is created using {@link SerialPortBuilder}. Once closed it cannot be opened again but has to be + * recreated. + */ +public interface SerialPort extends Closeable { + + /** + * Returns the input stream for this serial port. + *

    + * Closing the returned InputStream will close the associated serial port. + * + * @return the InputStream object that can be used to read from the port. + * @throws IOException + * if an I/O error occurred + */ + InputStream getInputStream() throws IOException; + + /** + * Returns the output stream for this serial port. + * + * @return the OutputStream object that can be used to write to the port. + * @throws IOException + * if an I/O error occurred. + */ + OutputStream getOutputStream() throws IOException; + + /** + * Closes the serial port. + *

    + * Also closes the associated input and output streams. + * + * @throws IOException + * if an I/O error occurred. + */ + void close() throws IOException; + + /** + * Returns whether the serial port is currently open and available for communication. + * + * @return true if the serial port is closed. + */ + boolean isClosed(); + + /** + * Get the name of the serial port. + * + * @return the serial port name. + */ + String getPortName(); + + /** + * Get the current data bits config. + * + * @return the dataBits the data bits. + */ + DataBits getDataBits(); + + /** + * Set the data bits. + * + * @param dataBits + * the new dataBits. + * @throws IOException + * if an I/O exception occurred when setting the new data bits.. + */ + void setDataBits(DataBits dataBits) throws IOException; + + /** + * Get the parity. + * + * @return the new parity. + */ + Parity getParity(); + + /** + * Set the new parity. + * + * @param parity + * the new parity. + * @throws IOException + * if an I/O exception occurred when setting the new parity. + */ + void setParity(Parity parity) throws IOException; + + /** + * Get the current stop bits settings. + * + * @return the stopBits the stop bits. + */ + StopBits getStopBits(); + + /** + * Set the stop bits. + * + * @param stopBits + * the stopBits to set + * @throws IOException + * if an I/O exception occurred when setting the new stop bits. + */ + void setStopBits(StopBits stopBits) throws IOException; + + /** + * @return the baudRate setting. + * + * @see #setBaudRate(int) + */ + int getBaudRate(); + + /** + * Sets the baud rate of the system. + * + * @param baudRate + * the new baud rate. + * @throws IOException + * if an I/O exception occurred when setting the new baud rate. + * + * @see #getBaudRate() + */ + void setBaudRate(int baudRate) throws IOException; + + /** + * Returns setting for serial port timeout. 0 returns implies that the option is disabled (i.e., + * timeout of infinity). + * + * @return the serialPortTimeout. + * + * @see #setSerialPortTimeout(int) + */ + int getSerialPortTimeout(); + + /** + * Enable/disable serial port timeout with the specified timeout, in milliseconds. With this option set to a + * non-zero timeout, a read() call on the InputStream associated with this serial port will block for only this + * amount of time. If the timeout expires, a org.openmuc.jrxtx.SerialPortTimeoutExcepption is raised, though the + * serial port is still valid. The option must be enabled prior to entering the blocking operation to have effect. + * The timeout must be > 0. A timeout of zero is interpreted as an infinite timeout. + * + * @param serialPortTimeout + * the specified timeout, in milliseconds. + * @throws IOException + * if there is an error in the underlying protocol. + * + * @see #getSerialPortTimeout() + */ + void setSerialPortTimeout(int serialPortTimeout) throws IOException; + + /** + * Set the flow control type. + * + * @param flowControl + * the flow control. + * @throws IOException + * if an I/O exception occurred when setting the new baud rate. + */ + void setFlowControl(FlowControl flowControl) throws IOException; + + /** + * Get the current flow control settings. + * + * @return the flow control. + */ + FlowControl getFlowControl(); +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPortBuilder.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPortBuilder.java index 7da178c..68983fc 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPortBuilder.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPortBuilder.java @@ -1,142 +1,142 @@ -package org.openmuc.jrxtx; - -import java.io.IOException; - -/** - * Builder class for SerialPorts. Provides a convenient way to set the various fields of a SerialPort. - * - * Example: - * - *

    - * 
    - * SerialPort port = newBuilder("/dev/ttyS0")
    - *                   .setBaudRate(19200)
    - *                   .setParity(Parity.EVEN)
    - *                   .build();
    - * InputStream is = port.getInputStream();
    - * ..
    - * 
    - * 
    - */ -@SuppressWarnings("deprecation") -public class SerialPortBuilder { - - private String portName; - private int baudRate; - private DataBits dataBits; - private Parity parity; - private StopBits stopBits; - private FlowControl flowControl; - - private SerialPortBuilder(String portName) { - this.portName = portName; - this.baudRate = 9600; - this.dataBits = DataBits.DATABITS_8; - this.parity = Parity.EVEN; - this.stopBits = StopBits.STOPBITS_1; - this.flowControl = FlowControl.NONE; - } - - /** - * Constructs a new SerialPortBuilder with the default values. - * - * @param portName - * the serial port name. E.g. on Unix systems: "/dev/ttyUSB0" and on Unix - * @return returns the new builder. - */ - public static SerialPortBuilder newBuilder(String portName) { - return new SerialPortBuilder(portName); - } - - /** - * Set the serial port name. - * - * @param portName - * the serial port name e.g. "/dev/ttyUSB0" - * @return the serial port builder. - */ - public SerialPortBuilder setPortName(String portName) { - this.portName = portName; - return this; - } - - /** - * Set the baud rate for the serial port. Values such as 9600 or 115200. - * - * @param baudRate - * the baud rate. - * @return the serial port builder. - * - * @see SerialPortBuilder#setBaudRate(int) - */ - public SerialPortBuilder setBaudRate(int baudRate) { - this.baudRate = baudRate; - return this; - } - - /** - * Set the number of data bits transfered with the serial port. - * - * @param dataBits - * the number of dataBits. - * @return the serial port builder. - * @see SerialPort#setDataBits(DataBits) - */ - public SerialPortBuilder setDataBits(DataBits dataBits) { - this.dataBits = dataBits; - return this; - } - - /** - * Set the parity of the serial port. - * - * @param parity - * the parity. - * @return the serial port builder. - * @see SerialPort#setParity(Parity) - */ - public SerialPortBuilder setParity(Parity parity) { - this.parity = parity; - return this; - } - - /** - * Set the number of stop bits after each data bits. - * - * @param stopBits - * the number of stop bits. - * @return the serial port builder. - * - * @see SerialPort#setStopBits(StopBits) - */ - public SerialPortBuilder setStopBits(StopBits stopBits) { - this.stopBits = stopBits; - return this; - } - - /** - * Set the flow control type. - * - * @param flowControl - * the flow control. - * - * @return the serial port builder. - * - * @see SerialPort#setFlowControl(FlowControl) - */ - public SerialPortBuilder setFlowControl(FlowControl flowControl) { - this.flowControl = flowControl; - return this; - } - - /** - * Combine all of the options that have been set and return a new SerialPort object. - * - * @return a new serial port object. - * @throws IOException - * if an I/O exception occurred while opening the serial port. - */ - public SerialPort build() throws IOException { - return JRxTxPort.openSerialPort(portName, baudRate, parity, dataBits, stopBits, flowControl); - } -} +package org.openmuc.jrxtx; + +import java.io.IOException; + +/** + * Builder class for SerialPorts. Provides a convenient way to set the various fields of a SerialPort. + * + * Example: + * + *
    + * 
    + * SerialPort port = newBuilder("/dev/ttyS0")
    + *                   .setBaudRate(19200)
    + *                   .setParity(Parity.EVEN)
    + *                   .build();
    + * InputStream is = port.getInputStream();
    + * ..
    + * 
    + * 
    + */ +@SuppressWarnings("deprecation") +public class SerialPortBuilder { + + private String portName; + private int baudRate; + private DataBits dataBits; + private Parity parity; + private StopBits stopBits; + private FlowControl flowControl; + + private SerialPortBuilder(String portName) { + this.portName = portName; + this.baudRate = 9600; + this.dataBits = DataBits.DATABITS_8; + this.parity = Parity.EVEN; + this.stopBits = StopBits.STOPBITS_1; + this.flowControl = FlowControl.NONE; + } + + /** + * Constructs a new SerialPortBuilder with the default values. + * + * @param portName + * the serial port name. E.g. on Unix systems: "/dev/ttyUSB0" and on Unix + * @return returns the new builder. + */ + public static SerialPortBuilder newBuilder(String portName) { + return new SerialPortBuilder(portName); + } + + /** + * Set the serial port name. + * + * @param portName + * the serial port name e.g. "/dev/ttyUSB0" + * @return the serial port builder. + */ + public SerialPortBuilder setPortName(String portName) { + this.portName = portName; + return this; + } + + /** + * Set the baud rate for the serial port. Values such as 9600 or 115200. + * + * @param baudRate + * the baud rate. + * @return the serial port builder. + * + * @see SerialPortBuilder#setBaudRate(int) + */ + public SerialPortBuilder setBaudRate(int baudRate) { + this.baudRate = baudRate; + return this; + } + + /** + * Set the number of data bits transfered with the serial port. + * + * @param dataBits + * the number of dataBits. + * @return the serial port builder. + * @see SerialPort#setDataBits(DataBits) + */ + public SerialPortBuilder setDataBits(DataBits dataBits) { + this.dataBits = dataBits; + return this; + } + + /** + * Set the parity of the serial port. + * + * @param parity + * the parity. + * @return the serial port builder. + * @see SerialPort#setParity(Parity) + */ + public SerialPortBuilder setParity(Parity parity) { + this.parity = parity; + return this; + } + + /** + * Set the number of stop bits after each data bits. + * + * @param stopBits + * the number of stop bits. + * @return the serial port builder. + * + * @see SerialPort#setStopBits(StopBits) + */ + public SerialPortBuilder setStopBits(StopBits stopBits) { + this.stopBits = stopBits; + return this; + } + + /** + * Set the flow control type. + * + * @param flowControl + * the flow control. + * + * @return the serial port builder. + * + * @see SerialPort#setFlowControl(FlowControl) + */ + public SerialPortBuilder setFlowControl(FlowControl flowControl) { + this.flowControl = flowControl; + return this; + } + + /** + * Combine all of the options that have been set and return a new SerialPort object. + * + * @return a new serial port object. + * @throws IOException + * if an I/O exception occurred while opening the serial port. + */ + public SerialPort build() throws IOException { + return JRxTxPort.openSerialPort(portName, baudRate, parity, dataBits, stopBits, flowControl); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPortException.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPortException.java index 00f31c8..472de9c 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPortException.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPortException.java @@ -1,23 +1,23 @@ -package org.openmuc.jrxtx; - -import java.io.IOException; - -/** - * Signals that a I/O exception with the SerialPort occurred. - * - * @see SerialPort - */ -public class SerialPortException extends IOException { - - private static final long serialVersionUID = -4848841747671551647L; - - /** - * Constructs a new SerialPortException with the specified detail message. - * - * @param message - * the detail message. - */ - public SerialPortException(String message) { - super(message); - } -} +package org.openmuc.jrxtx; + +import java.io.IOException; + +/** + * Signals that a I/O exception with the SerialPort occurred. + * + * @see SerialPort + */ +public class SerialPortException extends IOException { + + private static final long serialVersionUID = -4848841747671551647L; + + /** + * Constructs a new SerialPortException with the specified detail message. + * + * @param message + * the detail message. + */ + public SerialPortException(String message) { + super(message); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPortTimeoutException.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPortTimeoutException.java index aca7969..fb38143 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPortTimeoutException.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/SerialPortTimeoutException.java @@ -1,25 +1,25 @@ -package org.openmuc.jrxtx; - -import java.io.InterruptedIOException; - -/** - * Signals that the read function of the SerialPort input stream has timed out. - */ -public class SerialPortTimeoutException extends InterruptedIOException { - - private static final long serialVersionUID = -5808479011360793837L; - - public SerialPortTimeoutException() { - super(); - } - - /** - * Constructs a new SerialPortTimeoutException with the specified detail message. - * - * @param message - * the detail message. - */ - public SerialPortTimeoutException(String message) { - super(message); - } -} +package org.openmuc.jrxtx; + +import java.io.InterruptedIOException; + +/** + * Signals that the read function of the SerialPort input stream has timed out. + */ +public class SerialPortTimeoutException extends InterruptedIOException { + + private static final long serialVersionUID = -5808479011360793837L; + + public SerialPortTimeoutException() { + super(); + } + + /** + * Constructs a new SerialPortTimeoutException with the specified detail message. + * + * @param message + * the detail message. + */ + public SerialPortTimeoutException(String message) { + super(message); + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/StopBits.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/StopBits.java index f02c65d..28cc9ce 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/StopBits.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jrxtx/StopBits.java @@ -1,32 +1,32 @@ -package org.openmuc.jrxtx; - -import org.openhab.core.io.transport.serial.SerialPort; - -/** - * The stop bits. - */ -@SuppressWarnings("deprecation") -public enum StopBits { - /** - * 1 stop bit will be sent at the end of every character. - */ - STOPBITS_1(SerialPort.STOPBITS_1), - /** - * 1.5 stop bits will be sent at the end of every character - */ - STOPBITS_1_5(SerialPort.STOPBITS_1_5), - /** - * 2 stop bits will be sent at the end of every character - */ - STOPBITS_2(SerialPort.STOPBITS_2); - - private int odlValue; - - private StopBits(int oldValue) { - this.odlValue = oldValue; - } - - int getOldValue() { - return this.odlValue; - } -} +package org.openmuc.jrxtx; + +import org.openhab.core.io.transport.serial.SerialPort; + +/** + * The stop bits. + */ +@SuppressWarnings("deprecation") +public enum StopBits { + /** + * 1 stop bit will be sent at the end of every character. + */ + STOPBITS_1(SerialPort.STOPBITS_1), + /** + * 1.5 stop bits will be sent at the end of every character + */ + STOPBITS_1_5(SerialPort.STOPBITS_1_5), + /** + * 2 stop bits will be sent at the end of every character + */ + STOPBITS_2(SerialPort.STOPBITS_2); + + private int odlValue; + + private StopBits(int oldValue) { + this.odlValue = oldValue; + } + + int getOldValue() { + return this.odlValue; + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/AbstractWMBusTest.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/AbstractWMBusTest.java index ea3fea4..5cbd8fd 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/AbstractWMBusTest.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/AbstractWMBusTest.java @@ -1,54 +1,54 @@ -package org.openhab.binding.wmbus.device; - -import java.util.Collections; - -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openhab.core.util.HexUtils; -import org.openmuc.jmbus.DecodingException; -import org.openmuc.jmbus.wireless.VirtualWMBusMessageHelper; -import org.openmuc.jmbus.wireless.WMBusMessage; - -public class AbstractWMBusTest { - - // 7062A0 -> warm water - public final static String MESSAGE_112_WARM_WATER = "29446850084481637062A0009F255502D036410000030404030303030303020303030303030304020404"; - // 7072A0 -> cold water - public final static String MESSAGE_112_COLD_WATER = "29446850054381637072A0009F256E07D0366B01000F1313151712140F06101212170E12121414100F10"; - // 7462A2 -> warm water - public final static String MESSAGE_116_WARM_WATER = "2F446850878465427462A2069F234B00D016150000000101010100010100000100000101020201020201020101020201E60000"; - // 7472A2 -> cold water - public final static String MESSAGE_116_COLD_WATER = "2F446850122320417472A2069F23B301D016B50000000609090908080C09080A0A0A0A09080907080609090707060707000000"; - - // 7143A0 -> heat meter - public final static String MESSAGE_113_HEAT = "36446850180780147143A0009F23880400581B0000000800000000000000000000000000000000000000000001081C60400001001000001F0000"; - - // 4543A1 -> HKVv69 - public final static String MESSAGE_69_HKV = "37446850468519484543A1009F23274900607814000039434C3169650F3EB358D207121AD050451C4C2092C926E960F6577AD5E1C556639F16"; - // 61???? -> HKVv97 - public final static String MESSAGE_97_HKV = ""; - // 6480A0 -> HKVv100 - public final static String MESSAGE_100_HKV = "2E446850382041606480A0119F236800D016410000000000000000000000000000000000000009090F110F0C09000FDA0000"; - // 6980A0 -> HKVv105 - public final static String MESSAGE_105_HKV = "32446850591266506980A0119F23CF07E0169A01680845091A000100000200000000000000000000110B0020361D221F6E9287490000"; - // 9480A2 -> HKVv148 - public final static String MESSAGE_148_HKV = "33446850789492029480A20F9F259C01B031910201580A470A0000000000000000071F241E454E42588478775E4F2E1400000000DB0000"; - - // 76F0A0 -> SDv118 - public final static String MESSAGE_118_SD_1 = "294468507764866476F0A000DE246F2500586F2500001A000013006BA1007CB2008DC3009ED4000FE500EE0000"; - public final static String MESSAGE_118_SD_2 = "294468508364866476F0A000DE246F2500586F25010019000013006BA1007CB2008DC3009ED4000FE500F40000"; - public final static String MESSAGE_118_SD_3 = "294468501857639176f0a000e3267b2700dc7b2700000e0001315678006ba1007cb2008dc3009ed4000fe5"; - - public final static int RSSI = 10; - - protected final WMBusDevice message(String messageHex) throws DecodingException { - return message(messageHex, null); - } - - protected final WMBusDevice message(String messageHex, WMBusAdapter adapter) throws DecodingException { - byte[] buffer = HexUtils.hexToBytes(messageHex); - WMBusMessage message = VirtualWMBusMessageHelper.decode(buffer, RSSI, Collections.emptyMap()); - - return new WMBusDevice(message, adapter); - } -} +package org.openhab.binding.wmbus.device; + +import java.util.Collections; + +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openhab.core.util.HexUtils; +import org.openmuc.jmbus.DecodingException; +import org.openmuc.jmbus.wireless.VirtualWMBusMessageHelper; +import org.openmuc.jmbus.wireless.WMBusMessage; + +public class AbstractWMBusTest { + + // 7062A0 -> warm water + public final static String MESSAGE_112_WARM_WATER = "29446850084481637062A0009F255502D036410000030404030303030303020303030303030304020404"; + // 7072A0 -> cold water + public final static String MESSAGE_112_COLD_WATER = "29446850054381637072A0009F256E07D0366B01000F1313151712140F06101212170E12121414100F10"; + // 7462A2 -> warm water + public final static String MESSAGE_116_WARM_WATER = "2F446850878465427462A2069F234B00D016150000000101010100010100000100000101020201020201020101020201E60000"; + // 7472A2 -> cold water + public final static String MESSAGE_116_COLD_WATER = "2F446850122320417472A2069F23B301D016B50000000609090908080C09080A0A0A0A09080907080609090707060707000000"; + + // 7143A0 -> heat meter + public final static String MESSAGE_113_HEAT = "36446850180780147143A0009F23880400581B0000000800000000000000000000000000000000000000000001081C60400001001000001F0000"; + + // 4543A1 -> HKVv69 + public final static String MESSAGE_69_HKV = "37446850468519484543A1009F23274900607814000039434C3169650F3EB358D207121AD050451C4C2092C926E960F6577AD5E1C556639F16"; + // 61???? -> HKVv97 + public final static String MESSAGE_97_HKV = ""; + // 6480A0 -> HKVv100 + public final static String MESSAGE_100_HKV = "2E446850382041606480A0119F236800D016410000000000000000000000000000000000000009090F110F0C09000FDA0000"; + // 6980A0 -> HKVv105 + public final static String MESSAGE_105_HKV = "32446850591266506980A0119F23CF07E0169A01680845091A000100000200000000000000000000110B0020361D221F6E9287490000"; + // 9480A2 -> HKVv148 + public final static String MESSAGE_148_HKV = "33446850789492029480A20F9F259C01B031910201580A470A0000000000000000071F241E454E42588478775E4F2E1400000000DB0000"; + + // 76F0A0 -> SDv118 + public final static String MESSAGE_118_SD_1 = "294468507764866476F0A000DE246F2500586F2500001A000013006BA1007CB2008DC3009ED4000FE500EE0000"; + public final static String MESSAGE_118_SD_2 = "294468508364866476F0A000DE246F2500586F25010019000013006BA1007CB2008DC3009ED4000FE500F40000"; + public final static String MESSAGE_118_SD_3 = "294468501857639176f0a000e3267b2700dc7b2700000e0001315678006ba1007cb2008dc3009ed4000fe5"; + + public final static int RSSI = 10; + + protected final WMBusDevice message(String messageHex) throws DecodingException { + return message(messageHex, null); + } + + protected final WMBusDevice message(String messageHex, WMBusAdapter adapter) throws DecodingException { + byte[] buffer = HexUtils.hexToBytes(messageHex); + WMBusMessage message = VirtualWMBusMessageHelper.decode(buffer, RSSI, Collections.emptyMap()); + + return new WMBusDevice(message, adapter); + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/RecordPredicate.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/RecordPredicate.java index 82de597..7eb9da1 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/RecordPredicate.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/RecordPredicate.java @@ -1,27 +1,27 @@ -package org.openhab.binding.wmbus.device; - -import java.util.function.Predicate; - -import org.openhab.binding.wmbus.device.techem.Record; - -/** - * Predicate for testing records reported by techem decoders. - * - * @author Łukasz Dywicki - initial contribution - */ -public interface RecordPredicate extends Predicate> { - - /** - * Predicate/condition description. - * - * @return Textual description of predicate. - */ - String description(); - - /** - * Arguments passed additionally to format description text. - * - * @return Arguments for formatting predicate description. - */ - Object[] arguments(); -} +package org.openhab.binding.wmbus.device; + +import java.util.function.Predicate; + +import org.openhab.binding.wmbus.device.techem.Record; + +/** + * Predicate for testing records reported by techem decoders. + * + * @author Łukasz Dywicki - initial contribution + */ +public interface RecordPredicate extends Predicate> { + + /** + * Predicate/condition description. + * + * @return Textual description of predicate. + */ + String description(); + + /** + * Arguments passed additionally to format description text. + * + * @return Arguments for formatting predicate description. + */ + Object[] arguments(); +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/generic/GenericWMBusThingHandlerTest.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/generic/GenericWMBusThingHandlerTest.java index f772e47..7368938 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/generic/GenericWMBusThingHandlerTest.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/generic/GenericWMBusThingHandlerTest.java @@ -1,127 +1,127 @@ -package org.openhab.binding.wmbus.device.generic; - -import java.util.Map; - -import org.assertj.core.api.Assertions; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.ArgumentMatchers; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnitRunner; -import org.openhab.binding.wmbus.RecordType; -import org.openhab.binding.wmbus.WMBusBindingConstants; -import org.openhab.binding.wmbus.WMBusDevice; -import org.openhab.binding.wmbus.handler.WMBusBridgeHandler; -import org.openhab.binding.wmbus.internal.units.CompositeUnitRegistry; -import org.openhab.core.config.core.Configuration; -import org.openhab.core.library.types.QuantityType; -import org.openhab.core.library.unit.SIUnits; -import org.openhab.core.thing.Bridge; -import org.openhab.core.thing.Channel; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingUID; -import org.openhab.core.thing.binding.ThingHandler; -import org.openhab.core.thing.binding.ThingHandlerCallback; -import org.openhab.core.thing.binding.builder.BridgeBuilder; -import org.openhab.core.thing.binding.builder.ChannelBuilder; -import org.openhab.core.thing.binding.builder.ThingBuilder; -import org.openhab.core.types.State; -import org.openhab.io.transport.mbus.wireless.MapKeyStorage; -import org.openmuc.jmbus.DataRecord; -import org.openmuc.jmbus.DlmsUnit; - -import com.google.common.collect.ImmutableMap; - -/** - * Test of generic handler and its behavior upon receipt of new wmbus frame. - * - * @author Łukasz Dywicki - Initial contribution. - */ -@RunWith(MockitoJUnitRunner.class) -public class GenericWMBusThingHandlerTest { - - private static final String CHANNEL_VOLUME = "volume"; - private static final String DEVICE_ADDRESS = "68TCH100"; - private static final Map CONFIGURATION = ImmutableMap - .of(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS, DEVICE_ADDRESS); - - private static final RecordType VOLUME = new RecordType(0x0C, 0x14); - private static final Double VOLUME_VALUE = 10.0; - - private static final String THING_ID = "foobar"; - private static final String BRIDGE_ID = "usb0"; - private final static ThingUID THING_UID = new ThingUID(WMBusBindingConstants.THING_TYPE_METER, THING_ID); - private final static ChannelUID CHANNEL_UID = new ChannelUID(THING_UID, CHANNEL_VOLUME); - - private final static Map CHANNEL_MAPPING = ImmutableMap.of(CHANNEL_VOLUME, VOLUME); - - private final GenericWMBusThingHandler handler; - - @Mock - public WMBusBridgeHandler adapter; - - @Mock - private ThingHandlerCallback callback; - - public GenericWMBusThingHandlerTest() { - handler = new GenericWMBusThingHandler(createTestThing(), new MapKeyStorage(), - new CompositeUnitRegistry(), CHANNEL_MAPPING) { - @Override - protected org.openhab.core.thing.Bridge getBridge() { - return createTestBridge(adapter); - }; - }; - } - - @Before - public void setUp() { - handler.initialize(); - handler.setCallback(callback); - } - - @After - public void tearDown() { - handler.dispose(); - } - - @Test - public void testChannelUpdate() throws Exception { - WMBusDevice device = Mockito.mock(WMBusDevice.class); - DataRecord data = Mockito.mock(DataRecord.class); - - Mockito.when(device.getDeviceAddress()).thenReturn(DEVICE_ADDRESS); - Mockito.when(device.findRecord(VOLUME)).thenReturn(data); - Mockito.when(data.getUnit()).thenReturn(DlmsUnit.CUBIC_METRE); - Mockito.when(data.getScaledDataValue()).thenReturn(VOLUME_VALUE); - handler.onChangedWMBusDevice(adapter, device); - - ArgumentCaptor stateCapture = ArgumentCaptor.forClass(State.class); - Mockito.verify(callback).stateUpdated(ArgumentMatchers.eq(CHANNEL_UID), stateCapture.capture()); - - Assertions.assertThat(stateCapture.getAllValues()).hasSize(1); - - Assertions.assertThat(stateCapture.getAllValues().get(0)) - .isEqualTo(new QuantityType<>(VOLUME_VALUE, SIUnits.CUBIC_METRE)); - } - - public static Thing createTestThing() { - ThingBuilder thing = ThingBuilder.create(WMBusBindingConstants.THING_TYPE_METER, THING_ID); - thing.withConfiguration(new Configuration(CONFIGURATION)); - - Channel channel = ChannelBuilder.create(CHANNEL_UID, "DecimalType").build(); - thing.withChannel(channel); - - return thing.build(); - } - - static Bridge createTestBridge(ThingHandler handler) { - Bridge bridge = BridgeBuilder.create(WMBusBindingConstants.THING_TYPE_BRIDGE, BRIDGE_ID).build(); - bridge.setHandler(handler); - return bridge; - } -} +package org.openhab.binding.wmbus.device.generic; + +import java.util.Map; + +import org.assertj.core.api.Assertions; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.openhab.binding.wmbus.RecordType; +import org.openhab.binding.wmbus.WMBusBindingConstants; +import org.openhab.binding.wmbus.WMBusDevice; +import org.openhab.binding.wmbus.handler.WMBusBridgeHandler; +import org.openhab.binding.wmbus.internal.units.CompositeUnitRegistry; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.thing.binding.builder.BridgeBuilder; +import org.openhab.core.thing.binding.builder.ChannelBuilder; +import org.openhab.core.thing.binding.builder.ThingBuilder; +import org.openhab.core.types.State; +import org.openhab.io.transport.mbus.wireless.MapKeyStorage; +import org.openmuc.jmbus.DataRecord; +import org.openmuc.jmbus.DlmsUnit; + +import com.google.common.collect.ImmutableMap; + +/** + * Test of generic handler and its behavior upon receipt of new wmbus frame. + * + * @author Łukasz Dywicki - Initial contribution. + */ +@RunWith(MockitoJUnitRunner.class) +public class GenericWMBusThingHandlerTest { + + private static final String CHANNEL_VOLUME = "volume"; + private static final String DEVICE_ADDRESS = "68TCH100"; + private static final Map CONFIGURATION = ImmutableMap + .of(WMBusBindingConstants.PROPERTY_DEVICE_ADDRESS, DEVICE_ADDRESS); + + private static final RecordType VOLUME = new RecordType(0x0C, 0x14); + private static final Double VOLUME_VALUE = 10.0; + + private static final String THING_ID = "foobar"; + private static final String BRIDGE_ID = "usb0"; + private final static ThingUID THING_UID = new ThingUID(WMBusBindingConstants.THING_TYPE_METER, THING_ID); + private final static ChannelUID CHANNEL_UID = new ChannelUID(THING_UID, CHANNEL_VOLUME); + + private final static Map CHANNEL_MAPPING = ImmutableMap.of(CHANNEL_VOLUME, VOLUME); + + private final GenericWMBusThingHandler handler; + + @Mock + public WMBusBridgeHandler adapter; + + @Mock + private ThingHandlerCallback callback; + + public GenericWMBusThingHandlerTest() { + handler = new GenericWMBusThingHandler(createTestThing(), new MapKeyStorage(), + new CompositeUnitRegistry(), CHANNEL_MAPPING) { + @Override + protected org.openhab.core.thing.Bridge getBridge() { + return createTestBridge(adapter); + }; + }; + } + + @Before + public void setUp() { + handler.initialize(); + handler.setCallback(callback); + } + + @After + public void tearDown() { + handler.dispose(); + } + + @Test + public void testChannelUpdate() throws Exception { + WMBusDevice device = Mockito.mock(WMBusDevice.class); + DataRecord data = Mockito.mock(DataRecord.class); + + Mockito.when(device.getDeviceAddress()).thenReturn(DEVICE_ADDRESS); + Mockito.when(device.findRecord(VOLUME)).thenReturn(data); + Mockito.when(data.getUnit()).thenReturn(DlmsUnit.CUBIC_METRE); + Mockito.when(data.getScaledDataValue()).thenReturn(VOLUME_VALUE); + handler.onChangedWMBusDevice(adapter, device); + + ArgumentCaptor stateCapture = ArgumentCaptor.forClass(State.class); + Mockito.verify(callback).stateUpdated(ArgumentMatchers.eq(CHANNEL_UID), stateCapture.capture()); + + Assertions.assertThat(stateCapture.getAllValues()).hasSize(1); + + Assertions.assertThat(stateCapture.getAllValues().get(0)) + .isEqualTo(new QuantityType<>(VOLUME_VALUE, SIUnits.CUBIC_METRE)); + } + + public static Thing createTestThing() { + ThingBuilder thing = ThingBuilder.create(WMBusBindingConstants.THING_TYPE_METER, THING_ID); + thing.withConfiguration(new Configuration(CONFIGURATION)); + + Channel channel = ChannelBuilder.create(CHANNEL_UID, "DecimalType").build(); + thing.withChannel(channel); + + return thing.build(); + } + + static Bridge createTestBridge(ThingHandler handler) { + Bridge bridge = BridgeBuilder.create(WMBusBindingConstants.THING_TYPE_BRIDGE, BRIDGE_ID).build(); + bridge.setHandler(handler); + return bridge; + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/itron/ItronConfigStatusDataParserTest.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/itron/ItronConfigStatusDataParserTest.java index 2531c72..df8ffd4 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/itron/ItronConfigStatusDataParserTest.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/itron/ItronConfigStatusDataParserTest.java @@ -1,31 +1,31 @@ -package org.openhab.binding.wmbus.device.itron; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.Test; -import org.openhab.core.util.HexUtils; - -public class ItronConfigStatusDataParserTest { - - private ItronConfigStatusDataParser parser = new ItronConfigStatusDataParser( - HexUtils.hexToBytes("0C173E7E00208080")); - - @Test - public void testParser() { - assertThat(parser.getBillingDate()).isEqualTo(12); - - assertThat(parser.isRemovalOccurred()).isEqualTo(true); - - assertThat(parser.isProductInstalled()).isEqualTo(true); - - assertThat(parser.getOperationMode()).isEqualTo(1); - - assertThat(parser.isPerimeterIntrusionOccurred()).isEqualTo(true); - - assertThat(parser.isSmokeInletBlockedOccurred()).isEqualTo(false); - - assertThat(parser.isOutOfRangeTemperatureOccurred()).isEqualTo(false); - - assertThat(parser.getProductCode()).isEqualTo((byte) 0x3E); - } -} +package org.openhab.binding.wmbus.device.itron; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; +import org.openhab.core.util.HexUtils; + +public class ItronConfigStatusDataParserTest { + + private ItronConfigStatusDataParser parser = new ItronConfigStatusDataParser( + HexUtils.hexToBytes("0C173E7E00208080")); + + @Test + public void testParser() { + assertThat(parser.getBillingDate()).isEqualTo(12); + + assertThat(parser.isRemovalOccurred()).isEqualTo(true); + + assertThat(parser.isProductInstalled()).isEqualTo(true); + + assertThat(parser.getOperationMode()).isEqualTo(1); + + assertThat(parser.isPerimeterIntrusionOccurred()).isEqualTo(true); + + assertThat(parser.isSmokeInletBlockedOccurred()).isEqualTo(false); + + assertThat(parser.isOutOfRangeTemperatureOccurred()).isEqualTo(false); + + assertThat(parser.getProductCode()).isEqualTo((byte) 0x3E); + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/itron/ItronManufacturerDataParserTest.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/itron/ItronManufacturerDataParserTest.java index 164d8d0..7f5ad5c 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/itron/ItronManufacturerDataParserTest.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/itron/ItronManufacturerDataParserTest.java @@ -1,41 +1,41 @@ -package org.openhab.binding.wmbus.device.itron; - -import static org.assertj.core.api.Assertions.*; - -import java.time.LocalDateTime; - -import org.junit.Ignore; -import org.junit.Test; -import org.openhab.binding.wmbus.device.techem.decoder.Buffer; -import org.openhab.core.util.HexUtils; - -public class ItronManufacturerDataParserTest { - - public static final byte[] _2019_12_29_0313 = hex("0D 23 7D 2C"); - public static final byte[] _2020_01_09_1241 = hex("29 2C 89 21"); - public static final byte[] _2020_01_08_2121 = hex("15 35 88 21"); - public static final byte[] _2020_01_10_132057 = hex("39 14 AD 8A 21"); - - private static byte[] hex(String hexStr) { - return HexUtils.hexToBytes(hexStr.replace(" ", "")); - } - - @Test - public void testShortDate() { - assertThat(new ItronManufacturerDataParser(new Buffer(_2019_12_29_0313)).readShortDateTime()) - .isEqualTo(LocalDateTime.of(2019, 12, 29, 3, 13)); - - assertThat(new ItronManufacturerDataParser(new Buffer(_2020_01_09_1241)).readShortDateTime()) - .isEqualTo(LocalDateTime.of(2020, 1, 9, 12, 41)); - - assertThat(new ItronManufacturerDataParser(new Buffer(_2020_01_08_2121)).readShortDateTime()) - .isEqualTo(LocalDateTime.of(2020, 1, 8, 21, 21)); - } - - @Test - @Ignore // parsing logic is not yet done - public void testLongDate() { - assertThat(new ItronManufacturerDataParser(new Buffer(_2020_01_10_132057)).readLongDateTime()) - .isEqualTo(LocalDateTime.of(2020, 1, 10, 13, 20, 57)); - } -} +package org.openhab.binding.wmbus.device.itron; + +import static org.assertj.core.api.Assertions.*; + +import java.time.LocalDateTime; + +import org.junit.Ignore; +import org.junit.Test; +import org.openhab.binding.wmbus.device.techem.decoder.Buffer; +import org.openhab.core.util.HexUtils; + +public class ItronManufacturerDataParserTest { + + public static final byte[] _2019_12_29_0313 = hex("0D 23 7D 2C"); + public static final byte[] _2020_01_09_1241 = hex("29 2C 89 21"); + public static final byte[] _2020_01_08_2121 = hex("15 35 88 21"); + public static final byte[] _2020_01_10_132057 = hex("39 14 AD 8A 21"); + + private static byte[] hex(String hexStr) { + return HexUtils.hexToBytes(hexStr.replace(" ", "")); + } + + @Test + public void testShortDate() { + assertThat(new ItronManufacturerDataParser(new Buffer(_2019_12_29_0313)).readShortDateTime()) + .isEqualTo(LocalDateTime.of(2019, 12, 29, 3, 13)); + + assertThat(new ItronManufacturerDataParser(new Buffer(_2020_01_09_1241)).readShortDateTime()) + .isEqualTo(LocalDateTime.of(2020, 1, 9, 12, 41)); + + assertThat(new ItronManufacturerDataParser(new Buffer(_2020_01_08_2121)).readShortDateTime()) + .isEqualTo(LocalDateTime.of(2020, 1, 8, 21, 21)); + } + + @Test + @Ignore // parsing logic is not yet done + public void testLongDate() { + assertThat(new ItronManufacturerDataParser(new Buffer(_2020_01_10_132057)).readLongDateTime()) + .isEqualTo(LocalDateTime.of(2020, 1, 10, 13, 20, 57)); + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/TechemDecoderTest.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/TechemDecoderTest.java index caf98e0..06d109c 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/TechemDecoderTest.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/TechemDecoderTest.java @@ -1,233 +1,233 @@ -package org.openhab.binding.wmbus.device.techem; - -import java.time.LocalDate; -import java.util.function.Consumer; - -import javax.measure.Unit; - -import org.assertj.core.api.Assertions; -import org.assertj.core.api.Condition; -import org.junit.Test; -import org.openhab.binding.wmbus.device.AbstractWMBusTest; -import org.openhab.binding.wmbus.device.techem.Record.Type; -import org.openhab.binding.wmbus.device.techem.decoder.CompositeTechemFrameDecoder; -import org.openhab.binding.wmbus.device.techem.predicate.FloatPredicate; -import org.openhab.binding.wmbus.device.techem.predicate.IntegerPredicate; -import org.openhab.binding.wmbus.device.techem.predicate.LocalDatePredicate; -import org.openhab.binding.wmbus.device.techem.predicate.QuantityPredicate; -import org.openhab.binding.wmbus.device.techem.predicate.RssiPredicate; -import org.openhab.binding.wmbus.device.techem.predicate.StringPredicate; -import org.openhab.core.library.unit.SIUnits; -import org.openmuc.jmbus.DeviceType; - -import tec.uom.se.unit.Units; - -public class TechemDecoderTest extends AbstractWMBusTest { - - private final CompositeTechemFrameDecoder reader = new CompositeTechemFrameDecoder(); - - @Test - public void testWarmWater112Parser() throws Exception { - TechemDevice device = reader.decode(message(MESSAGE_112_WARM_WATER)); - - Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemWaterMeter.class, - expectedDevice(DeviceType.WARM_WATER_METER)); - Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH11298_6.getTechemType()); - - Assertions.assertThat(device.getMeasurements()).hasSize(8).areAtLeastOne(record(Record.Type.STATUS, 0)) - .areAtLeastOne(record(Record.Type.COUNTER, 3)) - .areAtLeastOne(record(Record.Type.ALMANAC, "4;4;3;3;3;3;3;3;2;3;3;3;3;3;3;3;4;2;4")) - .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 6.5, Units.CUBIC_METRE)) - .areAtLeastOne(record(Record.Type.PAST_VOLUME, 59.7, Units.CUBIC_METRE)).areAtLeastOne(rssi()); - } - - @Test - public void testColdWater112Parser() throws Exception { - TechemDevice device = reader.decode(message(MESSAGE_112_COLD_WATER)); - - Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemWaterMeter.class, - expectedDevice(DeviceType.COLD_WATER_METER)); - Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH112114_16.getTechemType()); - - Assertions.assertThat(device.getMeasurements()).hasSize(6).areAtLeastOne(record(Record.Type.STATUS, 0)) - .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 36.3, Units.CUBIC_METRE)) - .areAtLeastOne(record(Record.Type.PAST_VOLUME, 190.2, Units.CUBIC_METRE)).areAtLeastOne(rssi()); - } - - @Test - public void testWarmWater116Parser() throws Exception { - TechemDevice device = reader.decode(message(MESSAGE_116_WARM_WATER)); - - Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemWaterMeter.class, - expectedDevice(DeviceType.WARM_WATER_METER)); - Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH11698_6.getTechemType()); - - Assertions.assertThat(device.getMeasurements()).hasSize(8).areAtLeastOne(record(Record.Type.STATUS, 6)) - .areAtLeastOne(record(Record.Type.COUNTER, 0)) - .areAtLeastOne(record(Record.Type.ALMANAC, "1;1;1;1;0;1;1;0;0;1;0;0;1;1;2;2;1;2;2;1;2;1;1;2;2;1;230;0")) - .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 2.1, Units.CUBIC_METRE)) - .areAtLeastOne(record(Record.Type.PAST_VOLUME, 7.5, Units.CUBIC_METRE)).areAtLeastOne(rssi()); - } - - @Test - public void testColdWater116Parser() throws Exception { - TechemDevice device = reader.decode(message(MESSAGE_116_COLD_WATER)); - - Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemWaterMeter.class, - expectedDevice(DeviceType.COLD_WATER_METER)); - Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH116114_16.getTechemType()); - - Assertions.assertThat(device.getMeasurements()).hasSize(6).areAtLeastOne(record(Record.Type.STATUS, 6)) - .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 18.1, Units.CUBIC_METRE)) - .areAtLeastOne(record(Record.Type.PAST_VOLUME, 43.5, Units.CUBIC_METRE)).areAtLeastOne(rssi()); - } - - @Test - public void testHeat113Parser() throws Exception { - TechemDevice device = reader.decode(message(MESSAGE_113_HEAT)); - - Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemHeatMeter.class, - expectedDevice(DeviceType.HEAT_METER)); - Assertions.assertThat(device.getDeviceType()) - .isEqualTo(TechemBindingConstants._68TCH11367_4_A2.getTechemType()); - - Assertions.assertThat(device.getMeasurements()).hasSize(5) - .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 1769472.0)) - .areAtLeastOne(record(Record.Type.PAST_VOLUME, 8913920.0)).areAtLeastOne(rssi()); - } - - @Test - public void testHKV45() throws Exception { - TechemDevice device = reader.decode(message(MESSAGE_69_HKV)); - - Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemHeatCostAllocator.class, - expectedDevice(DeviceType.HEAT_COST_ALLOCATOR)); - Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH6967_8.getTechemType()); - - Assertions.assertThat(device.getMeasurements()).hasSize(7).areAtLeastOne(record(Record.Type.STATUS, 0)) - .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 5240.0)) - .areAtLeastOne(record(Record.Type.PAST_VOLUME, 18727.0)).areAtLeastOne(rssi()); - } - - @Test - public void testHKV6480() throws Exception { - TechemDevice device = reader.decode(message(MESSAGE_100_HKV)); - - Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemHeatCostAllocator.class, - expectedDevice(DeviceType.HEAT_COST_ALLOCATOR)); - Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH100128_8.getTechemType()); - - Assertions.assertThat(device.getMeasurements()).hasSize(7).areAtLeastOne(record(Record.Type.STATUS, 17)) - .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 65.0)) - .areAtLeastOne(record(Record.Type.PAST_VOLUME, 104.0)).areAtLeastOne(rssi()); - } - - @Test - public void testHKV6980() throws Exception { - TechemDevice device = reader.decode(message(MESSAGE_105_HKV)); - - Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemHeatCostAllocator.class, - expectedDevice(DeviceType.HEAT_COST_ALLOCATOR)); - Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH105128_8.getTechemType()); - - Assertions.assertThat(device.getMeasurements()).hasSize(10).areAtLeastOne(record(Record.Type.STATUS, 17)) - .areAtLeastOne(record(Record.Type.COUNTER, 16)) - .areAtLeastOne(record(Record.Type.ALMANAC, - "1;0;0;2;0;0;0;0;0;0;0;0;0;0;17;11;0;32;54;29;34;31;110;146;135;73;0")) - .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 410.0)) - .areAtLeastOne(record(Record.Type.PAST_VOLUME, 1999.0)) - .areAtLeastOne(record(Record.Type.ROOM_TEMPERATURE, 21.52, SIUnits.CELSIUS)) - .areAtLeastOne(record(Record.Type.RADIATOR_TEMPERATURE, 23.73, SIUnits.CELSIUS)).areAtLeastOne(rssi()); - } - - @Test - public void testHKV148() throws Exception { - TechemDevice device = reader.decode(message(MESSAGE_148_HKV)); - - Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemHeatCostAllocator.class, - expectedDevice(DeviceType.HEAT_COST_ALLOCATOR)); - Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH148128_8.getTechemType()); - - Assertions.assertThat(device.getMeasurements()).hasSize(10).areAtLeastOne(record(Record.Type.STATUS, 15)) - .areAtLeastOne(record(Record.Type.COUNTER, 0)) - .areAtLeastOne(record(Record.Type.ALMANAC, - "0;0;0;0;0;0;7;31;36;30;69;78;66;88;132;120;119;94;79;46;20;0;0;0;0;219;0")) - .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 258.0)) - .areAtLeastOne(record(Record.Type.PAST_VOLUME, 412.0)) - .areAtLeastOne(record(Record.Type.ROOM_TEMPERATURE, 26.48, SIUnits.CELSIUS)) - .areAtLeastOne(record(Record.Type.RADIATOR_TEMPERATURE, 26.31, SIUnits.CELSIUS)).areAtLeastOne(rssi()); - } - - @Test - public void testSD76F0() throws Exception { - TechemDevice device = reader.decode(message(MESSAGE_118_SD_3)); - - Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemSmokeDetector.class, - expectedDevice(DeviceType.SMOKE_DETECTOR)); - Assertions.assertThat(device.getDeviceType()) - .isEqualTo(TechemBindingConstants._68TCH118255_161_A0.getTechemType()); - - Assertions.assertThat(device.getMeasurements()).hasSize(4).areAtLeastOne(record(Type.STATUS, 0)) - .areAtLeastOne(record(Type.CURRENT_READING_DATE, LocalDate.of(LocalDate.now().getYear(), 3, 23))) - .areAtLeastOne(record(Type.CURRENT_READING_DATE_SMOKE, LocalDate.of(2019, 11, 27))) - .areAtLeastOne(rssi()); - } - - @Test - public void testSD76F0_extra() throws Exception { - // just another test frame - TechemDevice device = reader.decode(message(MESSAGE_118_SD_2)); - - Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemSmokeDetector.class, - expectedDevice(DeviceType.SMOKE_DETECTOR)); - Assertions.assertThat(device.getDeviceType()) - .isEqualTo(TechemBindingConstants._68TCH118255_161_A0.getTechemType()); - - Assertions.assertThat(device.getMeasurements()).hasSize(4).areAtLeastOne(record(Type.STATUS, 0)) - .areAtLeastOne(record(Type.CURRENT_READING_DATE, LocalDate.of(LocalDate.now().getYear(), 2, 22))) - .areAtLeastOne(record(Type.CURRENT_READING_DATE_SMOKE, LocalDate.of(2018, 11, 15))) - .areAtLeastOne(rssi()); - } - - private Condition> record(Type type, String expectedValue) { - StringPredicate predicate = new StringPredicate(type, expectedValue); - - return new Condition<>(predicate, predicate.description(), predicate.arguments()); - } - - private Condition> record(Type type, LocalDate expectedValue) { - LocalDatePredicate predicate = new LocalDatePredicate(type, expectedValue); - - return new Condition<>(predicate, predicate.description(), predicate.arguments()); - } - - private Condition> record(Type type, int expectedValue) { - IntegerPredicate predicate = new IntegerPredicate(type, expectedValue); - - return new Condition<>(predicate, predicate.description(), predicate.arguments()); - } - - private Condition> record(Type type, double expectedValue) { - FloatPredicate predicate = new FloatPredicate(type, expectedValue); - - return new Condition<>(predicate, predicate.description(), predicate.arguments()); - } - - private Condition> record(Type type, double expectedValue, Unit unit) { - QuantityPredicate predicate = new QuantityPredicate(type, expectedValue, unit); - - return new Condition<>(predicate, predicate.description(), predicate.arguments()); - } - - private Condition> rssi() { - RssiPredicate predicate = new RssiPredicate(RSSI); - - return new Condition<>(predicate, predicate.description(), predicate.arguments()); - } - - private Consumer expectedDevice(DeviceType deviceType) { - return device -> { - Assertions.assertThat(device.getTechemDeviceType()).isEqualTo(deviceType); - }; - } -} +package org.openhab.binding.wmbus.device.techem; + +import java.time.LocalDate; +import java.util.function.Consumer; + +import javax.measure.Unit; + +import org.assertj.core.api.Assertions; +import org.assertj.core.api.Condition; +import org.junit.Test; +import org.openhab.binding.wmbus.device.AbstractWMBusTest; +import org.openhab.binding.wmbus.device.techem.Record.Type; +import org.openhab.binding.wmbus.device.techem.decoder.CompositeTechemFrameDecoder; +import org.openhab.binding.wmbus.device.techem.predicate.FloatPredicate; +import org.openhab.binding.wmbus.device.techem.predicate.IntegerPredicate; +import org.openhab.binding.wmbus.device.techem.predicate.LocalDatePredicate; +import org.openhab.binding.wmbus.device.techem.predicate.QuantityPredicate; +import org.openhab.binding.wmbus.device.techem.predicate.RssiPredicate; +import org.openhab.binding.wmbus.device.techem.predicate.StringPredicate; +import org.openhab.core.library.unit.SIUnits; +import org.openmuc.jmbus.DeviceType; + +import tec.uom.se.unit.Units; + +public class TechemDecoderTest extends AbstractWMBusTest { + + private final CompositeTechemFrameDecoder reader = new CompositeTechemFrameDecoder(); + + @Test + public void testWarmWater112Parser() throws Exception { + TechemDevice device = reader.decode(message(MESSAGE_112_WARM_WATER)); + + Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemWaterMeter.class, + expectedDevice(DeviceType.WARM_WATER_METER)); + Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH11298_6.getTechemType()); + + Assertions.assertThat(device.getMeasurements()).hasSize(8).areAtLeastOne(record(Record.Type.STATUS, 0)) + .areAtLeastOne(record(Record.Type.COUNTER, 3)) + .areAtLeastOne(record(Record.Type.ALMANAC, "4;4;3;3;3;3;3;3;2;3;3;3;3;3;3;3;4;2;4")) + .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 6.5, Units.CUBIC_METRE)) + .areAtLeastOne(record(Record.Type.PAST_VOLUME, 59.7, Units.CUBIC_METRE)).areAtLeastOne(rssi()); + } + + @Test + public void testColdWater112Parser() throws Exception { + TechemDevice device = reader.decode(message(MESSAGE_112_COLD_WATER)); + + Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemWaterMeter.class, + expectedDevice(DeviceType.COLD_WATER_METER)); + Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH112114_16.getTechemType()); + + Assertions.assertThat(device.getMeasurements()).hasSize(6).areAtLeastOne(record(Record.Type.STATUS, 0)) + .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 36.3, Units.CUBIC_METRE)) + .areAtLeastOne(record(Record.Type.PAST_VOLUME, 190.2, Units.CUBIC_METRE)).areAtLeastOne(rssi()); + } + + @Test + public void testWarmWater116Parser() throws Exception { + TechemDevice device = reader.decode(message(MESSAGE_116_WARM_WATER)); + + Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemWaterMeter.class, + expectedDevice(DeviceType.WARM_WATER_METER)); + Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH11698_6.getTechemType()); + + Assertions.assertThat(device.getMeasurements()).hasSize(8).areAtLeastOne(record(Record.Type.STATUS, 6)) + .areAtLeastOne(record(Record.Type.COUNTER, 0)) + .areAtLeastOne(record(Record.Type.ALMANAC, "1;1;1;1;0;1;1;0;0;1;0;0;1;1;2;2;1;2;2;1;2;1;1;2;2;1;230;0")) + .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 2.1, Units.CUBIC_METRE)) + .areAtLeastOne(record(Record.Type.PAST_VOLUME, 7.5, Units.CUBIC_METRE)).areAtLeastOne(rssi()); + } + + @Test + public void testColdWater116Parser() throws Exception { + TechemDevice device = reader.decode(message(MESSAGE_116_COLD_WATER)); + + Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemWaterMeter.class, + expectedDevice(DeviceType.COLD_WATER_METER)); + Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH116114_16.getTechemType()); + + Assertions.assertThat(device.getMeasurements()).hasSize(6).areAtLeastOne(record(Record.Type.STATUS, 6)) + .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 18.1, Units.CUBIC_METRE)) + .areAtLeastOne(record(Record.Type.PAST_VOLUME, 43.5, Units.CUBIC_METRE)).areAtLeastOne(rssi()); + } + + @Test + public void testHeat113Parser() throws Exception { + TechemDevice device = reader.decode(message(MESSAGE_113_HEAT)); + + Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemHeatMeter.class, + expectedDevice(DeviceType.HEAT_METER)); + Assertions.assertThat(device.getDeviceType()) + .isEqualTo(TechemBindingConstants._68TCH11367_4_A2.getTechemType()); + + Assertions.assertThat(device.getMeasurements()).hasSize(5) + .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 1769472.0)) + .areAtLeastOne(record(Record.Type.PAST_VOLUME, 8913920.0)).areAtLeastOne(rssi()); + } + + @Test + public void testHKV45() throws Exception { + TechemDevice device = reader.decode(message(MESSAGE_69_HKV)); + + Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemHeatCostAllocator.class, + expectedDevice(DeviceType.HEAT_COST_ALLOCATOR)); + Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH6967_8.getTechemType()); + + Assertions.assertThat(device.getMeasurements()).hasSize(7).areAtLeastOne(record(Record.Type.STATUS, 0)) + .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 5240.0)) + .areAtLeastOne(record(Record.Type.PAST_VOLUME, 18727.0)).areAtLeastOne(rssi()); + } + + @Test + public void testHKV6480() throws Exception { + TechemDevice device = reader.decode(message(MESSAGE_100_HKV)); + + Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemHeatCostAllocator.class, + expectedDevice(DeviceType.HEAT_COST_ALLOCATOR)); + Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH100128_8.getTechemType()); + + Assertions.assertThat(device.getMeasurements()).hasSize(7).areAtLeastOne(record(Record.Type.STATUS, 17)) + .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 65.0)) + .areAtLeastOne(record(Record.Type.PAST_VOLUME, 104.0)).areAtLeastOne(rssi()); + } + + @Test + public void testHKV6980() throws Exception { + TechemDevice device = reader.decode(message(MESSAGE_105_HKV)); + + Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemHeatCostAllocator.class, + expectedDevice(DeviceType.HEAT_COST_ALLOCATOR)); + Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH105128_8.getTechemType()); + + Assertions.assertThat(device.getMeasurements()).hasSize(10).areAtLeastOne(record(Record.Type.STATUS, 17)) + .areAtLeastOne(record(Record.Type.COUNTER, 16)) + .areAtLeastOne(record(Record.Type.ALMANAC, + "1;0;0;2;0;0;0;0;0;0;0;0;0;0;17;11;0;32;54;29;34;31;110;146;135;73;0")) + .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 410.0)) + .areAtLeastOne(record(Record.Type.PAST_VOLUME, 1999.0)) + .areAtLeastOne(record(Record.Type.ROOM_TEMPERATURE, 21.52, SIUnits.CELSIUS)) + .areAtLeastOne(record(Record.Type.RADIATOR_TEMPERATURE, 23.73, SIUnits.CELSIUS)).areAtLeastOne(rssi()); + } + + @Test + public void testHKV148() throws Exception { + TechemDevice device = reader.decode(message(MESSAGE_148_HKV)); + + Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemHeatCostAllocator.class, + expectedDevice(DeviceType.HEAT_COST_ALLOCATOR)); + Assertions.assertThat(device.getDeviceType()).isEqualTo(TechemBindingConstants._68TCH148128_8.getTechemType()); + + Assertions.assertThat(device.getMeasurements()).hasSize(10).areAtLeastOne(record(Record.Type.STATUS, 15)) + .areAtLeastOne(record(Record.Type.COUNTER, 0)) + .areAtLeastOne(record(Record.Type.ALMANAC, + "0;0;0;0;0;0;7;31;36;30;69;78;66;88;132;120;119;94;79;46;20;0;0;0;0;219;0")) + .areAtLeastOne(record(Record.Type.CURRENT_VOLUME, 258.0)) + .areAtLeastOne(record(Record.Type.PAST_VOLUME, 412.0)) + .areAtLeastOne(record(Record.Type.ROOM_TEMPERATURE, 26.48, SIUnits.CELSIUS)) + .areAtLeastOne(record(Record.Type.RADIATOR_TEMPERATURE, 26.31, SIUnits.CELSIUS)).areAtLeastOne(rssi()); + } + + @Test + public void testSD76F0() throws Exception { + TechemDevice device = reader.decode(message(MESSAGE_118_SD_3)); + + Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemSmokeDetector.class, + expectedDevice(DeviceType.SMOKE_DETECTOR)); + Assertions.assertThat(device.getDeviceType()) + .isEqualTo(TechemBindingConstants._68TCH118255_161_A0.getTechemType()); + + Assertions.assertThat(device.getMeasurements()).hasSize(4).areAtLeastOne(record(Type.STATUS, 0)) + .areAtLeastOne(record(Type.CURRENT_READING_DATE, LocalDate.of(LocalDate.now().getYear(), 3, 23))) + .areAtLeastOne(record(Type.CURRENT_READING_DATE_SMOKE, LocalDate.of(2019, 11, 27))) + .areAtLeastOne(rssi()); + } + + @Test + public void testSD76F0_extra() throws Exception { + // just another test frame + TechemDevice device = reader.decode(message(MESSAGE_118_SD_2)); + + Assertions.assertThat(device).isNotNull().isInstanceOfSatisfying(TechemSmokeDetector.class, + expectedDevice(DeviceType.SMOKE_DETECTOR)); + Assertions.assertThat(device.getDeviceType()) + .isEqualTo(TechemBindingConstants._68TCH118255_161_A0.getTechemType()); + + Assertions.assertThat(device.getMeasurements()).hasSize(4).areAtLeastOne(record(Type.STATUS, 0)) + .areAtLeastOne(record(Type.CURRENT_READING_DATE, LocalDate.of(LocalDate.now().getYear(), 2, 22))) + .areAtLeastOne(record(Type.CURRENT_READING_DATE_SMOKE, LocalDate.of(2018, 11, 15))) + .areAtLeastOne(rssi()); + } + + private Condition> record(Type type, String expectedValue) { + StringPredicate predicate = new StringPredicate(type, expectedValue); + + return new Condition<>(predicate, predicate.description(), predicate.arguments()); + } + + private Condition> record(Type type, LocalDate expectedValue) { + LocalDatePredicate predicate = new LocalDatePredicate(type, expectedValue); + + return new Condition<>(predicate, predicate.description(), predicate.arguments()); + } + + private Condition> record(Type type, int expectedValue) { + IntegerPredicate predicate = new IntegerPredicate(type, expectedValue); + + return new Condition<>(predicate, predicate.description(), predicate.arguments()); + } + + private Condition> record(Type type, double expectedValue) { + FloatPredicate predicate = new FloatPredicate(type, expectedValue); + + return new Condition<>(predicate, predicate.description(), predicate.arguments()); + } + + private Condition> record(Type type, double expectedValue, Unit unit) { + QuantityPredicate predicate = new QuantityPredicate(type, expectedValue, unit); + + return new Condition<>(predicate, predicate.description(), predicate.arguments()); + } + + private Condition> rssi() { + RssiPredicate predicate = new RssiPredicate(RSSI); + + return new Condition<>(predicate, predicate.description(), predicate.arguments()); + } + + private Consumer expectedDevice(DeviceType deviceType) { + return device -> { + Assertions.assertThat(device.getTechemDeviceType()).isEqualTo(deviceType); + }; + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/TechemDiscoveryTest.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/TechemDiscoveryTest.java index eec2b65..c739b48 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/TechemDiscoveryTest.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/TechemDiscoveryTest.java @@ -1,109 +1,109 @@ -package org.openhab.binding.wmbus.device.techem; - -import static org.mockito.Mockito.when; - -import org.assertj.core.api.Assertions; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import org.openhab.binding.wmbus.WMBusBindingConstants; -import org.openhab.binding.wmbus.device.AbstractWMBusTest; -import org.openhab.binding.wmbus.device.techem.decoder.CompositeTechemFrameDecoder; -import org.openhab.binding.wmbus.handler.WMBusAdapter; -import org.openhab.binding.wmbus.internal.DynamicBindingConfiguration; -import org.openhab.core.config.discovery.DiscoveryResult; -import org.openhab.core.thing.ThingUID; -import org.openmuc.jmbus.DecodingException; - -@RunWith(MockitoJUnitRunner.class) -public class TechemDiscoveryTest extends AbstractWMBusTest implements TechemBindingConstants { - - private final TechemDiscoveryParticipant discoverer = new TechemDiscoveryParticipant(); - - @Mock - private WMBusAdapter adapter; - - @Before - public void setUp() { - discoverer.setTechemFrameDecoder(new CompositeTechemFrameDecoder()); - discoverer.setBindingConfiguration(new DynamicBindingConfiguration()); - } - - @Test - public void testWZ7062Support() throws Exception { - DiscoveryResult result = result(MESSAGE_112_WARM_WATER); - Assertions.assertThat(result).isNotNull(); - Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_WARM_WATER_METER); - } - - @Test - public void testWZ7072Support() throws Exception { - DiscoveryResult result = result(MESSAGE_112_COLD_WATER); - Assertions.assertThat(result).isNotNull(); - Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_COLD_WATER_METER); - } - - @Test - public void testWZ7462Support() throws Exception { - DiscoveryResult result = result(MESSAGE_116_WARM_WATER); - Assertions.assertThat(result).isNotNull(); - Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_WARM_WATER_METER); - } - - @Test - public void testWZ7472Support() throws Exception { - DiscoveryResult result = result(MESSAGE_116_COLD_WATER); - Assertions.assertThat(result).isNotNull(); - Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_COLD_WATER_METER); - } - - @Test - public void testWMZ43Support() throws Exception { - DiscoveryResult result = result(MESSAGE_113_HEAT); - Assertions.assertThat(result).isNotNull(); - Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_HEAT_METER); - } - - @Test - public void testHKV4543Support() throws Exception { - DiscoveryResult result = result(MESSAGE_69_HKV); - Assertions.assertThat(result).isNotNull(); - Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_HKV45); - } - - @Test - public void testHKV6480Support() throws Exception { - DiscoveryResult result = result(MESSAGE_100_HKV); - Assertions.assertThat(result).isNotNull(); - Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_HKV64); - } - - @Test - public void testHKV6980Support() throws Exception { - DiscoveryResult result = result(MESSAGE_105_HKV); - Assertions.assertThat(result).isNotNull(); - Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_HKV69); - } - - @Test - public void testHKV9480Support() throws Exception { - DiscoveryResult result = result(MESSAGE_148_HKV); - Assertions.assertThat(result).isNotNull(); - Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_HKV94); - } - - @Test - public void testSD76Support() throws Exception { - DiscoveryResult result = result(MESSAGE_118_SD_1); - Assertions.assertThat(result).isNotNull(); - Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_SD76); - } - - private DiscoveryResult result(String message) throws DecodingException { - when(adapter.getUID()).thenReturn(new ThingUID(WMBusBindingConstants.THING_TYPE_BRIDGE, "bridge0")); - - return discoverer.createResult(message(message, adapter)); - } -} +package org.openhab.binding.wmbus.device.techem; + +import static org.mockito.Mockito.when; + +import org.assertj.core.api.Assertions; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.openhab.binding.wmbus.WMBusBindingConstants; +import org.openhab.binding.wmbus.device.AbstractWMBusTest; +import org.openhab.binding.wmbus.device.techem.decoder.CompositeTechemFrameDecoder; +import org.openhab.binding.wmbus.handler.WMBusAdapter; +import org.openhab.binding.wmbus.internal.DynamicBindingConfiguration; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.thing.ThingUID; +import org.openmuc.jmbus.DecodingException; + +@RunWith(MockitoJUnitRunner.class) +public class TechemDiscoveryTest extends AbstractWMBusTest implements TechemBindingConstants { + + private final TechemDiscoveryParticipant discoverer = new TechemDiscoveryParticipant(); + + @Mock + private WMBusAdapter adapter; + + @Before + public void setUp() { + discoverer.setTechemFrameDecoder(new CompositeTechemFrameDecoder()); + discoverer.setBindingConfiguration(new DynamicBindingConfiguration()); + } + + @Test + public void testWZ7062Support() throws Exception { + DiscoveryResult result = result(MESSAGE_112_WARM_WATER); + Assertions.assertThat(result).isNotNull(); + Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_WARM_WATER_METER); + } + + @Test + public void testWZ7072Support() throws Exception { + DiscoveryResult result = result(MESSAGE_112_COLD_WATER); + Assertions.assertThat(result).isNotNull(); + Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_COLD_WATER_METER); + } + + @Test + public void testWZ7462Support() throws Exception { + DiscoveryResult result = result(MESSAGE_116_WARM_WATER); + Assertions.assertThat(result).isNotNull(); + Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_WARM_WATER_METER); + } + + @Test + public void testWZ7472Support() throws Exception { + DiscoveryResult result = result(MESSAGE_116_COLD_WATER); + Assertions.assertThat(result).isNotNull(); + Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_COLD_WATER_METER); + } + + @Test + public void testWMZ43Support() throws Exception { + DiscoveryResult result = result(MESSAGE_113_HEAT); + Assertions.assertThat(result).isNotNull(); + Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_HEAT_METER); + } + + @Test + public void testHKV4543Support() throws Exception { + DiscoveryResult result = result(MESSAGE_69_HKV); + Assertions.assertThat(result).isNotNull(); + Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_HKV45); + } + + @Test + public void testHKV6480Support() throws Exception { + DiscoveryResult result = result(MESSAGE_100_HKV); + Assertions.assertThat(result).isNotNull(); + Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_HKV64); + } + + @Test + public void testHKV6980Support() throws Exception { + DiscoveryResult result = result(MESSAGE_105_HKV); + Assertions.assertThat(result).isNotNull(); + Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_HKV69); + } + + @Test + public void testHKV9480Support() throws Exception { + DiscoveryResult result = result(MESSAGE_148_HKV); + Assertions.assertThat(result).isNotNull(); + Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_HKV94); + } + + @Test + public void testSD76Support() throws Exception { + DiscoveryResult result = result(MESSAGE_118_SD_1); + Assertions.assertThat(result).isNotNull(); + Assertions.assertThat(result.getThingTypeUID()).isEqualTo(THING_TYPE_TECHEM_SD76); + } + + private DiscoveryResult result(String message) throws DecodingException { + when(adapter.getUID()).thenReturn(new ThingUID(WMBusBindingConstants.THING_TYPE_BRIDGE, "bridge0")); + + return discoverer.createResult(message(message, adapter)); + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/TechemHandlerFactoryTest.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/TechemHandlerFactoryTest.java index 22c578d..738e9b2 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/TechemHandlerFactoryTest.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/TechemHandlerFactoryTest.java @@ -1,74 +1,74 @@ -package org.openhab.binding.wmbus.device.techem; - -import org.assertj.core.api.Assertions; -import org.junit.Before; -import org.junit.Test; -import org.openhab.binding.wmbus.device.techem.decoder.CompositeTechemFrameDecoder; -import org.openhab.core.thing.binding.ThingHandler; -import org.openhab.core.thing.internal.ThingImpl; - -public class TechemHandlerFactoryTest implements TechemBindingConstants { - - private final TechemHandlerFactory handlerFactory = new TechemHandlerFactory(); - - @Before - public void setUp() { - handlerFactory.setTechemFrameDecoder(new CompositeTechemFrameDecoder()); - } - - @Test - public void testHKV61Support() { - ThingImpl thing = new ThingImpl(THING_TYPE_TECHEM_HKV61, "00000"); - - ThingHandler handler = handlerFactory.createHandler(thing); - Assertions.assertThat(handler).isNotNull(); - } - - @Test - public void testHKV64Support() { - ThingImpl thing = new ThingImpl(THING_TYPE_TECHEM_HKV64, "00000"); - - ThingHandler handler = handlerFactory.createHandler(thing); - Assertions.assertThat(handler).isNotNull(); - } - - @Test - public void testHKV69Support() { - ThingImpl thing = new ThingImpl(THING_TYPE_TECHEM_HKV69, "00000"); - - ThingHandler handler = handlerFactory.createHandler(thing); - Assertions.assertThat(handler).isNotNull(); - } - - @Test - public void testSD76Support() { - ThingImpl thing = new ThingImpl(THING_TYPE_TECHEM_SD76, "00000"); - - ThingHandler handler = handlerFactory.createHandler(thing); - Assertions.assertThat(handler).isNotNull(); - } - - @Test - public void testWZ62Support() { - ThingImpl thing = new ThingImpl(THING_TYPE_TECHEM_WARM_WATER_METER, "00000"); - - ThingHandler handler = handlerFactory.createHandler(thing); - Assertions.assertThat(handler).isNotNull(); - } - - @Test - public void testWZ72Support() { - ThingImpl thing = new ThingImpl(THING_TYPE_TECHEM_COLD_WATER_METER, "00000"); - - ThingHandler handler = handlerFactory.createHandler(thing); - Assertions.assertThat(handler).isNotNull(); - } - - @Test - public void testWZ43Support() { - ThingImpl thing = new ThingImpl(THING_TYPE_TECHEM_HEAT_METER, "00000"); - - ThingHandler handler = handlerFactory.createHandler(thing); - Assertions.assertThat(handler).isNotNull(); - } -} +package org.openhab.binding.wmbus.device.techem; + +import org.assertj.core.api.Assertions; +import org.junit.Before; +import org.junit.Test; +import org.openhab.binding.wmbus.device.techem.decoder.CompositeTechemFrameDecoder; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.internal.ThingImpl; + +public class TechemHandlerFactoryTest implements TechemBindingConstants { + + private final TechemHandlerFactory handlerFactory = new TechemHandlerFactory(); + + @Before + public void setUp() { + handlerFactory.setTechemFrameDecoder(new CompositeTechemFrameDecoder()); + } + + @Test + public void testHKV61Support() { + ThingImpl thing = new ThingImpl(THING_TYPE_TECHEM_HKV61, "00000"); + + ThingHandler handler = handlerFactory.createHandler(thing); + Assertions.assertThat(handler).isNotNull(); + } + + @Test + public void testHKV64Support() { + ThingImpl thing = new ThingImpl(THING_TYPE_TECHEM_HKV64, "00000"); + + ThingHandler handler = handlerFactory.createHandler(thing); + Assertions.assertThat(handler).isNotNull(); + } + + @Test + public void testHKV69Support() { + ThingImpl thing = new ThingImpl(THING_TYPE_TECHEM_HKV69, "00000"); + + ThingHandler handler = handlerFactory.createHandler(thing); + Assertions.assertThat(handler).isNotNull(); + } + + @Test + public void testSD76Support() { + ThingImpl thing = new ThingImpl(THING_TYPE_TECHEM_SD76, "00000"); + + ThingHandler handler = handlerFactory.createHandler(thing); + Assertions.assertThat(handler).isNotNull(); + } + + @Test + public void testWZ62Support() { + ThingImpl thing = new ThingImpl(THING_TYPE_TECHEM_WARM_WATER_METER, "00000"); + + ThingHandler handler = handlerFactory.createHandler(thing); + Assertions.assertThat(handler).isNotNull(); + } + + @Test + public void testWZ72Support() { + ThingImpl thing = new ThingImpl(THING_TYPE_TECHEM_COLD_WATER_METER, "00000"); + + ThingHandler handler = handlerFactory.createHandler(thing); + Assertions.assertThat(handler).isNotNull(); + } + + @Test + public void testWZ43Support() { + ThingImpl thing = new ThingImpl(THING_TYPE_TECHEM_HEAT_METER, "00000"); + + ThingHandler handler = handlerFactory.createHandler(thing); + Assertions.assertThat(handler).isNotNull(); + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/FloatPredicate.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/FloatPredicate.java index d13661a..c815400 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/FloatPredicate.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/FloatPredicate.java @@ -1,40 +1,40 @@ -package org.openhab.binding.wmbus.device.techem.predicate; - -import org.assertj.core.api.Assertions; -import org.openhab.binding.wmbus.device.RecordPredicate; -import org.openhab.binding.wmbus.device.techem.Record; - -public class FloatPredicate implements RecordPredicate { - - protected final Record.Type type; - protected final float expectedValue; - - public FloatPredicate(Record.Type type, Double expectedValue) { - this.type = type; - this.expectedValue = expectedValue.floatValue(); - } - - @Override - public boolean test(Record record) { - try { - Assertions.assertThat(record.getType()).isEqualTo(type); - - testValue(record.getValue()); - } catch (AssertionError e) { - return false; - } - return true; - } - - protected void testValue(Object value) { - Assertions.assertThat(value).isInstanceOf(Float.class).isEqualTo(expectedValue); - } - - public String description() { - return "record of type %s, with value %f"; - } - - public Object[] arguments() { - return new Object[] { type, expectedValue }; - } -} +package org.openhab.binding.wmbus.device.techem.predicate; + +import org.assertj.core.api.Assertions; +import org.openhab.binding.wmbus.device.RecordPredicate; +import org.openhab.binding.wmbus.device.techem.Record; + +public class FloatPredicate implements RecordPredicate { + + protected final Record.Type type; + protected final float expectedValue; + + public FloatPredicate(Record.Type type, Double expectedValue) { + this.type = type; + this.expectedValue = expectedValue.floatValue(); + } + + @Override + public boolean test(Record record) { + try { + Assertions.assertThat(record.getType()).isEqualTo(type); + + testValue(record.getValue()); + } catch (AssertionError e) { + return false; + } + return true; + } + + protected void testValue(Object value) { + Assertions.assertThat(value).isInstanceOf(Float.class).isEqualTo(expectedValue); + } + + public String description() { + return "record of type %s, with value %f"; + } + + public Object[] arguments() { + return new Object[] { type, expectedValue }; + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/IntegerPredicate.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/IntegerPredicate.java index 8417783..71bdd7e 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/IntegerPredicate.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/IntegerPredicate.java @@ -1,41 +1,41 @@ -package org.openhab.binding.wmbus.device.techem.predicate; - -import java.util.function.Predicate; - -import org.assertj.core.api.Assertions; -import org.openhab.binding.wmbus.device.techem.Record; - -public class IntegerPredicate implements Predicate> { - - protected final Record.Type type; - protected final int expectedValue; - - public IntegerPredicate(Record.Type type, int expectedValue) { - this.type = type; - this.expectedValue = expectedValue; - } - - @Override - public boolean test(Record record) { - try { - Assertions.assertThat(record.getType()).isEqualTo(type); - - testValue(record.getValue()); - } catch (AssertionError e) { - return false; - } - return true; - } - - protected void testValue(Object value) { - Assertions.assertThat(value).isInstanceOf(Integer.class).isEqualTo(expectedValue); - } - - public String description() { - return "record of type %s, with value %d"; - } - - public Object[] arguments() { - return new Object[] { type, expectedValue }; - } -} +package org.openhab.binding.wmbus.device.techem.predicate; + +import java.util.function.Predicate; + +import org.assertj.core.api.Assertions; +import org.openhab.binding.wmbus.device.techem.Record; + +public class IntegerPredicate implements Predicate> { + + protected final Record.Type type; + protected final int expectedValue; + + public IntegerPredicate(Record.Type type, int expectedValue) { + this.type = type; + this.expectedValue = expectedValue; + } + + @Override + public boolean test(Record record) { + try { + Assertions.assertThat(record.getType()).isEqualTo(type); + + testValue(record.getValue()); + } catch (AssertionError e) { + return false; + } + return true; + } + + protected void testValue(Object value) { + Assertions.assertThat(value).isInstanceOf(Integer.class).isEqualTo(expectedValue); + } + + public String description() { + return "record of type %s, with value %d"; + } + + public Object[] arguments() { + return new Object[] { type, expectedValue }; + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/LocalDatePredicate.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/LocalDatePredicate.java index b8fb0c6..60d9202 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/LocalDatePredicate.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/LocalDatePredicate.java @@ -1,46 +1,46 @@ -package org.openhab.binding.wmbus.device.techem.predicate; - -import java.time.LocalDate; -import java.time.LocalDateTime; - -import org.assertj.core.api.Assertions; -import org.openhab.binding.wmbus.device.RecordPredicate; -import org.openhab.binding.wmbus.device.techem.Record; - -public class LocalDatePredicate implements RecordPredicate { - - protected final Record.Type type; - protected final LocalDate expectedValue; - - public LocalDatePredicate(Record.Type type, LocalDate expectedValue) { - this.type = type; - this.expectedValue = expectedValue; - } - - @Override - public boolean test(Record record) { - try { - Assertions.assertThat(record.getType()).isEqualTo(type); - - testValue(record.getValue()); - } catch (AssertionError e) { - return false; - } - return true; - } - - protected void testValue(Object value) { - Assertions.assertThat(value).isInstanceOf(LocalDateTime.class); - LocalDate date = ((LocalDateTime) value).toLocalDate(); - - Assertions.assertThat(date).isEqualTo(expectedValue); - } - - public String description() { - return "record of type %s, with value %s"; - } - - public Object[] arguments() { - return new Object[] { type, expectedValue }; - } -} +package org.openhab.binding.wmbus.device.techem.predicate; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import org.assertj.core.api.Assertions; +import org.openhab.binding.wmbus.device.RecordPredicate; +import org.openhab.binding.wmbus.device.techem.Record; + +public class LocalDatePredicate implements RecordPredicate { + + protected final Record.Type type; + protected final LocalDate expectedValue; + + public LocalDatePredicate(Record.Type type, LocalDate expectedValue) { + this.type = type; + this.expectedValue = expectedValue; + } + + @Override + public boolean test(Record record) { + try { + Assertions.assertThat(record.getType()).isEqualTo(type); + + testValue(record.getValue()); + } catch (AssertionError e) { + return false; + } + return true; + } + + protected void testValue(Object value) { + Assertions.assertThat(value).isInstanceOf(LocalDateTime.class); + LocalDate date = ((LocalDateTime) value).toLocalDate(); + + Assertions.assertThat(date).isEqualTo(expectedValue); + } + + public String description() { + return "record of type %s, with value %s"; + } + + public Object[] arguments() { + return new Object[] { type, expectedValue }; + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/QuantityPredicate.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/QuantityPredicate.java index a1d84f2..f6a1b68 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/QuantityPredicate.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/QuantityPredicate.java @@ -1,36 +1,36 @@ -package org.openhab.binding.wmbus.device.techem.predicate; - -import javax.measure.Quantity; -import javax.measure.Unit; - -import org.assertj.core.api.Assertions; -import org.openhab.binding.wmbus.device.techem.Record; - -public class QuantityPredicate extends FloatPredicate { - - private final Unit unit; - - public QuantityPredicate(Record.Type type, double expectedValue, Unit unit) { - super(type, expectedValue); - this.unit = unit; - } - - @Override - protected void testValue(Object value) { - Assertions.assertThat(value).isInstanceOf(Quantity.class); - - Quantity quantity = (Quantity) value; - Assertions.assertThat(quantity.getValue().floatValue()).isEqualTo(Double.valueOf(expectedValue).floatValue()); - Assertions.assertThat(quantity.getUnit()).isEqualTo(unit); - } - - @Override - public String description() { - return "record of type %s, with value %f in %s"; - } - - @Override - public Object[] arguments() { - return new Object[] { type, expectedValue, unit }; - } -} +package org.openhab.binding.wmbus.device.techem.predicate; + +import javax.measure.Quantity; +import javax.measure.Unit; + +import org.assertj.core.api.Assertions; +import org.openhab.binding.wmbus.device.techem.Record; + +public class QuantityPredicate extends FloatPredicate { + + private final Unit unit; + + public QuantityPredicate(Record.Type type, double expectedValue, Unit unit) { + super(type, expectedValue); + this.unit = unit; + } + + @Override + protected void testValue(Object value) { + Assertions.assertThat(value).isInstanceOf(Quantity.class); + + Quantity quantity = (Quantity) value; + Assertions.assertThat(quantity.getValue().floatValue()).isEqualTo(Double.valueOf(expectedValue).floatValue()); + Assertions.assertThat(quantity.getUnit()).isEqualTo(unit); + } + + @Override + public String description() { + return "record of type %s, with value %f in %s"; + } + + @Override + public Object[] arguments() { + return new Object[] { type, expectedValue, unit }; + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/RssiPredicate.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/RssiPredicate.java index 8f1d245..b9d8d31 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/RssiPredicate.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/RssiPredicate.java @@ -1,14 +1,14 @@ -package org.openhab.binding.wmbus.device.techem.predicate; - -import org.openhab.binding.wmbus.device.techem.Record; - -public class RssiPredicate extends IntegerPredicate { - - public RssiPredicate(int expectedValue) { - super(Record.Type.RSSI, expectedValue); - } - - public String description() { - return "Missing RSSI record, with expected value %d"; - } -} +package org.openhab.binding.wmbus.device.techem.predicate; + +import org.openhab.binding.wmbus.device.techem.Record; + +public class RssiPredicate extends IntegerPredicate { + + public RssiPredicate(int expectedValue) { + super(Record.Type.RSSI, expectedValue); + } + + public String description() { + return "Missing RSSI record, with expected value %d"; + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/StringPredicate.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/StringPredicate.java index 245d015..ed7bb26 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/StringPredicate.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/device/techem/predicate/StringPredicate.java @@ -1,40 +1,40 @@ -package org.openhab.binding.wmbus.device.techem.predicate; - -import org.assertj.core.api.Assertions; -import org.openhab.binding.wmbus.device.RecordPredicate; -import org.openhab.binding.wmbus.device.techem.Record; - -public class StringPredicate implements RecordPredicate { - - protected final Record.Type type; - protected final String expectedValue; - - public StringPredicate(Record.Type type, String expectedValue) { - this.type = type; - this.expectedValue = expectedValue; - } - - @Override - public boolean test(Record record) { - try { - Assertions.assertThat(record.getType()).isEqualTo(type); - - testValue(record.getValue()); - } catch (AssertionError e) { - return false; - } - return true; - } - - protected void testValue(Object value) { - Assertions.assertThat(value).isInstanceOf(String.class).isEqualTo(expectedValue); - } - - public String description() { - return "record of type %s, with value %s"; - } - - public Object[] arguments() { - return new Object[] { type, expectedValue }; - } -} +package org.openhab.binding.wmbus.device.techem.predicate; + +import org.assertj.core.api.Assertions; +import org.openhab.binding.wmbus.device.RecordPredicate; +import org.openhab.binding.wmbus.device.techem.Record; + +public class StringPredicate implements RecordPredicate { + + protected final Record.Type type; + protected final String expectedValue; + + public StringPredicate(Record.Type type, String expectedValue) { + this.type = type; + this.expectedValue = expectedValue; + } + + @Override + public boolean test(Record record) { + try { + Assertions.assertThat(record.getType()).isEqualTo(type); + + testValue(record.getValue()); + } catch (AssertionError e) { + return false; + } + return true; + } + + protected void testValue(Object value) { + Assertions.assertThat(value).isInstanceOf(String.class).isEqualTo(expectedValue); + } + + public String description() { + return "record of type %s, with value %s"; + } + + public Object[] arguments() { + return new Object[] { type, expectedValue }; + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/BaseUnitRegistryTest.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/BaseUnitRegistryTest.java index b3470ec..82a350e 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/BaseUnitRegistryTest.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/BaseUnitRegistryTest.java @@ -1,392 +1,392 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.internal.units; - -import java.util.Optional; - -import javax.measure.Unit; - -import org.assertj.core.api.Assertions; -import org.junit.Test; -import org.openhab.binding.wmbus.UnitRegistry; -import org.openhab.core.library.unit.ImperialUnits; -import org.openhab.core.library.unit.SIUnits; -import org.openhab.core.library.unit.Units; -import org.openmuc.jmbus.DlmsUnit; - -/** - * Test of unit conversion assuming just framework measure units registry. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public abstract class BaseUnitRegistryTest { - - protected final UnitRegistry registry; - - protected BaseUnitRegistryTest(UnitRegistry registry) { - this.registry = registry; - } - - @Test - public void testConversionOf_ampere() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.AMPERE)).hasValue(Units.AMPERE); - } - - @Test - public void testConversionOf_ampere_hour() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.AMPERE_HOUR)).isEmpty(); - } - - @Test - public void testConversionOf_ampere_per_metre() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.AMPERE_PER_METRE)).isEmpty(); - } - - @Test - public void testConversionOf_ampere_squared_hours() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.AMPERE_SQUARED_HOURS)).isEmpty(); - } - - @Test - public void testConversionOf_ampere_squared_hour_meter_constant_or_pulse_value() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.AMPERE_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE)).isEmpty(); - } - - @Test - public void testConversionOf_apparent_energy_meter_constant_or_pulse_value() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.APPARENT_ENERGY_METER_CONSTANT_OR_PULSE_VALUE)).isEmpty(); - } - - @Test - public void testConversionOf_bar() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.BAR)).isEmpty(); - } - - @Test - public void testConversionOf_calorific_value() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.CALORIFIC_VALUE)).isEmpty(); - } - - @Test - public void testConversionOf_coulomb() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.COULOMB)).contains(Units.COULOMB); - } - - @Test - public void testConversionOf_count() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.COUNT)).isEmpty(); - } - - @Test - public void testConversionOf_cubic_feet() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.CUBIC_FEET)).contains(ImperialUnits.CUBIC_FOOT); - } - - @Test - public void testConversionOf_cubic_metre_corrected() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE)).contains(SIUnits.CUBIC_METRE); - Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE_CORRECTED)).contains(SIUnits.CUBIC_METRE); - } - - @Test - public void testConversionOf_cubic_metre_per_day_corrected() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE_PER_DAY)).isEmpty(); - Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE_PER_DAY_CORRECTED)).isEmpty(); - } - - @Test - public void testConversionOf_cubic_metre_per_hour_corrected() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE_PER_HOUR)).isEmpty(); - Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE_PER_HOUR_CORRECTED)).isEmpty(); - } - - @Test - public void testConversionOf_cubic_metre_per_minute() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE_PER_MINUTE)).isEmpty(); - } - - @Test - public void testConversionOf_cubic_metre_per_second() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE_PER_SECOND)).isEmpty(); - } - - @Test - public void testConversionOf_currency() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.CURRENCY)).isEmpty(); - } - - @Test - public void testConversionOf_day() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.DAY)).contains(Units.DAY); - } - - @Test - public void testConversionOf_degree() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.DEGREE)).isEmpty(); - } - - @Test - public void testConversionOf_degree_celsius() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.DEGREE_CELSIUS)).contains(SIUnits.CELSIUS); - } - - @Test - public void testConversionOf_degree_fahrenheit() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.DEGREE_FAHRENHEIT)).isEmpty(); - } - - @Test - public void testConversionOf_energy_per_volume() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.ENERGY_PER_VOLUME)).isEmpty(); - } - - @Test - public void testConversionOf_farad() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.FARAD)).contains(Units.FARAD); - } - - @Test - public void testConversionOf_henry() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.HENRY)).contains(Units.HENRY); - } - - @Test - public void testConversionOf_hertz() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.HERTZ)).contains(Units.HERTZ); - } - - @Test - public void testConversionOf_hour() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.HOUR)).contains(Units.HOUR); - } - - @Test - public void testConversionOf_joule() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.JOULE)).contains(Units.JOULE); - } - - @Test - public void testConversionOf_joule_per_hour() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.JOULE_PER_HOUR)).isEmpty(); - } - - @Test - public void testConversionOf_kelvin() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.KELVIN)).contains(Units.KELVIN); - } - - @Test - public void testConversionOf_kilogram() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.KILOGRAM)).contains(SIUnits.KILOGRAM); - } - - @Test - public void testConversionOf_kilogram_per_hour() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.KILOGRAM_PER_HOUR)).isEmpty(); - } - - @Test - public void testConversionOf_kilogram_per_second() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.KILOGRAM_PER_SECOND)).isEmpty(); - } - - @Test - public void testConversionOf_litre() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.LITRE)).contains(Units.LITRE); - } - - @Test - public void testConversionOf_mass_density() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.MASS_DENSITY)).isEmpty(); - } - - @Test - public void testConversionOf_meter_constant_or_pulse_value() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.METER_CONSTANT_OR_PULSE_VALUE)).isEmpty(); - } - - @Test - public void testConversionOf_metre() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.METRE)).contains(SIUnits.METRE); - } - - @Test - public void testConversionOf_metre_per_second() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.METRE_PER_SECOND)).contains(Units.METRE_PER_SECOND); - } - - @Test - public void testConversionOf_min() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.MIN)).contains(Units.MINUTE); - } - - @Test - public void testConversionOf_mole_percent() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.MOLE_PERCENT)).isEmpty(); - } - - @Test - public void testConversionOf_month() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.MONTH)).contains(Units.FARAD); - } - - @Test - public void testConversionOf_newton() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.NEWTON)).contains(Units.NEWTON); - } - - @Test - public void testConversionOf_newtonmeter() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.NEWTONMETER)).isEmpty(); - } - - @Test - public void testConversionOf_ohm() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.OHM)).contains(Units.OHM); - } - - @Test - public void testConversionOf_ohm_metre() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.OHM_METRE)).isEmpty(); - } - - @Test - public void testConversionOf_other_unit() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.OTHER_UNIT)).isEmpty(); - } - - @Test - public void testConversionOf_pascal() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.PASCAL)).contains(SIUnits.PASCAL); - } - - @Test - public void testConversionOf_pascal_second() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.PASCAL_SECOND)).isEmpty(); - } - - @Test - public void testConversionOf_percentage() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.PERCENTAGE)).isEmpty(); - } - - @Test - public void testConversionOf_reactive_energy_meter_constant_or_pulse_value() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.REACTIVE_ENERGY_METER_CONSTANT_OR_PULSE_VALUE)).isEmpty(); - } - - @Test - public void testConversionOf_reserved() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.RESERVED)).isEmpty(); - } - - @Test - public void testConversionOf_second() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.SECOND)).contains(Units.SECOND); - } - - @Test - public void testConversionOf_signal_strength() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.SIGNAL_STRENGTH)).isEmpty(); - } - - @Test - public void testConversionOf_specific_energy() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.SPECIFIC_ENERGY)).isEmpty(); - } - - @Test - public void testConversionOf_tesla() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.TESLA)).contains(Units.TESLA); - } - - @Test - public void testConversionOf_us_gallon() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.US_GALLON)).isEmpty(); - } - - @Test - public void testConversionOf_us_gallon_per_hour() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.US_GALLON_PER_HOUR)).isEmpty(); - } - - @Test - public void testConversionOf_us_gallon_per_minute() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.US_GALLON_PER_MINUTE)).isEmpty(); - } - - @Test - public void testConversionOf_var() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.VAR)).isEmpty(); - } - - @Test - public void testConversionOf_var_hour() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.VAR_HOUR)).isEmpty(); - } - - @Test - public void testConversionOf_volt() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.VOLT)).contains(Units.VOLT); - } - - @Test - public void testConversionOf_volt_ampere() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.VOLT_AMPERE)).isEmpty(); - } - - @Test - public void testConversionOf_volt_ampere_hour() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.VOLT_AMPERE_HOUR)).isEmpty(); - } - - @Test - public void testConversionOf_volt_per_metre() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.VOLT_PER_METRE)).isEmpty(); - } - - @Test - public void testConversionOf_volt_squared_hours() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.VOLT_SQUARED_HOURS)).isEmpty(); - } - - @Test - public void testConversionOf_volt_squared_hour_meter_constant_or_pulse_value() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.VOLT_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE)).isEmpty(); - } - - @Test - public void testConversionOf_watt() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.WATT)).contains(Units.WATT); - } - - @Test - public void testConversionOf_watt_hour() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.WATT_HOUR)).contains(Units.WATT_HOUR); - } - - @Test - public void testConversionOf_weber() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.WEBER)).contains(Units.WEBER); - } - - @Test - public void testConversionOf_week() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.WEEK)).contains(Units.WEEK); - } - - @Test - public void testConversionOf_year() throws Exception { - Assertions.assertThat(lookup(DlmsUnit.YEAR)).contains(Units.YEAR); - } - - protected Optional> lookup(DlmsUnit wmbusType) { - return registry.lookup(wmbusType); - } -} +/** + * Copyright (c) 2010-2018 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.wmbus.internal.units; + +import java.util.Optional; + +import javax.measure.Unit; + +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.openhab.binding.wmbus.UnitRegistry; +import org.openhab.core.library.unit.ImperialUnits; +import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.library.unit.Units; +import org.openmuc.jmbus.DlmsUnit; + +/** + * Test of unit conversion assuming just framework measure units registry. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public abstract class BaseUnitRegistryTest { + + protected final UnitRegistry registry; + + protected BaseUnitRegistryTest(UnitRegistry registry) { + this.registry = registry; + } + + @Test + public void testConversionOf_ampere() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.AMPERE)).hasValue(Units.AMPERE); + } + + @Test + public void testConversionOf_ampere_hour() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.AMPERE_HOUR)).isEmpty(); + } + + @Test + public void testConversionOf_ampere_per_metre() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.AMPERE_PER_METRE)).isEmpty(); + } + + @Test + public void testConversionOf_ampere_squared_hours() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.AMPERE_SQUARED_HOURS)).isEmpty(); + } + + @Test + public void testConversionOf_ampere_squared_hour_meter_constant_or_pulse_value() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.AMPERE_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE)).isEmpty(); + } + + @Test + public void testConversionOf_apparent_energy_meter_constant_or_pulse_value() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.APPARENT_ENERGY_METER_CONSTANT_OR_PULSE_VALUE)).isEmpty(); + } + + @Test + public void testConversionOf_bar() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.BAR)).isEmpty(); + } + + @Test + public void testConversionOf_calorific_value() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.CALORIFIC_VALUE)).isEmpty(); + } + + @Test + public void testConversionOf_coulomb() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.COULOMB)).contains(Units.COULOMB); + } + + @Test + public void testConversionOf_count() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.COUNT)).isEmpty(); + } + + @Test + public void testConversionOf_cubic_feet() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.CUBIC_FEET)).contains(ImperialUnits.CUBIC_FOOT); + } + + @Test + public void testConversionOf_cubic_metre_corrected() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE)).contains(SIUnits.CUBIC_METRE); + Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE_CORRECTED)).contains(SIUnits.CUBIC_METRE); + } + + @Test + public void testConversionOf_cubic_metre_per_day_corrected() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE_PER_DAY)).isEmpty(); + Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE_PER_DAY_CORRECTED)).isEmpty(); + } + + @Test + public void testConversionOf_cubic_metre_per_hour_corrected() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE_PER_HOUR)).isEmpty(); + Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE_PER_HOUR_CORRECTED)).isEmpty(); + } + + @Test + public void testConversionOf_cubic_metre_per_minute() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE_PER_MINUTE)).isEmpty(); + } + + @Test + public void testConversionOf_cubic_metre_per_second() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.CUBIC_METRE_PER_SECOND)).isEmpty(); + } + + @Test + public void testConversionOf_currency() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.CURRENCY)).isEmpty(); + } + + @Test + public void testConversionOf_day() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.DAY)).contains(Units.DAY); + } + + @Test + public void testConversionOf_degree() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.DEGREE)).isEmpty(); + } + + @Test + public void testConversionOf_degree_celsius() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.DEGREE_CELSIUS)).contains(SIUnits.CELSIUS); + } + + @Test + public void testConversionOf_degree_fahrenheit() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.DEGREE_FAHRENHEIT)).isEmpty(); + } + + @Test + public void testConversionOf_energy_per_volume() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.ENERGY_PER_VOLUME)).isEmpty(); + } + + @Test + public void testConversionOf_farad() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.FARAD)).contains(Units.FARAD); + } + + @Test + public void testConversionOf_henry() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.HENRY)).contains(Units.HENRY); + } + + @Test + public void testConversionOf_hertz() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.HERTZ)).contains(Units.HERTZ); + } + + @Test + public void testConversionOf_hour() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.HOUR)).contains(Units.HOUR); + } + + @Test + public void testConversionOf_joule() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.JOULE)).contains(Units.JOULE); + } + + @Test + public void testConversionOf_joule_per_hour() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.JOULE_PER_HOUR)).isEmpty(); + } + + @Test + public void testConversionOf_kelvin() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.KELVIN)).contains(Units.KELVIN); + } + + @Test + public void testConversionOf_kilogram() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.KILOGRAM)).contains(SIUnits.KILOGRAM); + } + + @Test + public void testConversionOf_kilogram_per_hour() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.KILOGRAM_PER_HOUR)).isEmpty(); + } + + @Test + public void testConversionOf_kilogram_per_second() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.KILOGRAM_PER_SECOND)).isEmpty(); + } + + @Test + public void testConversionOf_litre() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.LITRE)).contains(Units.LITRE); + } + + @Test + public void testConversionOf_mass_density() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.MASS_DENSITY)).isEmpty(); + } + + @Test + public void testConversionOf_meter_constant_or_pulse_value() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.METER_CONSTANT_OR_PULSE_VALUE)).isEmpty(); + } + + @Test + public void testConversionOf_metre() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.METRE)).contains(SIUnits.METRE); + } + + @Test + public void testConversionOf_metre_per_second() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.METRE_PER_SECOND)).contains(Units.METRE_PER_SECOND); + } + + @Test + public void testConversionOf_min() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.MIN)).contains(Units.MINUTE); + } + + @Test + public void testConversionOf_mole_percent() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.MOLE_PERCENT)).isEmpty(); + } + + @Test + public void testConversionOf_month() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.MONTH)).contains(Units.FARAD); + } + + @Test + public void testConversionOf_newton() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.NEWTON)).contains(Units.NEWTON); + } + + @Test + public void testConversionOf_newtonmeter() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.NEWTONMETER)).isEmpty(); + } + + @Test + public void testConversionOf_ohm() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.OHM)).contains(Units.OHM); + } + + @Test + public void testConversionOf_ohm_metre() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.OHM_METRE)).isEmpty(); + } + + @Test + public void testConversionOf_other_unit() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.OTHER_UNIT)).isEmpty(); + } + + @Test + public void testConversionOf_pascal() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.PASCAL)).contains(SIUnits.PASCAL); + } + + @Test + public void testConversionOf_pascal_second() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.PASCAL_SECOND)).isEmpty(); + } + + @Test + public void testConversionOf_percentage() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.PERCENTAGE)).isEmpty(); + } + + @Test + public void testConversionOf_reactive_energy_meter_constant_or_pulse_value() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.REACTIVE_ENERGY_METER_CONSTANT_OR_PULSE_VALUE)).isEmpty(); + } + + @Test + public void testConversionOf_reserved() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.RESERVED)).isEmpty(); + } + + @Test + public void testConversionOf_second() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.SECOND)).contains(Units.SECOND); + } + + @Test + public void testConversionOf_signal_strength() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.SIGNAL_STRENGTH)).isEmpty(); + } + + @Test + public void testConversionOf_specific_energy() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.SPECIFIC_ENERGY)).isEmpty(); + } + + @Test + public void testConversionOf_tesla() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.TESLA)).contains(Units.TESLA); + } + + @Test + public void testConversionOf_us_gallon() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.US_GALLON)).isEmpty(); + } + + @Test + public void testConversionOf_us_gallon_per_hour() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.US_GALLON_PER_HOUR)).isEmpty(); + } + + @Test + public void testConversionOf_us_gallon_per_minute() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.US_GALLON_PER_MINUTE)).isEmpty(); + } + + @Test + public void testConversionOf_var() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.VAR)).isEmpty(); + } + + @Test + public void testConversionOf_var_hour() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.VAR_HOUR)).isEmpty(); + } + + @Test + public void testConversionOf_volt() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.VOLT)).contains(Units.VOLT); + } + + @Test + public void testConversionOf_volt_ampere() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.VOLT_AMPERE)).isEmpty(); + } + + @Test + public void testConversionOf_volt_ampere_hour() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.VOLT_AMPERE_HOUR)).isEmpty(); + } + + @Test + public void testConversionOf_volt_per_metre() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.VOLT_PER_METRE)).isEmpty(); + } + + @Test + public void testConversionOf_volt_squared_hours() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.VOLT_SQUARED_HOURS)).isEmpty(); + } + + @Test + public void testConversionOf_volt_squared_hour_meter_constant_or_pulse_value() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.VOLT_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE)).isEmpty(); + } + + @Test + public void testConversionOf_watt() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.WATT)).contains(Units.WATT); + } + + @Test + public void testConversionOf_watt_hour() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.WATT_HOUR)).contains(Units.WATT_HOUR); + } + + @Test + public void testConversionOf_weber() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.WEBER)).contains(Units.WEBER); + } + + @Test + public void testConversionOf_week() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.WEEK)).contains(Units.WEEK); + } + + @Test + public void testConversionOf_year() throws Exception { + Assertions.assertThat(lookup(DlmsUnit.YEAR)).contains(Units.YEAR); + } + + protected Optional> lookup(DlmsUnit wmbusType) { + return registry.lookup(wmbusType); + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/CompositeUnitRegistryTest.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/CompositeUnitRegistryTest.java index 10d41f0..a603dbc 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/CompositeUnitRegistryTest.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/CompositeUnitRegistryTest.java @@ -1,23 +1,23 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.internal.units; - -/** - * Test of {@link CompositeUnitRegistry} with {@link UnitsRegistry}. - * - * They should behave in same way together. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public class CompositeUnitRegistryTest extends BaseUnitRegistryTest { - - public CompositeUnitRegistryTest() { - super(new CompositeUnitRegistry(new UnitsRegistry())); - } -} +/** + * Copyright (c) 2010-2018 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.wmbus.internal.units; + +/** + * Test of {@link CompositeUnitRegistry} with {@link UnitsRegistry}. + * + * They should behave in same way together. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public class CompositeUnitRegistryTest extends BaseUnitRegistryTest { + + public CompositeUnitRegistryTest() { + super(new CompositeUnitRegistry(new UnitsRegistry())); + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/ExtendedCompositeUnitRegistryTest.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/ExtendedCompositeUnitRegistryTest.java index 13fa31c..9f9199b 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/ExtendedCompositeUnitRegistryTest.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/ExtendedCompositeUnitRegistryTest.java @@ -1,59 +1,59 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.internal.units; - -import java.util.Optional; - -import javax.measure.Quantity; -import javax.measure.Unit; - -import org.assertj.core.api.Assertions; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.wmbus.UnitRegistry; -import org.openhab.core.library.unit.Units; -import org.openmuc.jmbus.DlmsUnit; - -/** - * Test of {@link CompositeUnitRegistry} with {@link UnitsRegistry} and specific extension which provides - * support for {@link DlmsUnit#COUNT} unit. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public class ExtendedCompositeUnitRegistryTest extends BaseUnitRegistryTest { - - public ExtendedCompositeUnitRegistryTest() { - super(new CompositeUnitRegistry(new UnitsRegistry(), new CountUnitRegistry())); - } - - @Override - public void testConversionOf_count() throws Exception { - try { - super.testConversionOf_count(); - } catch (AssertionError e) { - Assertions.assertThat(registry.lookup(DlmsUnit.COUNT)).contains(Units.ONE); - } - } - - static class CountUnitRegistry implements UnitRegistry { - - @Override - public Optional> lookup(DlmsUnit wmbusType) { - if (DlmsUnit.COUNT.equals(wmbusType)) { - return Optional.of(Units.ONE); - } - - return Optional.empty(); - } - - @Override - public Optional>> quantity(@Nullable DlmsUnit wmbusType) { - return Optional.empty(); - } - } -} +/** + * Copyright (c) 2010-2018 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.wmbus.internal.units; + +import java.util.Optional; + +import javax.measure.Quantity; +import javax.measure.Unit; + +import org.assertj.core.api.Assertions; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.wmbus.UnitRegistry; +import org.openhab.core.library.unit.Units; +import org.openmuc.jmbus.DlmsUnit; + +/** + * Test of {@link CompositeUnitRegistry} with {@link UnitsRegistry} and specific extension which provides + * support for {@link DlmsUnit#COUNT} unit. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public class ExtendedCompositeUnitRegistryTest extends BaseUnitRegistryTest { + + public ExtendedCompositeUnitRegistryTest() { + super(new CompositeUnitRegistry(new UnitsRegistry(), new CountUnitRegistry())); + } + + @Override + public void testConversionOf_count() throws Exception { + try { + super.testConversionOf_count(); + } catch (AssertionError e) { + Assertions.assertThat(registry.lookup(DlmsUnit.COUNT)).contains(Units.ONE); + } + } + + static class CountUnitRegistry implements UnitRegistry { + + @Override + public Optional> lookup(DlmsUnit wmbusType) { + if (DlmsUnit.COUNT.equals(wmbusType)) { + return Optional.of(Units.ONE); + } + + return Optional.empty(); + } + + @Override + public Optional>> quantity(@Nullable DlmsUnit wmbusType) { + return Optional.empty(); + } + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/UnitsRegistryTest.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/UnitsRegistryTest.java index 8742790..8c8ef7a 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/UnitsRegistryTest.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/binding/wmbus/internal/units/UnitsRegistryTest.java @@ -1,21 +1,21 @@ -/** - * Copyright (c) 2010-2018 by the respective copyright holders. - * - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.openhab.binding.wmbus.internal.units; - -/** - * Test of standard framework unit lookup. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public class UnitsRegistryTest extends BaseUnitRegistryTest { - - public UnitsRegistryTest() { - super(new UnitsRegistry()); - } -} +/** + * Copyright (c) 2010-2018 by the respective copyright holders. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.openhab.binding.wmbus.internal.units; + +/** + * Test of standard framework unit lookup. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public class UnitsRegistryTest extends BaseUnitRegistryTest { + + public UnitsRegistryTest() { + super(new UnitsRegistry()); + } +} diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/io/transport/mbus/wireless/MapKeyStorageTest.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/io/transport/mbus/wireless/MapKeyStorageTest.java index 8756033..3046f5e 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/io/transport/mbus/wireless/MapKeyStorageTest.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/io/transport/mbus/wireless/MapKeyStorageTest.java @@ -1,45 +1,45 @@ -package org.openhab.io.transport.mbus.wireless; - -import org.assertj.core.api.Assertions; -import org.junit.Test; -import org.openhab.core.util.HexUtils; -import org.openmuc.jmbus.SecondaryAddress; - -/** - * A basic test of encryption key lookups. - * - * @author Łukasz Dywicki - Initial contribution. - */ -public class MapKeyStorageTest { - - private static final String ADDRESS_HEX = "2423870723421147"; - private static final byte[] ADDRESS_BYTE = HexUtils.hexToBytes(ADDRESS_HEX); - private static final SecondaryAddress ADDRESS_OBJECT = SecondaryAddress.newFromWMBusLlHeader(ADDRESS_BYTE, 0); - private static final byte[] KEY = new byte[] { 0x01, 0x02 }; - - private final MapKeyStorage storage = new MapKeyStorage(); - - @Test - public void testNoKey() { - miss(ADDRESS_BYTE, ADDRESS_OBJECT); - } - - @Test - public void tesKeyUpdate() { - testNoKey(); - - storage.registerKey(ADDRESS_BYTE, KEY); - - hit(ADDRESS_BYTE, ADDRESS_OBJECT, KEY); - } - - protected void miss(byte[] byteForm, SecondaryAddress objectForm) { - Assertions.assertThat(storage.lookupKey(byteForm)).isNotNull().isEmpty(); - Assertions.assertThat(storage.toMap().get(objectForm)).isNull(); - } - - protected void hit(byte[] byteForm, SecondaryAddress objectForm, byte[] key) { - Assertions.assertThat(storage.lookupKey(byteForm)).isNotNull().isNotEmpty().hasValue(key); - Assertions.assertThat(storage.toMap().get(objectForm)).isNotNull().isEqualTo(key); - } -} +package org.openhab.io.transport.mbus.wireless; + +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.openhab.core.util.HexUtils; +import org.openmuc.jmbus.SecondaryAddress; + +/** + * A basic test of encryption key lookups. + * + * @author Łukasz Dywicki - Initial contribution. + */ +public class MapKeyStorageTest { + + private static final String ADDRESS_HEX = "2423870723421147"; + private static final byte[] ADDRESS_BYTE = HexUtils.hexToBytes(ADDRESS_HEX); + private static final SecondaryAddress ADDRESS_OBJECT = SecondaryAddress.newFromWMBusLlHeader(ADDRESS_BYTE, 0); + private static final byte[] KEY = new byte[] { 0x01, 0x02 }; + + private final MapKeyStorage storage = new MapKeyStorage(); + + @Test + public void testNoKey() { + miss(ADDRESS_BYTE, ADDRESS_OBJECT); + } + + @Test + public void tesKeyUpdate() { + testNoKey(); + + storage.registerKey(ADDRESS_BYTE, KEY); + + hit(ADDRESS_BYTE, ADDRESS_OBJECT, KEY); + } + + protected void miss(byte[] byteForm, SecondaryAddress objectForm) { + Assertions.assertThat(storage.lookupKey(byteForm)).isNotNull().isEmpty(); + Assertions.assertThat(storage.toMap().get(objectForm)).isNull(); + } + + protected void hit(byte[] byteForm, SecondaryAddress objectForm, byte[] key) { + Assertions.assertThat(storage.lookupKey(byteForm)).isNotNull().isNotEmpty().hasValue(key); + Assertions.assertThat(storage.toMap().get(objectForm)).isNotNull().isEqualTo(key); + } +} diff --git a/pom.xml b/pom.xml index a39afe8..76dc42f 100644 --- a/pom.xml +++ b/pom.xml @@ -1,132 +1,77 @@ - - 4.0.0 + + 4.0.0 - - org.openhab.addons - org.openhab.addons.reactor - 2.5.0 - - + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 3.1.0-SNAPSHOT + + - org.openhab.binding - wmbus - 2.5.0-SNAPSHOT - pom + org.openhab.addons.bundles + wmbus + 3.1.0-SNAPSHOT + pom - WMBus Binding Aggregator + WMBus Binding Aggregator - - gnu.io*;version=0 - - - - org.openhab.binding.wmbus - org.openhab.binding.wmbus.tools - - - - - - org.commonjava.maven.plugins - directory-maven-plugin - 0.3.1 - - - directories - none - - directory-of - - - basedirRoot - - org.openhab.addons - org.openhab.addons.reactor - - - - - - - org.apache.maven.plugins - maven-jar-plugin - - - ${project.build.outputDirectory}/META-INF/MANIFEST.MF - - true - - - - biz.aQute.bnd - bnd-maven-plugin - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - - - - - jcenter - JCenter Repository - https://jcenter.bintray.com - - true - never - - - false - - - - openhab-artifactory-release - JFrog Artifactory Repository - https://openhab.jfrog.io/openhab/libs-release - - true - never - - - false - - - - openhab-snapshots-release - JFrog Artifactory Repository - https://openhab.jfrog.io/openhab/libs-snapshot-local - - false - never - - - true - - - - - - - jcenter - https://jcenter.bintray.com - - - openhab-artifactory-release - https://openhab.jfrog.io/openhab/libs-release - - + + org.openhab.binding.wmbus + org.openhab.binding.wmbus.tools + + + + + org.commonjava.maven.plugins + directory-maven-plugin + 0.3.1 + + + directories + none + + directory-of + + + basedirRoot + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + true + + + + biz.aQute.bnd + bnd-maven-plugin + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + From 844e3b98129f6be355088d85c393419e0007c18c Mon Sep 17 00:00:00 2001 From: Michael Weger Date: Sun, 7 Mar 2021 20:43:12 +0100 Subject: [PATCH 5/8] - dos2unix lf --- .../main/resources/OH-INF/binding/binding.xml | 52 +- .../main/resources/OH-INF/thing/bridge.xml | 226 ++--- .../resources/OH-INF/thing/channel-types.xml | 318 +++---- .../src/main/resources/OH-INF/thing/itron.xml | 858 +++++++++--------- .../main/resources/OH-INF/thing/techem.xml | 462 +++++----- .../resources/OH-INF/thing/thing-types.xml | 496 +++++----- 6 files changed, 1206 insertions(+), 1206 deletions(-) diff --git a/org.openhab.binding.wmbus/src/main/resources/OH-INF/binding/binding.xml b/org.openhab.binding.wmbus/src/main/resources/OH-INF/binding/binding.xml index 0f9de08..8f9028e 100644 --- a/org.openhab.binding.wmbus/src/main/resources/OH-INF/binding/binding.xml +++ b/org.openhab.binding.wmbus/src/main/resources/OH-INF/binding/binding.xml @@ -1,26 +1,26 @@ - - - - WMBus Binding - The WMBus binding uses a WMBus radio USB stick to receive messages and decode the contents to show the - data of the devices in the UI. It uses the jMBus library, but currently, only the Techem heat cost allocators are - supported. - Hanno - Felix Wagner, Ernst Rohlicek, Roman Malyugin - - - - - Amount of time (in minutes) over which discovery results are retained in inbox. By default set to 720 - minutes (24 hours) - minute - - - Whether to include the BridgeUID (stick/adapter name) into the ThingUID of the metering device. May be - helpful when receiving data from different sites or in different modes. By default set to true. - - - - - + + + + WMBus Binding + The WMBus binding uses a WMBus radio USB stick to receive messages and decode the contents to show the + data of the devices in the UI. It uses the jMBus library, but currently, only the Techem heat cost allocators are + supported. + Hanno - Felix Wagner, Ernst Rohlicek, Roman Malyugin + + + + + Amount of time (in minutes) over which discovery results are retained in inbox. By default set to 720 + minutes (24 hours) + minute + + + Whether to include the BridgeUID (stick/adapter name) into the ThingUID of the metering device. May be + helpful when receiving data from different sites or in different modes. By default set to true. + + + + + diff --git a/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/bridge.xml b/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/bridge.xml index c203544..b01daf0 100644 --- a/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/bridge.xml +++ b/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/bridge.xml @@ -1,113 +1,113 @@ - - - - - - The WMBus bridge represents the USB stick used to receive WMBus messages. There are three different - sticks supported: Amber Wireless AMB8465-M, Radiocrafts RC1180-MBUS and IMST iM871A-USB - - - - - - - The stick model used. - - true - - - - - - - - serial-port - - The name of the serial port (e.g. /dev/ttyUSB0 or COM5). - true - - - Radio mode to operate the WMBus radio module on. - - - - - - - true - - - Type of which date/time channels should be. - - - - - - - true - DATE_TIME - - - Encryption Keys in form ID:KEY;ID:KEY all in hex format like given to the jMBus message printer test - program. - - true - - - List of device IDs to filter during receive. If empty, all received devices will be handled, if at - least one ID is set, only messages from this device will be handled. Device ID in decumal format as shown in WMBus - message prints and given out by the jMBus message printer test program. - - true - - - - - - - The WMBus receiver which is not attached to serial port. - - - - - - - - Type of which date/time channels should be. - - - - - - - true - DATE_TIME - - - Encryption Keys in form ID:KEY;ID:KEY all in hex format like given to the jMBus message printer test - program. - - true - - - List of device IDs to filter during receive. If empty, all received devices will be handled, if at - least one ID is set, only messages from this device will be handled. Device ID in decumal format as shown in WMBus - message prints and given out by the jMBus message printer test program. - - true - - - - - - String - - Raw representation of last frame received by device encoded in HEX form. - - - - - + + + + + + The WMBus bridge represents the USB stick used to receive WMBus messages. There are three different + sticks supported: Amber Wireless AMB8465-M, Radiocrafts RC1180-MBUS and IMST iM871A-USB + + + + + + + The stick model used. + + true + + + + + + + + serial-port + + The name of the serial port (e.g. /dev/ttyUSB0 or COM5). + true + + + Radio mode to operate the WMBus radio module on. + + + + + + + true + + + Type of which date/time channels should be. + + + + + + + true + DATE_TIME + + + Encryption Keys in form ID:KEY;ID:KEY all in hex format like given to the jMBus message printer test + program. + + true + + + List of device IDs to filter during receive. If empty, all received devices will be handled, if at + least one ID is set, only messages from this device will be handled. Device ID in decumal format as shown in WMBus + message prints and given out by the jMBus message printer test program. + + true + + + + + + + The WMBus receiver which is not attached to serial port. + + + + + + + + Type of which date/time channels should be. + + + + + + + true + DATE_TIME + + + Encryption Keys in form ID:KEY;ID:KEY all in hex format like given to the jMBus message printer test + program. + + true + + + List of device IDs to filter during receive. If empty, all received devices will be handled, if at + least one ID is set, only messages from this device will be handled. Device ID in decumal format as shown in WMBus + message prints and given out by the jMBus message printer test program. + + true + + + + + + String + + Raw representation of last frame received by device encoded in HEX form. + + + + + diff --git a/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/channel-types.xml b/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/channel-types.xml index c6934e3..6f67033 100644 --- a/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/channel-types.xml +++ b/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/channel-types.xml @@ -1,159 +1,159 @@ - - - - - Number:Energy - - Current energy reading - Energy - - - - - - Number:Volume - - Current volume reading - Volume - - - - - - - Number:Power - - Current power reading. - Power - - - - - - Number:Dimensionless - - - Current flow volume reading. - VolumetricFlowRate - - - - - - Number:Temperature - - Current flow temperature reading. - FlowPipe - - - - - - Number:Temperature - - Current return temperature reading. - ReturnPipe - - - - - - Number:Temperature - - Current external temperature reading. - Temperature - - - - - - Number:Temperature - - Current difference between flow and return temperatures. - Temperature - - - - - - Number:Power - - Maximum power reading registered by meter. - Power - - - - - - Number:Dimensionless - - - Maximum flow volume reading registered by meter. - VolumetricFlowRate - - - - - - Number:Temperature - - Maximum flow temperature reading registered by meter. - Temperature - - - - - - Number:Temperature - - Maximum return temperature reading registered by meter. - Temperature - - - - - - Number:Temperature - - Maximum external temperature reading registered by meter. - Temperature - - - - - - Number:Time - - Time since meter reported error (?). - Time - - - - - - DateTime - - The date, when the device last encountered an error. May be set to manufacturing date, installation date - or sometime far in the future if none happened yet. - Date - - - - - - Number - - Current error flags value. - QualityOfService - - - - - + + + + + Number:Energy + + Current energy reading + Energy + + + + + + Number:Volume + + Current volume reading + Volume + + + + + + + Number:Power + + Current power reading. + Power + + + + + + Number:Dimensionless + + + Current flow volume reading. + VolumetricFlowRate + + + + + + Number:Temperature + + Current flow temperature reading. + FlowPipe + + + + + + Number:Temperature + + Current return temperature reading. + ReturnPipe + + + + + + Number:Temperature + + Current external temperature reading. + Temperature + + + + + + Number:Temperature + + Current difference between flow and return temperatures. + Temperature + + + + + + Number:Power + + Maximum power reading registered by meter. + Power + + + + + + Number:Dimensionless + + + Maximum flow volume reading registered by meter. + VolumetricFlowRate + + + + + + Number:Temperature + + Maximum flow temperature reading registered by meter. + Temperature + + + + + + Number:Temperature + + Maximum return temperature reading registered by meter. + Temperature + + + + + + Number:Temperature + + Maximum external temperature reading registered by meter. + Temperature + + + + + + Number:Time + + Time since meter reported error (?). + Time + + + + + + DateTime + + The date, when the device last encountered an error. May be set to manufacturing date, installation date + or sometime far in the future if none happened yet. + Date + + + + + + Number + + Current error flags value. + QualityOfService + + + + + diff --git a/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/itron.xml b/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/itron.xml index 9752fed..580bf8a 100644 --- a/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/itron.xml +++ b/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/itron.xml @@ -1,429 +1,429 @@ - - - - - - - - - - - - - - Smoke detectors implemented on top of WM-Bus specification, manufactured by Itron. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - DateTime - - Date and time with resolution up to a second. - - - - - - DateTime - - Date and time with resolution up to a second, formatted as a string. - - - - - - DateTime - - Date and time with resolution up to a second, formatted as a unix timestamp. - - - - - - - Switch - - Removal occurred - - - - - Switch - - Billing month - - - - - Switch - - Product is installed. - - - - - Number - - Operation mode. - - - - - - - - - - - Switch - - Perimeter intrusion occurred - - - - - Switch - - Smoke inlet blocked occurred - - - - - Switch - - Temperature out of range occurred - - - - - String - - - - - - - Number - - Battery lifetime - - - - - - - - DateTime - - Last Smoke Alert Start dates - - - - - DateTime - - Last Smoke Alert Start dates (formatted as string) - - - - - DateTime - - Last Smoke Alert Start dates (formatted as number) - - - - - DateTime - - Last Smoke Alert End dates - - - - - DateTime - - Last Smoke Alert End dates (formatted as string) - - - - - DateTime - - Last Smoke Alert End dates (formatted as number) - - - - - DateTime - - Last Beeper stopped during smoke alert dates - - - - - DateTime - - Last Beeper stopped during smoke alert dates (formatted as string) - - - - - DateTime - - Last Beeper stopped during smoke alert dates (formatted as number) - - - - - DateTime - - Last Perimeter Intrusion (Obstacle occured) dates - - - - - DateTime - - Last Perimeter Intrusion (Obstacle occured) dates (formatted as string) - - - - - DateTime - - Last Perimeter Intrusion (Obstacle occured) dates (formatted as number) - - - - - DateTime - - Last Perimeter Intrusion (Obstacle removed) dates - - - - - DateTime - - Last Perimeter Intrusion (Obstacle removed) dates (formatted as string) - - - - - DateTime - - Last Perimeter Intrusion (Obstacle removed) dates (formatted as number) - - - - - DateTime - - last Smoke inlet (Blocked) dates - - - - - DateTime - - Last Smoke inlet (Blocked) dates (formatted as string) - - - - - DateTime - - Last Smoke inlet (Blocked) dates (formatted as number) - - - - - DateTime - - Last Smoke inlet (Blocking removed) dates - - - - - DateTime - - Last Smoke inlet (Blocking removed) dates (formatted as string) - - - - - DateTime - - Last Smoke inlet (Blocking removed) dates (formatted as number) - - - - - DateTime - - Last out of Temperate Range date - - - - - DateTime - - Last out of Temperate Range date (formatted as string) - - - - - DateTime - - Last out of Temperate Range date (formatted as number) - - - - - DateTime - - Last Testswitch dates - - - - - DateTime - - Last Testswitch dates (formatted as string) - - - - - DateTime - - Last Testswitch dates (formatted as number) - - - - - Number - - Number of test switches operated - - - - - - Number - - Perimeter Intrusion day counter cumulated - - - - - - Number - - Smoke inlet day counter cumulated - - - - - + + + + + + + + + + + + + + Smoke detectors implemented on top of WM-Bus specification, manufactured by Itron. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DateTime + + Date and time with resolution up to a second. + + + + + + DateTime + + Date and time with resolution up to a second, formatted as a string. + + + + + + DateTime + + Date and time with resolution up to a second, formatted as a unix timestamp. + + + + + + + Switch + + Removal occurred + + + + + Switch + + Billing month + + + + + Switch + + Product is installed. + + + + + Number + + Operation mode. + + + + + + + + + + + Switch + + Perimeter intrusion occurred + + + + + Switch + + Smoke inlet blocked occurred + + + + + Switch + + Temperature out of range occurred + + + + + String + + + + + + + Number + + Battery lifetime + + + + + + + + DateTime + + Last Smoke Alert Start dates + + + + + DateTime + + Last Smoke Alert Start dates (formatted as string) + + + + + DateTime + + Last Smoke Alert Start dates (formatted as number) + + + + + DateTime + + Last Smoke Alert End dates + + + + + DateTime + + Last Smoke Alert End dates (formatted as string) + + + + + DateTime + + Last Smoke Alert End dates (formatted as number) + + + + + DateTime + + Last Beeper stopped during smoke alert dates + + + + + DateTime + + Last Beeper stopped during smoke alert dates (formatted as string) + + + + + DateTime + + Last Beeper stopped during smoke alert dates (formatted as number) + + + + + DateTime + + Last Perimeter Intrusion (Obstacle occured) dates + + + + + DateTime + + Last Perimeter Intrusion (Obstacle occured) dates (formatted as string) + + + + + DateTime + + Last Perimeter Intrusion (Obstacle occured) dates (formatted as number) + + + + + DateTime + + Last Perimeter Intrusion (Obstacle removed) dates + + + + + DateTime + + Last Perimeter Intrusion (Obstacle removed) dates (formatted as string) + + + + + DateTime + + Last Perimeter Intrusion (Obstacle removed) dates (formatted as number) + + + + + DateTime + + last Smoke inlet (Blocked) dates + + + + + DateTime + + Last Smoke inlet (Blocked) dates (formatted as string) + + + + + DateTime + + Last Smoke inlet (Blocked) dates (formatted as number) + + + + + DateTime + + Last Smoke inlet (Blocking removed) dates + + + + + DateTime + + Last Smoke inlet (Blocking removed) dates (formatted as string) + + + + + DateTime + + Last Smoke inlet (Blocking removed) dates (formatted as number) + + + + + DateTime + + Last out of Temperate Range date + + + + + DateTime + + Last out of Temperate Range date (formatted as string) + + + + + DateTime + + Last out of Temperate Range date (formatted as number) + + + + + DateTime + + Last Testswitch dates + + + + + DateTime + + Last Testswitch dates (formatted as string) + + + + + DateTime + + Last Testswitch dates (formatted as number) + + + + + Number + + Number of test switches operated + + + + + + Number + + Perimeter Intrusion day counter cumulated + + + + + + Number + + Smoke inlet day counter cumulated + + + + + diff --git a/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/techem.xml b/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/techem.xml index ede17e3..986cf4a 100644 --- a/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/techem.xml +++ b/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/techem.xml @@ -1,231 +1,231 @@ - - - - - - - - - - - A heat cost allocator of Techem - - - - - - - - - - - - - - - - - - - - - - - - A heat cost allocator of Techem - - - - - - - - - - - - - - - - - - - - - - - - - A heat cost allocator of Techem - - - - - - - - - - - - - - - - - - - - - - - - - A heat cost allocator of Techem - - - - - - - - - - - - - - - - - - - - - - - - - - - A heat cost allocator of Techem - - - - - - - - - - - - - - - - - - - - - - - - - - A smoke detector of Techem - - - - - - - - - - - - - - - - - - - - - - A warm water meter of Techem - - - - - - - - - - - - - - - - - - - - - - - - - - - A cold water meter of Techem - - - - - - - - - - - - - - - - - - - - - - - - A heat meter of Techem - - - - - - - - - - - - - - - - - + + + + + + + + + + + A heat cost allocator of Techem + + + + + + + + + + + + + + + + + + + + + + + + A heat cost allocator of Techem + + + + + + + + + + + + + + + + + + + + + + + + + A heat cost allocator of Techem + + + + + + + + + + + + + + + + + + + + + + + + + A heat cost allocator of Techem + + + + + + + + + + + + + + + + + + + + + + + + + + + A heat cost allocator of Techem + + + + + + + + + + + + + + + + + + + + + + + + + + A smoke detector of Techem + + + + + + + + + + + + + + + + + + + + + + A warm water meter of Techem + + + + + + + + + + + + + + + + + + + + + + + + + + + A cold water meter of Techem + + + + + + + + + + + + + + + + + + + + + + + + A heat meter of Techem + + + + + + + + + + + + + + + + + diff --git a/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/thing-types.xml b/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/thing-types.xml index df38d52..0876c2f 100644 --- a/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/thing-types.xml +++ b/org.openhab.binding.wmbus/src/main/resources/OH-INF/thing/thing-types.xml @@ -1,248 +1,248 @@ - - - - - - - - - - - - - Universal WMBus device. - - - - - Identifier which lets to find device (in hexdecimal format). - true - - - - - Frequency of updates sent by device. This value is used to determine if device goes offline. - Recommended value depends on manufacturer and specific meter and should be adjusted manually. - Defaults to 60 minutes - which is quite long. - - false - minutes - - - - - - - - - - - - - WMBus device which uses secured communication - you need to provide a encryption key to read reported - values. - - - - - Identifier which lets to find device (in hexdecimal format). - true - - - - - Frequency of updates sent by device. This value is used to determine if device goes offline. - Recommended value depends on manufacturer and specific meter and should be adjusted manually. - Defaults to 60 minutes - which is quite long. - - false - minutes - - - - - Encryption key in hexadecimal format to decode frames sent by this device. Value of this configuration - parameter - is optional and required only in case when meter is configured to use encrypted communication. - - false - encryption - - - - - - - Number - - Current temperature in the room in degrees celsius. - Temperature - - - - - Number - - Current temperature of the radiator in degrees celsius. - Temperature - - - - - Number - - Current heat cost allocation reading. - Energy - - - - - Number - - Heat cost allocation reading of previous month. - Energy - - - - - Number - - Heat cost allocation reading of the previous accounting period, usually yearly. - Energy - - - - - DateTime - - The time when the current reading was taken. - Date - - - - - String - - The time when the current reading was taken, formatted as text. - Date - - - - - Number - - The time when the current reading was taken, formatted as UNIX timestmap. - Date - - - - - DateTime - - The time when the previous month's reading was taken. - Date - - - - - String - - The time when the previous month's reading was taken, formatted as text. - Date - - - - - Number - - The time when the previous month's reading was taken, formatted as UNIX timestmap. - Date - - - - - DateTime - - The time when the reading of the previous period was taken. - Date - - - - - String - - The time when the reading of the previous period was taken, formatted as text. - Date - - - - - Number - - The time when the reading of the previous period was taken, formatted as UNIX timestmap. - Date - - - - - Number - - The Received Signal Strength Indication, power of the radio signal in dBm (decibel milliwatt) - QualityOfService - - - - - String - - The Readings of all last months separately. - Date - - - - - Number - - The status byte represented as number. - System - - - - - Number - - Consumption of water/heat accumulated in periods between 1-16. - - - - - - DateTime - - The time when the current reading was taken. - Date - - - - - String - - The time when the current reading was taken, formatted as text. - Date - - - - - Number - - The time when the current reading was taken, formatted as UNIX timestmap. - Date - - - - + + + + + + + + + + + + + Universal WMBus device. + + + + + Identifier which lets to find device (in hexdecimal format). + true + + + + + Frequency of updates sent by device. This value is used to determine if device goes offline. + Recommended value depends on manufacturer and specific meter and should be adjusted manually. + Defaults to 60 minutes + which is quite long. + + false + minutes + + + + + + + + + + + + + WMBus device which uses secured communication - you need to provide a encryption key to read reported + values. + + + + + Identifier which lets to find device (in hexdecimal format). + true + + + + + Frequency of updates sent by device. This value is used to determine if device goes offline. + Recommended value depends on manufacturer and specific meter and should be adjusted manually. + Defaults to 60 minutes + which is quite long. + + false + minutes + + + + + Encryption key in hexadecimal format to decode frames sent by this device. Value of this configuration + parameter + is optional and required only in case when meter is configured to use encrypted communication. + + false + encryption + + + + + + + Number + + Current temperature in the room in degrees celsius. + Temperature + + + + + Number + + Current temperature of the radiator in degrees celsius. + Temperature + + + + + Number + + Current heat cost allocation reading. + Energy + + + + + Number + + Heat cost allocation reading of previous month. + Energy + + + + + Number + + Heat cost allocation reading of the previous accounting period, usually yearly. + Energy + + + + + DateTime + + The time when the current reading was taken. + Date + + + + + String + + The time when the current reading was taken, formatted as text. + Date + + + + + Number + + The time when the current reading was taken, formatted as UNIX timestmap. + Date + + + + + DateTime + + The time when the previous month's reading was taken. + Date + + + + + String + + The time when the previous month's reading was taken, formatted as text. + Date + + + + + Number + + The time when the previous month's reading was taken, formatted as UNIX timestmap. + Date + + + + + DateTime + + The time when the reading of the previous period was taken. + Date + + + + + String + + The time when the reading of the previous period was taken, formatted as text. + Date + + + + + Number + + The time when the reading of the previous period was taken, formatted as UNIX timestmap. + Date + + + + + Number + + The Received Signal Strength Indication, power of the radio signal in dBm (decibel milliwatt) + QualityOfService + + + + + String + + The Readings of all last months separately. + Date + + + + + Number + + The status byte represented as number. + System + + + + + Number + + Consumption of water/heat accumulated in periods between 1-16. + + + + + + DateTime + + The time when the current reading was taken. + Date + + + + + String + + The time when the current reading was taken, formatted as text. + Date + + + + + Number + + The time when the current reading was taken, formatted as UNIX timestmap. + Date + + + + From 29542564135478116840e6a6d150f00696fda87f Mon Sep 17 00:00:00 2001 From: Michael Weger Date: Sun, 7 Mar 2021 21:05:11 +0100 Subject: [PATCH 6/8] - renamed artifacts --- org.openhab.binding.wmbus.tools/pom.xml | 4 ++-- org.openhab.binding.wmbus/pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/org.openhab.binding.wmbus.tools/pom.xml b/org.openhab.binding.wmbus.tools/pom.xml index 8585848..3793db7 100644 --- a/org.openhab.binding.wmbus.tools/pom.xml +++ b/org.openhab.binding.wmbus.tools/pom.xml @@ -10,7 +10,7 @@ org.openhab.addons.bundles - org.openhab.addons.bundles.wmbus.tools + org.openhab.binding.wmbus.tools WMBus Binding Tools @@ -21,7 +21,7 @@ org.openhab.addons.bundles - org.openhab.addons.bundles.wmbus + org.openhab.binding.wmbus ${project.version} diff --git a/org.openhab.binding.wmbus/pom.xml b/org.openhab.binding.wmbus/pom.xml index 1dec60c..5c2ef4e 100644 --- a/org.openhab.binding.wmbus/pom.xml +++ b/org.openhab.binding.wmbus/pom.xml @@ -11,7 +11,7 @@ org.openhab.addons.bundles - org.openhab.addons.bundles.wmbus + org.openhab.binding.wmbus WMBus Binding From 573cf5a384435ea255028953b449b306b4c9acee Mon Sep 17 00:00:00 2001 From: Michael Weger Date: Sun, 7 Mar 2021 22:18:23 +0100 Subject: [PATCH 7/8] - README.md updated --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 148c527..1bb58a3 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ This is a binding for the [openHAB](https://www.openhab.org/) / eclipse Smart Home home automation system. It aims to make data available to the user, which is already sent by lots of modern metering hardware (e.g. heat cost allocators, electricity/gas/water/heat meters) already widely deployed in houses and especially flats. -The binding is based on the [jMBus](https://www.openmuc.org/m-bus/) library, which provides all basic receiving and decoding. It is currently using [a fork](https://github.com/kaikreuzer/jmbus) of version 3.1.1 to make it work within openHAB. +The binding is based on the [jMBus](https://www.openmuc.org/m-bus/) library, which provides all basic receiving and decoding. It is currently using version 3.1.1 plus one extension for access to the RAW frame. Implementation of the Techem devices has been ported from [FHEM](https://forum.fhem.de/index.php/topic,42232.html). -The binding is working stable. To use it, you need a device to receive the transmissions. The underlying library supports transceivers of Amber, Radiocrafts (RC1180-MBUS) and IMST (iM871A-USB), the binding is mainly developed and tested with the [Amber Wireless AMB8465-M](https://www.amber-wireless.de/de/produkte/wireless-m-bus/alle-usb-sticks/wireless-m-bus-868-mhz-usb-stick-int-antenne-amb8465-m.html). -However, development is still going on and there are some known issues. One is, that sometimes when changing settings etc. to the stick, the serial port native parts segfault and the whole openHAB is restarting. This seems to be caused by a bug in the serial library or the jMBus library. +The binding is working stable. To use it, you need a device to receive the transmissions. The underlying library supports transceivers of Amber, Radiocrafts (RC1180-MBUS) and IMST (iM871A-USB), the binding is mainly developed and tested with the [Amber Wireless AMB8465-M](https://www.we-online.de/catalog/en/USB_RADIO_STICK_METERING). +However, development is still going on and there are some known issues. ### Features: * Receive WMBus frames as inbox discovery results @@ -55,7 +55,7 @@ There is some more information and discussion [in the forum](https://community.o * Room/radiator temperature etc. are always current (at the time of sending/receiving the message). * Some device's "Current Reading" will only update once each day. 14. If a Persistence Add-on (e.g. InfluxDB) is installed, the readings will also be stored into the database. -15. In HABmin or HABPanel, diagrams/charts/graphs can be configured to have a look at the latest values in comparison. Grafana is a good third party software to get an overview. +15. In OH3 UI, diagrams/charts/graphs can be configured to have a look at the latest values in comparison. Grafana is a good third party software to get an overview. 16. If any Exceptions or other messages turn up in the logs or console, please let us know and open an issue here. ### Encrypted messages From 09f6c0c24aa6ddd238436ac2891d8108562dad51 Mon Sep 17 00:00:00 2001 From: Michael Weger Date: Tue, 9 Mar 2021 21:42:23 +0100 Subject: [PATCH 8/8] - updated jmbus to v3.1.1 --- .../binding/wmbus/tools/Processor.java | 5 +- .../mbus/wireless/FilteredKeyStorage.java | 2 +- .../mbus/wireless/MapKeyStorage.java | 2 +- .../java/org/openmuc/jmbus/DataRecord.java | 143 +++++++++--------- .../main/java/org/openmuc/jmbus/DlmsUnit.java | 22 +-- .../main/java/org/openmuc/jmbus/HexUtils.java | 32 ++++ .../org/openmuc/jmbus/MBusConnection.java | 96 +++++++----- .../openmuc/jmbus/ScanSecondaryAddress.java | 21 ++- .../org/openmuc/jmbus/SecondaryAddress.java | 21 ++- .../openmuc/jmbus/VariableDataStructure.java | 106 +++++++++---- .../jmbus/transportlayer/TcpLayer.java | 18 +++ .../jmbus/wireless/HciMessageException.java | 1 + .../jmbus/wireless/WMBusConnectionAmber.java | 30 +++- .../jmbus/wireless/WMBusConnectionImst.java | 18 ++- .../openmuc/jmbus/wireless/WMBusMessage.java | 4 +- .../mbus/wireless/MapKeyStorageTest.java | 2 +- 16 files changed, 337 insertions(+), 186 deletions(-) create mode 100644 org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/HexUtils.java diff --git a/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/Processor.java b/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/Processor.java index c4fbe66..631db5d 100644 --- a/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/Processor.java +++ b/org.openhab.binding.wmbus.tools/src/main/java/org/openhab/binding/wmbus/tools/Processor.java @@ -17,8 +17,7 @@ */ public interface Processor { - String RSSI = "rssi"; - - T process(T frame, Map context); + String RSSI = "rssi"; + T process(T frame, Map context); } diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/FilteredKeyStorage.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/FilteredKeyStorage.java index e12d792..c5b55ee 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/FilteredKeyStorage.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/FilteredKeyStorage.java @@ -61,6 +61,6 @@ public Map toMap() { } private SecondaryAddress createKey(byte[] address) { - return SecondaryAddress.newFromWMBusLlHeader(address, 0); + return SecondaryAddress.newFromWMBusHeader(address, 0); } } diff --git a/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/MapKeyStorage.java b/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/MapKeyStorage.java index 38f8022..30f9a1b 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/MapKeyStorage.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openhab/io/transport/mbus/wireless/MapKeyStorage.java @@ -46,6 +46,6 @@ public Map toMap() { } private SecondaryAddress createKey(byte[] address) { - return SecondaryAddress.newFromWMBusLlHeader(address, 0); + return SecondaryAddress.newFromWMBusHeader(address, 0); } } diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DataRecord.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DataRecord.java index 1c6ac65..6373971 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DataRecord.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DataRecord.java @@ -1,19 +1,10 @@ /** - * Copyright (c) 2010-2021 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 + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.openmuc.jmbus; -import static javax.xml.bind.DatatypeConverter.printHexBinary; - import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Arrays; @@ -21,11 +12,11 @@ /** * Representation of a data record (sometimes called variable data block). - * + * * A data record is the basic data entity of the M-Bus application layer. A variable data structure contains a list of * data records. Each data record represents a single data point. A data record consists of three fields: The data * information block (DIB), the value information block (VIB) and the data field. - * + * * The DIB codes the following parameters: *
      *
    • Storage number - a meter can have several storages e.g. to store historical time series data. The storage number @@ -37,7 +28,7 @@ * tariffs.
    • *
    • Subunit - can be used by a slave to distinguish several subunits of the metering device
    • *
    - * + * * The VIB codes the following parameters: *
      *
    • Description - the meaning of the data value (e.g. "Energy", "Volume" etc.)
    • @@ -45,7 +36,7 @@ *
    • Multiplier - a factor by which the data value coded in the data field has to be multiplied with. * getScaledDataValue() returns the result of the data value multiplied with the multiplier.
    • *
    - * + * */ public class DataRecord { @@ -64,7 +55,7 @@ public enum DataValueType { /** * Function coded in the DIB - * + * */ public enum FunctionField { /** @@ -87,7 +78,7 @@ public enum FunctionField { /** * Data description stored in the VIB - * + * */ public enum Description { ENERGY, @@ -119,6 +110,7 @@ public enum Description { CUSTOMER, RESERVED, OPERATING_TIME_BATTERY, + RF_LEVEL, HCA, REACTIVE_ENERGY, TEMPERATURE_LIMIT, @@ -204,7 +196,7 @@ public byte[] getRawData() { return rawData; } - int decode(byte[] buffer, int offset, int length) throws DecodingException { + int decode(byte[] buffer, int offset) throws DecodingException { int i = offset; decodeDib(buffer, i); @@ -280,10 +272,12 @@ int decode(byte[] buffer, int offset, int length) throws DecodingException { break; case 0x02: /* INT16 */ if (dateTypeG) { - int day = (0x1f) & buffer[i]; - int year1 = ((0xe0) & buffer[i++]) >> 5; - int month = (0x0f) & buffer[i]; - int year2 = ((0xf0) & buffer[i++]) >> 1; + int day = (0x1f) & buffer[i]; // Byte 1; Bit 1-5 + int year1 = ((0xe0) & buffer[i++]) >> 5; // Byte 1: Bit 6-8 + + int month = (0x0f) & buffer[i]; // Byte 2: Bit 9-12 + int year2 = ((0xf0) & buffer[i++]) >> 1; // Byte 2: Bit 13-16 + int year = (2000 + year1 + year2); Calendar calendar = Calendar.getInstance(); @@ -292,6 +286,10 @@ int decode(byte[] buffer, int offset, int length) throws DecodingException { dataValue = calendar.getTime(); dataValueType = DataValueType.DATE; + } else if ((buffer[i + 1] & 0x80) == 0x80) { + // negative + dataValue = Long.valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) | 0xffff << 16); + dataValueType = DataValueType.LONG; } else { dataValue = Long.valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8)); dataValueType = DataValueType.LONG; @@ -310,13 +308,18 @@ int decode(byte[] buffer, int offset, int length) throws DecodingException { break; case 0x04: /* INT32 */ if (dateTypeF) { - int min = (buffer[i++] & 0x3f); - int hour = (buffer[i] & 0x1f); - int yearh = (0x60 & buffer[i++]) >> 5; - int day = (buffer[i] & 0x1f); - int year1 = (0xe0 & buffer[i++]) >> 5; - int mon = (buffer[i] & 0x0f); - int year2 = (0xf0 & buffer[i++]) >> 1; + Calendar calendar = Calendar.getInstance(); + int min = (buffer[i++] & 0x3f); // Byte 1: Bit 1-6 + + int hour = (buffer[i] & 0x1f); // Byte 2: Bit 9-13 + int yearh = (0x60 & buffer[i]) >> 5; // Byte 2: Bit 14-15 + int dst = (0x80 & buffer[i++]) >> 7; // Byte 2: Bit 16 + + int day = (buffer[i] & 0x1f); // Byte 3: Bit 17-21 + int year1 = (0xe0 & buffer[i++]) >> 5; // Byte 3: Bit 22-24 + + int mon = (buffer[i] & 0x0f); // Byte 4: Bit 25-28 + int year2 = (0xf0 & buffer[i++]) >> 1; // Byte 4: Bit 29-32 if (yearh == 0) { yearh = 1; @@ -324,15 +327,17 @@ int decode(byte[] buffer, int offset, int length) throws DecodingException { int year = 1900 + 100 * yearh + year1 + year2; - Calendar calendar = Calendar.getInstance(); - calendar.set(year, mon - 1, day, hour, min, 0); + if (dst == 1) { + calendar.set(Calendar.DST_OFFSET, 60000); + } + dataValue = calendar.getTime(); dataValueType = DataValueType.DATE; } else { - dataValue = Long.valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) - | ((buffer[i++] & 0xff) << 16) | ((buffer[i++] & 0xff) << 24)); + dataValue = (long) ByteBuffer.wrap(buffer, i, 4).order(ByteOrder.LITTLE_ENDIAN).getInt(); + i += 4; dataValueType = DataValueType.LONG; } break; @@ -345,22 +350,20 @@ int decode(byte[] buffer, int offset, int length) throws DecodingException { case 0x06: /* INT48 */ if ((buffer[i + 5] & 0x80) == 0x80) { // negative - dataValue = Long - .valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) | ((buffer[i++] & 0xff) << 16) - | ((buffer[i++] & 0xff) << 24) | (((long) buffer[i++] & 0xff) << 32) - | (((long) buffer[i++] & 0xff) << 40) | (0xffl << 48) | (0xffl << 56)); + dataValue = Long.valueOf( + (buffer[i++] & 0xffL) | ((buffer[i++] & 0xffL) << 8) | ((buffer[i++] & 0xffL) << 16) + | ((buffer[i++] & 0xffL) << 24) | ((buffer[i++] & 0xffL) << 32) + | ((buffer[i++] & 0xffL) << 40) | (0xffL << 48) | (0xffL << 56)); } else { - dataValue = Long.valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) - | ((buffer[i++] & 0xff) << 16) | ((buffer[i++] & 0xff) << 24) - | (((long) buffer[i++] & 0xff) << 32) | (((long) buffer[i++] & 0xff) << 40)); + dataValue = Long.valueOf((buffer[i++] & 0xffL) | ((buffer[i++] & 0xffL) << 8) + | ((buffer[i++] & 0xffL) << 16) | ((buffer[i++] & 0xffL) << 24) + | ((buffer[i++] & 0xffL) << 32) | ((buffer[i++] & 0xffL) << 40)); } dataValueType = DataValueType.LONG; break; case 0x07: /* INT64 */ - dataValue = Long.valueOf((buffer[i++] & 0xff) | ((buffer[i++] & 0xff) << 8) - | ((buffer[i++] & 0xff) << 16) | ((buffer[i++] & 0xff) << 24) - | (((long) buffer[i++] & 0xff) << 32) | (((long) buffer[i++] & 0xff) << 40) - | (((long) buffer[i++] & 0xff) << 48) | (((long) buffer[i++] & 0xff) << 56)); + dataValue = ByteBuffer.wrap(buffer, i, 8).order(ByteOrder.LITTLE_ENDIAN).getLong(); + i += 8; dataValueType = DataValueType.LONG; break; case 0x09: @@ -397,11 +400,6 @@ int decode(byte[] buffer, int offset, int length) throws DecodingException { throw new DecodingException("Unsupported LVAR Field: " + variableLength); } - // TODO check this: - // if (variableLength >= 0xc0) { - // throw new DecodingException("Variable length (LVAR) field >= 0xc0: " + variableLength); - // } - byte[] rawData = new byte[dataLength0x0d]; for (int j = 0; j < dataLength0x0d; j++) { @@ -464,7 +462,7 @@ int encode(byte[] buffer, int offset) { /** * Returns a byte array containing the DIB (i.e. the DIF and the DIFEs) contained in the data record. - * + * * @return a byte array containing the DIB */ public byte[] getDib() { @@ -473,7 +471,7 @@ public byte[] getDib() { /** * Returns a byte array containing the VIB (i.e. the VIF and the VIFEs) contained in the data record. - * + * * @return a byte array containing the VIB */ public byte[] getVib() { @@ -484,7 +482,7 @@ public byte[] getVib() { * Returns the decoded data field of the data record as an Object. The Object is of one of the four types Long, * Double, String or Date depending on information coded in the DIB/VIB. The DataType can be checked using * getDataValueType(). - * + * * @return the data value */ public Object getDataValue() { @@ -498,13 +496,17 @@ public DataValueType getDataValueType() { /** * Returns the data (value) multiplied by the multiplier as a Double. If the data is not a number than null is * returned. - * + * * @return the data (value) multiplied by the multiplier as a Double */ public Double getScaledDataValue() { - try { - return ((Number) dataValue).doubleValue() * Math.pow(10, multiplierExponent); - } catch (ClassCastException e) { + if (dataValue != null) { + try { + return ((Number) dataValue).doubleValue() * Math.pow(10, multiplierExponent); + } catch (ClassCastException e) { + return null; + } + } else { return null; } } @@ -540,7 +542,7 @@ public String getUserDefinedDescription() { /** * The multiplier is coded in the VIF. Is always a power of 10. This function returns the exponent. The base is * always 10. - * + * * @return the exponent of the multiplier. */ public int getMultiplierExponent() { @@ -567,7 +569,7 @@ private void decodeTimeUnit(int vif) { } } - private int decodeUserDefinedVif(byte[] buffer, int offset) throws DecodingException { + private int decodeUserDefinedVif(byte[] buffer, int offset) { int length = buffer[offset]; StringBuilder sb = new StringBuilder(); @@ -894,7 +896,7 @@ private void decodeMainExtendedVif(byte vif) throws DecodingException { description = Description.RESERVED; } else if ((vif & 0x7c) == 0x24) { // E010 01nn description = Description.STORAGE_INTERVALL; - this.unit = unitFor(vif); + this.unit = timeUnitFor(vif); } else if ((vif & 0x7f) == 0x28) { // E010 1000 description = Description.STORAGE_INTERVALL; unit = DlmsUnit.MONTH; @@ -908,19 +910,13 @@ private void decodeMainExtendedVif(byte vif) throws DecodingException { unit = DlmsUnit.SECOND; } else if ((vif & 0x7c) == 0x2c) { // E010 11nn description = Description.DURATION_LAST_READOUT; - this.unit = unitFor(vif); + this.unit = timeUnitFor(vif); } else if ((vif & 0x7c) == 0x30) { // E011 00nn description = Description.TARIF_DURATION; - switch (vif & 0x03) { - case 0: // E011 0000 - description = Description.NOT_SUPPORTED; // TODO: TARIF_START (Date/Time) - break; - default: - this.unit = unitFor(vif); - } + this.unit = timeUnitFor(vif); } else if ((vif & 0x7c) == 0x34) { // E011 01nn description = Description.TARIF_PERIOD; - this.unit = unitFor(vif); + this.unit = timeUnitFor(vif); } else if ((vif & 0x7f) == 0x38) { // E011 1000 description = Description.TARIF_PERIOD; unit = DlmsUnit.MONTH; @@ -960,7 +956,8 @@ private void decodeMainExtendedVif(byte vif) throws DecodingException { } else if ((vif & 0x7f) == 0x70) { // E111 0000 description = Description.NOT_SUPPORTED; // TODO: BATTERY_CHANGE_DATE_TIME } else if ((vif & 0x7f) == 0x71) { // E111 0001 - description = Description.NOT_SUPPORTED; // TODO: RF_LEVEL dBm + description = Description.RF_LEVEL; + this.unit = DlmsUnit.SIGNAL_STRENGTH; } else if ((vif & 0x7f) == 0x72) { // E111 0010 description = Description.NOT_SUPPORTED; // TODO: DAYLIGHT_SAVING (begin, ending, deviation) } else if ((vif & 0x7f) == 0x73) { // E111 0011 @@ -995,7 +992,7 @@ private static DlmsUnit unitBiggerFor(byte vif) throws DecodingException { } } - private static DlmsUnit unitFor(byte vif) throws DecodingException { + private static DlmsUnit timeUnitFor(byte vif) throws DecodingException { int u = vif & 0x03; switch (u) { case 0: // E010 1100 @@ -1315,8 +1312,8 @@ private void decodeAlternateExtendedVif(byte vif) { @Override public String toString() { - StringBuilder builder = new StringBuilder().append("DIB:").append(printHexBinary(dib)).append(", VIB:") - .append(printHexBinary(vib)).append(" -> descr:").append(description); + StringBuilder builder = new StringBuilder().append("DIB:").append(HexUtils.bytesToHex(dib)).append(", VIB:") + .append(HexUtils.bytesToHex(vib)).append(" -> descr:").append(description); if (description == Description.USER_DEFINED) { builder.append(" :").append(getUserDefinedDescription()); diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DlmsUnit.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DlmsUnit.java index 3efbf97..ccf85ae 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DlmsUnit.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/DlmsUnit.java @@ -12,7 +12,7 @@ * The units as defined in IEC 62056-6-2. Some units not defined in IEC 62056-6-2 but needed by M-Bus were added. */ public enum DlmsUnit { - // can be found in IEC 62056-6-2 2013 Capture 5.2.2 + // can be found in IEC 62056-6-2 2017 Capture 5.2.2 YEAR(1, "a"), MONTH(2, "mo"), WEEK(3, "wk"), @@ -34,8 +34,8 @@ public enum DlmsUnit { LITRE(19, "l"), KILOGRAM(20, "kg"), NEWTON(21, "N"), - NEWTONMETER(22, "n"), - PASCAL(23, "Nm"), + NEWTONMETER(22, "Nm"), + PASCAL(23, "Pa"), BAR(24, "bar"), JOULE(25, "J"), JOULE_PER_HOUR(26, "J/h"), @@ -63,10 +63,11 @@ public enum DlmsUnit { VOLT_SQUARED_HOURS(48, "V²h"), AMPERE_SQUARED_HOURS(49, "A²h"), KILOGRAM_PER_SECOND(50, "kg/s"), - KELVIN(52, "S"), - VOLT_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE(53, "K"), - AMPERE_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE(54, "1/(V²h)"), - METER_CONSTANT_OR_PULSE_VALUE(55, "1/(A²h)"), + SIEMENS(51, "S"), + KELVIN(52, "K"), + VOLT_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE(53, "1/(V²h)"), + AMPERE_SQUARED_HOUR_METER_CONSTANT_OR_PULSE_VALUE(54, "1/(A²h)"), + METER_CONSTANT_OR_PULSE_VALUE(55, "1/m³"), PERCENTAGE(56, "%"), AMPERE_HOUR(57, "Ah"), @@ -78,10 +79,13 @@ public enum DlmsUnit { SPECIFIC_ENERGY(65, "J/kg"), SIGNAL_STRENGTH(70, "dBm"), + SIGNAL_STRENGTH_MICROVOLT(71, "dBµv"), + LOGARITHMIC(72, "dB"), RESERVED(253, ""), - OTHER_UNIT(254, ""), - COUNT(255, ""), + OTHER_UNIT(254, "other"), + COUNT(255, "count"), + // not mentioned in 62056, added for MBus: CUBIC_METRE_PER_SECOND(150, "m³/s"), CUBIC_METRE_PER_MINUTE(151, "m³/min"), diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/HexUtils.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/HexUtils.java new file mode 100644 index 0000000..b5d5273 --- /dev/null +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/HexUtils.java @@ -0,0 +1,32 @@ +package org.openmuc.jmbus; + +public class HexUtils { + + private static final String HEXES = "0123456789ABCDEF"; + + public static String bytesToHex(byte[] bytes) { + if (bytes == null) { + return null; + } + final StringBuilder hex = new StringBuilder(2 * bytes.length); + for (final byte b : bytes) { + hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt((b & 0x0F))); + } + return hex.toString(); + } + + public static byte[] hexToBytes(String hexString) { + byte[] bytes = new byte[hexString.length() / 2]; + int index; + + for (int i = 0; i < bytes.length; i++) { + index = i * 2; + bytes[i] = (byte) Integer.parseInt(hexString.substring(index, index + 2), 16); + } + return bytes; + } + + private HexUtils() { + // hide it + } +} diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/MBusConnection.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/MBusConnection.java index d2d329e..8c53f3a 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/MBusConnection.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/MBusConnection.java @@ -5,10 +5,10 @@ */ package org.openmuc.jmbus; +import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.io.InterruptedIOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Arrays; @@ -31,6 +31,10 @@ */ public class MBusConnection implements AutoCloseable { + private static final int START_BYTE = 0x68; + private static final int STOP_BYTE = 0x16; + private static final int SINGLE_CHARACTER = 0xe5; + // 261 is the maximum size of a long frame private static final int MAX_MESSAGE_SIZE = 261; @@ -105,20 +109,22 @@ public void setVerboseMessageListener(VerboseMessageListener verboseMessageListe * @param secondaryAddressListener * listener to get scan messages and scanned secondary address just at time.
    * If null, all detected address will only returned if finished. + * @param delay + * delay between every sent message. Sometimes needed for slow devices. Deactivated if 0 or less. * * @return a list of secondary addresses of all detected devices * @throws IOException * if any kind of error (including timeout) occurs while writing to the remote device. Note that the * connection is not closed when an IOException is thrown. */ - public List scan(String wildcardMask, SecondaryAddressListener secondaryAddressListener) - throws IOException { + public List scan(String wildcardMask, SecondaryAddressListener secondaryAddressListener, + long delay) throws IOException { if (wildcardMask == null || wildcardMask.isEmpty()) { wildcardMask = "ffffffff"; } - return ScanSecondaryAddress.scan(this, wildcardMask, secondaryAddressListener); + return ScanSecondaryAddress.scan(this, wildcardMask, secondaryAddressListener, delay); } /** @@ -128,15 +134,11 @@ public List scan(String wildcardMask, SecondaryAddressListener * @param primaryAddress * the primary address of the meter to read. For secondary address use 0xfd. * @return the variable data structure from the received RSP_UD frame - * @throws InterruptedIOException - * if no response at all (not even a single byte) was received from the meter within the timeout span. * @throws IOException * if any kind of error (including timeout) occurs while trying to read the remote device. Note that the * connection is not closed when an IOException is thrown. - * @throws InterruptedIOException - * if no response at all (not even a single byte) was received from the meter within the timeout span. */ - public VariableDataStructure read(int primaryAddress) throws IOException, InterruptedIOException { + public VariableDataStructure read(int primaryAddress) throws IOException { if (transportLayer.isClosed()) { throw new IllegalStateException("Port is not open."); } @@ -247,10 +249,8 @@ public MBusMessage sendShortMessage(int primaryAddr, int cmd, boolean responseEx * @throws IOException * if any kind of error (including timeout) occurs while writing to the remote device. Note that the * connection is not closed when an IOException is thrown. - * @throws InterruptedIOException - * if no response at all (not even a single byte) was received from the meter within the timeout span. */ - public void write(int primaryAddress, byte[] data) throws IOException, InterruptedIOException { + public void write(int primaryAddress, byte[] data) throws IOException { if (data == null) { data = new byte[0]; } @@ -271,10 +271,8 @@ public void write(int primaryAddress, byte[] data) throws IOException, Interrupt * @throws IOException * if any kind of error (including timeout) occurs while trying to read the remote device. Note that the * connection is not closed when an IOException is thrown. - * @throws InterruptedIOException - * if no response at all (not even a single byte) was received from the meter within the timeout span. */ - public void selectComponent(SecondaryAddress secondaryAddress) throws IOException, InterruptedIOException { + public void selectComponent(SecondaryAddress secondaryAddress) throws IOException { this.secondaryAddress = secondaryAddress; componentSelection(false); } @@ -285,10 +283,8 @@ public void selectComponent(SecondaryAddress secondaryAddress) throws IOExceptio * @throws IOException * if any kind of error (including timeout) occurs while trying to read the remote device. Note that the * connection is not closed when an IOException is thrown. - * @throws InterruptedIOException - * if no response at all (not even a single byte) was received from the meter within the timeout span. */ - public void deselectComponent() throws IOException, InterruptedIOException { + public void deselectComponent() throws IOException { if (secondaryAddress == null) { return; } @@ -306,11 +302,8 @@ public void deselectComponent() throws IOException, InterruptedIOException { * @throws IOException * if any kind of error (including timeout) occurs while trying to read the remote device. Note that the * connection is not closed when an IOException is thrown. - * @throws InterruptedIOException - * if no response at all (not even a single byte) was received from the meter within the timeout span. */ - public void selectForReadout(int primaryAddress, List dataRecords) - throws IOException, InterruptedIOException { + public void selectForReadout(int primaryAddress, List dataRecords) throws IOException { int i = 0; for (DataRecord dataRecord : dataRecords) { i += dataRecord.encode(dataRecordsAsBytes, i); @@ -331,10 +324,8 @@ public void selectForReadout(int primaryAddress, List dataRecords) * @throws IOException * if any kind of error (including timeout) occurs while trying to read the remote device. Note that the * connection is not closed when an IOException is thrown. - * @throws InterruptedIOException - * if no response at all (not even a single byte) was received from the meter within the timeout span. */ - public void resetReadout(int primaryAddress) throws IOException, InterruptedIOException { + public void resetReadout(int primaryAddress) throws IOException { sendLongMessage(primaryAddress, 0x53, 0x50, 0, new byte[] {}); MBusMessage mBusMessage = receiveMessage(); @@ -348,14 +339,10 @@ public void resetReadout(int primaryAddress) throws IOException, InterruptedIOEx * * @param primaryAddress * the primary address of the meter to reset. - * @throws InterruptedIOException - * if the slave does not answer with an 0xe5 message within the configured timeout span. * @throws IOException * if an error occurs during the reset process. - * @throws InterruptedIOException - * if the slave does not answer with an 0xe5 message within the configured timeout span. */ - public void linkReset(int primaryAddress) throws IOException, InterruptedIOException { + public void linkReset(int primaryAddress) throws IOException { sendShortMessage(primaryAddress, 0x40); MBusMessage mBusMessage = receiveMessage(); @@ -366,7 +353,7 @@ public void linkReset(int primaryAddress) throws IOException, InterruptedIOExcep frameCountBits[primaryAddress] = true; } - private void componentSelection(boolean deselect) throws IOException, InterruptedIOException { + private void componentSelection(boolean deselect) throws IOException { byte[] ba = secondaryAddressAsBa(); // send select/deselect @@ -397,7 +384,7 @@ private void sendShortMessage(int slaveAddr, int cmd) throws IOException { outputBuffer[1] = (byte) (cmd); outputBuffer[2] = (byte) (slaveAddr); outputBuffer[3] = (byte) (cmd + slaveAddr); - outputBuffer[4] = 0x16; + outputBuffer[4] = STOP_BYTE; verboseMessage(MessageDirection.SEND, outputBuffer, 0, 5); @@ -407,10 +394,10 @@ private void sendShortMessage(int slaveAddr, int cmd) throws IOException { void sendLongMessage(int slaveAddr, int controlField, int ci, int length, byte[] data) throws IOException { synchronized (os) { - outputBuffer[0] = 0x68; + outputBuffer[0] = START_BYTE; outputBuffer[1] = (byte) (length + 3); outputBuffer[2] = (byte) (length + 3); - outputBuffer[3] = 0x68; + outputBuffer[3] = START_BYTE; outputBuffer[4] = (byte) controlField; outputBuffer[5] = (byte) slaveAddr; outputBuffer[6] = (byte) ci; @@ -421,7 +408,7 @@ void sendLongMessage(int slaveAddr, int controlField, int ci, int length, byte[] outputBuffer[length + 7] = computeChecksum(length, outputBuffer); - outputBuffer[length + 8] = 0x16; + outputBuffer[length + 8] = STOP_BYTE; verboseMessage(MessageDirection.SEND, outputBuffer, 0, length + 9); @@ -439,12 +426,10 @@ private static byte computeChecksum(int length, byte[] oBuffer) { MBusMessage receiveMessage() throws IOException { byte[] receivedBytes; - int b0 = is.read(); - if (b0 == 0xe5) { - // messageLength = 1; + if (b0 == SINGLE_CHARACTER) { receivedBytes = new byte[] { (byte) b0 }; - } else if ((b0 & 0xff) == 0x68) { + } else if ((b0 & 0xff) == START_BYTE) { int b1 = is.readByte() & 0xff; /** @@ -456,9 +441,21 @@ MBusMessage receiveMessage() throws IOException { receivedBytes[0] = (byte) b0; receivedBytes[1] = (byte) b1; - int lenRead = messageLength - 2; + is.readFully(receivedBytes, 2, messageLength - 2); + + if (receivedBytes[messageLength - 1] != STOP_BYTE) { + receivedBytes = readUntilStop(receivedBytes, messageLength); + } + // skip undesired Calling Direction Frame (echo) + if ((receivedBytes[4] & 0x40) == 0x40) { + return receiveMessage(); + } - is.readFully(receivedBytes, 2, lenRead); + } else if ((b0 & 0xff) == 0x10) { + // skip undesired Short Frame (echo) + receivedBytes = new byte[4]; + is.readFully(receivedBytes, 0, 4); + return receiveMessage(); } else { throw new IOException(String.format("Received unknown message: %02X", b0)); } @@ -468,6 +465,23 @@ MBusMessage receiveMessage() throws IOException { return MBusMessage.decode(receivedBytes, receivedBytes.length); } + private byte[] readUntilStop(byte[] receivedBytes, int messageLength) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(messageLength); + baos.write(receivedBytes); + byte read = is.readByte(); + baos.write(read); + try { + while (read != STOP_BYTE) { + read = is.readByte(); + baos.write(read); + } + } catch (Exception e) { + // ignore + } + receivedBytes = baos.toByteArray(); + return receivedBytes; + } + private void verboseMessage(MessageDirection direction, byte[] array, int from, int to) { if (this.verboseMessageListener != null) { byte[] message = Arrays.copyOfRange(array, from, to); diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/ScanSecondaryAddress.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/ScanSecondaryAddress.java index 84e04f6..6e38b5b 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/ScanSecondaryAddress.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/ScanSecondaryAddress.java @@ -5,8 +5,6 @@ */ package org.openmuc.jmbus; -import static javax.xml.bind.DatatypeConverter.printHexBinary; - import java.io.IOException; import java.io.InterruptedIOException; import java.nio.ByteBuffer; @@ -24,7 +22,7 @@ class ScanSecondaryAddress { private static byte[] value = new byte[MAX_LENGTH]; public static List scan(MBusConnection mBusConnection, String wildcardMask, - SecondaryAddressListener secondaryAddressListener) throws IOException { + SecondaryAddressListener secondaryAddressListener, long waitTime) throws IOException { List secondaryAddresses = new LinkedList<>(); @@ -45,17 +43,17 @@ public static List scan(MBusConnection mBusConnection, String value[pos] = 0; while (!stop) { - String msg = MessageFormat.format("scan with wildcard: {0}", printHexBinary(toSendByteArray(value))); + String msg = MessageFormat.format("scan with wildcard: {0}", HexUtils.bytesToHex(toSendByteArray(value))); notifyScanMsg(secondaryAddressListener, msg); SecondaryAddress secondaryAddessesWildCard = SecondaryAddress.newFromLongHeader(toSendByteArray(value), 0); SecondaryAddress readSecondaryAddress = null; if (scanSelection(mBusConnection, secondaryAddessesWildCard)) { + sleep(waitTime); try { readSecondaryAddress = mBusConnection.read(0xfd).getSecondaryAddress(); - } catch (InterruptedIOException e) { notifyScanMsg(secondaryAddressListener, "Read (REQ_UD2) TimeoutException"); collision = false; @@ -63,6 +61,7 @@ public static List scan(MBusConnection mBusConnection, String notifyScanMsg(secondaryAddressListener, "Read (REQ_UD2) IOException / Collision"); collision = true; } + sleep(waitTime); if (collision) { if (pos < 7) { @@ -112,7 +111,7 @@ public static List scan(MBusConnection mBusConnection, String * * @param wildcard * secondary address wildcard e.g. f1ffffffffffffff - * @return true if any device responsed else false + * @return true if any device response else false * @throws IOException */ private static boolean scanSelection(MBusConnection mBusConnection, SecondaryAddress wildcard) throws IOException { @@ -215,4 +214,14 @@ private static String flipString(String value) { */ private ScanSecondaryAddress() { } + + private static void sleep(long millis) throws IOException { + if (millis > 0) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + throw new IOException(e); + } + } + } } diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/SecondaryAddress.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/SecondaryAddress.java index 33e75d3..515bf42 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/SecondaryAddress.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/SecondaryAddress.java @@ -11,8 +11,6 @@ import java.nio.ByteOrder; import java.util.Arrays; -import javax.xml.bind.DatatypeConverter; - /** * This class represents a secondary address. Use the static initializer to initialize the */ @@ -52,7 +50,7 @@ public static SecondaryAddress newFromLongHeader(byte[] buffer, int offset) { * the offset. * @return a new secondary address. */ - public static SecondaryAddress newFromWMBusLlHeader(byte[] buffer, int offset) { + public static SecondaryAddress newFromWMBusHeader(byte[] buffer, int offset) { return new SecondaryAddress(buffer, offset, false); } @@ -71,15 +69,22 @@ public static SecondaryAddress newFromWMBusLlHeader(byte[] buffer, int offset) { * @throws NumberFormatException * if the idNumber is not long enough. */ - public static SecondaryAddress newFromManufactureId(byte[] idNumber, String manufactureId, byte version, byte media) - throws NumberFormatException { + public static SecondaryAddress newFromManufactureId(byte[] idNumber, String manufactureId, byte version, byte media, + boolean longHeader) throws NumberFormatException { if (idNumber.length != ID_NUMBER_LENGTH) { throw new NumberFormatException("Wrong length of ID. Length must be " + ID_NUMBER_LENGTH + " byte."); } byte[] mfId = encodeManufacturerId(manufactureId); - byte[] buffer = ByteBuffer.allocate(idNumber.length + mfId.length + 1 + 1).put(idNumber).put(mfId).put(version) - .put(media).array(); + + ByteBuffer byteBuffer = ByteBuffer.allocate(idNumber.length + mfId.length + 1 + 1); + if (longHeader) { + byteBuffer.put(idNumber).put(mfId); + } else { + byteBuffer.put(mfId).put(idNumber); + } + byte[] buffer = byteBuffer.put(version).put(media).array(); + return new SecondaryAddress(buffer, 0, true); } @@ -136,7 +141,7 @@ public boolean isLongHeader() { public String toString() { return new StringBuilder().append("manufacturer ID: ").append(manufacturerId).append(", device ID: ") .append(deviceId).append(", device version: ").append(version).append(", device type: ") - .append(deviceType).append(", as bytes: ").append(DatatypeConverter.printHexBinary(bytes)).toString(); + .append(deviceType).append(", as bytes: ").append(HexUtils.bytesToHex(bytes)).toString(); } @Override diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/VariableDataStructure.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/VariableDataStructure.java index f2ce2c8..88f45cf 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/VariableDataStructure.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/VariableDataStructure.java @@ -17,8 +17,6 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import javax.xml.bind.DatatypeConverter; - /** * Representation of the data transmitted in RESP-UD (M-Bus) and SND-NR (wM-Bus) messages. * @@ -54,6 +52,8 @@ public class VariableDataStructure { private List dataRecords; + private int ciField; + public VariableDataStructure(byte[] buffer, int offset, int length, SecondaryAddress linkLayerSecondaryAddress, Map keyMap) { this.buffer = buffer; @@ -72,7 +72,7 @@ public VariableDataStructure(byte[] buffer, int offset, int length, SecondaryAdd public void decode() throws DecodingException { if (!decoded) { try { - int ciField = readUnsignedByte(buffer, offset); + ciField = readUnsignedByte(buffer, offset); switch (ciField) { case 0x72: @@ -83,9 +83,9 @@ public void decode() throws DecodingException { decodeDataRecords(buffer, offset + 1, length - 1); break; case 0x7a: /* short header */ - decodeWithShortHeader(); + decodeShortHeader(); break; - case 0x8d: /* ELL */ + case 0x8d: /* Extended Link Layer */ decodeExtendedLinkLayer(buffer, offset + 1); // 6 bytes header + CRC header = Arrays.copyOfRange(buffer, offset, offset + 7); // don't include CRC vdr = new byte[length - 7]; @@ -117,25 +117,42 @@ public void decode() throws DecodingException { } catch (RuntimeException e) { throw new DecodingException(e); } - decoded = true; } } - private void decodeWithShortHeader() throws DecodingException { + private void decodeShortHeader() throws DecodingException { decodeShortHeader(buffer, offset + 1); - if (encryptionMode == EncryptionMode.NONE) { - decodeDataRecords(buffer, offset + 5, length - 5); - } else if (encryptionMode == EncryptionMode.AES_CBC_IV) { - decryptAesCbcIv(buffer, offset + 5, numberOfEncryptedBlocks * 16); - } else { - throw new DecodingException("Unsupported encryption mode used: " + encryptionMode); + + switch (encryptionMode) { + case NONE: + decodeDataRecords(buffer, offset + 5, length - 5); + break; + case AES_CBC_IV: + decryptAesCbcIv(buffer, offset + 5, numberOfEncryptedBlocks * 16); + break; + case AES_128: + case AES_CBC_IV_0: + case DES_CBC: + case DES_CBC_IV: + case RESERVED_04: + case RESERVED_06: + case RESERVED_08: + case RESERVED_09: + case RESERVED_10: + case RESERVED_11: + case RESERVED_12: + case RESERVED_14: + case RESERVED_15: + case TLS: + default: + throw new DecodingException("Unsupported encryption mode used: " + encryptionMode); } } private void decryptAesCbcIv(byte[] buffer, int offset, int encryptedDataLength) throws DecodingException { - final int len = length - 5; - vdr = new byte[len]; + vdr = new byte[encryptedDataLength]; + System.arraycopy(buffer, offset, vdr, 0, encryptedDataLength); byte[] key = keyMap.get(linkLayerSecondaryAddress); @@ -146,7 +163,7 @@ private void decryptAesCbcIv(byte[] buffer, int offset, int encryptedDataLength) throw new DecodingException(msg); } - decodeDataRecords(decryptMessage(key), 0, len); + decodeDataRecords(decryptMessage(key), 0, encryptedDataLength); } private void decodeLongHeaderData() throws DecodingException { @@ -160,10 +177,29 @@ private void decodeLongHeaderData() throws DecodingException { vdr = new byte[length - headerLength]; System.arraycopy(buffer, offset + headerLength, vdr, 0, length - headerLength); - if (encryptionMode == EncryptionMode.AES_CBC_IV) { - decryptMessage(getKey()); - } else if (encryptionMode != EncryptionMode.NONE) { - throw new DecodingException("Unsupported encryption mode used: " + encryptionMode); + switch (encryptionMode) { + case NONE: + // nothing to do + break; + case AES_CBC_IV: + decryptMessage(getKey()); + break; + case AES_128: + case AES_CBC_IV_0: + case DES_CBC: + case DES_CBC_IV: + case RESERVED_04: + case RESERVED_06: + case RESERVED_08: + case RESERVED_09: + case RESERVED_10: + case RESERVED_11: + case RESERVED_12: + case RESERVED_14: + case RESERVED_15: + case TLS: + default: + throw new DecodingException("Unsupported encryption mode used: " + encryptionMode); } decodeDataRecords(vdr, 0, length - headerLength); } @@ -223,7 +259,7 @@ private void decodeShortHeader(byte[] buffer, int offset) { numberOfEncryptedBlocks = (buffer[i++] & 0xf0) >> 4; encryptionMode = EncryptionMode.getInstance(buffer[i++] & 0x0f); - if (msgIsNotEnc(buffer, i)) { + if (msgIsNotEnc(buffer, i) || numberOfEncryptedBlocks == 0) { encryptionMode = EncryptionMode.NONE; } } @@ -263,7 +299,7 @@ private void decodeDataRecords(byte[] buffer, int offset, int length) throws Dec } DataRecord dataRecord = new DataRecord(); - i = dataRecord.decode(buffer, i, length); + i = dataRecord.decode(buffer, i); dataRecords.add(dataRecord); } @@ -302,7 +338,7 @@ private void decodeShortFrame(byte[] data, int offset, int length) throws Decodi os.write(b); DataRecord newDataRecord = new DataRecord(); - newDataRecord.decode(os.toByteArray(), 0, dataLegth); + newDataRecord.decode(os.toByteArray(), 0); iter.set(newDataRecord); } catch (IOException e) { // ignore @@ -356,6 +392,7 @@ private void decryptAes128(byte[] key, final int len) throws DecodingException { private void decryptAesCbcIv(byte[] key, final int len) throws DecodingException { byte[] iv = createIv(); byte[] result = AesCrypt.newAesCrypt(key, iv).decrypt(this.vdr, len); + if (!(result[0] == 0x2f && result[1] == 0x2f)) { throw new DecodingException(newDecyptionExceptionMsg()); } @@ -376,6 +413,11 @@ private byte[] createIv() { System.arraycopy(saBytes, 0, iv, 4, 2); // Manufacture System.arraycopy(saBytes, 2, iv, 0, 4); // Identification System.arraycopy(saBytes, 6, iv, 6, 2); // Version and Device Type + } else if (ciField == 0x72) { + saBytes = secondaryAddress.asByteArray(); + System.arraycopy(saBytes, 0, iv, 2, 4); // Identification + System.arraycopy(saBytes, 4, iv, 0, 2); // Manufacture + System.arraycopy(saBytes, 6, iv, 6, 2); // Version and Device Type } else { System.arraycopy(saBytes, 0, iv, 0, 8); } @@ -415,15 +457,19 @@ private byte[] getKey() throws DecodingException { @Override public String toString() { + StringBuilder builder = new StringBuilder(); if (!decoded) { - int from = offset; - int to = from + length; - String hexString = DatatypeConverter.printHexBinary(Arrays.copyOfRange(buffer, from, to)); - return MessageFormat.format("VariableDataResponse has not been decoded. Bytes:\n{0}", hexString); + if (dataRecords.isEmpty()) { + int from = offset; + int to = from + length; + String hexString = HexUtils.bytesToHex(Arrays.copyOfRange(buffer, from, to)); + return MessageFormat.format("VariableDataResponse has not been decoded. Bytes:\n{0}", hexString); + } else { + builder.append("VariableDataResponse has not been fully decoded. " + dataRecords.size() + + " data records decoded.\n"); + } } - StringBuilder builder = new StringBuilder(); - if (secondaryAddress != null) { builder.append("Secondary address: {").append(secondaryAddress).append("}\n"); } @@ -437,7 +483,7 @@ public String toString() { } if (manufacturerData.length != 0) { - String manDaraHexStr = DatatypeConverter.printHexBinary(manufacturerData); + String manDaraHexStr = HexUtils.bytesToHex(manufacturerData); builder.append("\nManufacturer specific bytes:\n").append(manDaraHexStr); } diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/TcpLayer.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/TcpLayer.java index d447ec1..73a7612 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/TcpLayer.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/transportlayer/TcpLayer.java @@ -56,6 +56,7 @@ private void initialiseIOStreams() throws IOException { close(); throw new IOException("Error getting output or input stream from TCP connection.", e); } + flushInputStream(); } @Override @@ -95,4 +96,21 @@ public void setTimeout(int timeout) throws IOException { public int getTimeout() throws IOException { return client.getSoTimeout(); } + + /** + * Flushes the input stream if it contains readable bytes + * + * @throws IOException + * if an error occurs while reading the input stream. + */ + private void flushInputStream() throws IOException { + try { + while (is.available() > 0) { + is.readFully(new byte[is.available()]); + } + } catch (IOException e) { + close(); + throw new IOException("Error flushing input stream from TCP connection.", e); + } + } } diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/HciMessageException.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/HciMessageException.java index 89175ba..995d6ad 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/HciMessageException.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/HciMessageException.java @@ -4,6 +4,7 @@ class HciMessageException extends IOException { + private static final long serialVersionUID = -1789394121831686912L; private final byte[] data; public HciMessageException(byte[] data) { diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionAmber.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionAmber.java index b24fd10..67981a3 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionAmber.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionAmber.java @@ -67,7 +67,14 @@ private void task() throws IOException { this.transportLayer.setTimeout(MESSAGE_FRAGEMENT_TIMEOUT); b1 = is.read(); + if (b0 == 0xff && b1 == 0x03) { + // it's optional if UART_CMD_Out_Enable is enabled on amber module + // then you will get also CRC at the end of message frame + continue; + } + if ((b1 ^ MBUS_BL_CONTROL) == 0) { + // we found beginning of mBUS frame, in the b0 will be the length of message break; } @@ -82,13 +89,13 @@ private void task() throws IOException { } } - int len = (b0 & 0xff) + 1; - byte[] data = new byte[2 + len]; + int length = (b0 & 0xff) + 1; // +1 because length don't count the length byte itself + byte[] data = new byte[length]; data[0] = (byte) b0; data[1] = (byte) b1; - int readLength = len - 2; + int readLength = length - 2; // we already have first two bytes int actualLength = is.read(data, 2, readLength); if (readLength != actualLength) { @@ -96,7 +103,22 @@ private void task() throws IOException { return; } - notifyListener(data); + if (is.available() > 0) { + // if there is one more bit it will be CRC, it's optional + byte crc = is.readByte(); + byte countedCRC = (byte) (0xFF ^ 0x03); + for (byte element : data) { + countedCRC = (byte) (countedCRC ^ element); + } + if (crc == countedCRC) { + notifyListener(data); + } else { + notifyDiscarded(data); + } + } else { + // parse data without CRC check + notifyListener(data); + } if (discardBuffer.position() > 0) { discard(discardBuffer.array(), 0, discardBuffer.position()); diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionImst.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionImst.java index 4c1f0c0..db68548 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionImst.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusConnectionImst.java @@ -12,13 +12,12 @@ import java.text.MessageFormat; import java.util.Arrays; -import javax.xml.bind.DatatypeConverter; - import org.openmuc.jmbus.DecodingException; +import org.openmuc.jmbus.HexUtils; import org.openmuc.jmbus.transportlayer.TransportLayer; /** - * Was tested with the IMST iM871A-USB Wireless M-Bus stick.
    + * Was tested with IMST iM871A-USB Wireless M-Bus stick.
    */ class WMBusConnectionImst extends AbstractWMBusConnection { @@ -123,7 +122,7 @@ private static byte linkRadioModeFor(WMBusMode mode) throws IOException { case C: return 0x08; // Link/Radio Mode: C2 with telegram format A (C2 + format B = 0x09) default: - String msg = MessageFormat.format("wMBUS Mode ''{0}'' is not supported", mode.toString()); + String msg = MessageFormat.format("wMBUS Mode ''{0}'' is not supported", mode); throw new IOException(msg); } } @@ -237,6 +236,10 @@ class Const { public static final byte RADIOLINK_MSG_WMBUSMSG_IND = 0x03; public static final byte RADIOLINK_MSG_DATA_REQ = 0x04; public static final byte RADIOLINK_MSG_DATA_RSP = 0x05; + + private Const() { + // hide constructor + } } /** @@ -289,7 +292,8 @@ private HciMessage(byte controlField, byte endpointID, byte msgId, int length, b public static HciMessage decode(TransportLayer transportLayer) throws IOException { DataInputStream is = transportLayer.getInputStream(); - byte b0, b1; + byte b0; + byte b1; transportLayer.setTimeout(0); b0 = is.readByte(); @@ -350,8 +354,8 @@ public String toString() { .append("\nEndpointID: ").append(byteAsHexString(endpointID)).append("\nMsg ID: ") .append(byteAsHexString(msgId)).append("\nLength: ").append(length) .append("\nTimestamp: ").append(timeStamp).append("\nRSSI: ").append(rSSI) - .append("\nFCS: ").append(fCS).append("\nPayload:\n") - .append(DatatypeConverter.printHexBinary(payload)).toString(); + .append("\nFCS: ").append(fCS).append("\nPayload:\n").append(HexUtils.bytesToHex(payload)) + .toString(); } private static String byteAsHexString(byte b) { diff --git a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusMessage.java b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusMessage.java index 8b99cb1..84ffea2 100644 --- a/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusMessage.java +++ b/org.openhab.binding.wmbus/src/main/java/org/openmuc/jmbus/wireless/WMBusMessage.java @@ -68,7 +68,7 @@ static WMBusMessage decode(byte[] buffer, Integer signalStrengthInDBm, Map ").append(secondaryAddress).append("\nVariable Data Response:\n") .append(vdr).toString(); } diff --git a/org.openhab.binding.wmbus/src/test/java/org/openhab/io/transport/mbus/wireless/MapKeyStorageTest.java b/org.openhab.binding.wmbus/src/test/java/org/openhab/io/transport/mbus/wireless/MapKeyStorageTest.java index 3046f5e..04ef034 100644 --- a/org.openhab.binding.wmbus/src/test/java/org/openhab/io/transport/mbus/wireless/MapKeyStorageTest.java +++ b/org.openhab.binding.wmbus/src/test/java/org/openhab/io/transport/mbus/wireless/MapKeyStorageTest.java @@ -14,7 +14,7 @@ public class MapKeyStorageTest { private static final String ADDRESS_HEX = "2423870723421147"; private static final byte[] ADDRESS_BYTE = HexUtils.hexToBytes(ADDRESS_HEX); - private static final SecondaryAddress ADDRESS_OBJECT = SecondaryAddress.newFromWMBusLlHeader(ADDRESS_BYTE, 0); + private static final SecondaryAddress ADDRESS_OBJECT = SecondaryAddress.newFromWMBusHeader(ADDRESS_BYTE, 0); private static final byte[] KEY = new byte[] { 0x01, 0x02 }; private final MapKeyStorage storage = new MapKeyStorage();