Skip to content

Commit 1af1a4f

Browse files
authored
Merge pull request #203 from somakeit/Add-open-hours-states
Add open hours states
2 parents 886ae7a + 3511450 commit 1af1a4f

File tree

8 files changed

+120
-15
lines changed

8 files changed

+120
-15
lines changed

smibhid/README.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,17 @@ Use existing space state buttons, lights, slack API wrapper and watchers as an e
9090
- Display drivers can be added by creating a new display driver module
9191
- Ensure the driver registers itself with the driver registry, use LCD1602 as an example
9292
- Import the new driver module in display.py
93-
- Update the config.py file to cinlude the option for your new driver
93+
- Update the config.py file to include the option for your new driver
9494
- UIState machine
9595
- A state machine exists and can be extended by various modules such as space_state to manage the state of the buttons and display output
9696
- The current state instance is held in hid.ui_state_instance
97-
- Enter a new UI state by caling the transition_to() method on a UIstate instance and pass any arguments needed by that state
98-
- You will need to pass any core objects needed by the base UIState class and apply using super() as normal. These are currently HID (for managing the current state instance) and SpaceState so that the open and close buttons are avaialble in all UIs with default space open/closed behaviour.
97+
- Enter a new UI state by calling the transition_to() method on a UIstate instance and pass any arguments needed by that state
98+
- You will need to pass any core objects needed by the base UIState class and apply using super() as normal. These are currently HID (for managing the current state instance) and SpaceState so that the open and close buttons are available in all UIs with default space open/closed behaviour.
99+
100+
### UI State diagram
101+
The space state UI state machine is described in this diagram:
102+
103+
![Space state UI state machine diagram](images/SMIBHID_UI_state_diagram.drawio.png)
99104

100105
## Version
101106
Ensure that the `HID` class version attribute is updated to match the version in `pyproject.toml`
59.2 KB
Loading

smibhid/lib/LCD1602.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,18 @@ async def _async_busy_spinner(self, col: int, row: int) -> None:
184184
self.printout(char)
185185
await async_sleep(0.2)
186186

187+
def add_hours(self, open_for_hours: int) -> None:
188+
"""Display a screen for adding open for hours information."""
189+
self.log.info(f"Adding hours screen: {open_for_hours}")
190+
self.print_on_line(0, "Opening space")
191+
self.print_on_line(1, f"for {open_for_hours} hours")
192+
193+
def cancelling(self) -> None:
194+
"""Display cancelling text."""
195+
self.log.info("Cancelling")
196+
self.print_on_line(0, "Cancelling")
197+
self.print_on_line(1, "Please wait...")
198+
187199
def _begin(self, lines: int) -> None:
188200
"""Configure and set initial display output."""
189201
if (lines > 1):

smibhid/lib/display.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,13 @@ def clear_busy_output(self) -> None:
8080
"""Clear all screens from busy output."""
8181
self.log.info("Clearing all screens of busy output")
8282
self._execute_command("clear_busy_output")
83+
84+
def add_hours(self, open_for_hours: int) -> None:
85+
"""Display a screen for adding open for hours information."""
86+
self.log.info("Adding hours screen")
87+
self._execute_command("add_hours", open_for_hours)
88+
89+
def cancelling(self) -> None:
90+
"""Display cancelling text."""
91+
self.log.info("Cancelling")
92+
self._execute_command("cancelling")

smibhid/lib/hid.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ class StartUIState(UIState):
7979

8080
def __init__(self, hid: HID, space_state: SpaceState) -> None:
8181
super().__init__(hid, space_state)
82-
self.hid = hid
8382
self.display = self.hid.display
8483
self.version = self.hid.version
8584

smibhid/lib/module_config.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from lib.networking import WirelessNetwork
33
from lib.ulogging import uLogger
44
from lib.rfid.reader import RFIDReader
5+
from config import RFID_ENABLED
56

67
class ModuleNotRegisteredError(Exception):
78
"""Exception raised when a required module is not registered."""
@@ -41,8 +42,8 @@ def get_wifi(self) -> WirelessNetwork:
4142
raise ModuleNotRegisteredError("WiFi")
4243
return self.wifi
4344

44-
def get_rfid(self) -> RFIDReader:
45-
if not self.reader:
45+
def get_rfid(self) -> RFIDReader | None:
46+
if not self.reader and RFID_ENABLED:
4647
self.log.warn("RFID module not registered")
4748
raise ModuleNotRegisteredError("RFID")
4849
return self.reader

smibhid/lib/space_state.py

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from lib.ulogging import uLogger
1414
from lib.utils import StatusLED
1515
from lib.uistate import UIState
16+
from time import ticks_ms
1617

1718

1819
class SpaceState:
@@ -56,6 +57,7 @@ def __init__(self, module_config: ModuleConfig, hid: object) -> None:
5657
self.space_state_poll_frequency = 5
5758
self.state_check_error_open_led_flash_task = None
5859
self.state_check_error_closed_led_flash_task = None
60+
self.last_button_press_ms = 0
5961
self.configure_error_handling()
6062

6163
def configure_error_handling(self) -> None:
@@ -230,6 +232,7 @@ async def async_space_open_button_watcher(self) -> None:
230232
while True:
231233
await self.space_open_button_event.wait()
232234
self.space_open_button_event.clear()
235+
self.last_button_press_ms = ticks_ms()
233236
await self.hid.ui_state_instance.async_on_space_open_button()
234237

235238

@@ -241,6 +244,7 @@ async def async_space_close_button_watcher(self) -> None:
241244
while True:
242245
await self.space_closed_button_event.wait()
243246
self.space_closed_button_event.clear()
247+
self.last_button_press_ms = ticks_ms()
244248
await self.hid.ui_state_instance.async_on_space_closed_button()
245249

246250
async def async_space_state_watcher(self) -> None:
@@ -266,10 +270,47 @@ async def task_wrapper_for_error_handling():
266270
finally:
267271
await sleep(self.space_state_poll_frequency)
268272

273+
class SpaceStateUIState(UIState):
274+
"""
275+
Base class for space state UI state.
276+
"""
277+
def __init__(self, hid: object, space_state: SpaceState) -> None:
278+
super().__init__(hid, space_state)
279+
self.open_for_hours = 0
280+
281+
def last_button_press_x_seconds_ago(self, x: int = 2) -> bool:
282+
"""
283+
Check if the last button press was x seconds ago.
284+
"""
285+
now = ticks_ms()
286+
return now - self.space_state.last_button_press_ms > (x * 1000)
287+
288+
def increment_open_for_hours_single_digit(self) -> None:
289+
"""
290+
Increment the open for hours counter.
291+
"""
292+
if self.open_for_hours < 9:
293+
self.open_for_hours += 1
294+
else:
295+
self.open_for_hours = 0
296+
297+
self.hid.display.add_hours(self.open_for_hours)
298+
299+
async def _async_button_timeout_watcher(self) -> None:
300+
"""
301+
Call open space with current open hours count if no button press for 2 seconds.
302+
"""
303+
while not self.last_button_press_x_seconds_ago(2):
304+
await sleep(0.1)
305+
306+
await self._async_open_space(self.open_for_hours) # TODO change this so it cancels if closed button pressed
307+
269308
class OpenState(UIState):
270309
"""
271310
UI state for open space state.
272311
"""
312+
def __init__(self, hid: object, space_state: SpaceState) -> None:
313+
super().__init__(hid, space_state)
273314

274315
async def async_on_space_closed_button(self) -> None:
275316
await super().async_on_space_closed_button()
@@ -281,20 +322,47 @@ class ClosedState(UIState):
281322
"""
282323
UI state for closed space state.
283324
"""
325+
def __init__(self, hid: object, space_state: SpaceState) -> None:
326+
super().__init__(hid, space_state)
284327

