Skip to content

Commit 20845d5

Browse files
committed
read serial data in chunk to prevent memory error
1 parent 4b17762 commit 20845d5

File tree

1 file changed

+96
-65
lines changed

1 file changed

+96
-65
lines changed

circuitpython/code.py

Lines changed: 96 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,22 @@
2222

2323
supervisor.runtime.autoreload = False
2424

25-
SETTINGSFILE = "settings.json" # The file in which the settings are saved
26-
MACROFILE = "macros.json" # The file in which the macros are saved
25+
# The file in which the settings are saved
26+
SETTINGSFILE = "settings.json"
27+
# The file in which the macros are saved
28+
MACROFILE = "macros.json"
2729

2830
SETTINGS = {
29-
"sleeptime": 2, # Time in seconds until the display turns off
30-
"keyboardlayout": "us", # Supported keyboard layouts: br, cz, da, de, es, fr, hu, it, po, sw, tr, uk, us
31-
"useunicodefont": False, # Use a unicode bitmap font, which will increas the initial load time!
32-
"fliprotation": False, # Flips the rotation of the device by 180 degrees
33-
"brightness": 0.1 # Set the LCD and LED Brightness
31+
# Time in seconds until the display turns off
32+
"sleeptime": 2,
33+
# Supported keyboard layouts: br, cz, da, de, es, fr, hu, it, po, sw, tr, uk, us
34+
"keyboardlayout": "us",
35+
# Use a unicode bitmap font, which will increas the initial load time!
36+
"useunicodefont": False,
37+
# Flips the rotation of the device by 180 degrees
38+
"fliprotation": False,
39+
# Set the LCD and LED Brightness
40+
"brightness": 0.1
3441
}
3542

3643
try:
@@ -85,9 +92,13 @@
8592

8693
class MacroApp():
8794
""" Main Class """
95+
8896
def __init__(self) -> None:
89-
self.macropad = MacroPad(layout_class=KeyboardLayout, rotation=180 if SETTINGS["fliprotation"] else 0)
90-
97+
self.macropad = MacroPad(
98+
layout_class=KeyboardLayout,
99+
rotation=180 if SETTINGS["fliprotation"] else 0
100+
)
101+
91102
self.macropad.display.auto_refresh = False
92103
self.macropad.display.brightness = SETTINGS["brightness"]
93104
self.macropad.display.root_group = displayio.Group()
@@ -97,6 +108,7 @@ def __init__(self) -> None:
97108

98109
self.readonly = storage.getmount('/').readonly
99110
self.serial_data = usb_cdc.data
111+
self.serial_buffer = ""
100112
self.serial_last_state = False
101113

102114
self.macroStack = [self._init_macros()]
@@ -127,16 +139,16 @@ def _init_macros(self) -> list[dict]:
127139
macros = json.load(f)
128140
if isinstance(macros, list):
129141
return {
130-
"label": rootLabel,
142+
"label": rootLabel,
131143
"content": macros,
132144
}
133145
return macros
134146
except OSError:
135147
return {
136-
"label": rootLabel,
148+
"label": rootLabel,
137149
"content": [],
138150
}
139-
151+
140152
def _save_macros(self) -> None:
141153
""" store the macros in the macrofile
142154
"""
@@ -148,17 +160,19 @@ def _save_macros(self) -> None:
148160

149161
def _init_group_label(self) -> dict[str, Key]:
150162
group_label = Label(
151-
font=load_font("/fonts/6x12.pcf") if SETTINGS["useunicodefont"] else terminalio.FONT,
152-
text="",
153-
padding_top=0,
154-
padding_bottom=0,
155-
padding_left=0,
156-
padding_right=0,
157-
color=0xFFFFFF,
158-
anchored_position=(self.macropad.display.width // 2, self.macropad.display.height - 10),
159-
anchor_point=(0.5, 0.0)
160-
)
161-
163+
font=load_font(
164+
"/fonts/6x12.pcf") if SETTINGS["useunicodefont"] else terminalio.FONT,
165+
text="",
166+
padding_top=0,
167+
padding_bottom=0,
168+
padding_left=0,
169+
padding_right=0,
170+
color=0xFFFFFF,
171+
anchored_position=(self.macropad.display.width // 2,
172+
self.macropad.display.height - 10),
173+
anchor_point=(0.5, 0.0)
174+
)
175+
162176
self.macropad.display.root_group.append(group_label)
163177

164178
return group_label
@@ -173,19 +187,20 @@ def _init_keys(self) -> list[Key]:
173187

