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.modbus.studer.internal;
15 import static org.openhab.binding.modbus.studer.internal.StuderBindingConstants.*;
17 import java.util.ArrayList;
18 import java.util.Optional;
21 import javax.measure.Unit;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.modbus.handler.ModbusEndpointThingHandler;
26 import org.openhab.binding.modbus.studer.internal.StuderParser.ModeXtender;
27 import org.openhab.binding.modbus.studer.internal.StuderParser.VSMode;
28 import org.openhab.binding.modbus.studer.internal.StuderParser.VTMode;
29 import org.openhab.binding.modbus.studer.internal.StuderParser.VTType;
30 import org.openhab.core.io.transport.modbus.AsyncModbusFailure;
31 import org.openhab.core.io.transport.modbus.ModbusBitUtilities;
32 import org.openhab.core.io.transport.modbus.ModbusCommunicationInterface;
33 import org.openhab.core.io.transport.modbus.ModbusConstants.ValueType;
34 import org.openhab.core.io.transport.modbus.ModbusReadFunctionCode;
35 import org.openhab.core.io.transport.modbus.ModbusReadRequestBlueprint;
36 import org.openhab.core.io.transport.modbus.ModbusRegisterArray;
37 import org.openhab.core.io.transport.modbus.PollTask;
38 import org.openhab.core.library.types.DecimalType;
39 import org.openhab.core.library.types.OnOffType;
40 import org.openhab.core.library.types.QuantityType;
41 import org.openhab.core.library.types.StringType;
42 import org.openhab.core.thing.Bridge;
43 import org.openhab.core.thing.ChannelUID;
44 import org.openhab.core.thing.Thing;
45 import org.openhab.core.thing.ThingStatus;
46 import org.openhab.core.thing.ThingStatusDetail;
47 import org.openhab.core.thing.ThingStatusInfo;
48 import org.openhab.core.thing.ThingTypeUID;
49 import org.openhab.core.thing.binding.BaseThingHandler;
50 import org.openhab.core.thing.binding.ThingHandler;
51 import org.openhab.core.types.Command;
52 import org.openhab.core.types.State;
53 import org.openhab.core.types.UnDefType;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
58 * The {@link StuderHandler} is responsible for handling commands, which are
59 * sent to one of the channels.
61 * @author Giovanni Mirulla - Initial contribution
64 public class StuderHandler extends BaseThingHandler {
66 private final Logger logger = LoggerFactory.getLogger(StuderHandler.class);
68 private @Nullable StuderConfiguration config;
71 * Array of tasks used to poll the device
73 private ArrayList<PollTask> pollTasks = new ArrayList<PollTask>();
76 * Communication interface to the slave endpoint we're connecting to
78 protected volatile @Nullable ModbusCommunicationInterface comms = null;
81 * Importing parser methods and enums
83 final StuderParser parser = new StuderParser();
85 * Support variable for type of thing
87 protected ThingTypeUID type;
90 * Array of registers of Studer slave to read, we store this once initialization is complete
92 private Integer[] registers = new Integer[0];
95 * Instances of this handler
97 * @param thing the thing to handle
98 * @param type the type of thing to handle
99 * @param slaveAddress the address of thing
100 * @param refreshSec the address of thing
102 public StuderHandler(Thing thing) {
104 this.type = thing.getThingTypeUID();
108 public void handleCommand(ChannelUID channelUID, Command command) {
110 // Currently we do not support any commands
115 * Load the config object
116 * Connect to the slave bridge
117 * Get registers to poll
118 * Start the periodic polling
121 public void initialize() {
122 config = getConfigAs(StuderConfiguration.class);
123 logger.debug("Initializing thing with configuration: {}", thing.getConfiguration());
129 * This method starts the operation of this handler
130 * Connect to the slave bridge
131 * Get registers to poll
132 * Start the periodic polling
134 private void startUp() {
138 if (comms == null || config == null) {
139 logger.debug("Invalid endpoint/config/manager ref for studer handler");
143 if (!pollTasks.isEmpty()) {
147 if (type.equals(THING_TYPE_BSP)) {
148 Set<Integer> keys = CHANNELS_BSP.keySet();
149 registers = keys.toArray(new Integer[keys.size()]);
150 } else if (type.equals(THING_TYPE_XTENDER)) {
151 Set<Integer> keys = CHANNELS_XTENDER.keySet();
152 registers = keys.toArray(new Integer[keys.size()]);
153 } else if (type.equals(THING_TYPE_VARIOTRACK)) {
154 Set<Integer> keys = CHANNELS_VARIOTRACK.keySet();
155 registers = keys.toArray(new Integer[keys.size()]);
156 } else if (type.equals(THING_TYPE_VARIOSTRING)) {
157 Set<Integer> keys = CHANNELS_VARIOSTRING.keySet();
158 registers = keys.toArray(new Integer[keys.size()]);
161 for (int r : registers) {
168 * Dispose the binding correctly
171 public void dispose() {
176 * Unregister the poll tasks and release the endpoint reference
178 private void tearDown() {
179 unregisterPollTasks();
180 unregisterEndpoint();
184 * Get the endpoint handler from the bridge this handler is connected to
185 * Checks that we're connected to the right type of bridge
187 * @return the endpoint handler or null if the bridge does not exist
189 private @Nullable ModbusEndpointThingHandler getEndpointThingHandler() {
190 Bridge bridge = getBridge();
191 if (bridge == null) {
192 logger.debug("Bridge is null");
195 if (bridge.getStatus() != ThingStatus.ONLINE) {
196 logger.debug("Bridge is not online");
200 ThingHandler handler = bridge.getHandler();
201 if (handler == null) {
202 logger.debug("Bridge handler is null");
206 if (handler instanceof ModbusEndpointThingHandler) {
207 ModbusEndpointThingHandler slaveEndpoint = (ModbusEndpointThingHandler) handler;
208 return slaveEndpoint;
210 logger.debug("Unexpected bridge handler: {}", handler);
216 * Get a reference to the modbus endpoint
218 private void connectEndpoint() {
223 ModbusEndpointThingHandler slaveEndpointThingHandler = getEndpointThingHandler();
224 if (slaveEndpointThingHandler == null) {
225 @SuppressWarnings("null")
226 String label = Optional.ofNullable(getBridge()).map(b -> b.getLabel()).orElse("<null>");
227 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
228 String.format("Bridge '%s' is offline", label));
229 logger.debug("No bridge handler available -- aborting init for {}", label);
232 comms = slaveEndpointThingHandler.getCommunicationInterface();
234 @SuppressWarnings("null")
235 String label = Optional.ofNullable(getBridge()).map(b -> b.getLabel()).orElse("<null>");
236 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
237 String.format("Bridge '%s' not completely initialized", label));
238 logger.debug("Bridge not initialized fully (no endpoint) -- aborting init for {}", this);
244 * Remove the endpoint if exists
246 private void unregisterEndpoint() {
247 // Comms will be close()'d by endpoint thing handler
251 private synchronized void unregisterPollTasks() {
252 if (pollTasks.isEmpty()) {
255 logger.debug("Unregistering polling from ModbusManager");
256 ModbusCommunicationInterface mycomms = comms;
257 if (mycomms != null) {
258 for (PollTask t : pollTasks) {
259 mycomms.unregisterRegularPoll(t);
267 * This is where we set up our regular poller
269 private synchronized void registerPollTask(int registerNumber) {
270 if (pollTasks.size() >= registers.length) {
271 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
272 throw new IllegalStateException("New pollTask invalid");
274 ModbusCommunicationInterface mycomms = comms;
275 StuderConfiguration studerConfig = config;
276 if (studerConfig == null || mycomms == null) {
277 throw new IllegalStateException("registerPollTask called without proper configuration");
280 logger.debug("Setting up regular polling");
282 ModbusReadRequestBlueprint request = new ModbusReadRequestBlueprint(studerConfig.slaveAddress,
283 ModbusReadFunctionCode.READ_INPUT_REGISTERS, registerNumber, 2, studerConfig.maxTries);
284 long refreshMillis = studerConfig.refresh * 1000;
285 PollTask pollTask = mycomms.registerRegularPoll(request, refreshMillis, 1000, result -> {
286 if (result.getRegisters().isPresent()) {
287 ModbusRegisterArray reg = result.getRegisters().get();
288 handlePolledData(registerNumber, reg);
290 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
293 if (getThing().getStatus() != ThingStatus.ONLINE) {
294 updateStatus(ThingStatus.ONLINE);
296 }, this::handleError);
297 pollTasks.add(pollTask);
301 * This method is called each time new data has been polled from the modbus slave
302 * The register array is first parsed, then each of the channels are updated
305 * @param n register readed
306 * @param registers byte array read from the modbus slave
308 protected void handlePolledData(int registerNumber, ModbusRegisterArray registers) {
309 Optional<DecimalType> quantity = ModbusBitUtilities.extractStateFromRegisters(registers, 0, ValueType.FLOAT32);
310 quantity.ifPresent(value -> {
311 if (type.equals(THING_TYPE_BSP)) {
312 Unit<?> unit = UNIT_CHANNELS_BSP.get(registerNumber);
314 internalUpdateState(CHANNELS_BSP.get(registerNumber), new QuantityType<>(value, unit));
316 } else if (type.equals(THING_TYPE_XTENDER)) {
317 handlePolledDataXtender(registerNumber, value);
318 } else if (type.equals(THING_TYPE_VARIOTRACK)) {
319 handlePolledDataVarioTrack(registerNumber, value);
320 } else if (type.equals(THING_TYPE_VARIOSTRING)) {
321 handlePolledDataVarioString(registerNumber, value);
324 resetCommunicationError();
328 * This method is called each time new data has been polled from the VarioString slave
329 * The register array is first parsed, then each of the channels are updated
332 protected void handlePolledDataVarioString(int registerNumber, DecimalType quantity) {
333 switch (CHANNELS_VARIOSTRING.get(registerNumber)) {
334 case CHANNEL_PV_OPERATING_MODE:
335 case CHANNEL_PV1_OPERATING_MODE:
336 case CHANNEL_PV2_OPERATING_MODE:
337 VSMode vsmode = StuderParser.getVSModeByCode(quantity.intValue());
338 if (vsmode == VSMode.UNKNOWN) {
339 internalUpdateState(CHANNELS_VARIOSTRING.get(registerNumber), UnDefType.UNDEF);
341 internalUpdateState(CHANNELS_VARIOSTRING.get(registerNumber), new StringType(vsmode.name()));
344 case CHANNEL_STATE_VARIOSTRING:
345 OnOffType vsstate = StuderParser.getStateByCode(quantity.intValue());
346 internalUpdateState(CHANNELS_VARIOSTRING.get(registerNumber), vsstate);
349 Unit<?> unit = UNIT_CHANNELS_VARIOSTRING.get(registerNumber);
351 internalUpdateState(CHANNELS_VARIOSTRING.get(registerNumber), new QuantityType<>(quantity, unit));
357 * This method is called each time new data has been polled from the VarioTrack slave
358 * The register array is first parsed, then each of the channels are updated
361 protected void handlePolledDataVarioTrack(int registerNumber, DecimalType quantity) {
362 switch (CHANNELS_VARIOTRACK.get(registerNumber)) {
363 case CHANNEL_MODEL_VARIOTRACK:
364 VTType type = StuderParser.getVTTypeByCode(quantity.intValue());
365 if (type == VTType.UNKNOWN) {
366 internalUpdateState(CHANNELS_VARIOTRACK.get(registerNumber), UnDefType.UNDEF);
368 internalUpdateState(CHANNELS_VARIOTRACK.get(registerNumber), new StringType(type.name()));
372 case CHANNEL_OPERATING_MODE:
373 VTMode vtmode = StuderParser.getVTModeByCode(quantity.intValue());
374 if (vtmode == VTMode.UNKNOWN) {
375 internalUpdateState(CHANNELS_VARIOTRACK.get(registerNumber), UnDefType.UNDEF);
377 internalUpdateState(CHANNELS_VARIOTRACK.get(registerNumber), new StringType(vtmode.name()));
381 case CHANNEL_STATE_VARIOTRACK:
382 OnOffType vtstate = StuderParser.getStateByCode(quantity.intValue());
383 internalUpdateState(CHANNELS_VARIOTRACK.get(registerNumber), vtstate);
386 Unit<?> unit = UNIT_CHANNELS_VARIOTRACK.get(registerNumber);
388 internalUpdateState(CHANNELS_VARIOTRACK.get(registerNumber), new QuantityType<>(quantity, unit));
394 * This method is called each time new data has been polled from the Xtender slave
395 * The register array is first parsed, then each of the channels are updated
398 protected void handlePolledDataXtender(int registerNumber, DecimalType quantity) {
399 switch (CHANNELS_XTENDER.get(registerNumber)) {
400 case CHANNEL_OPERATING_STATE:
401 ModeXtender mode = StuderParser.getModeXtenderByCode(quantity.intValue());
402 if (mode == ModeXtender.UNKNOWN) {
403 internalUpdateState(CHANNELS_XTENDER.get(registerNumber), UnDefType.UNDEF);
405 internalUpdateState(CHANNELS_XTENDER.get(registerNumber), new StringType(mode.name()));
408 case CHANNEL_STATE_INVERTER:
409 OnOffType xtstate = StuderParser.getStateByCode(quantity.intValue());
410 internalUpdateState(CHANNELS_XTENDER.get(registerNumber), xtstate);
413 Unit<?> unit = UNIT_CHANNELS_XTENDER.get(registerNumber);
415 internalUpdateState(CHANNELS_XTENDER.get(registerNumber), new QuantityType<>(quantity, unit));
421 * Handle errors received during communication
423 protected void handleError(AsyncModbusFailure<ModbusReadRequestBlueprint> failure) {
424 // Ignore all incoming data and errors if configuration is not correct
425 if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
428 String msg = failure.getCause().getMessage();
429 String cls = failure.getCause().getClass().getName();
430 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
431 String.format("Error with read: %s: %s", cls, msg));
435 * Returns true, if we're in a CONFIGURATION_ERROR state
439 protected boolean hasConfigurationError() {
440 ThingStatusInfo statusInfo = getThing().getStatusInfo();
441 return statusInfo.getStatus() == ThingStatus.OFFLINE
442 && statusInfo.getStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR;
446 * Reset communication status to ONLINE if we're in an OFFLINE state
448 protected void resetCommunicationError() {
449 ThingStatusInfo statusInfo = thing.getStatusInfo();
450 if (ThingStatus.OFFLINE.equals(statusInfo.getStatus())
451 && ThingStatusDetail.COMMUNICATION_ERROR.equals(statusInfo.getStatusDetail())) {
452 updateStatus(ThingStatus.ONLINE);
456 protected void internalUpdateState(@Nullable String channelUID, @Nullable State state) {
457 if (channelUID != null && state != null) {
458 super.updateState(channelUID, state);