This commit is contained in:
Daniel Bulant 2025-12-05 10:33:03 +01:00
parent 8e7b04f3ae
commit 634ca63c6c
6 changed files with 38 additions and 24 deletions

4
rpi/autostart.sh Executable file
View file

@ -0,0 +1,4 @@
#!/usr/bin/fish
cd /home/dan/ambientlightdemo/rpi
source .venv/bin/activate.fish
/home/dan/.local/bin/uv run main.py

View file

@ -2,8 +2,8 @@ import board
import neopixel import neopixel
pixels = neopixel.NeoPixel( pixels = neopixel.NeoPixel(
board.D12, 10, brightness=1, auto_write=False, pixel_order=neopixel.GRB board.D12, 25, brightness=1, auto_write=False, pixel_order=neopixel.GRB
) )
pixels.fill((0, 0, 0)) pixels.fill((255, 255, 255))
pixels.show() pixels.show()

View file

@ -2,8 +2,8 @@ import board
import neopixel import neopixel
pixels = neopixel.NeoPixel( pixels = neopixel.NeoPixel(
board.D12, 10, brightness=1, auto_write=False, pixel_order=neopixel.GRB board.D12, 25, brightness=1, auto_write=False, pixel_order=neopixel.GRB
) )
pixels.fill((0, 0, 0)) pixels.fill((0, 0, 0))
pixels.show() pixels.show()

View file

@ -1,27 +1,32 @@
import board import board
import neopixel import neopixel
from gdmath import * from gdmath import *
from math import cos, pi
pixels = neopixel.NeoPixel( pixels = neopixel.NeoPixel(
board.D12, 10, brightness=.2, auto_write=False, pixel_order=neopixel.GRB board.D12, 25, brightness=1, auto_write=False, pixel_order=neopixel.GRB
) )
class Lights: class Lights:
virtual_rotation = 0 virtual_rotation = 0
color = (255, 255, 255) color = (255, 255, 255)
expected_rotation_delta = 0 expected_rotation_delta_remote = 0
expected_rotation_delta_local = 0
rotation_remote = 0
rotation_local = 0
def __init__(self, pixels): def __init__(self, pixels):
self.pixels = pixels self.pixels = pixels
def process(self, delta): def process(self, delta):
self.virtual_rotation += self.expected_rotation_delta * (delta * 60) self.virtual_rotation += self.expected_rotation_delta_remote * (delta * 60)
max_darkness = clamp(abs(self.expected_rotation_delta / FULL_DARK_ROTATION_DELTA), 0, 1) max_darkness = clamp(abs(self.expected_rotation_delta_remote / FULL_DARK_ROTATION_DELTA), 0, 1)
light_intensity = 1 - cos(self.rotation_local * 2 * pi - self.rotation_remote * 2 * pi) ** 2
for i in range(pixels.n): for i in range(pixels.n):
pattern_offset = wrap((i + (self.virtual_rotation * PATTERN_REPETITION)) / PATTERN_REPETITION, 0, 1) pattern_offset = wrap((i + (self.virtual_rotation * PATTERN_REPETITION)) / PATTERN_REPETITION, 0, 1)
energy = (1 - (1 - pattern_offset) * max_darkness) energy = (1 - (1 - pattern_offset) * max_darkness) * light_intensity
new_color = tuple(int(x * energy) for x in self.color) new_color = tuple(int(x * energy) for x in self.color)
self.pixels[i] = new_color self.pixels[i] = new_color
self.pixels.show() self.pixels.show()
self.expected_rotation_delta = lerp(self.expected_rotation_delta, 0, delta * LIGHT_SLOWDOWN_SPEED) self.expected_rotation_delta_remote = lerp(self.expected_rotation_delta_remote, 0, delta * LIGHT_SLOWDOWN_SPEED)
lights = Lights(pixels) lights = Lights(pixels)

View file

