Skip to content

Commit 3f50926

Browse files
committed
lib/cmwx1: Add CMWX1ZZABZ-093 driver.
Signed-off-by: iabdalkader <[email protected]>
1 parent aca36f7 commit 3f50926

File tree

3 files changed

+385
-0
lines changed

3 files changed

+385
-0
lines changed

lib/cmwx1/__init__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# This file is part of the cmwx1 module.
2+
#
3+
# Copyright (c) 2013-2021 Ibrahim Abdelkader <[email protected]>
4+
# Copyright (c) 2013-2021 Sebastian Romero <[email protected]>
5+
# Copyright (c) 2021 Arduino SA
6+
#
7+
# This work is licensed under the MIT license, see the file LICENSE for details.
8+
#
9+
# CMWX1ZZABZ-093 driver for Arduino boards.
10+
# Note this module runs a stock or a custom Arduino firmware.
11+
12+
13+
from .cmwx1 import * # noqa

lib/cmwx1/cmwx1.py

Lines changed: 366 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,366 @@
1+
# This file is part of the cmwx1 module.
2+
#
3+
# Copyright (c) 2013-2021 Ibrahim Abdelkader <[email protected]>
4+
# Copyright (c) 2013-2021 Sebastian Romero <[email protected]>
5+
# Copyright (c) 2021 Arduino SA
6+
#
7+
# This work is licensed under the MIT license, see the file LICENSE for details.
8+
#
9+
# CMWX1ZZABZ-093 driver for Arduino boards.
10+
# Note this module runs a stock or a custom Arduino firmware.
11+
12+
from time import sleep_ms
13+
from time import ticks_ms
14+
from machine import UART
15+
from machine import Pin
16+
from micropython import const
17+
18+
class LoraError(Exception):
19+
pass
20+
21+
22+
class LoraErrorTimeout(LoraError):
23+
pass
24+
25+
26+
class LoraErrorParam(LoraError):
27+
pass
28+
29+
30+
class LoraErrorBusy(LoraError):
31+
pass
32+
33+
34+
class LoraErrorOverflow(LoraError):
35+
pass
36+
37+
38+
class LoraErrorNoNetwork(LoraError):
39+
pass
40+
41+
42+
class LoraErrorRX(LoraError):
43+
pass
44+
45+
46+
class LoraErrorUnknown(LoraError):
47+
pass
48+
49+
50+
class Lora:
51+
MODE_ABP = const(0)
52+
MODE_OTAA = const(1)
53+
54+
CLASS_A = const("A")
55+
CLASS_B = const("B")
56+
CLASS_C = const("C")
57+
58+
BAND_AS923 = const(0)
59+
BAND_AU915 = const(1)
60+
BAND_EU868 = const(5)
61+
BAND_KR920 = const(6)
62+
BAND_IN865 = const(7)
63+
BAND_US915 = const(8)
64+
BAND_US915_HYBRID = const(9)
65+
66+
RF_MODE_RFO = const(0)
67+
RF_MODE_PABOOST = const(1)
68+
69+
LoraErrors = {
70+
"": LoraErrorTimeout, # empty buffer
71+
"+ERR": LoraError,
72+
"+ERR_PARAM": LoraErrorParam,
73+
"+ERR_BUSY": LoraErrorBusy,
74+
"+ERR_PARAM_OVERFLOW": LoraErrorOverflow,
75+
"+ERR_NO_NETWORK": LoraErrorNoNetwork,
76+
"+ERR_RX": LoraErrorRX,
77+
"+ERR_UNKNOWN": LoraErrorUnknown,
78+
}
79+
80+
def __init__(
81+
self,
82+
uart=None,
83+
rst_pin=None,
84+
boot_pin=None,
85+
band=5,
86+
poll_ms=300000,
87+
debug=False,
88+
):
89+
self.debug = debug
90+
self.uart = uart
91+
self.rst_pin = rst_pin
92+
self.boot_pin = boot_pin
93+
self.band = band
94+
self.poll_ms = poll_ms
95+
self.last_poll_ms = ticks_ms()
96+
97+
self.init_modem()
98+
99+
# Reset module
100+
self.boot_pin.value(0)
101+
self.rst_pin.value(1)
102+
sleep_ms(200)
103+
self.rst_pin.value(0)
104+
sleep_ms(200)
105+
self.rst_pin.value(1)
106+
107+
# Restart module
108+
self.restart()
109+
110+
def init_modem(self):
111+
# Arduino Portenta H7 Pin Configuration
112+
if not self.rst_pin:
113+
self.rst_pin = Pin("PC6", Pin.OUT_PP, Pin.PULL_UP, value=1)
114+
if not self.boot_pin:
115+
self.boot_pin = Pin("PG7", Pin.OUT_PP, Pin.PULL_DOWN, value=0)
116+
if not self.uart:
117+
self.uart = UART(8, 19200)
118+
# self.uart = UART(1, 19200) # Use external module
119+
self.uart.init(
120+
19200, bits=8, parity=None, stop=2, timeout=250, timeout_char=100
121+
)
122+
123+
def debug_print(self, data):
124+
if self.debug:
125+
print(data)
126+
127+
def is_arduino_firmware(self):
128+
return "ARD-078" in self.fw_version
129+
130+
def configure_class(self, _class):
131+
self.send_command("+CLASS=", _class)
132+
133+
def configure_band(self, band):
134+
self.send_command("+BAND=", band)
135+
if band == self.BAND_EU868 and self.is_arduino_firmware():
136+
self.send_command("+DUTYCYCLE=", 1)
137+
return True
138+
139+
def set_baudrate(self, baudrate):
140+
self.send_command("+UART=", baudrate)
141+
142+
def set_autobaud(self, timeout=10000):
143+
start = ticks_ms()
144+
while (ticks_ms() - start) < timeout:
145+
if self.send_command("", timeout=200, raise_error=False) == "+OK":
146+
sleep_ms(200)
147+
while self.uart.any():
148+
self.uart.readchar()
149+
return True
150+
return False
151+
152+
def get_fw_version(self):
153+
dev = self.send_command("+DEV?")
154+
fw_ver = self.send_command("+VER?")
155+
return dev + " " + fw_ver
156+
157+
def get_device_eui(self):
158+
return self.send_command("+DEVEUI?")
159+
160+
def factory_default(self):
161+
self.send_command("+FACNEW")
162+
163+
def restart(self):
164+
if self.set_autobaud() is False:
165+
raise (LoraError("Failed to set autobaud"))
166+
167+
# Different delimiter as REBOOT response EVENT doesn't end with '\r'.
168+
if self.send_command("+REBOOT", delimiter="+EVENT=0,0", timeout=10000) != "+EVENT=0,0":
169+
raise (LoraError("Failed to reboot module"))
170+
sleep_ms(1000)
171+
self.fw_version = self.get_fw_version()
172+
self.configure_band(self.band)
173+
174+
def set_rf_power(self, mode, power):
175+
self.send_command("+RFPOWER=", mode, ",", power)
176+
177+
def set_port(self, port):
178+
self.send_command("+PORT=", port)
179+
180+
def set_public_network(self, enable):
181+
self.send_command("+NWK=", int(enable))
182+
183+
def sleep(self, enable):
184+
self.send_command("+SLEEP=", int(enable))
185+
186+
def format(self, hexMode):
187+
self.send_command("+DFORMAT=", int(hexMode))
188+
189+
def set_datarate(self, dr):
190+
self.send_command("+DR=", dr)
191+
192+
def get_datarate(self):
193+
return int(self.send_command("+DR?"))
194+
195+
def set_adr(self, adr):
196+
self.send_command("+ADR=", int(adr))
197+
198+
def get_adr(self):
199+
return int(self.send_command("+ADR?"))
200+
201+
def get_devaddr(self):
202+
return self.send_command("+DEVADDR?")
203+
204+
def get_nwk_skey(self):
205+
return self.send_command("+NWKSKEY?")
206+
207+
def get_appskey(self):
208+
return self.send_command("+APPSKEY?")
209+
210+
def get_rx2dr(self):
211+
return int(self.send_command("+RX2DR?"))
212+
213+
def set_rx2dr(self, dr):
214+
self.send_command("+RX2DR=", dr)
215+
216+
def get_ex2freq(self):
217+
return int(self.send_command("+RX2FQ?"))
218+
219+
def set_rx2freq(self, freq):
220+
self.send_command("+RX2FQ=", freq)
221+
222+
def set_fcu(self, fcu):
223+
self.send_command("+FCU=", fcu)
224+
225+
def get_fcu(self):
226+
return int(self.send_command("+FCU?"))
227+
228+
def set_fcd(self, fcd):
229+
self.send_command("+FCD=", fcd)
230+
231+
def get_fcd(self):
232+
return int(self.send_command("+FCD?"))
233+
234+
def change_mode(self, mode):
235+
self.send_command("+MODE=", mode)
236+
237+
def join(self, timeout_ms):
238+
if self.send_command("+JOIN", timeout=timeout_ms) != "+ACK":
239+
return False
240+
response = self.receive("\r", timeout=timeout_ms)
241+
return response == "+EVENT=1,1"
242+
243+
def get_join_status(self):
244+
return int(self.send_command("+NJS?")) == 1
245+
246+
def get_max_size(self):
247+
if self.is_arduino_firmware():
248+
return 64
249+
return int(self.send_command("+MSIZE?", timeout=2000))
250+
251+
def poll(self):
252+
if (ticks_ms() - self.last_poll_ms) > self.poll_ms:
253+
self.last_poll_ms = ticks_ms()
254+
# Triggers a fake write
255+
self.send_data("\0", True)
256+
257+
def send_data(self, buff, confirmed=True):
258+
max_len = self.get_max_size()
259+
if len(buff) > max_len:
260+
raise (LoraError("Packet exceeds max length"))
261+
if self.send_command("+CTX " if confirmed else "+UTX ", len(buff), data=buff) != "+OK":
262+
return False
263+
if confirmed:
264+
response = self.receive("\r", timeout=10000)
265+
return response == "+ACK"
266+
return True
267+
268+
def receive_data(self, timeout=1000):
269+
response = self.receive("\r", timeout=timeout)
270+
if response.startswith("+RECV"):
271+
params = response.split("=")[1].split(",")
272+
port = params[0]
273+
length = int(params[1])
274+
dummy_data_length = 2 # Data starts with \n\n sequence
275+
data = self.receive(max_bytes=length + dummy_data_length, timeout=timeout)[
276+
dummy_data_length:
277+
]
278+
return {"port": port, "data": data}
279+
280+
def receive(self, delimiter=None, max_bytes=None, timeout=1000):
281+
buf = []
282+
start = ticks_ms()
283+
while (ticks_ms() - start) < timeout:
284+
while self.uart.any():
285+
buf += chr(self.uart.readchar())
286+
287+
if max_bytes and len(buf) == max_bytes:
288+
data = "".join(buf)
289+
self.debug_print(data)
290+
return data
291+
if len(buf) and delimiter is not None:
292+
data = "".join(buf)
293+
trimmed = data[0:-1] if data[-1] == "\r" else data
294+
295+
if isinstance(delimiter, str) and len(delimiter) == 1 and buf[-1] == delimiter:
296+
self.debug_print(trimmed)
297+
return trimmed
298+
if isinstance(delimiter, str) and trimmed == delimiter:
299+
self.debug_print(trimmed)
300+
return trimmed
301+
if isinstance(delimiter, list) and trimmed in delimiter:
302+
self.debug_print(trimmed)
303+
return trimmed
304+
305+
data = "".join(buf)
306+
self.debug_print(data)
307+
return data[0:-1] if len(data) != 0 and data[-1] == "\r" else data
308+
309+
def available(self):
310+
return self.uart.any()
311+
312+
def join_OTAA(self, appEui, appKey, devEui=None, timeout=60000):
313+
self.change_mode(self.MODE_OTAA)
314+
self.send_command("+APPEUI=", appEui)
315+
self.send_command("+APPKEY=", appKey)
316+
if devEui:
317+
self.send_command("+DEVEUI=", devEui)
318+
network_joined = self.join(timeout)
319+
# This delay was in MKRWAN.h
320+
# delay(1000);
321+
return network_joined
322+
323+
def join_ABP(self, nwkId, devAddr, nwkSKey, appSKey, timeout=60000):
324+
self.change_mode(self.MODE_ABP)
325+
# Commented in MKRWAN.h
326+
# self.send_command("+IDNWK=", nwkId)
327+
self.send_command("+DEVADDR=", devAddr)
328+
self.send_command("+NWKSKEY=", nwkSKey)
329+
self.send_command("+APPSKEY=", appSKey)
330+
self.join(timeout)
331+
return self.get_join_status()
332+
333+
def handle_error(self, command, data):
334+
if not data.startswith("+ERR") and data != "":
335+
return
336+
if data in self.LoraErrors:
337+
raise (self.LoraErrors[data]('Command "%s" has failed!' % command))
338+
raise (
339+
LoraError(
340+
'Command: "%s" failed with unknown status: "%s"' % (command, data)
341+
)
342+
)
343+
344+
def send_command(
345+
self, cmd, *args, delimiter="\r", data=None, timeout=1000, raise_error=True
346+
):
347+
# Write command and args
348+
uart_cmd = "AT" + cmd + "".join([str(x) for x in args]) + "\r"
349+
self.debug_print(uart_cmd)
350+
self.uart.write(uart_cmd)
351+
352+
# Attach raw data
353+
if data:
354+
self.debug_print(data)
355+
self.uart.write(data)
356+
357+
# Read status and value (if any)
358+
response = self.receive(delimiter, timeout=timeout)
359+
360+
# Error handling
361+
if raise_error:
362+
self.handle_error(cmd, response)
363+
364+
if cmd.endswith("?"):
365+
return response.split("=")[1]
366+
return response

lib/cmwx1/manifest.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
metadata(
2+
description="CMWX1ZZABZ-093 driver for Arduino boards.",
3+
version="0.0.1",
4+
)
5+
6+
package("cmwx1")

0 commit comments

Comments
 (0)