Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions profiles/Eddy-Solutions/Eddy-Solutions-H20-V3.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
codec: |
function validBytes(data) {
if (!Array.isArray(data) || data.length === 0 || data.length > 255) {
return "invalid payload";
}
for (var i = 0; i < data.length; i += 1) {
if (!Number.isInteger(data[i]) || data[i] < 0 || data[i] > 255) {
return "invalid payload byte";
}
}
return null;
}

function frameError(fPort, data) {
if (fPort !== 3) {
return "unsupported fPort";
}
var error = validBytes(data);
if (error !== null) {
return error;
}
var type = data[0];
if (type === 0x00 || type === 0x03) {
return data.length === 11 ? null : "invalid describe/ack length";
}
if (type === 0x01) {
return data.length === 11 ? null : "invalid report length";
}
if (type === 0x02 || type === 0x04) {
return data.length === 7 ? null : "invalid alert/deep-sleep length";
}
return "unsupported message type";
}

function appendStatus(values, status) {
values.push({ name: "Leak Alert", channel: 1, value: status & 0x01, unit: null });
values.push({ name: "Leak Clear", channel: 2, value: (status >> 3) & 0x01, unit: null });
}

function Decode(fPort, data, variables) {
var error = frameError(fPort, data);
if (error !== null) {
return [];
}

var values = [];
var type = data[0];
appendStatus(values, data[1]);

if (type === 0x00) {
values.push({ name: "Get Report Period", channel: 5, value: data[2], unit: null });
// Firmware byte: bits 5-7 major, bits 2-4 minor, bits 0-1 revision.
values.push({ name: "Firmware Version", channel: 6, value: data[10], unit: null });
values.push({ name: "Set Report Period", channel: 7, value: data[2], unit: null });
return values;
}

if (type === 0x01 || type === 0x02 || type === 0x04) {
values.push({ name: "Temperature", channel: 3, value: data[2], unit: "degreesCelsius" });
values.push({ name: "Humidity", channel: 4, value: data[3], unit: "percentRelativeHumidity" });
return values;
}

if (type === 0x03) {
values.push({ name: "Get Report Period", channel: 5, value: data[2], unit: null });
values.push({ name: "Set Report Period", channel: 7, value: data[2], unit: null });
return values;
}

return [];
}

function Encode(data, variables) {
if (!data || !Number.isInteger(data.channel) || !Number.isFinite(data.value)) {
return [];
}
if (data.channel !== 7 || !Number.isInteger(data.value) || data.value < 0 || data.value > 255) {
return [];
}
return [0x10, data.value, 0x0F];
}

function decodeUplink(input) {
if (!input) {
return { data: [], errors: ["invalid input"] };
}
var error = frameError(input.fPort, input.bytes);
if (error !== null) {
return { data: [], errors: [error] };
}
return { data: Decode(input.fPort, input.bytes, input.variables) };
}

function encodeDownlink(input) {
if (!input) {
return { bytes: [], errors: ["invalid input"] };
}
var bytes = Encode(input.data, input.variables);
if (bytes.length === 0) {
return { bytes: [], errors: ["unsupported channel or value"] };
}
return { bytes: bytes };
}

datatype:
"1":
name: Leak Alert
type: BinaryInputObject
channel: 1
"2":
name: Leak Clear
type: BinaryInputObject
channel: 2
"3":
name: Temperature
type: AnalogValueObject
units: degreesCelsius
channel: 3
"4":
name: Humidity
type: AnalogValueObject
units: percentRelativeHumidity
channel: 4
"5":
name: Get Report Period
type: AnalogValueObject
channel: 5
"6":
name: Firmware Version
type: AnalogValueObject
channel: 6
"7":
name: Set Report Period
type: AnalogValueObject
fport: 5
channel: 7

