Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions smibhid/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ Press the space_open or space_closed buttons to call the smib server endpoint ap

## Features
- Space open and closed buttons with LED feedback that calls the S.M.I.B. space_open/space_closed endpoint
- Press the open button multiple times to set the number of hours the space will be open for
- LED flashes while trying to set state so you know it's trying to do something
- Confirms the space state after change by calling space_state
- Regularly polls for space state (polling period configurable in config.py) and updates the SMIBHID status appropriately to sync with other space state controls
- Flashes both space state LEDs at 2Hz if space state cannot be determined
- 2x16 character LCD display support
- Error information shown on connected displays where configured in modules using ErrorHandler class
- UI Logger captures timestamps of button presses and uploads to SMIB for logging and review of usage patterns

## Circuit diagram
### Pico W Connections
Expand Down
2 changes: 2 additions & 0 deletions smibhid/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,5 @@
RFID_MISO = 16
RFID_RST = 21
RFID_CS = 17

ENABLE_UI_LOGGING_UPLOAD = False
7 changes: 6 additions & 1 deletion smibhid/lib/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""

from asyncio import Event, sleep
from re import sub

from machine import Pin

Expand Down Expand Up @@ -51,7 +52,11 @@ async def wait_for_press(self) -> None:
def get_name(self) -> str:
"""Get the name of the button"""
return self.name


def get_id(self) -> str:
"""Get the ID of the button"""
return sub(r"\s+", "_", self.name).lower()

def get_pin(self) -> int:
"""Get the GPIO pin of the button"""
return self.gpio
10 changes: 7 additions & 3 deletions smibhid/lib/hid.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from lib.rfid.reader import RFIDReader
from config import RFID_ENABLED
from lib.uistate import UIState
from lib.ui_log import UILog

class HID:

Expand All @@ -26,10 +27,12 @@ def __init__(self) -> None:
self.moduleConfig.register_rfid(RFIDReader(Event()))
self.display = self.moduleConfig.get_display()
self.wifi = self.moduleConfig.get_wifi()
self.moduleConfig.register_ui_log(UILog(self.wifi))
self.reader = self.moduleConfig.get_rfid()
self.ui_log = self.moduleConfig.get_ui_log()
self.space_state = SpaceState(self.moduleConfig, self)
self.errorHandler = ErrorHandler("HID")
self.errorHandler.configure_display(self.display)
self.error_handler = ErrorHandler("HID")
self.error_handler.configure_display(self.display)
self.ui_state_instance = StartUIState(self, self.space_state)
self.ui_state_instance.on_enter()

Expand All @@ -49,6 +52,7 @@ def startup(self) -> None:
self.space_state.startup()
if self.reader:
self.reader.startup()
self.ui_log.startup()

self.log.info("Entering main loop")
self.switch_to_appropriate_spacestate_uistate()
Expand All @@ -74,7 +78,7 @@ def switch_to_appropriate_spacestate_uistate(self) -> None:
else:
self.log.error("Space state is in an unexpected state")
raise ValueError("Space state is in an unexpected state")

class StartUIState(UIState):

def __init__(self, hid: HID, space_state: SpaceState) -> None:
Expand Down
11 changes: 11 additions & 0 deletions smibhid/lib/module_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from lib.ulogging import uLogger
from lib.rfid.reader import RFIDReader
from config import RFID_ENABLED
from lib.ui_log import UILog

class ModuleNotRegisteredError(Exception):
"""Exception raised when a required module is not registered."""
Expand All @@ -20,6 +21,7 @@ def __init__(self) -> None:
self.display = None
self.wifi = None
self.reader = None
self.ui_log = None

def register_display(self, display: Display) -> None:
self.display = display
Expand All @@ -29,6 +31,9 @@ def register_wifi(self, wifi: WirelessNetwork) -> None:

def register_rfid(self, reader: RFIDReader) -> None:
self.reader = reader

def register_ui_log(self, ui_log: UILog) -> None:
self.ui_log = ui_log

def get_display(self) -> Display:
if not self.display:
Expand All @@ -47,4 +52,10 @@ def get_rfid(self) -> RFIDReader | None:
self.log.warn("RFID module not registered")
raise ModuleNotRegisteredError("RFID")
return self.reader

def get_ui_log(self) -> UILog:
if not self.ui_log:
self.log.warn("UI Log module not registered")
raise ModuleNotRegisteredError("UI Log")
return self.ui_log

