From 33c2417534a4fbdfcbedfa9eae83232ab33fb9b7 Mon Sep 17 00:00:00 2001 From: Fabian Schlenz Date: Wed, 4 Sep 2019 06:05:45 +0200 Subject: [PATCH] Added recorder to be able to stream the current LED data via network. That way you can create nice looking GIF images of the effects - and even develop effects without having to look at the actual LED panel. --- include/config.sample.h | 3 ++ include/recorder.h | 21 +++++++++ platformio.ini | 1 + src/pitrix.cpp | 11 +++++ src/recorder.cpp | 67 +++++++++++++++++++++++++++ src/tools/generate_gifs.sh | 20 ++++++++ src/tools/monitor.rb | 59 ++++++++++++++++++++++++ src/tools/recorder.rb | 93 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 275 insertions(+) create mode 100644 include/recorder.h create mode 100644 src/recorder.cpp create mode 100755 src/tools/generate_gifs.sh create mode 100644 src/tools/monitor.rb create mode 100755 src/tools/recorder.rb diff --git a/include/config.sample.h b/include/config.sample.h index f5b6a97..8fcda69 100644 --- a/include/config.sample.h +++ b/include/config.sample.h @@ -42,6 +42,9 @@ #define FPS 50 #define SHOW_TEXT_DELAY 100 +#define RECORDER_ENABLE +#define RECORDER_PORT 2122 + #define MONITOR_LOOP_TIMES false #define MONITOR_LOOP_TIME_THRESHOLD 500 #define MONITOR_LOOP_TIME_COUNT_MAX 10 diff --git a/include/recorder.h b/include/recorder.h new file mode 100644 index 0000000..37e14c3 --- /dev/null +++ b/include/recorder.h @@ -0,0 +1,21 @@ +#pragma once +#include "my_wifi.h" +#include "config.h" +#include +#include + +#ifdef RECORDER_ENABLE +class Recorder { +private: + WiFiUDP _udp; + AsyncServer* _server; + AsyncClient* _client = NULL; + uint16_t _client_port = 0; + size_t _buffer_len; + char* _buffer; + uint16_t _msgid; +public: + Recorder(); + void loop(); +}; +#endif diff --git a/platformio.ini b/platformio.ini index a5ba8cc..a8a7978 100644 --- a/platformio.ini +++ b/platformio.ini @@ -19,6 +19,7 @@ lib_deps = https://github.com/fabianonline/NTPClient.git ESP8266WebServer ErriezCRC32 + ESPAsyncTCP [env:ota] upload_port = 10.10.2.78 diff --git a/src/pitrix.cpp b/src/pitrix.cpp index ae4a0cb..d9f4c5c 100644 --- a/src/pitrix.cpp +++ b/src/pitrix.cpp @@ -9,12 +9,16 @@ #include "functions.h" #include "effects.h" #include "http_server.h" +#include "recorder.h" uint8_t starting_up = OTA_STARTUP_DELAY; int loop_timeouts = 0; long loop_started_at = 0; uint8_t baseHue = 0; // defined as extern in prototypes.h char hostname[30]; // defined as extern in prototypes.h +#ifdef RECORDER_ENABLE +Recorder* recorder; +#endif void setup() { Serial.begin(74880); @@ -40,6 +44,9 @@ void setup() { #ifdef MQTT_ENABLE mqtt_setup(); #endif + #ifdef RECORDER_ENABLE + recorder = new Recorder(); + #endif SPIFFS.begin(); LOGln("Core * Setup complete"); } @@ -84,6 +91,10 @@ void loop() { effect_clock.loop(current_effect->clock_as_mask(), CRGB(0xFFFFFF), CRGB(0x000000)); } FastLED.show(); + + #ifdef RECORDER_ENABLE + recorder->loop(); + #endif } #if defined(MQTT_ENABLE) && defined(MQTT_REPORT_METRICS) diff --git a/src/recorder.cpp b/src/recorder.cpp new file mode 100644 index 0000000..cd886b7 --- /dev/null +++ b/src/recorder.cpp @@ -0,0 +1,67 @@ +#include "recorder.h" +#include "my_fastled.h" +#include "functions.h" +#include "effects.h" +#include + +#ifdef RECORDER_ENABLE + +Recorder::Recorder() { + _buffer_len = LED_WIDTH * LED_HEIGHT * 3 + 3; + _buffer = (char*)malloc(_buffer_len); + _server = new AsyncServer(RECORDER_PORT); + _server->onClient([&](void* a, AsyncClient* c) { + LOGln("Recorder * New client: %s. Waiting for port number...", c->remoteIP().toString().c_str()); + if (_client) { + LOGln("Recorder * Killing old client."); + _client->close(); + _client_port = 0; + delete _client; + } + _client = c; + _msgid = 0; + char dim[3] = {LED_WIDTH, LED_HEIGHT, 255}; + _client->write(dim, 3); + _client->onDisconnect([&](void* a, AsyncClient* client) { + LOGln("Recorder * Client disconnected"); + _client_port = 0; + }, NULL); + _client->onData([&](void* a, AsyncClient* client, void* data, size_t len) { + if (*(char*)data == 'P') { + LOGln("Found."); + if (len >= 3) { + uint8_t* d = (uint8_t*)data; + _client_port = d[1]<<8 | d[2]; + LOGln("Recorder * Sending data to port %d", _client_port); + } + } else if (*(char*)data == 'E') { + String effect = String(((char*)(data+1))); + LOGln("Recorder * Setting effect %s", effect.c_str()); + change_current_effect(effect); + } + }, NULL); + }, _server); + _server->begin(); + LOGln("Recorder * Listening on port %d", RECORDER_PORT); +} + +void Recorder::loop() { + if (_client && _client_port) { + _buffer[0] = _msgid >> 8; + _buffer[1] = _msgid & 0xFF; + for (uint8_t y=0; ywrite(_buffer, _buffer_len); + } +} +#endif diff --git a/src/tools/generate_gifs.sh b/src/tools/generate_gifs.sh new file mode 100755 index 0000000..263a881 --- /dev/null +++ b/src/tools/generate_gifs.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +IP="$1" +EFFECTS=`egrep "case" ../effects.cpp | tr -s "\t" " " | cut -d" " -f 7 | sort` + +mkdir effects + +for effect in $EFFECTS; do + [ "$effect" = "cycle" ] && continue + [ "$effect" = "off" ] && continue + [ "$effect" = "koopa" ] && continue + [ "$effect" = "couple_rain" ] && continue + [ "$effect" = "cake" ] && continue + + echo " + ./recorder.rb $IP /tmp/effect.gif $effect" + ./recorder.rb $IP /tmp/effect.gif $effect + echo + echo " + gifsicle /tmp/effect.gif -o effects/$effect.gif" + gifsicle /tmp/effect.gif -o effects/$effect.gif +done diff --git a/src/tools/monitor.rb b/src/tools/monitor.rb new file mode 100644 index 0000000..dff7ad6 --- /dev/null +++ b/src/tools/monitor.rb @@ -0,0 +1,59 @@ +#!/usr/bin/env ruby +require 'socket' +require 'pp' + +def rgb2ansi(r, g, b) + if r==g && g==b + return 16 if r<8 + return 231 if r>248 + return (((r - 8) / 247.0) * 24).round + 232 + end + + return 16 + 36*(r/51.0).round + 6*(g/51.0).round + (b/51.0).round +end + +IP = ARGV[0] +PORT = 2122 +EFFECT = ARGV[1] + +puts "Connecting to #{IP}:#{PORT}..." + +s = TCPSocket.new(IP, PORT) + +puts "Connected." +init = s.recv(3).unpack("C*") + +raise "Initial data packet wasn't usable!" if init[2] != 0xFF +puts "Got initial data packet." + +dim_x, dim_y = *init +len = dim_x * dim_y * 3 + 3 + +puts "Size: #{dim_x}x#{dim_y}. Expecting packet length #{len}." + +puts "Opening local UDP socket..." +udp = UDPSocket.new +udp.bind("10.10.2.1", 13330) +puts "Waiting for UDP packets on port 13330..." +s.sendmsg("P\x12\x34\x00") +s.sendmsg("E#{EFFECT}\x00") if EFFECT + + +while 1 + data = udp.recvfrom(1024)[0].unpack("C*") + #puts "Packet. ID: #{data[0]}, length: #{data.length}" + raise "Unexpected packet length" unless data.count == len + raise "Invalid data packet" unless data[len - 1]==0xFF + id = data.shift << 8 | data.shift + #next + #print "." + puts "\033[2J" + (0...dim_y).each do |y| + (0...dim_x).each do |x| + r, g, b = *data.shift(3) + color_code = rgb2ansi(r, g, b) + print "\033[48;5;#{color_code}m " + end + puts "\033[0m" + end +end diff --git a/src/tools/recorder.rb b/src/tools/recorder.rb new file mode 100755 index 0000000..f0b1837 --- /dev/null +++ b/src/tools/recorder.rb @@ -0,0 +1,93 @@ +#!/usr/bin/env ruby +require 'socket' +require 'pp' +require 'rmagick' + +include Magick + +IP = ARGV[0] +PORT = 2122 +FILE = ARGV[1] or raise "No filename given" +EFFECT = ARGV[2] +FRAMES = 250 +FACTOR = 1 +delay = 50 + +puts "Connecting to #{IP}:#{PORT}..." + +s = TCPSocket.new(IP, PORT) + +puts "Connected." +init = s.recv(3).unpack("C*") + +raise "Initial data packet wasn't usable!" if init[2] != 0xFF +puts "Got initial data packet." + +dim_x, dim_y = *init +len = dim_x * dim_y * 3 + 3 + +puts "Size: #{dim_x}x#{dim_y}. Expecting packet length #{len}." + +puts "Opening local UDP socket..." +udp = UDPSocket.new +udp.bind("10.10.2.1", 13330) +puts "Waiting for UDP packets on port 13330..." +s.sendmsg("P\x12\x34\x00") +s.sendmsg("E#{EFFECT}\x00") if EFFECT + +gif = ImageList.new +last_id = 255 +last_frame_time = nil +img = nil +last_diff = nil + +while gif.length < FRAMES do + data = udp.recvfrom(1024)[0].unpack("C*") + if delay > 0 + delay -= 1 + next + end + #puts "Packet. ID: #{data[0]}, length: #{data.length}" + raise "Unexpected packet length" unless data.count == len + raise "Invalid data packet" unless data[len - 1]==0xFF + + id = data.shift << 8 | data.shift + if last_id != id-1 && last_id != id-2 + puts + gif = ImageList.new + end + last_id = id + + if img && last_frame_time + last_diff = diff = Time.now.to_f * 100 - last_frame_time.to_f * 100 + img.delay = diff + end + + last_frame_time = Time.now + + img = Image.new(dim_x, dim_y) + img.delay = 5 + gc = Draw.new + + #next + print "." + print "#{gif.length}" if gif.length%50==0 + (0...dim_y).each do |y| + (0...dim_x).each do |x| + r, g, b = *data.shift(3) + gc.fill("rgb(#{r}, #{g}, #{b})") + gc.point(x, y) + #img.pixel_color(x, y, Pixel.new(r, g, b, 255)) + end + end + gc.draw(img) + img.sample!(FACTOR) + gif << img +end +img.delay = last_diff +s.close +puts +puts "Generating gif..." +gif.write(FILE) +puts +puts