2 * Copyright (c) 2010-2020 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 org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.binding.modbus.handler.ModbusEndpointThingHandler;
24 import org.openhab.binding.modbus.studer.internal.StuderParser.ModeXtender;
25 import org.openhab.binding.modbus.studer.internal.StuderParser.VSMode;
26 import org.openhab.binding.modbus.studer.internal.StuderParser.VTMode;
27 import org.openhab.binding.modbus.studer.internal.StuderParser.VTType;
28 import org.openhab.core.library.types.OnOffType;
29 import org.openhab.core.library.types.QuantityType;
30 import org.openhab.core.library.types.StringType;
31 import org.openhab.core.thing.Bridge;
32 import org.openhab.core.thing.ChannelUID;
33 import org.openhab.core.thing.Thing;
34 import org.openhab.core.thing.ThingStatus;
35 import org.openhab.core.thing.ThingStatusDetail;
36 import org.openhab.core.thing.ThingStatusInfo;
37 import org.openhab.core.thing.ThingTypeUID;
38 import org.openhab.core.thing.binding.BaseThingHandler;
39 import org.openhab.core.thing.binding.ThingHandler;
40 import org.openhab.core.types.Command;
41 import org.openhab.core.types.UnDefType;
42 import org.openhab.io.transport.modbus.AsyncModbusFailure;
43 import org.openhab.io.transport.modbus.ModbusCommunicationInterface;
44 import org.openhab.io.transport.modbus.ModbusReadFunctionCode;
45 import org.openhab.io.transport.modbus.ModbusReadRequestBlueprint;
46 import org.openhab.io.transport.modbus.ModbusRegisterArray;
47 import org.openhab.io.transport.modbus.PollTask;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
52 * The {@link StuderHandler} is responsible for handling commands, which are
53 * sent to one of the channels.
55 * @author Giovanni Mirulla - Initial contribution
58 public class StuderHandler extends BaseThingHandler {
60 private final Logger logger = LoggerFactory.getLogger(StuderHandler.class);
62 private @Nullable StuderConfiguration config;
65 * Array of tasks used to poll the device
67 private ArrayList<PollTask> pollTasks = new ArrayList<PollTask>();
70 * Communication interface to the slave endpoint we're connecting to
72 protected volatile @Nullable ModbusCommunicationInterface comms = null;
75 * Importing parser methods and enums
77 final StuderParser parser = new StuderParser();
79 * Support variable for type of thing
81 protected ThingTypeUID type;
84 * Array of registers of Studer slave to read, we store this once initialization is complete
86 private Integer[] registers = new Integer[0];
89 * Instances of this handler
91 * @param thing the thing to handle
92 * @param type the type of thing to handle
93 * @param slaveAddress the address of thing
94 * @param refreshSec the address of thing
96 public StuderHandler(Thing thing) {
98 this.type = thing.getThingTypeUID();
102 public void handleCommand(ChannelUID channelUID, Command command) {
104 // Currently we do not support any commands
109 * Load the config object
110 * Connect to the slave bridge
111 * Get registers to poll
112 * Start the periodic polling
115 public void initialize() {
116 config = getConfigAs(StuderConfiguration.class);
117 logger.debug("Initializing thing with configuration: {}", thing.getConfiguration());
123 * This method starts the operation of this handler
124 * Connect to the slave bridge
125 * Get registers to poll
126 * Start the periodic polling
128 private void startUp() {
132 if (comms == null || config == null) {
133 logger.debug("Invalid endpoint/config/manager ref for studer handler");
137 if (!pollTasks.isEmpty()) {
141 if (type.equals(THING_TYPE_BSP)) {
142 Set<Integer> keys = CHANNELS_BSP.keySet();
143 registers = keys.toArray(new Integer[keys.size()]);
144 } else if (type.equals(THING_TYPE_XTENDER)) {
145 Set<Integer> keys = CHANNELS_XTENDER.keySet();
146 registers = keys.toArray(new Integer[keys.size()]);
147 } else if (type.equals(THING_TYPE_VARIOTRACK)) {
148 Set<Integer> keys = CHANNELS_VARIOTRACK.keySet();
149 registers = keys.toArray(new Integer[keys.size()]);
150 } else if (type.equals(THING_TYPE_VARIOSTRING)) {
151 Set<Integer> keys = CHANNELS_VARIOSTRING.keySet();
152 registers = keys.toArray(new Integer[keys.size()]);
155 for (int r : registers) {
162 * Dispose the binding correctly
165 public void dispose() {
170 * Unregister the poll tasks and release the endpoint reference
172 private void tearDown() {
173 unregisterPollTasks();
174 unregisterEndpoint();
178 * Get the endpoint handler from the bridge this handler is connected to
179 * Checks that we're connected to the right type of bridge
181 * @return the endpoint handler or null if the bridge does not exist
183 private @Nullable ModbusEndpointThingHandler getEndpointThingHandler() {
184 Bridge bridge = getBridge();
185 if (bridge == null) {
186 logger.debug("Bridge is null");
189 if (bridge.getStatus() != ThingStatus.ONLINE) {
190 logger.debug("Bridge is not online");
194 ThingHandler handler = bridge.getHandler();
195 if (handler == null) {
196 logger.debug("Bridge handler is null");
200 if (handler instanceof ModbusEndpointThingHandler) {
201 ModbusEndpointThingHandler slaveEndpoint = (ModbusEndpointThingHandler) handler;
202 return slaveEndpoint;
204 logger.debug("Unexpected bridge handler: {}", handler);
210 * Get a reference to the modbus endpoint
212 private void connectEndpoint() {
217 ModbusEndpointThingHandler slaveEndpointThingHandler = getEndpointThingHandler();
218 if (slaveEndpointThingHandler == null) {
219 @SuppressWarnings("null")
220 String label = Optional.ofNullable(getBridge()).map(b -> b.getLabel()).orElse("<null>");
221 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
222 String.format("Bridge '%s' is offline", label));
223 logger.debug("No bridge handler available -- aborting init for {}", label);
226 comms = slaveEndpointThingHandler.getCommunicationInterface();
228 @SuppressWarnings("null")
229 String label = Optional.ofNullable(getBridge()).map(b -> b.getLabel()).orElse("<null>");
230 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
231 String.format("Bridge '%s' not completely initialized", label));
232 logger.debug("Bridge not initialized fully (no endpoint) -- aborting init for {}", this);
238 * Remove the endpoint if exists
240 private void unregisterEndpoint() {
241 // Comms will be close()'d by endpoint thing handler
245 private synchronized void unregisterPollTasks() {
246 if (pollTasks.isEmpty()) {
249 logger.debug("Unregistering polling from ModbusManager");
250 ModbusCommunicationInterface mycomms = comms;
251 if (mycomms != null) {
252 for (PollTask t : pollTasks) {
253 mycomms.unregisterRegularPoll(t);
261 * This is where we set up our regular poller
263 private synchronized void registerPollTask(int registerNumber) {
264 if (pollTasks.size() >= registers.length) {
265 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
266 throw new IllegalStateException("New pollTask invalid");
268 ModbusCommunicationInterface mycomms = comms;
269 StuderConfiguration studerConfig = config;
270 if (studerConfig == null || mycomms == null) {
271 throw new IllegalStateException("registerPollTask called without proper configuration");
274 logger.debug("Setting up regular polling");
276 ModbusReadRequestBlueprint request = new ModbusReadRequestBlueprint(studerConfig.slaveAddress,
277 ModbusReadFunctionCode.READ_INPUT_REGISTERS, registerNumber, 2, studerConfig.maxTries);
278 long refreshMillis = studerConfig.refresh * 1000;
279 PollTask pollTask = mycomms.registerRegularPoll(request, refreshMillis, 1000, result -> {
280 if (result.getRegisters().isPresent()) {
281 ModbusRegisterArray reg = result.getRegisters().get();
282 handlePolledData(registerNumber, reg);
284 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
287 if (getThing().getStatus() != ThingStatus.ONLINE) {
288 updateStatus(ThingStatus.ONLINE);
290 }, this::handleError);
291 pollTasks.add(pollTask);
295 * This method is called each time new data has been polled from the modbus slave
296 * The register array is first parsed, then each of the channels are updated
299 * @param n register readed
300 * @param registers byte array read from the modbus slave
302 protected void handlePolledData(int registerNumber, ModbusRegisterArray registers) {
303 String hexString = registers.toHexString().toString();
304 Float quantity = parser.hexToFloat(hexString);
305 if (quantity != null) {
306 if (type.equals(THING_TYPE_BSP)) {
307 updateState(CHANNELS_BSP.get(registerNumber),
308 new QuantityType<>(quantity, UNIT_CHANNELS_BSP.get(registerNumber)));
309 } else if (type.equals(THING_TYPE_XTENDER)) {
310 handlePolledDataXtender(registerNumber, quantity);
311 } else if (type.equals(THING_TYPE_VARIOTRACK)) {
312 handlePolledDataVarioTrack(registerNumber, quantity);
313 } else if (type.equals(THING_TYPE_VARIOSTRING)) {
314 handlePolledDataVarioString(registerNumber, quantity);
317 resetCommunicationError();
321 * This method is called each time new data has been polled from the VarioString slave
322 * The register array is first parsed, then each of the channels are updated
325 protected void handlePolledDataVarioString(int registerNumber, Float quantity) {
326 switch (CHANNELS_VARIOSTRING.get(registerNumber)) {
327 case CHANNEL_PV_OPERATING_MODE:
328 case CHANNEL_PV1_OPERATING_MODE:
329 case CHANNEL_PV2_OPERATING_MODE:
330 VSMode vsmode = StuderParser.getVSModeByCode(quantity.intValue());
331 if (vsmode == VSMode.UNKNOWN) {
332 updateState(CHANNELS_VARIOSTRING.get(registerNumber), UnDefType.UNDEF);
334 updateState(CHANNELS_VARIOSTRING.get(registerNumber), new StringType(vsmode.name()));
337 case CHANNEL_STATE_VARIOSTRING:
338 OnOffType vsstate = StuderParser.getStateByCode(quantity.intValue());
339 updateState(CHANNELS_VARIOSTRING.get(registerNumber), vsstate);
342 updateState(CHANNELS_VARIOSTRING.get(registerNumber),
343 new QuantityType<>(quantity, UNIT_CHANNELS_VARIOSTRING.get(registerNumber)));
348 * This method is called each time new data has been polled from the VarioTrack slave
349 * The register array is first parsed, then each of the channels are updated
352 protected void handlePolledDataVarioTrack(int registerNumber, Float quantity) {
353 switch (CHANNELS_VARIOTRACK.get(registerNumber)) {
354 case CHANNEL_MODEL_VARIOTRACK:
355 VTType type = StuderParser.getVTTypeByCode(quantity.intValue());
356 if (type == VTType.UNKNOWN) {
357 updateState(CHANNELS_VARIOTRACK.get(registerNumber), UnDefType.UNDEF);
359 updateState(CHANNELS_VARIOTRACK.get(registerNumber), new StringType(type.name()));
363 case CHANNEL_OPERATING_MODE:
364 VTMode vtmode = StuderParser.getVTModeByCode(quantity.intValue());
365 if (vtmode == VTMode.UNKNOWN) {
366 updateState(CHANNELS_VARIOTRACK.get(registerNumber), UnDefType.UNDEF);
368 updateState(CHANNELS_VARIOTRACK.get(registerNumber), new StringType(vtmode.name()));
372 case CHANNEL_STATE_VARIOTRACK:
373 OnOffType vtstate = StuderParser.getStateByCode(quantity.intValue());
374 updateState(CHANNELS_VARIOTRACK.get(registerNumber), vtstate);
377 updateState(CHANNELS_VARIOTRACK.get(registerNumber),
378 new QuantityType<>(quantity, UNIT_CHANNELS_VARIOTRACK.get(registerNumber)));
383 * This method is called each time new data has been polled from the Xtender slave
384 * The register array is first parsed, then each of the channels are updated
387 protected void handlePolledDataXtender(int registerNumber, Float quantity) {
388 switch (CHANNELS_XTENDER.get(registerNumber)) {
389 case CHANNEL_OPERATING_STATE:
390 ModeXtender mode = StuderParser.getModeXtenderByCode(quantity.intValue());
391 if (mode == ModeXtender.UNKNOWN) {
392 updateState(CHANNELS_XTENDER.get(registerNumber), UnDefType.UNDEF);
394 updateState(CHANNELS_XTENDER.get(registerNumber), new StringType(mode.name()));
397 case CHANNEL_STATE_INVERTER:
398 OnOffType xtstate = StuderParser.getStateByCode(quantity.intValue());
399 updateState(CHANNELS_XTENDER.get(registerNumber), xtstate);
402 updateState(CHANNELS_XTENDER.get(registerNumber),
403 new QuantityType<>(quantity, UNIT_CHANNELS_XTENDER.get(registerNumber)));
408 * Handle errors received during communication
410 protected void handleError(AsyncModbusFailure<ModbusReadRequestBlueprint> failure) {
411 // Ignore all incoming data and errors if configuration is not correct
412 if (hasConfigurationError() || getThing().getStatus() == ThingStatus.OFFLINE) {
415 String msg = failure.getCause().getMessage();
416 String cls = failure.getCause().getClass().getName();
417 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
418 String.format("Error with read: %s: %s", cls, msg));
422 * Returns true, if we're in a CONFIGURATION_ERROR state
426 protected boolean hasConfigurationError() {
427 ThingStatusInfo statusInfo = getThing().getStatusInfo();
428 return statusInfo.getStatus() == ThingStatus.OFFLINE
429 && statusInfo.getStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR;
433 * Reset communication status to ONLINE if we're in an OFFLINE state
435 protected void resetCommunicationError() {
436 ThingStatusInfo statusInfo = thing.getStatusInfo();
437 if (ThingStatus.OFFLINE.equals(statusInfo.getStatus())
438 && ThingStatusDetail.COMMUNICATION_ERROR.equals(statusInfo.getStatusDetail())) {
439 updateStatus(ThingStatus.ONLINE);