~jojo/jojos-hue

513c8db43ddf594d8b630ac84b2e15d0061b48de — JoJo 15 days ago ab767a3
Clients connect to server & Client LEDs indiv. addressable by server
6 files changed, 341 insertions(+), 122 deletions(-)

A client/jojos-hue.ino
D esp32_ws2801.ino
A server/.gitignore
A server/Cargo.lock
A server/Cargo.toml
A server/src/main.rs
A client/jojos-hue.ino => client/jojos-hue.ino +216 -0
@@ 0,0 1,216 @@
#include <FastLED.h>

#define UNO_WS2812B
// #define ESP32_WS2801

// vvvvvvv UNIT vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// ---------------------------------------------------------
#if                               defined(UNO_WS2812B)
#define LED_PINS 8
#define LED_TYPE WS2812B
#define COLOR_ORDER GRB
#define NUM_LEDS 48
#define MAX_MILLIAMPS 1000
static byte mymac[] = { 0x74,0x69,0x69,0x2D,0x30,0x31 };
#define CONN_ETH
// ---------------------------------------------------------
#elif                             defined(ESP32_WS2801)
#define LED_PINS G33, G23
#define LED_TYPE WS2801
#define COLOR_ORDER RGB
#define NUM_LEDS 50
#define MAX_MILLIAMPS 3000
#define CONN_WIFI
// ---------------------------------------------------------
#                                 else
#error No unit defined (e.g. ESP32_WS2801)
#endif
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


// vvvvvvv Connectivity vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// ---------------------------------------------------------
#if                               defined(CONN_ETH)
#include <Ethernet.h>
EthernetClient client;
#define NET
// ---------------------------------------------------------
#elif                             defined(CONN_WIFI)
// #include <WiFi.h>
// #include <WiFiClient.h>
// #include <WebServer.h>
// #include <ESPmDNS.h>
// #include "secrets.h"
// WebServer server(80);
#define NET
// ---------------------------------------------------------
#                                 else
// ...
#endif

#ifdef NET
#define SERVER_IP "192.168.0.41"
#define SERVER_PORT 7017
#endif

#define NUM_BYTES (3*NUM_LEDS)

CRGB leds[NUM_LEDS];

void setAll(CRGB c) {
    for (uint16_t i = 0 ; i < NUM_LEDS; i++) {
        leds[i] = c;
    }
}

void setEach(byte* buf) {
    for (uint16_t i = 0; i < NUM_LEDS; i++) {
        leds[i] = CRGB(buf[3*i+0], buf[3*i+1], buf[3*i+2]);
    }
}

void setup() {
    Serial.begin(115200);
    Serial.println("hello!");
#if defined(CONN_ETH)
    Ethernet.init(SS);
    Ethernet.begin(mymac);
    Serial.println("online");
    Serial.print("IP address: ");
    Serial.println(Ethernet.localIP());
#elif defined(CONN_WIFI)
    // WiFi.mode(WIFI_STA);
    // WiFi.begin(ssid, password);
    // while (WiFi.status() != WL_CONNECTED) {
    //     delay(300);
    //     Serial.print(".");
    // }
    // Serial.print("\nConnected to ");
    // Serial.println(ssid);
    // Serial.print("IP address: ");
    // Serial.println(WiFi.localIP());
    // if (MDNS.begin("esp32")) {
    //     Serial.println("MDNS responder started");
    // }
#endif

    FastLED.addLeds<LED_TYPE, LED_PINS, COLOR_ORDER>(leds, NUM_LEDS)
        .setCorrection(TypicalLEDStrip);
    FastLED.setMaxPowerInVoltsAndMilliamps(5, MAX_MILLIAMPS);
    CRGB initCols[] = {
        CRGB(255, 0, 0),
        CRGB(0, 255, 0),
        CRGB(0, 0, 255),
        CRGB(255, 255, 255),
        CRGB(0, 0, 0)
    };
    for (uint8_t i = 0; i < sizeof(initCols)/sizeof(CRGB); i++) {
        setAll(initCols[i]);
        FastLED.show();
        delay(1000);
    }
    Serial.println("setup finished");
}

#ifdef NET
bool connected() {
    return (bool)client.connected();
}
bool connect() {
    return (bool)client.connect(SERVER_IP, SERVER_PORT);
}
uint16_t readMax(uint16_t n, byte* buf) {
    int r = client.read(buf, n);
    return max(0, r);
}

uint16_t writeMax(uint16_t n, const byte* buf) {
    int r = client.write(buf, n);
    return max(0, r);
}
#endif

void writeExact(uint16_t, byte*);

