├── .gitignore ├── LICENSE ├── README.md ├── breadboard.png └── esp-opc-server.ino /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Patrick Pelletier 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Patrick Pelletier nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is an [Arduino][1] sketch which implements an 2 | [Open Pixel Control][2] server for controlling [NeoPixels][3] from an 3 | [ESP8266][5], such as the [Adafruit Feather HUZZAH][4]. 4 | 5 | Here is an example circuit: 6 | 7 | Breadboard with ESP8266 Feather and level shifter 8 | 9 | Bill of materials: 10 | 11 | * [Adafruit Feather HUZZAH ESP8266][4] 12 | * [74AHCT125 - Quad Level-Shifter][6] 13 | * [470 ohm resistor][7] 14 | * [Big Freaking Capacitor - 4700uF][8] 15 | * [NeoPixel stick][9] 16 | 17 | You will need to edit the sketch to set the number of pixels you have, 18 | and your WiFi SSID and password. 19 | 20 | Once the sketch is running, you'll need to figure out which IP address 21 | it has been assigned. Generally, your router's web interface will 22 | have a way to look at DHCP leases, and you can find it from there. 23 | 24 | It may also be useful to call [WiFi.hostname()][13] to assign a 25 | hostname to your ESP board. This may show up as `hostname.lan` in 26 | your DNS, although it [depends on your router][14]. 27 | 28 | Once you know the IP address or hostname, you can connect with any 29 | Open Pixel Control client. The [openpixelcontrol repository][10] has 30 | some [clients in Python][11], and I have [a client in Haskell][12]. 31 | 32 | ## Caveats 33 | 34 | This example is a nice proof-of-concept, but there are a couple of 35 | reasons it isn't a robust solution. 36 | 37 | First, interrupts are disabled while writing to NeoPixels. Interrupts 38 | are necessary for WiFi to work, so the server may eventually crash and 39 | need to be rebooted, especially if you have a lot of NeoPixels. (This 40 | problem could be alleviated by using [DotStars][15] instead of 41 | NeoPixels, because DotStars are not timing-sensitive and do not need 42 | to disable interrupts.) 43 | 44 | Second, Open Pixel Control runs over TCP, and 45 | [TCP is not the best for real-time applications][16]. If a packet is 46 | lost, TCP will retransmit it, delaying the packets after it. 47 | [UDP][17] would be more suitable for this application. 48 | 49 | [1]: https://www.arduino.cc/ 50 | [2]: http://openpixelcontrol.org/ 51 | [3]: https://www.adafruit.com/category/168 52 | [4]: https://www.adafruit.com/products/2821 53 | [5]: https://github.com/esp8266/Arduino 54 | [6]: https://www.adafruit.com/products/1787 55 | [7]: https://www.adafruit.com/products/2781 56 | [8]: https://www.adafruit.com/products/1589 57 | [9]: https://www.adafruit.com/products/1426 58 | [10]: https://github.com/zestyping/openpixelcontrol/ 59 | [11]: https://github.com/zestyping/openpixelcontrol/tree/master/python 60 | [12]: https://github.com/ppelleti/hs-opc-client 61 | [13]: https://arduino-esp8266.readthedocs.io/en/latest/esp8266wifi/station-class.html#hostname 62 | [14]: https://unix.stackexchange.com/questions/92441/whats-the-difference-between-local-home-and-lan 63 | [15]: https://www.adafruit.com/category/885 64 | [16]: https://en.wikipedia.org/wiki/Transmission_Control_Protocol#Alternatives 65 | [17]: https://en.wikipedia.org/wiki/User_Datagram_Protocol 66 | -------------------------------------------------------------------------------- /breadboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mignon-p/esp-opc-server/646e8a4c82740fdb7acd331ee065ebe853d3a776/breadboard.png -------------------------------------------------------------------------------- /esp-opc-server.ino: -------------------------------------------------------------------------------- 1 | /* This Arduino sketch implements an Open Pixel Control server 2 | * for controlling NeoPixels from an ESP8266, such as the Adafruit 3 | * Feather HUZZAH. 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | /* Edit this to configure the number of pixels you have, and your SSID and password */ 10 | #define PIN 2 // pin that NeoPixels are connected to 11 | #define N_PIXELS 8 // number of NeoPixels on your strip 12 | #define WIFI_SSID "Your SSID" 13 | #define WIFI_PASSWD "Your password" 14 | 15 | Adafruit_NeoPixel strip = Adafruit_NeoPixel(N_PIXELS, PIN, NEO_GRB + NEO_KHZ800); 16 | WiFiServer server(7890); 17 | 18 | void setup() { 19 | uint16_t i = 0; 20 | 21 | strip.begin(); 22 | strip.show(); // Initialize all pixels to 'off' 23 | 24 | // display a moving red pixel while connecting to WiFi 25 | WiFi.begin(WIFI_SSID, WIFI_PASSWD); 26 | while (WiFi.status() != WL_CONNECTED) { 27 | strip.setPixelColor(i, strip.Color(32, 0, 0)); 28 | strip.show(); 29 | delay(200); 30 | strip.setPixelColor(i, strip.Color(0, 0, 0)); 31 | i++; 32 | if (i >= strip.numPixels()) 33 | i = 0; 34 | } 35 | 36 | // all pixels green once we've connected to WiFi 37 | for (i = 0; i < strip.numPixels(); i++) 38 | strip.setPixelColor(i, strip.Color(0, 32, 0)); 39 | strip.show(); 40 | 41 | server.begin(); 42 | } 43 | 44 | void loop() { 45 | WiFiClient client = server.available(); 46 | if (client) { 47 | while (client.connected()) { 48 | readFrame(client); 49 | yield(); 50 | } 51 | client.stop(); 52 | } 53 | } 54 | 55 | int readFully (WiFiClient &client, uint8_t *buf, size_t len) { 56 | size_t i; 57 | 58 | for (i = 0; i < len; i++) { 59 | int b; 60 | 61 | if ((b = blockingRead(client)) < 0) 62 | return -1; 63 | 64 | buf[i] = (uint8_t) b; 65 | } 66 | 67 | return 0; 68 | } 69 | 70 | int blockingRead (WiFiClient &client) { 71 | int b; 72 | while ((b = client.read()) < 0) { 73 | yield(); 74 | if (! client.connected()) 75 | return -1; 76 | } 77 | return b; 78 | } 79 | 80 | void readFrame(WiFiClient &client) { 81 | uint8_t buf4[4]; 82 | uint8_t cmd; 83 | size_t payload_length, leds_in_payload, i; 84 | 85 | // read channel number (ignored), command, and length 86 | if (readFully (client, buf4, sizeof (buf4)) < 0) 87 | return; 88 | 89 | cmd = buf4[1]; 90 | payload_length = (((size_t) buf4[2]) << 8) + (size_t) buf4[3]; 91 | leds_in_payload = payload_length / 3; 92 | if (leds_in_payload > strip.numPixels()) 93 | leds_in_payload = strip.numPixels(); 94 | if (cmd != 0) // we only support command 0, set pixel colors 95 | leds_in_payload = 0; 96 | 97 | // read pixel data; 3 bytes per pixel 98 | for (i = 0; i < leds_in_payload; i++) { 99 | if (readFully (client, buf4, 3) < 0) 100 | return; 101 | strip.setPixelColor(i, strip.Color(buf4[0], buf4[1], buf4[2])); 102 | } 103 | strip.show(); 104 | 105 | // discard any remaining data (e. g. if they sent us more pixels than we have) 106 | payload_length -= leds_in_payload * 3; 107 | 108 | for (; payload_length != 0; payload_length--) { 109 | if (blockingRead(client) < 0) 110 | return; 111 | } 112 | } 113 | --------------------------------------------------------------------------------