7 changes: 6 additions & 1 deletion smibhid/lib/slack_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ async def async_get_space_state(self) -> bool | None:
self.log.error(f"Unable to load space state from response data: {e}")
raise
return state

async def async_upload_ui_log(self, log: list) -> None:
"""Upload the UI log to the server."""
json_log = dumps({"log" : log})
await self._async_slack_api_request("POST", "smibhid_ui_log", json_log)

async def _async_slack_api_request(self, method: str, url_suffix: str, json_data: str = "") -> dict:
"""
Expand All @@ -48,7 +53,7 @@ async def _async_slack_api_request(self, method: str, url_suffix: str, json_data

async def _async_api_request(self, method: str, url: str, json_data: str = "") -> dict:
"""Internal method to make a PUT or GET request to an API, provide the HTTP method and the full API URL"""
if method in ["GET", "PUT"]:
if method in ["GET", "PUT", "POST"]:
response = await self._async_api_make_request(method, url, json_data)
return response
else:
Expand Down
3 changes: 3 additions & 0 deletions smibhid/lib/space_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def __init__(self, module_config: ModuleConfig, hid: object) -> None:
self.hid = hid
self.display = module_config.get_display()
self.wifi = module_config.get_wifi()
self.ui_log = module_config.get_ui_log()
self.slack_api = Wrapper(self.wifi)
self.space_open_button_event = Event()
self.space_closed_button_event = Event()
Expand Down Expand Up @@ -233,6 +234,7 @@ async def async_space_open_button_watcher(self) -> None:
await self.space_open_button_event.wait()
self.space_open_button_event.clear()
self.last_button_press_ms = ticks_ms()
self.ui_log.log_button_press(self.open_button)
await self.hid.ui_state_instance.async_on_space_open_button()


Expand All @@ -245,6 +247,7 @@ async def async_space_close_button_watcher(self) -> None:
await self.space_closed_button_event.wait()
self.space_closed_button_event.clear()
self.last_button_press_ms = ticks_ms()
self.ui_log.log_button_press(self.closed_button)
await self.hid.ui_state_instance.async_on_space_closed_button()

async def async_space_state_watcher(self) -> None:
Expand Down
72 changes: 72 additions & 0 deletions smibhid/lib/ui_log.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from time import time
from lib.button import Button
from lib.ulogging import uLogger
from asyncio import create_task, sleep
from config import ENABLE_UI_LOGGING_UPLOAD
from lib.error_handling import ErrorHandler
from lib.slack_api import Wrapper
from lib.networking import WirelessNetwork

class UILog:
def __init__(self, wifi: WirelessNetwork) -> None:
self.log = uLogger("UI Log")
self.log.info("UI Log initialised")
self.ui_log = []
self.slack = Wrapper(wifi)
self.configure_error_handling()

def configure_error_handling(self) -> None:
"""
Register errors with the error handler for the space state module.
"""
self.error_handler = ErrorHandler("UI Log")
self.errors = {
"UIL": "Failed to upload UI log"
}

for error_key, error_message in self.errors.items():
self.error_handler.register_error(error_key, error_message)

def startup(self) -> None:
"""
Start the UI log uploader.
"""
self.log.info("Starting UI log uploader")
create_task(self.async_ui_log_uploader())

def log_button_press(self, button: Button) -> None:
self.log.info(f"Button press logged: {button.get_name()}")
self.ui_log.append({"timestamp": time(), "type": "button_press", "event": {"button_id": button.get_id(), "button_name": button.get_name()}})

def log_rotary_dial_input(self) -> None:
self.log.info("Rotary dial input logged")

async def async_ui_log_uploader(self) -> None:
"""
Periodically upload the UI log to the server.
"""
while True:
self.log.info("Uploading UI log to server")
if self.ui_log:
if ENABLE_UI_LOGGING_UPLOAD:
self.log.info("UI log contains data, uploading to server")
try:
await self.slack.async_upload_ui_log(self.ui_log)
self.log.info("UI log uploaded successfully")
self.log.info("Clearing UI log")
self.ui_log = []
self.error_handler.disable_error("UIL")
except Exception as e:
self.log.error(f"Failed to upload UI log: {e}")
self.error_handler.enable_error("UIL")

else:
self.log.info("UI logging is disabled, no upload will occur")
self.log.info(f"UI log: {self.ui_log}")
self.log.info("Clearing UI log")
self.ui_log = []

else:
self.log.info("UI log is empty, no upload required")

await sleep(51)