]> git.basschouten.com Git - openhab-addons.git/blob
68be23da8b083339fcba433f52020e1c428a2168
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.plugwise.internal.protocol;
14
15 import static org.openhab.binding.plugwise.internal.protocol.field.MessageType.*;
16
17 import java.util.HashMap;
18 import java.util.Map;
19 import java.util.regex.Matcher;
20 import java.util.regex.Pattern;
21
22 import org.openhab.binding.plugwise.internal.protocol.field.MACAddress;
23 import org.openhab.binding.plugwise.internal.protocol.field.MessageType;
24
25 /**
26  * Acknowledgement message class - ACKs are used in the Plugwise protocol to serve different means, from acknowledging a
27  * message sent to the Stick by the host, as well as confirmation messages from nodes in the network for various
28  * purposes. Not all purposes are yet reverse-engineered.
29  *
30  * @author Wouter Born, Karel Goderis - Initial contribution
31  */
32 public class AcknowledgementMessage extends Message {
33
34     public enum ExtensionCode {
35         NOT_EXTENDED(0),
36         SENSE_INTERVAL_SET_ACK(179),
37         SENSE_INTERVAL_SET_NACK(180),
38         SENSE_BOUNDARIES_SET_ACK(181),
39         SENSE_BOUNDARIES_SET_NACK(182),
40         LIGHT_CALIBRATION_ACK(189),
41         SCAN_PARAMETERS_SET_ACK(190),
42         SCAN_PARAMETERS_SET_NACK(191),
43         SUCCESS(193),
44         ERROR(194),
45         CIRCLE_PLUS(221),
46         CLOCK_SET_ACK(215),
47         ON_ACK(216),
48         POWER_CALIBRATION_ACK(218),
49         OFF_ACK(222),
50         REAL_TIME_CLOCK_SET_ACK(223),
51         TIMEOUT(225),
52         ON_OFF_NACK(226),
53         REAL_TIME_CLOCK_SET_NACK(231),
54         SLEEP_SET_ACK(246),
55         POWER_LOG_INTERVAL_SET_ACK(248),
56         UNKNOWN(999);
57
58         private static final Map<Integer, ExtensionCode> TYPES_BY_VALUE = new HashMap<>();
59
60         static {
61             for (ExtensionCode type : ExtensionCode.values()) {
62                 TYPES_BY_VALUE.put(type.identifier, type);
63             }
64         }
65
66         public static ExtensionCode forValue(int value) {
67             return TYPES_BY_VALUE.get(value);
68         }
69
70         private int identifier;
71
72         private ExtensionCode(int value) {
73             identifier = value;
74         }
75
76         public int toInt() {
77             return identifier;
78         }
79     }
80
81     private static final Pattern V1_SHORT_PAYLOAD_PATTERN = Pattern.compile("(\\w{4})");
82     private static final Pattern V1_EXTENDED_PAYLOAD_PATTERN = Pattern.compile("(\\w{4})(\\w{16})");
83     private static final Pattern V2_EXTENDED_PAYLOAD_PATTERN = Pattern.compile("(\\w{16})(\\w{4})");
84
85     private ExtensionCode code;
86
87     public AcknowledgementMessage(MessageType messageType, int sequenceNumber, String payload) {
88         super(messageType, sequenceNumber, payload);
89     }
90
91     public ExtensionCode getExtensionCode() {
92         if (isExtended()) {
93             return code;
94         } else {
95             return ExtensionCode.NOT_EXTENDED;
96         }
97     }
98
99     @Override
100     public String getPayload() {
101         return payloadToHexString();
102     }
103
104     public boolean isError() {
105         return code == ExtensionCode.ERROR;
106     }
107
108     public boolean isExtended() {
109         return code != ExtensionCode.NOT_EXTENDED && code != ExtensionCode.SUCCESS && code != ExtensionCode.ERROR;
110     }
111
112     public boolean isSuccess() {
113         return code == ExtensionCode.SUCCESS;
114     }
115
116     public boolean isTimeOut() {
117         return code == ExtensionCode.TIMEOUT;
118     }
119
120     @Override
121     protected void parsePayload() {
122         if (getType() == ACKNOWLEDGEMENT_V1) {
123             parseV1Payload();
124         } else if (getType() == ACKNOWLEDGEMENT_V2) {
125             parseV2Payload();
126         }
127     }
128
129     private void parseV1Payload() {
130         Matcher shortMatcher = V1_SHORT_PAYLOAD_PATTERN.matcher(payload);
131         Matcher extendedMatcher = V1_EXTENDED_PAYLOAD_PATTERN.matcher(payload);
132
133         if (extendedMatcher.matches()) {
134             code = ExtensionCode.forValue(Integer.parseInt(extendedMatcher.group(1), 16));
135             if (code == null) {
136                 code = ExtensionCode.UNKNOWN;
137             }
138             macAddress = new MACAddress(extendedMatcher.group(2));
139         } else if (shortMatcher.matches()) {
140             code = ExtensionCode.forValue(Integer.parseInt(shortMatcher.group(1), 16));
141             if (code == null) {
142                 code = ExtensionCode.UNKNOWN;
143             }
144         } else {
145             code = ExtensionCode.UNKNOWN;
146             throw new PlugwisePayloadMismatchException(ACKNOWLEDGEMENT_V1, V1_SHORT_PAYLOAD_PATTERN,
147                     V1_EXTENDED_PAYLOAD_PATTERN, payload);
148         }
149     }
150
151     private void parseV2Payload() {
152         Matcher matcher = V2_EXTENDED_PAYLOAD_PATTERN.matcher(payload);
153
154         if (matcher.matches()) {
155             macAddress = new MACAddress(matcher.group(1));
156             code = ExtensionCode.forValue(Integer.parseInt(matcher.group(2), 16));
157             if (code == null) {
158                 code = ExtensionCode.UNKNOWN;
159             }
160         } else {
161             code = ExtensionCode.UNKNOWN;
162             throw new PlugwisePayloadMismatchException(ACKNOWLEDGEMENT_V2, V2_EXTENDED_PAYLOAD_PATTERN, payload);
163         }
164     }
165 }