]> git.basschouten.com Git - openhab-addons.git/blob
482c96d3ba78bdba7530db905c04e18866368bd4
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.velbus.internal.handler;
14
15 import static org.openhab.binding.velbus.internal.VelbusBindingConstants.*;
16
17 import java.util.Arrays;
18 import java.util.HashSet;
19 import java.util.Set;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.velbus.internal.VelbusChannelIdentifier;
26 import org.openhab.binding.velbus.internal.config.VelbusSensorConfig;
27 import org.openhab.binding.velbus.internal.packets.VelbusDimmerPacket;
28 import org.openhab.binding.velbus.internal.packets.VelbusPacket;
29 import org.openhab.binding.velbus.internal.packets.VelbusSensorReadoutRequestPacket;
30 import org.openhab.binding.velbus.internal.packets.VelbusStatusRequestPacket;
31 import org.openhab.core.library.types.PercentType;
32 import org.openhab.core.library.types.QuantityType;
33 import org.openhab.core.library.types.StringType;
34 import org.openhab.core.library.unit.MetricPrefix;
35 import org.openhab.core.library.unit.Units;
36 import org.openhab.core.thing.ChannelUID;
37 import org.openhab.core.thing.Thing;
38 import org.openhab.core.thing.ThingStatus;
39 import org.openhab.core.thing.ThingStatusDetail;
40 import org.openhab.core.thing.ThingTypeUID;
41 import org.openhab.core.types.Command;
42 import org.openhab.core.types.RefreshType;
43
44 /**
45  * The {@link VelbusVMB4ANHandler} is responsible for handling commands, which are
46  * sent to one of the channels.
47  *
48  * @author Cedric Boon - Initial contribution
49  */
50 @NonNullByDefault
51 public class VelbusVMB4ANHandler extends VelbusSensorWithAlarmClockHandler {
52     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = new HashSet<>(Arrays.asList(THING_TYPE_VMB4AN));
53
54     private static final String ALARM_GROUP = "alarm";
55     private static final String ANALOG_INPUT_GROUP = "analogInput";
56     private static final String ANALOG_OUTPUT_GROUP = "analogOutput";
57     private static final String RAW_CHANNEL_SUFFIX = "Raw";
58
59     private static final byte VOLTAGE_SENSOR_TYPE = 0x00;
60     private static final byte CURRENT_SENSOR_TYPE = 0x01;
61     private static final byte RESISTANCE_SENSOR_TYPE = 0x02;
62     private static final byte PERIOD_MEASUREMENT_SENSOR_TYPE = 0x03;
63
64     private String[] channelText = new String[] { "", "", "", "" };
65
66     private @Nullable ScheduledFuture<?> refreshJob;
67     private @NonNullByDefault({}) VelbusSensorConfig sensorConfig;
68
69     public VelbusVMB4ANHandler(Thing thing) {
70         super(thing, 0);
71     }
72
73     @Override
74     public void initialize() {
75         this.sensorConfig = getConfigAs(VelbusSensorConfig.class);
76
77         super.initialize();
78
79         initializeAutomaticRefresh();
80     }
81
82     private void initializeAutomaticRefresh() {
83         int refreshInterval = sensorConfig.refresh;
84
85         if (refreshInterval > 0) {
86             startAutomaticRefresh(refreshInterval);
87         }
88     }
89
90     @Override
91     public void dispose() {
92         final ScheduledFuture<?> refreshJob = this.refreshJob;
93         if (refreshJob != null) {
94             refreshJob.cancel(true);
95         }
96     }
97
98     private void startAutomaticRefresh(int refreshInterval) {
99         VelbusBridgeHandler velbusBridgeHandler = getVelbusBridgeHandler();
100         if (velbusBridgeHandler == null) {
101             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
102             return;
103         }
104
105         refreshJob = scheduler.scheduleWithFixedDelay(() -> {
106             sendSensorReadoutRequest(velbusBridgeHandler, ALL_CHANNELS);
107         }, 0, refreshInterval, TimeUnit.SECONDS);
108     }
109
110     @Override
111     public void handleCommand(ChannelUID channelUID, Command command) {
112         super.handleCommand(channelUID, command);
113
114         VelbusBridgeHandler velbusBridgeHandler = getVelbusBridgeHandler();
115         if (velbusBridgeHandler == null) {
116             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
117             return;
118         }
119
120         byte channelByte = convertChannelUIDToChannelByte(channelUID);
121
122         if (command instanceof RefreshType) {
123             VelbusStatusRequestPacket packet = new VelbusStatusRequestPacket(channelByte);
124
125             byte[] packetBytes = packet.getBytes();
126             velbusBridgeHandler.sendPacket(packetBytes);
127         } else if (command instanceof PercentType percentCommand && isAnalogOutputChannel(channelUID)) {
128             VelbusDimmerPacket packet = new VelbusDimmerPacket(
129                     new VelbusChannelIdentifier(this.getModuleAddress().getAddress(), channelByte), COMMAND_SET_VALUE,
130                     percentCommand.byteValue(), 0x00, false);
131
132             byte[] packetBytes = packet.getBytes();
133             velbusBridgeHandler.sendPacket(packetBytes);
134         }
135     }
136
137     protected void sendSensorReadoutRequest(VelbusBridgeHandler velbusBridgeHandler, byte channel) {
138         VelbusSensorReadoutRequestPacket packet = new VelbusSensorReadoutRequestPacket(getModuleAddress().getAddress(),
139                 channel);
140
141         byte[] packetBytes = packet.getBytes();
142         velbusBridgeHandler.sendPacket(packetBytes);
143     }
144
145     @Override
146     public void onPacketReceived(byte[] packet) {
147         super.onPacketReceived(packet);
148
149         logger.trace("onPacketReceived() was called");
150
151         if (packet[0] == VelbusPacket.STX && packet.length >= 5) {
152             byte command = packet[4];
153
154             if (command == COMMAND_SENSOR_RAW_DATA && packet.length >= 10) {
155                 byte channel = packet[5];
156                 byte operatingMode = packet[6];
157                 byte upperByteSensorValue = packet[7];
158                 byte highByteSensorValue = packet[8];
159                 byte lowByteSensorValue = packet[9];
160
161                 double sensorValue = (((upperByteSensorValue & 0xff) << 16) + ((highByteSensorValue & 0xff) << 8)
162                         + (lowByteSensorValue & 0xff));
163                 String channelUID = convertAnalogInputChannelByteToRawChannelUID(channel);
164
165                 switch (operatingMode) {
166                     case VOLTAGE_SENSOR_TYPE:
167                         double voltageResolution = 0.25;
168                         double voltageSensorValueState = sensorValue * voltageResolution;
169                         updateState(channelUID,
170                                 new QuantityType<>(voltageSensorValueState, MetricPrefix.MILLI(Units.VOLT)));
171                         break;
172                     case CURRENT_SENSOR_TYPE:
173                         double currentResolution = 5;
174                         double currentSensorValueState = sensorValue * currentResolution;
175                         updateState(channelUID,
176                                 new QuantityType<>(currentSensorValueState, MetricPrefix.MICRO(Units.AMPERE)));
177                         break;
178                     case RESISTANCE_SENSOR_TYPE:
179                         double resistanceResolution = 0.25;
180                         double resistanceSensorValueState = sensorValue * resistanceResolution;
181                         updateState(channelUID, new QuantityType<>(resistanceSensorValueState, Units.OHM));
182                         break;
183                     case PERIOD_MEASUREMENT_SENSOR_TYPE:
184                         double periodResolution = 0.5;
185                         double periodSensorValueState = sensorValue * periodResolution;
186                         updateState(channelUID,
187                                 new QuantityType<>(periodSensorValueState, MetricPrefix.MICRO(Units.SECOND)));
188                         break;
189                 }
190             } else if (command == COMMAND_TEXT) {
191                 byte channel = packet[5];
192                 byte textStartPosition = packet[6];
193
194                 StringBuilder contents = new StringBuilder();
195                 for (int i = 7; i < packet.length - 2; i++) {
196                     byte currentChar = packet[i];
197                     if (currentChar == (byte) -0x50) {
198                         contents.append("°");
199                     } else if (currentChar != (byte) 0x00) {
200                         contents.append((char) currentChar);
201                     }
202                 }
203
204                 channelText[channel - 9] = channelText[channel - 9].substring(0, textStartPosition)
205                         + contents.toString()
206                         + (channelText[channel - 9].length() > textStartPosition + 5 ? channelText[channel - 9]
207                                 .substring(textStartPosition + 5, channelText[channel - 9].length()) : "");
208
209                 String channelUID = convertAnalogInputChannelByteToChannelUID(channel);
210                 updateState(channelUID, new StringType(channelText[channel - 9]));
211             }
212         }
213     }
214
215     protected byte convertChannelUIDToChannelByte(ChannelUID channelUID) {
216         if (isAlarmChannel(channelUID)) {
217             return convertAlarmChannelUIDToChannelByte(channelUID);
218         } else if (isTextAnalogInputChannel(channelUID)) {
219             return convertTextAnalogInputChannelUIDToChannelByte(channelUID);
220         } else if (isRawAnalogInputChannel(channelUID)) {
221             return convertRawAnalogInputChannelUIDToChannelByte(channelUID);
222         } else if (isAnalogOutputChannel(channelUID)) {
223             return convertAnalogOutputChannelUIDToChannelByte(channelUID);
224         } else {
225             throw new UnsupportedOperationException(
226                     "The channel '" + channelUID + "' is not supported on a VMB4AN module.");
227         }
228     }
229
230     protected boolean isAlarmChannel(ChannelUID channelUID) {
231         return ALARM_GROUP.equals(channelUID.getGroupId());
232     }
233
234     protected byte convertAlarmChannelUIDToChannelByte(ChannelUID channelUID) {
235         return Byte.parseByte(channelUID.getIdWithoutGroup().replaceAll(CHANNEL, ""));
236     }
237
238     protected boolean isTextAnalogInputChannel(ChannelUID channelUID) {
239         return ANALOG_INPUT_GROUP.equals(channelUID.getGroupId())
240                 && !channelUID.getIdWithoutGroup().endsWith(RAW_CHANNEL_SUFFIX);
241     }
242
243     protected boolean isRawAnalogInputChannel(ChannelUID channelUID) {
244         return ANALOG_INPUT_GROUP.equals(channelUID.getGroupId())
245                 && channelUID.getIdWithoutGroup().endsWith(RAW_CHANNEL_SUFFIX);
246     }
247
248     protected byte convertRawAnalogInputChannelUIDToChannelByte(ChannelUID channelUID) {
249         return Byte
250                 .parseByte(channelUID.getIdWithoutGroup().replaceAll(CHANNEL, "").replaceAll(RAW_CHANNEL_SUFFIX, ""));
251     }
252
253     protected byte convertTextAnalogInputChannelUIDToChannelByte(ChannelUID channelUID) {
254         return Byte.parseByte(channelUID.getIdWithoutGroup().replaceAll(CHANNEL, ""));
255     }
256
257     protected String convertAnalogInputChannelByteToRawChannelUID(byte channelByte) {
258         return convertAnalogInputChannelByteToChannelUID(channelByte) + RAW_CHANNEL_SUFFIX;
259     }
260
261     protected String convertAnalogInputChannelByteToChannelUID(byte channelByte) {
262         return ANALOG_INPUT_GROUP + "#" + CHANNEL + channelByte;
263     }
264
265     protected boolean isAnalogOutputChannel(ChannelUID channelUID) {
266         return ANALOG_OUTPUT_GROUP.equals(channelUID.getGroupId());
267     }
268
269     protected byte convertAnalogOutputChannelUIDToChannelByte(ChannelUID channelUID) {
270         return Byte.parseByte(channelUID.getIdWithoutGroup().replaceAll(CHANNEL, ""));
271     }
272
273     @Override
274     protected int getClockAlarmAndProgramSelectionIndexInModuleStatus() {
275         return 8;
276     }
277 }