2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.souliss.internal.protocol;
15 import java.net.DatagramPacket;
16 import java.net.InetAddress;
17 import java.net.UnknownHostException;
18 import java.util.ArrayList;
19 import java.util.Iterator;
20 import java.util.List;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.openhab.binding.souliss.internal.SoulissBindingConstants;
25 import org.openhab.binding.souliss.internal.SoulissProtocolConstants;
26 import org.openhab.binding.souliss.internal.SoulissUDPConstants;
27 import org.openhab.binding.souliss.internal.discovery.DiscoverResult;
28 import org.openhab.binding.souliss.internal.handler.SoulissGatewayHandler;
29 import org.openhab.binding.souliss.internal.handler.SoulissGenericHandler;
30 import org.openhab.binding.souliss.internal.handler.SoulissT11Handler;
31 import org.openhab.binding.souliss.internal.handler.SoulissT12Handler;
32 import org.openhab.binding.souliss.internal.handler.SoulissT13Handler;
33 import org.openhab.binding.souliss.internal.handler.SoulissT14Handler;
34 import org.openhab.binding.souliss.internal.handler.SoulissT16Handler;
35 import org.openhab.binding.souliss.internal.handler.SoulissT18Handler;
36 import org.openhab.binding.souliss.internal.handler.SoulissT19Handler;
37 import org.openhab.binding.souliss.internal.handler.SoulissT1AHandler;
38 import org.openhab.binding.souliss.internal.handler.SoulissT22Handler;
39 import org.openhab.binding.souliss.internal.handler.SoulissT31Handler;
40 import org.openhab.binding.souliss.internal.handler.SoulissT41Handler;
41 import org.openhab.binding.souliss.internal.handler.SoulissT42Handler;
42 import org.openhab.binding.souliss.internal.handler.SoulissT5nHandler;
43 import org.openhab.binding.souliss.internal.handler.SoulissT61Handler;
44 import org.openhab.binding.souliss.internal.handler.SoulissT62Handler;
45 import org.openhab.binding.souliss.internal.handler.SoulissT63Handler;
46 import org.openhab.binding.souliss.internal.handler.SoulissT64Handler;
47 import org.openhab.binding.souliss.internal.handler.SoulissT65Handler;
48 import org.openhab.binding.souliss.internal.handler.SoulissT66Handler;
49 import org.openhab.binding.souliss.internal.handler.SoulissT67Handler;
50 import org.openhab.binding.souliss.internal.handler.SoulissT68Handler;
51 import org.openhab.binding.souliss.internal.handler.SoulissTopicsHandler;
52 import org.openhab.core.library.types.DecimalType;
53 import org.openhab.core.library.types.StringType;
54 import org.openhab.core.thing.Bridge;
55 import org.openhab.core.thing.Thing;
56 import org.openhab.core.thing.binding.ThingHandler;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
61 * This class decodes incoming Souliss packets, starting from decodevNet
63 * @author Tonino Fazio - Initial contribution
64 * @author Luca Calcaterra - Refactor for OH3
65 * @author Alessandro Del Pex - Souliss App
68 public class UDPDecoder {
70 private final Logger logger = LoggerFactory.getLogger(UDPDecoder.class);
71 private @Nullable DiscoverResult discoverResult;
72 private @Nullable SoulissGatewayHandler gwHandler;
74 private @Nullable Byte lastByteGatewayIp = null;
76 public UDPDecoder(Bridge bridge, @Nullable DiscoverResult pDiscoverResult) {
77 this.gwHandler = (SoulissGatewayHandler) bridge.getHandler();
78 this.discoverResult = pDiscoverResult;
79 var localGwHandler = this.gwHandler;
80 if (localGwHandler != null) {
81 this.lastByteGatewayIp = (byte) Integer
82 .parseInt(localGwHandler.getGwConfig().gatewayLanAddress.split("\\.")[3]);
87 * Get packet from VNET Frame
92 public void decodeVNetDatagram(DatagramPacket packet) {
93 int checklen = packet.getLength();
94 ArrayList<Byte> mac = new ArrayList<>();
95 for (var ig = 7; ig < checklen; ig++) {
96 mac.add((byte) (packet.getData()[ig] & 0xFF));
99 // Check if decoded Gw equal to ip of bridge handler or 1 (action messages)
100 Byte gwCheck = (byte) (packet.getData()[5] & 0xFF);
101 if ((gwCheck == 1) || (gwCheck.equals(this.lastByteGatewayIp))) {
102 decodeMacaco((byte) (packet.getData()[5] & 0xFF), mac);
107 * Decodes lower level MaCaCo packet
109 * @param lastByteGatewayIP
113 private void decodeMacaco(byte lastByteGatewayIP, ArrayList<Byte> macacoPck) {
114 int functionalCode = macacoPck.get(0);
115 switch (functionalCode) {
116 case SoulissUDPConstants.SOULISS_UDP_FUNCTION_PING_RESP:
117 logger.debug("Received functional code: 0x{}- Ping answer", Integer.toHexString(functionalCode));
118 decodePing(macacoPck);
121 case SoulissUDPConstants.SOULISS_UDP_FUNCTION_DISCOVER_GW_NODE_BCAST_RESP:
122 logger.debug("Received functional code: 0x{} - Discover a gateway node answer (broadcast)",
123 Integer.toHexString(functionalCode));
125 decodePingBroadcast(macacoPck);
126 } catch (UnknownHostException e) {
127 logger.warn("Error: {}", e.getLocalizedMessage());
131 case SoulissUDPConstants.SOULISS_UDP_FUNCTION_POLL_RESP:
132 logger.debug("Received functional code: 0x{} - subscribe response",
133 Integer.toHexString(functionalCode));
134 decodeStateRequest(macacoPck);
136 case SoulissUDPConstants.SOULISS_UDP_FUNCTION_SUBSCRIBE_RESP:
137 logger.debug("Received functional code: 0x{} - Read state answer", Integer.toHexString(functionalCode));
138 decodeStateRequest(macacoPck);
141 // Answer for assigned typical logic
142 case SoulissUDPConstants.SOULISS_UDP_FUNCTION_TYP_RESP:
143 logger.debug("Received functional code: 0x{}- Read typical logic answer",
144 Integer.toHexString(functionalCode));
145 decodeTypRequest(lastByteGatewayIP, macacoPck);
148 case SoulissUDPConstants.SOULISS_UDP_FUNCTION_HEALTHY_RESP:
150 logger.debug("Received functional code: 0x{} - Nodes Healthy", Integer.toHexString(functionalCode));
151 decodeHealthyRequest(macacoPck);
154 case (byte) SoulissUDPConstants.SOULISS_UDP_FUNCTION_DBSTRUCT_RESP:
155 logger.debug("Received functional code: 0x{} - Database structure answer",
156 Integer.toHexString(functionalCode));
157 decodeDBStructRequest(macacoPck);
160 logger.debug("Functional code not supported");
163 logger.debug("Data out of range");
166 logger.debug("Subscription refused");
168 case (byte) SoulissUDPConstants.SOULISS_UDP_FUNCTION_ACTION_MESSAGE:
169 logger.debug("Received functional code: 0x{} - Action Message (Topic)",
170 Integer.toHexString(functionalCode));
171 decodeActionMessages(macacoPck);
174 logger.debug("Received functional code: 0x{} - unused by OH Binding",
175 Integer.toHexString(functionalCode));
182 private void decodePing(ArrayList<Byte> mac) {
184 int putIn1 = mac.get(1);
186 int putIn2 = mac.get(2);
187 logger.debug("decodePing: putIn code: {}, {}", putIn1, putIn2);
188 var localGwHandler = this.gwHandler;
189 if (localGwHandler != null) {
190 localGwHandler.gatewayDetected();
194 private void decodePingBroadcast(ArrayList<Byte> macaco) throws UnknownHostException {
195 String ip = macaco.get(5) + "." + macaco.get(6) + "." + macaco.get(7) + "." + macaco.get(8);
196 byte[] addr = { (macaco.get(5)).byteValue(), (macaco.get(6)).byteValue(), (macaco.get(7)).byteValue(),
197 (macaco.get(8)).byteValue() };
198 logger.debug("decodePingBroadcast. Gateway Discovery. IP: {}", ip);
200 var localDiscoverResult = this.discoverResult;
201 if (localDiscoverResult != null) {
202 localDiscoverResult.gatewayDetected(InetAddress.getByAddress(addr), macaco.get(8).toString());
204 logger.debug("decodePingBroadcast aborted. 'discoverResult' is null");
209 * decode Typicals Request Packet
210 * It read Souliss Network and create OH items
212 * @param lastByteGatewayIP
216 private void decodeTypRequest(byte lastByteGatewayIP, ArrayList<Byte> mac) {
217 var localGwHandler = this.gwHandler;
218 if (localGwHandler != null) {
219 int typXnodo = localGwHandler.getMaxTypicalXnode();
221 byte tgtnode = mac.get(3);
222 int numberOf = mac.get(4);
224 // creates Souliss nodes
225 for (var j = 0; j < numberOf; j++) {
226 // create only not-empty typicals
227 if ((mac.get(5 + j) != 0) && (mac.get(5 + j) != SoulissProtocolConstants.SOULISS_T_RELATED)) {
228 byte typical = mac.get(5 + j);
229 byte slot = (byte) (j % typXnodo);
230 byte node = (byte) (j / typXnodo + tgtnode);
231 logger.debug("Thing Detected. IP (last byte): {}, Typical: 0x{}, Node: {}, Slot: {} ",
232 lastByteGatewayIP, Integer.toHexString(typical), node, slot);
234 var localDiscoverResult = this.discoverResult;
235 if (localDiscoverResult != null) {
236 localDiscoverResult.thingDetectedTypicals(lastByteGatewayIP, typical, node, slot);
238 logger.debug("decodeTypRequest aborted. 'discoverResult' is null");
246 * decode Typicals Request Packet
247 * It read Action Messages on Souliss Network and create items
249 * @param lastByteGatewayIP
253 private void decodeActionMessages(ArrayList<Byte> mac) {
255 String sTopicVariant;
259 // A 16-bit Topic Number: Define the topic itself
260 // A 8-bit Topic Variant : Define a variant for the topic
262 String[] sTopicNumberArray = { Integer.toHexString(mac.get(2)).toUpperCase(),
263 Integer.toHexString(mac.get(1)).toUpperCase() };
264 if (sTopicNumberArray[0].length() == 1) {
265 sTopicNumberArray[0] = "0" + sTopicNumberArray[0];
267 if (sTopicNumberArray[1].length() == 1) {
268 sTopicNumberArray[1] = "0" + sTopicNumberArray[1];
270 sTopicNumber = sTopicNumberArray[0] + sTopicNumberArray[1];
271 logger.debug("Topic Number: 0x{}", sTopicNumber);
273 sTopicVariant = Integer.toHexString(mac.get(3)).toUpperCase();
274 if (sTopicVariant.length() == 1) {
275 sTopicVariant = "0" + sTopicVariant;
277 logger.debug("Topic Variant: 0x{}", sTopicVariant);
278 if (mac.get(4) == 1) {
280 logger.debug("Topic Value (Payload one byte): {} ", Integer.toHexString(mac.get(5)).toUpperCase());
281 } else if (mac.get(4) == 2) {
282 byte[] value = { mac.get(5), mac.get(6) };
284 int shifted = value[1] << 8;
285 fRet = HalfFloatUtils.toFloat(shifted + value[0]);
286 logger.debug("Topic Value (Payload 2 bytes): {}", fRet);
288 var localGwHandler = this.gwHandler;
289 if (localGwHandler != null) {
290 var listThings = localGwHandler.getThing().getThings();
292 Boolean bIsPresent = false;
294 for (Thing t : listThings) {
295 if (t.getUID().toString().split(":")[2]
296 .equals(sTopicNumber + SoulissBindingConstants.UUID_NODE_SLOT_SEPARATOR + sTopicVariant)) {
297 var topicHandler = (SoulissTopicsHandler) (t.getHandler());
298 if (topicHandler != null) {
299 topicHandler.setState(DecimalType.valueOf(Float.toString(fRet)));
304 var localDiscoverResult = this.discoverResult;
305 if (localDiscoverResult != null && !bIsPresent) {
306 localDiscoverResult.thingDetectedActionMessages(sTopicNumber, sTopicVariant);
309 } catch (Exception uy) {
310 logger.warn("decodeActionMessages ERROR");
315 * decode DB Struct Request Packet
316 * It return Souliss Network:
318 * max supported number of nodes
319 * max typical per node
321 * See Souliss wiki for details
323 * @param lastByteGatewayIP
327 private void decodeDBStructRequest(ArrayList<Byte> mac) {
328 int nodes = mac.get(5);
329 int maxTypicalXnode = mac.get(7);
331 SoulissGatewayHandler localGwHandler = this.gwHandler;
332 if (localGwHandler != null) {
333 localGwHandler.setNodes(nodes);
334 localGwHandler.setMaxTypicalXnode(maxTypicalXnode);
335 localGwHandler.dbStructAnswerReceived();
340 * Decodes a souliss nodes health request
345 private void decodeHealthyRequest(ArrayList<Byte> mac) {
346 int numberOf = mac.get(4);
348 for (var i = 5; i < 5 + numberOf; i++) {
349 var localGwHandler = this.gwHandler;
350 if (localGwHandler != null) {
351 // build an array containing healths
352 List<Thing> listaThings = localGwHandler.getThing().getThings();
354 ThingHandler handler = null;
355 for (Thing thing : listaThings) {
356 if (thing.getThingTypeUID().equals(SoulissBindingConstants.TOPICS_THING_TYPE)) {
359 handler = thing.getHandler();
360 if (handler != null) {
362 if (((SoulissGenericHandler) handler).getNode() == tgtnode) {
363 ((SoulissGenericHandler) handler).setHealthy((mac.get(i)));
366 logger.debug("decode Healthy Request Warning. Thing handler is null");
373 private void decodeStateRequest(ArrayList<Byte> mac) {
374 int tgtnode = mac.get(3);
376 Iterator<Thing> thingsIterator = null;
377 var localGwHandler = this.gwHandler;
378 if (localGwHandler != null) {
379 thingsIterator = localGwHandler.getThing().getThings().iterator();
383 while (thingsIterator.hasNext() && !bFound) {
384 typ = thingsIterator.next();
385 // if a topic continue
387 if (typ.getThingTypeUID().equals(SoulissBindingConstants.TOPICS_THING_TYPE)) {
390 String[] sUIDArray = typ.getUID().getAsString().split(":");
391 ThingHandler handler = typ.getHandler();
392 // execute it only if binding is Souliss and update is for my
394 if (handler != null) {
400 if (((SoulissGenericHandler) handler).getNode() == tgtnode) {
402 int slot = ((SoulissGenericHandler) handler).getSlot();
404 var sVal = getByteAtSlot(mac, slot);
405 var decodingLiteralLabel = "Decoding {}{}";
406 var packetLabel = " packet";
408 switch (sUIDArray[1]) {
409 case SoulissBindingConstants.T11:
410 logger.debug(decodingLiteralLabel, SoulissBindingConstants.T11, packetLabel);
411 ((SoulissT11Handler) handler).setRawState(sVal);
413 case SoulissBindingConstants.T12:
414 logger.debug(decodingLiteralLabel, SoulissBindingConstants.T12, packetLabel);
415 ((SoulissT12Handler) handler).setRawState(sVal);
417 case SoulissBindingConstants.T13:
418 logger.debug(decodingLiteralLabel, SoulissBindingConstants.T13, packetLabel);
419 ((SoulissT13Handler) handler).setRawState(sVal);
421 case SoulissBindingConstants.T14:
422 logger.debug(decodingLiteralLabel, SoulissBindingConstants.T14, packetLabel);
423 ((SoulissT14Handler) handler).setRawState(sVal);
425 case SoulissBindingConstants.T16:
426 logger.debug(decodingLiteralLabel, SoulissBindingConstants.T16, packetLabel);
427 ((SoulissT16Handler) handler).setRawStateCommand(sVal);
428 ((SoulissT16Handler) handler).setRawStateRgb(getByteAtSlot(mac, slot + 1),
429 getByteAtSlot(mac, slot + 2), getByteAtSlot(mac, slot + 3));
432 case SoulissBindingConstants.T18:
433 logger.debug(decodingLiteralLabel, SoulissBindingConstants.T18, packetLabel);
434 ((SoulissT18Handler) handler).setRawState(sVal);
437 case SoulissBindingConstants.T19:
438 logger.debug(decodingLiteralLabel, SoulissBindingConstants.T19, packetLabel);
439 ((SoulissT19Handler) handler).setRawState(sVal);
440 ((SoulissT19Handler) handler).setRawStateDimmerValue(getByteAtSlot(mac, slot + 1));
443 case SoulissBindingConstants.T1A:
444 logger.debug(decodingLiteralLabel, SoulissBindingConstants.T1A, packetLabel);
445 ((SoulissT1AHandler) handler).setRawState(sVal);
447 case SoulissBindingConstants.T21:
448 case SoulissBindingConstants.T22:
449 logger.debug(decodingLiteralLabel,
450 SoulissBindingConstants.T21 + "/" + SoulissBindingConstants.T22, packetLabel);
451 ((SoulissT22Handler) handler).setRawState(sVal);
453 case SoulissBindingConstants.T31:
454 logger.debug("Decoding {}/{}", SoulissBindingConstants.T31,
455 SoulissBindingConstants.T31);
456 logger.debug("packet: ");
457 logger.debug("- bit0 (system on-off): {}", getBitState(sVal, 0));
458 logger.debug("- bit1 (heating on-off): {}", getBitState(sVal, 1));
459 logger.debug("- bit2 (cooling on-off): {}", getBitState(sVal, 2));
460 logger.debug("- bit3 (fan1 on-off): {}", getBitState(sVal, 3));
461 logger.debug("- bit4 (fan2 on-off): {}", getBitState(sVal, 4));
462 logger.debug("- bit5 (fan3 on-off): {}", getBitState(sVal, 5));
463 logger.debug("- bit6 (Manual/automatic fan mode): {}", getBitState(sVal, 6));
464 logger.debug("- bit7 (heating/cooling mode): {}", getBitState(sVal, 7));
466 ((SoulissT31Handler) handler).setRawStateValues(sVal, getFloatAtSlot(mac, slot + 1),
467 getFloatAtSlot(mac, slot + 3));
470 case SoulissBindingConstants.T41:
471 ((SoulissT41Handler) handler).setRawState(sVal);
473 case SoulissBindingConstants.T42:
474 ((SoulissT42Handler) handler).setRawState(sVal);
476 case SoulissProtocolConstants.SOULISS_T4N_NO_ANTITHEFT:
477 ((SoulissT42Handler) handler).setState(StringType
478 .valueOf(SoulissBindingConstants.T4N_ALARMOFF_MESSAGE_CHANNEL));
480 case SoulissProtocolConstants.SOULISS_T4N_ALARM:
481 ((SoulissT42Handler) handler).setState(StringType
482 .valueOf(SoulissBindingConstants.T4N_ALARMON_MESSAGE_CHANNEL));
488 case SoulissBindingConstants.T51:
489 case SoulissBindingConstants.T52:
490 case SoulissBindingConstants.T53:
491 case SoulissBindingConstants.T54:
492 case SoulissBindingConstants.T55:
493 case SoulissBindingConstants.T56:
494 case SoulissBindingConstants.T57:
495 case SoulissBindingConstants.T58:
496 logger.debug("Decoding T5n packet");
497 if (!Float.isNaN(getFloatAtSlot(mac, slot))) {
498 ((SoulissT5nHandler) handler).setFloatValue(getFloatAtSlot(mac, slot));
501 case SoulissBindingConstants.T61:
502 logger.debug(decodingLiteralLabel, SoulissBindingConstants.T61, packetLabel);
503 if (!Float.isNaN(getFloatAtSlot(mac, slot))) {
504 ((SoulissT61Handler) handler).setFloatValue(getFloatAtSlot(mac, slot));
507 case SoulissBindingConstants.T62:
508 logger.debug(decodingLiteralLabel, SoulissBindingConstants.T62, packetLabel);
509 if (!Float.isNaN(getFloatAtSlot(mac, slot))) {
510 ((SoulissT62Handler) handler).setFloatValue(getFloatAtSlot(mac, slot));
513 case SoulissBindingConstants.T63:
514 logger.debug(decodingLiteralLabel, SoulissBindingConstants.T63, packetLabel);
515 if (!Float.isNaN(getFloatAtSlot(mac, slot))) {
516 ((SoulissT63Handler) handler).setFloatValue(getFloatAtSlot(mac, slot));
519 case SoulissBindingConstants.T64:
520 logger.debug(decodingLiteralLabel, SoulissBindingConstants.T64, packetLabel);
521 if (!Float.isNaN(getFloatAtSlot(mac, slot))) {
522 ((SoulissT64Handler) handler).setFloatValue(getFloatAtSlot(mac, slot));
525 case SoulissBindingConstants.T65:
526 logger.debug(decodingLiteralLabel, SoulissBindingConstants.T65, packetLabel);
527 if (!Float.isNaN(getFloatAtSlot(mac, slot))) {
528 ((SoulissT65Handler) handler).setFloatValue(getFloatAtSlot(mac, slot));
531 case SoulissBindingConstants.T66:
532 logger.debug(decodingLiteralLabel, SoulissBindingConstants.T66, packetLabel);
533 if (!Float.isNaN(getFloatAtSlot(mac, slot))) {
534 ((SoulissT66Handler) handler).setFloatValue(getFloatAtSlot(mac, slot));
537 case SoulissBindingConstants.T67:
538 logger.debug(decodingLiteralLabel, SoulissBindingConstants.T67, packetLabel);
539 if (!Float.isNaN(getFloatAtSlot(mac, slot))) {
540 ((SoulissT67Handler) handler).setFloatValue(getFloatAtSlot(mac, slot));
543 case SoulissBindingConstants.T68:
544 logger.debug(decodingLiteralLabel, SoulissBindingConstants.T68, packetLabel);
545 if (!Float.isNaN(getFloatAtSlot(mac, slot))) {
546 ((SoulissT68Handler) handler).setFloatValue(getFloatAtSlot(mac, slot));
550 case SoulissBindingConstants.TOPICS:
551 logger.debug(decodingLiteralLabel, SoulissBindingConstants.TOPICS, packetLabel);
552 if (!Float.isNaN(getFloatAtSlot(mac, slot))) {
553 ((SoulissTopicsHandler) handler).setFloatValue(getFloatAtSlot(mac, slot));
557 logger.debug("Unsupported typical");
565 private byte getByteAtSlot(ArrayList<Byte> mac, int slot) {
566 return mac.get(5 + slot);
569 private float getFloatAtSlot(ArrayList<Byte> mac, int slot) {
570 int iOutput = mac.get(5 + slot) & 0xFF;
571 int iOutput2 = mac.get(5 + slot + 1) & 0xFF;
572 // we have two bytes, convert them...
573 int shifted = iOutput2 << 8;
574 return HalfFloatUtils.toFloat(shifted + iOutput);
577 public byte getBitState(byte vRaw, int iBit) {
578 final var maskBit1 = 0x1;
580 if (((vRaw >>> iBit) & maskBit1) == 0) {