Merge pull request #815 from dasdigidings/master

lorawan-device repo initial files
This commit is contained in:
Verkehrsrot 2021-07-01 18:37:02 +02:00 committed by GitHub
commit 46f3827871
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 686 additions and 0 deletions

View File

@ -0,0 +1,12 @@
vendors:
- id: example
name: Example
vendorID: 0
draft: true
#SNIP - a lot of other vendors, just attach the lower lines to the index.yaml
#SNIP - could add a logo with "logo: filename"
- id: opensource
name: Open Source Community Projects
website: https://en.wikipedia.org/wiki/Open_source

View File

@ -0,0 +1,21 @@
# lorawan-devices repo for The Things Network & The Things Stack V3
To add bigger payload decoders than 4k (via web ui) we provide the metadata to the lorawan-devices repo on github. For this we create a vendor "opensource" and specify a device "esp32-paxcounter" via this files:
> /
> - index.yaml (include the marked lines at the bottom of the original file)
>
> /vendor (existing folder in the repo)
>
> - /opensource (new folder with all the good stuff inside)
>
> /vendor/opensource
>
> - index.yaml (name of the device inside)
> - esp32-paxcounter.yaml (metadata for the software)
> - esp32-paxcounter-profile-eu868.yaml (profile for europe)
> - esp32-paxcounter-profile-us915.yaml (profile for north america)
> - esp32-paxcounter-codec.yaml (examples for the payload decoder)
> - esp32-paxcounter-packed_decodeUplink.js (payload decoder as provided before for the web ui, now for the lorawan devices repo)
With these files, we can make a pull request at the lorawan-devices repo: https://github.com/TheThingsNetwork/lorawan-devices and hope to be included in the future. Cool thing about it would be that then the users need only to choose "Open Source Community Projects" from the vendor list and "EXP32-Paxcounter" as device and most of the compex things is taking care of (like choosing the right LoRaWAN version and installing the payload decoder).

View File

