-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding Adeunis FTD decoder to the catalog #53
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"uplinkDecoder": "uplink_decoder.js", | ||
"documentation": "https://www.uco.es/atdfiware/images/doc/comunica/lora/User_Guide_FTD_LoRaWAN_EU863-870_V123.pdf", | ||
"ngsildContext": "https://easy-global-market.github.io/ngsild-api-data-models/device/jsonld-contexts/device-compound.jsonld", | ||
"devicePicture": "ARF8123AA.png", | ||
"chirpstack": { | ||
"region": "EU868", | ||
"macVersion": 0, | ||
"regParamsRevision": "A", | ||
"adrAlgorithmId": "default", | ||
"flushQueueOnActivate": true, | ||
"uplinkInterval": 86400, | ||
"supportsOTAA": true, | ||
"supportsClassB": false, | ||
"supportsClassC": false | ||
}, | ||
"liveObjects": { | ||
"profile": "Generic_classA_RX2SF12", | ||
"activationType": "OTAA", | ||
"connectivityPlan": "orange-cs/CP_Basic" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,214 @@ | ||
function Decode(fPort, bytes, variables) { | ||
// Functions | ||
function parseCoordinate(raw_value, coordinate) { | ||
var raw_itude = raw_value; | ||
var temp = ""; | ||
var itude_string = ((raw_itude >> 28) & 0xF).toString(); | ||
raw_itude <<= 4; | ||
itude_string += ((raw_itude >> 28) & 0xF).toString(); | ||
raw_itude <<= 4; | ||
coordinate.degrees += itude_string; | ||
itude_string += "°"; | ||
temp = ((raw_itude >> 28) & 0xF).toString(); | ||
raw_itude <<= 4; | ||
temp += ((raw_itude >> 28) & 0xF).toString(); | ||
raw_itude <<= 4; | ||
itude_string += temp; | ||
itude_string += "."; | ||
coordinate.minutes += temp; | ||
temp = ((raw_itude >> 28) & 0xF).toString(); | ||
raw_itude <<= 4; | ||
temp += ((raw_itude >> 28) & 0xF).toString(); | ||
raw_itude <<= 4; | ||
itude_string += temp; | ||
coordinate.minutes += "."; | ||
coordinate.minutes += temp; | ||
return itude_string; | ||
} | ||
|
||
function parseLatitude(raw_latitude, coordinate) { | ||
var latitude = parseCoordinate(raw_latitude, coordinate); | ||
latitude += ((raw_latitude & 0xF0) >> 4).toString(); | ||
coordinate.minutes += ((raw_latitude & 0xF0) >> 4).toString(); | ||
return latitude; | ||
} | ||
|
||
function parseLongitude(raw_longitude, coordinate) { | ||
var longitude = (((raw_longitude >> 28) & 0xF)).toString(); | ||
coordinate.degrees = longitude; | ||
longitude += parseCoordinate(raw_longitude << 4, coordinate); | ||
return longitude; | ||
} | ||
|
||
function addField(field_no, payload) { | ||
switch (field_no) { | ||
case 0: | ||
payload.temperature = bytes[bytes_pos_] & 0x7F; | ||
if ((bytes[bytes_pos_] & 0x80) > 0) { | ||
payload.temperature -= 128; | ||
} | ||
bytes_pos_++; | ||
break; | ||
case 1: | ||
payload.trigger = "accelerometer"; | ||
break; | ||
case 2: | ||
payload.trigger = "pushbutton"; | ||
break; | ||
case 3: | ||
var coordinate = {}; | ||
coordinate.degrees = ""; | ||
coordinate.minutes = ""; | ||
var raw_value = (bytes[bytes_pos_++] << 24) | (bytes[bytes_pos_++] << 16) | (bytes[bytes_pos_++] << 8) | bytes[bytes_pos_++]; | ||
payload.lati_hemisphere = (raw_value & 1) == 1 ? "South" : "North"; | ||
payload.latitude_dmm = payload.lati_hemisphere.charAt(0) + " "; | ||
payload.latitude_dmm += parseLatitude(raw_value, coordinate); | ||
payload.latitude = (parseFloat(coordinate.degrees) + parseFloat(coordinate.minutes) / 60) * ((raw_value & 1) == 1 ? -1.0 : 1.0); | ||
coordinate.degrees = ""; | ||
coordinate.minutes = ""; | ||
raw_value = (bytes[bytes_pos_++] << 24) | (bytes[bytes_pos_++] << 16) | (bytes[bytes_pos_++] << 8) | bytes[bytes_pos_++]; | ||
payload.long_hemisphere = (raw_value & 1) == 1 ? "West" : "East"; | ||
payload.longitude_dmm = payload.long_hemisphere.charAt(0) + " "; | ||
payload.longitude_dmm += parseLongitude(raw_value, coordinate); | ||
payload.longitude = (parseFloat(coordinate.degrees) + parseFloat(coordinate.minutes) / 60) * ((raw_value & 1) == 1 ? -1.0 : 1.0); | ||
raw_value = bytes[bytes_pos_++]; | ||
switch ((raw_value & 0xF0) >> 4) { | ||
case 1: | ||
payload.gps_quality = "Good"; | ||
break; | ||
case 2: | ||
payload.gps_quality = "Average"; | ||
break; | ||
case 3: | ||
payload.gps_quality = "Poor"; | ||
break; | ||
default: | ||
payload.gps_quality = (raw_value >> 4) & 0xF; | ||
break; | ||
} | ||
payload.hdop = (raw_value >> 4) & 0xF; | ||
payload.sats = raw_value & 0xF; | ||
break; | ||
case 4: | ||
payload.ul_counter = bytes[bytes_pos_++]; | ||
break; | ||
case 5: | ||
payload.dl_counter = bytes[bytes_pos_++]; | ||
break; | ||
case 6: | ||
payload.battery_level = (bytes[bytes_pos_] << 8) | bytes[bytes_pos_ + 1]; | ||
bytes_pos_ += 2; | ||
break; | ||
case 7: | ||
payload.rssi_dl = bytes[bytes_pos_++]; | ||
payload.rssi_dl *= -1; | ||
payload.snr_dl = bytes[bytes_pos_] & 0x7F; | ||
if ((bytes[bytes_pos_] & 0x80) > 0) { | ||
payload.snr_dl -= 128; | ||
} | ||
bytes_pos_++; | ||
break; | ||
default: | ||
break; | ||
} | ||
} | ||
|
||
// Declaration & initialization | ||
var status_ = bytes[0]; | ||
var bytes_len_ = bytes.length; | ||
var bytes_pos_ = 1; | ||
var i = 0; | ||
var payload = {}; | ||
var temp_hex_str = ""; | ||
payload.payload = ""; | ||
for (var j = 0; j < bytes_len_; j++) { | ||
temp_hex_str = bytes[j].toString(16).toUpperCase(); | ||
if (temp_hex_str.length == 1) { | ||
temp_hex_str = "0" + temp_hex_str; | ||
} | ||
payload.payload += temp_hex_str; | ||
} | ||
|
||
// Get payload values | ||
do { | ||
if ((status_ & 0x80) > 0) { | ||
addField(i, payload); | ||
} | ||
i++; | ||
} while (((status_ <<= 1) & 0xFF) > 0); | ||
|
||
return payload; | ||
} | ||
|
||
let parametersMapping = { | ||
temperature: {label:"temperature", unitCode: "CEL", datasetId: 'temperature:Raw'}, | ||
trigger: {label:"trigger", unitCode: null, datasetId: 'trigger:Raw'}, | ||
latitude: {label:"latitude", unitCode: "DEG", datasetId: 'latitude:Raw'}, | ||
longitude: {label:"longitude", unitCode: "DEG", datasetId: 'longitude:Raw'}, | ||
gps_quality: {label:"gpsQuality", unitCode: null, datasetId: 'gps_quality:Raw'}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. gps_quality: needed ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, corrected. |
||
hdop: {label:"hdop", unitCode: null, datasetId: 'hdop:Raw'}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hdop: needed ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, corrected. |
||
sats: {label:"satellites", unitCode: null, datasetId: 'sats:Raw'}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sats: Needed ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, corrected. |
||
ul_counter: {label:"uplinkCounter", unitCode: null, datasetId: 'ul_counter:Raw'}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ul_counter : does it provide any identifier that we could relate to a reception ion the gateway side ? If not, I don't really see an interest to colelct this parameter There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It does not send any identifier. |
||
dl_counter: {label:"downlinkCounter", unitCode: null, datasetId: 'dl_counter:Raw'}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. downlinkCounter: needed ? We don't know about missed DL so not that relevant to know. Interest would be if from ul/dl counters we could infer areas where data transfers have been missed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These values can be kept for the suggested idea. |
||
battery_level: {label:"batteryLevel", unitCode: "VLT", datasetId: 'battery_level:Raw'}, | ||
rssi_dl: {label:"rssiDownlink", unitCode: "DBM", datasetId: 'rssi_dl:Raw'}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use rssi and snr to avoid inventing a new attribute name. It is always DL here so no possble confusion There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can use snr and rssi, but adding a datasetId 'Downlink:Raw' for these values, and another datasetId for the other snr/rssi from the gateway "gatewayId:7276ff002e05011c:Raw". |
||
snr_dl: {label:"snrDownlink", unitCode: "DB", datasetId: 'snr_dl:Raw'} | ||
}; | ||
|
||
function ngsildInstance(value, time = null, unitCode = null, datasetSuffix = null) { | ||
const ngsildInstance = { | ||
type: "Property", | ||
value: value, | ||
}; | ||
if (time) { | ||
ngsildInstance.observedAt = time; | ||
} | ||
if (unitCode) { | ||
ngsildInstance.unitCode = unitCode; | ||
} | ||
if (datasetSuffix) { | ||
ngsildInstance.datasetId = `urn:ngsi-ld:Dataset:${datasetSuffix}`; | ||
} | ||
return ngsildInstance; | ||
} | ||
|
||
function ngsildWrapper(decoded, time, entity_id) { | ||
var ngsild_payload = [{ | ||
id: entity_id, | ||
type: "Device" | ||
}]; | ||
|
||
function addToPayload(key, value) { | ||
if (ngsild_payload.every(d => d.hasOwnProperty(key))) { | ||
ngsild_payload.push({id: entity_id, type: "Device", ...{[key]: value}}); | ||
} else { | ||
for (let d of ngsild_payload) { | ||
if (!d.hasOwnProperty(key)) { | ||
d[key] = value; | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
|
||
for (var key in decoded) { | ||
if (parametersMapping[key]) { | ||
addToPayload(parametersMapping[key].label, ngsildInstance(decoded[key], time, parametersMapping[key].unitCode, parametersMapping[key].datasetId)); | ||
} | ||
} | ||
return ngsild_payload; | ||
} | ||
|
||
function main() { | ||
var fPort = process.argv[2]; | ||
var payload = Buffer.from(process.argv[3], 'hex'); | ||
var time = process.argv[4]; | ||
var entity_id = "urn:ngsi-ld:Device:" + process.argv[5]; | ||
var decoded = Decode(fPort, payload); | ||
var ngsild_payload = ngsildWrapper(decoded, time, entity_id); | ||
process.stdout.write(JSON.stringify(ngsild_payload)); | ||
} | ||
|
||
if (require.main === module) { | ||
main(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trigger: Needed ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, corrected.