174188
for i in range(self.macropad.keys.key_count):
175189
label = Label(
176-
font=load_font("/fonts/6x12.pcf") if SETTINGS["useunicodefont"] else terminalio.FONT,
177-
text="",
178-
padding_top=0,
179-
padding_bottom=1,
180-
padding_left=4,
181-
padding_right=4,
182-
color=0xFFFFFF,
183-
anchored_position=(
184-
(self.macropad.display.width - 2) / 2 * (i % 3) + 1,
185-
self.macropad.display.height / 5 * (i // 3) + 2),
186-
anchor_point=((i % 3) / 2, 0.0)
187-
)
188-
190+
font=load_font(
191+
"/fonts/6x12.pcf") if SETTINGS["useunicodefont"] else terminalio.FONT,
192+
text="",
193+
padding_top=0,
194+
padding_bottom=1,
195+
padding_left=4,
196+
padding_right=4,
197+
color=0xFFFFFF,
198+
anchored_position=(
199+
(self.macropad.display.width - 2) / 2 * (i % 3) + 1,
200+
self.macropad.display.height / 5 * (i // 3) + 2),
201+
anchor_point=((i % 3) / 2, 0.0)
202+
)
203+
189204
keys.append(Key(self.macropad, i, label))
190205
self.macropad.display.root_group.append(label)
191206

@@ -198,7 +213,7 @@ def _init_group(self) -> None:
198213

199214
self._update_tab()
200215

201-
def run_macro(self, item:dict, *args) -> None:
216+
def run_macro(self, item: dict, *args) -> None:
202217
""" run the macro, can be:
203218
Float (e.g. 0.25): delay in seconds
204219
String (e.g. "Foo"): corresponding keys pressed & released
@@ -225,20 +240,23 @@ def run_macro(self, item:dict, *args) -> None:
225240
key_name = key['kc'][1:] if key['kc'][:1] == "-" else key['kc']
226241
key_code = getattr(Keycode, key_name.upper(), None)
227242
if key_code:
228-
if key['kc'][:1] != "-" :
243+
if key['kc'][:1] != "-":
229244
self.macropad.keyboard.press(key_code)
230245
else:
231246
self.macropad.keyboard.release(key_code)
232247
if 'ccc' in key:
233-
control_code = getattr(ConsumerControlCode, key['ccc'].upper(), None)
248+
control_code = getattr(
249+
ConsumerControlCode, key['ccc'].upper(), None)
234250
if control_code:
235251
self.macropad.consumer_control.press(control_code)
236252
self.macropad.consumer_control.release()
237253
if 'tone' in key:
238-
self.macropad.play_tone(key['tone']['frequency'], key['tone']['duration'])
254+
self.macropad.play_tone(
255+
key['tone']['frequency'], key['tone']['duration'])
239256
if 'mse' in key:
240257
if "b" in key["mse"]:
241-
btn = getattr(Mouse, f"{key['mse']['b'].upper()}_BUTTON", None)
258+
btn = getattr(
259+
Mouse, f"{key['mse']['b'].upper()}_BUTTON", None)
242260
if btn:
243261
self.macropad.mouse.click(btn)
244262
self.macropad.mouse.move(
@@ -249,11 +267,11 @@ def run_macro(self, item:dict, *args) -> None:
249267
method = getattr(System, key['sys'], None)
250268
if method:
251269
method(self)
252-
270+
253271
self.macropad.keyboard.release_all()
254272
self.macropad.mouse.release_all()
255273

256-
def open_group(self, item:dict, *args) -> None:
274+
def open_group(self, item: dict, *args) -> None:
257275
""" open a group
258276
259277
Args:
@@ -284,16 +302,17 @@ def _update_tab(self) -> None:
284302

285303
for i, item in enumerate(self.macroStack[-1]["content"][:self.macropad.keys.key_count]):
286304
self.keys[i].type = item["type"]
287-
self.keys[i].label = "" if item["type"] == "blank" else item["label"]
288-
self.keys[i].color = (0, 0, 0) if item["type"] == "blank" else item["color"]
305+
self.keys[i].label = "" if item["type"] == "blank" else item["label"]
306+
self.keys[i].color = (
307+
0, 0, 0) if item["type"] == "blank" else item["color"]
289308
self.keys[i].set_func(self._get_key_func(item["type"]), item)
290309

291310
self.group_label.text = self.macroStack[-1]["label"]
292311

293312
for key in self.keys:
294313
key.update_colors()
295314

296-
def _get_key_func(self, type:str) -> function:
315+
def _get_key_func(self, type: str) -> function:
297316
""" get the specific function for the type
298317
299318
Args:
@@ -313,12 +332,14 @@ def _update_encoder_macros(self) -> None:
313332
""" update the rotary encoder macros defined for opened group
314333
"""
315334
self.encoder.update_encoder_macros(
316-
on_switch = self.macroStack[-1].get("encoder", {}).get("switch"),
317-
on_increased = self.macroStack[-1].get("encoder", {}).get("increased"),
318-
on_decreased = self.macroStack[-1].get("encoder", {}).get("decreased")
335+
on_switch=self.macroStack[-1].get("encoder", {}).get("switch"),
336+
on_increased=self.macroStack[-1].get("encoder",
337+
{}).get("increased"),
338+
on_decreased=self.macroStack[-1].get("encoder",
339+
{}).get("decreased")
319340
)
320341

321-
def _handle_serial_data(self, payload:str) -> dict:
342+
def _handle_serial_data(self, payload: str) -> dict:
322343
""" handle the data comming over the serial connection
323344
324345
Args:
@@ -341,12 +362,12 @@ def _handle_serial_data(self, payload:str) -> dict:
341362
response['ACK'] = 'settings'
342363
response['CONTENT'] = SETTINGS
343364
return response
344-
365+
345366
elif command == 'set_settings':
346367
if 'content' not in payload.keys():
347368
response['ERR'] = 'No content: %s' % payload
348369
return response
349-
370+
350371
content = payload['content']
351372

352373
if self._save_settings(content):
@@ -360,54 +381,54 @@ def _handle_serial_data(self, payload:str) -> dict:
360381
response['ACK'] = 'macros'
361382
response['CONTENT'] = self.macroStack[0]
362383
return response
363-
384+
364385
elif command == 'set_macros':
365386
if 'content' not in payload.keys():
366387
response['ERR'] = 'No content: %s' % payload
367388
return response
368-
389+
369390
content = payload['content']
370391
self.macroStack = [content]
371392
self._display_on()
372393
self._init_group()
373394

374395
response['ACK'] = 'Macros received'
375396
return response
376-
397+
377398
elif command == 'save_macros':
378399
if self._save_macros():
379400
response['ACK'] = 'Macros stored'
380401
else:
381402
response['ERR'] = 'Cannot store macros because USB storage is enabled'
382403

383404
return response
384-
405+
385406
elif command == 'enable_usb':
386407
System.enable_usb()
387408

388409
response['ACK'] = 'Enable USB'
389410
return response
390-
411+
391412
elif command == 'soft_reset':
392413
System.soft_reset()
393414

394415
response['ACK'] = 'Softreset'
395416
return response
396-
417+
397418
elif command == 'hard_reset':
398419
System.hard_reset()
399420

400421
response['ACK'] = 'Hardreset'
401422
return response
402-
423+
403424
else:
404425
response['ERR'] = 'Unkown command: %s' % command
405426
return response
406427
except Exception as e:
407428
response['ERR'] = str(e)
408429
return response
409430

410-
def _send_serial_data(self, payload:dict) -> None:
431+
def _send_serial_data(self, payload: dict) -> None:
411432
""" prepare and send data over serial connection
412433
413434
Args:
@@ -437,12 +458,20 @@ def start(self) -> None:
437458
if self.serial_last_state != self.serial_data.connected:
438459
self.serial_last_state = self.serial_data.connected
439460
if self.serial_data.connected:
440-
self._send_serial_data({'ACK': 'usbenabled', 'CONTENT': self.readonly })
461+
self._send_serial_data(
462+
{'ACK': 'usbenabled', 'CONTENT': self.readonly})
441463

442464
if self.serial_data.connected:
443465
if self.serial_data.in_waiting > 0:
444-
data = self.serial_data.readline()
445-
self._send_serial_data(self._handle_serial_data(data.decode("utf-8").strip()))
466+
while self.serial_data.in_waiting:
467+
chunk = self.serial_data.read(
468+
self.serial_data.in_waiting)
469+
self.serial_buffer += chunk.decode("utf-8")
470+
471+
if self.serial_buffer.endswith("\n"):
472+
self._send_serial_data(
473+
self._handle_serial_data(self.serial_buffer[:-1]))
474+
self.serial_buffer = ""
446475

447476
# get key events, so no inputs will be stored during connection
448477
# self.macropad.keys.events.get()
@@ -451,7 +480,8 @@ def start(self) -> None:
451480
key_event = self.macropad.keys.events.get()
452481
if key_event:
453482
self._display_on()
454-
self.keys[key_event.key_number].pressed = True if key_event.pressed and not any([key.pressed for key in self.keys]) else False
483+
self.keys[key_event.key_number].pressed = True if key_event.pressed and not any(
484+
[key.pressed for key in self.keys]) else False
455485

456486
if self.encoder.switch and self.encoder.on_switch:
457487
self._display_on()
@@ -469,5 +499,6 @@ def start(self) -> None:
469499
"content": self.encoder.on_decreased
470500
})
471501

502+
472503
app = MacroApp()
473-
app.start()
504+
app.start()

0 commit comments

Comments
 (0)