Skip to content

Commit 92e0c87

Browse files
Merge pull request #60 from FrameworkComputer/autosleep
2 parents 76822e9 + 6a91065 commit 92e0c87

File tree

3 files changed

+119
-33
lines changed

3 files changed

+119
-33
lines changed

ledmatrix/README.md

+33
Original file line numberDiff line numberDiff line change
@@ -184,3 +184,36 @@ run the stop command.
184184
```sh
185185
inputmodule-control led-amtrix --stop-game
186186
```
187+
188+
## Sleep Behavior
189+
190+
Currently sleeping means all LEDs and the LED controller are turned off.
191+
Transitions of sleep state slowly fade the LEDs on or off.
192+
193+
Optionally the firmware can be configured, at build-time, to turn the LEDs
194+
on/off immediately. Or display "SLEEP" instead of turning the LEDs off, which
195+
is useful for debugging whether the device is sleeping or not powered.
196+
197+
198+
###### Changing Sleep State
199+
200+
What can change the sleep state
201+
202+
- Hardware/OS triggers
203+
- `SLEEP#` pin
204+
- USB Suspend
205+
- Software/Firmware Triggers
206+
- Sleep/Wake or other command via USB Serial
207+
- Idle timer
208+
209+
Both of the hardware/OS triggers change the sleep state if they transition from one state to another.
210+
For example, if USB suspends, the LED matrix turns off. If it resumes, the LEDs come back on.
211+
Same for the `SLEEP#` pin.
212+
If either of them indicates sleep, even if they didn'td change state, the module goes to sleep.
213+
If they're active, they don't influence module state. That way sleep state can be controlled by commands and isn't overridden immediately.
214+
215+
The sleep/wake command always changes the state. But it can't be received when USB is suspended.
216+
Any other command will also wake up the device.
217+
218+
The idle timer will send the device to sleep after a configured timeout (default 60 seconds).
219+
The idle timer is reset once the device wakes up or once it receives a command.

ledmatrix/src/main.rs

+84-30
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ const SLEEP_MODE: SleepMode = SleepMode::Fading;
3333

3434
const STARTUP_ANIMATION: bool = true;
3535

36+
/// Go to sleep after 60s awake
37+
const SLEEP_TIMEOUT: u64 = 60_000_000;
38+
39+
/// List maximum current as 500mA in the USB descriptor
3640
const MAX_CURRENT: usize = 500;
3741

