Last active
November 7, 2017 21:56
-
-
Save chriszero/6e12daaf085ac66bbcc597fd194352a6 to your computer and use it in GitHub Desktop.
Open Pixel Control Server on a Particle Photon
This file contains hidden or 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
| // 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