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.
This commit is contained in:
parent
bf1666fb32
commit
33c2417534
@ -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
|
||||
|
21
include/recorder.h
Normal file
21
include/recorder.h
Normal file
@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include "my_wifi.h"
|
||||
#include "config.h"
|
||||
#include <ESPAsyncTCP.h>
|
||||
#include <WiFiUdp.h>
|
||||
|
||||
#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
|
@ -19,6 +19,7 @@ lib_deps =
|
||||
https://github.com/fabianonline/NTPClient.git
|
||||
ESP8266WebServer
|
||||
ErriezCRC32
|
||||
ESPAsyncTCP
|
||||
|
||||
[env:ota]
|
||||
upload_port = 10.10.2.78
|
||||
|
@ -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)
|
||||
|
67
src/recorder.cpp
Normal file
67
src/recorder.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
#include "recorder.h"
|
||||
#include "my_fastled.h"
|
||||
#include "functions.h"
|
||||
#include "effects.h"
|
||||
#include <WiFiUdp.h>
|
||||
|
||||
#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; y<LED_HEIGHT; y++) for(uint8_t x=0; x<LED_WIDTH; x++) {
|
||||
uint16_t index = XYsafe(x, y);
|
||||
CRGB pixel = leds[index];
|
||||
_buffer[2 + (y*LED_WIDTH + x)*3 + 0] = (pixel.r==255 ? 254 : pixel.r);
|
||||
_buffer[2 + (y*LED_WIDTH + x)*3 + 1] = (pixel.g==255 ? 254 : pixel.g);
|
||||
_buffer[2 + (y*LED_WIDTH + x)*3 + 2] = (pixel.b==255 ? 254 : pixel.b);
|
||||
}
|
||||
_buffer[_buffer_len - 1] = 255;
|
||||
_udp.beginPacket("10.10.2.1", 13330);
|
||||
_udp.write(_buffer, _buffer_len);
|
||||
_udp.endPacket();
|
||||
_msgid++;
|
||||
//_client->write(_buffer, _buffer_len);
|
||||
}
|
||||
}
|
||||
#endif
|
20
src/tools/generate_gifs.sh
Executable file
20
src/tools/generate_gifs.sh
Executable file
@ -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
|
59
src/tools/monitor.rb
Normal file
59
src/tools/monitor.rb
Normal file
@ -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
|
93
src/tools/recorder.rb
Executable file
93
src/tools/recorder.rb
Executable file
@ -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
|
Loading…
Reference in New Issue
Block a user