@lifefinder/vsm-mqtt-client-open-source 0.0.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +21 -0
- package/README.md +210 -0
- package/chirpstack-devices.list +4 -0
- package/constants.json +14 -0
- package/decorators/default.js +10 -0
- package/decorators/minimal.js +19 -0
- package/decorators/yggio-push.js +11 -0
- package/helium-devices.list +4 -0
- package/integrations/chirpstack3.js +135 -0
- package/integrations/chirpstack4.js +120 -0
- package/integrations/helium.js +150 -0
- package/integrations/yggio.js +136 -0
- package/package.json +19 -0
- package/publishers/console.js +13 -0
- package/publishers/https.js +38 -0
- package/publishers/mqtt.js +53 -0
- package/rules.js +246 -0
- package/solvers/aws.js +102 -0
- package/solvers/combain-loracloud.js +263 -0
- package/solvers/loracloud.js +287 -0
- package/solvers/none.js +40 -0
- package/store.js +82 -0
- package/util.js +35 -0
- package/vsm-mqtt-client.js +299 -0
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Sensative AB
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# VSM MQTT Client "Munin"
|
|
2
|
+
|
|
3
|
+
Decoding and managing the Sensative new generation of products based on the Virtual Sensoring Machine technology.
|
|
4
|
+
|
|
5
|
+
# What is it?
|
|
6
|
+
|
|
7
|
+
* Software Service which will Translate Sensative AB Puck, Square, and Lifefinder data.
|
|
8
|
+
* Manage GNSS almanac and assistance position updates for the devices over LoRaWan.
|
|
9
|
+
* Solve positions (provided that you provide the necessary loracloud or AWS key information, reqiuires a paid account with respective supplier, e.g. https://www.loracloud.com/documentation/modem_services or https://docs.aws.amazon.com/location/ .
|
|
10
|
+
* Sensative can also provide this service through your Yggio account.
|
|
11
|
+
* Manage device time in LoraWan networks which lack this feature
|
|
12
|
+
* Format the device data
|
|
13
|
+
* Forward the device data to the wanted destination.
|
|
14
|
+
|
|
15
|
+
# License
|
|
16
|
+
MIT License, see license file.
|
|
17
|
+
|
|
18
|
+
# Installation
|
|
19
|
+
|
|
20
|
+
## Using NPM
|
|
21
|
+
|
|
22
|
+
### Add it to your project
|
|
23
|
+
* Add the following to your package.json
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
{
|
|
27
|
+
"dependencies": {
|
|
28
|
+
...
|
|
29
|
+
"vsm-mqtt-client-open-source": ">0.0.1"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
Or run ```yarn install vsm-mqtt-client-open-source```
|
|
34
|
+
|
|
35
|
+
### Use the environment to set up your own custom Decorators, Publishers, Storage, etc. See the vsm-mqtt-client
|
|
36
|
+
|
|
37
|
+
## As a fork
|
|
38
|
+
Clone the repository
|
|
39
|
+
At the top level, run yarn install
|
|
40
|
+
|
|
41
|
+
## Adding support for AWS Cloud Solver
|
|
42
|
+
If you wish to run the program with the AWS Cloud solver follow the below steps
|
|
43
|
+
|
|
44
|
+
At the top level run yarn add aws-sdk
|
|
45
|
+
Locate the constants.json file and fill out the required fields, e.g:
|
|
46
|
+
{
|
|
47
|
+
"AWS": {
|
|
48
|
+
"VERSION": "<SDK VERSION>", Format:
|
|
49
|
+
"ACCESS_KEY_ID": "<YOUR ACCESS KEY ID>",
|
|
50
|
+
"SECRET_ACCESS_KEY": "<YOUR SECRET ACCESS KEY>",
|
|
51
|
+
"REGION": "<YOUR REGION, EU NORTH IS NOT SUPPORTED>"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
Nota bene, the AWS Cloud Solver does not support GNSS almanac updates at the time of writing this.
|
|
56
|
+
|
|
57
|
+
# Running
|
|
58
|
+
|
|
59
|
+
This is expecting node, tried on node version 18, 19 and 20.
|
|
60
|
+
|
|
61
|
+
General arguments:
|
|
62
|
+
* -v Run in verbose mode, will generate extra log printouts (no argument)
|
|
63
|
+
* -f <filename> Select device identity file (can be substituted with -w for some integrations)
|
|
64
|
+
* -w Wildcard all devices available on the server matching a Sensative DevEUI
|
|
65
|
+
* -i <integration> Select integration (see integrations folder for list of available integrations)
|
|
66
|
+
* -k <api key> Enter the API key for semtech loracloud location services (required for GNSS and WIFI solve)
|
|
67
|
+
* -d <decorator> Select decorator (see decorators folder for list of available decorators). If omitted the full translated object with all bookkeeping data will be used.
|
|
68
|
+
* -O <publisher> Select publisher (see publishers folder for list of available publishers). If omitted the publishing will be only to the console.
|
|
69
|
+
* -z <solver> Select a GNSS almanac and solution provider. Defaults to loracloud.
|
|
70
|
+
* -N Do not invoke solver, but still use it for GNSS almanac updates (assume solving is done elsewhere in chain)
|
|
71
|
+
|
|
72
|
+
## Environment variables
|
|
73
|
+
|
|
74
|
+
As a separate extension mechanism, environment variables can also be used to specify the various components, such as store, decorator, etc (see folder structure below). This is in order to work better as a npm import with customizations instead of through the forking mechanism.
|
|
75
|
+
|
|
76
|
+
# Integrations
|
|
77
|
+
Where is raw device data fetched and where do we send downlinks?
|
|
78
|
+
|
|
79
|
+
## Running with Chirpstack 3.x
|
|
80
|
+
Extra Arguments
|
|
81
|
+
* -a <n> Provide the application id (an integer number) in which the device ids are valid
|
|
82
|
+
* -s mqtts://<chirpstack server url> Select the URL of the chirpstack server in use
|
|
83
|
+
|
|
84
|
+
Example command line
|
|
85
|
+
```
|
|
86
|
+
node vsm-mqtt-client.js -v -f chirpstack-devices.list -i chirpstack3 -a 12 -s mqtts://chirpstack.company.com -k AQEAf8i6p8...
|
|
87
|
+
```
|
|
88
|
+
## Running with Chirpstack 4
|
|
89
|
+
Note - this integration is not completed since downlinks are not yet implemented which means that the rules will not have effect (and hence devices are not properly updated with almanacs, device time relies on the device).
|
|
90
|
+
|
|
91
|
+
Example command line
|
|
92
|
+
```
|
|
93
|
+
node vsm-mqtt-client.js -v -f chirpstack-devices.list -i chirpstack3 -a appname -s mqtts://chirpstack.company.com -k AQEAf8i6p8...
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Running with Helium
|
|
97
|
+
A general note: At the time of writing this I do not get device time support from Helium. This is solved at application level by this service, but gives lower time precision than LoRaWan native device time.
|
|
98
|
+
|
|
99
|
+
Extra arguments
|
|
100
|
+
* - s mqtts://<mqtt broker URL> Select the URL of the broker
|
|
101
|
+
* - u <username> User name on the broker (possibly ignored)
|
|
102
|
+
* - p <password> Password on the broker for the selected user (possibly ignored)
|
|
103
|
+
|
|
104
|
+
## Helium Console integration
|
|
105
|
+
It is assumed that the device EUI is used as identifier. This means that your MQTT integration topics need be updated to use device_eui instead of device_id (which is the default).
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
Uplink topic: helium/vsm/rx/{{device_eui}}
|
|
109
|
+
|
|
110
|
+
Downlink topic: helium/vsm/tx/{{device_eui}}
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
Point the console to the same mqtt broker.
|
|
114
|
+
|
|
115
|
+
## Running with Yggio
|
|
116
|
+
Needs Chirpstack 3.x and Yggio MongoDB running.
|
|
117
|
+
|
|
118
|
+
Requires the following environment variables to be set in constants.json:
|
|
119
|
+
* MONGODB.URI - MongoDB connection URI
|
|
120
|
+
|
|
121
|
+
Extra Arguments
|
|
122
|
+
* -a <n> Provide the application id (an integer number) in which the device ids are valid
|
|
123
|
+
* -s mqtts://<chirpstack server url> Select the URL of the chirpstack server in use
|
|
124
|
+
|
|
125
|
+
### Notes
|
|
126
|
+
While experimenting with this it was clear that the helium console did not work with HiveMQ cloud,
|
|
127
|
+
|
|
128
|
+
### Example command lines
|
|
129
|
+
node vsm-mqtt-client.js -v -w -i helium -s mqtt://test.mosquitto.org:1883 -k AQEAf8i... -u username -p pass Run with all devices, using helium as lora server (set up to push data to test.mosquitto.org)
|
|
130
|
+
|
|
131
|
+
# Decorators
|
|
132
|
+
The decorator can be selected or developed to filter or transform the data so it fits the application.
|
|
133
|
+
|
|
134
|
+
# Publishers
|
|
135
|
+
Publishers have the role of making the decorated translated data available to downstream applications.
|
|
136
|
+
|
|
137
|
+
## Console Publisher
|
|
138
|
+
The console publisher (default) is selected with the -O console command line option. It will print a formatted version of the decorated object to the command line.
|
|
139
|
+
|
|
140
|
+
## HTTP(s) publisher
|
|
141
|
+
The HTTPS publisher is selected with the -O https command line option.
|
|
142
|
+
|
|
143
|
+
### Extra command line arguments
|
|
144
|
+
The HTTPS publisher will require an additional command line argument
|
|
145
|
+
```
|
|
146
|
+
-S <URL> Select the URL for the target. The object will be passed as application/json and in the format given by the decorator.
|
|
147
|
+
```
|
|
148
|
+
Note: It would be simple to add an option to replace a placeholder in the url with the deveui of the device.
|
|
149
|
+
|
|
150
|
+
###
|
|
151
|
+
|
|
152
|
+
## MQTT publisher
|
|
153
|
+
The mqtt publisher (mqtt) is selected with the -O mqtt command line option.
|
|
154
|
+
|
|
155
|
+
### Extra command line arguments
|
|
156
|
+
The MQTT publisher will require two additional command line arguments
|
|
157
|
+
```
|
|
158
|
+
-S <mqtt broker url> Select the URL for the target MQTT broker
|
|
159
|
+
-T <topic format> Decide the topic format for the published data.
|
|
160
|
+
```
|
|
161
|
+
### Topic Format
|
|
162
|
+
The topic format is used to format how the mqtt publishing is done. Basically it will be done literally as is, the only exception is that the text deveui will be replaced with the deveui of the device in lowercase OR the text DEVEUI will be replaced with the device deveui in UPPERCASE.
|
|
163
|
+
|
|
164
|
+
### Command line example
|
|
165
|
+
Run select devices with data from helium, publish only latest values to mqtt node vsm-mqtt-client.js -f helium-devices.list -i helium -s mqtt://test.mosquitto.org:1883 -k AQEAf8i6p... -u test -p pass -d minimal -O mqtt -S mqtt://test.mosquitto.org:1883 -T interpreted/deveui/data
|
|
166
|
+
|
|
167
|
+
Run with data from helium, all devices, print full data to console node vsm-mqtt-client.js -w -i helium -s mqtt://test.mosquitto.org:1883 -k AQEAf8i6... -u test -p pass
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# Extensibility
|
|
171
|
+
To add your own implementations there is a series of different environment variables that can be set to override the defaults. See the implementation in vsm-mqtt-client.js for the full list. Use this as an alternative to forking this repository and isolating your changes and additions.
|
|
172
|
+
|
|
173
|
+
# Code Structure
|
|
174
|
+
This code is designed to listen to a lorawan network server publishing the raw uplinks from a VSM device. It will translate it correctly and can additionally control the device with necessary downlinks to keep it up-to-date with regards to assistance positions. Once new data is available it will be re-published using a publisher and finally the data will be stored using the storage mechanism.
|
|
175
|
+
|
|
176
|
+
## vsm-mqtt-client.js
|
|
177
|
+
The main file which ties together the below components and contains the main logic.
|
|
178
|
+
|
|
179
|
+
## integrations/
|
|
180
|
+
Contains the currently supported integrations. Each integration can have their own options.
|
|
181
|
+
|
|
182
|
+
## decorators/
|
|
183
|
+
Contains the currently supported decorators. A decorator transforms the actual representation of the data into something palatable by the user, e.g. it can change names of fields or filter out information of no interest to the user (there is plenty of bookkeeping information in the translated object).
|
|
184
|
+
|
|
185
|
+
## publishers/
|
|
186
|
+
Contains the currently supported publishers. A publisher will publish the data from the integration, translated and decorated. The publishers include an mqtt publisher and a console publisher.
|
|
187
|
+
|
|
188
|
+
## solvers/
|
|
189
|
+
Contains the solver implementations. The default is loracloud but provide -z argument to select none or some of the other solvers. Currently loracloud is the default solver, but aws and none is options. With none selected no position solving is enabled.
|
|
190
|
+
|
|
191
|
+
## store.js
|
|
192
|
+
Provides object storage and error storage. This is required in order to save the state of the sensors between uplinks.
|
|
193
|
+
|
|
194
|
+
## util.js
|
|
195
|
+
Utility functions, in particular object merging
|
|
196
|
+
|
|
197
|
+
## solvers/loracloud.js
|
|
198
|
+
Client code to the lora cloud. You will have to provide your own API key [-k option] in order to use the loracloud solver functionality.
|
|
199
|
+
|
|
200
|
+
## solvers/aws.js
|
|
201
|
+
Client code to the aws implementation of lora cloud. You will have to provide your own AWS Access Key Id and Secret Access Key in order to use the AWS solver functionality. As of now AWS does not support full almanac download
|
|
202
|
+
|
|
203
|
+
## rules.js
|
|
204
|
+
Rules for updating the device with downlinks depending on the device state.
|
|
205
|
+
|
|
206
|
+
# Suggested extensions
|
|
207
|
+
* Add support for skipping the list of devices, instead use MQTT wildcards and filter on the deveui range
|
|
208
|
+
* vsm-translator-open-source
|
|
209
|
+
The device data translator is a separate open source repository. It contains the functionality for decoding all of the products and versions released by Sensative. It is recommended to frequently update this as new products and versions are published frequently.
|
|
210
|
+
* Add support for other products by generalizing the translator selection per device
|
package/constants.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"AWS": {
|
|
3
|
+
"VERSION": "<SDK VERSION (YYYY-MM-DD)>",
|
|
4
|
+
"ACCESS_KEY_ID": "<YOUR ACCESS KEY ID>",
|
|
5
|
+
"SECRET_ACCESS_KEY": "<YOUR SECRET ACCESS KEY>",
|
|
6
|
+
"REGION": "<YOUR REGION, EU NORTH IS NOT SUPPORTED>"
|
|
7
|
+
},
|
|
8
|
+
"MONGODB": {
|
|
9
|
+
"URI": "mongodb://172.18.0.16:27017"
|
|
10
|
+
},
|
|
11
|
+
"MQTT": {
|
|
12
|
+
"CLIENT_ID": "vsm_client_f6fbda4d"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Decoration which only provides the current values + the position
|
|
2
|
+
|
|
3
|
+
module.exports.api = {
|
|
4
|
+
decorate: (obj, deveui) => {
|
|
5
|
+
let result = {};
|
|
6
|
+
if (obj.output)
|
|
7
|
+
result = JSON.parse(JSON.stringify(obj.output))
|
|
8
|
+
result.latitude = obj.latitude;
|
|
9
|
+
result.longitude = obj.longitude;
|
|
10
|
+
result.accuracy = obj.accuracy;
|
|
11
|
+
result.positionTimestamp = obj.positionTimestamp;
|
|
12
|
+
result.appName = (obj.vsm && obj.vsm.appName) ? obj.vsm.appName : "unknown";
|
|
13
|
+
return result;
|
|
14
|
+
},
|
|
15
|
+
getVersionString: () => {
|
|
16
|
+
return "Minimal Object Decorator";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Publisher which does a transformation to vaguely resemble the format of yggio pushes
|
|
2
|
+
|
|
3
|
+
module.exports.api = {
|
|
4
|
+
decorate: (obj, deveui) => {
|
|
5
|
+
return { payload: { iotnode: { _id:deveui.toLowerCase(), ...obj } } };
|
|
6
|
+
},
|
|
7
|
+
getVersionString: () => {
|
|
8
|
+
return "Yggio-like push transformation (add payload.iotnode._id field)";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
const mqtt = require('mqtt')
|
|
2
|
+
const { isDate } = require('util/types');
|
|
3
|
+
|
|
4
|
+
const printUsageAndExit = (info) => {
|
|
5
|
+
console.log(info);
|
|
6
|
+
process.exit(1);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const getMaxSize = (obj) => {
|
|
10
|
+
if (obj.txInfo && Number.isInteger(obj.txInfo.dr)) {
|
|
11
|
+
if (obj.txInfo.dr <= 2)
|
|
12
|
+
return 51;
|
|
13
|
+
if (obj.txInfo.dr == 3)
|
|
14
|
+
return 115;
|
|
15
|
+
return 222;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return 51;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports.api = {
|
|
22
|
+
getVersionString: () => { return "Chirpstack 3.x MQTT Integration"; },
|
|
23
|
+
checkArgumentsOrExit: (args) => {
|
|
24
|
+
if (!args.a || !isFinite(args.a))
|
|
25
|
+
printUsageAndExit("Chirpstack: -a <application-id> is required and should be an integer number");
|
|
26
|
+
if (!args.s)
|
|
27
|
+
printUsageAndExit("Chirpstack: -s <server url> is required");
|
|
28
|
+
},
|
|
29
|
+
connectAndSubscribe: async (args, devices, onUplinkDevicePortBufferDateLatLng) => {
|
|
30
|
+
args.v && console.log("Trying to connect to " + args.s + " with application " + args.a);
|
|
31
|
+
try {
|
|
32
|
+
let client;
|
|
33
|
+
if (process.env.CS3_USERNAME || process.env.CS3_PASSWORD || process.env.CS3_CLIENTID) {
|
|
34
|
+
args.v && console.log(" Note: Using user name " + process.env.CS3_USERNAME);
|
|
35
|
+
client = mqtt.connect(args.s, {
|
|
36
|
+
clientId: process.env.CS3_CLIENTID ? process.env.CS3_CLIENTID : "vsm-mqtt-client",
|
|
37
|
+
username: process.env.CS3_USERNAME ? process.env.CS3_USERNAME : "",
|
|
38
|
+
password: process.env.CS3_PASSWORD ? process.env.CS3_PASSWORD : "",
|
|
39
|
+
});
|
|
40
|
+
} else {
|
|
41
|
+
client = mqtt.connect(args.s);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
client.on('error', (e) => {
|
|
45
|
+
console.log("MQTT Chirpstack 3 Connection Error", e);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
client.on('connect', () => {
|
|
50
|
+
args.v && console.log("Connected to chirpstack server");
|
|
51
|
+
|
|
52
|
+
/* Would work if all devices were vsm devices, but do not make that assumption
|
|
53
|
+
const allTopics = `application/${args.a}/#`;
|
|
54
|
+
client.subscribe(allTopics, (err) => {
|
|
55
|
+
if (err) {
|
|
56
|
+
console.log("Chirpstack subscribe all error: " + err.message);
|
|
57
|
+
} else {
|
|
58
|
+
console.log("Chirpstack subscribe all ok");
|
|
59
|
+
}
|
|
60
|
+
})});
|
|
61
|
+
|
|
62
|
+
Instead:
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
if (Array.isArray(devices) && devices.length > 0) {
|
|
66
|
+
for (let i = 0; i < devices.length; ++i) {
|
|
67
|
+
const topic = `application/${args.a}/device/${devices[i].toLowerCase()}/event/up`;
|
|
68
|
+
client.subscribe(topic, (err) => {
|
|
69
|
+
if (err)
|
|
70
|
+
console.log(`Chirpstack subscribe: ${topic} failed:` + err.message );
|
|
71
|
+
else
|
|
72
|
+
args.v && console.log(`Chirpstack subscribed ok to ${topic}`);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
// Use wildcard for the subscription
|
|
77
|
+
const topic = `application/${args.a}/device/+/event/up`;
|
|
78
|
+
client.subscribe(topic, (err) => {
|
|
79
|
+
if (err)
|
|
80
|
+
console.log(`Chirpstack subscribe: ${topic} failed:` + err.message );
|
|
81
|
+
else
|
|
82
|
+
args.v && console.log(`Chirpstack subscribed ok to ${topic}`);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
client.on('message', async (topic, message) => {
|
|
87
|
+
// message is Buffer
|
|
88
|
+
args.v && console.log(topic, message.toString());
|
|
89
|
+
|
|
90
|
+
const obj = JSON.parse(message.toString('utf-8'));
|
|
91
|
+
if (!obj.data)
|
|
92
|
+
return;
|
|
93
|
+
const data = Buffer.from(obj.data, "base64");
|
|
94
|
+
const port = obj.fPort;
|
|
95
|
+
const id = obj.devEUI;
|
|
96
|
+
const maxSize = getMaxSize(obj);
|
|
97
|
+
|
|
98
|
+
let lat, lng;
|
|
99
|
+
let date;
|
|
100
|
+
// Take first gateways lat & lng values, any gateway likely to hear this is likely within 150km
|
|
101
|
+
if (obj.rxInfo && obj.rxInfo.length > 0) {
|
|
102
|
+
let gwinfo = obj.rxInfo[0];
|
|
103
|
+
if (gwinfo.location) {
|
|
104
|
+
lat = gwinfo.location.latitude;
|
|
105
|
+
lng = gwinfo.location.longitude;
|
|
106
|
+
}
|
|
107
|
+
date = new Date(gwinfo.time);
|
|
108
|
+
}
|
|
109
|
+
if (! (date && isDate(date)))
|
|
110
|
+
date = new Date()
|
|
111
|
+
|
|
112
|
+
await onUplinkDevicePortBufferDateLatLng(client, id, port, data, date, lat, lng, maxSize);
|
|
113
|
+
});
|
|
114
|
+
return client;
|
|
115
|
+
} catch (e) {
|
|
116
|
+
console.log("Chirpstack: Got exception: " + e.message);
|
|
117
|
+
throw e;
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
sendDownlink: async (client, args, deviceId, port, data, confirmed) => {
|
|
121
|
+
if (!Buffer.isBuffer(data))
|
|
122
|
+
throw new Error("Chirpstack sendDownlink: data must be a buffer object");
|
|
123
|
+
const devEUI = deviceId.toLowerCase();
|
|
124
|
+
const topic = `application/${args.a}/device/${devEUI}/command/down`;
|
|
125
|
+
const obj = {
|
|
126
|
+
devEUI,
|
|
127
|
+
confirmed,
|
|
128
|
+
fPort: port,
|
|
129
|
+
data: data.toString('base64'),
|
|
130
|
+
};
|
|
131
|
+
client.publish(topic, JSON.stringify(obj));
|
|
132
|
+
args.v && console.log("Publish downlink on port " + port + " data: " + data.toString("hex"));
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
const mqtt = require('mqtt')
|
|
2
|
+
const { isDate } = require('util/types');
|
|
3
|
+
|
|
4
|
+
const printUsageAndExit = (info) => {
|
|
5
|
+
console.log(info);
|
|
6
|
+
process.exit(1);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const getMaxSize = (obj) => {
|
|
10
|
+
if (Number.isInteger(obj.dr)) {
|
|
11
|
+
if (obj.dr <= 2)
|
|
12
|
+
return 51;
|
|
13
|
+
if (obj.dr == 3)
|
|
14
|
+
return 115;
|
|
15
|
+
return 222;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return 51;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports.api = {
|
|
22
|
+
getVersionString: () => { return "Chirpstack 4.x MQTT Integration"; },
|
|
23
|
+
checkArgumentsOrExit: (args) => {
|
|
24
|
+
if (!args.a)
|
|
25
|
+
printUsageAndExit("Chirpstack: -a <application-id> is required");
|
|
26
|
+
if (!args.s)
|
|
27
|
+
printUsageAndExit("Chirpstack: -s <server url> is required");
|
|
28
|
+
},
|
|
29
|
+
connectAndSubscribe: async (args, devices, onUplinkDevicePortBufferDateLatLng) => {
|
|
30
|
+
args.v && console.log("Trying to connect to " + args.s + " with application " + args.a);
|
|
31
|
+
try {
|
|
32
|
+
const client = mqtt.connect(args.s);
|
|
33
|
+
|
|
34
|
+
client.on('connect', () => {
|
|
35
|
+
args.v && console.log("Connected to chirpstack server");
|
|
36
|
+
|
|
37
|
+
/* Would work if all devices were vsm devices, but do not make that assumption
|
|
38
|
+
const allTopics = `application/${args.a}/#`;
|
|
39
|
+
client.subscribe(allTopics, (err) => {
|
|
40
|
+
if (err) {
|
|
41
|
+
console.log("Chirpstack subscribe all error: " + err.message);
|
|
42
|
+
} else {
|
|
43
|
+
console.log("Chirpstack subscribe all ok");
|
|
44
|
+
}
|
|
45
|
+
})});
|
|
46
|
+
|
|
47
|
+
Instead:
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
if (Array.isArray(devices) && devices.length > 0) {
|
|
51
|
+
for (let i = 0; i < devices.length; ++i) {
|
|
52
|
+
const topic = `application/${args.a}/device/${devices[i].toLowerCase()}/event/up`;
|
|
53
|
+
client.subscribe(topic, (err) => {
|
|
54
|
+
if (err)
|
|
55
|
+
console.log(`Chirpstack subscribe: ${topic} failed:` + err.message );
|
|
56
|
+
else
|
|
57
|
+
args.v && console.log(`Chirpstack subscribed ok to ${topic}`);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
// Use wildcard for the subscription
|
|
62
|
+
const topic = `application/${args.a}/device/+/event/up`;
|
|
63
|
+
client.subscribe(topic, (err) => {
|
|
64
|
+
if (err)
|
|
65
|
+
console.log(`Chirpstack subscribe: ${topic} failed:` + err.message );
|
|
66
|
+
else
|
|
67
|
+
args.v && console.log(`Chirpstack subscribed ok to ${topic}`);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
client.on('message', async (topic, message) => {
|
|
72
|
+
// message is Buffer
|
|
73
|
+
args.v && console.log(topic, message.toString());
|
|
74
|
+
|
|
75
|
+
const obj = JSON.parse(message.toString('utf-8'));
|
|
76
|
+
if (!obj.data)
|
|
77
|
+
return;
|
|
78
|
+
const data = Buffer.from(obj.data, "base64");
|
|
79
|
+
const port = obj.fPort;
|
|
80
|
+
const id = obj.deviceInfo.devEui;
|
|
81
|
+
const maxSize = getMaxSize(obj);
|
|
82
|
+
|
|
83
|
+
let lat, lng;
|
|
84
|
+
let date;
|
|
85
|
+
// Take first gateways lat & lng values, any gateway likely to hear this is likely within 150km
|
|
86
|
+
if (obj.rxInfo && obj.rxInfo.length > 0) {
|
|
87
|
+
let gwinfo = obj.rxInfo[0];
|
|
88
|
+
if (gwinfo.location) {
|
|
89
|
+
lat = gwinfo.location.latitude;
|
|
90
|
+
lng = gwinfo.location.longitude;
|
|
91
|
+
}
|
|
92
|
+
date = new Date(gwinfo.time);
|
|
93
|
+
}
|
|
94
|
+
if (! (date && isDate(date)))
|
|
95
|
+
date = new Date()
|
|
96
|
+
|
|
97
|
+
await onUplinkDevicePortBufferDateLatLng(client, id, port, data, date, lat, lng, maxSize);
|
|
98
|
+
});
|
|
99
|
+
return client;
|
|
100
|
+
} catch (e) {
|
|
101
|
+
console.log("Chirpstack: Got exception: " + e.message);
|
|
102
|
+
throw e;
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
sendDownlink: async (client, args, deviceId, port, data, confirmed) => {
|
|
106
|
+
if (!Buffer.isBuffer(data))
|
|
107
|
+
throw new Error("Chirpstack sendDownlink: data must be a buffer object");
|
|
108
|
+
const devEUI = deviceId.toLowerCase();
|
|
109
|
+
const topic = `application/${args.a}/device/${devEUI}/command/down`;
|
|
110
|
+
const obj = {
|
|
111
|
+
devEui: devEUI,
|
|
112
|
+
confirmed,
|
|
113
|
+
fPort: port,
|
|
114
|
+
payload: data.toString('base64'),
|
|
115
|
+
};
|
|
116
|
+
client.publish(topic, JSON.stringify(obj));
|
|
117
|
+
args.v && console.log("Publish downlink on port " + port + " data: " + data.toString("hex"));
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
|