Skip to content

Commit

Permalink
Add NeoPixel emulation option for true color terminals, default to 20…
Browse files Browse the repository at this point in the history
…0 pixels
  • Loading branch information
meowmeowahr committed Jun 5, 2024
1 parent a36c9bf commit c364bcb
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 23 deletions.
25 changes: 15 additions & 10 deletions animator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@
import random
import time
from dataclasses import dataclass, field
from typing import Iterable
import logging

import neopixel
try:
import neopixel
except NotImplementedError as e:
logging.error(f"Error importing NeoPixel driver {repr(e)}. If you are using the emulator, you can ignore this message.")

import neopixel_emu

from animator import light_funcs

Expand All @@ -22,7 +27,7 @@

@dataclass
class AnimationState:
"State of animations and neopixels"
"""State of animations and neopixels"""
state: str = "OFF"
color: tuple = (255, 255, 255)
effect: str = "SingleColor"
Expand All @@ -31,40 +36,40 @@ class AnimationState:

@dataclass
class SingleColorArgs:
"Single Color mode options"
"""Single Color mode options"""
color: tuple = (255, 0, 0)

@dataclass
class GlitterRainbowArgs:
"Glitter Rainbow Animation options"
"""Glitter Rainbow Animation options"""
glitter_ratio: float = 0.05

@dataclass
class FadeArgs:
"Fade Animation options"
"""Fade Animation options"""
colora: tuple = (255, 0, 0)
colorb: tuple = (0, 0, 0)


@dataclass
class FlashArgs:
"Flash Animation options"
"""Flash Animation options"""
colora: tuple = (255, 0, 0)
colorb: tuple = (0, 0, 0)
speed: float = 25


@dataclass
class WipeArgs:
"Wipe Animation options"
"""Wipe Animation options"""
colora: tuple = (255, 0, 0)
colorb: tuple = (0, 0, 255)
leds_iter: int = 1


@dataclass
class AnimationArgs:
"Options for animations"
"""Options for animations"""
single_color: SingleColorArgs = field(default_factory=SingleColorArgs)
glitter_rainbow: GlitterRainbowArgs = field(default_factory=GlitterRainbowArgs)
fade: FadeArgs = field(default_factory=FadeArgs)
Expand Down Expand Up @@ -110,7 +115,7 @@ class Animator:
"""NeoPixel Animation class"""
def __init__(
self,
pixels: neopixel.NeoPixel,
pixels: 'neopixel.NeoPixel | neopixel_emu.NeoPixel',
num_pixels: int,
animation_state: AnimationState,
animation_args: AnimationArgs,
Expand Down
6 changes: 4 additions & 2 deletions animator/light_funcs.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Any

"Useful functions for animator"


Expand Down Expand Up @@ -47,7 +49,7 @@ def lerp(begin, end, t): # linear interpolation
return (lerp(r1, r2, t), lerp(g1, g2, t), lerp(b1, b2, t))


def map_range(x: int, in_min: int, in_max: int, out_min: int, out_max: int):
def map_range(x: float, in_min: int, in_max: int, out_min: int, out_max: int):
"""Map bounds of input to bounds of output
Args:
Expand Down Expand Up @@ -111,7 +113,7 @@ def square_wave(t, period, amplitude):
return -amplitude


def rindex(lst: list, value: any) -> int:
def rindex(lst: list, value: Any) -> int | None:
"""Get last occurrence of object in list
Args:
Expand Down
3 changes: 2 additions & 1 deletion config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ mqtt:
max_reconnect_delay: 60

driver:
num_pixels: 50
virtual: false
num_pixels: 200
pin: "D18"
order: "RGB"
30 changes: 22 additions & 8 deletions mqtt_animator.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,18 @@
import traceback
import dataclasses

import board
import neopixel
try:
import board
import neopixel
except NotImplementedError as e:
logging.error(f"Error importing NeoPixel driver {repr(e)}. If you are using the emulator, you can ignore this message.")

import yaml
from paho.mqtt import client as mqtt_client

import animator
from animator import AnimationArgs
import neopixel_emu

# Import yaml config
with open("config.yaml", encoding="utf-8") as stream:
Expand Down Expand Up @@ -62,22 +67,31 @@
# NeoPixel driver config
driver_config: dict = configuration.get("driver", {})

virtual: bool = driver_config.get("virtual", False)
num_pixels: int = driver_config.get("num_pixels", 100) # strip length
pixel_pin = getattr(board, driver_config.get("pin", "D18")) # rpi gpio pin
if not virtual:
pixel_pin = getattr(board, driver_config.get("pin", "D18")) # rpi gpio pin
else:
pixel_pin = None
pixel_order = driver_config.get("order", "RGB") # Color order

global animation_args

animation_args = animator.AnimationArgs()
animation_args.single_color.color = [0, 255, 0]
animation_args.single_color.color = (0, 255, 0)

animation_state = animator.AnimationState()
animation_state.brightness = 100

# Create NeoPixel object
pixels = neopixel.NeoPixel(
pixel_pin, num_pixels, brightness=1.0, auto_write=False, pixel_order=pixel_order
)
if virtual:
pixels = neopixel_emu.NeoPixel(
pixel_pin, num_pixels, brightness=1.0, auto_write=False, pixel_order=pixel_order
)
else:
pixels = neopixel.NeoPixel(
pixel_pin, num_pixels, brightness=1.0, auto_write=False, pixel_order=pixel_order # type: ignore
)
animator = animator.Animator(pixels, num_pixels, animation_state, animation_args)

def validate_arg_import(json_data, dataclass_type):
Expand Down Expand Up @@ -135,7 +149,7 @@ def on_message(cli: mqtt_client.Client, __, msg):
global animation_args

"Callback for mqtt message recieved"
print(f"Received `{msg.payload.decode()}` from `{msg.topic}` topic")
logging.debug(f"Received `{msg.payload.decode()}` from `{msg.topic}` topic")

if msg.topic == data_request_topic:
publish_state(cli)
Expand Down
52 changes: 52 additions & 0 deletions neopixel_emu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import tcolorpy
import adafruit_pixelbuf

__version__ = "0.1.0"

RGB = "RGB"
GRB = "GRB"
RGBW = "RGBW"
GRBW = "GRBW"


class NeoPixel(adafruit_pixelbuf.PixelBuf):
"""NeoPixel Emulator
Semi-compatible with the Adafruit CircuitPython Neopixel Module
"""

def __init__(
self,
pin,
n: int,
*,
bpp: int = 3,
brightness: float = 1.0,
auto_write: bool = True,
pixel_order: str = "RGB"
):
super().__init__(n, byteorder=pixel_order, brightness=brightness, auto_write=auto_write)

def deinit(self) -> None:
pass

def __enter__(self):
return self

def __repr__(self):
return "[" + ", ".join([str(x) for x in self]) + "]"

@property
def n(self) -> int:
""" Get the number of pixels """
return len(self)

def write(self) -> None:
""" Same as .show(), deprecated """
self.show()

def _transmit(self, _: bytearray) -> None:
termout = ""
for pixel in self:
termout += tcolorpy.tcolor("█", '#{:02x}{:02x}{:02x}'.format(*[int(i * self.brightness) for i in pixel]))
print(termout)

5 changes: 3 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
adafruit-circuitpython-neopixel~=6.3.11
paho-mqtt~=2.0.0
pyyaml~=6.0.1
paho-mqtt<2.0.0
pyyaml~=6.0.1
tcolorpy~=0.1.6

0 comments on commit c364bcb

Please sign in to comment.