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