lorawan:
adrAlgorithm: LoRa Only
classCDownlinkTimeout: 5
macVersion: LORAWAN_1_0_3
regionalParametersRevision: A
supportClassB: false
supportClassC: false
supportOTAA: true
model: Eddy-Solutions-H20-V3
profileVersion: 1.0.0
name: H20-V3
vendor: Eddy-Solutions
id: f2c5f7c1-6b5d-4b7a-9f1e-2c4d8a6e7310
87 changes: 87 additions & 0 deletions profiles/Eddy-Solutions/tests/Eddy-Solutions-H20-V3.test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
{
"schemaVersion": 1,
"strict": true,
"profile": "Eddy-Solutions-H20-V3",
"evidenceLevel": "documentation-only",
"reviewMode": "single-model",
"fPortPolicy": {
"mode": "fixed",
"ports": [3],
"citation": "Issue #41 supplies all uplink examples on fPort 3; the documented Set Report and Read Time Period downlink is on fPort 5."
},
"sources": [
{
"type": "issue",
"reference": "https://github.com/RAKWireless/RAK-BACnet-Profiles/issues/41",
"citation": "Four known uplink payloads, BACnet mappings, and the Set Report Period downlink vector."
},
{
"type": "official-document",
"reference": "H20 Leak Detector V3 Technical Specification, supplied official-document.txt",
"citation": "Sections 5.1 and 6.1-6.6; 9-page PDF SHA-256 cc69eaeb11bea043d32f2f92f652a8bb8d1a890c435ee0cc117ee373c5d7e768."
}
],
"robustness": {
"checkTruncation": true,
"checkUnknownFPort": true,
"checkFuzz": true
},
"testCases": [
{
"name": "Describe",
"fPort": 3,
"input": "0000040F27E3286E000097",
"expectedOutput": [
{ "name": "Leak Alert", "channel": 1, "value": 0, "unit": null },
{ "name": "Leak Clear", "channel": 2, "value": 0, "unit": null },
{ "name": "Get Report Period", "channel": 5, "value": 4, "unit": null },
{ "name": "Firmware Version", "channel": 6, "value": 151, "unit": null },
{ "name": "Set Report Period", "channel": 7, "value": 4, "unit": null }
]
},
{
"name": "Report",
"fPort": 3,
"input": "010015241E640A15162424",
"expectedOutput": [
{ "name": "Leak Alert", "channel": 1, "value": 0, "unit": null },
{ "name": "Leak Clear", "channel": 2, "value": 0, "unit": null },
{ "name": "Temperature", "channel": 3, "value": 21, "unit": "degreesCelsius" },
{ "name": "Humidity", "channel": 4, "value": 36, "unit": "percentRelativeHumidity" }
]
},
{
"name": "Alert",
"fPort": 3,
"input": "0201185A215A08",
"expectedOutput": [
{ "name": "Leak Alert", "channel": 1, "value": 1, "unit": null },
{ "name": "Leak Clear", "channel": 2, "value": 0, "unit": null },
{ "name": "Temperature", "channel": 3, "value": 24, "unit": "degreesCelsius" },
{ "name": "Humidity", "channel": 4, "value": 90, "unit": "percentRelativeHumidity" }
]
},
{
"name": "Acknowledgement",
"fPort": 3,
"input": "0318040F27FE286E000097",
"expectedOutput": [
{ "name": "Leak Alert", "channel": 1, "value": 0, "unit": null },
{ "name": "Leak Clear", "channel": 2, "value": 1, "unit": null },
{ "name": "Get Report Period", "channel": 5, "value": 4, "unit": null },
{ "name": "Set Report Period", "channel": 7, "value": 4, "unit": null }
]
}
],
"downlinkTestCases": [
{
"name": "Set Report Period to 20 Hours",
"channel": 7,
"value": 20,
"expectedFPort": 5,
"expectedBytes": "10 14 0F",
"description": "Issue #41 prepared downlink evidence: report period 20 hours and read period 15 minutes.",
"citation": "Issue #41 downlinkEvidenceText."
}
]
}
22 changes: 19 additions & 3 deletions registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "./registry-schema.json",
"version": "1.0.0",
"lastUpdate": "2026-09-14",
"totalProfiles": 28,
"totalProfiles": 29,
"profiles": [
{
"id": "rakwireless-rak2461",
Expand Down Expand Up @@ -148,6 +148,22 @@
"contentSha256": "ec6bd00b66f02bbc08dc5b9f9a929d0622355b89ee95e8bca4a2ce817c853c65",
"lastUpdate": "2026-04-24"
},
{
"id": "eddy-solutions-h20-v3",
"vendor": "Eddy-Solutions",
"model": "Eddy-Solutions-H20-V3",
"version": "3.0.0",
"path": "profiles/Eddy-Solutions/Eddy-Solutions-H20-V3.yaml",
"verified": false,
"hasTests": true,
"description": "Eddy-Solutions Eddy-Solutions-H20-V3 Sensor",
"deviceType": "Sensor",
"lorawanClass": [
"A"
],
"contentSha256": "1459359acd021ba0cea89306c932dd77fbdaddd08cdd1d1437eb743bcb83bc66",
"lastUpdate": "2026-09-14"
},
{
"id": "eddy-solutions-link",
"vendor": "Eddy-Solutions",
Expand Down Expand Up @@ -458,15 +474,15 @@
"RAKwireless": 2,
"Becasmart": 1,
"Dragino": 6,
"Eddy-Solutions": 2,
"Eddy-Solutions": 3,
"Kaiterra": 1,
"Milesight": 4,
"Moko": 1,
"QingPing": 1,
"Senso8": 9,
"Thermokon": 1
},
"withTests": 6,
"withTests": 7,
"withoutTests": 22
}
}