Skip to content

Commit 3d504ca

Browse files
committed
lib/cmwx1: Add CMWX1ZZABZ-093 driver.
Signed-off-by: iabdalkader <i.abdalkader@gmail.com>
1 parent aca36f7 commit 3d504ca

File tree

2 files changed

+370
-0
lines changed

2 files changed

+370
-0
lines changed

lib/cmwx1/cmwx1.py

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

lib/cmwx1/manifest.py

+6
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+
module("cmwx1.py")

0 commit comments

Comments
 (0)