Skip to content

Commit 37ebd74

Browse files
authored
mokosmart: Add LW005-MP device profile (#21)
1 parent 132ba6f commit 37ebd74

9 files changed

Lines changed: 414 additions & 0 deletions

File tree

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
//LW005-MP Payload Decoder rule
2+
//Creation time:2022-07-20
3+
//Creator:yujiahang
4+
//Suitable firmware versions:LW005-MP V1.X.X
5+
//Programming languages:Javascript
6+
//Suitable platforms:Chirpstack
7+
var payloadTypeArray = [
8+
"Switch"
9+
, "Electrical"
10+
, "Electrical"
11+
, "Energy"
12+
, "Over-voltage"
13+
, "Sag-voltage"
14+
, "Over-current"
15+
, "Over-load"
16+
, "Load state"
17+
, "Countdown"
18+
];
19+
// Decode uplink function.
20+
//
21+
// Input is an object with the following fields:
22+
// - bytes = Byte array containing the uplink payload, e.g. [255, 230, 255, 0]
23+
// - fPort = Uplink fPort.
24+
// - variables = Object containing the configured device variables.
25+
//
26+
// Output must be an object with the following fields:
27+
// - data = Object representing the decoded payload.
28+
function decodeUplink(input) {
29+
var bytes = input.bytes;
30+
var fPort = input.fPort;
31+
var deviceInfo = {};
32+
var data = {};
33+
if (fPort == 0) {
34+
deviceInfo.data = data;
35+
return deviceInfo;
36+
}
37+
38+
data.port = fPort;
39+
data.hex_format_payload = bytesToHexString(bytes, 0, bytes.length);
40+
data.payload_type = payloadTypeArray[fPort - 5];
41+
if (command_format_check(bytes, fPort) == false) {
42+
data.result = "Format wrong";
43+
deviceInfo.data = data;
44+
return deviceInfo;
45+
}
46+
var timestamp = bytesToInt(bytes, 0, 4);
47+
if (timestamp < 1000000000){
48+
data.result = "Timestamp wrong";
49+
deviceInfo.data = data;
50+
return deviceInfo;
51+
}
52+
data.time = parse_time(timestamp, bytes[4] * 0.5);
53+
data.timestamp = timestamp;
54+
data.timezone = signedHexToInt(bytesToHexString(bytes, 4, 1)) / 2;
55+
switch (fPort) {
56+
case 5:
57+
data.ac_output_state = bytes[5] == 1 ? "ON" : "OFF";
58+
data.plug_load_status = bytes[6] == 1 ? "There is a load on the plug" : "No load on the plug";
59+
break;
60+
case 6:
61+
data.instantaneous_voltage = bytesToInt(bytes, 5, 2) / 10 + "V";
62+
data.instantaneous_current = bytesToInt(bytes, 7, 2) / 1000 + "A";
63+
data.instantaneous_current_frequency = bytesToInt(bytes, 9, 2) / 1000 + "HZ";
64+
break;
65+
case 7:
66+
data.instantaneous_active_power = bytesToInt(bytes, 5, 4) / 10 + "W";
67+
data.instantaneous_power_factor = (bytes[9] & 0xFF) + "%";
68+
break;
69+
case 8:
70+
data.total_energy = Number(bytesToInt(bytes, 5, 4) / 3200).toFixed(2) + "KWH";
71+
data.energy_of_last_hour = Number(bytesToInt(bytes, 9, 2) / 3200).toFixed(2) + "KWH";
72+
break;
73+
case 9:
74+
data.over_voltage_state = bytes[5];
75+
data.current_instantaneous_voltage = bytesToInt(bytes, 6, 2) / 10 + "V";
76+
data.over_voltage_threshold = bytesToInt(bytes, 8, 2) / 10 + "V";
77+
break;
78+
case 10:
79+
data.sag_voltage_state = bytes[5];
80+
data.current_instantaneous_voltage = bytesToInt(bytes, 6, 2) / 10 + "V";
81+
data.sag_voltage_threshold = bytesToInt(bytes, 8, 2) / 10 + "V";
82+
break;
83+
case 11:
84+
data.over_current_state = bytes[5];
85+
data.current_instantaneous_current = parse_int16(bytesToInt(bytes, 6, 2)) / 1000 + "A";
86+
data.over_current_threshold = bytesToInt(bytes, 8, 2) / 1000 + "A";
87+
break;
88+
case 12:
89+
data.over_load_state = bytes[5];
90+
data.current_instantaneous_power = parse_int24(bytesToInt(bytes, 6, 3)) / 10 + "W";
91+
data.over_load_threshold = bytesToInt(bytes, 9, 2) / 10 + "W";
92+
break;
93+
case 13:
94+
data.load_change_state = bytes[5] == 1 ? "load starts working" : "load starts stop";
95+
break;
96+
case 14:
97+
data.ac_output_state_after_countdown = bytes[5] == 1 ? "ON" : "OFF";;
98+
data.remaining_time_of_countdown_process = bytesToInt(bytes, 6, 4) + "s";
99+
break;
100+
default:
101+
break;
102+
}
103+
deviceInfo.data = data;
104+
return deviceInfo;
105+
}
106+
107+
function command_format_check(bytes, port) {
108+
if (port >= 1 || port <= 4) {
109+
return true;
110+
}
111+
switch (port) {
112+
case 5:
113+
if (bytes.length === 7)
114+
return true;
115+
break;
116+
117+
case 6:
118+
if (bytes.length === 11)
119+
return true;
120+
break;
121+
122+
case 7:
123+
if (bytes.length === 10)
124+
return true;
125+
break;
126+
127+
case 8:
128+
if (bytes.length === 11)
129+
return true;
130+
break;
131+
132+
case 9:
133+
if (bytes.length === 10)
134+
return true;
135+
break;
136+
137+
case 10:
138+
if (bytes.length === 10)
139+
return true;
140+
break;
141+
142+
case 11:
143+
if (bytes.length === 10)
144+
return true;
145+
break;
146+
147+
case 12:
148+
if (bytes.length === 11)
149+
return true;
150+
break;
151+
152+
case 13:
153+
if (bytes.length === 6)
154+
return true;
155+
break;
156+
157+
case 14:
158+
if (bytes.length === 10)
159+
return true;
160+
break;
161+
162+
default:
163+
break;
164+
}
165+
166+
return false;
167+
}
168+
169+
function bytesToHexString(bytes, start, len) {
170+
var char = [];
171+
for (var i = 0; i < len; i++) {
172+
var data = bytes[start + i].toString(16);
173+
var dataHexStr = ("0x" + data) < 0x10 ? ("0" + data) : data;
174+
char.push(dataHexStr);
175+
}
176+
return char.join("");
177+
}
178+
179+
function parse_int16(num) {
180+
if (num & 0x8000)
181+
return (num - 0x10000);
182+
else
183+
return num;
184+
}
185+
186+
function parse_int24(num) {
187+
if (num & 0x800000)
188+
return (num - 0x1000000);
189+
else
190+
return num;
191+
}
192+
193+
194+
function parse_time(timestamp, timezone) {
195+
timezone = timezone > 64 ? timezone - 128 : timezone;
196+
timestamp = timestamp + timezone * 3600;
197+
if (timestamp < 0) {
198+
timestamp = 0;
199+
}
200+
201+
var d = new Date(timestamp * 1000);
202+
//d.setUTCSeconds(1660202724);
203+
204+
var time_str = "";
205+
time_str += d.getUTCFullYear();
206+
time_str += "-";
207+
time_str += formatNumber(d.getUTCMonth() + 1);
208+
time_str += "-";
209+
time_str += formatNumber(d.getUTCDate());
210+
time_str += " ";
211+
212+
time_str += formatNumber(d.getUTCHours());
213+
time_str += ":";
214+
time_str += formatNumber(d.getUTCMinutes());
215+
time_str += ":";
216+
time_str += formatNumber(d.getUTCSeconds());
217+
218+
return time_str;
219+
}
220+
221+
function formatNumber(number) {
222+
return number < 10 ? "0" + number : number;
223+
}
224+
225+
function signedHexToInt(hexStr) {
226+
var twoStr = parseInt(hexStr, 16).toString(2); // 将十六转十进制,再转2进制
227+
var bitNum = hexStr.length * 4; // 1个字节 = 8bit ,0xff 一个 "f"就是4位
228+
if (twoStr.length < bitNum) {
229+
while (twoStr.length < bitNum) {
230+
twoStr = "0" + twoStr;
231+
}
232+
}
233+
if (twoStr.substring(0, 1) == "0") {
234+
// 正数
235+
twoStr = parseInt(twoStr, 2); // 二进制转十进制
236+
return twoStr;
237+
}
238+
// 负数
239+
var twoStr_unsign = "";
240+
twoStr = parseInt(twoStr, 2) - 1; // 补码:(负数)反码+1,符号位不变;相对十进制来说也是 +1,但这里是负数,+1就是绝对值数据-1
241+
twoStr = twoStr.toString(2);
242+
twoStr_unsign = twoStr.substring(1, bitNum); // 舍弃首位(符号位)
243+
// 去除首字符,将0转为1,将1转为0 反码
244+
twoStr_unsign = twoStr_unsign.replace(/0/g, "z");
245+
twoStr_unsign = twoStr_unsign.replace(/1/g, "0");
246+
twoStr_unsign = twoStr_unsign.replace(/z/g, "1");
247+
twoStr = -parseInt(twoStr_unsign, 2);
248+
return twoStr;
249+
}
250+
251+
function bytesToInt(bytes, start, len) {
252+
var value = 0;
253+
for (var i = 0; i < len; i++) {
254+
var m = ((len - 1) - i) * 8;
255+
value = value | bytes[start + i] << m;
256+
}
257+
// var value = ((bytes[start] << 24) | (bytes[start + 1] << 16) | (bytes[start + 2] << 8) | (bytes[start + 3]));
258+
return value;
259+
}
260+
261+
// function getData(hex) {
262+
// var length = hex.length;
263+
// var datas = [];
264+
// for (var i = 0; i < length; i += 2) {
265+
// var start = i;
266+
// var end = i + 2;
267+
// var data = parseInt("0x" + hex.substring(start, end));
268+
// datas.push(data);
269+
// }
270+
// return datas;
271+
// }
272+
273+
// var input = {};
274+
// input.fPort = 8;
275+
// input.bytes = getData("0000000010000216eb0000");
276+
// console.log(decodeUplink(input));
277+
278+
//deviceInfo = Decoder([0x62, 0xF4, 0xBA, 0xDA, 0x10, 0x00, 0x00], 5);
279+
//deviceInfo = Decoder([0x61, 0xAD, 0x6C, 0x62, 0x10, 0x09, 0x2D, 0xF2, 0x0F, 0xC3, 0x65], 6);
280+
//deviceInfo = Decoder([0x61, 0xAD, 0x6C, 0x62, 0x10, 0x00, 0x00, 0x78, 0xF9, 0x26], 7);
281+
// deviceInfo = Decoder([0x61, 0xAD, 0x6C, 0x44, 0x10, 0x00, 0xB4, 0x1F, 0x3F, 0x01, 0x67], 8);
282+
// console.log(deviceInfo);
283+
// console.log(parse_time(1660202724, 0));
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[device]
2+
id = "f2b69399-969c-45e6-bef4-898e74332fd2"
3+
name = "LW005-MP"
4+
description = "Smart plug with power and energy monitoring"
5+
6+
[[device.firmware]]
7+
version = "1.0"
8+
profiles = [
9+
"EU868-1_0_3.toml",
10+
"AU915-1_0_3.toml",
11+
"AS923-1_0_3.toml",
12+
"US915-1_0_3.toml",
13+
]
14+
codec = "lw005-mp.js"
15+
16+
[device.metadata]
17+
product_url = "https://www.mokosmart.com/lorawan-meter-plug-lw005-mp/"
18+
documentation_url = "https://www.mokosmart.com/lorawan-meter-plug-lw005-mp/"
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[profile]
2+
id = "a36dade4-89e5-4aee-9c16-a2d7d8403c12"
3+
vendor_profile_id = 0
4+
region = "AS923"
5+
mac_version = "1.0.3"
6+
reg_params_revision = "A"
7+
supports_otaa = true
8+
supports_class_b = false
9+
supports_class_c = false
10+
max_eirp = 16
11+
12+
[profile.abp]
13+
rx1_delay = 0
14+
rx1_dr_offset = 0
15+
rx2_dr = 0
16+
rx2_freq = 0
17+
18+
[profile.class_b]
19+
timeout_secs = 0
20+
ping_slot_nb_k = 0
21+
ping_slot_dr = 0
22+
ping_slot_freq = 0
23+
24+
[profile.class_c]
25+
timeout_secs = 0
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[profile]
2+
id = "9067cb54-7f52-4f37-85d1-851c1e96c4e6"
3+
vendor_profile_id = 0
4+
region = "AU915"
5+
mac_version = "1.0.3"
6+
reg_params_revision = "A"
7+
supports_otaa = true
8+
supports_class_b = false
9+
supports_class_c = false
10+
max_eirp = 30
11+
12+
[profile.abp]
13+
rx1_delay = 0
14+
rx1_dr_offset = 0
15+
rx2_dr = 0
16+
rx2_freq = 0
17+
18+
[profile.class_b]
19+
timeout_secs = 0
20+
ping_slot_nb_k = 0
21+
ping_slot_dr = 0
22+
ping_slot_freq = 0
23+
24+
[profile.class_c]
25+
timeout_secs = 0
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[profile]
2+
id = "03d8d816-bf65-42d1-bccf-b3a5451f41b0"
3+
vendor_profile_id = 0
4+
region = "EU868"
5+
mac_version = "1.0.3"
6+
reg_params_revision = "A"
7+
supports_otaa = true
8+
supports_class_b = false
9+
supports_class_c = false
10+
max_eirp = 16
11+
12+
[profile.abp]
13+
rx1_delay = 0
14+
rx1_dr_offset = 0
15+
rx2_dr = 0
16+
rx2_freq = 0
17+
18+
[profile.class_b]
19+
timeout_secs = 0
20+
ping_slot_nb_k = 0
21+
ping_slot_dr = 0
22+
ping_slot_freq = 0
23+
24+
[profile.class_c]
25+
timeout_secs = 0

0 commit comments

Comments
 (0)