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

Skip to content
64 changes: 64 additions & 0 deletions smib/slack/plugins/space/smibhid/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
__plugin_name__ = "Space Open/Close"
__description__ = "Space Open Close Button"
__author__ = "Sam Cork"

from datetime import datetime
from logging import Logger
from pprint import pformat

from pydantic import ValidationError
from injectable import inject

from smib.slack.custom_app import CustomApp
from smib.slack.db import database
from mogo.connection import Connection
from smib.common.utils import http_bolt_response
from .models.api import UILogs as ApiUILogs, UILog as ApiUILog
from .models.db import UILog as DbUILog

from slack_bolt.request import BoltRequest

app: CustomApp = inject("SlackApp")


@app.event("http_post_smibhid_ui_log")
@http_bolt_response
def on_smibhid_ui_log_post(event: dict, context: dict, ack: callable, request: BoltRequest):
ack()
logger: Logger = context.get('logger')

device_hostname, *_ = request.headers.get('device-hostname', None)
device_ip = event['request']['ip']

data = event.get("data", [])
if not data:
logger.info("No logs received in request")
return

logger.debug(pformat(data))

try:
ui_logs = ApiUILogs(ui_logs=data)
logger.debug(ui_logs)
except ValidationError as e:
logger.warning(e)
return 400, {}

save_api_logs_to_db(ui_logs, device_ip, device_hostname)


@database()
def save_api_logs_to_db(api_logs: ApiUILogs, device_ip: str, device_hostname: str | None):
for api_log in api_logs:
api_log: ApiUILog

db_log = DbUILog()

db_log.timestamp = datetime.utcfromtimestamp(api_log.timestamp)
db_log.type = api_log.type
db_log.event = api_log.event.dict()

db_log.device_hostname = device_hostname
db_log.device_ip = device_ip

db_log.save()
Empty file.
23 changes: 23 additions & 0 deletions smib/slack/plugins/space/smibhid/models/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from pydantic import BaseModel
from typing import Literal


class ButtonPressEvent(BaseModel):
button_name: str
button_id: str


class UILog(BaseModel):
event: ButtonPressEvent
type: Literal['button_press', 'rotary_dial']
timestamp: int


class UILogs(BaseModel):
ui_logs: list[UILog]

def __iter__(self):
yield from self.ui_logs



16 changes: 16 additions & 0 deletions smib/slack/plugins/space/smibhid/models/db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from datetime import datetime
from typing import Any

from smib.slack.db import Model, Field

from pydantic import BaseModel


class UILog(Model):
_name = "smibhid-ui-logs"

timestamp = Field[datetime](datetime)
type = Field[str](str, required=True)
event = Field[dict](dict, required=True)
device_hostname = Field[str](str)
device_ip = Field[str](str, required=True)
33 changes: 33 additions & 0 deletions smib/slack/plugins/space/smibhid/tests/stub_log.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import json
import time
from datetime import datetime, timezone

import requests


def main():
utc_time = datetime.now(timezone.utc)
utc_timestamp = int(utc_time.timestamp())
data = [
{"event": {"button_name": "Space Open", "button_id": "space_open"}, "type": "button_press",
"timestamp": utc_timestamp},
{"event": {"button_name": "Space Closed", "button_id": "space_closed"}, "type": "button_press",
"timestamp": utc_timestamp},
{"event": {"button_name": "Space Open", "button_id": "space_open"}, "type": "button_press",
"timestamp": utc_timestamp},
{"event": {"button_name": "Space Closed", "button_id": "space_closed"}, "type": "button_press",
"timestamp": utc_timestamp}
]
headers = {"Content-Type": "application/json", 'device-hostname': "smibhid-dummy"}
url = f"http://localhost/smib/event/smibhid_ui_log"
print(f"url: {url}")
print(f"headers: {headers}")
print(f"data: {data}")
print(f"JSON data: {json.dumps(data)}")
response = requests.post(url, headers=headers, data=json.dumps(data), verify=False)

print(response.status_code)


if __name__ == '__main__':
main()
6 changes: 3 additions & 3 deletions smib/webserver/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ async def generate_request_body(fastapi_request):
"base_url": str(fastapi_request.base_url).rstrip('/') + str(fastapi_request.url.path),
"url": str(fastapi_request.url),
"parameters": dict(fastapi_request.query_params),
"headers": dict(filter(lambda item: is_pickleable(item), fastapi_request.headers.items()))
"headers": dict(filter(lambda item: is_pickleable(item), fastapi_request.headers.items())),
"ip": fastapi_request.scope["client"][0]
}
}
}


async def generate_bolt_request(fastapi_request: Request):
body = await fastapi_request.body()
bolt_request: BoltRequest = to_bolt_request(fastapi_request, body=body)
bolt_request: BoltRequest = to_bolt_request(fastapi_request, body=b'')
bolt_request.body = await generate_request_body(fastapi_request)
return bolt_request

Expand Down
2 changes: 2 additions & 0 deletions smibhid/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
# Leave as none for MAC based unique hostname or specify a custom hostname string
CUSTOM_HOSTNAME = None

NTP_SYNC_INTERVAL_SECONDS = 86400

