Last active
September 10, 2022 22:21
-
-
Save danasf/868bdc2a7171cb486e56c22cd1520b82 to your computer and use it in GitHub Desktop.
Noisebridge Square Table Sketch - 2022.09.07
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extern "C" { | |
#include "osapi.h" | |
#include "ets_sys.h" | |
#include "user_interface.h" | |
} | |
// From https://github.com/miloh/esp8266ft/blob/master/square_noise_server/square_noise_server.ino | |
#include <lwip/udp.h> | |
#include <ESP8266WiFi.h> | |
#include "FastLED.h" | |
FASTLED_USING_NAMESPACE | |
const char* ssid = "Noisebridge Cap"; | |
#define BRIGHTNESS 160 | |
#define NETWORK_TIMEOUT 10 | |
int count_leds(const int* num_leds_arr, const int len) { | |
int count = 0; | |
for (int i = 0; i < len; i++) { | |
count += num_leds_arr[i]; | |
} | |
return count; | |
} | |
const int NUM_LEDS_PER_STRIP[1] = {291}; | |
const int NUM_LEDS = count_leds(NUM_LEDS_PER_STRIP, 1); | |
const int WIDTH = NUM_LEDS; | |
const int HEIGHT = 1; | |
udp_pcb *_pcb; | |
bool unhandled = 0; | |
void recv(void *arg, | |
udp_pcb *upcb, pbuf *p, | |
ip4_addr *addr, short unsigned int port); | |
#define IPADDR_ANY ((u32_t)0x00000000UL) | |
static const int kBufferSize = 5000; | |
char packetBuffer[kBufferSize]; | |
int packetSize = 0; | |
static CRGB leds[810]; | |
int last = 0; | |
int fps = 30; | |
int time_between_frames = 1000 / fps; | |
int attempt_count = 0; | |
int last_packet = 0; | |
int max_incoming_fps = fps; | |
int min_packet_interval = 1000 / max_incoming_fps; | |
// List of patterns to cycle through. Each is defined as a separate function below. | |
typedef void (*SimplePatternList[])(); | |
SimplePatternList gPatterns = { rainbow, rainbowWithGlitter, confetti, sinelon, juggle, bpm }; | |
uint8_t gCurrentPatternNumber = 0; // Index number of which pattern is current | |
uint8_t gHue = 0; // rotating "base color" used by many of the patterns | |
struct ImageMetaInfo { | |
int width; | |
int height; | |
int range; // Range of gray-levels. We only handle 255 correctly(=1byte) | |
// FlaschenTaschen extensions | |
int offset_x; // display image at this x-offset | |
int offset_y; // .. y-offset | |
int layer; // stacked layer | |
}; | |
void setup() { | |
Serial.begin(115200); | |
delay(500); | |
Serial.println(); | |
Serial.println(); | |
Serial.print("Connecting to "); | |
Serial.println(ssid); | |
WiFi.begin(ssid); | |
// WiFi.begin(ssid, password); | |
while (WiFi.status() != WL_CONNECTED && attempt_count < 40) { | |
delay(500); | |
Serial.print("."); | |
attempt_count++; | |
} | |
if(WiFi.status() == WL_CONNECTED) { | |
Serial.println(""); | |
Serial.println("WiFi connected"); | |
Serial.println("IP address: "); | |
Serial.println(WiFi.localIP()); | |
} else { | |
Serial.println("WiFi connection didn't work, oh well, moving forward anyway."); | |
} | |
_pcb = udp_new(); | |
udp_recv(_pcb, &recv, 0); | |
ip_addr_t addr; | |
addr.addr = IPADDR_ANY; | |
int port = 1337; | |
udp_bind(_pcb, &addr, port); | |
Serial.println("listening to udp on port 1337"); | |
// use GPIO 5, not "D1" per https://www.reddit.com/r/esp8266/comments/9i8jkr/neopixels_on_esp8266_not_working_after_upgrade/ | |
FastLED.addLeds<WS2812B, 5, GRB>(leds, count_leds(NUM_LEDS_PER_STRIP, 0), NUM_LEDS_PER_STRIP[0]); | |
FastLED.setBrightness(BRIGHTNESS); | |
} | |
void loop() { | |
if (unhandled) { | |
int start = millis(); | |
ImageMetaInfo img_info = {0}; | |
img_info.width = WIDTH; | |
img_info.height = HEIGHT; | |
const char *pixel_pos = ReadImageData(packetBuffer, packetSize, | |
&img_info); | |
for (int x = 0; x < img_info.width; ++x) { | |
const byte red = *pixel_pos++; | |
const byte green = *pixel_pos++; | |
const byte blue = *pixel_pos++; | |
leds[x + img_info.offset_x] = CRGB(red, green, blue); | |
} | |
// If this runs too frequently, the network traffic can back up and overwhelm the network stack, which | |
// doesn't seem to gracefully discard packets. | |
if (millis() - last > time_between_frames) { | |
FastLED.show(); | |
} | |
last = millis(); | |
unhandled = 0; | |
} else if (millis() - last_packet > (1000 * NETWORK_TIMEOUT) ) { | |
if (millis() - last < time_between_frames) { | |
return; | |
} | |
int start = millis(); | |
// Call the current pattern function once, updating the 'leds' array | |
gPatterns[gCurrentPatternNumber](); | |
// rainbowWithGlitter(); | |
FastLED.show(); | |
} | |
EVERY_N_MILLISECONDS( 20 ) { | |
gHue++; // slowly cycle the "base color" through the rainbow | |
} | |
EVERY_N_SECONDS( 20 ) { nextPattern(); } // change patterns periodically | |
} | |
void recv(void *arg, | |
udp_pcb *upcb, pbuf *p, | |
const ip4_addr *addr, u16_t port) { | |
packetSize = p->tot_len; | |
pbuf_copy_partial(p, packetBuffer, kBufferSize, 0); | |
pbuf_free(p); | |
unhandled = 1; | |
last_packet = millis(); | |
} | |
static const char *skipWhitespace(const char *buffer, const char *end) { | |
for (;;) { | |
while (buffer < end && isspace(*buffer)) | |
++buffer; | |
if (buffer >= end) | |
return NULL; | |
if (*buffer == '#') { | |
while (buffer < end && *buffer != '\n') // read to end of line. | |
++buffer; | |
continue; // Back to whitespace eating. | |
} | |
return buffer; | |
} | |
} | |
// Read next number. Start reading at *start; modifies the *start pointer | |
// to point to the character just after the decimal number or NULL if reading | |
// was not successful. | |
static int readNextNumber(const char **start, const char *end) { | |
const char *start_number = skipWhitespace(*start, end); | |
if (start_number == NULL) { | |
*start = NULL; | |
return 0; | |
} | |
char *end_number = NULL; | |
int result = strtol(start_number, &end_number, 10); | |
if (end_number == start_number) { | |
*start = NULL; | |
return 0; | |
} | |
*start = end_number; | |
return result; | |
} | |
const char *ReadImageData(const char *in_buffer, size_t buf_len, | |
struct ImageMetaInfo *info) { | |
if (in_buffer[0] != 'P' || in_buffer[1] != '6' || | |
(!isspace(in_buffer[2]) && in_buffer[2] != '#')) { | |
return in_buffer; // raw image. No P6 magic header. | |
} | |
const char *const end = in_buffer + buf_len; | |
const char *parse_buffer = in_buffer + 2; | |
const int width = readNextNumber(&parse_buffer, end); | |
if (parse_buffer == NULL) return in_buffer; | |
const int height = readNextNumber(&parse_buffer, end); | |
if (parse_buffer == NULL) return in_buffer; | |
const int range = readNextNumber(&parse_buffer, end); | |
if (parse_buffer == NULL) return in_buffer; | |
if (!isspace(*parse_buffer++)) return in_buffer; // last char before data | |
// Now make sure that the rest of the buffer still makes sense | |
const size_t expected_image_data = width * height * 3; | |
const size_t actual_data = end - parse_buffer; | |
if (actual_data < expected_image_data) | |
return in_buffer; // Uh, not enough data. | |
if (actual_data > expected_image_data) { | |
// Our extension: at the end of the binary data, we provide an optional | |
// offset. We can't store it in the header, as it is fixed in number | |
// of fields. But nobody cares what is at the end of the buffer. | |
const char *offset_data = parse_buffer + expected_image_data; | |
info->offset_x = readNextNumber(&offset_data, end); | |
if (offset_data != NULL) { | |
info->offset_y = readNextNumber(&offset_data, end); | |
} | |
if (offset_data != NULL) { | |
info->layer = readNextNumber(&offset_data, end); | |
} | |
} | |
info->width = width; | |
info->height = height; | |
info->range = range; | |
return parse_buffer; | |
} | |
#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0])) | |
void nextPattern() | |
{ | |
// add one to the current pattern number, and wrap around at the end | |
gCurrentPatternNumber = (gCurrentPatternNumber + 1) % ARRAY_SIZE( gPatterns); | |
} | |
void rainbow() | |
{ | |
// FastLED's built-in rainbow generator | |
fill_rainbow( leds, NUM_LEDS, gHue, 7); | |
} | |
void addGlitter( fract8 chanceOfGlitter); | |
void rainbowWithGlitter() | |
{ | |
//Serial.println("rainbowWithGlitter()"); | |
// built-in FastLED rainbow, plus some random sparkly glitter | |
rainbow(); | |
addGlitter(80); | |
} | |
void addGlitter( fract8 chanceOfGlitter) | |
{ | |
if ( random8() < chanceOfGlitter) { | |
leds[ random16(NUM_LEDS) ] += CRGB::White; | |
} | |
} | |
void confetti() | |
{ | |
// random colored speckles that blink in and fade smoothly | |
fadeToBlackBy( leds, NUM_LEDS, 10); | |
int pos = random16(NUM_LEDS); | |
leds[pos] += CHSV( gHue + random8(64), 200, 255); | |
} | |
void sinelon() | |
{ | |
// a colored dot sweeping back and forth, with fading trails | |
fadeToBlackBy( leds, NUM_LEDS, 20); | |
int pos = beatsin16( 13, 0, NUM_LEDS-1 ); | |
leds[pos] += CHSV( gHue, 255, 192); | |
} | |
void bpm() | |
{ | |
// colored stripes pulsing at a defined Beats-Per-Minute (BPM) | |
uint8_t BeatsPerMinute = 62; | |
CRGBPalette16 palette = PartyColors_p; | |
uint8_t beat = beatsin8( BeatsPerMinute, 64, 255); | |
for( int i = 0; i < NUM_LEDS; i++) { //9948 | |
leds[i] = ColorFromPalette(palette, gHue+(i*2), beat-gHue+(i*10)); | |
} | |
} | |
void juggle() { | |
// eight colored dots, weaving in and out of sync with each other | |
fadeToBlackBy( leds, NUM_LEDS, 20); | |
uint8_t dothue = 0; | |
for( int i = 0; i < 8; i++) { | |
leds[beatsin16( i+7, 0, NUM_LEDS-1 )] |= CHSV(dothue, 200, 255); | |
dothue += 32; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment