Skip to content

Commit

Permalink
use ui inputs for remote rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
jackw01 committed Sep 3, 2023
1 parent bddf692 commit cd1e454
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 98 deletions.
2 changes: 1 addition & 1 deletion firmware/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@

#define StripType SK6812_STRIP_GRBW // See definitions in ws2812b.h
#define LEDPin 12 // RP2040 GPIO Pin
#define LEDCount 60 // Number of LEDs
#define LEDCount 144 // Number of LEDs
4 changes: 2 additions & 2 deletions firmware/wifi.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,14 @@ void wifi_init() {

cyw43_arch_lwip_begin();

cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1);

printf("IP address: %s\n", ip4addr_ntoa(netif_ip4_addr(netif_list)));

pcb = udp_new();
udp_bind(pcb, IP_ADDR_ANY, UDPPort);
udp_recv(pcb, udp_receive, 0);

cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1);

cyw43_arch_lwip_end();
}

Expand Down
5 changes: 1 addition & 4 deletions ledcontrol/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ def main():
help='Do not reset the animation timer when patterns are changed. Default: False')
parser.add_argument('--dev', action='store_true',
help='Development flag. Default: False')
parser.add_argument('--serial_port',
help='Serial port for external LED driver.')
args = parser.parse_args()

app = create_app(args.led_count,
Expand All @@ -55,8 +53,7 @@ def main():
args.sacn,
args.hap,
args.no_timer_reset,
args.dev,
args.serial_port)
args.dev)

if args.dev:
app.run(host=args.host, port=args.port)
Expand Down
122 changes: 79 additions & 43 deletions ledcontrol/animationcontroller.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from itertools import zip_longest
from ledcontrol.intervaltimer import IntervalTimer

