]> git.basschouten.com Git - openhab-addons.git/blob
a416e91e324dfea1f10bc162e6cb98e9f9da3561
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.io.transport.modbus.json;
14
15 import java.util.Collection;
16 import java.util.Deque;
17 import java.util.LinkedList;
18 import java.util.concurrent.atomic.AtomicBoolean;
19
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;
31
32 import com.google.gson.JsonArray;
33 import com.google.gson.JsonElement;
34 import com.google.gson.JsonObject;
35 import com.google.gson.JsonParser;
36
37 /**
38  * Utilities for converting JSON to {@link ModbusWriteRequestBlueprint}
39  *
40  *
41  * @author Sami Salonen - Initial contribution
42  */
43 @NonNullByDefault
44 public final class WriteRequestJsonUtilities {
45     /**
46      * Constant for the function code key in the JSON
47      */
48     public static final String JSON_FUNCTION_CODE = "functionCode";
49     /**
50      * Constant for the write address key in the JSON
51      */
52     public static final String JSON_ADDRESS = "address";
53     /**
54      * Constant for the value key in the JSON
55      */
56     public static final String JSON_VALUE = "value";
57     /**
58      * Constant for the maxTries key in the JSON
59      */
60     public static final String JSON_MAX_TRIES = "maxTries";
61
62     /**
63      * Default maxTries when it has not been specified
64      */
65     public static final int DEFAULT_MAX_TRIES = 3;
66
67     private static final JsonParser PARSER = new JsonParser();
68
69     private WriteRequestJsonUtilities() {
70         throw new NotImplementedException();
71     }
72
73     /**
74      * Parse JSON string to collection of {@link ModbusWriteRequestBlueprint}
75      *
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
83      *
84      *
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
91      *
92      * @see WriteRequestJsonUtilities.JSON_FUNCTION_CODE
93      * @see WriteRequestJsonUtilities.JSON_ADDRESS
94      * @see WriteRequestJsonUtilities.JSON_VALUE
95      * @see WriteRequestJsonUtilities.JSON_MAX_TRIES
96      */
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<>();
101         }
102         Deque<ModbusWriteRequestBlueprint> writes = new LinkedList<>();
103         jsonArray.forEach(writeElem -> {
104             writes.add(constructBluerint(unitId, writeElem));
105         });
106         return writes;
107     }
108
109     private static ModbusWriteRequestBlueprint constructBluerint(int unitId, JsonElement arrayElement) {
110         final JsonObject writeObject;
111         try {
112             writeObject = arrayElement.getAsJsonObject();
113         } catch (IllegalStateException e) {
114             throw new IllegalStateException("JSON array contained something else than a JSON object!", e);
115         }
116         @Nullable
117         JsonElement functionCode = writeObject.get(JSON_FUNCTION_CODE);
118         @Nullable
119         JsonElement address = writeObject.get(JSON_ADDRESS);
120         @Nullable
121         JsonElement maxTries = writeObject.get(JSON_MAX_TRIES);
122         @Nullable
123         JsonArray valuesElem;
124
125         try {
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);
129         }
130         return constructBluerint(unitId, functionCode, address, maxTries, valuesElem);
131     }
132
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));
138         }
139         try {
140             functionCodeNumeric = functionCodeElem.getAsInt();
141         } catch (ClassCastException | IllegalStateException e) {
142             throw new IllegalStateException(String.format("Value for '%s' is invalid", JSON_FUNCTION_CODE), e);
143         }
144         ModbusWriteFunctionCode functionCode = ModbusWriteFunctionCode.fromFunctionCode(functionCodeNumeric);
145         int address;
146         if (addressElem == null || addressElem.isJsonNull()) {
147             throw new IllegalStateException(String.format("Value for '%s' is invalid", JSON_ADDRESS));
148         }
149         try {
150             address = addressElem.getAsInt();
151         } catch (ClassCastException | IllegalStateException e) {
152             throw new IllegalStateException(String.format("Value for '%s' is invalid", JSON_ADDRESS), e);
153         }
154         int maxTries;
155         if (maxTriesElem == null || maxTriesElem.isJsonNull()) {
156             // Go with default
157             maxTries = DEFAULT_MAX_TRIES;
158         } else {
159             try {
160                 maxTries = maxTriesElem.getAsInt();
161             } catch (ClassCastException | IllegalStateException e) {
162                 throw new IllegalStateException(String.format("Value for '%s' is invalid", JSON_MAX_TRIES), e);
163             }
164         }
165
166         if (valuesElem == null || valuesElem.isJsonNull()) {
167             throw new IllegalArgumentException(String.format("Expecting non-null value, got: %s", valuesElem));
168         }
169
170         AtomicBoolean writeSingle = new AtomicBoolean(false);
171         switch (functionCode) {
172             case WRITE_COIL:
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));
177                 }
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));
186                 }
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);
190                 }
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));
197                 }
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));
207                 }
208                 for (int i = 0; i < valuesElem.size(); i++) {
209                     registers[i] = new ModbusRegister(valuesElem.get(i).getAsInt());
210                 }
211                 return new ModbusWriteRegisterRequestBlueprint(unitId, address, new ModbusRegisterArray(registers),
212                         !writeSingle.get(), maxTries);
213             }
214             default:
215                 throw new IllegalArgumentException("Unknown function code");
216         }
217     }
218 }