@ -0,0 +1,127 @@
# Uplink decoder decodes binary data uplink into a JSON object (optional)
# For documentation on writing encoders and decoders, see: https://thethingsstack.io/integrations/payload-formatters/javascript/
uplinkDecoder:
fileName: esp32-paxcounter-packed_decodeUplink.js
examples:
- description: Paxcount data
input:
fPort: 1
bytes: [0x00, 0x07, 0x00, 0x03]
output:
data:
bytes: [0x00, 0x07, 0x00, 0x03]
port: 1
wifi: 7
ble: 3
errors: []
warnings: []
- description: Device status query result
input:
fPort: 2
bytes: [0x01, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x4B, 0xC0, 0x2D, 0x00, 0x03, 0x4B, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00]
output:
data:
bytes: [0x01, 0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x4B, 0xC0, 0x2D, 0x00, 0x03, 0x4B, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00]
port: 2
voltage: 303
uptime: 216000
cputemp: 45
memory: 216000
reset0: 0
restarts: 0
errors: []
warnings: []
- description: Device config data
input:
fPort: 3
bytes: [0x09, 0x0F, 0x00, 0x00, 0x78, 0x32, 0x0A, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
output:
data:
bytes: [0x09, 0x0F, 0x00, 0x00, 0x78, 0x32, 0x0A, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
port: 3
loradr: 9
txpower: 15
rssilimit: 0
sendcycle: 120
wifichancycle: 50
blescantime: 10
rgblum: 30
flags: 0
payloadmask: 0
version: 0
errors: []
warnings: []
- description: GPS data
input:
fPort: 4
bytes: [0x6E, 0x1C, 0x07, 0x03, 0x0A, 0x63, 0x6F, 0x0B, 0x51, 0x00, 0xCD, 0x00]
output:
data:
bytes: [0x6E, 0x1C, 0x07, 0x03, 0x0A, 0x63, 0x6F, 0x0B, 0x51, 0x00, 0xCD, 0x00]
port: 4
latitude: 50.797678
longitude: 7.29985
sats: 11
hdop: 0.81
altitude: 205
errors: []
warnings: []
- description: Button data
input:
fPort: 5
bytes: [0x01]
output:
data:
bytes: [0x00]
port: 5
button: 1
errors: []
warnings: []
- description: Environmental sensor data
input:
fPort: 7
bytes: [0x08, 0x34, 0x27, 0x10, 0x1F, 0x40, 0x27, 0x10]
output:
data:
bytes: [0x08, 0x34, 0x27, 0x10, 0x1F, 0x40, 0x27, 0x10]
port: 7
temperature: 21.00
pressure: 1000.0
humidity: 80.00
air: 100.00
errors: []
warnings: []
- description: Battery voltage data
input:
fPort: 8
bytes: [0x01, 0x2F]
output:
data:
bytes: [0x01, 0x2F]
port: 8
voltage: 303
errors: []
warnings: []
- description: Time/Date
input:
fPort: 9
bytes: [0x60, 0xC8, 0x86, 0x90, 0x00]
output:
data:
bytes: [0x60, 0xC8, 0x86, 0x90, 0x00]
port: 9
time: 1623754384
timestatus: 0
errors: []
warnings: []
- description: User sensor data
input:
fPort: 10
bytes: [0x00]
output:
data:
bytes: [0x00]
port: 10
ens: 0
errors: []
warnings: []

View File

@ -0,0 +1,344 @@
// Decoder for device payload encoder "PACKED"
// copy&paste to TTN Console V3 -> Applications -> Payload formatters -> Uplink -> Javascript
// modified for The Things Stack V3 by Caspar Armster, dasdigidings e.V.
function decodeUplink(input) {
var data = {};
if (input.fPort === 1) {
// only wifi counter data, no gps
if (input.bytes.length === 2) {
data = decode(input.bytes, [uint16], ['wifi']);
}
// wifi + ble counter data, no gps
if (input.bytes.length === 4) {
data = decode(input.bytes, [uint16, uint16], ['wifi', 'ble']);
}
// combined wifi + ble + SDS011
if (input.bytes.length === 8) {
data = decode(input.bytes, [uint16, uint16, uint16, uint16], ['wifi', 'ble', 'PM10', 'PM25']);
}
// combined wifi counter and gps data, used by https://opensensemap.org
if (input.bytes.length === 10) {
data = decode(input.bytes, [latLng, latLng, uint16], ['latitude', 'longitude', 'wifi']);
}
// combined wifi + ble counter and gps data, used by https://opensensemap.org
if (input.bytes.length === 12) {
data = decode(input.bytes, [latLng, latLng, uint16, uint16], ['latitude', 'longitude', 'wifi', 'ble']);
}
// combined wifi counter and gps data
if (input.bytes.length === 15) {
data = decode(input.bytes, [uint16, latLng, latLng, uint8, hdop, altitude], ['wifi', 'latitude', 'longitude', 'sats', 'hdop', 'altitude']);
}
// combined wifi + ble counter and gps data
if (input.bytes.length === 17) {
data = decode(input.bytes, [uint16, uint16, latLng, latLng, uint8, hdop, altitude], ['wifi', 'ble', 'latitude', 'longitude', 'sats', 'hdop', 'altitude']);
}
data.pax = 0;
if ('wifi' in data) {
data.pax += data.wifi;
}
if ('ble' in data) {
data.pax += data.ble;
}
}
if (input.fPort === 2) {
// device status data
if (input.bytes.length === 20) {
data = decode(input.bytes, [uint16, uptime, uint8, uint32, uint8, uint32], ['voltage', 'uptime', 'cputemp', 'memory', 'reset0', 'restarts']);
}
}
if (input.fPort === 3) {
// device config data
data = decode(input.bytes, [uint8, uint8, int16, uint8, uint8, uint8, uint8, bitmap1, bitmap2, version], ['loradr', 'txpower', 'rssilimit', 'sendcycle', 'wifichancycle', 'blescantime', 'rgblum', 'flags', 'payloadmask', 'version']);
}
if (input.fPort === 4) {
// gps data
if (input.bytes.length === 8) {
data = decode(input.bytes, [latLng, latLng], ['latitude', 'longitude']);
} else {
data = decode(input.bytes, [latLng, latLng, uint8, hdop, altitude], ['latitude', 'longitude', 'sats', 'hdop', 'altitude']);
}
}
if (input.fPort === 5) {
// button pressed
data = decode(input.bytes, [uint8], ['button']);
}
if (input.fPort === 7) {
// BME680 sensor data
data = decode(input.bytes, [float, pressure, ufloat, ufloat], ['temperature', 'pressure', 'humidity', 'air']);
}
if (input.fPort === 8) {
// battery voltage
data = decode(input.bytes, [uint16], ['voltage']);
}
if (input.fPort === 9) {
// timesync request
if (input.bytes.length === 1) {
data.timesync_seqno = input.bytes[0];
}
// epoch time answer
if (input.bytes.length === 5) {
data = decode(input.bytes, [uint32, uint8], ['time', 'timestatus']);
}
}
if (input.fPort === 10) {
// ENS count
data = decode(input.bytes, [uint16], ['ens']);
}
data.bytes = input.bytes; // comment out if you do not want to include the original payload
data.port = input.fPort; // comment out if you do not want to inlude the port
return {
data: data,
warnings: [],
errors: []
};
}
function encodeDownlink(input) {
return {
data: {
bytes: input.bytes
},
warnings: ["Encoding of downlink is not supported by the JS decoder."],
errors: []
}
}
function decodeDownlink(input) {
return {
data: {
bytes: input.bytes
},
warnings: ["Decoding of downlink is not supported by the JS decoder."],
errors: []
}
}
// ----- contents of /src/decoder.js --------------------------------------------
// https://github.com/thesolarnomad/lora-serialization/blob/master/src/decoder.js
var bytesToInt = function (bytes) {
var i = 0;
for (var x = 0; x < bytes.length; x++) {
i |= (bytes[x] << (x * 8));
}
return i;
};
var version = function (bytes) {
if (bytes.length !== version.BYTES) {
throw new Error('version must have exactly 10 bytes');
}
return String.fromCharCode.apply(null, bytes).split('\u0000')[0];
};
version.BYTES = 10;
var uint8 = function (bytes) {
if (bytes.length !== uint8.BYTES) {
throw new Error('uint8 must have exactly 1 byte');
}
return bytesToInt(bytes);
};
uint8.BYTES = 1;
var uint16 = function (bytes) {
if (bytes.length !== uint16.BYTES) {
throw new Error('uint16 must have exactly 2 bytes');
}
return bytesToInt(bytes);
};
uint16.BYTES = 2;
var uint32 = function (bytes) {
if (bytes.length !== uint32.BYTES) {
throw new Error('uint32 must have exactly 4 bytes');
}
return bytesToInt(bytes);
};
uint32.BYTES = 4;
var uint64 = function (bytes) {
if (bytes.length !== uint64.BYTES) {
throw new Error('uint64 must have exactly 8 bytes');
}
return bytesToInt(bytes);
};
uint64.BYTES = 8;
var int8 = function (bytes) {
if (bytes.length !== int8.BYTES) {
throw new Error('int8 must have exactly 1 byte');
}
var value = +(bytesToInt(bytes));
if (value > 127) {
value -= 256;
}
return value;
};
int8.BYTES = 1;
var int16 = function (bytes) {
if (bytes.length !== int16.BYTES) {
throw new Error('int16 must have exactly 2 bytes');
}
var value = +(bytesToInt(bytes));
if (value > 32767) {
value -= 65536;
}
return value;
};
int16.BYTES = 2;
var int32 = function (bytes) {
if (bytes.length !== int32.BYTES) {
throw new Error('int32 must have exactly 4 bytes');
}
var value = +(bytesToInt(bytes));
if (value > 2147483647) {
value -= 4294967296;
}
return value;
};
int32.BYTES = 4;
var latLng = function (bytes) {
return +(int32(bytes) / 1e6).toFixed(6);
};
latLng.BYTES = int32.BYTES;
var uptime = function (bytes) {
return uint64(bytes);
};
uptime.BYTES = uint64.BYTES;
var hdop = function (bytes) {
return +(uint16(bytes) / 100).toFixed(2);
};
hdop.BYTES = uint16.BYTES;
var altitude = function (bytes) {
// Option to increase altitude resolution (also on encoder side)
// return +(int16(bytes) / 4 - 1000).toFixed(1);
return +(int16(bytes));
};
altitude.BYTES = int16.BYTES;
var float = function (bytes) {
if (bytes.length !== float.BYTES) {
throw new Error('Float must have exactly 2 bytes');
}
var isNegative = bytes[0] & 0x80;
var b = ('00000000' + Number(bytes[0]).toString(2)).slice(-8)
+ ('00000000' + Number(bytes[1]).toString(2)).slice(-8);
if (isNegative) {
var arr = b.split('').map(function (x) { return !Number(x); });
for (var i = arr.length - 1; i > 0; i--) {
arr[i] = !arr[i];
if (arr[i]) {
break;
}
}
b = arr.map(Number).join('');
}
var t = parseInt(b, 2);
if (isNegative) {
t = -t;
}
return +(t / 100).toFixed(2);
};
float.BYTES = 2;
var ufloat = function (bytes) {
return +(uint16(bytes) / 100).toFixed(2);
};
ufloat.BYTES = uint16.BYTES;
var pressure = function (bytes) {
return +(uint16(bytes) / 10).toFixed(1);
};
pressure.BYTES = uint16.BYTES;
var bitmap1 = function (byte) {
if (byte.length !== bitmap1.BYTES) {
throw new Error('Bitmap must have exactly 1 byte');
}
var i = bytesToInt(byte);
var bm = ('00000000' + Number(i).toString(2)).substr(-8).split('').map(Number).map(Boolean);
return ['adr', 'screensaver', 'screen', 'countermode', 'blescan', 'antenna', 'reserved', 'reserved']
.reduce(function (obj, pos, index) {
obj[pos] = +bm[index];
return obj;
}, {});
};
bitmap1.BYTES = 1;
var bitmap2 = function (byte) {
if (byte.length !== bitmap2.BYTES) {
throw new Error('Bitmap must have exactly 1 byte');
}
var i = bytesToInt(byte);
var bm = ('00000000' + Number(i).toString(2)).substr(-8).split('').map(Number).map(Boolean);
return ['battery', 'sensor3', 'sensor2', 'sensor1', 'gps', 'bme', 'reserved', 'counter']
.reduce(function (obj, pos, index) {
obj[pos] = +bm[index];
return obj;
}, {});
};
bitmap2.BYTES = 1;
var decode = function (bytes, mask, names) {
var maskLength = mask.reduce(function (prev, cur) {
return prev + cur.BYTES;
}, 0);
if (bytes.length < maskLength) {
throw new Error('Mask length is ' + maskLength + ' whereas input is ' + bytes.length);
}
names = names || [];
var offset = 0;
return mask
.map(function (decodeFn) {
var current = bytes.slice(offset, offset += decodeFn.BYTES);
return decodeFn(current);
})
.reduce(function (prev, cur, idx) {
prev[names[idx] || idx] = cur;
return prev;
}, {});
};
if (typeof module === 'object' && typeof module.exports !== 'undefined') {
module.exports = {
uint8: uint8,
uint16: uint16,
uint32: uint32,
int8: int8,
int16: int16,
int32: int32,
uptime: uptime,
float: float,
ufloat: ufloat,
pressure: pressure,
latLng: latLng,
hdop: hdop,
altitude: altitude,
bitmap1: bitmap1,
bitmap2: bitmap2,
version: version,
decode: decode
};
}

View File

@ -0,0 +1,24 @@
# Vendor profile ID, can be freely issued by the vendor
# This vendor profile ID is also used on the QR code for LoRaWAN devices, see
# https://lora-alliance.org/sites/default/files/2020-10/LoRa_Alliance_Vendor_ID_for_QR_Code.pdf
#vendorProfileID: 0
# Whether the end device supports class B
supportsClassB: false
# Whether the end device supports class C
supportsClassC: false
# LoRaWAN MAC version: 1.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4 or 1.1
macVersion: 1.0.3
# LoRaWAN Regional Parameters version. Values depend on the LoRaWAN version:
# 1.0: TS001-1.0
# 1.0.1: TS001-1.0.1
# 1.0.2: RP001-1.0.2 or RP001-1.0.2-RevB
# 1.0.3: RP001-1.0.3-RevA
# 1.0.4: RP002-1.0.0 or RP002-1.0.1
# 1.1: RP001-1.1-RevA or RP001-1.1-RevB
regionalParametersVersion: RP001-1.0.3-RevA
# Whether the end device supports join (OTAA) or not (ABP)
supportsJoin: true
# Maximum EIRP
maxEIRP: 16
# Whether the end device supports 32-bit frame counters
supports32bitFCnt: true

View File

@ -0,0 +1,24 @@
# Vendor profile ID, can be freely issued by the vendor
# This vendor profile ID is also used on the QR code for LoRaWAN devices, see
# https://lora-alliance.org/sites/default/files/2020-10/LoRa_Alliance_Vendor_ID_for_QR_Code.pdf
#vendorProfileID: 0
# Whether the end device supports class B
supportsClassB: false
# Whether the end device supports class C
supportsClassC: false
# LoRaWAN MAC version: 1.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4 or 1.1
macVersion: 1.0.3
# LoRaWAN Regional Parameters version. Values depend on the LoRaWAN version:
# 1.0: TS001-1.0
# 1.0.1: TS001-1.0.1
# 1.0.2: RP001-1.0.2 or RP001-1.0.2-RevB
# 1.0.3: RP001-1.0.3-RevA
# 1.0.4: RP002-1.0.0 or RP002-1.0.1
# 1.1: RP001-1.1-RevA or RP001-1.1-RevB
regionalParametersVersion: RP001-1.0.3-RevA
# Whether the end device supports join (OTAA) or not (ABP)
supportsJoin: true
# Maximum EIRP
maxEIRP: 30
# Whether the end device supports 32-bit frame counters
supports32bitFCnt: true

View File

@ -0,0 +1,131 @@
name: ESP32-Paxcounter
description: Software providing a basis on the esp32 platform for multiple sensors, including pax (ble/wifi), gps, temperature, humidity, pressure, pm2.5, pm10 and more.
# Hardware versions (optional, use when you have revisions)
#hardwareVersions:
# - version: '1.0'
# numeric: 1
# Firmware versions (at least one is mandatory)
# LoRaWAN Device Profiles per region
# Supported regions are EU863-870, US902-928, AU915-928, AS923, CN779-787, EU433, CN470-510, KR920-923, IN865-867, RU864-870
firmwareVersions:
- version: '2.4.0'
numeric: 240
profiles:
EU863-870:
id: esp32-paxcounter-profile-eu868
#lorawanCertified: true
codec: esp32-paxcounter-codec
US902-928:
id: esp32-paxcounter-profile-us915
#lorawanCertified: true
codec: esp32-paxcounter-codec
- version: '3.0.0'
numeric: 300
profiles:
EU863-870:
id: esp32-paxcounter-profile-eu868
#lorawanCertified: true
codec: esp32-paxcounter-codec
US902-928:
id: esp32-paxcounter-profile-us915
#lorawanCertified: true
codec: esp32-paxcounter-codec
# Sensors that this device features (optional)
# 4-20 ma, accelerometer, altitude, analog input, auxiliary, barometer, battery, button, bvoc, co, co2, conductivity,
# current, digital input, dissolved oxygen, distance, dust, energy, gps, gyroscope, h2s, humidity, iaq, level, light,
# lightning, link, magnetometer, moisture, motion, no, no2, o3, particulate matter, ph, pir, pm2.5, pm10, potentiometer,
# power, precipitation, pressure, proximity, pulse count, pulse frequency, radar, rainfall, rssi, smart valve, snr, so2,
# solar radiation, sound, strain, surface temperature, temperature, tilt, time, tvoc, uv, vapor pressure, velocity,
# vibration, voltage, water potential, water, weight, wifi ssid, wind direction, wind speed.
sensors:
- battery
- ble
- wifi
- pax
- gps
- altitude
- latitude
- longitude
- hdop
- sats
- temperature
- humidity
- barometer
- pm2.5
- pm10
# Dimensions in mm (optional)
# Use width, height, length and/or diameter
#dimensions:
# width: 22.5
# length: 119
# height: 101
# Weight in grams (optional)
#weight: 160
## Operating conditions (optional)
#operatingConditions:
# Temperature (Celsius)
# temperature:
# min: -30
# max: 60
# Relative humidity (fraction of 1)
# relativeHumidity:
# min: 0
# max: 0.9
# IP rating (optional)
#ipCode: IP20
# Key provisioning (optional)
# Valid values are: custom (user can configure keys), join server and manifest.
keyProvisioning:
- custom
- join server
# Key security (optional)
# Valid values are: none, read protected and secure element.
keySecurity: none
# Product and data sheet URLs (optional)
productURL: https://github.com/cyberman54/ESP32-Paxcounter
dataSheetURL: https://github.com/cyberman54/ESP32-Paxcounter
#resellerURLs:
# - name: 'Reseller 1'
# region:
# - European Union
# url: https://example.org/reseller1
# - name: 'Reseller 2'
# region:
# - United States
# - Canada
# url: https://example.org/reseller2
# Photos
#photos:
# main: Paxcounter-title.jpg
# other:
# - Paxcounter-title.jpg
# Youtube or Vimeo Video (optional)
###video: https://www.youtube.com/watch?v=JHzxcD2oEn8
# Regulatory compliances (optional)
#compliances:
# safety:
# - body: IEC
# norm: EN
# standard: 62368-1
# radioEquipment:
# - body: ETSI
# norm: EN
# standard: 301 489-1
# version: 2.2.0
# - body: ETSI
# norm: EN
# standard: 301 489-3
# version: 2.1.0

View File

@ -0,0 +1,3 @@
endDevices:
# Unique identifier of the end device (lowercase, alphanumeric with dashes, max 36 characters)
- esp32-paxcounter # look in esp32-paxcounter.yaml for the end device definition