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: 0 additions & 2 deletions crystalfontz/atx.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,6 @@ def as_dict(self: Self) -> Dict[str, Any]:

as_["functions"] = [fn.value for fn in self.functions]

print(as_)

return as_

def __repr__(self: Self) -> str:
Expand Down
7 changes: 4 additions & 3 deletions crystalfontz/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -676,10 +676,11 @@ async def detect(
@pass_client(run_forever=True, report_handler_cls=CliReportHandler)
async def listen(client: Client, for_: Optional[float]) -> None:
"""
Listen for key and temperature reports.
Listen for key activity and temperature reports.

To configure which reports to receive, use 'crystalfontz keypad reporting' and
'crystalfontz temperature reporting' respectively.
To configure which reports to receive, use
'python -m crystalfontz keypad reporting' and
'python -m crystalfontz temperature reporting' respectively.
"""

if for_ is not None:
Expand Down
3 changes: 3 additions & 0 deletions crystalfontz/dbus/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from crystalfontz.dbus.service.cli import main

main()
19 changes: 0 additions & 19 deletions crystalfontz/dbus/bus.py

This file was deleted.

9 changes: 7 additions & 2 deletions crystalfontz/dbus/client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,22 @@
from crystalfontz.dbus.config import StagedConfig
from crystalfontz.dbus.domain import ConfigM
from crystalfontz.dbus.interface import DBUS_NAME, DbusInterface
from crystalfontz.dbus.report import DbusClientReportHandler


class DbusClient(DbusInterface):
"""
A DBus client for the Crystalfontz device.
"""

def __init__(self: Self, bus: Optional[SdBus] = None) -> None:
def __init__(
self: Self,
bus: Optional[SdBus] = None,
report_handler: Optional[DbusClientReportHandler] = None,
) -> None:
client = Mock(name="client", side_effect=NotImplementedError("client"))
self.subscribe = Mock(name="client.subscribe")
super().__init__(client)
super().__init__(client, report_handler=report_handler)

cast(Any, self)._proxify(DBUS_NAME, "/", bus=bus)

Expand Down
39 changes: 35 additions & 4 deletions crystalfontz/dbus/client/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
OutputMode,
WATCHDOG_SETTING,
)
from crystalfontz.dbus.bus import select_session_bus, select_system_bus
from crystalfontz.dbus.client import DbusClient
from crystalfontz.dbus.config import StagedConfig
from crystalfontz.dbus.domain import (
Expand All @@ -52,6 +51,12 @@
VersionsM,
)
from crystalfontz.dbus.error import handle_dbus_error
from crystalfontz.dbus.report import DbusClientCliReportHandler
from crystalfontz.dbus.select import (
select_default_bus,
select_session_bus,
select_system_bus,
)
from crystalfontz.gpio import GpioDriveMode, GpioFunction
from crystalfontz.lcd import LcdRegister
from crystalfontz.temperature import (
Expand All @@ -70,6 +75,7 @@ class Obj:
output: OutputMode
timeout: TimeoutT
retry_times: RetryTimesT
report_handler: DbusClientCliReportHandler


def pass_config(fn: AsyncCommand) -> AsyncCommand:
Expand Down Expand Up @@ -101,6 +107,15 @@ async def wrapped(obj: Obj, *args, **kwargs) -> None:
return wrapped


def pass_report_handler(fn: AsyncCommand) -> AsyncCommand:
@click.pass_obj
@functools.wraps(fn)
async def wrapped(obj: Obj, *args, **kwargs) -> None:
await fn(obj.report_handler, *args, **kwargs)

return wrapped


def should_sudo(config_file: str) -> bool:
st = os.stat(config_file)
return os.geteuid() != st.st_uid
Expand Down Expand Up @@ -202,14 +217,20 @@ async def load() -> None:
select_session_bus()
elif user is None:
select_system_bus()
else:
select_default_bus()

report_handler = DbusClientCliReportHandler()
report_handler.mode = output

client = DbusClient()
client = DbusClient(report_handler=report_handler)
ctx.obj = Obj(
client=client,
log_level=log_level,
output=output,
timeout=TimeoutM.pack(timeout),
retry_times=RetryTimesM.pack(retry_times),
report_handler=report_handler,
)

asyncio.run(load())
Expand Down Expand Up @@ -355,16 +376,26 @@ async def detect(
@main.command()
@click.option("--for", "for_", type=float, help="Amount of time to listen for reports")
@async_command
@pass_report_handler
@pass_client
async def listen(client: DbusClient, for_: Optional[float]) -> None:
async def listen(
client: DbusClient,
report_handler: DbusClientCliReportHandler,
for_: Optional[float],
) -> None:
"""
Listen for key and temperature reports.

To configure which reports to receive, use 'crystalfontz keypad reporting' and
'crystalfontz temperature reporting' respectively.
"""

raise NotImplementedError("listen")
await report_handler.listen()

if for_ is not None:
await asyncio.sleep(for_)
report_handler.stop()
await report_handler.done


@main.command(help="0 (0x00): Ping command")
Expand Down
30 changes: 30 additions & 0 deletions crystalfontz/dbus/config.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
"""
Manage a DBus service configuration.

Configuration for the DBus service is a little different than for the serial client.
This is because the DBus service doesn't live reload a config after it changes. In
other words, if you edit the config file, the DBus service's loaded config will
show drift.

This is captured in the `StagedConfig` class, which holds both the config as served
by the live DBus service, and the config as loaded from the same file. These are
called the "active" and "target" config, respectively.
"""

from dataclasses import asdict, dataclass, fields
import json
from typing import Any, Dict, Generic, Literal, Self, TypeVar
Expand All @@ -20,6 +33,11 @@ class StagedAttr(Generic[T]):
"""
A staged attribute. Shows both the active and target value, and how the value is
expected to change when applied.

Attributes:
type (StageType): The type of staged change. Either "set", "unset" or None.
active (T): The attribute value from the active config.
target (T): The attribute value from the target config.
"""

type: StageType
Expand All @@ -45,6 +63,14 @@ class StagedConfig:
"""
A staged configuration. Shows both the active and target configurations, and how
the attributes are expected to change.

Attributes:
active_config (Config): The active configuration, as loaded from the live
DBus service.

target_config (Config): The target configuration, as loaded from the service's
config file.
dirty (bool): When true, there is drift between the active and target config.
"""

def __init__(self: Self, active_config: Config, target_config: Config) -> None:
Expand Down Expand Up @@ -72,6 +98,10 @@ def _check_config_dirty(self: Self) -> None:

@property
def file(self: Self) -> str:
"""
The path to the config file.
"""

file = self.target_config.file
assert file is not None, "Target config must be from a file"
return file
Expand Down
8 changes: 8 additions & 0 deletions crystalfontz/dbus/domain/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,16 @@
DowTransactionResultT,
GpioReadM,
GpioReadT,
KeyActivityReportM,
KeyActivityReportT,
KeypadPolledM,
KeypadPolledT,
LcdMemoryM,
LcdMemoryT,
PongM,
PongT,
TemperatureReportM,
TemperatureReportT,
VersionsM,
VersionsT,
)
Expand All @@ -116,6 +120,8 @@
"DowTransactionResultT",
"GpioReadM",
"GpioReadT",
"KeyActivityReportM",
"KeyActivityReportT",
"KeypadBrightnessM",
"KeypadBrightnessT",
"KeypadPolledM",
Expand All @@ -142,6 +148,8 @@
"TemperatureUnitT",
"TemperatureDisplayItemM",
"TemperatureDisplayItemT",
"TemperatureReportM",
"TemperatureReportT",
"TimeoutM",
"TimeoutT",
"VersionsM",
Expand Down
20 changes: 20 additions & 0 deletions crystalfontz/dbus/domain/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from crystalfontz.dbus.domain.base import ByteM, OptFloatM, OptFloatT, struct
from crystalfontz.keys import (
KeyActivity,
KeyPress,
KeyState,
KeyStates,
Expand Down Expand Up @@ -95,3 +96,22 @@ class KeypadBrightnessM(OptFloatM):
"""

t: ClassVar[str] = OptFloatM.t


KeyActivityT = int


class KeyActivityM:
"""
Map `KeyActivity` to and from `KeyActivityT` (`int`).
"""

t: ClassVar[str] = ByteM.t

@staticmethod
def pack(activity: KeyActivity) -> KeyActivityT:
return activity.to_byte()

@staticmethod
def unpack(activity: KeyActivityT) -> KeyActivity:
return KeyActivity.from_byte(activity)
48 changes: 47 additions & 1 deletion crystalfontz/dbus/domain/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,21 @@
GpioStateM,
GpioStateT,
)
from crystalfontz.dbus.domain.keys import KeyStatesM, KeyStatesT
from crystalfontz.dbus.domain.keys import (
KeyActivityM,
KeyActivityT,
KeyStatesM,
KeyStatesT,
)
from crystalfontz.response import (
DowDeviceInformation,
DowTransactionResult,
GpioRead,
KeyActivityReport,
KeypadPolled,
LcdMemory,
Pong,
TemperatureReport,
UserFlashAreaRead,
Versions,
)
Expand Down Expand Up @@ -240,3 +247,42 @@ def unpack(gpio_read: GpioReadT) -> GpioRead:
requested_level=requested_level,
settings=GpioSettingsM.unpack(settings),
)


KeyActivityReportT = KeyActivityT


class KeyActivityReportM:
"""
Map `KeyActivityReport` to and from `KeyActivityReportT`
"""

t: ClassVar[str] = KeyActivityM.t

@staticmethod
def pack(report: KeyActivityReport) -> KeyActivityReportT:
return KeyActivityM.pack(report.activity)

@staticmethod
def unpack(report: KeyActivityReportT) -> KeyActivityReport:
return KeyActivityReport(KeyActivityM.unpack(report))


TemperatureReportT = Tuple[IndexT, float, float]


class TemperatureReportM:
"""
Map `TemperatureReport` to and from `KeyActivityReportT`
"""

t: ClassVar[str] = t(IndexM, "dd")

@staticmethod
def pack(report: TemperatureReport) -> TemperatureReportT:
return (report.index, report.celsius, report.fahrenheit)

@staticmethod
def unpack(report: TemperatureReportT) -> TemperatureReport:
index, celsius, fahrenheit = report
return TemperatureReport(index, celsius, fahrenheit)
Loading