Skip to content

Commit c55c129

Browse files
committed
init
0 parents  commit c55c129

File tree

9 files changed

+1516
-0
lines changed

9 files changed

+1516
-0
lines changed

README.md

Whitespace-only changes.

src/audio_gain.nvm

2.09 KB
Binary file not shown.

src/audio_ve.nvm

5.26 KB
Binary file not shown.

src/logging.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import utime
2+
import sys
3+
import uio as io
4+
import _thread
5+
6+
7+
class Level(object):
8+
DEBUG = 0
9+
INFO = 1
10+
WARN = 2
11+
ERROR = 3
12+
CRITICAL = 4
13+
14+
15+
_levelToName = {
16+
Level.CRITICAL: "CRITICAL",
17+
Level.ERROR: "ERROR",
18+
Level.WARN: "WARN",
19+
Level.INFO: "INFO",
20+
Level.DEBUG: "DEBUG"
21+
}
22+
23+
_nameToLevel = {
24+
"CRITICAL": Level.CRITICAL,
25+
"ERROR": Level.ERROR,
26+
"WARN": Level.WARN,
27+
"INFO": Level.INFO,
28+
"DEBUG": Level.DEBUG,
29+
}
30+
31+
32+
def getLevelName(level):
33+
if level not in _levelToName:
34+
raise ValueError("unknown level \"{}\", choose from <class Level>.".format(level))
35+
return _levelToName[level]
36+
37+
38+
def getNameLevel(name):
39+
temp = name.upper()
40+
if temp not in _nameToLevel:
41+
raise ValueError("\"{}\" is not valid. choose from [{}]".format(name, list(_nameToLevel.keys())))
42+
return _nameToLevel[temp]
43+
44+
45+
class BasicConfig(object):
46+
logger_register_table = {}
47+
basic_configure = {
48+
"level": Level.WARN,
49+
"debug": True,
50+
"stream": sys.stdout
51+
}
52+
53+
@classmethod
54+
def getLogger(cls, name):
55+
if name not in cls.logger_register_table:
56+
logger = Logger(name)
57+
cls.logger_register_table[name] = logger
58+
else:
59+
logger = cls.logger_register_table[name]
60+
return logger
61+
62+
@classmethod
63+
def update(cls, **kwargs):
64+
level = kwargs.pop("level", None)
65+
if level is not None:
66+
kwargs["level"] = getNameLevel(level)
67+
return cls.basic_configure.update(kwargs)
68+
69+
@classmethod
70+
def get(cls, key):
71+
return cls.basic_configure[key]
72+
73+
@classmethod
74+
def set(cls, key, value):
75+
if key == "level":
76+
value = getNameLevel(value)
77+
cls.basic_configure[key] = value
78+
79+
80+
class Logger(object):
81+
lock = _thread.allocate_lock()
82+
83+
def __init__(self, name):
84+
self.name = name
85+
86+
@staticmethod
87+
def __getFormattedTime():
88+
# (2023, 9, 30, 11, 11, 41, 5, 273)
89+
cur_time_tuple = utime.localtime()
90+
return "{:04d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}".format(
91+
cur_time_tuple[0],
92+
cur_time_tuple[1],
93+
cur_time_tuple[2],
94+
cur_time_tuple[3],
95+
cur_time_tuple[4],
96+
cur_time_tuple[5]
97+
)
98+
99+
def log(self, level, *message):
100+
if not BasicConfig.get("debug"):
101+
if level < BasicConfig.get("level"):
102+
return
103+
stream = BasicConfig.get("stream")
104+
prefix = "[{}][{}][{}]".format(
105+
self.__getFormattedTime(),
106+
getLevelName(level),
107+
self.name,
108+
)
109+
with self.lock:
110+
print(prefix, *message, file=stream)
111+
if isinstance(stream, io.TextIOWrapper):
112+
stream.flush()
113+
114+
def debug(self, *message):
115+
self.log(Level.DEBUG, *message)
116+
117+
def info(self, *message):
118+
self.log(Level.INFO, *message)
119+
120+
def warn(self, *message):
121+
self.log(Level.WARN, *message)
122+
123+
def error(self, *message):
124+
self.log(Level.ERROR, *message)
125+
126+
def critical(self, *message):
127+
self.log(Level.CRITICAL, *message)
128+
129+
130+
def getLogger(name):
131+
return BasicConfig.getLogger(name)