import ledcontrol.ledcontroller as ledcontroller
import ledcontrol.animationfunctions as animfunctions
import ledcontrol.colorpalettes as colorpalettes
import ledcontrol.driver as driver
Expand Down Expand Up @@ -100,6 +101,7 @@ def __init__(self,

# Initialize sACN / E1.31
if enable_sacn:
self._sacn_buffer = [(0, 0, 0) for i in range(self._led_count)]
self._last_sacn_time = 0
self._sacn_perf_avg = 0
self._sacn_count = 0
Expand Down Expand Up @@ -196,7 +198,6 @@ def recursive_update(d1, d2):
elif k in ['range_start', 'range_end']:
self._flag_clear = True # clear LEDs to make range selection less ambiguous
elif k in ['render_mode', 'render_target']:
# todo
self._flag_clear = True
elif k == 'sacn' and self._enable_sacn:
if v:
Expand Down Expand Up @@ -336,12 +337,20 @@ def update_leds(self):
print(f'Execution time: {self._timer.get_perf_avg():0.5f}s, {self._timer.get_rate():05.1f} FPS')

if self._settings['calibration'] == 1:
self._led_controller.show_calibration_color(self._led_count,
self._correction,
self._settings['global_brightness'] / 2)
for group, settings in list(self._settings['groups'].items()):
range_start = settings['range_start']
range_end = min(self._led_count, settings['range_end'])
self._led_controller.show_calibration_color(
range_end - range_start,
self._correction,
self._settings['global_brightness'] / 2,
settings['render_mode'],
settings['render_target']
)

return

if self._update_needed and self._settings['sacn'] == 0:
if self._update_needed:
self._update_needed = False
# Store dict keys as list in case they are changed during iteration
for group, settings in list(self._settings['groups'].items()):
Expand All @@ -367,35 +376,60 @@ def update_leds(self):
continue

try:
# Determine current pattern mode
c, mode = function_1(0, 0.1, 0, 0, 0, (0, 0, 0))

# Run pattern to determine color
state = [function_1(time_1,
delta_t_1,
mapping[i][0],
mapping[i][1],
mapping[i][2],
self._prev_state[i])[0]
for i in range(range_start, range_end)]
self._prev_state[range_start:range_end] = state

self._led_controller.set_range(state, range_start, range_end,
self._correction,
computed_saturation,
computed_brightness,
mode)
if self._settings['sacn'] == 0:
# Determine current pattern mode
c, mode = function_1(0, 0.1, 0, 0, 0, (0, 0, 0))

# Run pattern to determine color
state = [function_1(time_1,
delta_t_1,
mapping[i][0],
mapping[i][1],
mapping[i][2],
self._prev_state[i])[0]
for i in range(range_start, range_end)]
self._prev_state[range_start:range_end] = state

self._led_controller.set_range(
state,
range_start,
range_end,
self._correction,
computed_saturation,
computed_brightness,
mode,
settings['render_mode'],
settings['render_target']
)

else:
self._led_controller.set_range(
[self._sacn_buffer[i] for i in range(range_start, range_end)],
range_start,
range_end,
self._correction,
1.0,
self._settings['global_brightness'],
animfunctions.ColorMode.rgb,
settings['render_mode'],
settings['render_target']
)

except Exception as e:
msg = traceback.format_exception(type(e), e, e.__traceback__)
print(f'Animation execution: {msg}')
print(f'Error during animation execution: {msg}')
r = 0.1 * driver.wave_pulse(time_fix, 0.5)
self._led_controller.set_range([(r, 0, 0) for i in range(self._led_count)],
0, self._led_count,
self._correction,
1.0,
1.0,
animfunctions.ColorMode.rgb)
self._led_controller.set_range(
[(r, 0, 0) for i in range(range_end - range_start)],
range_start,
range_end,
self._correction,
1.0,
1.0,
animfunctions.ColorMode.rgb,
settings['render_mode'],
settings['render_target']
)
self._led_controller.render()
return

Expand All @@ -421,22 +455,24 @@ def _sacn_callback(self, packet):
self._sacn_perf_avg = 0

data = [x / 255.0 for x in packet.dmxData[:self._led_count * 3]]
self._led_controller.set_range(list(zip_longest(*(iter(data),) * 3)),
0, self._led_count,
self._correction,
1.0,
self._settings['global_brightness'],
animfunctions.ColorMode.rgb)
self._led_controller.render()
self._sacn_buffer = list(zip_longest(*(iter(data),) * 3))

def clear_leds(self):
'Turn all LEDs off'
self._led_controller.set_range([(0, 0, 0) for i in range(self._led_count)],
0, self._led_count,
self._correction,
1.0,
1.0,
animfunctions.ColorMode.rgb)
for group, settings in list(self._settings['groups'].items()):
range_start = settings['range_start']
range_end = min(self._led_count, settings['range_end'])
self._led_controller.set_range(
[(0, 0, 0) for i in range(range_end - range_start)],
range_start,
range_end,
self._correction,
1.0,
1.0,
animfunctions.ColorMode.rgb,
settings['render_mode'],
settings['render_target']
)
self._led_controller.render()

def end_animation(self):
Expand Down
6 changes: 2 additions & 4 deletions ledcontrol/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ def create_app(led_count,
enable_sacn,
enable_hap,
no_timer_reset,
dev,
serial_port):
dev):
app = Flask(__name__)

