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
3 changes: 2 additions & 1 deletion crystalfontz/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from crystalfontz.config import Config
from crystalfontz.cursor import CursorStyle
from crystalfontz.device import Device, DeviceStatus
from crystalfontz.effects import Effect, EffectClient, Marquee, Screensaver
from crystalfontz.effects import DanceParty, Effect, EffectClient, Marquee, Screensaver
from crystalfontz.error import (
ConnectionError,
CrystalfontzError,
Expand Down Expand Up @@ -95,6 +95,7 @@
"CursorPositionSet",
"CursorStyle",
"CursorStyleSet",
"DanceParty",
"DataSent",
"DecodeError",
"Device",
Expand Down
20 changes: 20 additions & 0 deletions crystalfontz/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -1148,8 +1148,11 @@ async def marquee(
) -> None:
tick = obj.effect_options.tick if obj.effect_options else None
for_ = obj.effect_options.for_ if obj.effect_options else None

m = client.marquee(row, text, pause=pause, tick=tick)

await client.clear_screen()

await run_effect(m, client.loop, for_)


Expand All @@ -1163,6 +1166,23 @@ async def screensaver(obj: Obj, client: Client, text: str) -> None:
for_ = obj.effect_options.for_ if obj.effect_options else None
s = client.screensaver(text, tick=tick)

await client.clear_screen()

await run_effect(s, client.loop, for_)


@effects.command(help="Have a dance party!")
@click.argument("text")
@async_command
@pass_client()
@click.pass_obj
async def dance_party(obj: Obj, client: Client, text: str) -> None:
tick = obj.effect_options.tick if obj.effect_options else None
for_ = obj.effect_options.for_ if obj.effect_options else None
s = client.dance_party(text=text, tick=tick)

await client.clear_screen()

await run_effect(s, client.loop, for_)


Expand Down
22 changes: 21 additions & 1 deletion crystalfontz/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ async def main():
)
from crystalfontz.cursor import CursorStyle
from crystalfontz.device import Device, DeviceStatus, lookup_device
from crystalfontz.effects import Marquee, Screensaver
from crystalfontz.effects import DanceParty, Marquee, Screensaver
from crystalfontz.error import (
ConnectionError,
CrystalfontzError,
Expand Down Expand Up @@ -1381,6 +1381,26 @@ def screensaver(
loop=self.loop,
)

def dance_party(
self: Self,
text: str,
tick: Optional[float] = None,
timeout: Optional[float] = None,
retry_times: Optional[int] = None,
) -> DanceParty:
"""
Display a dance party effect on the LCD screen.
"""

return DanceParty(
client=self,
text=text,
tick=tick,
timeout=timeout,
retry_times=retry_times,
loop=self.loop,
)


async def create_connection(
port: str,
Expand Down
1 change: 0 additions & 1 deletion crystalfontz/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,6 @@ class SetContrast(Command):

def __init__(self: Self, contrast: float, device: Device) -> None:
self.contrast = device.contrast(contrast)
print(self.contrast[0], self.contrast[1])

def to_packet(self: Self) -> Packet:
return (self.command, self.contrast)
Expand Down
22 changes: 21 additions & 1 deletion crystalfontz/dbus/client/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
select_session_bus,
select_system_bus,
)
from crystalfontz.effects import Marquee, Screensaver
from crystalfontz.effects import DanceParty, Marquee, Screensaver
from crystalfontz.gpio import GpioDriveMode, GpioFunction
from crystalfontz.lcd import LcdRegister
from crystalfontz.temperature import (
Expand Down Expand Up @@ -961,6 +961,8 @@ async def marquee(

m = Marquee(client=client, row=row, text=text, pause=pause, tick=tick)

await client.clear_screen()

await run_effect(m, asyncio.get_running_loop(), for_)


Expand All @@ -975,4 +977,22 @@ async def screensaver(obj: Obj, client: DbusEffectClient, text: str) -> None:

s = Screensaver(client=client, text=text, tick=tick)

await client.clear_screen()

await run_effect(s, asyncio.get_running_loop(), for_)


@effects.command(help="Have a dance party!")
@click.argument("text")
@async_command
@pass_effect_client
@click.pass_obj
async def dance_party(obj: Obj, client: DbusEffectClient, text: str) -> None:
tick = obj.effect_options.tick if obj.effect_options else None
for_ = obj.effect_options.for_ if obj.effect_options else None

d = DanceParty(client=client, text=text, tick=tick)

await client.clear_screen()

await run_effect(d, asyncio.get_running_loop(), for_)
2 changes: 1 addition & 1 deletion crystalfontz/dbus/effects.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ async def set_contrast(

async def set_backlight(
self: Self,
lcd_brightness: int,
lcd_brightness: float,
keypad_brightness: Optional[int] = None,
timeout: Optional[float] = None,
retry_times: Optional[int] = None,
Expand Down
61 changes: 58 additions & 3 deletions crystalfontz/effects.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ async def set_contrast(

async def set_backlight(
self: Self,
lcd_brightness: int,
lcd_brightness: float,
keypad_brightness: Optional[int] = None,
timeout: Optional[float] = None,
retry_times: Optional[int] = None,
Expand Down Expand Up @@ -182,7 +182,7 @@ async def render(self: Self) -> None:
self.row, 0, buffer, timeout=self.timeout, retry_times=self.retry_times
)
self.shift += 1
if self.shift >= device.columns:
if self.shift > device.columns:
self.shift = 0

def _line(self: Self) -> bytes:
Expand All @@ -195,6 +195,11 @@ def _line(self: Self) -> bytes:


class Screensaver(Effect):
"""
A screensaver effect. Prints text at a random position, and moves it around the
screen on an interval.
"""

def __init__(
self: Self,
client: EffectClient,
Expand All @@ -207,7 +212,7 @@ def __init__(
device = client.device
buffer = device.character_rom.encode(text)

if len(buffer) >= device.columns:
if len(buffer) > device.columns:
raise ValueError(
f"Text length {len(buffer)} is too long to fit onto the device's "
f"{device.columns} columns"
Expand Down Expand Up @@ -236,3 +241,53 @@ async def render(self: Self) -> None:
await self.client.send_data(
row, column, self.text, timeout=self.timeout, retry_times=self.retry_times
)


class DanceParty(Effect):
"""
A dance party effect. Randomly changes the backlight and contrast settings on
an interval.
"""

def __init__(
self: Self,
client: EffectClient,
text: str,
tick: Optional[float] = None,
timeout: Optional[float] = None,
retry_times: Optional[int] = None,
loop: Optional[asyncio.AbstractEventLoop] = None,
) -> None:
device = client.device
buffer = device.character_rom.encode(text.center(device.columns))

if len(buffer) > device.columns:
raise ValueError(
f"Text length {len(buffer)} is too long to fit onto the device's "
f"{device.columns} columns"
)

super().__init__(
client=client,
tick=tick if tick is not None else 0.5,
timeout=timeout,
retry_times=retry_times,
loop=loop,
)

self.text: bytes = buffer

def _random_contrast(self: Self) -> float:
return random.uniform(0.4, 0.6)

def _random_brightness(self: Self) -> float:
return random.uniform(0.2, 0.8)

async def start(self: Self) -> None:
await self.client.send_data(0, 0, self.text)

async def render(self: Self) -> None:
await asyncio.gather(
self.client.set_contrast(self._random_contrast()),
self.client.set_backlight(self._random_brightness()),
)
2 changes: 1 addition & 1 deletion tests/integration/__snapshots__/test_cli.ambr
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# serializer version: 1
# name: test_status
b'CFA533 Status:\n--------------\nTemperature sensors enabled: \nKey states:\n up: pressed=no, pressed_since=yes, released_since=yes\n enter: pressed=no, pressed_since=yes, released_since=yes\n exit: pressed=no, pressed_since=yes, released_since=yes\n left: pressed=no, pressed_since=yes, released_since=yes\n right: pressed=no, pressed_since=yes, released_since=yes\n down: pressed=no, pressed_since=yes, released_since=yes\nATX Power Switch Functionality Settings:\n Functions enabled: \n Auto-Polarity Enabled: no\n Reset Inverted: no\n Power Inverted: no\n Power Pulse Length (seconds): None\nWatchdog Counter: 0\nContrast: 0.3137254901960784\nContrast (CFA633 Compatible): 0.24\nBacklight:\n Keypad Brightness: 0.2\n LCD Brightness: 0.2'
b'CFA533 Status:\n--------------\nTemperature sensors enabled: \nKey states:\n up: keypress=KP_UP, pressed=no, pressed_since=yes, released_since=yes\n enter: keypress=KP_ENTER, pressed=no, pressed_since=yes, released_since=yes\n exit: keypress=KP_EXIT, pressed=no, pressed_since=yes, released_since=yes\n left: keypress=KP_LEFT, pressed=no, pressed_since=yes, released_since=yes\n right: keypress=KP_RIGHT, pressed=no, pressed_since=yes, released_since=yes\n down: keypress=KP_DOWN, pressed=no, pressed_since=yes, released_since=yes\nATX Power Switch Functionality Settings:\n Functions enabled: \n Auto-Polarity Enabled: no\n Reset Inverted: no\n Power Inverted: no\n Power Pulse Length (seconds): None\nWatchdog Counter: 0\nContrast: 0.3137254901960784\nContrast (CFA633 Compatible): 0.24\nBacklight:\n Keypad Brightness: 0.2\n LCD Brightness: 0.2'
# ---
# name: test_versions
b'CFA533: h1.4, u1v2'
Expand Down
5 changes: 5 additions & 0 deletions tests/integration/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ def test_screensaver_for(cli: Cli) -> None:
cli("effects", "--for", "1.0", "screensaver", "Josh!")


def test_dance_party(cli, confirm) -> None:
with cli.bg("effects", "dance-party", "Carameldansen!!"):
confirm("Is the LCD showing a dance party effect?")


@pytest.mark.skip
def test_read_user_flash() -> None:
raise NotImplementedError("test_read_user_flash")
Expand Down
5 changes: 5 additions & 0 deletions tests/integration/test_dbus.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ def test_screensaver_for(dbus_cli: Cli) -> None:
dbus_cli("effects", "--for", "1.0", "screensaver", "Josh!")


def test_dance_party(dbus_cli, confirm) -> None:
with dbus_cli.bg("effects", "dance-party", "Carameldansen!!"):
confirm("Is the LCD showing a dance party effect?")


@pytest.mark.skip
def test_read_user_flash() -> None:
raise NotImplementedError("test_read_user_flash")
Expand Down