285328
async def async_on_space_closed_button(self) -> None:
286329
self.log.info("Space is already closed")
287330

288331
async def async_on_space_open_button(self) -> None:
289-
await super().async_on_space_open_button()
332+
self.log.info("Adding hours to open for hours counter")
333+
self.hid.ui_state_instance.transition_to(AddingHoursState(self.hid, self.space_state))
290334

291335
class NoneState(UIState):
292336
"""
293337
UI state for unknown space state.
294338
"""
295-
339+
def __init__(self, hid: object, space_state: SpaceState) -> None:
340+
super().__init__(hid, space_state)
341+
296342
async def async_on_space_closed_button(self) -> None:
297343
await super().async_on_space_closed_button()
298344

299345
async def async_on_space_open_button(self) -> None:
300346
await super().async_on_space_open_button()
347+
348+
class AddingHoursState(SpaceStateUIState):
349+
"""
350+
UI state for adding hours to the open for hours counter.
351+
"""
352+
def __init__(self, hid: object, space_state: SpaceState) -> None:
353+
super().__init__(hid, space_state)
354+
355+
def on_enter(self) -> None:
356+
super().on_enter()
357+
self.hid.display.add_hours(self.open_for_hours)
358+
self.button_timeout_task = create_task(self._async_button_timeout_watcher())
359+
360+
async def async_on_space_closed_button(self) -> None:
361+
self.hid.display.cancelling()
362+
self.button_timeout_task.cancel()
363+
await sleep(1)
364+
self.space_state._set_space_output(CLOSED)
365+
self.hid.ui_state_instance.transition_to(ClosedState(self.hid, self.space_state))
366+
367+
async def async_on_space_open_button(self) -> None:
368+
self.increment_open_for_hours_single_digit()

smibhid/lib/uistate.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,9 @@ def transition_to(self, state: 'UIState') -> None:
3535
self.hid.set_ui_state(state)
3636
state.on_enter()
3737

38-
async def async_on_space_closed_button(self) -> None:
38+
async def _async_close_space(self) -> None:
3939
"""
40-
Default action for space closed button press.
41-
Updates SMIB space state to closed regardless of current space state.
40+
Default action for closing the space.
4241
"""
4342
self.space_state.flash_task = create_task(self.space_state.space_closed_led.async_constant_flash(4))
4443
try:
@@ -53,14 +52,13 @@ async def async_on_space_closed_button(self) -> None:
5352
self.space_state.flash_task.cancel()
5453
self.space_state.space_closed_led.off()
5554

56-
async def async_on_space_open_button(self) -> None:
55+
async def _async_open_space(self, open_for_hours: int = 0) -> None:
5756
"""
58-
Default action for space open button press.
59-
Updates SMIB space state to open regardless of current space state.
57+
Default action for opening the space.
6058
"""
6159
self.space_state.flash_task = create_task(self.space_state.space_open_led.async_constant_flash(4))
6260
try:
63-
await self.space_state.slack_api.async_space_open()
61+
await self.space_state.slack_api.async_space_open(open_for_hours)
6462
self.space_state.flash_task.cancel()
6563
self.space_state.set_output_space_open()
6664
create_task(self.space_state.async_update_space_state_output())
@@ -70,3 +68,15 @@ async def async_on_space_open_button(self) -> None:
7068
)
7169
self.space_state.flash_task.cancel()
7270
self.space_state.space_open_led.off()
71+
72+
async def async_on_space_closed_button(self) -> None:
73+
"""
74+
Close space when space closed button pressed outside of space state UI.
75+
"""
76+
await self._async_close_space()
77+
78+
async def async_on_space_open_button(self) -> None:
79+
"""
80+
Open space with no hours when when space open button pressed outside of space state UI.
81+
"""
82+
await self._async_open_space()

0 commit comments

Comments
 (0)