diff --git a/vendor/waggle/esp32-paxcounter-codec.yaml b/vendor/waggle/esp32-paxcounter-codec.yaml new file mode 100644 index 0000000000..cc59f7c125 --- /dev/null +++ b/vendor/waggle/esp32-paxcounter-codec.yaml @@ -0,0 +1,143 @@ +# 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.js + examples: + - description: Paxcount data + input: + fPort: 1 + bytes: [0x07, 0x00, 0x03, 0x00] + output: + data: + bytes: [0x07, 0x00, 0x03, 0x00] + port: 1 + wifi: 7 + ble: 3 + pax: 10 + errors: [] + warnings: [] + - description: Device status query result + input: + fPort: 2 + bytes: [0x2F, 0x01, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x4B, 0x03, 0x00, 0x2D, 0xC0, 0x4B, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + output: + data: + bytes: [0x2F, 0x01, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x4B, 0x03, 0x00, 0x2D, 0xC0, 0x4B, 0x03, 0x00, 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: + adr: 0 + antenna: 0 + blescan: 0 + countermode: 0 + reserved: 0 + screen: 0 + screensaver: 0 + payloadmask: + battery: 0 + bme: 0 + counter: 0 + gps: 0 + reserved: 0 + sensor1: 0 + sensor2: 0 + sensor3: 0 + version: '' + errors: [] + warnings: [] + - description: GPS data + input: + fPort: 4 + bytes: [0x65, 0xCA, 0x06, 0x03, 0x05, 0x19, 0x6F, 0x00, 0x05, 0xC6, 0x00, 0x42, 0x00] + output: + data: + bytes: [0x65, 0xCA, 0x06, 0x03, 0x05, 0x19, 0x6F, 0x00, 0x05, 0xC6, 0x00, 0x42, 0x00] + port: 4 + latitude: 50.776677 + longitude: 7.280901 + sats: 5 + hdop: 1.98 + altitude: 66 + errors: [] + warnings: [] + - description: Button data + input: + fPort: 5 + bytes: [0x01] + output: + data: + bytes: [0x01] + port: 5 + button: 1 + errors: [] + warnings: [] + - description: Environmental sensor data + input: + fPort: 7 + bytes: [0x08, 0x34, 0x10, 0x27, 0x40, 0x1F, 0x10, 0x27] + output: + data: + bytes: [0x08, 0x34, 0x10, 0x27, 0x40, 0x1F, 0x10, 0x27] + port: 7 + temperature: 21.00 + pressure: 1000.0 + humidity: 80.00 + air: 100.00 + errors: [] + warnings: [] + - description: Battery voltage data + input: + fPort: 8 + bytes: [0x2F, 0x01] + output: + data: + bytes: [0x2F, 0x01] + port: 8 + voltage: 303 + errors: [] + warnings: [] + - description: Time/Date + input: + fPort: 9 + bytes: [0x90, 0x86, 0xC8, 0x60, 0x00] + output: + data: + bytes: [0x90, 0x86, 0xC8, 0x60, 0x00] + port: 9 + time: 1623754384 + timestatus: 0 + errors: [] + warnings: [] + - description: User sensor data + input: + fPort: 10 + bytes: [0x00, 0x00] + output: + data: + bytes: [0x00, 0x00] + port: 10 + ens: 0 + errors: [] + warnings: [] diff --git a/vendor/waggle/esp32-paxcounter-packed.js b/vendor/waggle/esp32-paxcounter-packed.js new file mode 100644 index 0000000000..d82e56e979 --- /dev/null +++ b/vendor/waggle/esp32-paxcounter-packed.js @@ -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 + }; +} \ No newline at end of file diff --git a/vendor/waggle/esp32-paxcounter-profile-eu868.yaml b/vendor/waggle/esp32-paxcounter-profile-eu868.yaml new file mode 100644 index 0000000000..dc8b265cec --- /dev/null +++ b/vendor/waggle/esp32-paxcounter-profile-eu868.yaml @@ -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 diff --git a/vendor/waggle/esp32-paxcounter-profile-us915.yaml b/vendor/waggle/esp32-paxcounter-profile-us915.yaml new file mode 100644 index 0000000000..d845f6eeb0 --- /dev/null +++ b/vendor/waggle/esp32-paxcounter-profile-us915.yaml @@ -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 diff --git a/vendor/waggle/esp32-paxcounter.yaml b/vendor/waggle/esp32-paxcounter.yaml new file mode 100644 index 0000000000..182d7bd13b --- /dev/null +++ b/vendor/waggle/esp32-paxcounter.yaml @@ -0,0 +1,137 @@ +name: ESP32-Paxcounter +description: The ESP32-Paxcounter has sensors for battery, GPS, altitude, temperature, humidity, barometer, pm2.5, and pm10. It is an ESP32 MCU-based device for metering passenger flows in real-time. It counts how many mobile devices are around by scanning WiFi and Bluetooth signals in the air and provides an estimation of how many people are around. The captured data is transmitted over a public or private LoRaWAN® radio network. + +# 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 + +# Additional radios that this device has (optional) +# Valid values are: ble, nfc, wifi, cellular. +additionalRadios: + - ble + - wifi + +# 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.png +# 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 diff --git a/vendor/waggle/index.yaml b/vendor/waggle/index.yaml index 9dc1af1ac9..18cad50611 100644 --- a/vendor/waggle/index.yaml +++ b/vendor/waggle/index.yaml @@ -1,5 +1,4 @@ -# This example contains just one end device: windsensor. It is referenced here in the index. - endDevices: # Unique identifier of the end device (lowercase, alphanumeric with dashes, max 36 characters) - windsensor # look in windsensor.yaml for the end device definition + - esp32-paxcounter # look in esp32-paxcounter.yaml for the end device definition diff --git a/vendor/waggle/paxcounter.png b/vendor/waggle/paxcounter.png new file mode 100644 index 0000000000..3791f0936b Binary files /dev/null and b/vendor/waggle/paxcounter.png differ