Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Bytes  | 0   | 1 .. len-3      | len-2 . len-1  | len   |
       +-----+-----------------+----------------+-------+
Field  | len | Modbus response | start register | count |

Reference Decoder

Expand

Reference decoder

This is a decoder written in JavaScript that can be used to parse the device's LoRaWAN messages. It can be used as is in The Things Network.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218

function readVersion(bytes) {
    if (bytes.length<3) {
        return null;
    }
    return "v" + bytes[0] + "." + bytes[1] + "." + bytes[2];
}
 
function int40_BE(bytes, idx) {
    bytes = bytes.slice(idx || 0);
    return bytes[0] << 32 |
        bytes[1] << 24 | bytes[2] << 16 | bytes[3] << 8 | bytes[4] << 0;
}
 
function int16_BE(bytes, idx) {
    bytes = bytes.slice(idx || 0);
    return bytes[0] << 8 | bytes[1] << 0;
}
 
function uint16_BE(bytes, idx) {
    bytes = bytes.slice(idx || 0);
    return bytes[0] << 8 | bytes[1] << 0;
}
 
function port1(bytes) {
    return {
        "port":1,
        "version":readVersion(bytes),
        "flags":bytes[3],
        "temp": int16_BE(bytes, 4) / 10,
        "vBat": int16_BE(bytes, 6) / 1000,
        "timestamp": int40_BE(bytes, 8),
        "operationMode": bytes[13],
        "noData": !!(bytes[3] & 0x01)
    };
}
 
function port2(bytes) {
    var regs = [];
    if (bytes.length > 5) {
        // loop through data packs
        var b = bytes.slice(5);
        while (b.length>=4) {
            var r = {
                "device":b[0],
                "register":int16_BE(b, 1),
                "count":b[3] & 0x3f,
                "error":!!(b[3]>>7),
                "data":null
            };
            var dataLen = r["count"]*2;
            if (b.length >= dataLen+4) {
                r["data"] = b.slice(4, 4 + dataLen);
            }
            regs.push(r);
            b = b.slice(4+dataLen);
        }
    }
    return {
        "port":2,
        "timestamp": int40_BE(bytes, 0),
        "registers": regs
    };
}
 
function modbusErrorString(code) {
    // Modbus exception codes
    // see https://en.wikipedia.org/wiki/Modbus#Exception_responses
    switch (code) {
        case 1:
            return "Illegal Function";
        case 2:
            return "Illegal Data Address";
        case 3:
            return "Illegal Data Value";
        case 4:
            return "Slave Device Failure";
        case 5:
            return "Acknowledge";
        case 6:
            return "Slave Device Busy";
        case 7:
            return "Negative Acknowledge";
        case 8:
            return "Memory Parity Error";
        case 10:
            return "Gateway Path Unavailable";
        case 11:
            return "Gateway Target Device Failed to Respond";
        default:
            return "Unknown error code";
    }
}
 
function parseModbusPayloadRegisters(payload) {
    if (payload.length < 1) {
        return null;
    }
    var byteCnt = payload[0];
    if (payload.length !== byteCnt + 1) {
        return null;
    }
    var vals = [];
    for (var i=0; i<byteCnt; i+=2) {
        vals.push([+payload[i+1], +payload[i+2]])
    }
    return vals;
 
}
function parseModbusResponse(raw) {
    var resp = {};
    if (raw.length >= 6) {
        var fun = raw[1] & 0xf;
        var error = !!(raw[1] & 0x80);
        var rawResp = raw.slice(0, raw.length - 3);
        resp["slave"] = raw[0];
        resp["function"] = fun;
        resp["error"] = error;
        resp["start"] = uint16_BE(raw, raw.length - 3);
        resp["cnt"] = raw[raw.length - 1];
        resp["raw"] = rawResp;
        if (error) {
            resp["errorCode"] = raw[2];
            resp["errorText"] = modbusErrorString(raw[2]);
        } else {
            resp["values"] = parseModbusPayloadRegisters(rawResp.slice(2))
            // TODO: coils
        }
    }
    return resp;
}
 
function FullResponses(bytes, port) {
    var timestamp = int40_BE(bytes);
    var pos = 5;
    var resps = [];
    while (pos < bytes.length) {
        var respLen = bytes[pos++];
        if (bytes.length >= pos + respLen) {
            var rawResponse = bytes.slice(pos, pos + respLen);
            resps.push(parseModbusResponse(rawResponse));
            pos += respLen;
        } else {
            break;
        }
    }
    return {
        "port": port,
        "timestamp" : timestamp,
        "responses": resps
    };
}
 
function bin2String(array) {
    var result = "";
    for (var i = 0; i < array.length; i++) {
        result += String.fromCharCode(array[i]);
    }
    return result;
}
 
function ConfigResponse(data) {
    var t = bin2String(data);
    return {
        "response" : t,
        "error" : (t.length === 0) || (t[0] === '!')
    }
}
 
/**
 * TTN decoder function.
 */
function Decoder(bytes, port) {
    switch (port) {
        case 1:
            // Status message:
            return port1(bytes);
        case 2:
            // not legacy format:
            return port2(bytes);
        case 3:
        case 4:
            // v1.0.0 format, full modbus responses:
            return FullResponses(bytes, port);
        case 5:
            // continuation of previous response:
            return {};
        case 6:
            // dense format with prefixed timestamp:
            return {};
        case 7:
            // dense format without timestamp:
            return {};
        case 128:
            return ConfigResponse(bytes);
    }
    return {"error":"invalid port", "port":port};
}
 
/**
 * LoRaServer decoder function.
 */
function Decode(fPort, bytes) {
    // wrap TTN Decoder:
    return Decoder(bytes, fPort);
}
function Parse(input) {
    var data = bytes(atob(input.data));
    var port = input.fPort;
    var fcnt = input.fCnt;
    var vals =  Decoder(data, port);
    vals["port"] = port;
    vals["data"] = data;
    vals["fnct"] = fcnt;
    var lastFcnt = Device.getProperty("lastFcnt");
    vals["reset"] = fcnt <= lastFcnt;
    Device.setProperty("lastFcnt", fcnt);
    return vals;
}


...