3842
/// Maximum brightness out of 255
@@ -232,8 +236,9 @@ fn main() -> ! {
232236
fill_grid_pixels(&state, &mut matrix);
233237

234238
let timer = Timer::new(pac.TIMER, &mut pac.RESETS);
235-
let mut prev_timer = timer.get_counter().ticks();
239+
let mut animation_timer = timer.get_counter().ticks();
236240
let mut game_timer = timer.get_counter().ticks();
241+
let mut sleep_timer = timer.get_counter().ticks();
237242

238243
let mut startup_percentage = Some(0);
239244
if !STARTUP_ANIMATION {
@@ -254,27 +259,58 @@ fn main() -> ! {
254259
}
255260

256261
let mut usb_initialized = false;
257-
let mut usb_suspended = true;
262+
let mut usb_suspended = false;
263+
let mut last_usb_suspended = usb_suspended;
264+
let mut sleeping = false;
265+
let mut last_host_sleep = sleep.is_low().unwrap();
258266

259267
loop {
260268
if sleep_present {
261269
// Go to sleep if the host is sleeping
262-
// Or if USB is suspended. Only if it was previously initialized,
263-
// since the OS puts the device into suspend before it's fully
264-
// initialized for the first time. But we don't want to show the
265-
// sleep animation during startup.
266-
let host_sleeping = sleep.is_low().unwrap() || (usb_suspended && usb_initialized);
267-
handle_sleep(
268-
host_sleeping,
269-
&mut state,
270-
&mut matrix,
271-
&mut delay,
272-
&mut led_enable,
273-
);
270+
let host_sleeping = sleep.is_low().unwrap();
271+
let host_sleep_changed = host_sleeping != last_host_sleep;
272+
// Change sleep state either if SLEEP# has changed
273+
// Or if it currently sleeping. Don't change if not sleeping
274+
// because then sleep is controlled by timing or by API.
275+
if host_sleep_changed || host_sleeping {
276+
sleeping = host_sleeping;
277+
}
278+
last_host_sleep = host_sleeping;
274279
}
275280

281+
// Change sleep state either if SLEEP# has changed
282+
// Or if it currently sleeping. Don't change if not sleeping
283+
// because then sleep is controlled by timing or by API.
284+
let usb_suspended_changed = usb_suspended != last_usb_suspended;
285+
// Only if USB was previously initialized,
286+
// since the OS puts the device into suspend before it's fully
287+
// initialized for the first time. But we don't want to show the
288+
// sleep animation during startup.
289+
if usb_initialized && (usb_suspended_changed || usb_suspended) {
290+
sleeping = usb_suspended;
291+
}
292+
last_usb_suspended = usb_suspended;
293+
294+
// Go to sleep after the timer has run out
295+
if timer.get_counter().ticks() > sleep_timer + SLEEP_TIMEOUT {
296+
sleeping = true;
297+
}
298+
// Constantly resetting timer during sleep is same as reset it once on waking up.
299+
// This means the timer ends up counting the time spent awake.
300+
if sleeping {
301+
sleep_timer = timer.get_counter().ticks();
302+
}
303+
304+
handle_sleep(
305+
sleeping,
306+
&mut state,
307+
&mut matrix,
308+
&mut delay,
309+
&mut led_enable,
310+
);
311+
276312
// Handle period display updates. Don't do it too often
277-
let render_again = timer.get_counter().ticks() > prev_timer + state.animation_period;
313+
let render_again = timer.get_counter().ticks() > animation_timer + state.animation_period;
278314
if matches!(state.sleeping, SleepState::Awake) && render_again {
279315
// On startup slowly turn the screen on - it's a pretty effect :)
280316
match startup_percentage {
@@ -291,7 +327,7 @@ fn main() -> ! {
291327
state.grid.0[x].rotate_right(1);
292328
}
293329
}
294-
prev_timer = timer.get_counter().ticks();
330+
animation_timer = timer.get_counter().ticks();
295331
}
296332

297333
// Check for new data
@@ -325,28 +361,43 @@ fn main() -> ! {
325361
Ok(count) => {
326362
let random = get_random_byte(&rosc);
327363
match (parse_command(count, &buf), &state.sleeping) {
328-
(Some(Command::Sleep(go_sleeping)), _) => {
364+
// Handle bootloader command without any delay
365+
// No need, it'll reset the device anyways
366+
(Some(c @ Command::BootloaderReset), _) => {
367+
handle_command(&c, &mut state, &mut matrix, random);
368+
}
369+
(Some(command), _) => {
370+
if let Command::Sleep(go_sleeping) = command {
371+
sleeping = go_sleeping;
372+
} else {
373+
// If already sleeping, wake up.
374+
// This means every command will wake the device up.
375+
// Much more convenient than having to send the wakeup commmand.
376+
sleeping = false;
377+
}
378+
// Make sure sleep animation only goes up to newly set brightness,
379+
// if setting the brightness causes wakeup
380+
if let SleepState::Sleeping((ref grid, _)) = state.sleeping {
381+
if let Command::SetBrightness(new_brightness) = command {
382+
state.sleeping =
383+
SleepState::Sleeping((grid.clone(), new_brightness));
384+
}
385+
}
329386
handle_sleep(
330-
go_sleeping,
387+
sleeping,
331388
&mut state,
332389
&mut matrix,
333390
&mut delay,
334391
&mut led_enable,
335392
);
336-
}
337-
(Some(c @ Command::BootloaderReset), _)
338-
| (Some(c @ Command::IsSleeping), _) => {
339-
if let Some(response) =
340-
handle_command(&c, &mut state, &mut matrix, random)
341-
{
342-
let _ = serial.write(&response);
343-
};
344-
}
345-
(Some(command), SleepState::Awake) => {
393+
346394
// If there's a very early command, cancel the startup animation
347395
startup_percentage = None;
348396

349-
// While sleeping no command is handled, except waking up
397+
// Reset sleep timer when interacting with the device
398+
// Very easy way to keep the device from going to sleep
399+
sleep_timer = timer.get_counter().ticks();
400+
350401
if let Some(response) =
351402
handle_command(&command, &mut state, &mut matrix, random)
352403
{
@@ -361,9 +412,11 @@ fn main() -> ! {
361412
buf[0], buf[1], buf[2], buf[3]
362413
)
363414
.unwrap();
415+
// let _ = serial.write(text.as_bytes());
416+
364417
fill_grid_pixels(&state, &mut matrix);
365418
}
366-
_ => {}
419+
(None, _) => {}
367420
}
368421
}
369422
}
@@ -435,6 +488,7 @@ fn get_random_byte(rosc: &RingOscillator<Enabled>) -> u8 {
435488
byte
436489
}
437490

491+
// Will do nothing if already in the right state
438492
fn handle_sleep(
439493
go_sleeping: bool,
440494
state: &mut LedmatrixState,

ledmatrix_control.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -1108,9 +1108,8 @@ def gui(devices):
11081108
sg.Button("Stop", k='-STOP-EQ-')
11091109
],
11101110

1111-
# Recent hardware sleeps based on sleep pin, not command
1112-
#[sg.Text("Sleep")],
1113-
#[sg.Button("Sleep"), sg.Button("Wake")],
1111+
[sg.Text("Sleep")],
1112+
[sg.Button("Sleep"), sg.Button("Wake")],
11141113
# [sg.Button("Panic")]
11151114
]
11161115
window = sg.Window("LED Matrix Control", layout)

0 commit comments

Comments
 (0)