Skip to content

Commit 07257e7

Browse files
add: AT Module (#20)
* add: PSM EG95M3 example. * update: rename psm example file name. * update: psm BG95M3 example code note. * delete: psm_EG95M3.py * add: at module.
1 parent 61216d8 commit 07257e7

File tree

1 file changed

+259
-0
lines changed

1 file changed

+259
-0
lines changed

ATModule/at.py

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
# Copyright (c) Quectel Wireless Solution, Co., Ltd.All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""
16+
@file : at.py
17+
@author : Jack Sun (jack.sun@quectel.com)
18+
@brief : <Description>
19+
@version : v1.0.0
20+
@date : 2025-02-17 14:12:29
21+
@copyright : Copyright (c) 2025
22+
"""
23+
24+
import ure
25+
import sys
26+
import modem
27+
import osTimer
28+
import _thread
29+
import log as logging
30+
from machine import UART
31+
try:
32+
import atcmd
33+
except ImportError:
34+
atcmd = None
35+
36+
log = logging.getLogger(__name__)
37+
38+
CRLF = "\r\n"
39+
40+
41+
class ATServer:
42+
pattern = r"^(AT|at)((\+|\*)?[a-zA-Z]+)(=?)((.+),?)?$"
43+
44+
def __init__(self, UARTn, buadrate=115200, databits=8, parity=0, stopbits=1, flowctl=0, echo=True, stack_size=0x2000):
45+
self.__sem = _thread.allocate_semphore(0xFF)
46+
while self.__sem.getCnt()[1] > 0:
47+
self.__sem.acquire()
48+
self.__stop_sem = _thread.allocate_semphore(1)
49+
while self.__stop_sem.getCnt()[1] > 0:
50+
self.__stop_sem.acquire()
51+
self.__uart_args = (UARTn, buadrate, databits, parity, stopbits, flowctl)
52+
self.__uart = None
53+
self.__echo = echo
54+
self.cmd_table = {}
55+
self.__tid = None
56+
self.__stack_size = stack_size
57+
self._recv_data = ""
58+
self._recv_run = 0
59+
60+
def __recv_thread_start(self):
61+
if not self.__tid or (self.__tid and not _thread.threadIsRunning(self.__tid)):
62+
self._recv_run = 1
63+
_thread.stack_size(self.__stack_size)
64+
self.__tid = _thread.start_new_thread(self.__recv_cmd, ())
65+
66+
def __recv_thread_stop(self):
67+
if self.__tid and _thread.threadIsRunning(self.__tid):
68+
self._recv_run = 0
69+
if self.__sem.getCnt()[1] < self.__sem.getCnt()[0]:
70+
self.__sem.release()
71+
_timer = osTimer()
72+
_timer.start(5 * 1000, 0, self.__recv_stop_overtime)
73+
self.__stop_sem.acquire()
74+
_timer.stop()
75+
self.__tid = None
76+
77+
def __recv_stop_overtime(self, *args):
78+
try:
79+
if self.__stop_sem.getCnt(1) < self.__stop_sem.getCnt(0):
80+
self.__stop_sem.release()
81+
except Exception:
82+
pass
83+
84+
def __uart_callback(self, args):
85+
# log.debug("__uart_callback args %s" % str(args))
86+
if self.__sem.getCnt()[1] < self.__sem.getCnt()[0]:
87+
self.__sem.release()
88+
self.__recv_thread_start()
89+
90+
def __recv_cmd(self):
91+
while self._recv_run:
92+
try:
93+
if self.__uart.any() <= 0:
94+
self.__sem.acquire()
95+
96+
while self.__uart.any() > 0:
97+
self._recv_data += self.__uart.read(1024).decode()
98+
pos = self._recv_data.find(CRLF)
99+
if pos == -1:
100+
continue
101+
if self.__echo:
102+
self.reply(self._recv_data[:pos + 2])
103+
self.__parse(self._recv_data[:pos])
104+
self._recv_data = self._recv_data[pos + 2:]
105+
except Exception as e:
106+
sys.print_exception(e)
107+
log.error(str(e))
108+
self.__stop_sem.release()
109+
110+
def __cmd_resp(self, res):
111+
if res[1]:
112+
_msg_ = "%s%s" % ((CRLF if not self.__echo else ""), res[1])
113+
self.replyln(_msg_)
114+
if res[0] is True:
115+
self.replyln(("%sOK" % CRLF) if not res[1] and not self.__echo else "OK")
116+
elif not res[1]:
117+
self.replyln("ERROR" if self.__echo else "%sERROR" % CRLF)
118+
119+
def __parse(self, cmd_line):
120+
log.debug("cmd_line %s" % cmd_line)
121+
m = ure.match(self.pattern, cmd_line)
122+
if m:
123+
log.debug(", ".join(["m.group(%s) %s" % (i, m.group(i)) for i in range(6)]))
124+
cmd = m.group(2)
125+
cmd_obj = self.cmd_table.get(str(cmd).upper())
126+
if cmd_obj:
127+
if m.group(4) == "=":
128+
if cmd[0] in ("+", "*"):
129+
if m.group(5) == "?":
130+
log.debug("at cmd help")
131+
res = cmd_obj.help()
132+
self.__cmd_resp(res)
133+
else:
134+
log.debug("at cmd setup 1")
135+
param = m.group(5).split(",")
136+
res = cmd_obj.setup(*param)
137+
self.__cmd_resp(res)
138+
else:
139+
self.replyln("ERROR")
140+
else:
141+
if m.group(5) == "?":
142+
log.debug("at cmd query")
143+
res = cmd_obj.query()
144+
self.__cmd_resp(res)
145+
else:
146+
if m.group(5):
147+
log.debug("at cmd setup 2")
148+
param = m.group(5).split(",")
149+
res = cmd_obj.setup(*param)
150+
self.__cmd_resp(res)
151+
else:
152+
log.debug("at cmd exec")
153+
res = cmd_obj.exec()
154+
self.__cmd_resp(res)
155+
else:
156+
if atcmd:
157+
self.__iner_at_cmd(cmd_line)
158+
else:
159+
self.__cmd_resp((False, "ERROR: NOT SUPPORT CMD."))
160+
else:
161+
if cmd_line.lower() == "at":
162+
cmd_obj = self.cmd_table.get("AT")
163+
if cmd_obj:
164+
res = cmd_obj.query()
165+
self.__cmd_resp(res)
166+
else:
167+
if atcmd:
168+
self.__iner_at_cmd(cmd_line)
169+
else:
170+
self.__cmd_resp((False, "ERROR: NOT SUPPORT CMD."))
171+
else:
172+
self.__cmd_resp((False, "ERROR: AT CMD NOT COMPARE."))
173+
174+
def __iner_at_cmd(self, cmd_line):
175+
try:
176+
resp = bytearray(512)
177+
atcmd.sendSync(cmd_line + "\r\n", resp, "", 20)
178+
self.reply(resp.strip(b"\x00"))
179+
except Exception as e:
180+
sys.print_exception(e)
181+
182+
def open(self):
183+
self.__uart = UART(*self.__uart_args)
184+
self.__uart.set_callback(self.__uart_callback)
185+
self.__recv_thread_start()
186+
187+
def close(self):
188+
self.__uart.close()
189+
self.__recv_thread_stop()
190+
191+
def register(self, cmd_obj):
192+
self.cmd_table[cmd_obj.CMD] = cmd_obj
193+
194+
def reply(self, msg):
195+
self.__uart.write(msg)
196+
197+
def replyln(self, msg):
198+
self.reply(msg + CRLF)
199+
200+
def echo_enable(self, val):
201+
if isinstance(val, bool) or (isinstance(val, int) and val in (0, 1)):
202+
self.__echo = bool(val)
203+
return True
204+
return False
205+
206+
207+
class ATCMDBase:
208+
209+
def __init__(self):
210+
pass
211+
212+
def set_module(self, module, name):
213+
setattr(self, ("__%s" % name), module)
214+
215+
def help(self):
216+
return (False, "")
217+
218+
def query(self):
219+
return (False, "")
220+
221+
def setup(self, *args):
222+
return (False, "")
223+
224+
def exec(self):
225+
return (False, "")
226+
227+
228+
class ATIMEI(ATCMDBase):
229+
CMD = "+IMEI"
230+
231+
def __init__(self):
232+
super().__init__()
233+
234+
def query(self):
235+
try:
236+
return (True, "%s: %s%s" % (self.CMD, modem.getDevImei(), CRLF))
237+
except Exception as e:
238+
sys.print_exception(e)
239+
return (False, "")
240+
241+
242+
def test():
243+
at_server_cfg = {
244+
"UARTn": UART.UART2,
245+
"buadrate": 115200,
246+
"databits": 8,
247+
"parity": 0,
248+
"stopbits": 1,
249+
"flowctl": 0,
250+
"echo": True,
251+
"stack_size": 0x2000
252+
}
253+
at_server = ATServer(**at_server_cfg)
254+
at_server.register(ATIMEI()) # Register yourself at cmd.
255+
at_server.open()
256+
257+
258+
if __name__ == "__main__":
259+
test()

0 commit comments

Comments
 (0)