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