## Web host
WEBSERVER_HOST = ""
WEBSERVER_PORT = "80"
Expand Down
58 changes: 56 additions & 2 deletions smibhid/lib/networking.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from utime import ticks_ms
from utime import ticks_ms, gmtime, time
from math import ceil
import rp2
import network
Expand All @@ -8,6 +8,9 @@
from lib.utils import StatusLED
from asyncio import sleep, create_task
from lib.error_handling import ErrorHandler
from machine import RTC
from socket import getaddrinfo, socket, AF_INET, SOCK_DGRAM
import struct

class WirelessNetwork:

Expand Down Expand Up @@ -42,6 +45,7 @@ def __init__(self) -> None:
self.subnet = "Unknown"
self.gateway = "Unknown"
self.dns = "Unknown"
self.ntp_last_synced_timestamp = 0

self.configure_wifi()
self.configure_error_handling()
Expand Down Expand Up @@ -163,6 +167,9 @@ async def check_network_access(self) -> bool:

if self.get_status() == 3:
self.log.info("Connected to wireless network")
if self.ntp_last_synced_timestamp == 0 or (time() - self.ntp_last_synced_timestamp) > config.NTP_SYNC_INTERVAL_SECONDS:
self.log.info(f"Syncing RTC from NTP as it has not been synced in {config.NTP_SYNC_INTERVAL_SECONDS} seconds.")
await self.async_sync_rtc_from_ntp()
return True
else:
self.log.warn("Unable to connect to wireless network")
Expand All @@ -189,4 +196,51 @@ def get_all_data(self) -> dict:
return all_data

def get_hostname(self) -> str:
return self.hostname
return self.hostname

async def async_get_timestamp_from_ntp(self) -> tuple:
ntp_host = "pool.ntp.org"
port = 123
buf_size = 48
ntp_request_id = 0x1b
timestamp = (2000, 1, 1, 0, 0, 0, 0, 0)

try:
query = bytearray(buf_size)
query[0] = ntp_request_id
address = getaddrinfo(ntp_host, port)[0][-1]
udp_socket = socket(AF_INET, SOCK_DGRAM)
udp_socket.setblocking(False)

socket.sendto(udp_socket, query, address)

timeout_ms = 5000
start_time = ticks_ms()
while (ticks_ms() - start_time) < timeout_ms:
try:
data, _ = udp_socket.recvfrom(buf_size)
udp_socket.close()

local_epoch = 2208988800
timestamp = struct.unpack("!I", data[40:44])[0] - local_epoch
timestamp = gmtime(timestamp)
break
except OSError:
await sleep(0.1)

except Exception as e:
self.log.error(f"Failed to get NTP time: {e}")

return timestamp

async def async_sync_rtc_from_ntp(self) -> tuple:
try:
timestamp = await self.async_get_timestamp_from_ntp()
RTC().datetime((
timestamp[0], timestamp[1], timestamp[2], timestamp[6],
timestamp[3], timestamp[4], timestamp[5], 0))
self.ntp_last_synced_timestamp = time()
self.log.info("RTC synced from NTP")
except Exception as e:
self.log.error(f"Failed to sync RTC from NTP: {e}")
return timestamp
2 changes: 1 addition & 1 deletion smibhid/lib/slack_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ async def async_get_space_state(self) -> bool | None:

async def async_upload_ui_log(self, log: list) -> None:
"""Upload the UI log to the server."""
json_log = dumps({"log" : log})
json_log = dumps(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 Down
4 changes: 2 additions & 2 deletions smibhid/lib/space_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ def __init__(self, module_config: ModuleConfig, hid: object) -> None:
self.space_open_button_event = Event()
self.space_closed_button_event = Event()
self.open_button = Button(
config.SPACE_OPEN_BUTTON, "Space_open", self.space_open_button_event
config.SPACE_OPEN_BUTTON, "Space Open", self.space_open_button_event
)
self.closed_button = Button(
config.SPACE_CLOSED_BUTTON, "Space_closed", self.space_closed_button_event
config.SPACE_CLOSED_BUTTON, "Space Closed", self.space_closed_button_event
)
self.space_open_led = StatusLED(config.SPACE_OPEN_LED)
self.space_closed_led = StatusLED(config.SPACE_CLOSED_LED)
Expand Down
5 changes: 4 additions & 1 deletion smibhid/lib/ulogging.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from gc import mem_free
from os import stat, remove, rename
from time import gmtime, time

class uLogger:

Expand Down Expand Up @@ -57,7 +58,9 @@ def configure_handlers(self, handlers: list) -> None:
raise

def decorate_message(self, message: str, level: str) -> str:
decorated_message = f"[Mem: {round(mem_free() / 1024)}kB free][{level}][{self.module_name}]: {message}"
time_str = gmtime(time())
timestamp = f"{time_str[0]}-{time_str[1]}-{time_str[2]} {time_str[3]}:{time_str[4]}:{time_str[5]}"
decorated_message = f"[{timestamp}][Mem: {round(mem_free() / 1024)}kB free][{level}][{self.module_name}]: {message}"
return decorated_message

def process_handlers(self, message: str) -> None:
Expand Down