@ -26,15 +26,15 @@ def receive_json():
except BlockingIOError: except BlockingIOError:
return None return None
local_rotation = 0 local_rotation: float = 0
local_delta = 0 local_delta: float = 0
remote_rotation = 0 remote_rotation: float = 0
remote_delta = 0 remote_delta: float = 0
def motor_rotation_fn(): def motor_rotation_fn():
global remote_rotation global remote_rotation
while True: while True:
target_pos = stepper.fpos_to_pos(1 - remote_rotation) target_pos = stepper.fpos_to_pos(remote_rotation)
stepper.single_step_towards(target_pos) # blocking / time.sleep stepper.single_step_towards(target_pos) # blocking / time.sleep
def read_thread_fn(): def read_thread_fn():
@ -43,21 +43,23 @@ def read_thread_fn():
old_rotation = local_rotation old_rotation = local_rotation
local_rotation = read_angle_f() local_rotation = read_angle_f()
local_delta = gdmath.shortest_diff(old_rotation, local_rotation) local_delta = gdmath.shortest_diff(old_rotation, local_rotation)
lights.expected_rotation_delta_local = local_delta
lights.rotation_local = local_rotation
try: try:
send_json({"value": local_rotation, "delta": local_delta}) send_json({"value": local_rotation, "delta": local_delta})
except Exception as e: except Exception as e:
print("Error sending data:", e) print("Error sending data:", e)
time.sleep(0.01)
def lights_fn(): def lights_fn():
global local_rotation, local_delta, remote_rotation, remote_delta global local_rotation, local_delta, remote_rotation, remote_delta
last_time = time.time() last_time = time.time()
delta = 1/60 delta = 1/60
while True: while True:
lights.color = gdmath.sample_color_gradient(local_rotation + remote_rotation) lights.color = gdmath.sample_color_gradient((local_rotation + remote_rotation) / 2)
lights.expected_rotation_delta = local_delta
lights.process(delta) lights.process(delta)
current_time = time.time()
time.sleep(1/60) time.sleep(1/60)
current_time = time.time()
delta = current_time - last_time delta = current_time - last_time
last_time = current_time last_time = current_time
@ -68,8 +70,10 @@ def network_recv_fn():
if data: if data:
if "value" in data: if "value" in data:
remote_rotation = data["value"] remote_rotation = data["value"]
lights.rotation_remote = remote_rotation
if "delta" in data: if "delta" in data:
remote_delta = data["delta"] remote_delta = data["delta"]
lights.expected_rotation_delta_remote = remote_delta
time.sleep(0.01) time.sleep(0.01)
motor_thread = threading.Thread(target=motor_rotation_fn) motor_thread = threading.Thread(target=motor_rotation_fn)

View file

@ -1,10 +1,11 @@
import RPi.GPIO as GPIO import RPi.GPIO as GPIO
import time import time
import atexit import atexit
from gdmath import wrap
class StepperMotor: class StepperMotor:
pins = (17, 27, 22, 23) pins = (17, 27, 22, 23)
step_sleep = 0.002 step_sleep = 0.01
step_count = 200 step_count = 200
def __init__(self): def __init__(self):
@ -28,7 +29,7 @@ class StepperMotor:
current_step = 0 current_step = 0
def _apply_single_step(self): def _apply_single_step(self):
current_step_abs = abs(self.current_step) current_step_abs = wrap(self.current_step, 0, 4)
if current_step_abs%4==0: if current_step_abs%4==0:
self._set_pins(GPIO.LOW, GPIO.LOW, GPIO.LOW, GPIO.HIGH) self._set_pins(GPIO.LOW, GPIO.LOW, GPIO.LOW, GPIO.HIGH)
elif current_step_abs%4==1: elif current_step_abs%4==1:
@ -72,4 +73,4 @@ class StepperMotor:
def fpos_to_pos(self, fpos): def fpos_to_pos(self, fpos):
return int((fpos % 1) * self.step_count) return int((fpos % 1) * self.step_count)
stepper = StepperMotor() stepper = StepperMotor()