# Create pixel mapping function
Expand All @@ -49,8 +48,7 @@ def create_app(led_count,
led_pin,
led_data_rate,
led_dma_channel,
led_pixel_order,
serial_port)
led_pixel_order)
controller = AnimationController(leds,
refresh_rate,
led_count,
Expand Down
101 changes: 57 additions & 44 deletions ledcontrol/ledcontroller.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,25 @@
import numpy as np
import itertools
import socket

import traceback
from enum import Enum

import ledcontrol.animationfunctions as animfunctions
import ledcontrol.driver as driver
import ledcontrol.utils as utils

TargetMode = Enum('TargetMode', ['serial', 'udp'])
class TargetMode(str, Enum):
local = 'local'
serial = 'serial'
udp = 'udp'

class LEDController:
def __init__(self,
led_count,
led_pin,
led_data_rate,
led_dma_channel,
led_pixel_order,
serial_port):
led_pixel_order):
if driver.is_raspberrypi():
# This is bad but it's the only way
px_order = driver.WS2811_STRIP_GRB
Expand Down Expand Up @@ -86,18 +88,15 @@ def __init__(self,
if resp != 0:
str_resp = driver.ws2811_get_return_t_str(resp)
raise RuntimeError('ws2811_init failed with code {0} ({1})'.format(resp, str_resp))
else:
self._where_hue = np.zeros((led_count * 3,),dtype=bool)
self._where_hue[0::3] = True

self._target_mode = TargetMode.udp
# Used for scaling values sent for remote rendering
self._where_hue = np.zeros((led_count * 3,), dtype=bool)
self._where_hue[0::3] = True

self._targets_serial = {}
self._targets_udp = {}

if self._target_mode == TargetMode.serial:
self._serial = serial.Serial(serial_port, 115200, timeout=0.01, write_timeout=0)
elif self._target_mode == TargetMode.udp:
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self._udp_target = "lcpico-0e3062"
self._udp_port = 8888
self._udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

def _cleanup(self):
# Clean up memory used by the library when not needed anymore
Expand All @@ -106,17 +105,21 @@ def _cleanup(self):
self._leds = None
self._channel = None

def set_range(self, pixels, start, end, correction, saturation, brightness, color_mode):
if driver.is_raspberrypi():
if color_mode == animfunctions.ColorMode.hsv:
driver.ws2811_hsv_render_range_float(self._channel, pixels, start, end,
correction, saturation, brightness, 1.0,
self._has_white)
else:
driver.ws2811_rgb_render_range_float(self._channel, pixels, start, end,
correction, saturation, brightness, 1.0,
self._has_white)

def set_range(self, pixels, start, end,
correction, saturation, brightness, color_mode,
render_mode, render_target):
if render_mode == TargetMode.local:
if driver.is_raspberrypi():
if color_mode == animfunctions.ColorMode.hsv:
driver.ws2811_hsv_render_range_float(self._channel, pixels,
start, end,
correction, saturation, brightness, 1.0,
self._has_white)
else:
driver.ws2811_rgb_render_range_float(self._channel, pixels,
start, end,
correction, saturation, brightness, 1.0,
self._has_white)
else:
data = np.fromiter(itertools.chain.from_iterable(pixels), np.float32)
if color_mode == animfunctions.ColorMode.hsv:
Expand All @@ -135,30 +138,40 @@ def set_range(self, pixels, start, end, correction, saturation, brightness, colo
+ start.to_bytes(2, 'big')
+ end.to_bytes(2, 'big')
+ data.tobytes())
self._send(packet)

def show_calibration_color(self, count, correction, brightness):
if driver.is_raspberrypi():
driver.ws2811_rgb_render_calibration(self._leds, self._channel, count,
correction, brightness)
self._send(packet, render_mode, render_target)
self._send(b'\x00\x03\x00\x05\x00', render_mode, render_target)

def show_calibration_color(self, count, correction, brightness,
render_mode, render_target):
if render_mode == TargetMode.local:
if driver.is_raspberrypi():
driver.ws2811_rgb_render_calibration(self._leds,
self._channel, self._count,
correction, brightness)
else:
packet = (b'\x00\x00\x00\x08'
+ correction.to_bytes(3, 'big')
+ int(brightness * 255).to_bytes(1, 'big'))
self._send(packet)
self._send(packet, render_mode, render_target)

def render(self):
# send global render command if output mode is raspberry pi
if driver.is_raspberrypi():
driver.ws2811_render(self._leds)
else:
packet = b'\x00\x03\x00\x05\x00'
self._send(packet)

def _send(self, packet):
if self._target_mode == TargetMode.serial:
self._serial.write(packet)
elif self._target_mode == TargetMode.udp:
try:
self._socket.sendto(packet, (self._udp_target, self._udp_port))
except:
pass

def _send(self, packet, mode, target):
try:
if mode == TargetMode.serial:
if target not in self._targets_serial:
self._targets_serial[target] = serial.Serial(target,
115200,
timeout=0.01,
write_timeout=0)
self._targets_serial[target].write(packet)
elif mode == TargetMode.udp:
if target not in self._targets_udp:
self._targets_udp[target] = (socket.gethostbyname(target), 8888)
self._udp_socket.sendto(packet, self._targets_udp[target])
except Exception as e:
msg = traceback.format_exception(type(e), e, e.__traceback__)
print(f'Error during remote rendering: {msg}')

0 comments on commit cd1e454

Please sign in to comment.