├── README.md └── slackbot └── slackbot.ino /README.md: -------------------------------------------------------------------------------- 1 | # Real-Time Slack Bot for Arduino 2 | 3 | Copyright (C) 2016, Uri Shaked 4 | 5 | License: MIT. 6 | 7 | This bot let you control the colors of a NeoPixel Ring through Slack. It uses the Slack Real Time Messaging API to listen for slack messages. 8 | 9 | ➜ [Read more about it in my blog post](https://medium.com/@urish/how-to-connect-your-t-shirt-to-slack-using-arduino-90761201d70f) 10 | 11 | Before running this Sketch, make sure the set the following constants in the program: 12 | 13 | * `SLACK_BOT_TOKEN` - The API token of your slack bot 14 | * `WIFI_SSID` - Your WiFi signal name (SSID) 15 | * `WIFI_PASSWORD` - Your WiFi password 16 | 17 | In addition, you will need to install the following Arduino libraries: [ADAFruit_NeoPixel](https://github.com/adafruit/Adafruit_NeoPixel), [WebSockets](https://github.com/Links2004/arduinoWebSockets), [ArduinoJson](https://github.com/bblanchon/ArduinoJson). 18 | -------------------------------------------------------------------------------- /slackbot/slackbot.ino: -------------------------------------------------------------------------------- 1 | /** 2 | Arduino Real-Time Slack Bot 3 | 4 | Copyright (C) 2016, Uri Shaked. 5 | 6 | Licensed under the MIT License 7 | */ 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #define SLACK_BOT_TOKEN "put-your-slack-token-here" 20 | #define WIFI_SSID "wifi-name" 21 | #define WIFI_PASSWORD "wifi-password" 22 | 23 | #define LEDS_PIN D2 24 | #define LEDS_NUMPIXELS 24 25 | #define WORD_SEPERATORS "., \"'()[]<>;:-+&?!\n\t" 26 | 27 | ESP8266WiFiMulti WiFiMulti; 28 | WebSocketsClient webSocket; 29 | 30 | Adafruit_NeoPixel pixels(LEDS_NUMPIXELS, LEDS_PIN, NEO_GRB + NEO_KHZ800); 31 | 32 | long nextCmdId = 1; 33 | bool connected = false; 34 | 35 | /** 36 | Sends a ping message to Slack. Call this function immediately after establishing 37 | the WebSocket connection, and then every 5 seconds to keep the connection alive. 38 | */ 39 | void sendPing() { 40 | DynamicJsonBuffer jsonBuffer; 41 | JsonObject& root = jsonBuffer.createObject(); 42 | root["type"] = "ping"; 43 | root["id"] = nextCmdId++; 44 | String json; 45 | root.printTo(json); 46 | webSocket.sendTXT(json); 47 | } 48 | 49 | /** 50 | Input a value 0 to 255 to get a color value. 51 | The colors are a transition r - g - b - back to r. 52 | */ 53 | uint32_t wheel(byte wheelPos) { 54 | wheelPos = 255 - wheelPos; 55 | if (wheelPos < 85) { 56 | return pixels.Color(255 - wheelPos * 3, 0, wheelPos * 3); 57 | } 58 | if (wheelPos < 170) { 59 | wheelPos -= 85; 60 | return pixels.Color(0, wheelPos * 3, 255 - wheelPos * 3); 61 | } 62 | wheelPos -= 170; 63 | return pixels.Color(wheelPos * 3, 255 - wheelPos * 3, 0); 64 | } 65 | 66 | /** 67 | Animate a NeoPixel ring color change. 68 | Setting `zebra` to true skips every other led. 69 | */ 70 | void drawColor(uint32_t color, bool zebra) { 71 | int step = zebra ? 2 : 1; 72 | for (int i = 0; i < LEDS_NUMPIXELS; i += step) { 73 | pixels.setPixelColor(i, color); 74 | pixels.show(); 75 | delay(30 * step); 76 | } 77 | } 78 | 79 | /** 80 | Draws a rainbow :-) 81 | */ 82 | void drawRainbow(bool zebra) { 83 | int step = zebra ? 2 : 1; 84 | for (int i = 0; i < LEDS_NUMPIXELS; i += step) { 85 | pixels.setPixelColor(i, wheel(i * 256 / LEDS_NUMPIXELS)); 86 | pixels.show(); 87 | delay(30 * step); 88 | } 89 | } 90 | 91 | /** 92 | Looks for color names in the incoming slack messages and 93 | animates the ring accordingly. You can include several 94 | colors in a single message, e.g. `red blue zebra black yellow rainbow` 95 | */ 96 | void processSlackMessage(char *payload) { 97 | char *nextWord = NULL; 98 | bool zebra = false; 99 | for (nextWord = strtok(payload, WORD_SEPERATORS); nextWord; nextWord = strtok(NULL, WORD_SEPERATORS)) { 100 | if (strcasecmp(nextWord, "zebra") == 0) { 101 | zebra = true; 102 | } 103 | if (strcasecmp(nextWord, "red") == 0) { 104 | drawColor(pixels.Color(255, 0, 0), zebra); 105 | } 106 | if (strcasecmp(nextWord, "green") == 0) { 107 | drawColor(pixels.Color(0, 255, 0), zebra); 108 | } 109 | if (strcasecmp(nextWord, "blue") == 0) { 110 | drawColor(pixels.Color(0, 0, 255), zebra); 111 | } 112 | if (strcasecmp(nextWord, "yellow") == 0) { 113 | drawColor(pixels.Color(255, 160, 0), zebra); 114 | } 115 | if (strcasecmp(nextWord, "white") == 0) { 116 | drawColor(pixels.Color(255, 255, 255), zebra); 117 | } 118 | if (strcasecmp(nextWord, "purple") == 0) { 119 | drawColor(pixels.Color(128, 0, 128), zebra); 120 | } 121 | if (strcasecmp(nextWord, "pink") == 0) { 122 | drawColor(pixels.Color(255, 0, 96), zebra); 123 | } 124 | if (strcasecmp(nextWord, "orange") == 0) { 125 | drawColor(pixels.Color(255, 64, 0), zebra); 126 | } 127 | if (strcasecmp(nextWord, "black") == 0) { 128 | drawColor(pixels.Color(0, 0, 0), zebra); 129 | } 130 | if (strcasecmp(nextWord, "rainbow") == 0) { 131 | drawRainbow(zebra); 132 | } 133 | if (nextWord[0] == '#') { 134 | int color = strtol(&nextWord[1], NULL, 16); 135 | Serial.println("Color"); 136 | Serial.print(color); 137 | if (color) { 138 | drawColor(color, zebra); 139 | } 140 | } 141 | } 142 | } 143 | 144 | /** 145 | Called on each web socket event. Handles disconnection, and also 146 | incoming messages from slack. 147 | */ 148 | void webSocketEvent(WStype_t type, uint8_t *payload, size_t len) { 149 | switch (type) { 150 | case WStype_DISCONNECTED: 151 | Serial.printf("[WebSocket] Disconnected :-( \n"); 152 | connected = false; 153 | break; 154 | 155 | case WStype_CONNECTED: 156 | Serial.printf("[WebSocket] Connected to: %s\n", payload); 157 | sendPing(); 158 | break; 159 | 160 | case WStype_TEXT: 161 | Serial.printf("[WebSocket] Message: %s\n", payload); 162 | processSlackMessage((char*)payload); 163 | break; 164 | } 165 | } 166 | 167 | /** 168 | Establishes a bot connection to Slack: 169 | 1. Performs a REST call to get the WebSocket URL 170 | 2. Conencts the WebSocket 171 | Returns true if the connection was established successfully. 172 | */ 173 | bool connectToSlack() { 174 | // Step 1: Find WebSocket address via RTM API (https://api.slack.com/methods/rtm.start) 175 | HTTPClient http; 176 | http.begin("https://slack.com/api/rtm.start?token=" SLACK_BOT_TOKEN); 177 | int httpCode = http.GET(); 178 | 179 | if (httpCode != HTTP_CODE_OK) { 180 | Serial.printf("HTTP GET failed with code %d\n", httpCode); 181 | return false; 182 | } 183 | 184 | WiFiClient *client = http.getStreamPtr(); 185 | client->find("wss:\\/\\/"); 186 | String host = client->readStringUntil('\\'); 187 | String path = client->readStringUntil('"'); 188 | path.replace("\\/", "/"); 189 | 190 | // Step 2: Open WebSocket connection and register event handler 191 | Serial.println("WebSocket Host=" + host + " Path=" + path); 192 | webSocket.beginSSL(host, 443, path, "", ""); 193 | webSocket.onEvent(webSocketEvent); 194 | return true; 195 | } 196 | 197 | void setup() { 198 | Serial.begin(115200); 199 | Serial.setDebugOutput(true); 200 | 201 | pixels.begin(); 202 | 203 | WiFiMulti.addAP(WIFI_SSID, WIFI_PASSWORD); 204 | while (WiFiMulti.run() != WL_CONNECTED) { 205 | delay(100); 206 | } 207 | 208 | configTime(3 * 3600, 0, "pool.ntp.org", "time.nist.gov"); 209 | } 210 | 211 | unsigned long lastPing = 0; 212 | 213 | /** 214 | Sends a ping every 5 seconds, and handles reconnections 215 | */ 216 | void loop() { 217 | webSocket.loop(); 218 | 219 | if (connected) { 220 | // Send ping every 5 seconds, to keep the connection alive 221 | if (millis() - lastPing > 5000) { 222 | sendPing(); 223 | lastPing = millis(); 224 | } 225 | } else { 226 | // Try to connect / reconnect to slack 227 | connected = connectToSlack(); 228 | if (!connected) { 229 | delay(500); 230 | } 231 | } 232 | } 233 | 234 | --------------------------------------------------------------------------------