void ensureConnected() {
    if (!connected()) {
        Serial.println("disconnected. connecting...");
        uint16_t tries = 0;
        const uint16_t max_tries = 200;
        while (!connect()) {
            if (tries == max_tries) {
                Serial.println("server seems to be offline");
                Serial.println("will turn off leds and keep trying");
                setAll(CRGB(0, 0, 0));
                FastLED.show();
            }
            tries = min(max_tries, tries) + 1;
            delay(300);
        }
        uint16_t x = NUM_LEDS;
        // If the write fails, fuck it. The server will just timeout the read and
        // disconnect us again and we'll retry.
        writeMax(2, (byte*)&x);
        Serial.println("connected");
    }
}

void readExact(uint16_t n, byte* buf) {
    while (n > 0) {
        uint16_t m = readMax(n, buf);
        if (m == 0) {
            ensureConnected();
            delay(10);
        }
        n -= m;
        buf += m;
    }
}

byte readSingle() {
    byte x;
    readExact(1, &x);
    return x;
}

#define MAGIC_COOKIE_LEN 5
const byte magicCookie[MAGIC_COOKIE_LEN] = {'s','a','t','a','n'};

bool isMagicCookie(byte* buf) {
    uint16_t i = 0;
    for (uint16_t i = 0; i < MAGIC_COOKIE_LEN; i++) {
        if (magicCookie[i] != buf[i]) {
            return false;
        }
    }
    return true;
}

void sync() {
    byte buf[MAGIC_COOKIE_LEN];
    readExact(MAGIC_COOKIE_LEN, buf);
    if (isMagicCookie(buf)) {
        return;
    }
    Serial.println("out of sync");
    while (!isMagicCookie(buf)) {
        uint16_t i = 0;
        for (; i < MAGIC_COOKIE_LEN; i++) {
            buf[i] = buf[i+1];
        }
        buf[i] = readSingle();
    }
}

byte frame[NUM_BYTES];

void readFrame() {
    sync();
    readExact(NUM_BYTES, frame);
    setEach(frame);
}

void loop() {
    readFrame();
    FastLED.show();
}

D esp32_ws2801.ino => esp32_ws2801.ino +0 -122
@@ 1,122 0,0 @@
#include <FastLED.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>

#include "secrets.h"

#define DATA_PIN G33
#define CLK_PIN G23
#define LED_TYPE WS2801
#define COLOR_ORDER RGB
#define NUM_LEDS 50
#define MAX_MILLIAMPS 1500

CRGB leds[NUM_LEDS];

WebServer server(80);

void handleRoot() {
    server.send(200, "text/plain", "ESP color server. ");
}

void handleNotFound() {
    String message = "File Not Found\n\n";
    message += "URI: ";
    message += server.uri();
    message += "\nMethod: ";
    message += (server.method() == HTTP_GET) ? "GET" : "POST";
    message += "\nArguments: ";
    message += server.args();
    message += "\n";

    for (uint8_t i = 0; i < server.args(); i++) {
        message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
    }

    server.send(404, "text/plain", message);
}

void setup() {
    delay(2000); // delay for recovery
    FastLED.addLeds<LED_TYPE, DATA_PIN, CLK_PIN, COLOR_ORDER>(leds, NUM_LEDS)
        .setCorrection(TypicalLEDStrip);
    FastLED.setMaxPowerInVoltsAndMilliamps(5, MAX_MILLIAMPS);

    Serial.begin(115200);
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);
    Serial.println("");

    // Wait for connection
    while (WiFi.status() != WL_CONNECTED) {
        delay(400);
        Serial.print(".");
    }
    Serial.println("");
    Serial.print("Connected to ");
    Serial.println(ssid);
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());

    if (MDNS.begin("esp32")) {
        Serial.println("MDNS responder started");
    }
    server.on("/", handleRoot);
    server.onNotFound(handleNotFound);
    server.begin();
    Serial.println("HTTP server started");
}


void loop()
{
    server.handleClient();
    pride();
    FastLED.show();
    // delay(2); // allow the cpu to switch to other tasks
}