src/main.py

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
"""
2+
提高 CPU 主频: AT+LOG=17,5
3+
"""
4+
import utime
5+
from machine import ExtInt, Pin
6+
from usr.protocol import WebSocketClient
7+
from usr.utils import ChargeManager, AudioManager, NetManager, TaskManager
8+
from usr.threading import Thread, Event, Condition
9+
from usr.logging import getLogger
10+
11+
12+
logger = getLogger(__name__)
13+
14+
15+
class Led(object):
16+
17+
def __init__(self, GPIOn):
18+
self.__led = Pin(
19+
getattr(Pin, 'GPIO{}'.format(GPIOn)),
20+
Pin.OUT,
21+
Pin.PULL_PD,
22+
0
23+
)
24+
self.__off_period = 1000
25+
self.__on_period = 1000
26+
self.__count = 0
27+
self.__running_cond = Condition()
28+
self.__blink_thread = None
29+
self.off()
30+
31+
@property
32+
def status(self):
33+
with self.__running_cond:
34+
return self.__led.read()
35+
36+
def on(self):
37+
with self.__running_cond:
38+
self.__count = 0
39+
return self.__led.write(0)
40+
41+
def off(self):
42+
with self.__running_cond:
43+
self.__count = 0
44+
return self.__led.write(1)
45+
46+
def blink(self, on_period=50, off_period=50, count=None):
47+
if not isinstance(count, (int, type(None))):
48+
raise TypeError('count must be int or None type')
49+
with self.__running_cond:
50+
if self.__blink_thread is None:
51+
self.__blink_thread = Thread(target=self.__blink_thread_worker)
52+
self.__blink_thread.start()
53+
self.__on_period = on_period
54+
self.__off_period = off_period
55+
self.__count = count
56+
self.__running_cond.notify_all()
57+
58+
def __blink_thread_worker(self):
59+
while True:
60+
with self.__running_cond:
61+
if self.__count is not None:
62+
self.__running_cond.wait_for(lambda: self.__count is None or self.__count > 0)
63+
status = self.__led.read()
64+
self.__led.write(1 - status)
65+
utime.sleep_ms(self.__on_period if status else self.__off_period)
66+
self.__led.write(status)
67+
utime.sleep_ms(self.__on_period if status else self.__off_period)
68+
if self.__count is not None:
69+
self.__count -= 1
70+
71+
72+
73+
class Application(object):
74+
75+
def __init__(self):
76+
# 初始化唤醒按键
77+
self.talk_key = ExtInt(ExtInt.GPIO19, ExtInt.IRQ_RISING_FALLING, ExtInt.PULL_PU, self.on_talk_key_click, 50)
78+
79+
# 初始化 led; write(1) 灭; write(0) 亮
80+
self.wifi_red_led = Led(33)
81+
self.wifi_green_led = Led(32)
82+
self.power_red_led = Led(39)
83+
self.power_green_led = Led(38)
84+
self.lte_red_led = Led(23)
85+
self.lte_green_led = Led(24)
86+
self.led_power_pin = Pin(Pin.GPIO27, Pin.OUT, Pin.PULL_DISABLE, 0)
87+
88+
# 初始化充电管理
89+
self.charge_manager = ChargeManager()
90+
91+
# 初始化音频管理
92+
self.audio_manager = AudioManager()
93+
self.audio_manager.set_kws_cb(self.on_keyword_spotting)
94+
self.audio_manager.set_vad_cb(self.on_voice_activity_detection)
95+
96+
# 初始化网络管理
97+
self.net_manager = NetManager()
98+
99+
# 初始化任务调度器
100+
self.task_manager = TaskManager()
101+
102+
# 初始化协议
103+
self.__protocol = WebSocketClient()
104+
self.__protocol.set_callback(
105+
audio_message_handler=self.on_audio_message,
106+
json_message_handler=self.on_json_message
107+
)
108+
109+
self.__working_thread = None
110+
self.__record_thread = None
111+
self.__record_thread_stop_event = Event()
112+
self.__voice_activity_event = Event()
113+
self.__keyword_spotting_event = Event()
114+
115+
def __record_thread_handler(self):
116+
"""纯粹是为了kws&vad能识别才起的线程持续读音频"""
117+
logger.debug("record thread handler enter")
118+
while not self.__record_thread_stop_event.is_set():
119+
self.audio_manager.opus_read()
120+
utime.sleep_ms(5)
121+
logger.debug("record thread handler exit")
122+
123+
def start_kws(self):
124+
self.audio_manager.start_kws()
125+
self.__record_thread_stop_event.clear()
126+
self.__record_thread = Thread(target=self.__record_thread_handler)
127+
self.__record_thread.start(stack_size=64)
128+
129+
def stop_kws(self):
130+
self.__record_thread_stop_event.set()
131+
self.__record_thread.join()
132+
self.audio_manager.stop_kws()
133+
134+
def start_vad(self):
135+
self.audio_manager.start_vad()
136+
137+
def stop_vad(self):
138+
self.audio_manager.stop_vad()
139+
140+
def __working_thread_handler(self):
141+
t = Thread(target=self.__chat_process)
142+
t.start(stack_size=64)
143+
t.join()
144+
145+
def test_audio_play(self):
146+
"""just for test"""
147+
global audio_data, audio_data_length_list
148+
total = 0
149+
for _ in range(10000 // 60):
150+
data = self.audio_manager.opus_read()
151+
audio_data += data
152+
audio_data_length_list.append(len(data))
153+
for count in audio_data_length_list:
154+
self.audio_manager.opus_write(audio_data[total:total+count])
155+
total += count
156+
157+
def __chat_process(self):
158+
logger.debug("chat process enter.")
159+
# self.start_vad()
160+
try:
161+
with self.__protocol:
162+
self.power_red_led.on()
163+
self.__protocol.hello()
164+
self.__protocol.wakeword_detected("小智")
165+
is_listen_flag = False
166+
while True:
167+
if self.__voice_activity_event.is_set():
168+
data = self.audio_manager.opus_read()
169+
# 有人声
170+
if not is_listen_flag:
171+
self.__protocol.abort()
172+
self.__protocol.listen("start")
173+
is_listen_flag = True
174+
self.__protocol.send(data)
175+
# logger.debug("send opus data to server")
176+
else:
177+
if is_listen_flag:
178+
self.__protocol.listen("stop")
179+
is_listen_flag = False
180+
# logger.debug("read opus data length: {}".format(len(data)))
181+
if not self.__protocol.is_state_ok():
182+
break
183+
utime.sleep_ms(1)
184+
except Exception as e:
185+
logger.debug("working thread handler got Exception: {}".format(repr(e)))
186+
finally:
187+
# self.stop_vad()
188+
self.power_red_led.blink(250, 250)
189+
logger.debug("chat process exit.")
190+
191+
def on_talk_key_click(self, args):
192+
logger.info("on_talk_key_click: ", args)
193+
if args[1] == 1:
194+
# 按键按下
195+
self.__voice_activity_event.set() # 有人声
196+
else:
197+
# 按键抬起
198+
self.__voice_activity_event.clear() # 无人声
199+
if self.__working_thread is not None and self.__working_thread.is_running():
200+
return
201+
self.__working_thread = Thread(target=self.__working_thread_handler)
202+
self.__working_thread.start()
203+
204+
def on_keyword_spotting(self, state):
205+
logger.info("on_keyword_spotting: {}".format(state))
206+
if state == 0:
207+
# 唤醒词触发
208+
if self.__working_thread is not None and self.__working_thread.is_running():
209+
return
210+
self.__working_thread = Thread(target=self.__working_thread_handler)
211+
self.__working_thread.start()
212+
self.__keyword_spotting_event.clear()
213+
else:
214+
self.__keyword_spotting_event.set()
215+
216+
def on_voice_activity_detection(self, state):
217+
logger.info("on_voice_activity_detection: {}".format(state))
218+
if state == 1:
219+
self.__voice_activity_event.set() # 有人声
220+
else:
221+
self.__voice_activity_event.clear() # 无人声
222+
223+
def on_audio_message(self, raw):
224+
self.audio_manager.opus_write(raw)
225+
226+
def on_json_message(self, msg):
227+
return getattr(self, "handle_{}_message".format(msg["type"]))(msg)
228+
229+
def handle_stt_message(self, msg):
230+
logger.debug("handle_stt_message: {}".format(msg))
231+
232+
def handle_tts_message(self, msg):
233+
state = msg["state"]
234+
if state == "start":
235+
self.wifi_green_led.blink(250, 250)
236+
elif state == "stop":
237+
self.wifi_green_led.off()
238+
else:
239+
pass
240+
241+
def handle_llm_message(self, msg):
242+
logger.debug("handle_llm_message: {}".format(msg))
243+
244+
def handle_iot_message(self, msg):
245+
logger.debug("handle_iot_message: {}".format(msg))
246+
247+
def run(self):
248+
self.charge_manager.enable_charge()
249+
self.audio_manager.open_opus()
250+
self.talk_key.enable()
251+
self.led_power_pin.write(1)
252+
self.power_red_led.blink(250, 250)
253+
254+
255+
if __name__ == "__main__":
256+
app = Application()
257+
app.run()

0 commit comments

Comments
 (0)