diff --git a/decoders/connector/sensative/multi-sensor-+drip/assets/logo.png b/decoders/connector/sensative/multi-sensor-+drip/assets/logo.png new file mode 100644 index 00000000..2c24f4f8 Binary files /dev/null and b/decoders/connector/sensative/multi-sensor-+drip/assets/logo.png differ diff --git a/decoders/connector/sensative/multi-sensor-+drip/connector.jsonc b/decoders/connector/sensative/multi-sensor-+drip/connector.jsonc new file mode 100644 index 00000000..85e0566c --- /dev/null +++ b/decoders/connector/sensative/multi-sensor-+drip/connector.jsonc @@ -0,0 +1,13 @@ +{ + "$schema": "../../../../schema/connector.json", + "name": "Sensative Multi-sensor +Drip", + "images": { + "logo": "./assets/logo.png" + }, + "versions": { + "v1.0.0": { + "src": "./v1.0.0/payload.js", + "manifest": "./v1.0.0/payload-config.jsonc" + } + } +} diff --git a/decoders/connector/sensative/multi-sensor-+drip/description.md b/decoders/connector/sensative/multi-sensor-+drip/description.md new file mode 100644 index 00000000..73d3b7bd --- /dev/null +++ b/decoders/connector/sensative/multi-sensor-+drip/description.md @@ -0,0 +1 @@ +Temperature, water leak, magnetic contact and LUX sensors over LoRaWAN™ \ No newline at end of file diff --git a/decoders/connector/sensative/multi-sensor-+drip/v1.0.0/payload-config.jsonc b/decoders/connector/sensative/multi-sensor-+drip/v1.0.0/payload-config.jsonc new file mode 100644 index 00000000..8f11adaa --- /dev/null +++ b/decoders/connector/sensative/multi-sensor-+drip/v1.0.0/payload-config.jsonc @@ -0,0 +1,25 @@ +{ + "$schema": "../../../../../schema/connector_details.json", + "description": "../description.md", + "install_text": "Strips MS +Drip is an innovative ultra-thin (3mm/0.12in) and wireless multi-sensor for LoRaWAN with water leak detection, developed for smart IoT applications such as building monitoring and operations. MS +Drip is excellent at detecting any water leakage and its slim and sturdy design makes it perfect for mounting concealed in small spaces. With a battery life of up to 10 years, you can set it and forget it. \n\nApart from water leakage, MS +Drip provides temperature, ambient light (LUX) measuring, as well as magnetic open/close detection (suitable for applications such as windows and doors). This multi-functionality means that the more you realize your own specific needs, the more value our sensors will bring to your company and its operations.\n\nBasic applications for MS +Drip is to alert in case of a water leak, and the temperature sensor can be used to alert if there is a risk of freezing pipes. Integrated to a smart building system and other IoT devices, MS +Drip can automatically turn on/off water flow or sound an alarm to alert the user about the water leak.\n\n**Specifications**\n* Features: Flooding alert. Temperature +/- 0.24°C accuracy. Magnet Sensor. Light 1-64000 LUX. LED indication.\n* Reporting: Temperature light reporting based on deviation to save battery.\n* Regions: Europe (863-870 MHz), North America (902-928 MHz)\n* Range: Up to + 14 dBm output power. Rx sensitivity -137 dBm. Up to 10 km range (free line of sight).\n* Dimensions: Sensor: 195 x 15 x 2.98 mm. Magnet: 12 x 2 mm. Mounting plate: 195 x 15 x 3 mm.\n* Operating conditions: -30 to +60 °C. Indoor usage.\n* Power supply: Built-in battery (LiMnO2). 10 years battery life.\n* Device type: Type A\n* Supports: LoRaWAN v1.0.3\n", + "install_end_text": "", + "device_annotation": "", + "device_parameters": [], + "networks": [ + "../../../../network/lorawan-actility/v1.0.0/payload.js", + "../../../../network/lorawan-chirpstack/v1.0.0/payload.js", + "../../../../network/lorawan-citykinect/v1.0.0/payload.js", + "../../../../network/lorawan-everynet/v1.0.0/payload.js", + "../../../../network/lorawan-helium/v1.0.0/payload.js", + "../../../../network/lorawan-kerlink/v1.0.0/payload.js", + "../../../../network/lorawan-loriot-/v1.0.0/payload.js", + "../../../../network/lorawan-machineq/v1.0.0/payload.js", + "../../../../network/lorawan-orbiwise/v1.0.0/payload.js", + "../../../../network/lorawan-senet/v1.0.0/payload.js", + "../../../../network/lorawan-senra/v1.0.0/payload.js", + "../../../../network/lorawan-swisscom/v1.0.0/payload.js", + "../../../../network/lorawan-tektelic/v1.0.0/payload.js", + "../../../../network/lorawan-ttittn-v3/v1.0.0/payload.js", + "../../../../network/lorawan-brdot-/v1.0.0/payload.js" + ] +} \ No newline at end of file diff --git a/decoders/connector/sensative/multi-sensor-+drip/v1.0.0/payload.js b/decoders/connector/sensative/multi-sensor-+drip/v1.0.0/payload.js new file mode 100644 index 00000000..472d75d2 --- /dev/null +++ b/decoders/connector/sensative/multi-sensor-+drip/v1.0.0/payload.js @@ -0,0 +1,295 @@ +var ignore_vars = []; + +function toTagoFormat(object_item, serie, prefix = '') { + const result = []; + for (const key in object_item) { + if (ignore_vars.includes(key)) continue; + + if (typeof object_item[key] === 'object') { + result.push({ + variable: object_item[key].variable || `${prefix}${key}`, + value: object_item[key].value, + serie: object_item[key].serie || serie, + metadata: object_item[key].metadata, + location: object_item[key].location, + unit: object_item[key].unit, + }); + } else { + result.push({ + variable: `${prefix}${key}`, + value: object_item[key], + serie, + }); + } + } + + return result; +} + + +function Decoder(bytes, port) { + // Decode an uplink message from a buffer + // (array) of bytes to an object of fields. + + function decodeFrame(type, target, prefix = "") + { + // REPORT TYPE + switch(type & 0x7f) { + // empty report + case 0: + target[`${prefix}empty_frame`] = {}; + break; + // battery report + case 1: // Battery 1byte 0-100% + target[`${prefix}battery`] = {}; + target[`${prefix}battery`].value = bytes[pos++]; + target[`${prefix}battery`].unit = "%"; + break; + // temperature report + case 2: // TempReport 2bytes 0.1degree C + target[`${prefix}temperature`] = {}; // celcius 0.1 precision: -40 to 125 + target[`${prefix}temperature`].value = ((bytes[pos++] << 8 | bytes[pos++]) <<16>>16 ) * 0.1; // ((bytes[pos] & 0x80 ? 0xFFFF<<16 : 0) | (bytes[pos++] << 8) | bytes[pos++]) / 10; + target[`${prefix}temperature`].unit = "°C"; + break; + // temperature level alarm + case 3: + // Temp alarm + target[`${prefix}temp_high_alarm`] = {}; // sends alarm after >x< + target[`${prefix}temp_low_alarm`] = {}; // sends alarm after >x< + // console.log(bytes[pos].toString(2), bytes[pos].toString(16)); + target[`${prefix}temp_high_alarm`].value = !!(bytes[pos] & 0x01); // boolean + target[`${prefix}temp_low_alarm`].value = !!(bytes[pos] & 0x02); // boolean + pos++; + break; + // average temperature report + case 4: // AvgTempReport 2bytes 0.1degree C + target[`${prefix}average_temperature`] = {}; + target[`${prefix}average_temperature`].value = ((bytes[pos++] << 8 | bytes[pos++]) <<16>>16 ) * 0.1; //((bytes[pos] & 0x80 ? 0xFFFF<<16 : 0) | (bytes[pos++] << 8) | bytes[pos++]) / 10; + target[`${prefix}average_temperature`].unit = "°C"; + break; + // average temperature level alarm + case 5: + // AvgTemp alarm + target[`${prefix}avg_temp_high_alarm`] = {}; // sends alarm after >x< + target[`${prefix}avg_temp_low_alarm`] = {}; // sends alarm after >x< + target[`${prefix}avg_temp_high_alarm`].value = !!(bytes[pos] & 0x01); // boolean + target[`${prefix}avg_temp_low_alarm`].value = !!(bytes[pos] & 0x02); // boolean + pos++; + break; + // relative humidity report + case 6: // Humidity 1byte 0-100% in 0.5% + target[`${prefix}humidity`] = {}; + target[`${prefix}humidity`].value = bytes[pos++] / 2; // relativeHumidity percent 0,5 + target[`${prefix}humidity`].unit = "%"; + break; + // ambient light report + case 7: // Lux 2bytes 0-65535lux + target[`${prefix}lux`] = {}; + target[`${prefix}lux`].value = ((bytes[pos++] << 8) | bytes[pos++]); // you can the lux range between two sets (lux1 and 2) + target[`${prefix}lux`].unit = "lux"; + break; + // second ambient light report + case 8: // Lux 2bytes 0-65535lux + target[`${prefix}lux2`] = {}; + target[`${prefix}lux2`].value = ((bytes[pos++] << 8) | bytes[pos++]); + target[`${prefix}lux2`].unit = "lux"; + break; + // door report - external input + case 9: // DoorSwitch 1bytes binary + target[`${prefix}door`] = {}; + target[`${prefix}door`].value = !!bytes[pos++]; + break; + // door alarm + case 10: // DoorAlarm 1bytes binary + target[`${prefix}door_alarm`] = {}; + target[`${prefix}door_alarm`].value = !!bytes[pos++]; // boolean true = alarm + break; + // tamper switch report + case 11: // TamperReport 1bytes binary (was previously TamperSwitch) + target[`${prefix}tamper`] = {}; + target[`${prefix}tamper`].value = !!bytes[pos++] ? "Magnet on tamper sensor" : "No magnet on tamper sensor"; + break; + // tamper alarm + case 12: // TamperAlarm 1bytes binary + target[`${prefix}tamper_alarm`] = {}; + target[`${prefix}tamper_alarm`].value = !!bytes[pos++]; + break; + // flood sensor level report + case 13: // Flood 1byte 0-100% + target[`${prefix}flood`] = {}; + target[`${prefix}flood`].value = bytes[pos++]; // percentage, relative wetness + target[`${prefix}flood`].unit = "%"; + break; + // flood alarm + case 14: // FloodAlarm 1bytes binary + target[`${prefix}flood_alarm`] = {}; + target[`${prefix}flood_alarm`].value = !!bytes[pos++]; // boolean, after >x< + break; + // foil alarm + case 15: // FoilAlarm 1bytes binary + target[`${prefix}foil_alarm`] = {}; + target[`${prefix}foil_alarm`].value = !!bytes[pos++]; + break; + // user switch alarm + case 16: // UserSwitch1Alarm, 1 byte digital + target[`${prefix}user_switch_alarm`] = {}; + target[`${prefix}user_switch_alarm`].value = !!bytes[pos++]; + break; + // door count report + case 17: // DoorCountReport, 2 byte analog + target[`${prefix}door_count`] = {}; + target[`${prefix}door_count`].value = ((bytes[pos++] << 8) | bytes[pos++]); + break; + // presence report + case 18: // PresenceReport, 1 byte digital + target[`${prefix}presence`] = {}; + target[`${prefix}presence`].value = !!bytes[pos++] ? "Occupied" : "Vacant"; + break; + // IR proximity report + case 19: // IRProximityReport + target[`${prefix}ir_proximity`] = {}; + target[`${prefix}ir_proximity`].value = ((bytes[pos++] << 8) | bytes[pos++]); + break; + // IR low power proximity report + case 20: // IRCloseProximityReport, low power + target[`${prefix}ir_low_power_proximity`] = {}; + target[`${prefix}ir_low_power_proximity`].value = ((bytes[pos++] << 8) | bytes[pos++]); + break; + // close proximity report + case 21: // CloseProximityAlarm, something very close to presence sensor + target[`${prefix}close_proximity`] = {}; + target[`${prefix}close_proximity`].value = !!bytes[pos++] ? "Something in close proximity" : "Nothing in close proximity"; + break; + // disinfect report + case 22: // DisinfectAlarm + target[`${prefix}disinfect`] = {}; + target[`${prefix}disinfect`].value = bytes[pos++]; + if (target[`${prefix}disinfect`].value == 0) target[`${prefix}disinfect`].value="Dirty"; + else if (target[`${prefix}disinfect`].value == 1) target[`${prefix}disinfect`].value="Occupied"; + else if (target[`${prefix}disinfect`].value == 2) target[`${prefix}disinfect`].value="Cleaning"; + else if (target[`${prefix}disinfect`].value == 3) target[`${prefix}disinfect`].value="Clean"; + break; + // combined temperature and humidity report + case 80: + target[`${prefix}temperature`] = {}; + target[`${prefix}temperature`].value = ((bytes[pos++] << 8 | bytes[pos++]) <<16>>16 ) * 0.1; + target[`${prefix}temperature`].unit = "°C"; + target[`${prefix}humidity`] = {}; + target[`${prefix}humidity`].value = bytes[pos++] / 2; + target[`${prefix}humidity`].unit = "%"; + break; + // combined average temperature and humidity report + case 81: + target[`${prefix}average_temperature`] = {}; + target[`${prefix}average_temperature`].value = ((bytes[pos++] << 8 | bytes[pos++]) <<16>>16 ) * 0.1; + target[`${prefix}average_temperature`].unit = "°C"; + target[`${prefix}humidity`] = {}; + target[`${prefix}humidity`].value = bytes[pos++] / 2; + target[`${prefix}humidity`].unit = "%"; + break; + // combined temperature and door report -- external_input + case 82: + target[`${prefix}average_temperature`] = {}; + target[`${prefix}average_temperature`].value = ((bytes[pos++] << 8 | bytes[pos++]) <<16>>16 ) * 0.1; + target[`${prefix}average_temperature`].unit = "°C"; + target[`${prefix}external_input`] = {}; + target[`${prefix}external_input`].value = !!bytes[pos++]; // true = door open, false = door closed + break; + // status report + case 110: + target[`${prefix}build_id_modified`] = {}; + target[`${prefix}build_id_modified`].value = bytes[pos] >> 7; + target[`${prefix}build_id`] = {}; + target[`${prefix}build_id`].value = bytes.readUInt32BE(pos) & 0x7fffffff; + pos += 4; + target[`${prefix}status_info`] = {}; + target[`${prefix}status_info`].value = bytes.readUInt32BE(pos); + pos += 4; + break; + // raw capacitance flood sensor report + case 112: // Capacitance Raw Sensor Value 2bytes 0-65535 + target[`${prefix}raw_capacitance_flood`] = {}; + target[`${prefix}raw_capacitance_flood`].value = ((bytes[pos++] << 8) | bytes[pos++]); // should never trigger anymore + break; + // raw capacitance pad sensor report + case 113: // Capacitance Raw Sensor Value 2bytes 0-65535 + target[`${prefix}raw_capacitance_pad`] = {}; + target[`${prefix}raw_capacitance_pad`].value = ((bytes[pos++] << 8) | bytes[pos++]); // should never trigger anymore + break; + // raw capacitance end sensor report + case 114: // Capacitance Raw Sensor Value 2bytes 0-65535 + target[`${prefix}raw_capacitance_end`] = {}; + target[`${prefix}raw_capacitance_end`].value = ((bytes[pos++] << 8) | bytes[pos++]); // should never trigger anymore + break; + } + } + + var decoded = {}; + var pos = 0; + var type; + + switch(port) { + case 1: + if(bytes.length < 2) { + decoded.error = 'Wrong length of RX package'; + break; + } + decoded.historySeqNr = (bytes[pos++] << 8) | bytes[pos++]; + decoded.prevHistSeqNr = decoded.historySeqNr; + while(pos < bytes.length) { + type = bytes[pos++]; + if(type & 0x80) + decoded.prevHistSeqNr--; + decodeFrame(type, decoded); + } + break; + + case 2: + var now = new Date(); + decoded = {}; + if(bytes.length < 2) { + decoded.error = 'Wrong length of RX package'; + break; + } + var seqNr = (bytes[pos++] << 8) | bytes[pos++]; + while(pos < bytes.length) { + // decoded[seqNr] = {}; + decoded.now = now.toUTCString(); + secondsAgo = bytes.readUInt32BE(pos); + pos += 4; + decoded[`history_${seqNr}_timestamp`] = new Date(now.getTime() - secondsAgo*1000).toUTCString(); + type = bytes[pos++]; + decodeFrame(type, decoded, `history_${seqNr}_`); + seqNr++; + } + break; + case 11: + if(bytes.length < 2) { + decoded.error = 'Wrong length of RX package'; + break; + } + decoded.command = {} + decoded.command.value = bytes[pos++]; + decoded.value_id = {}; + decoded.value_id.value = bytes[pos++]; + decoded.value = {}; + decoded.value.value = `0x${bytes.slice(pos, pos+4).toString("hex")}`; + pos+=4; + break; + } + return decoded; +} + +// let payload = [{ variable: "payload", value: "000200014502ffa1030204ffa16e80000005000020a1" },{ variable: "port", value: 1 }]; +// let payload = [{ variable: "payload", value: "0002000000010d460000000206be" },{ variable: "port", value: 2 }]; +// let payload = [{ variable: "payload", value: "0102abbaabba" },{ variable: "port", value: 11 }]; + + +const data = payload.find((x) => x.variable === "payload" || x.variable === "payload_raw" || x.variable === "data"); +const port = payload.find((x) => x.variable === "port" || x.variable === "fport" ); + +if(data) { + const serie = data.serie || new Date().getTime(); + const bytes = Buffer.from(data.value, "hex"); + payload = payload.concat(toTagoFormat(Decoder(bytes, Number(port.value)), serie)).map((x) => ({ ...x, serie })); +} diff --git a/decoders/connector/sensative/strips-multi-sensor-+comfort/assets/logo.png b/decoders/connector/sensative/strips-multi-sensor-+comfort/assets/logo.png new file mode 100644 index 00000000..c29b335b Binary files /dev/null and b/decoders/connector/sensative/strips-multi-sensor-+comfort/assets/logo.png differ diff --git a/decoders/connector/sensative/strips-multi-sensor-+comfort/connector.jsonc b/decoders/connector/sensative/strips-multi-sensor-+comfort/connector.jsonc new file mode 100644 index 00000000..d30b7ab1 --- /dev/null +++ b/decoders/connector/sensative/strips-multi-sensor-+comfort/connector.jsonc @@ -0,0 +1,13 @@ +{ + "$schema": "../../../../schema/connector.json", + "name": "Sensative Strips Multi-sensor +Comfort", + "images": { + "logo": "./assets/logo.png" + }, + "versions": { + "v1.0.0": { + "src": "./v1.0.0/payload.js", + "manifest": "./v1.0.0/payload-config.jsonc" + } + } +} diff --git a/decoders/connector/sensative/strips-multi-sensor-+comfort/description.md b/decoders/connector/sensative/strips-multi-sensor-+comfort/description.md new file mode 100644 index 00000000..15cfae8b --- /dev/null +++ b/decoders/connector/sensative/strips-multi-sensor-+comfort/description.md @@ -0,0 +1 @@ +Temperature, humidity, magnetic contact and LUX sensors over LoRaWAN™ \ No newline at end of file diff --git a/decoders/connector/sensative/strips-multi-sensor-+comfort/v1.0.0/payload-config.jsonc b/decoders/connector/sensative/strips-multi-sensor-+comfort/v1.0.0/payload-config.jsonc new file mode 100644 index 00000000..cc220d83 --- /dev/null +++ b/decoders/connector/sensative/strips-multi-sensor-+comfort/v1.0.0/payload-config.jsonc @@ -0,0 +1,25 @@ +{ + "$schema": "../../../../../schema/connector_details.json", + "description": "../description.md", + "install_text": "With its low power consumption and long battery life of up to 10 years, it is the ideal choice for any professional indoor IoT application. With the MS +Comfort’s precise measuring, you can get a great overview of the climate in your property. Maybe you want to keep the climate in your office optimal or want to keep a close eye on any changes in temperature and humidity in vulnerable areas such as cellars or attics. \n\nStrips is a multi-sensor with multi-purposes, with humidity, temperature, and light (LUX) measuring, as well as magnetic open/close sensor (suitable for applications such as window or door monitoring). This multi-functionality means that the more you realize your own specific needs, the more value our sensors will bring to your company and its operations. Implementing MS +Comfort sensors is a great choice for stepping into the modern world of IoT and PropTech, energy optimization, automation, convenience services, and property protection.\n\n**Specifications**\n* Features: Humidity +/- 3% RH. Temperature +/- 0.40°C accuracy. Magnet Sensor. Light 1-64000 LUX. LED indication.\n* Reporting: Temperature light reporting based on deviation to save battery.\n* Regions: Europe (863-870 MHz), North America (902-928 MHz)\n* Range: Up to + 14 dBm output power. Rx sensitivity -137 dBm. Up to 10 km range (free line of sight).\n* Dimensions: Sensor: 195 x 15 x 2.98 mm. Magnet: 12 x 2 mm. Mounting plate: 195 x 15 x 3 mm.\n* Operating conditions: -30 to +60 °C. Indoor usage.\n* Power supply: Built-in battery (LiMnO2). 10 years battery life.\n* Supports: LoRaWAN v1.0.3\n\n\n", + "install_end_text": "", + "device_annotation": "", + "device_parameters": [], + "networks": [ + "../../../../network/lorawan-actility/v1.0.0/payload.js", + "../../../../network/lorawan-chirpstack/v1.0.0/payload.js", + "../../../../network/lorawan-citykinect/v1.0.0/payload.js", + "../../../../network/lorawan-everynet/v1.0.0/payload.js", + "../../../../network/lorawan-helium/v1.0.0/payload.js", + "../../../../network/lorawan-kerlink/v1.0.0/payload.js", + "../../../../network/lorawan-loriot-/v1.0.0/payload.js", + "../../../../network/lorawan-machineq/v1.0.0/payload.js", + "../../../../network/lorawan-orbiwise/v1.0.0/payload.js", + "../../../../network/lorawan-senra/v1.0.0/payload.js", + "../../../../network/lorawan-senet/v1.0.0/payload.js", + "../../../../network/lorawan-tektelic/v1.0.0/payload.js", + "../../../../network/lorawan-swisscom/v1.0.0/payload.js", + "../../../../network/lorawan-ttittn-v3/v1.0.0/payload.js", + "../../../../network/lorawan-brdot-/v1.0.0/payload.js" + ] +} \ No newline at end of file diff --git a/decoders/connector/sensative/strips-multi-sensor-+comfort/v1.0.0/payload.js b/decoders/connector/sensative/strips-multi-sensor-+comfort/v1.0.0/payload.js new file mode 100644 index 00000000..6f0cc624 --- /dev/null +++ b/decoders/connector/sensative/strips-multi-sensor-+comfort/v1.0.0/payload.js @@ -0,0 +1,295 @@ +var ignore_vars = []; + +function toTagoFormat(object_item, serie, prefix = '') { + const result = []; + for (const key in object_item) { + if (ignore_vars.includes(key)) continue; + + if (typeof object_item[key] === 'object') { + result.push({ + variable: object_item[key].variable || `${prefix}${key}`, + value: object_item[key].value, + serie: object_item[key].serie || serie, + metadata: object_item[key].metadata, + location: object_item[key].location, + unit: object_item[key].unit, + }); + } else { + result.push({ + variable: `${prefix}${key}`, + value: object_item[key], + serie, + }); + } + } + + return result; +} + + +function Decoder(bytes, port) { + // Decode an uplink message from a buffer + // (array) of bytes to an object of fields. + + function decodeFrame(type, target, prefix = "") + { + // REPORT TYPE + switch(type & 0x7f) { + // empty report + case 0: + target[`${prefix}empty_frame`] = {}; + break; + // battery report + case 1: // Battery 1byte 0-100% + target[`${prefix}battery`] = {}; + target[`${prefix}battery`].value = bytes[pos++]; + target[`${prefix}battery`].unit = "%"; + break; + // temperature report + case 2: // TempReport 2bytes 0.1degree C + target[`${prefix}temperature`] = {}; // celcius 0.1 precision: -40 to 125 + target[`${prefix}temperature`].value = ((bytes[pos++] << 8 | bytes[pos++]) <<16>>16 ) * 0.1; // ((bytes[pos] & 0x80 ? 0xFFFF<<16 : 0) | (bytes[pos++] << 8) | bytes[pos++]) / 10; + target[`${prefix}temperature`].unit = "°C"; + break; + // temperature level alarm + case 3: + // Temp alarm + target[`${prefix}temp_high_alarm`] = {}; // sends alarm after >x< + target[`${prefix}temp_low_alarm`] = {}; // sends alarm after >x< + // console.log(bytes[pos].toString(2), bytes[pos].toString(16)); + target[`${prefix}temp_high_alarm`].value = !!(bytes[pos] & 0x01); // boolean + target[`${prefix}temp_low_alarm`].value = !!(bytes[pos] & 0x02); // boolean + pos++; + break; + // average temperature report + case 4: // AvgTempReport 2bytes 0.1degree C + target[`${prefix}average_temperature`] = {}; + target[`${prefix}average_temperature`].value = ((bytes[pos++] << 8 | bytes[pos++]) <<16>>16 ) * 0.1; //((bytes[pos] & 0x80 ? 0xFFFF<<16 : 0) | (bytes[pos++] << 8) | bytes[pos++]) / 10; + target[`${prefix}average_temperature`].unit = "°C"; + break; + // average temperature level alarm + case 5: + // AvgTemp alarm + target[`${prefix}avg_temp_high_alarm`] = {}; // sends alarm after >x< + target[`${prefix}avg_temp_low_alarm`] = {}; // sends alarm after >x< + target[`${prefix}avg_temp_high_alarm`].value = !!(bytes[pos] & 0x01); // boolean + target[`${prefix}avg_temp_low_alarm`].value = !!(bytes[pos] & 0x02); // boolean + pos++; + break; + // relative humidity report + case 6: // Humidity 1byte 0-100% in 0.5% + target[`${prefix}humidity`] = {}; + target[`${prefix}humidity`].value = bytes[pos++] / 2; // relativeHumidity percent 0,5 + target[`${prefix}humidity`].unit = "%"; + break; + // ambient light report + case 7: // Lux 2bytes 0-65535lux + target[`${prefix}lux`] = {}; + target[`${prefix}lux`].value = ((bytes[pos++] << 8) | bytes[pos++]); // you can the lux range between two sets (lux1 and 2) + target[`${prefix}lux`].unit = "lux"; + break; + // second ambient light report + case 8: // Lux 2bytes 0-65535lux + target[`${prefix}lux2`] = {}; + target[`${prefix}lux2`].value = ((bytes[pos++] << 8) | bytes[pos++]); + target[`${prefix}lux2`].unit = "lux"; + break; + // door report - external input + case 9: // DoorSwitch 1bytes binary + target[`${prefix}external_input`] = {}; + target[`${prefix}external_input`].value = !!bytes[pos++]; + break; + // door alarm + case 10: // DoorAlarm 1bytes binary + target[`${prefix}door_alarm`] = {}; + target[`${prefix}door_alarm`].value = !!bytes[pos++]; // boolean true = alarm + break; + // tamper switch report + case 11: // TamperReport 1bytes binary (was previously TamperSwitch) + target[`${prefix}tamper`] = {}; + target[`${prefix}tamper`].value = !!bytes[pos++] ? "Magnet on tamper sensor" : "No magnet on tamper sensor"; + break; + // tamper alarm + case 12: // TamperAlarm 1bytes binary + target[`${prefix}tamper_alarm`] = {}; + target[`${prefix}tamper_alarm`].value = !!bytes[pos++]; + break; + // flood sensor level report + case 13: // Flood 1byte 0-100% + target[`${prefix}flood`] = {}; + target[`${prefix}flood`].value = bytes[pos++]; // percentage, relative wetness + target[`${prefix}flood`].unit = "%"; + break; + // flood alarm + case 14: // FloodAlarm 1bytes binary + target[`${prefix}flood_alarm`] = {}; + target[`${prefix}flood_alarm`].value = !!bytes[pos++]; // boolean, after >x< + break; + // foil alarm + case 15: // FoilAlarm 1bytes binary + target[`${prefix}foil_alarm`] = {}; + target[`${prefix}foil_alarm`].value = !!bytes[pos++]; + break; + // user switch alarm + case 16: // UserSwitch1Alarm, 1 byte digital + target[`${prefix}user_switch_alarm`] = {}; + target[`${prefix}user_switch_alarm`].value = !!bytes[pos++]; + break; + // door count report + case 17: // DoorCountReport, 2 byte analog + target[`${prefix}door_count`] = {}; + target[`${prefix}door_count`].value = ((bytes[pos++] << 8) | bytes[pos++]); + break; + // presence report + case 18: // PresenceReport, 1 byte digital + target[`${prefix}presence`] = {}; + target[`${prefix}presence`].value = !!bytes[pos++] ? "Occupied" : "Vacant"; + break; + // IR proximity report + case 19: // IRProximityReport + target[`${prefix}ir_proximity`] = {}; + target[`${prefix}ir_proximity`].value = ((bytes[pos++] << 8) | bytes[pos++]); + break; + // IR low power proximity report + case 20: // IRCloseProximityReport, low power + target[`${prefix}ir_low_power_proximity`] = {}; + target[`${prefix}ir_low_power_proximity`].value = ((bytes[pos++] << 8) | bytes[pos++]); + break; + // close proximity report + case 21: // CloseProximityAlarm, something very close to presence sensor + target[`${prefix}close_proximity`] = {}; + target[`${prefix}close_proximity`].value = !!bytes[pos++] ? "Something in close proximity" : "Nothing in close proximity"; + break; + // disinfect report + case 22: // DisinfectAlarm + target[`${prefix}disinfect`] = {}; + target[`${prefix}disinfect`].value = bytes[pos++]; + if (target[`${prefix}disinfect`].value == 0) target[`${prefix}disinfect`].value="Dirty"; + else if (target[`${prefix}disinfect`].value == 1) target[`${prefix}disinfect`].value="Occupied"; + else if (target[`${prefix}disinfect`].value == 2) target[`${prefix}disinfect`].value="Cleaning"; + else if (target[`${prefix}disinfect`].value == 3) target[`${prefix}disinfect`].value="Clean"; + break; + // combined temperature and humidity report + case 80: + target[`${prefix}temperature`] = {}; + target[`${prefix}temperature`].value = ((bytes[pos++] << 8 | bytes[pos++]) <<16>>16 ) * 0.1; + target[`${prefix}temperature`].unit = "°C"; + target[`${prefix}humidity`] = {}; + target[`${prefix}humidity`].value = bytes[pos++] / 2; + target[`${prefix}humidity`].unit = "%"; + break; + // combined average temperature and humidity report + case 81: + target[`${prefix}average_temperature`] = {}; + target[`${prefix}average_temperature`].value = ((bytes[pos++] << 8 | bytes[pos++]) <<16>>16 ) * 0.1; + target[`${prefix}average_temperature`].unit = "°C"; + target[`${prefix}humidity`] = {}; + target[`${prefix}humidity`].value = bytes[pos++] / 2; + target[`${prefix}humidity`].unit = "%"; + break; + // combined temperature and door report -- external_input + case 82: + target[`${prefix}average_temperature`] = {}; + target[`${prefix}average_temperature`].value = ((bytes[pos++] << 8 | bytes[pos++]) <<16>>16 ) * 0.1; + target[`${prefix}average_temperature`].unit = "°C"; + target[`${prefix}external_input`] = {}; + target[`${prefix}external_input`].value = !!bytes[pos++]; // true = door open, false = door closed + break; + // status report + case 110: + target[`${prefix}build_id_modified`] = {}; + target[`${prefix}build_id_modified`].value = bytes[pos] >> 7; + target[`${prefix}build_id`] = {}; + target[`${prefix}build_id`].value = bytes.readUInt32BE(pos) & 0x7fffffff; + pos += 4; + target[`${prefix}status_info`] = {}; + target[`${prefix}status_info`].value = bytes.readUInt32BE(pos); + pos += 4; + break; + // raw capacitance flood sensor report + case 112: // Capacitance Raw Sensor Value 2bytes 0-65535 + target[`${prefix}raw_capacitance_flood`] = {}; + target[`${prefix}raw_capacitance_flood`].value = ((bytes[pos++] << 8) | bytes[pos++]); // should never trigger anymore + break; + // raw capacitance pad sensor report + case 113: // Capacitance Raw Sensor Value 2bytes 0-65535 + target[`${prefix}raw_capacitance_pad`] = {}; + target[`${prefix}raw_capacitance_pad`].value = ((bytes[pos++] << 8) | bytes[pos++]); // should never trigger anymore + break; + // raw capacitance end sensor report + case 114: // Capacitance Raw Sensor Value 2bytes 0-65535 + target[`${prefix}raw_capacitance_end`] = {}; + target[`${prefix}raw_capacitance_end`].value = ((bytes[pos++] << 8) | bytes[pos++]); // should never trigger anymore + break; + } + } + + var decoded = {}; + var pos = 0; + var type; + + switch(port) { + case 1: + if(bytes.length < 2) { + decoded.error = 'Wrong length of RX package'; + break; + } + decoded.historySeqNr = (bytes[pos++] << 8) | bytes[pos++]; + decoded.prevHistSeqNr = decoded.historySeqNr; + while(pos < bytes.length) { + type = bytes[pos++]; + if(type & 0x80) + decoded.prevHistSeqNr--; + decodeFrame(type, decoded); + } + break; + + case 2: + var now = new Date(); + decoded = {}; + if(bytes.length < 2) { + decoded.error = 'Wrong length of RX package'; + break; + } + var seqNr = (bytes[pos++] << 8) | bytes[pos++]; + while(pos < bytes.length) { + // decoded[seqNr] = {}; + decoded.now = now.toUTCString(); + secondsAgo = bytes.readUInt32BE(pos); + pos += 4; + decoded[`history_${seqNr}_timestamp`] = new Date(now.getTime() - secondsAgo*1000).toUTCString(); + type = bytes[pos++]; + decodeFrame(type, decoded, `history_${seqNr}_`); + seqNr++; + } + break; + case 11: + if(bytes.length < 2) { + decoded.error = 'Wrong length of RX package'; + break; + } + decoded.command = {} + decoded.command.value = bytes[pos++]; + decoded.value_id = {}; + decoded.value_id.value = bytes[pos++]; + decoded.value = {}; + decoded.value.value = `0x${bytes.slice(pos, pos+4).toString("hex")}`; + pos+=4; + break; + } + return decoded; +} + +// let payload = [{ variable: "payload", value: "000200014502ffa1030204ffa16e80000005000020a1" },{ variable: "port", value: 1 }]; +// let payload = [{ variable: "payload", value: "0002000000010d460000000206be" },{ variable: "port", value: 2 }]; +// let payload = [{ variable: "payload", value: "0102abbaabba" },{ variable: "port", value: 11 }]; + + +const data = payload.find((x) => x.variable === "payload" || x.variable === "payload_raw" || x.variable === "data"); +const port = payload.find((x) => x.variable === "port" || x.variable === "fport" ); + +if(data) { + const serie = data.serie || new Date().getTime(); + const bytes = Buffer.from(data.value, "hex"); + payload = payload.concat(toTagoFormat(Decoder(bytes, Number(port.value)), serie)).map((x) => ({ ...x, serie })); +} diff --git a/decoders/connector/skiply/smilio-actioncancelation/assets/logo.png b/decoders/connector/skiply/smilio-actioncancelation/assets/logo.png new file mode 100644 index 00000000..b2b8c979 Binary files /dev/null and b/decoders/connector/skiply/smilio-actioncancelation/assets/logo.png differ diff --git a/decoders/connector/skiply/smilio-actioncancelation/connector.jsonc b/decoders/connector/skiply/smilio-actioncancelation/connector.jsonc new file mode 100644 index 00000000..84d3e425 --- /dev/null +++ b/decoders/connector/skiply/smilio-actioncancelation/connector.jsonc @@ -0,0 +1,13 @@ +{ + "$schema": "../../../../schema/connector.json", + "name": "Skiply - Smilio Action/Cancelation ", + "images": { + "logo": "./assets/logo.png" + }, + "versions": { + "v1.0.0": { + "src": "./v1.0.0/payload.js", + "manifest": "./v1.0.0/payload-config.jsonc" + } + } +} diff --git a/decoders/connector/skiply/smilio-actioncancelation/description.md b/decoders/connector/skiply/smilio-actioncancelation/description.md new file mode 100644 index 00000000..2c9ff9fe --- /dev/null +++ b/decoders/connector/skiply/smilio-actioncancelation/description.md @@ -0,0 +1 @@ +Buttons to report any kind of event over LoRaWAN and Sigfox using SKIPLY Server \ No newline at end of file diff --git a/decoders/connector/skiply/smilio-actioncancelation/v1.0.0/payload-config.jsonc b/decoders/connector/skiply/smilio-actioncancelation/v1.0.0/payload-config.jsonc new file mode 100644 index 00000000..8826b68f --- /dev/null +++ b/decoders/connector/skiply/smilio-actioncancelation/v1.0.0/payload-config.jsonc @@ -0,0 +1,18 @@ +{ + "$schema": "../../../../../schema/connector_details.json", + "description": "../description.md", + "install_text": "##\nSmilio Action / Cancellation is a device composed of 2 connected buttons which allow to trigger an action, cancel it if necessary and capable to time-and-date-stamp staff interventions thanks to an integrated badging device.\n\nStrategically placed, Smilio Action can be part of multiple use cases. It offers the opportunity to take immediate action and report any kind of event that can improve operational effectiveness: \n* **LoRaWAN and Sigfox**\n* Restocking of consumable materials\n* Demands for automated on-site interventions (servicing, maintenance)\n* Waste collection\n* Optimization of delivery rounds\n* Call button on production chains\n* Connection to isolated bus stop\n##\nMore information about this device can be found [here.](https://www.smilio.eu/action)\n##\nThis setup uses the **SKIPLY Server** to connect a device for Sigfox or LoRaWAN Network.\n##", + "install_end_text": "", + "device_annotation": "", + "device_parameters": [], + "networks": [ + "../../../../network/lorawan-citykinect/v1.0.0/payload.js", + "../../../../network/lorawan-ttittn-v3/v1.0.0/payload.js", + "../../../../network/lorawan-actility/v1.0.0/payload.js", + "../../../../network/lorawan-chirpstack/v1.0.0/payload.js", + "../../../../network/lorawan-everynet/v1.0.0/payload.js", + "../../../../network/lorawan-loriot-/v1.0.0/payload.js", + "../../../../network/lorawan-tektelic/v1.0.0/payload.js", + "../../../../network/lorawan-brdot-/v1.0.0/payload.js" + ] +} \ No newline at end of file diff --git a/decoders/connector/skiply/smilio-actioncancelation/v1.0.0/payload.js b/decoders/connector/skiply/smilio-actioncancelation/v1.0.0/payload.js new file mode 100644 index 00000000..fa01bbf9 --- /dev/null +++ b/decoders/connector/skiply/smilio-actioncancelation/v1.0.0/payload.js @@ -0,0 +1,138 @@ +/* + * TagoIO Decoders - (https://tago.io/) + * ------------------- + * Generated by :: tagoio + * Generated at :: Fri Aug 25 2023 19:37:29 GMT+0000 (Coordinated Universal Time) + * Machine :: fred - Node.js v18.16.0 + * ------------------- +*/ + +function Decoder_smilioaction(input) { + switch(input[0]){ + case 0x01: + return decodeFrame01_smilioaction(input); + case 0x02: + return decodeFrame02or03_smilioaction(input); + case 0x03: + return decodeFrame02or03_smilioaction(input); + case 0x40: + return decodeFrame40_smilioaction(input); + case 0x10: + return decodeFrameCode_smilioaction(input); + case 0x30: + return decodeFrameCode_smilioaction(input); + case 0x11: + return decodeFrameCode_smilioaction(input); + case 0x13: + return decodeFrameCode_smilioaction(input); + case 0x31: + return decodeFrameCode_smilioaction(input); + case 0x33: + return decodeFrameCode_smilioaction(input); + default: + throw new Error("unknown dataframe code (first two digits of payload)"); + } +} +function decodeFrame01_smilioaction(bytes) { + const data = []; + data.push({ + variable: "battery_idle", + value: bytes[1] << 8 | bytes[2], + unit: "mV" + }); + data.push({ + variable: "battery_emission", + value: bytes[3] << 8 | bytes[4], + unit: "mV" + }); + return data; +} +function decodeFrame02or03_smilioaction(bytes) { + const data = []; + let i = 1; + while(i < bytes.length){ + data.push({ + variable: `counter_button_${(i + 1) / 2}`, + value: bytes[i] << 8 | bytes[i + 1] + }); + i += 2; + } + return data; +} +function decodeFrame40_smilioaction(bytes) { + const data = []; + data.push({ + variable: "button_push_status_1", + value: bytes[2] ? "pushed" : "not pushed" + }); + data.push({ + variable: "button_push_status_2", + value: bytes[4] ? "pushed" : "not pushed" + }); + data.push({ + variable: "button_push_status_3", + value: bytes[6] ? "pushed" : "not pushed" + }); + data.push({ + variable: "button_push_status_4", + value: bytes[8] ? "pushed" : "not pushed" + }); + data.push({ + variable: "button_push_status_5", + value: bytes[10] ? "pushed" : "not pushed" + }); + return data; +} +function decodeFrameCode_smilioaction(bytes) { + const data = []; + data.push({ + variable: "acknowledgment_1", + value: (bytes[0] & 0xf0) >> 4 == 3 ? "acknowledged" : "unacknowledged" + }); + data.push({ + variable: "acknowledgment_2", + value: (bytes[0] & 0x0f) == 3 ? "acknowledged" : "unacknowledged" + }); + data.push({ + variable: "time_it_took_to_upload_code", + value: bytes.readUInt16BE(1), + unit: "minutes" + }); + data.push({ + variable: "time_since_last_code_upload", + value: bytes.readUInt16BE(6), + unit: "minutes" + }); + data.push({ + variable: "code_1", + value: Number(bytes.slice(4, 7).toString("hex")) + }); + data.push({ + variable: "code_2", + value: Number(bytes.slice(7, 10).toString("hex")) + }); + return data; +} +const payload_smilioaction = payload.find((x)=>x.variable === "payload_raw" || x.variable === "payload" || x.variable === "data"); +if (payload_smilioaction) { + try { + const buffer = Buffer.from(payload_smilioaction.value, "hex"); + const data = Decoder_smilioaction(buffer); + payload = payload.concat(data.map((x)=>({ + ...x, + serie: payload_smilioaction.group, + time: payload_smilioaction.time + }))); + } catch (error) { + console.error(error); + payload = [ + { + variable: "parse_error", + value: error.message + } + ]; + } +} + + +//#sourceMappingURL=data:application/json;charset=utf-8;base64,IntcInZlcnNpb25cIjozLFwic291cmNlc1wiOltdLFwibmFtZXNcIjpbXSxcIm1hcHBpbmdzXCI6XCJcIixcImZpbGVcIjpcInN0ZG91dFwifSI= \ No newline at end of file diff --git a/decoders/connector/strega/smart-emitter-for-irrigation-valve-full-edition/assets/logo.png b/decoders/connector/strega/smart-emitter-for-irrigation-valve-full-edition/assets/logo.png new file mode 100644 index 00000000..3ae898e2 Binary files /dev/null and b/decoders/connector/strega/smart-emitter-for-irrigation-valve-full-edition/assets/logo.png differ diff --git a/decoders/connector/strega/smart-emitter-for-irrigation-valve-full-edition/connector.jsonc b/decoders/connector/strega/smart-emitter-for-irrigation-valve-full-edition/connector.jsonc new file mode 100644 index 00000000..736611c5 --- /dev/null +++ b/decoders/connector/strega/smart-emitter-for-irrigation-valve-full-edition/connector.jsonc @@ -0,0 +1,13 @@ +{ + "$schema": "../../../../schema/connector.json", + "name": "STREGA Smart-Emitter for Irrigation valve Full Edition", + "images": { + "logo": "./assets/logo.png" + }, + "versions": { + "v1.0.0": { + "src": "./v1.0.0/payload.js", + "manifest": "./v1.0.0/payload-config.jsonc" + } + } +} diff --git a/decoders/connector/strega/smart-emitter-for-irrigation-valve-full-edition/description.md b/decoders/connector/strega/smart-emitter-for-irrigation-valve-full-edition/description.md new file mode 100644 index 00000000..e4da63ec --- /dev/null +++ b/decoders/connector/strega/smart-emitter-for-irrigation-valve-full-edition/description.md @@ -0,0 +1 @@ +LoRaWAN Time-controlled Emitter for irrigation valve (Class A, Class C and IO’s) \ No newline at end of file diff --git a/decoders/connector/strega/smart-emitter-for-irrigation-valve-full-edition/v1.0.0/payload-config.jsonc b/decoders/connector/strega/smart-emitter-for-irrigation-valve-full-edition/v1.0.0/payload-config.jsonc new file mode 100644 index 00000000..036080a4 --- /dev/null +++ b/decoders/connector/strega/smart-emitter-for-irrigation-valve-full-edition/v1.0.0/payload-config.jsonc @@ -0,0 +1,25 @@ +{ + "$schema": "../../../../../schema/connector_details.json", + "description": "../description.md", + "install_text": "STREGA LoRaWAN Time-controlled Emitter remotely actuates an irrigation valve thanks to its embedded LoRaWAN technology. With its ultra-low-power consumption, the Smart-Emitter can trigger Open or Close operations programmed directly at the zone level, avoiding the necessity to use an irrigation controller. The STREGA emitter is working on batteries providing 7+ years of autonomy or endlessly if powered externally. The device communicate through extreme long distances with exceptional obstacles penetration.", + "install_end_text": "", + "device_annotation": "", + "device_parameters": [], + "networks": [ + "../../../../network/lorawan-everynet/v1.0.0/payload.js", + "../../../../network/lorawan-loriot-/v1.0.0/payload.js", + "../../../../network/lorawan-citykinect/v1.0.0/payload.js", + "../../../../network/lorawan-kerlink/v1.0.0/payload.js", + "../../../../network/lorawan-machineq/v1.0.0/payload.js", + "../../../../network/lorawan-orbiwise/v1.0.0/payload.js", + "../../../../network/lorawan-tektelic/v1.0.0/payload.js", + "../../../../network/lorawan-actility/v1.0.0/payload.js", + "../../../../network/lorawan-ttittn-v3/v1.0.0/payload.js", + "../../../../network/lorawan-swisscom/v1.0.0/payload.js", + "../../../../network/lorawan-chirpstack/v1.0.0/payload.js", + "../../../../network/lorawan-helium/v1.0.0/payload.js", + "../../../../network/lorawan-senet/v1.0.0/payload.js", + "../../../../network/lorawan-senra/v1.0.0/payload.js", + "../../../../network/lorawan-brdot-/v1.0.0/payload.js" + ] +} \ No newline at end of file diff --git a/decoders/connector/strega/smart-emitter-for-irrigation-valve-full-edition/v1.0.0/payload.js b/decoders/connector/strega/smart-emitter-for-irrigation-valve-full-edition/v1.0.0/payload.js new file mode 100644 index 00000000..d434ec54 --- /dev/null +++ b/decoders/connector/strega/smart-emitter-for-irrigation-valve-full-edition/v1.0.0/payload.js @@ -0,0 +1,404 @@ +/* eslint-disable no-mixed-operators */ +/* eslint-disable no-bitwise */ + +/* This is an example code for Everynet Parser. +** Everynet send several parameters to TagoIO. The job of this parse is to convert all these parameters into a TagoIO format. +** One of these parameters is the payload of your device. We find it too and apply the appropriate sensor parse. +** +** IMPORTANT: In most case, you will only need to edit the parsePayload function. +** +** Testing: +** You can do manual tests to this parse by using the Device Emulator. Copy and Paste the following code: +** [{ "variable": "everynet_payload", "value": "{ \"params\": { \"payload\": \"0109611395\" } }" }] +** +** The ignore_vars variable in this code should be used to ignore variables +** from the device that you don't want. +*/ +// Add ignorable variables in this array. +const ignore_vars = ['device_addr', 'port', 'duplicate', 'network', 'packet_hash', 'application', 'device', 'packet_id']; + + +/** + * Convert an object to TagoIO object format. + * Can be used in two ways: + * toTagoFormat({ myvariable: myvalue , anothervariable: anothervalue... }) + * toTagoFormat({ myvariable: { value: myvalue, unit: 'C', metadata: { color: 'green' }} , anothervariable: anothervalue... }) + * + * @param {Object} object_item Object containing key and value. + * @param {String} serie Serie for the variables + * @param {String} prefix Add a prefix to the variables name + */ +function toTagoFormat(object_item, serie, prefix = '') { + const result = []; + for (const key in object_item) { + if (ignore_vars.includes(key)) continue; + + if (typeof object_item[key] === 'object') { + result.push({ + variable: object_item[key].variable || `${prefix}${key}`.toLowerCase(), + value: object_item[key].value, + serie: object_item[key].serie || serie, + metadata: object_item[key].metadata, + location: object_item[key].location, + unit: object_item[key].unit, + }); + } else { + result.push({ + variable: `${prefix}${key}`.toLowerCase(), + value: object_item[key], + serie, + }); + } + } + + return result; +} + +function pad(num, len) { + return (`00000000${num}`).substr(-len); +} +function roundToTwo(num) { + return +(`${Math.round(`${num}e+2`)}e-2`); +} +function dec_to_bho(n, base) { + if (n < 0) { + n = 0xFFFFFFFF + n + 1; + } + switch (base) { + case 'B': + return parseInt(n, 10).toString(2); + case 'H': + return parseInt(n, 10).toString(16); + case 'O': + return parseInt(n, 10).toString(8); + default: + return ('Wrong input.........'); + } +} + +function toHexString(hex_bytes) { + return hex_bytes.map((byte) => { + return (`00${(byte & 0xFF).toString(16)}`).slice(-2); + }).join(''); +} +function hex2bin(hex) { + return (`00000000${(parseInt(hex, 16)).toString(2)}`).substr(-8); +} + +function Decoder(bytes, port) { + const ports = port; + const v4 = toHexString(bytes); + const v4_1 = v4.substr(10, 2); + const v4_2 = v4.substr(0, 2); + const v4_3 = v4.substr(0, 1); + const v3 = v4.substr(8, 1); + const length = v4.length; + let battery; let Tamper; let Valve; let status_v4; let Cable_v4; let conf_p; let conf_s; let unit1; let time1; let Cable; + let unit2; let time2; let temperature; let hygrometry; let status; let DI_0; let DI_1; let Leakage; let Fraud; + let clas; let power; let t; let t1; + // Battery operated V3 Old + if (v4_3 === '3') { + const msg0 = String.fromCharCode.apply(null, bytes); + const bat0 = msg0.substr(0, 4); + battery = Math.round((bat0 - 2000) / 16); + if (length === 10) { + const st0 = msg0.substr(4, 1); + const sta0 = dec_to_bho(st0, 'B'); + status = pad(sta0, 8); + Valve = status.substr(7, 1); + Tamper = status.substr(6, 1); + } + } + // Battery operated V3 & V4 & Class A + if (v4_3 === '3') { + const msg = String.fromCharCode.apply(null, bytes); + const bat = msg.substr(0, 4); + battery = Math.round((bat - 2000) / 16); + if (length >= 10) { + const st = msg.substr(4, 1); + const sta = dec_to_bho(st, 'B'); + status = pad(sta, 8); + Valve = status.substr(7, 1); + Tamper = status.substr(6, 1); + } + const st_1 = v4.substr(8, 2); + t = st_1.substr(0, 1) - 3; + t1 = st_1.substr(1, 1); + const st1 = t + t1; + const st01 = hex2bin(st1); + status = pad(st01, 8); + Valve = status.substr(7, 1); + Tamper = status.substr(6, 1); + Cable = status.substr(5, 1); + DI_0 = status.substr(4, 1); + DI_1 = status.substr(3, 1); + Leakage = status.substr(2, 1); + Fraud = status.substr(1, 1); + conf_p = parseInt(msg.substr(6, 2), 16); + conf_s = msg.substr(8, 2); + unit1 = msg.substr(8, 2); + time1 = msg.substr(10, 2); + unit2 = msg.substr(12, 2); + time2 = msg.substr(14, 2); + const T_H = v4.substr(12, 8); + const temp = T_H.substr(0, 4); + const box_temp = parseInt((temp), 16); + temperature = (((box_temp / 65536) * 165) - 40); + const hum = T_H.substr(4, 8); + const box_hum = parseInt((hum), 16); + hygrometry = ((box_hum / 65536) * 100); + } + // Externally power V4 & Class variation + if (v4_3 !== '3' || v4_3 !== 'B' || v4_3 !== 'b') { + const msg1 = String.fromCharCode.apply(1, bytes); + const st2 = hex2bin(v4_2); + clas = st2.substr(0, 1); + power = st2.substr(1, 1); + const st_3 = v4.substr(8, 2); + t = st_3.substr(0, 1) - 3; + t1 = st_3.substr(1, 1); + const st3 = t + t1; + const st02 = hex2bin(st3); + status = pad(st02, 8); + Valve = status.substr(7, 1); + Tamper = status.substr(6, 1); + Cable = status.substr(5, 1); + DI_0 = status.substr(4, 1); + DI_1 = status.substr(3, 1); + Leakage = status.substr(2, 1); + Fraud = status.substr(1, 1); + conf_p = parseInt(msg1.substr(6, 2), 16); + conf_s = msg1.substr(8, 2); + unit1 = msg1.substr(8, 2); + time1 = msg1.substr(10, 2); + unit2 = msg1.substr(12, 2); + time2 = msg1.substr(14, 2); + const T_H1 = v4.substr(12, 8); + const temp1 = T_H1.substr(0, 4); + const box_temp1 = parseInt((temp1), 16); + temperature = (((box_temp1 / 65536) * 165) - 40); + const hum1 = T_H1.substr(4, 8); + const box_hum1 = parseInt((hum1), 16); + hygrometry = ((box_hum1 / 65536) * 100); + } + // Battery operated V4 & Class variation + if (v4_3 == 'B' || v4_3 == 'b') { + const msg2 = String.fromCharCode.apply(1, bytes); + const st4 = hex2bin(v4_2); + clas = st4.substr(0, 1); + power = st4.substr(1, 1); + const b = v4.substr(1, 1); + const b1 = msg2.substr(2, 3); + const bat1 = b.concat(b1); + battery = Math.round((bat1 - 2000) / 16); + const st_5 = v4.substr(8, 2); + t = st_5.substr(0, 1) - 3; + t1 = st_5.substr(1, 1); + const st5 = t + t1; + const st03 = hex2bin(st5); + status = pad(st03, 8); + Valve = status.substr(7, 1); + Tamper = status.substr(6, 1); + Cable = status.substr(5, 1); + DI_0 = status.substr(4, 1); + DI_1 = status.substr(3, 1); + Leakage = status.substr(2, 1); + Fraud = status.substr(1, 1); + conf_p = parseInt(msg2.substr(7, 2), 16); + conf_s = msg2.substr(9, 2); + unit1 = msg2.substr(9, 2); + time1 = msg2.substr(11, 2); + unit2 = msg2.substr(13, 2); + time2 = msg2.substr(15, 2); + const T_H2 = v4.substr(12, 8); + const temp2 = T_H2.substr(0, 4); + const box_temp2 = parseInt((temp2), 16); + temperature = (((box_temp2 / 65536) * 165) - 40); + const hum2 = T_H2.substr(4, 8); + const box_hum2 = parseInt((hum2), 16); + hygrometry = ((box_hum2 / 65536) * 100); + } + + if (v4_1 === '40') { + if (conf_p === 14 || conf_p === 15 || conf_p === 16 || conf_p === 17 || conf_p === 18 || conf_p === 19 || conf_p === 20) { + return { + Port: ports, + Battery: battery, + Valve, + Tamper, + Cable, + DI_0, + DI_1, + Leakage, + Fraud, + Class: clas, + Power: power, + Schl_Port: conf_p || 0, + Schl_status: conf_s || 0, + }; + } + if (conf_p === 21) { + return { + Port: ports, + Battery: battery, + Valve, + Tamper, + Cable, + DI_0, + DI_1, + Leakage, + Fraud, + Class: clas, + Power: power, + Schl_status_Port: conf_p || 0, + Schl_status_ack: conf_s || 0, + }; + } + if (conf_p === 12 || conf_p === 13) { + return { + Port: ports, + Battery: battery, + Valve, + Tamper, + Cable, + DI_0, + DI_1, + Leakage, + Fraud, + Class: clas, + Power: power, + RTC_Port: conf_p || 0, + RTC_status: conf_s || 0, + }; + } + if (conf_p === 9) { + return { + Port: ports, + Battery: battery, + Valve, + Tamper, + Cable, + DI_0, + DI_1, + Leakage, + Fraud, + Class: clas, + Power: power, + Class_Port: conf_p || 0, + Class_status: conf_s || 0, + }; + } + if (conf_p === 22) { + return { + Port: ports, + Battery: battery, + Valve, + Tamper, + Cable, + DI_0, + DI_1, + Leakage, + Fraud, + Class: clas, + Power: power, + magnet_Port: conf_p || 0, + magnet_status: conf_s || 0, + }; + } + } + if (v4_1 === '23') { + return { + Port: ports, + Status: status, + Battery: battery, + Valve, + Tamper, + Cable, + DI_0, + DI_1, + Leakage, + Fraud, + Class: clas, + Power: power, + Temperature: roundToTwo(temperature), + Hygrometry: roundToTwo(hygrometry), + }; + } + + return { + Port: ports, + Battery: battery, + Valve, + Tamper, + }; +} + + +// let payload = [ +// { +// variable: 'time', +// value: 1571874770.422976, +// serie: 1571874770524, +// }, +// { +// variable: 'payload', +// value: '4Be202000008000001', +// serie: 1571874770524, +// }, +// { +// variable: 'port', +// value: 2, +// serie: 1571874770524, +// }, +// { +// variable: 'duplicate', +// value: false, +// serie: 1571874770524, +// }, +// { +// variable: 'counter_up', +// value: 38, +// serie: 1571874770524, +// }, +// { +// variable: 'rx_time', +// value: 1571874770.3568993, +// serie: 1571874770524, +// }, +// { +// variable: 'encrypted_payload', +// value: 'c12oeBn03DxbfqcD', +// serie: 1571874770524, +// }, +// ]; +// Check if what is being stored is the ttn_payload. +// Payload is an environment variable. Is where what is being inserted to your device comes in. +// Payload always is an array of objects. [ { variable, value...}, {variable, value...} ...] +const payload_raw = payload.find(x => x.variable === 'payload' || x.variable === 'payload_raw' || x.variable === 'data'); +const port = payload.find(x => x.variable === 'fport' || x.variable === 'port'); +if (payload_raw) { + // Get a unique serie for the incoming data. + const serie = payload_raw.serie || new Date().getTime(); + let vars_to_tago = []; + // Parse the payload from your sensor to function parsePayload + try { + const decoded = Decoder(Buffer.from(payload_raw.value, 'hex'), port ? port.value : null); + console.log(decoded); + vars_to_tago = vars_to_tago.concat(toTagoFormat(decoded, serie)); + } catch (e) { + // Catch any error in the parse code and send to parse_error variable. + vars_to_tago = vars_to_tago.concat({ variable: 'parse_error', value: e.message || e }); + } + + payload = payload.concat(vars_to_tago); +} + +payload = payload.filter((item) => { + if (item.location) { + if (item.location.lat === 0 && item.location.lng === 0) { + return false; + } + } + return true; +}); +// console.log(payload) diff --git a/decoders/connector/strega/smart-emitter-for-irrigation-valve-lite-edition/assets/logo.png b/decoders/connector/strega/smart-emitter-for-irrigation-valve-lite-edition/assets/logo.png new file mode 100644 index 00000000..bd20385d Binary files /dev/null and b/decoders/connector/strega/smart-emitter-for-irrigation-valve-lite-edition/assets/logo.png differ diff --git a/decoders/connector/strega/smart-emitter-for-irrigation-valve-lite-edition/connector.jsonc b/decoders/connector/strega/smart-emitter-for-irrigation-valve-lite-edition/connector.jsonc new file mode 100644 index 00000000..7370bb29 --- /dev/null +++ b/decoders/connector/strega/smart-emitter-for-irrigation-valve-lite-edition/connector.jsonc @@ -0,0 +1,13 @@ +{ + "$schema": "../../../../schema/connector.json", + "name": "STREGA Smart-Emitter for Irrigation valve Lite Edition", + "images": { + "logo": "./assets/logo.png" + }, + "versions": { + "v1.0.0": { + "src": "./v1.0.0/payload.js", + "manifest": "./v1.0.0/payload-config.jsonc" + } + } +} diff --git a/decoders/connector/strega/smart-emitter-for-irrigation-valve-lite-edition/description.md b/decoders/connector/strega/smart-emitter-for-irrigation-valve-lite-edition/description.md new file mode 100644 index 00000000..90bad7f0 --- /dev/null +++ b/decoders/connector/strega/smart-emitter-for-irrigation-valve-lite-edition/description.md @@ -0,0 +1 @@ +LoRaWAN Time-controlled Emitter for irrigation valve (Class A) \ No newline at end of file diff --git a/decoders/connector/strega/smart-emitter-for-irrigation-valve-lite-edition/v1.0.0/payload-config.jsonc b/decoders/connector/strega/smart-emitter-for-irrigation-valve-lite-edition/v1.0.0/payload-config.jsonc new file mode 100644 index 00000000..16e19e65 --- /dev/null +++ b/decoders/connector/strega/smart-emitter-for-irrigation-valve-lite-edition/v1.0.0/payload-config.jsonc @@ -0,0 +1,25 @@ +{ + "$schema": "../../../../../schema/connector_details.json", + "description": "../description.md", + "install_text": "STREGA LoRaWAN Time-controlled Emitter remotely actuates an irrigation valve thanks to its embedded LoRaWAN technology. With its ultra-low-power consumption, the Smart-Emitter can trigger Open or Close operations programmed directly at the zone level, avoiding the necessity to use an irrigation controller. The STREGA emitter is working on batteries providing 7+ years of autonomy or endlessly if powered externally. The device communicate through extreme long distances with exceptional obstacles penetration.", + "install_end_text": "", + "device_annotation": "", + "device_parameters": [], + "networks": [ + "../../../../network/lorawan-everynet/v1.0.0/payload.js", + "../../../../network/lorawan-citykinect/v1.0.0/payload.js", + "../../../../network/lorawan-loriot-/v1.0.0/payload.js", + "../../../../network/lorawan-kerlink/v1.0.0/payload.js", + "../../../../network/lorawan-machineq/v1.0.0/payload.js", + "../../../../network/lorawan-orbiwise/v1.0.0/payload.js", + "../../../../network/lorawan-tektelic/v1.0.0/payload.js", + "../../../../network/lorawan-actility/v1.0.0/payload.js", + "../../../../network/lorawan-ttittn-v3/v1.0.0/payload.js", + "../../../../network/lorawan-swisscom/v1.0.0/payload.js", + "../../../../network/lorawan-chirpstack/v1.0.0/payload.js", + "../../../../network/lorawan-helium/v1.0.0/payload.js", + "../../../../network/lorawan-senet/v1.0.0/payload.js", + "../../../../network/lorawan-senra/v1.0.0/payload.js", + "../../../../network/lorawan-brdot-/v1.0.0/payload.js" + ] +} \ No newline at end of file diff --git a/decoders/connector/strega/smart-emitter-for-irrigation-valve-lite-edition/v1.0.0/payload.js b/decoders/connector/strega/smart-emitter-for-irrigation-valve-lite-edition/v1.0.0/payload.js new file mode 100644 index 00000000..eae3bbc4 --- /dev/null +++ b/decoders/connector/strega/smart-emitter-for-irrigation-valve-lite-edition/v1.0.0/payload.js @@ -0,0 +1,404 @@ +/* eslint-disable no-mixed-operators */ +/* eslint-disable no-bitwise */ + +/* This is an example code for Everynet Parser. +** Everynet send several parameters to TagoIO. The job of this parse is to convert all these parameters into a TagoIO format. +** One of these parameters is the payload of your device. We find it too and apply the appropriate sensor parse. +** +** IMPORTANT: In most case, you will only need to edit the parsePayload function. +** +** Testing: +** You can do manual tests to this parse by using the Device Emulator. Copy and Paste the following code: +** [{ "variable": "everynet_payload", "value": "{ \"params\": { \"payload\": \"0109611395\" } }" }] +** +** The ignore_vars variable in this code should be used to ignore variables +** from the device that you don't want. +*/ +// Add ignorable variables in this array. +const ignore_vars = ['device_addr', 'port', 'duplicate', 'network', 'packet_hash', 'application', 'device', 'packet_id']; + + +/** + * Convert an object to TagoIO object format. + * Can be used in two ways: + * toTagoFormat({ myvariable: myvalue , anothervariable: anothervalue... }) + * toTagoFormat({ myvariable: { value: myvalue, unit: 'C', metadata: { color: 'green' }} , anothervariable: anothervalue... }) + * + * @param {Object} object_item Object containing key and value. + * @param {String} serie Serie for the variables + * @param {String} prefix Add a prefix to the variables name + */ +function toTagoFormat(object_item, serie, prefix = '') { + const result = []; + for (const key in object_item) { + if (ignore_vars.includes(key)) continue; + + if (typeof object_item[key] === 'object') { + result.push({ + variable: object_item[key].variable || `${prefix}${key}`.toLowerCase(), + value: object_item[key].value, + serie: object_item[key].serie || serie, + metadata: object_item[key].metadata, + location: object_item[key].location, + unit: object_item[key].unit, + }); + } else { + result.push({ + variable: `${prefix}${key}`.toLowerCase(), + value: object_item[key], + serie, + }); + } + } + + return result; +} + +function pad(num, len) { + return (`00000000${num}`).substr(-len); +} +function roundToTwo(num) { + return +(`${Math.round(`${num}e+2`)}e-2`); +} +function dec_to_bho(n, base) { + if (n < 0) { + n = 0xFFFFFFFF + n + 1; + } + switch (base) { + case 'B': + return parseInt(n, 10).toString(2); + case 'H': + return parseInt(n, 10).toString(16); + case 'O': + return parseInt(n, 10).toString(8); + default: + return ('Wrong input.........'); + } +} + +function toHexString(hex_bytes) { + return hex_bytes.map((byte) => { + return (`00${(byte & 0xFF).toString(16)}`).slice(-2); + }).join(''); +} +function hex2bin(hex) { + return (`00000000${(parseInt(hex, 16)).toString(2)}`).substr(-8); +} + +function Decoder(bytes, port) { + const ports = port; + const v4 = toHexString(bytes); + const v4_1 = v4.substr(10, 2); + const v4_2 = v4.substr(0, 2); + const v4_3 = v4.substr(0, 1); + const v3 = v4.substr(8, 1); + const length = v4.length; + let battery; let Tamper; let Valve; let status_v4; let Cable_v4; let conf_p; let conf_s; let unit1; let time1; let Cable; + let unit2; let time2; let temperature; let hygrometry; let status; let DI_0; let DI_1; let Leakage; let Fraud; + let clas; let power; let t; let t1; + // Battery operated V3 Old + if (v4_3 === '3') { + const msg0 = String.fromCharCode.apply(null, bytes); + const bat0 = msg0.substr(0, 4); + battery = Math.round((bat0 - 2000) / 16); + if (length === 10) { + const st0 = msg0.substr(4, 1); + const sta0 = dec_to_bho(st0, 'B'); + status = pad(sta0, 8); + Valve = status.substr(7, 1); + Tamper = status.substr(6, 1); + } + } + // Battery operated V3 & V4 & Class A + if (v4_3 === '3') { + const msg = String.fromCharCode.apply(null, bytes); + const bat = msg.substr(0, 4); + battery = Math.round((bat - 2000) / 16); + if (length >= 10) { + const st = msg.substr(4, 1); + const sta = dec_to_bho(st, 'B'); + status = pad(sta, 8); + Valve = status.substr(7, 1); + Tamper = status.substr(6, 1); + } + const st_1 = v4.substr(8, 2); + t = st_1.substr(0, 1) - 3; + t1 = st_1.substr(1, 1); + const st1 = t + t1; + const st01 = hex2bin(st1); + status = pad(st01, 8); + Valve = status.substr(7, 1); + Tamper = status.substr(6, 1); + Cable = status.substr(5, 1); + DI_0 = status.substr(4, 1); + DI_1 = status.substr(3, 1); + Leakage = status.substr(2, 1); + Fraud = status.substr(1, 1); + conf_p = parseInt(msg.substr(6, 2), 16); + conf_s = msg.substr(8, 2); + unit1 = msg.substr(8, 2); + time1 = msg.substr(10, 2); + unit2 = msg.substr(12, 2); + time2 = msg.substr(14, 2); + const T_H = v4.substr(12, 8); + const temp = T_H.substr(0, 4); + const box_temp = parseInt((temp), 16); + temperature = (((box_temp / 65536) * 165) - 40); + const hum = T_H.substr(4, 8); + const box_hum = parseInt((hum), 16); + hygrometry = ((box_hum / 65536) * 100); + } + // Externally power V4 & Class variation + if (v4_3 !== '3' || v4_3 !== 'B' || v4_3 !== 'b') { + const msg1 = String.fromCharCode.apply(1, bytes); + const st2 = hex2bin(v4_2); + clas = st2.substr(0, 1); + power = st2.substr(1, 1); + const st_3 = v4.substr(8, 2); + t = st_3.substr(0, 1) - 3; + t1 = st_3.substr(1, 1); + const st3 = t + t1; + const st02 = hex2bin(st3); + status = pad(st02, 8); + Valve = status.substr(7, 1); + Tamper = status.substr(6, 1); + Cable = status.substr(5, 1); + DI_0 = status.substr(4, 1); + DI_1 = status.substr(3, 1); + Leakage = status.substr(2, 1); + Fraud = status.substr(1, 1); + conf_p = parseInt(msg1.substr(6, 2), 16); + conf_s = msg1.substr(8, 2); + unit1 = msg1.substr(8, 2); + time1 = msg1.substr(10, 2); + unit2 = msg1.substr(12, 2); + time2 = msg1.substr(14, 2); + const T_H1 = v4.substr(12, 8); + const temp1 = T_H1.substr(0, 4); + const box_temp1 = parseInt((temp1), 16); + temperature = (((box_temp1 / 65536) * 165) - 40); + const hum1 = T_H1.substr(4, 8); + const box_hum1 = parseInt((hum1), 16); + hygrometry = ((box_hum1 / 65536) * 100); + } + // Battery operated V4 & Class variation + if (v4_3 == 'B' || v4_3 == 'b') { + const msg2 = String.fromCharCode.apply(1, bytes); + const st4 = hex2bin(v4_2); + clas = st4.substr(0, 1); + power = st4.substr(1, 1); + const b = v4.substr(1, 1); + const b1 = msg2.substr(2, 3); + const bat1 = b.concat(b1); + battery = Math.round((bat1 - 2000) / 16); + const st_5 = v4.substr(8, 2); + t = st_5.substr(0, 1) - 3; + t1 = st_5.substr(1, 1); + const st5 = t + t1; + const st03 = hex2bin(st5); + status = pad(st03, 8); + Valve = status.substr(7, 1); + Tamper = status.substr(6, 1); + Cable = status.substr(5, 1); + DI_0 = status.substr(4, 1); + DI_1 = status.substr(3, 1); + Leakage = status.substr(2, 1); + Fraud = status.substr(1, 1); + conf_p = parseInt(msg2.substr(7, 2), 16); + conf_s = msg2.substr(9, 2); + unit1 = msg2.substr(9, 2); + time1 = msg2.substr(11, 2); + unit2 = msg2.substr(13, 2); + time2 = msg2.substr(15, 2); + const T_H2 = v4.substr(12, 8); + const temp2 = T_H2.substr(0, 4); + const box_temp2 = parseInt((temp2), 16); + temperature = (((box_temp2 / 65536) * 165) - 40); + const hum2 = T_H2.substr(4, 8); + const box_hum2 = parseInt((hum2), 16); + hygrometry = ((box_hum2 / 65536) * 100); + } + + if (v4_1 === '40') { + if (conf_p === 14 || conf_p === 15 || conf_p === 16 || conf_p === 17 || conf_p === 18 || conf_p === 19 || conf_p === 20) { + return { + Port: ports, + Battery: battery, + Valve, + Tamper, + Cable, + DI_0, + DI_1, + Leakage, + Fraud, + Class: clas, + Power: power, + Schl_Port: conf_p || 0, + Schl_status: conf_s || 0, + }; + } + if (conf_p === 21) { + return { + Port: ports, + Battery: battery, + Valve, + Tamper, + Cable, + DI_0, + DI_1, + Leakage, + Fraud, + Class: clas, + Power: power, + Schl_status_Port: conf_p || 0, + Schl_status_ack: conf_s || 0, + }; + } + if (conf_p === 12 || conf_p === 13) { + return { + Port: ports, + Battery: battery, + Valve, + Tamper, + Cable, + DI_0, + DI_1, + Leakage, + Fraud, + Class: clas, + Power: power, + RTC_Port: conf_p || 0, + RTC_status: conf_s || 0, + }; + } + if (conf_p === 9) { + return { + Port: ports, + Battery: battery, + Valve, + Tamper, + Cable, + DI_0, + DI_1, + Leakage, + Fraud, + Class: clas, + Power: power, + Class_Port: conf_p || 0, + Class_status: conf_s || 0, + }; + } + if (conf_p === 22) { + return { + Port: ports, + Battery: battery, + Valve, + Tamper, + Cable, + DI_0, + DI_1, + Leakage, + Fraud, + Class: clas, + Power: power, + magnet_Port: conf_p || 0, + magnet_status: conf_s || 0, + }; + } + } + if (v4_1 === '23') { + return { + Port: ports, + Status: status, + Battery: battery, + Valve, + Tamper, + Cable, + DI_0, + DI_1, + Leakage, + Fraud, + Class: clas, + Power: power, + Temperature: roundToTwo(temperature), + Hygrometry: roundToTwo(hygrometry), + }; + } + + return { + Port: ports, + Battery: battery, + Valve, + Tamper, + }; +} + + +// let payload = [ +// { +// variable: 'time', +// value: 1571874770.422976, +// serie: 1571874770524, +// }, +// { +// variable: 'payload', +// value: '4Be202000008000001', +// serie: 1571874770524, +// }, +// { +// variable: 'port', +// value: 2, +// serie: 1571874770524, +// }, +// { +// variable: 'duplicate', +// value: false, +// serie: 1571874770524, +// }, +// { +// variable: 'counter_up', +// value: 38, +// serie: 1571874770524, +// }, +// { +// variable: 'rx_time', +// value: 1571874770.3568993, +// serie: 1571874770524, +// }, +// { +// variable: 'encrypted_payload', +// value: 'c12oeBn03DxbfqcD', +// serie: 1571874770524, +// }, +// ]; +// Check if what is being stored is the ttn_payload. +// Payload is an environment variable. Is where what is being inserted to your device comes in. +// Payload always is an array of objects. [ { variable, value...}, {variable, value...} ...] +const payload_raw = payload.find(x => x.variable === 'payload' || x.variable === 'payload_raw' || x.variable === 'data'); +const port = payload.find(x => x.variable === 'fport' || x.variable === 'port'); +if (payload_raw) { + // Get a unique serie for the incoming data. + const serie = payload_raw.serie || new Date().getTime(); + let vars_to_tago = []; + // Parse the payload from your sensor to function parsePayload + try { + const decoded = Decoder(Buffer.from(payload_raw.value, 'hex'), port ? port.value : null); + + vars_to_tago = vars_to_tago.concat(toTagoFormat(decoded, serie)); + } catch (e) { + // Catch any error in the parse code and send to parse_error variable. + vars_to_tago = vars_to_tago.concat({ variable: 'parse_error', value: e.message || e }); + } + + payload = payload.concat(vars_to_tago); +} + +payload = payload.filter((item) => { + if (item.location) { + if (item.location.lat === 0 && item.location.lng === 0) { + return false; + } + } + return true; +}); +// console.log(payload) diff --git a/decoders/connector/strega/smart-valve-full-edition/assets/logo.png b/decoders/connector/strega/smart-valve-full-edition/assets/logo.png new file mode 100644 index 00000000..8e504b09 Binary files /dev/null and b/decoders/connector/strega/smart-valve-full-edition/assets/logo.png differ diff --git a/decoders/connector/strega/smart-valve-full-edition/connector.jsonc b/decoders/connector/strega/smart-valve-full-edition/connector.jsonc new file mode 100644 index 00000000..fb654fa7 --- /dev/null +++ b/decoders/connector/strega/smart-valve-full-edition/connector.jsonc @@ -0,0 +1,13 @@ +{ + "$schema": "../../../../schema/connector.json", + "name": "STREGA Smart-Valve Full Edition", + "images": { + "logo": "./assets/logo.png" + }, + "versions": { + "v1.0.0": { + "src": "./v1.0.0/payload.js", + "manifest": "./v1.0.0/payload-config.jsonc" + } + } +} diff --git a/decoders/connector/strega/smart-valve-full-edition/description.md b/decoders/connector/strega/smart-valve-full-edition/description.md new file mode 100644 index 00000000..2080120d --- /dev/null +++ b/decoders/connector/strega/smart-valve-full-edition/description.md @@ -0,0 +1 @@ +Extreme range LoraWaN Shut-off Valve (Class A, Class C and IO’s) \ No newline at end of file diff --git a/decoders/connector/strega/smart-valve-full-edition/v1.0.0/payload-config.jsonc b/decoders/connector/strega/smart-valve-full-edition/v1.0.0/payload-config.jsonc new file mode 100644 index 00000000..b3d4c10b --- /dev/null +++ b/decoders/connector/strega/smart-valve-full-edition/v1.0.0/payload-config.jsonc @@ -0,0 +1,25 @@ +{ + "$schema": "../../../../../schema/connector_details.json", + "description": "../description.md", + "install_text": "The STREGA time-controlled shut-off valve is a battery-operated wireless valve with embedded LoRaWAN® technology. With its ultra-low-power consumption, the Smart-Valve can be triggered for remote OPEN/CLOSE operations. The valve is working on batteries during 10+ years (Class A) or limitless on external power supply (Class C). It communicates on extreme long distances with\nexceptional deep indoor signal penetration.", + "install_end_text": "", + "device_annotation": "", + "device_parameters": [], + "networks": [ + "../../../../network/lorawan-everynet/v1.0.0/payload.js", + "../../../../network/lorawan-loriot-/v1.0.0/payload.js", + "../../../../network/lorawan-kerlink/v1.0.0/payload.js", + "../../../../network/lorawan-citykinect/v1.0.0/payload.js", + "../../../../network/lorawan-machineq/v1.0.0/payload.js", + "../../../../network/lorawan-orbiwise/v1.0.0/payload.js", + "../../../../network/lorawan-tektelic/v1.0.0/payload.js", + "../../../../network/lorawan-actility/v1.0.0/payload.js", + "../../../../network/lorawan-ttittn-v3/v1.0.0/payload.js", + "../../../../network/lorawan-swisscom/v1.0.0/payload.js", + "../../../../network/lorawan-chirpstack/v1.0.0/payload.js", + "../../../../network/lorawan-helium/v1.0.0/payload.js", + "../../../../network/lorawan-senet/v1.0.0/payload.js", + "../../../../network/lorawan-senra/v1.0.0/payload.js", + "../../../../network/lorawan-brdot-/v1.0.0/payload.js" + ] +} \ No newline at end of file diff --git a/decoders/connector/strega/smart-valve-full-edition/v1.0.0/payload.js b/decoders/connector/strega/smart-valve-full-edition/v1.0.0/payload.js new file mode 100644 index 00000000..d434ec54 --- /dev/null +++ b/decoders/connector/strega/smart-valve-full-edition/v1.0.0/payload.js @@ -0,0 +1,404 @@ +/* eslint-disable no-mixed-operators */ +/* eslint-disable no-bitwise */ + +/* This is an example code for Everynet Parser. +** Everynet send several parameters to TagoIO. The job of this parse is to convert all these parameters into a TagoIO format. +** One of these parameters is the payload of your device. We find it too and apply the appropriate sensor parse. +** +** IMPORTANT: In most case, you will only need to edit the parsePayload function. +** +** Testing: +** You can do manual tests to this parse by using the Device Emulator. Copy and Paste the following code: +** [{ "variable": "everynet_payload", "value": "{ \"params\": { \"payload\": \"0109611395\" } }" }] +** +** The ignore_vars variable in this code should be used to ignore variables +** from the device that you don't want. +*/ +// Add ignorable variables in this array. +const ignore_vars = ['device_addr', 'port', 'duplicate', 'network', 'packet_hash', 'application', 'device', 'packet_id']; + + +/** + * Convert an object to TagoIO object format. + * Can be used in two ways: + * toTagoFormat({ myvariable: myvalue , anothervariable: anothervalue... }) + * toTagoFormat({ myvariable: { value: myvalue, unit: 'C', metadata: { color: 'green' }} , anothervariable: anothervalue... }) + * + * @param {Object} object_item Object containing key and value. + * @param {String} serie Serie for the variables + * @param {String} prefix Add a prefix to the variables name + */ +function toTagoFormat(object_item, serie, prefix = '') { + const result = []; + for (const key in object_item) { + if (ignore_vars.includes(key)) continue; + + if (typeof object_item[key] === 'object') { + result.push({ + variable: object_item[key].variable || `${prefix}${key}`.toLowerCase(), + value: object_item[key].value, + serie: object_item[key].serie || serie, + metadata: object_item[key].metadata, + location: object_item[key].location, + unit: object_item[key].unit, + }); + } else { + result.push({ + variable: `${prefix}${key}`.toLowerCase(), + value: object_item[key], + serie, + }); + } + } + + return result; +} + +function pad(num, len) { + return (`00000000${num}`).substr(-len); +} +function roundToTwo(num) { + return +(`${Math.round(`${num}e+2`)}e-2`); +} +function dec_to_bho(n, base) { + if (n < 0) { + n = 0xFFFFFFFF + n + 1; + } + switch (base) { + case 'B': + return parseInt(n, 10).toString(2); + case 'H': + return parseInt(n, 10).toString(16); + case 'O': + return parseInt(n, 10).toString(8); + default: + return ('Wrong input.........'); + } +} + +function toHexString(hex_bytes) { + return hex_bytes.map((byte) => { + return (`00${(byte & 0xFF).toString(16)}`).slice(-2); + }).join(''); +} +function hex2bin(hex) { + return (`00000000${(parseInt(hex, 16)).toString(2)}`).substr(-8); +} + +function Decoder(bytes, port) { + const ports = port; + const v4 = toHexString(bytes); + const v4_1 = v4.substr(10, 2); + const v4_2 = v4.substr(0, 2); + const v4_3 = v4.substr(0, 1); + const v3 = v4.substr(8, 1); + const length = v4.length; + let battery; let Tamper; let Valve; let status_v4; let Cable_v4; let conf_p; let conf_s; let unit1; let time1; let Cable; + let unit2; let time2; let temperature; let hygrometry; let status; let DI_0; let DI_1; let Leakage; let Fraud; + let clas; let power; let t; let t1; + // Battery operated V3 Old + if (v4_3 === '3') { + const msg0 = String.fromCharCode.apply(null, bytes); + const bat0 = msg0.substr(0, 4); + battery = Math.round((bat0 - 2000) / 16); + if (length === 10) { + const st0 = msg0.substr(4, 1); + const sta0 = dec_to_bho(st0, 'B'); + status = pad(sta0, 8); + Valve = status.substr(7, 1); + Tamper = status.substr(6, 1); + } + } + // Battery operated V3 & V4 & Class A + if (v4_3 === '3') { + const msg = String.fromCharCode.apply(null, bytes); + const bat = msg.substr(0, 4); + battery = Math.round((bat - 2000) / 16); + if (length >= 10) { + const st = msg.substr(4, 1); + const sta = dec_to_bho(st, 'B'); + status = pad(sta, 8); + Valve = status.substr(7, 1); + Tamper = status.substr(6, 1); + } + const st_1 = v4.substr(8, 2); + t = st_1.substr(0, 1) - 3; + t1 = st_1.substr(1, 1); + const st1 = t + t1; + const st01 = hex2bin(st1); + status = pad(st01, 8); + Valve = status.substr(7, 1); + Tamper = status.substr(6, 1); + Cable = status.substr(5, 1); + DI_0 = status.substr(4, 1); + DI_1 = status.substr(3, 1); + Leakage = status.substr(2, 1); + Fraud = status.substr(1, 1); + conf_p = parseInt(msg.substr(6, 2), 16); + conf_s = msg.substr(8, 2); + unit1 = msg.substr(8, 2); + time1 = msg.substr(10, 2); + unit2 = msg.substr(12, 2); + time2 = msg.substr(14, 2); + const T_H = v4.substr(12, 8); + const temp = T_H.substr(0, 4); + const box_temp = parseInt((temp), 16); + temperature = (((box_temp / 65536) * 165) - 40); + const hum = T_H.substr(4, 8); + const box_hum = parseInt((hum), 16); + hygrometry = ((box_hum / 65536) * 100); + } + // Externally power V4 & Class variation + if (v4_3 !== '3' || v4_3 !== 'B' || v4_3 !== 'b') { + const msg1 = String.fromCharCode.apply(1, bytes); + const st2 = hex2bin(v4_2); + clas = st2.substr(0, 1); + power = st2.substr(1, 1); + const st_3 = v4.substr(8, 2); + t = st_3.substr(0, 1) - 3; + t1 = st_3.substr(1, 1); + const st3 = t + t1; + const st02 = hex2bin(st3); + status = pad(st02, 8); + Valve = status.substr(7, 1); + Tamper = status.substr(6, 1); + Cable = status.substr(5, 1); + DI_0 = status.substr(4, 1); + DI_1 = status.substr(3, 1); + Leakage = status.substr(2, 1); + Fraud = status.substr(1, 1); + conf_p = parseInt(msg1.substr(6, 2), 16); + conf_s = msg1.substr(8, 2); + unit1 = msg1.substr(8, 2); + time1 = msg1.substr(10, 2); + unit2 = msg1.substr(12, 2); + time2 = msg1.substr(14, 2); + const T_H1 = v4.substr(12, 8); + const temp1 = T_H1.substr(0, 4); + const box_temp1 = parseInt((temp1), 16); + temperature = (((box_temp1 / 65536) * 165) - 40); + const hum1 = T_H1.substr(4, 8); + const box_hum1 = parseInt((hum1), 16); + hygrometry = ((box_hum1 / 65536) * 100); + } + // Battery operated V4 & Class variation + if (v4_3 == 'B' || v4_3 == 'b') { + const msg2 = String.fromCharCode.apply(1, bytes); + const st4 = hex2bin(v4_2); + clas = st4.substr(0, 1); + power = st4.substr(1, 1); + const b = v4.substr(1, 1); + const b1 = msg2.substr(2, 3); + const bat1 = b.concat(b1); + battery = Math.round((bat1 - 2000) / 16); + const st_5 = v4.substr(8, 2); + t = st_5.substr(0, 1) - 3; + t1 = st_5.substr(1, 1); + const st5 = t + t1; + const st03 = hex2bin(st5); + status = pad(st03, 8); + Valve = status.substr(7, 1); + Tamper = status.substr(6, 1); + Cable = status.substr(5, 1); + DI_0 = status.substr(4, 1); + DI_1 = status.substr(3, 1); + Leakage = status.substr(2, 1); + Fraud = status.substr(1, 1); + conf_p = parseInt(msg2.substr(7, 2), 16); + conf_s = msg2.substr(9, 2); + unit1 = msg2.substr(9, 2); + time1 = msg2.substr(11, 2); + unit2 = msg2.substr(13, 2); + time2 = msg2.substr(15, 2); + const T_H2 = v4.substr(12, 8); + const temp2 = T_H2.substr(0, 4); + const box_temp2 = parseInt((temp2), 16); + temperature = (((box_temp2 / 65536) * 165) - 40); + const hum2 = T_H2.substr(4, 8); + const box_hum2 = parseInt((hum2), 16); + hygrometry = ((box_hum2 / 65536) * 100); + } + + if (v4_1 === '40') { + if (conf_p === 14 || conf_p === 15 || conf_p === 16 || conf_p === 17 || conf_p === 18 || conf_p === 19 || conf_p === 20) { + return { + Port: ports, + Battery: battery, + Valve, + Tamper, + Cable, + DI_0, + DI_1, + Leakage, + Fraud, + Class: clas, + Power: power, + Schl_Port: conf_p || 0, + Schl_status: conf_s || 0, + }; + } + if (conf_p === 21) { + return { + Port: ports, + Battery: battery, + Valve, + Tamper, + Cable, + DI_0, + DI_1, + Leakage, + Fraud, + Class: clas, + Power: power, + Schl_status_Port: conf_p || 0, + Schl_status_ack: conf_s || 0, + }; + } + if (conf_p === 12 || conf_p === 13) { + return { + Port: ports, + Battery: battery, + Valve, + Tamper, + Cable, + DI_0, + DI_1, + Leakage, + Fraud, + Class: clas, + Power: power, + RTC_Port: conf_p || 0, + RTC_status: conf_s || 0, + }; + } + if (conf_p === 9) { + return { + Port: ports, + Battery: battery, + Valve, + Tamper, + Cable, + DI_0, + DI_1, + Leakage, + Fraud, + Class: clas, + Power: power, + Class_Port: conf_p || 0, + Class_status: conf_s || 0, + }; + } + if (conf_p === 22) { + return { + Port: ports, + Battery: battery, + Valve, + Tamper, + Cable, + DI_0, + DI_1, + Leakage, + Fraud, + Class: clas, + Power: power, + magnet_Port: conf_p || 0, + magnet_status: conf_s || 0, + }; + } + } + if (v4_1 === '23') { + return { + Port: ports, + Status: status, + Battery: battery, + Valve, + Tamper, + Cable, + DI_0, + DI_1, + Leakage, + Fraud, + Class: clas, + Power: power, + Temperature: roundToTwo(temperature), + Hygrometry: roundToTwo(hygrometry), + }; + } + + return { + Port: ports, + Battery: battery, + Valve, + Tamper, + }; +} + + +// let payload = [ +// { +// variable: 'time', +// value: 1571874770.422976, +// serie: 1571874770524, +// }, +// { +// variable: 'payload', +// value: '4Be202000008000001', +// serie: 1571874770524, +// }, +// { +// variable: 'port', +// value: 2, +// serie: 1571874770524, +// }, +// { +// variable: 'duplicate', +// value: false, +// serie: 1571874770524, +// }, +// { +// variable: 'counter_up', +// value: 38, +// serie: 1571874770524, +// }, +// { +// variable: 'rx_time', +// value: 1571874770.3568993, +// serie: 1571874770524, +// }, +// { +// variable: 'encrypted_payload', +// value: 'c12oeBn03DxbfqcD', +// serie: 1571874770524, +// }, +// ]; +// Check if what is being stored is the ttn_payload. +// Payload is an environment variable. Is where what is being inserted to your device comes in. +// Payload always is an array of objects. [ { variable, value...}, {variable, value...} ...] +const payload_raw = payload.find(x => x.variable === 'payload' || x.variable === 'payload_raw' || x.variable === 'data'); +const port = payload.find(x => x.variable === 'fport' || x.variable === 'port'); +if (payload_raw) { + // Get a unique serie for the incoming data. + const serie = payload_raw.serie || new Date().getTime(); + let vars_to_tago = []; + // Parse the payload from your sensor to function parsePayload + try { + const decoded = Decoder(Buffer.from(payload_raw.value, 'hex'), port ? port.value : null); + console.log(decoded); + vars_to_tago = vars_to_tago.concat(toTagoFormat(decoded, serie)); + } catch (e) { + // Catch any error in the parse code and send to parse_error variable. + vars_to_tago = vars_to_tago.concat({ variable: 'parse_error', value: e.message || e }); + } + + payload = payload.concat(vars_to_tago); +} + +payload = payload.filter((item) => { + if (item.location) { + if (item.location.lat === 0 && item.location.lng === 0) { + return false; + } + } + return true; +}); +// console.log(payload) diff --git a/decoders/connector/strega/smart-valve-lite-edition/assets/logo.png b/decoders/connector/strega/smart-valve-lite-edition/assets/logo.png new file mode 100644 index 00000000..b7ec05d5 Binary files /dev/null and b/decoders/connector/strega/smart-valve-lite-edition/assets/logo.png differ diff --git a/decoders/connector/strega/smart-valve-lite-edition/connector.jsonc b/decoders/connector/strega/smart-valve-lite-edition/connector.jsonc new file mode 100644 index 00000000..10294244 --- /dev/null +++ b/decoders/connector/strega/smart-valve-lite-edition/connector.jsonc @@ -0,0 +1,13 @@ +{ + "$schema": "../../../../schema/connector.json", + "name": "STREGA Smart-Valve Lite Edition", + "images": { + "logo": "./assets/logo.png" + }, + "versions": { + "v1.0.0": { + "src": "./v1.0.0/payload.js", + "manifest": "./v1.0.0/payload-config.jsonc" + } + } +} diff --git a/decoders/connector/strega/smart-valve-lite-edition/description.md b/decoders/connector/strega/smart-valve-lite-edition/description.md new file mode 100644 index 00000000..06eeb563 --- /dev/null +++ b/decoders/connector/strega/smart-valve-lite-edition/description.md @@ -0,0 +1 @@ +Extreme range LoraWaN Shut-off Valve (Class A) \ No newline at end of file diff --git a/decoders/connector/strega/smart-valve-lite-edition/v1.0.0/payload-config.jsonc b/decoders/connector/strega/smart-valve-lite-edition/v1.0.0/payload-config.jsonc new file mode 100644 index 00000000..8fb7c96b --- /dev/null +++ b/decoders/connector/strega/smart-valve-lite-edition/v1.0.0/payload-config.jsonc @@ -0,0 +1,25 @@ +{ + "$schema": "../../../../../schema/connector_details.json", + "description": "../description.md", + "install_text": "The STREGA time-controlled shut-off valve is a battery-operated wireless valve with embedded LoRaWAN® technology. With its ultra-low-power consumption, the Smart-Valve can be triggered for remote OPEN/CLOSE operations. The valve is working on batteries during 10+ years an through extreme long distances with exceptional deep indoor signal penetration.\nexceptional deep indoor signal penetration.", + "install_end_text": "", + "device_annotation": "", + "device_parameters": [], + "networks": [ + "../../../../network/lorawan-everynet/v1.0.0/payload.js", + "../../../../network/lorawan-citykinect/v1.0.0/payload.js", + "../../../../network/lorawan-loriot-/v1.0.0/payload.js", + "../../../../network/lorawan-kerlink/v1.0.0/payload.js", + "../../../../network/lorawan-machineq/v1.0.0/payload.js", + "../../../../network/lorawan-orbiwise/v1.0.0/payload.js", + "../../../../network/lorawan-tektelic/v1.0.0/payload.js", + "../../../../network/lorawan-actility/v1.0.0/payload.js", + "../../../../network/lorawan-ttittn-v3/v1.0.0/payload.js", + "../../../../network/lorawan-swisscom/v1.0.0/payload.js", + "../../../../network/lorawan-chirpstack/v1.0.0/payload.js", + "../../../../network/lorawan-helium/v1.0.0/payload.js", + "../../../../network/lorawan-senra/v1.0.0/payload.js", + "../../../../network/lorawan-senet/v1.0.0/payload.js", + "../../../../network/lorawan-brdot-/v1.0.0/payload.js" + ] +} \ No newline at end of file diff --git a/decoders/connector/strega/smart-valve-lite-edition/v1.0.0/payload.js b/decoders/connector/strega/smart-valve-lite-edition/v1.0.0/payload.js new file mode 100644 index 00000000..d434ec54 --- /dev/null +++ b/decoders/connector/strega/smart-valve-lite-edition/v1.0.0/payload.js @@ -0,0 +1,404 @@ +/* eslint-disable no-mixed-operators */ +/* eslint-disable no-bitwise */ + +/* This is an example code for Everynet Parser. +** Everynet send several parameters to TagoIO. The job of this parse is to convert all these parameters into a TagoIO format. +** One of these parameters is the payload of your device. We find it too and apply the appropriate sensor parse. +** +** IMPORTANT: In most case, you will only need to edit the parsePayload function. +** +** Testing: +** You can do manual tests to this parse by using the Device Emulator. Copy and Paste the following code: +** [{ "variable": "everynet_payload", "value": "{ \"params\": { \"payload\": \"0109611395\" } }" }] +** +** The ignore_vars variable in this code should be used to ignore variables +** from the device that you don't want. +*/ +// Add ignorable variables in this array. +const ignore_vars = ['device_addr', 'port', 'duplicate', 'network', 'packet_hash', 'application', 'device', 'packet_id']; + + +/** + * Convert an object to TagoIO object format. + * Can be used in two ways: + * toTagoFormat({ myvariable: myvalue , anothervariable: anothervalue... }) + * toTagoFormat({ myvariable: { value: myvalue, unit: 'C', metadata: { color: 'green' }} , anothervariable: anothervalue... }) + * + * @param {Object} object_item Object containing key and value. + * @param {String} serie Serie for the variables + * @param {String} prefix Add a prefix to the variables name + */ +function toTagoFormat(object_item, serie, prefix = '') { + const result = []; + for (const key in object_item) { + if (ignore_vars.includes(key)) continue; + + if (typeof object_item[key] === 'object') { + result.push({ + variable: object_item[key].variable || `${prefix}${key}`.toLowerCase(), + value: object_item[key].value, + serie: object_item[key].serie || serie, + metadata: object_item[key].metadata, + location: object_item[key].location, + unit: object_item[key].unit, + }); + } else { + result.push({ + variable: `${prefix}${key}`.toLowerCase(), + value: object_item[key], + serie, + }); + } + } + + return result; +} + +function pad(num, len) { + return (`00000000${num}`).substr(-len); +} +function roundToTwo(num) { + return +(`${Math.round(`${num}e+2`)}e-2`); +} +function dec_to_bho(n, base) { + if (n < 0) { + n = 0xFFFFFFFF + n + 1; + } + switch (base) { + case 'B': + return parseInt(n, 10).toString(2); + case 'H': + return parseInt(n, 10).toString(16); + case 'O': + return parseInt(n, 10).toString(8); + default: + return ('Wrong input.........'); + } +} + +function toHexString(hex_bytes) { + return hex_bytes.map((byte) => { + return (`00${(byte & 0xFF).toString(16)}`).slice(-2); + }).join(''); +} +function hex2bin(hex) { + return (`00000000${(parseInt(hex, 16)).toString(2)}`).substr(-8); +} + +function Decoder(bytes, port) { + const ports = port; + const v4 = toHexString(bytes); + const v4_1 = v4.substr(10, 2); + const v4_2 = v4.substr(0, 2); + const v4_3 = v4.substr(0, 1); + const v3 = v4.substr(8, 1); + const length = v4.length; + let battery; let Tamper; let Valve; let status_v4; let Cable_v4; let conf_p; let conf_s; let unit1; let time1; let Cable; + let unit2; let time2; let temperature; let hygrometry; let status; let DI_0; let DI_1; let Leakage; let Fraud; + let clas; let power; let t; let t1; + // Battery operated V3 Old + if (v4_3 === '3') { + const msg0 = String.fromCharCode.apply(null, bytes); + const bat0 = msg0.substr(0, 4); + battery = Math.round((bat0 - 2000) / 16); + if (length === 10) { + const st0 = msg0.substr(4, 1); + const sta0 = dec_to_bho(st0, 'B'); + status = pad(sta0, 8); + Valve = status.substr(7, 1); + Tamper = status.substr(6, 1); + } + } + // Battery operated V3 & V4 & Class A + if (v4_3 === '3') { + const msg = String.fromCharCode.apply(null, bytes); + const bat = msg.substr(0, 4); + battery = Math.round((bat - 2000) / 16); + if (length >= 10) { + const st = msg.substr(4, 1); + const sta = dec_to_bho(st, 'B'); + status = pad(sta, 8); + Valve = status.substr(7, 1); + Tamper = status.substr(6, 1); + } + const st_1 = v4.substr(8, 2); + t = st_1.substr(0, 1) - 3; + t1 = st_1.substr(1, 1); + const st1 = t + t1; + const st01 = hex2bin(st1); + status = pad(st01, 8); + Valve = status.substr(7, 1); + Tamper = status.substr(6, 1); + Cable = status.substr(5, 1); + DI_0 = status.substr(4, 1); + DI_1 = status.substr(3, 1); + Leakage = status.substr(2, 1); + Fraud = status.substr(1, 1); + conf_p = parseInt(msg.substr(6, 2), 16); + conf_s = msg.substr(8, 2); + unit1 = msg.substr(8, 2); + time1 = msg.substr(10, 2); + unit2 = msg.substr(12, 2); + time2 = msg.substr(14, 2); + const T_H = v4.substr(12, 8); + const temp = T_H.substr(0, 4); + const box_temp = parseInt((temp), 16); + temperature = (((box_temp / 65536) * 165) - 40); + const hum = T_H.substr(4, 8); + const box_hum = parseInt((hum), 16); + hygrometry = ((box_hum / 65536) * 100); + } + // Externally power V4 & Class variation + if (v4_3 !== '3' || v4_3 !== 'B' || v4_3 !== 'b') { + const msg1 = String.fromCharCode.apply(1, bytes); + const st2 = hex2bin(v4_2); + clas = st2.substr(0, 1); + power = st2.substr(1, 1); + const st_3 = v4.substr(8, 2); + t = st_3.substr(0, 1) - 3; + t1 = st_3.substr(1, 1); + const st3 = t + t1; + const st02 = hex2bin(st3); + status = pad(st02, 8); + Valve = status.substr(7, 1); + Tamper = status.substr(6, 1); + Cable = status.substr(5, 1); + DI_0 = status.substr(4, 1); + DI_1 = status.substr(3, 1); + Leakage = status.substr(2, 1); + Fraud = status.substr(1, 1); + conf_p = parseInt(msg1.substr(6, 2), 16); + conf_s = msg1.substr(8, 2); + unit1 = msg1.substr(8, 2); + time1 = msg1.substr(10, 2); + unit2 = msg1.substr(12, 2); + time2 = msg1.substr(14, 2); + const T_H1 = v4.substr(12, 8); + const temp1 = T_H1.substr(0, 4); + const box_temp1 = parseInt((temp1), 16); + temperature = (((box_temp1 / 65536) * 165) - 40); + const hum1 = T_H1.substr(4, 8); + const box_hum1 = parseInt((hum1), 16); + hygrometry = ((box_hum1 / 65536) * 100); + } + // Battery operated V4 & Class variation + if (v4_3 == 'B' || v4_3 == 'b') { + const msg2 = String.fromCharCode.apply(1, bytes); + const st4 = hex2bin(v4_2); + clas = st4.substr(0, 1); + power = st4.substr(1, 1); + const b = v4.substr(1, 1); + const b1 = msg2.substr(2, 3); + const bat1 = b.concat(b1); + battery = Math.round((bat1 - 2000) / 16); + const st_5 = v4.substr(8, 2); + t = st_5.substr(0, 1) - 3; + t1 = st_5.substr(1, 1); + const st5 = t + t1; + const st03 = hex2bin(st5); + status = pad(st03, 8); + Valve = status.substr(7, 1); + Tamper = status.substr(6, 1); + Cable = status.substr(5, 1); + DI_0 = status.substr(4, 1); + DI_1 = status.substr(3, 1); + Leakage = status.substr(2, 1); + Fraud = status.substr(1, 1); + conf_p = parseInt(msg2.substr(7, 2), 16); + conf_s = msg2.substr(9, 2); + unit1 = msg2.substr(9, 2); + time1 = msg2.substr(11, 2); + unit2 = msg2.substr(13, 2); + time2 = msg2.substr(15, 2); + const T_H2 = v4.substr(12, 8); + const temp2 = T_H2.substr(0, 4); + const box_temp2 = parseInt((temp2), 16); + temperature = (((box_temp2 / 65536) * 165) - 40); + const hum2 = T_H2.substr(4, 8); + const box_hum2 = parseInt((hum2), 16); + hygrometry = ((box_hum2 / 65536) * 100); + } + + if (v4_1 === '40') { + if (conf_p === 14 || conf_p === 15 || conf_p === 16 || conf_p === 17 || conf_p === 18 || conf_p === 19 || conf_p === 20) { + return { + Port: ports, + Battery: battery, + Valve, + Tamper, + Cable, + DI_0, + DI_1, + Leakage, + Fraud, + Class: clas, + Power: power, + Schl_Port: conf_p || 0, + Schl_status: conf_s || 0, + }; + } + if (conf_p === 21) { + return { + Port: ports, + Battery: battery, + Valve, + Tamper, + Cable, + DI_0, + DI_1, + Leakage, + Fraud, + Class: clas, + Power: power, + Schl_status_Port: conf_p || 0, + Schl_status_ack: conf_s || 0, + }; + } + if (conf_p === 12 || conf_p === 13) { + return { + Port: ports, + Battery: battery, + Valve, + Tamper, + Cable, + DI_0, + DI_1, + Leakage, + Fraud, + Class: clas, + Power: power, + RTC_Port: conf_p || 0, + RTC_status: conf_s || 0, + }; + } + if (conf_p === 9) { + return { + Port: ports, + Battery: battery, + Valve, + Tamper, + Cable, + DI_0, + DI_1, + Leakage, + Fraud, + Class: clas, + Power: power, + Class_Port: conf_p || 0, + Class_status: conf_s || 0, + }; + } + if (conf_p === 22) { + return { + Port: ports, + Battery: battery, + Valve, + Tamper, + Cable, + DI_0, + DI_1, + Leakage, + Fraud, + Class: clas, + Power: power, + magnet_Port: conf_p || 0, + magnet_status: conf_s || 0, + }; + } + } + if (v4_1 === '23') { + return { + Port: ports, + Status: status, + Battery: battery, + Valve, + Tamper, + Cable, + DI_0, + DI_1, + Leakage, + Fraud, + Class: clas, + Power: power, + Temperature: roundToTwo(temperature), + Hygrometry: roundToTwo(hygrometry), + }; + } + + return { + Port: ports, + Battery: battery, + Valve, + Tamper, + }; +} + + +// let payload = [ +// { +// variable: 'time', +// value: 1571874770.422976, +// serie: 1571874770524, +// }, +// { +// variable: 'payload', +// value: '4Be202000008000001', +// serie: 1571874770524, +// }, +// { +// variable: 'port', +// value: 2, +// serie: 1571874770524, +// }, +// { +// variable: 'duplicate', +// value: false, +// serie: 1571874770524, +// }, +// { +// variable: 'counter_up', +// value: 38, +// serie: 1571874770524, +// }, +// { +// variable: 'rx_time', +// value: 1571874770.3568993, +// serie: 1571874770524, +// }, +// { +// variable: 'encrypted_payload', +// value: 'c12oeBn03DxbfqcD', +// serie: 1571874770524, +// }, +// ]; +// Check if what is being stored is the ttn_payload. +// Payload is an environment variable. Is where what is being inserted to your device comes in. +// Payload always is an array of objects. [ { variable, value...}, {variable, value...} ...] +const payload_raw = payload.find(x => x.variable === 'payload' || x.variable === 'payload_raw' || x.variable === 'data'); +const port = payload.find(x => x.variable === 'fport' || x.variable === 'port'); +if (payload_raw) { + // Get a unique serie for the incoming data. + const serie = payload_raw.serie || new Date().getTime(); + let vars_to_tago = []; + // Parse the payload from your sensor to function parsePayload + try { + const decoded = Decoder(Buffer.from(payload_raw.value, 'hex'), port ? port.value : null); + console.log(decoded); + vars_to_tago = vars_to_tago.concat(toTagoFormat(decoded, serie)); + } catch (e) { + // Catch any error in the parse code and send to parse_error variable. + vars_to_tago = vars_to_tago.concat({ variable: 'parse_error', value: e.message || e }); + } + + payload = payload.concat(vars_to_tago); +} + +payload = payload.filter((item) => { + if (item.location) { + if (item.location.lat === 0 && item.location.lng === 0) { + return false; + } + } + return true; +}); +// console.log(payload)