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.ModbusRegisterArray;
26 import org.openhab.io.transport.modbus.ModbusWriteCoilRequestBlueprint;
27 import org.openhab.io.transport.modbus.ModbusWriteFunctionCode;
28 import org.openhab.io.transport.modbus.ModbusWriteRegisterRequestBlueprint;
29 import org.openhab.io.transport.modbus.ModbusWriteRequestBlueprint;
31 import com.google.gson.JsonArray;
32 import com.google.gson.JsonElement;
33 import com.google.gson.JsonObject;
34 import com.google.gson.JsonParser;
37 * Utilities for converting JSON to {@link ModbusWriteRequestBlueprint}
40 * @author Sami Salonen - Initial contribution
43 public final class WriteRequestJsonUtilities {
45 * Constant for the function code key in the JSON
47 public static final String JSON_FUNCTION_CODE = "functionCode";
49 * Constant for the write address key in the JSON
51 public static final String JSON_ADDRESS = "address";
53 * Constant for the value key in the JSON
55 public static final String JSON_VALUE = "value";
57 * Constant for the maxTries key in the JSON
59 public static final String JSON_MAX_TRIES = "maxTries";
62 * Default maxTries when it has not been specified
64 public static final int DEFAULT_MAX_TRIES = 3;
66 private static final JsonParser PARSER = new JsonParser();
68 private WriteRequestJsonUtilities() {
69 throw new NotImplementedException();
73 * Parse JSON string to collection of {@link ModbusWriteRequestBlueprint}
75 * JSON string should represent a JSON array, with JSON objects. Each JSON object represents a write request. The
76 * JSON object must have the following keys
77 * - functionCode: numeric function code
78 * - address: reference or start address of the write
79 * - value: array of data to be written. Use zero and one when writing coils. With registers, each number
80 * corresponds to register's 16 bit data.
81 * - maxTries: number of tries with the write in case of errors
84 * @param unitId unit id for the constructed {@link ModbusWriteRequestBlueprint}
85 * @param jsonString json to be parsed in string format
86 * @return collection of {@link ModbusWriteRequestBlueprint} representing the json
87 * @throws IllegalArgumentException in case of unexpected function codes, or too large payload exceeding modbus
88 * protocol specification
89 * @throws IllegalStateException in case of parsing errors and unexpected json structure
91 * @see WriteRequestJsonUtilities.JSON_FUNCTION_CODE
92 * @see WriteRequestJsonUtilities.JSON_ADDRESS
93 * @see WriteRequestJsonUtilities.JSON_VALUE
94 * @see WriteRequestJsonUtilities.JSON_MAX_TRIES
96 public static Collection<ModbusWriteRequestBlueprint> fromJson(int unitId, String jsonString) {
97 JsonArray jsonArray = PARSER.parse(jsonString).getAsJsonArray();
98 if (jsonArray.size() == 0) {
99 return new LinkedList<>();
101 Deque<ModbusWriteRequestBlueprint> writes = new LinkedList<>();
102 jsonArray.forEach(writeElem -> {
103 writes.add(constructBluerint(unitId, writeElem));
108 private static ModbusWriteRequestBlueprint constructBluerint(int unitId, JsonElement arrayElement) {
109 final JsonObject writeObject;
111 writeObject = arrayElement.getAsJsonObject();
112 } catch (IllegalStateException e) {
113 throw new IllegalStateException("JSON array contained something else than a JSON object!", e);
116 JsonElement functionCode = writeObject.get(JSON_FUNCTION_CODE);
118 JsonElement address = writeObject.get(JSON_ADDRESS);
120 JsonElement maxTries = writeObject.get(JSON_MAX_TRIES);
122 JsonArray valuesElem;
125 valuesElem = writeObject.get(JSON_VALUE).getAsJsonArray();
126 } catch (IllegalStateException e) {
127 throw new IllegalStateException(String.format("JSON object '%s' is not an JSON array!", JSON_VALUE), e);
129 return constructBluerint(unitId, functionCode, address, maxTries, valuesElem);
132 private static ModbusWriteRequestBlueprint constructBluerint(int unitId, @Nullable JsonElement functionCodeElem,
133 @Nullable JsonElement addressElem, @Nullable JsonElement maxTriesElem, @Nullable JsonArray valuesElem) {
134 int functionCodeNumeric;
135 if (functionCodeElem == null || functionCodeElem.isJsonNull()) {
136 throw new IllegalStateException(String.format("Value for '%s' is invalid", JSON_FUNCTION_CODE));
139 functionCodeNumeric = functionCodeElem.getAsInt();
140 } catch (ClassCastException | IllegalStateException e) {
141 throw new IllegalStateException(String.format("Value for '%s' is invalid", JSON_FUNCTION_CODE), e);
143 ModbusWriteFunctionCode functionCode = ModbusWriteFunctionCode.fromFunctionCode(functionCodeNumeric);
145 if (addressElem == null || addressElem.isJsonNull()) {
146 throw new IllegalStateException(String.format("Value for '%s' is invalid", JSON_ADDRESS));
149 address = addressElem.getAsInt();
150 } catch (ClassCastException | IllegalStateException e) {
151 throw new IllegalStateException(String.format("Value for '%s' is invalid", JSON_ADDRESS), e);
154 if (maxTriesElem == null || maxTriesElem.isJsonNull()) {
156 maxTries = DEFAULT_MAX_TRIES;
159 maxTries = maxTriesElem.getAsInt();
160 } catch (ClassCastException | IllegalStateException e) {
161 throw new IllegalStateException(String.format("Value for '%s' is invalid", JSON_MAX_TRIES), e);
165 if (valuesElem == null || valuesElem.isJsonNull()) {
166 throw new IllegalArgumentException(String.format("Expecting non-null value, got: %s", valuesElem));
169 AtomicBoolean writeSingle = new AtomicBoolean(false);
170 switch (functionCode) {
172 writeSingle.set(true);
173 if (valuesElem.size() != 1) {
174 throw new IllegalArgumentException(String
175 .format("Expecting single value with functionCode=%s, got: %d", functionCode, valuesElem));
177 // fall-through to WRITE_MULTIPLE_COILS
178 case WRITE_MULTIPLE_COILS:
179 if (valuesElem.size() == 0) {
180 throw new IllegalArgumentException("Must provide at least one coil");
181 } else if (valuesElem.size() > ModbusConstants.MAX_BITS_WRITE_COUNT) {
182 throw new IllegalArgumentException(
183 String.format("Trying to write too many coils (%d). Maximum is %s", valuesElem.size(),
184 ModbusConstants.MAX_BITS_WRITE_COUNT));
186 BitArray bits = new BitArray(valuesElem.size());
187 for (int i = 0; i < valuesElem.size(); i++) {
188 bits.setBit(i, valuesElem.get(i).getAsInt() != 0);
190 return new ModbusWriteCoilRequestBlueprint(unitId, address, bits, !writeSingle.get(), maxTries);
191 case WRITE_SINGLE_REGISTER:
192 writeSingle.set(true);
193 if (valuesElem.size() != 1) {
194 throw new IllegalArgumentException(String
195 .format("Expecting single value with functionCode=%s, got: %d", functionCode, valuesElem));
197 // fall-through to WRITE_MULTIPLE_REGISTERS
198 case WRITE_MULTIPLE_REGISTERS: {
199 int[] registers = new int[valuesElem.size()];
200 if (registers.length == 0) {
201 throw new IllegalArgumentException("Must provide at least one register");
202 } else if (valuesElem.size() > ModbusConstants.MAX_REGISTERS_WRITE_COUNT) {
203 throw new IllegalArgumentException(
204 String.format("Trying to write too many registers (%d). Maximum is %s", valuesElem.size(),
205 ModbusConstants.MAX_REGISTERS_WRITE_COUNT));
207 for (int i = 0; i < valuesElem.size(); i++) {
208 registers[i] = valuesElem.get(i).getAsInt();
210 return new ModbusWriteRegisterRequestBlueprint(unitId, address, new ModbusRegisterArray(registers),
211 !writeSingle.get(), maxTries);
214 throw new IllegalArgumentException("Unknown function code");