Skip to content

Instantly share code, notes, and snippets.

@chriszero
Last active November 7, 2017 21:56
Show Gist options
  • Select an option

  • Save chriszero/6e12daaf085ac66bbcc597fd194352a6 to your computer and use it in GitHub Desktop.

Select an option

Save chriszero/6e12daaf085ac66bbcc597fd194352a6 to your computer and use it in GitHub Desktop.
Open Pixel Control Server on a Particle Photon
// This #include statement was automatically added by the Particle IDE.
#include "opcserver.h"
SYSTEM_THREAD(ENABLED);
#include "FastLED/FastLED.h"
FASTLED_USING_NAMESPACE
#include "math.h"
#define MAX_LEDS 256 // Upper limit; OK to receive data for fewer
#define LED_S 192
#define PIN A5
#define MODE_DATA 0
#define MODE_HEADER 1
#define MODE_DISCARD 2
// bytesToRead is the number of bytes remaining in current mode, while
// bytesRead is the number read so far (used as an index into a destination
// buffer). bytesToDiscard is the number remaining when in MODE_DISCARD.
int16_t bytesToRead = 0, bytesRead = 0, bytesToDiscard = 0, numLEDs = MAX_LEDS, nextNumLEDs = MAX_LEDS;
TCPServer server = TCPServer(7890);
TCPClient client;
// Data for most-recently-received OPC color payload, payload before that,
// and new in-progress payload currently arriving. Also, a 'sink' buffer
// for quickly discarding data.
uint8_t rgbBuf[4][MAX_LEDS*3]; // 512 LEDs = 6144 bytes.
CRGB ledBuf[MAX_LEDS];
// These tables (computed at runtime) are used for gamma correction and
// dithering. RAM used = 256*9+MAX_LEDS*3 bytes. 512 LEDs = 3840 bytes.
uint8_t loR[256], hiR[256], fracR[256], errR[MAX_LEDS],
loG[256], hiG[256], fracG[256], errG[MAX_LEDS],
loB[256], hiB[256], fracB[256], errB[MAX_LEDS];
// Compute gamma/dither tables for one color component. Pass gamma and max
// brightness level (e.g. 2.7, 255) followed by pointers to 'lo', 'hi' and
// 'frac' tables to fill. Typically will call this 3 times (R, G, B).
void fillGamma(float g, uint8_t m, uint8_t *lo, uint8_t *hi, uint8_t *frac) {
uint16_t i, j, n;
for (i = 0; i < 256; i++) {
// Calc 16-bit gamma-corrected level
n = (uint16_t)(pow((double)i / 255.0, g) * (double)m * 256.0 + 0.5);
lo[i] = n >> 8; // Store as 8-bit brightness level
frac[i] = n & 0xFF; // and 'dither up' probability (error term)
}
// Second pass, calc 'hi' level for each (based on 'lo' value)
for (i = 0; i < 256; i++) {
n = lo[i];
for (j = i; (j < 256) && (lo[j] <= n); j++);
hi[i] = lo[j];
}
}
// This function interpolates between two RGB input buffers, gamma- and
// color-corrects the interpolated result with 16-bit dithering and issues
// the resulting data to the GPIO port.
void magic(
uint8_t *rgbIn1, // First RGB input buffer being interpolated
uint8_t *rgbIn2, // Second RGB input buffer being interpolated
uint8_t w2, // Weighting (0-255) of second buffer in interpolation
uint8_t *fillBuf, // data buffer being filled
uint16_t numLEDs) { // Number of LEDs in buffer
uint8_t mix;
uint16_t weight1, weight2, pixelNum, e;
uint8_t *fillPtr = fillBuf;// + 5; // Skip 4-byte header + 1 byte pixel marker
weight2 = (uint16_t)w2 + 1; // 1-256
weight1 = 257 - weight2; // 1-256
for (pixelNum = 0; pixelNum < numLEDs; pixelNum++, fillPtr += 3) {
// Interpolate red from rgbIn1 and rgbIn2 based on weightings
mix = (*rgbIn1++ * weight1 + *rgbIn2++ * weight2) >> 8;
// fracR is the fractional portion (0-255) of the 16-bit gamma-
// corrected value for a given red brightness...essentially it's
// how far 'off' a given 8-bit brightness value is from its ideal.
// This error is carried forward to the next frame in the errR
// buffer...added to the fracR value for the current pixel...
e = fracR[mix] + errR[pixelNum];
// ...if this accumulated value exceeds 255, the resulting red
// value is bumped up to the next brightness level and 256 is
// subtracted from the error term before storing back in errR.
// Diffusion dithering is the result.
fillPtr[0] = (e < 256) ? loR[mix] : hiR[mix];
// If e exceeds 256, it *should* be reduced by 256 at this point...
// but rather than subtract, we just rely on truncation in the 8-bit
// store operation below to do this implicitly. (e & 0xFF)
errR[pixelNum] = e;
// Repeat same operations for green...
mix = (*rgbIn1++ * weight1 + *rgbIn2++ * weight2) >> 8;
e = fracG[mix] + errG[pixelNum];
fillPtr[1] = (e < 256) ? loG[mix] : hiG[mix];
errG[pixelNum] = e;
// ...and blue...
mix = (*rgbIn1++ * weight1 + *rgbIn2++ * weight2) >> 8;
e = fracB[mix] + errB[pixelNum];
fillPtr[2] = (e < 256) ? loB[mix] : hiB[mix];
errB[pixelNum] = e;
}
}
uint8_t bufPrior = 0, bufMostRecentlyRead = 1, bufBeingRead = 2;
uint32_t lastFrameTime = 0, timeBetweenFrames = 0, updates = 0, priorSeconds = 0;
uint8_t mode = MODE_HEADER;
int resetHandler(String command) {
System.reset();
return 1;
}
int setRgbHandler(String command) {
}
int enableOpcHandler(String command) {
}
void setup() {
//pinMode(PIN, OUTPUT);
Serial.begin(115200);
Serial.println("OPC WiFi Server");
server.begin();
fillGamma(2.7, 255, loR, hiR, fracR); // Initialize gamma tables to
fillGamma(2.7, 255, loG, hiG, fracG); // default values (OPC data may
fillGamma(2.7, 255, loB, hiB, fracB); // override this later).
memset(rgbBuf, 0, sizeof(rgbBuf)); // Clear buffers
FastLED.addLeds<WS2812B, PIN, GRB>(ledBuf, LED_S);
FastLED.setDither(0);
magic(rgbBuf[0], rgbBuf[0], 0, (uint8_t*)&ledBuf, MAX_LEDS);
FastLED.show();
// register cloudfunctions
// wait for cloud
waitUntil(Particle.connected);
Particle.function("reset", resetHandler);
Particle.function("setRgb", setRgbHandler);
Particle.function("enableOpc", enableOpcHandler);
}
void loop() {
client = server.available();
if (client) {
//Serial.println("new client");
uint32_t t, timeSinceFrameStart, seconds, lastShow;
int16_t a, bytesPending, dataSize;
uint8_t w;
unsigned long recvTimeoutLast = millis();
lastShow = micros();
while (client.connected()) {
//Particle.process();
t = micros(); // Current time
// limit update rate of the strip to 100hz
if(t - lastShow >= 10000) { // 1/50hz => 20ms => 20000us // 100hz => 10000
// Interpolation weight (0-255) is the ratio of the time since last
// frame arrived to the prior two frames' interval.
timeSinceFrameStart = t - lastFrameTime; // Elapsed since data recv'd
w = (timeSinceFrameStart >= timeBetweenFrames) ? 255 : (255L * timeSinceFrameStart / timeBetweenFrames);
magic(rgbBuf[bufPrior], rgbBuf[bufMostRecentlyRead], w, (uint8_t*)&ledBuf, numLEDs);
lastShow = t;
ATOMIC_BLOCK() {
FastLED.show();
}
}
// timeout 5s, close connection
/* unsigned long delta = millis() - recvTimeoutLast;
if (delta > 50000) {
// Serial.println(" Timeout...");
client.stop();
break;
}*/
// Process up to 1/2 of pending data on stream. Rather than waiting
// for full packets to arrive, this interleaves Stream I/O with LED
// dithering so the latter doesn't get too 'stuttery.' It DOES
// however limit the potential throughput; 256 LEDs seems fine at
// 60 FPS, but with 512 you may need to limit it to 30 FPS.
if (bytesPending = client.available()) { // Any incoming data?
bytesPending = 128 < bytesPending ? 128 : bytesPending; // Process just a fraction of the incomming data.
do {
if (mode == MODE_DATA) {
if (bytesPending > bytesToRead)
bytesPending = bytesToRead;
if (bytesPending > sizeof(rgbBuf[0]))
bytesPending = sizeof(rgbBuf[0]);
if ((a = client.read(&rgbBuf[bufBeingRead][bytesRead], bytesPending)) > 0) {
bytesPending -= a;
bytesToRead -= a;
if (bytesToRead <= 0) { // End of pixel payload?
t = micros();
timeBetweenFrames = t - lastFrameTime;
lastFrameTime = t;
bufPrior = bufMostRecentlyRead; // Cycle buffers
bufMostRecentlyRead = bufBeingRead;
bufBeingRead = (bufBeingRead + 1) % 3;
numLEDs = nextNumLEDs;
bytesRead = 0; // Reset index
mode = bytesToDiscard ? MODE_DISCARD : MODE_HEADER;
recvTimeoutLast = millis(); // reset timeout
} else {
bytesRead += a; // Advance index & keep reading
}
} // else no data received
} else if (mode == MODE_HEADER) { // Receiving header data
if (bytesPending > 4) bytesPending = 4; // Limit to header size
if ((a = client.read(&rgbBuf[bufBeingRead][bytesRead], bytesPending)) > 0) {
bytesRead += a;
bytesPending -= a;
if (bytesPending <= 0) { // Full header received, parse it!
bytesRead = 0; // Reset read buffer index
dataSize = (rgbBuf[bufBeingRead][2] << 8) | rgbBuf[bufBeingRead][3];
if (dataSize > 0) { // Payload size > 0?
mode = MODE_DISCARD; // Assume DISCARD until validated,
bytesToDiscard = dataSize; // may override below
if (rgbBuf[bufBeingRead][0] <= 1) { // Valid channel?
if (rgbBuf[bufBeingRead][1] == 0) { // Pixel data command?
// Valid! Switch to DATA mode, set up counters...
mode = MODE_DATA;
if (dataSize <= sizeof(rgbBuf[0])) { // <= MAX_LEDS
bytesToRead = dataSize; // Read all,
bytesToDiscard = 0; // no discard
} else { // > MAX_LEDS
bytesToRead = sizeof(rgbBuf[0]); // Read MAX_LEDS,
bytesToDiscard -= sizeof(rgbBuf[0]); // discard rest
}
nextNumLEDs = bytesToRead / 3; // Pixel count when done
} // endif valid command
} // endif valid channel
} // else 0-byte payload; remain in HEADER mode
} // else full header not yet received; remain in HEADER mode
} // else no data received
} else { // MODE_DISCARD
// Read size mustn't exceed discard size or pixel buffer size
if (bytesPending > bytesToDiscard) bytesPending = bytesToDiscard;
if (bytesPending > sizeof(rgbBuf[3])) bytesPending = sizeof(rgbBuf[3]);
if ((a = client.read(&rgbBuf[3][0], bytesPending)) > 0) { // Poof!
bytesPending -= a;
if (bytesPending <= 0) { // End of DISCARD mode,
mode = MODE_HEADER; // switch back to HEADER mode
}
}
} // end MODE_DISCARD
} while (bytesPending > 0);
} // end if client.available()
} // end while client connected
client.stop();
Serial.println("client disonnected");
// clear buffers, turn off LED's
memset(rgbBuf, 0, sizeof(rgbBuf)); // Clear buffers
magic(rgbBuf[0], rgbBuf[0], 0, (uint8_t*)&ledBuf, MAX_LEDS);
FastLED.show();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment