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.io.transport.modbus.json;
15 import java.util.Collection;
16 import java.util.Deque;
17 import java.util.LinkedList;
18 import java.util.concurrent.atomic.AtomicBoolean;
20 import org.apache.commons.lang.NotImplementedException;
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.io.transport.modbus.BitArray;
24 import org.openhab.io.transport.modbus.ModbusConstants;
25 import org.openhab.io.transport.modbus.ModbusRegister;
26 import org.openhab.io.transport.modbus.ModbusRegisterArray;
27 import org.openhab.io.transport.modbus.ModbusWriteCoilRequestBlueprint;
28 import org.openhab.io.transport.modbus.ModbusWriteFunctionCode;
29 import org.openhab.io.transport.modbus.ModbusWriteRegisterRequestBlueprint;
30 import org.openhab.io.transport.modbus.ModbusWriteRequestBlueprint;
32 import com.google.gson.JsonArray;
33 import com.google.gson.JsonElement;
34 import com.google.gson.JsonObject;
35 import com.google.gson.JsonParser;
38 * Utilities for converting JSON to {@link ModbusWriteRequestBlueprint}
41 * @author Sami Salonen - Initial contribution
44 public final class WriteRequestJsonUtilities {
46 * Constant for the function code key in the JSON
48 public static final String JSON_FUNCTION_CODE = "functionCode";
50 * Constant for the write address key in the JSON
52 public static final String JSON_ADDRESS = "address";
54 * Constant for the value key in the JSON
56 public static final String JSON_VALUE = "value";
58 * Constant for the maxTries key in the JSON
60 public static final String JSON_MAX_TRIES = "maxTries";
63 * Default maxTries when it has not been specified
65 public static final int DEFAULT_MAX_TRIES = 3;
67 private static final JsonParser PARSER = new JsonParser();
69 private WriteRequestJsonUtilities() {
70 throw new NotImplementedException();
74 * Parse JSON string to collection of {@link ModbusWriteRequestBlueprint}
76 * JSON string should represent a JSON array, with JSON objects. Each JSON object represents a write request. The
77 * JSON object must have the following keys
78 * - functionCode: numeric function code
79 * - address: reference or start address of the write
80 * - value: array of data to be written. Use zero and one when writing coils. With registers, each number
81 * corresponds to register's 16 bit data.
82 * - maxTries: number of tries with the write in case of errors
85 * @param unitId unit id for the constructed {@link ModbusWriteRequestBlueprint}
86 * @param jsonString json to be parsed in string format
87 * @return collection of {@link ModbusWriteRequestBlueprint} representing the json
88 * @throws IllegalArgumentException in case of unexpected function codes, or too large payload exceeding modbus
89 * protocol specification
90 * @throws IllegalStateException in case of parsing errors and unexpected json structure
92 * @see WriteRequestJsonUtilities.JSON_FUNCTION_CODE
93 * @see WriteRequestJsonUtilities.JSON_ADDRESS
94 * @see WriteRequestJsonUtilities.JSON_VALUE
95 * @see WriteRequestJsonUtilities.JSON_MAX_TRIES
97 public static Collection<ModbusWriteRequestBlueprint> fromJson(int unitId, String jsonString) {
98 JsonArray jsonArray = PARSER.parse(jsonString).getAsJsonArray();
99 if (jsonArray.size() == 0) {
100 return new LinkedList<>();
102 Deque<ModbusWriteRequestBlueprint> writes = new LinkedList<>();
103 jsonArray.forEach(writeElem -> {
104 writes.add(constructBluerint(unitId, writeElem));
109 private static ModbusWriteRequestBlueprint constructBluerint(int unitId, JsonElement arrayElement) {
110 final JsonObject writeObject;
112 writeObject = arrayElement.getAsJsonObject();
113 } catch (IllegalStateException e) {
114 throw new IllegalStateException("JSON array contained something else than a JSON object!", e);
117 JsonElement functionCode = writeObject.get(JSON_FUNCTION_CODE);
119 JsonElement address = writeObject.get(JSON_ADDRESS);
121 JsonElement maxTries = writeObject.get(JSON_MAX_TRIES);
123 JsonArray valuesElem;
126 valuesElem = writeObject.get(JSON_VALUE).getAsJsonArray();
127 } catch (IllegalStateException e) {
128 throw new IllegalStateException(String.format("JSON object '%s' is not an JSON array!", JSON_VALUE), e);
130 return constructBluerint(unitId, functionCode, address, maxTries, valuesElem);
133 private static ModbusWriteRequestBlueprint constructBluerint(int unitId, @Nullable JsonElement functionCodeElem,
134 @Nullable JsonElement addressElem, @Nullable JsonElement maxTriesElem, @Nullable JsonArray valuesElem) {
135 int functionCodeNumeric;
136 if (functionCodeElem == null || functionCodeElem.isJsonNull()) {
137 throw new IllegalStateException(String.format("Value for '%s' is invalid", JSON_FUNCTION_CODE));
140 functionCodeNumeric = functionCodeElem.getAsInt();
141 } catch (ClassCastException | IllegalStateException e) {
142 throw new IllegalStateException(String.format("Value for '%s' is invalid", JSON_FUNCTION_CODE), e);
144 ModbusWriteFunctionCode functionCode = ModbusWriteFunctionCode.fromFunctionCode(functionCodeNumeric);
146 if (addressElem == null || addressElem.isJsonNull()) {
147 throw new IllegalStateException(String.format("Value for '%s' is invalid", JSON_ADDRESS));
150 address = addressElem.getAsInt();
151 } catch (ClassCastException | IllegalStateException e) {
152 throw new IllegalStateException(String.format("Value for '%s' is invalid", JSON_ADDRESS), e);
155 if (maxTriesElem == null || maxTriesElem.isJsonNull()) {
157 maxTries = DEFAULT_MAX_TRIES;
160 maxTries = maxTriesElem.getAsInt();
161 } catch (ClassCastException | IllegalStateException e) {
162 throw new IllegalStateException(String.format("Value for '%s' is invalid", JSON_MAX_TRIES), e);
166 if (valuesElem == null || valuesElem.isJsonNull()) {
167 throw new IllegalArgumentException(String.format("Expecting non-null value, got: %s", valuesElem));
170 AtomicBoolean writeSingle = new AtomicBoolean(false);
171 switch (functionCode) {
173 writeSingle.set(true);
174 if (valuesElem.size() != 1) {
175 throw new IllegalArgumentException(String
176 .format("Expecting single value with functionCode=%s, got: %d", functionCode, valuesElem));
178 // fall-through to WRITE_MULTIPLE_COILS
179 case WRITE_MULTIPLE_COILS:
180 if (valuesElem.size() == 0) {
181 throw new IllegalArgumentException("Must provide at least one coil");
182 } else if (valuesElem.size() > ModbusConstants.MAX_BITS_WRITE_COUNT) {
183 throw new IllegalArgumentException(
184 String.format("Trying to write too many coils (%d). Maximum is %s", valuesElem.size(),
185 ModbusConstants.MAX_BITS_WRITE_COUNT));
187 BitArray bits = new BitArray(valuesElem.size());
188 for (int i = 0; i < valuesElem.size(); i++) {
189 bits.setBit(i, valuesElem.get(i).getAsInt() != 0);
191 return new ModbusWriteCoilRequestBlueprint(unitId, address, bits, !writeSingle.get(), maxTries);
192 case WRITE_SINGLE_REGISTER:
193 writeSingle.set(true);
194 if (valuesElem.size() != 1) {
195 throw new IllegalArgumentException(String
196 .format("Expecting single value with functionCode=%s, got: %d", functionCode, valuesElem));
198 // fall-through to WRITE_MULTIPLE_REGISTERS
199 case WRITE_MULTIPLE_REGISTERS: {
200 ModbusRegister[] registers = new ModbusRegister[valuesElem.size()];
201 if (registers.length == 0) {
202 throw new IllegalArgumentException("Must provide at least one register");
203 } else if (valuesElem.size() > ModbusConstants.MAX_REGISTERS_WRITE_COUNT) {
204 throw new IllegalArgumentException(
205 String.format("Trying to write too many registers (%d). Maximum is %s", valuesElem.size(),
206 ModbusConstants.MAX_REGISTERS_WRITE_COUNT));
208 for (int i = 0; i < valuesElem.size(); i++) {
209 registers[i] = new ModbusRegister(valuesElem.get(i).getAsInt());
211 return new ModbusWriteRegisterRequestBlueprint(unitId, address, new ModbusRegisterArray(registers),
212 !writeSingle.get(), maxTries);
215 throw new IllegalArgumentException("Unknown function code");