// This function draws rainbows with an ever-changing,
// widely-varying set of parameters.
void pride() {
    static uint16_t sPseudotime = 0;
    static uint16_t sLastMillis = 0;
    static uint16_t sHue16 = 0;
 
    uint8_t sat8 = beatsin88( 87, 220, 250);
    uint8_t brightdepth = beatsin88( 341, 96, 224);
    uint16_t brightnessthetainc16 = beatsin88( 203, (25 * 256), (40 * 256));
    uint8_t msmultiplier = beatsin88(147, 23, 60);

    uint16_t hue16 = sHue16;//gHue * 256;
    uint16_t hueinc16 = beatsin88(113, 1, 3000);
  
    uint16_t ms = millis();
    uint16_t deltams = ms - sLastMillis ;
    sLastMillis  = ms;
    sPseudotime += deltams * msmultiplier;
    sHue16 += deltams * beatsin88( 400, 5,9);
    uint16_t brightnesstheta16 = sPseudotime;

    for( uint16_t i = 0 ; i < NUM_LEDS; i++) {
        hue16 += hueinc16;
        uint8_t hue8 = hue16 / 256;

        brightnesstheta16  += brightnessthetainc16;
        uint16_t b16 = sin16( brightnesstheta16  ) + 32768;

        uint16_t bri16 = (uint32_t)((uint32_t)b16 * (uint32_t)b16) / 65536;
        uint8_t bri8 = (uint32_t)(((uint32_t)bri16) * brightdepth) / 65536;
        bri8 += (255 - brightdepth);
    
        CRGB newcolor = CHSV( hue8, sat8, bri8);
    
        uint16_t pixelnumber = i;
        pixelnumber = (NUM_LEDS-1) - pixelnumber;
    
        nblend( leds[pixelnumber], newcolor, 64);
    }
}

A server/.gitignore => server/.gitignore +1 -0
@@ 0,0 1,1 @@
/target

A server/Cargo.lock => server/Cargo.lock +16 -0
@@ 0,0 1,16 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3

[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"

[[package]]
name = "jojos-hue-server"
version = "0.1.0"
dependencies = [
 "byteorder",
]

A server/Cargo.toml => server/Cargo.toml +9 -0
@@ 0,0 1,9 @@
[package]
name = "jojos-hue-server"
version = "0.1.0"
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
byteorder = "*"
\ No newline at end of file

A server/src/main.rs => server/src/main.rs +99 -0
@@ 0,0 1,99 @@
use byteorder::{ByteOrder, LittleEndian};
use std::{
    io::{Read, Write},
    iter::once,
    net,
    sync::mpsc,
    thread,
    time::{Duration, Instant},
};

const FPS: f32 = 120.0;
const TIMEOUT: Duration = Duration::from_millis(200);

struct RGB {
    r: f32,
    g: f32,
    b: f32,
}

fn main() {
    let (clients_tx, clients_rx) = mpsc::channel::<(net::TcpStream, net::SocketAddr)>();

    thread::spawn(move || {
        let listener = net::TcpListener::bind("0.0.0.0:7017").unwrap();
        loop {
            if let Ok(c) = listener.accept() {
                clients_tx.send(c).unwrap();
            }
        }
    });

    let mut clients = Vec::new();
    let t0 = Instant::now();
    let mut tprev = Instant::now();
    loop {
        if let Ok(con) = clients_rx.try_recv() {
            if let Some(client) = setup_client(con) {
                clients.push(client);
            }
        }
        let t = t0.elapsed().as_secs_f64();
        clients = clients
            .into_iter()
            .filter_map(|(tx, nleds)| tx.send(lightf(t, nleds)).map(|()| (tx, nleds)).ok())
            .collect();
        let dt = tprev.elapsed();
        tprev = Instant::now();
        let period = Duration::from_secs_f32(1.0 / FPS);
        if dt < period {
            thread::sleep(period - dt);
        }
    }
}

fn setup_client(
    (mut stream, addr): (net::TcpStream, net::SocketAddr),
) -> Option<(mpsc::Sender<Vec<RGB>>, u16)> {
    stream
        .set_write_timeout(Some(TIMEOUT))
        .map_err(|e| println!("Error setting write timeout. {:?}", e))
        .ok()?;
    let mut buf = [0; 2];
    stream
        .read_exact(&mut buf)
        .map_err(|e| println!("Error reading client num leds. {:?}", e))
        .ok()?;
    let nleds = LittleEndian::read_u16(&buf);
    let (tx, rx) = mpsc::channel::<Vec<RGB>>();
    thread::spawn(move || loop {
        let magic_cookie = b"satan";
        let color_data = rx
            .recv()
            .unwrap()
            .into_iter()
            .flat_map(|RGB { r, g, b }| once(r).chain(once(g)).chain(once(b)))
            .map(|x| (x * 255.999) as u8);
        let data = magic_cookie
            .iter()
            .cloned()
            .chain(color_data)
            .collect::<Vec<u8>>();
        if let Err(_) = stream.write_all(&data) {
            println!("Error writing to client {}. Dropping them.", addr);
            return;
        }
    });
    println!("New client {} with {} LEDs", addr, nleds);
    Some((tx, nleds))
}

fn lightf(t: f64, nleds: u16) -> Vec<RGB> {
    (0..nleds)
        .map(|_i| RGB {
            r: (t * 2.0).sin() as f32 * 0.5 + 0.5,
            g: (t * 5.0).sin() as f32 * 0.5 + 0.5,
            b: (t * 7.0).sin() as f32 * 0.5 + 0.5,
        })
        .collect()
}