diff --git a/rpi/gdmath.py b/rpi/gdmath.py new file mode 100644 index 0000000..ecbb82f --- /dev/null +++ b/rpi/gdmath.py @@ -0,0 +1,33 @@ +colors = ["d062ff", "00b097", "50cc00", "8dcaff", "d062ff"] +colors_rgb = [tuple(int(colors[i][j:j+2], 16) for j in (0, 2, 4)) for i in range(len(colors))] +color_offsets = [0, .25, .5, .75, 1] + +FULL_DARK_ROTATION_DELTA = .1 +PATTERN_REPETITION = 5 +LIGHT_SLOWDOWN_SPEED = 1 + +def clamp(value, min_value, max_value): + return max(min_value, min(value, max_value)) +def wrap(value, min_value, max_value): + range_size = max_value - min_value + while value < min_value: + value += range_size + while value >= max_value: + value -= range_size + return value +def lerp(a, b, t): + return a + (b - a) * t +def pingpong(value, max): + value = wrap(value, 0, max * 2) + if value > max: + value = max * 2 - value + return value +def sample_color_gradient(t): + t = wrap(t, 0, 1) + for i in range(len(color_offsets) - 1): + if t >= color_offsets[i] and t <= color_offsets[i + 1]: + local_t = (t - color_offsets[i]) / (color_offsets[i + 1] - color_offsets[i]) + color_a = colors_rgb[i] + color_b = colors_rgb[i + 1] + return tuple(int(lerp(color_a[j], color_b[j], local_t)) for j in range(3)) + return colors_rgb[-1] \ No newline at end of file diff --git a/rpi/light-test.py b/rpi/light-test.py new file mode 100644 index 0000000..7186111 --- /dev/null +++ b/rpi/light-test.py @@ -0,0 +1,9 @@ +import board +import neopixel + +pixels = neopixel.NeoPixel( + board.D12, 10, brightness=1, auto_write=False, pixel_order=neopixel.GRB +) + +pixels.fill((0, 0, 0)) +pixels.show() \ No newline at end of file diff --git a/rpi/lights.py b/rpi/lights.py index a508ec8..1802f3a 100644 --- a/rpi/lights.py +++ b/rpi/lights.py @@ -1,70 +1,10 @@ -import socket -import time import board import neopixel -import json +from gdmath import * -num_pixels = 30 -local_pixels = neopixel.NeoPixel( - board.D18, num_pixels, brightness=0.2, auto_write=False, pixel_order=neopixel.GRB +pixels = neopixel.NeoPixel( + board.D12, 10, brightness=.2, auto_write=False, pixel_order=neopixel.GRB ) -networked_pixels = neopixel.NeoPixel( - board.D21, num_pixels, brightness=0.2, auto_write=False, pixel_order=neopixel.GRB -) - -colors = ["d062ff", "00b097", "50cc00", "8dcaff", "d062ff"] -colors_rgb = [tuple(int(colors[i][j:j+2], 16) for j in (0, 2, 4)) for i in range(len(colors))] -color_offsets = [0, .25, .5, .75, 1] - -UDP_HOST = "steamdeck" -UDP_PORT_LOCAL = 4444 -UDP_PORT_NETWORKED = 4433 - -sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) -sock.bind(("", UDP_PORT_LOCAL)) -sock.setblocking(0) - -FULL_DARK_ROTATION_DELTA = .1 -PATTERN_REPETITION = 5 -LIGHT_SLOWDOWN_SPEED = 1 - -def clamp(value, min_value, max_value): - return max(min_value, min(value, max_value)) -def wrap(value, min_value, max_value): - range_size = max_value - min_value - while value < min_value: - value += range_size - while value >= max_value: - value -= range_size - return value -def lerp(a, b, t): - return a + (b - a) * t -def pingpong(value, max): - value = wrap(value, 0, max * 2) - if value > max: - value = max * 2 - value - return value -def sample_color_gradient(t): - t = wrap(t, 0, 1) - for i in range(len(color_offsets) - 1): - if t >= color_offsets[i] and t <= color_offsets[i + 1]: - local_t = (t - color_offsets[i]) / (color_offsets[i + 1] - color_offsets[i]) - color_a = colors_rgb[i] - color_b = colors_rgb[i + 1] - return tuple(int(lerp(color_a[j], color_b[j], local_t)) for j in range(3)) - return colors_rgb[-1] -def send_data(value, delta): - message = json.dumps({"value": value, "delta": delta}).encode('utf-8') - sock.sendto(message, (UDP_HOST, UDP_PORT_NETWORKED)) -def shortest_diff(old, new): - diff = old - new - wrapped_diff = (new + 1) - old - if abs(wrapped_diff) < abs(diff): - diff = -wrapped_diff - wrapped_diff = (old + 1) - new - if abs(wrapped_diff) < abs(diff): - diff = wrapped_diff - return diff class Lights: virtual_rotation = 0 @@ -76,39 +16,10 @@ class Lights: def process(self, delta): self.virtual_rotation += self.expected_rotation_delta * (delta * 60) max_darkness = clamp(abs(self.expected_rotation_delta / FULL_DARK_ROTATION_DELTA), 0, 1) - for i in range(num_pixels): + for i in range(pixels.n): pattern_offset = wrap((i + (self.virtual_rotation * PATTERN_REPETITION)) / PATTERN_REPETITION, 0, 1) energy = (1 - (1 - pattern_offset) * max_darkness) new_color = tuple(x * energy for x in self.color) self.pixels[i] = new_color self.pixels.show() self.expected_rotation_delta = lerp(self.expected_rotation_delta, 0, delta * LIGHT_SLOWDOWN_SPEED) - -local_lights = Lights(local_pixels) -networked_lights = Lights(networked_pixels) - -last_time = time.time() -while True: - current_time = time.time() - delta = current_time - last_time - last_time = current_time - - try: - data, addr = sock.recvfrom(1024) - message = json.loads(data.decode('utf-8')) - value = message.get("value", 0) - delta_value = message.get("delta", 0) - - if value: - networked_lights.color = sample_color_gradient(value) - if delta_value: - networked_lights.expected_rotation_delta = delta_value - except BlockingIOError: - pass - - # todo: get input from local sensors - - local_lights.process(delta) - networked_lights.process(delta) - - time.sleep(0.03) \ No newline at end of file diff --git a/rpi/old-all.py b/rpi/old-all.py new file mode 100644 index 0000000..a508ec8 --- /dev/null +++ b/rpi/old-all.py @@ -0,0 +1,114 @@ +import socket +import time +import board +import neopixel +import json + +num_pixels = 30 +local_pixels = neopixel.NeoPixel( + board.D18, num_pixels, brightness=0.2, auto_write=False, pixel_order=neopixel.GRB +) +networked_pixels = neopixel.NeoPixel( + board.D21, num_pixels, brightness=0.2, auto_write=False, pixel_order=neopixel.GRB +) + +colors = ["d062ff", "00b097", "50cc00", "8dcaff", "d062ff"] +colors_rgb = [tuple(int(colors[i][j:j+2], 16) for j in (0, 2, 4)) for i in range(len(colors))] +color_offsets = [0, .25, .5, .75, 1] + +UDP_HOST = "steamdeck" +UDP_PORT_LOCAL = 4444 +UDP_PORT_NETWORKED = 4433 + +sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +sock.bind(("", UDP_PORT_LOCAL)) +sock.setblocking(0) + +FULL_DARK_ROTATION_DELTA = .1 +PATTERN_REPETITION = 5 +LIGHT_SLOWDOWN_SPEED = 1 + +def clamp(value, min_value, max_value): + return max(min_value, min(value, max_value)) +def wrap(value, min_value, max_value): + range_size = max_value - min_value + while value < min_value: + value += range_size + while value >= max_value: + value -= range_size + return value +def lerp(a, b, t): + return a + (b - a) * t +def pingpong(value, max): + value = wrap(value, 0, max * 2) + if value > max: + value = max * 2 - value + return value +def sample_color_gradient(t): + t = wrap(t, 0, 1) + for i in range(len(color_offsets) - 1): + if t >= color_offsets[i] and t <= color_offsets[i + 1]: + local_t = (t - color_offsets[i]) / (color_offsets[i + 1] - color_offsets[i]) + color_a = colors_rgb[i] + color_b = colors_rgb[i + 1] + return tuple(int(lerp(color_a[j], color_b[j], local_t)) for j in range(3)) + return colors_rgb[-1] +def send_data(value, delta): + message = json.dumps({"value": value, "delta": delta}).encode('utf-8') + sock.sendto(message, (UDP_HOST, UDP_PORT_NETWORKED)) +def shortest_diff(old, new): + diff = old - new + wrapped_diff = (new + 1) - old + if abs(wrapped_diff) < abs(diff): + diff = -wrapped_diff + wrapped_diff = (old + 1) - new + if abs(wrapped_diff) < abs(diff): + diff = wrapped_diff + return diff + +class Lights: + virtual_rotation = 0 + color = (255, 255, 255) + expected_rotation_delta = 0 + def __init__(self, pixels): + self.pixels = pixels + + def process(self, delta): + self.virtual_rotation += self.expected_rotation_delta * (delta * 60) + max_darkness = clamp(abs(self.expected_rotation_delta / FULL_DARK_ROTATION_DELTA), 0, 1) + for i in range(num_pixels): + pattern_offset = wrap((i + (self.virtual_rotation * PATTERN_REPETITION)) / PATTERN_REPETITION, 0, 1) + energy = (1 - (1 - pattern_offset) * max_darkness) + new_color = tuple(x * energy for x in self.color) + self.pixels[i] = new_color + self.pixels.show() + self.expected_rotation_delta = lerp(self.expected_rotation_delta, 0, delta * LIGHT_SLOWDOWN_SPEED) + +local_lights = Lights(local_pixels) +networked_lights = Lights(networked_pixels) + +last_time = time.time() +while True: + current_time = time.time() + delta = current_time - last_time + last_time = current_time + + try: + data, addr = sock.recvfrom(1024) + message = json.loads(data.decode('utf-8')) + value = message.get("value", 0) + delta_value = message.get("delta", 0) + + if value: + networked_lights.color = sample_color_gradient(value) + if delta_value: + networked_lights.expected_rotation_delta = delta_value + except BlockingIOError: + pass + + # todo: get input from local sensors + + local_lights.process(delta) + networked_lights.process(delta) + + time.sleep(0.03) \ No newline at end of file diff --git a/rpi/stepper-test.py b/rpi/stepper-test.py new file mode 100644 index 0000000..e01ba7a --- /dev/null +++ b/rpi/stepper-test.py @@ -0,0 +1,69 @@ +#!/usr/bin/python3 +import RPi.GPIO as GPIO +import time + +out1 = 17 +out2 = 27 +out3 = 22 +out4 = 23 + +# careful lowering this, at some point you run into the mechanical limitation of how quick your motor can move +step_sleep = 0.002 + +step_count = 200 + +# setting up +GPIO.setmode( GPIO.BCM ) +GPIO.setup( out1, GPIO.OUT ) +GPIO.setup( out2, GPIO.OUT ) +GPIO.setup( out3, GPIO.OUT ) +GPIO.setup( out4, GPIO.OUT ) + +# initializing +GPIO.output( out1, GPIO.LOW ) +GPIO.output( out2, GPIO.LOW ) +GPIO.output( out3, GPIO.LOW ) +GPIO.output( out4, GPIO.LOW ) + + +def cleanup(): + GPIO.output( out1, GPIO.LOW ) + GPIO.output( out2, GPIO.LOW ) + GPIO.output( out3, GPIO.LOW ) + GPIO.output( out4, GPIO.LOW ) + GPIO.cleanup() + + +# the meat +try: + i = 0 + for i in range(step_count): + if i%4==0: + GPIO.output( out4, GPIO.HIGH ) + GPIO.output( out3, GPIO.LOW ) + GPIO.output( out2, GPIO.LOW ) + GPIO.output( out1, GPIO.LOW ) + elif i%4==1: + GPIO.output( out4, GPIO.LOW ) + GPIO.output( out3, GPIO.LOW ) + GPIO.output( out2, GPIO.HIGH ) + GPIO.output( out1, GPIO.LOW ) + elif i%4==2: + GPIO.output( out4, GPIO.LOW ) + GPIO.output( out3, GPIO.HIGH ) + GPIO.output( out2, GPIO.LOW ) + GPIO.output( out1, GPIO.LOW ) + elif i%4==3: + GPIO.output( out4, GPIO.LOW ) + GPIO.output( out3, GPIO.LOW ) + GPIO.output( out2, GPIO.LOW ) + GPIO.output( out1, GPIO.HIGH ) + + time.sleep( step_sleep ) + +except KeyboardInterrupt: + cleanup() + exit( 1 ) + +cleanup() +exit( 0 ) \ No newline at end of file diff --git a/rpi/stepper.py b/rpi/stepper.py new file mode 100644 index 0000000..a7ca79d --- /dev/null +++ b/rpi/stepper.py @@ -0,0 +1,50 @@ +import RPi.GPIO as GPIO +import time + +class StepperMotor: + pins = (17, 27, 22, 23) + step_sleep = 0.002 + step_count = 200 + + def __init__(self): + GPIO.setmode( GPIO.BCM ) + for pin in self.pins: + GPIO.setup( pin, GPIO.OUT ) + GPIO.output( pin, GPIO.LOW ) + + def cleanup(self): + for pin in self.pins: + GPIO.output( pin, GPIO.LOW ) + GPIO.cleanup() + + def _set_pins(self, out1, out2, out3, out4): + GPIO.output( self.pins[0], out1 ) + GPIO.output( self.pins[1], out2 ) + GPIO.output( self.pins[2], out3 ) + GPIO.output( self.pins[3], out4 ) + + current_step = 0 + def _apply_single_step(self): + if self.current_step%4==0: + self._set_pins(GPIO.HIGH, GPIO.LOW, GPIO.LOW, GPIO.LOW) + elif self.current_step%4==1: + self._set_pins(GPIO.LOW, GPIO.HIGH, GPIO.LOW, GPIO.LOW) + elif self.current_step%4==2: + self._set_pins(GPIO.LOW, GPIO.LOW, GPIO.HIGH, GPIO.LOW) + elif self.current_step%4==3: + self._set_pins(GPIO.LOW, GPIO.LOW, GPIO.LOW, GPIO.HIGH) + time.sleep(self.step_sleep) + def single_step(self): + self._apply_single_step() + self.current_step += 1 + def single_step_back(self): + self.current_step -= 1 + self._apply_single_step() + def step(self, steps): + for _ in range(abs(steps)): + if steps > 0: + self.single_step() + else: + self.single_step_back() + def pos(self): + return self.current_step % self.step_count \ No newline at end of file