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

...

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
Expand


Code Block


function readVersion(bytes) {
    if (bytes.length<3) {
        return null;
    }
    return "v" + bytes[0] 
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
218function readVersion(bytes) {
    if (bytes.length<3) {
        return null;
    }
    return "v" + bytes[0]
+ "." + bytes[1] + "." + bytes[2];


}

 
function int40

 
function int40_BE(bytes, idx)
{
    bytes =
 {
    bytes = bytes.slice(idx || 0);

    return bytes

    return bytes[0] << 32 |

        bytes

        bytes[1] << 24 | bytes[2] << 16 | bytes[3] << 8 | bytes[4] << 0;


}

 
function int16

 
function int16_BE(bytes, idx)
{
    bytes =
 {
    bytes = bytes.slice(idx || 0);

    return bytes

    return bytes[0] << 8 | bytes[1] << 0;


}

 
function uint16

 
function uint16_BE(bytes, idx)
{
    bytes =
 {
    bytes = bytes.slice(idx || 0);

    return bytes

    return bytes[0] << 8 | bytes[1] << 0;


}

 
function port1

 
function port1(bytes)
{
    return {
 {
    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

 
function port2(bytes)
{
    var regs =
 {
    var regs = [];

    if 

    if (bytes.length > 5) {


        // loop through data
packs
        var b =
 packs
        var b = bytes.slice(5);

        while 

        while (b.length>=4)
{
            var r = {
 {
            var r = {
                "device":b[0],


                "register":int16_BE(b, 1),


                "count":b[3] & 0x3f,


                "error":!!(b[3]>>7),


                "data":null


            };

            var dataLen =

            var dataLen = r["count"]*2;

            if 

            if (b.length >= dataLen+4) {

                r

                r["data"] = b.slice(4, 4 + dataLen);


            }

            regs

            regs.push(r);

            b =

            b = b.slice(4+dataLen);


        }


    }

    return {

    return {
        "port":2,


        "timestamp": int40_BE(bytes, 0),


        "registers": regs


    };


}

 
function modbusErrorString

 
function modbusErrorString(code) {


    // Modbus exception codes


    //
see https
 see https://en.wikipedia.org/wiki/Modbus#Exception_responses

    switch 

    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
 {
        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

    return vals;
 
}
function parseModbusResponse(raw)
{
    var resp =
 {
    var resp = {};

    if 

    if (raw.length >= 6)
{
        var fun =
 {
        var fun = raw[1] & 0xf;

        var error =

        var error = !!(raw[1] & 0x80);

        var rawResp =

        var rawResp = raw.slice(0, raw.length - 3);

        resp

        resp["slave"] = raw[0];

        resp

        resp["function"] = fun;

        resp

        resp["error"] = error;

        resp

        resp["start"] = uint16_BE(raw, raw.length - 3);

        resp

        resp["cnt"] = raw[raw.length - 1];

        resp

        resp["raw"] = rawResp;

        if 

        if (error) {

            resp

            resp["errorCode"] = raw[2];

            resp

            resp["errorText"] = modbusErrorString(raw[2]);


        }
 else {
            resp
 else {
            resp["values"] = parseModbusPayloadRegisters(rawResp.slice(2))


            // TODO: coils


        }


    }

    return resp;
}
 
function FullResponses

    return resp;
}
 
function FullResponses(bytes, port)
{
    var timestamp =
 {
    var timestamp = int40_BE(bytes);

    var pos = 5;
    var resps = [];
    while (pos <

    var pos = 5;
    var resps = [];
    while (pos < bytes.length)
{
        var respLen =
 {
        var respLen = bytes[pos++];

        if 

        if (bytes.length >= pos + respLen)
{
            var rawResponse =
 {
            var rawResponse = bytes.slice(pos, pos + respLen);

            resps

            resps.push(parseModbusResponse(rawResponse));

            pos

            pos += respLen;


        }
 else {
            break;
        }
    }
    return {
 else {
            break;
        }
    }
    return {
        "port": port,


        "timestamp" : timestamp,


        "responses": resps


    };


}

 
function bin2String

 
function bin2String(array)
{
    var result
 {
    var result = "";

    for (var i =

    for (var i = 0; i < array.length; i++)
{
        result
 {
        result += String.fromCharCode(array[i]);


    }

    return result;
}
 
function ConfigResponse

    return result;
}
 
function ConfigResponse(data)
{
    var t =
 {
    var t = bin2String(data);

    return {

    return {
        "response" : t,


        "error" : (t.length === 0) || (t[0] === '!')


    }


}


 


/**


 * TTN decoder function.


 */

function Decoder

function Decoder(bytes, port)
{
    switch 
 {
    switch (port)
{
        case 1:
 {
        case 1:
            // Status message:

            return port1

            return port1(bytes);

        case 2:

        case 2:
            // not legacy format:

            return port2

            return port2(bytes);

        case 3:
        case 4:

        case 3:
        case 4:
            // v1.0.0 format, full modbus responses:

            return FullResponses

            return FullResponses(bytes, port);

        case 5:

        case 5:
            // continuation of previous response:

            return 

            return {};

        case 6:

        case 6:
            // dense format with prefixed timestamp:

            return 

            return {};

        case 7:

        case 7:
            // dense format without timestamp:

            return 

            return {};

        case 128:
            return ConfigResponse

        case 128:
            return ConfigResponse(bytes);


    }

    return 

    return {"error":"invalid port", "port":port};


}


 


/**


 * LoRaServer decoder function.


 */

function Decode

function Decode(fPort, bytes) {


    // wrap TTN Decoder:

    return Decoder

    return Decoder(bytes, fPort);

}
function Parse

}
function Parse(input)
{
    var data =
 {
    var data = bytes(atob(input.data));

    var port =

    var port = input.fPort;

    var fcnt =

    var fcnt = input.fCnt;

    var vals

    var vals  Decoder(data, port);

    vals

    vals["port"] = port;

    vals

    vals["data"] = data;

    vals

    vals["fnct"] = fcnt;

    var lastFcnt =

    var lastFcnt = Device.getProperty("lastFcnt");

    vals

    vals["reset"] = fcnt <= lastFcnt;

    Device

    Device.setProperty("lastFcnt", fcnt);

    return vals;

    return vals;
}


Compact Payload Format (Port 20, (21-59) PlFmt=4&5)

...