├── README.md └── slackpager.ino /README.md: -------------------------------------------------------------------------------- 1 | # Real-Time Slack Pager on ESP8266 and SSD1306 OLED display 2 | 3 | This sketch turns your ESP chip into real time Slack pager, dedicated display for Slack channels. 4 | 5 | **Demo video** : https://youtu.be/F2sbvJaTipA 6 | 7 | Before running this Sketch, make sure the set the following constants in the program: 8 | 9 | * `SLACK_TOKEN` - The API token of your slack bot (get it at https://my.slack.com/services/new/bot) 10 | * `WIFI_SSID` - Your WiFi network name (SSID) 11 | * `WIFI_PASSWORD` - Your WiFi password 12 | 13 | In addition, you will need to install the following Arduino libraries: 14 | - [WebSockets](https://github.com/Links2004/arduinoWebSockets), 15 | - [ArduinoJson](https://github.com/bblanchon/ArduinoJson), 16 | - [ESP8266-oled-ssd1306](https://github.com/squix78/esp8266-oled-ssd1306) 17 | 18 | I used NodeMCU ESP board for this code (available for less than $10 at http://amzn.to/29v9AaZ) and SSD1306 (available for $10 at http://amzn.to/2kKtu7w). You can also buy these for a lot cheaper if you can wait a couple weeks: NodeMCU for $5.26 at http://www.banggood.com/NodeMcu-Lua-WIFI-Internet-Things-Development-Board-Based-ESP8266-CP2102-Wireless-Module-p-1097112.html?p=NR1115712752201408A0 and OLED screen for $5.49 at http://www.banggood.com/0_96-Inch-4Pin-White-IIC-I2C-OLED-Display-Module-12864-LED-For-Arduino-p-958196.html?p=NR1115712752201408A0 19 | 20 | TODO : 21 | 22 | - [ ] When initial JSON is received from Slack, create a hash table of channel IDs/their names and also user IDs/usernames so that you could see what channel the message is coming from 23 | - [ ] Connect Neopixels and specify certain triggers for various colors 24 | - [ ] Enable a way to specify what channel you want to listen in, instead of all channels where the bot is present 25 | 26 | Copyright (C) 2016, Maks Surguy 27 | 28 | License: MIT. 29 | -------------------------------------------------------------------------------- /slackpager.ino: -------------------------------------------------------------------------------- 1 | /** 2 | ESP8266 Arduino Real-Time Slack Pager with SSD1306 OLED display. 3 | 4 | Copyright (C) 2016, Maks Surguy. 5 | 6 | Licensed under the MIT License 7 | */ 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | #include 19 | #include "SSD1306.h" // Using https://github.com/squix78/esp8266-oled-ssd1306 library 20 | 21 | #include 22 | 23 | #define WIFI_SSID "******" 24 | #define WIFI_PASSWORD "******" 25 | #define SLACK_TOKEN "your-bot-token-here" // Follow https://my.slack.com/services/new/bot to create a new bot 26 | #define SLACK_SSL_FINGERPRINT "AB F0 5B A9 1A E0 AE 5F CE 32 2E 7C 66 67 49 EC DD 6D 6A 38" // If Slack changes their SSL fingerprint, you would need to update this 27 | 28 | ESP8266WiFiMulti WiFiMulti; 29 | WebSocketsClient webSocket; 30 | 31 | SSD1306 display(0x3c, 4, 5); 32 | 33 | #define Slack_Logo_width 90 34 | #define Slack_Logo_height 26 35 | const char Slack_Logo_bits[] PROGMEM = { 36 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 37 | 0x00, 0xf0, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 38 | 0x00, 0xf0, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 39 | 0x00, 0xf0, 0x01, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 40 | 0x78, 0xf0, 0x3f, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 41 | 0x78, 0xe0, 0x3f, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 42 | 0xf8, 0xf8, 0x3f, 0x00, 0xfc, 0xf0, 0xf0, 0x03, 0xf0, 0xe1, 0xc1, 0x00, 43 | 0xf8, 0xff, 0x3f, 0x00, 0xff, 0xf3, 0xfc, 0x0f, 0xfc, 0xe7, 0xe1, 0x01, 44 | 0xf0, 0xff, 0x0f, 0x00, 0xff, 0xf3, 0xfc, 0x1f, 0xfe, 0xe7, 0xf1, 0x01, 45 | 0xfc, 0xff, 0x03, 0x80, 0x87, 0xf1, 0x1c, 0x1e, 0x1f, 0xe2, 0xf9, 0x00, 46 | 0xff, 0xff, 0x07, 0x80, 0x07, 0xf0, 0x00, 0x1c, 0x0f, 0xe0, 0x3d, 0x00, 47 | 0xff, 0xc3, 0x07, 0x80, 0x0f, 0xf0, 0x00, 0xbc, 0x07, 0xe0, 0x1f, 0x00, 48 | 0xff, 0x83, 0x0f, 0x00, 0x7f, 0xf0, 0xf0, 0xbf, 0x07, 0xe0, 0x1f, 0x00, 49 | 0xee, 0x83, 0xff, 0x00, 0xfe, 0xf1, 0xfc, 0xbf, 0x07, 0xe0, 0x3f, 0x00, 50 | 0xc0, 0x83, 0xff, 0x01, 0xf0, 0xf3, 0x3c, 0xbe, 0x07, 0xe0, 0x7f, 0x00, 51 | 0xc0, 0xc7, 0xff, 0x01, 0xc0, 0xf3, 0x1e, 0xbc, 0x07, 0xe0, 0x79, 0x00, 52 | 0xc0, 0xff, 0xff, 0x00, 0x80, 0xf3, 0x0e, 0x3c, 0x0f, 0xe0, 0xf1, 0x00, 53 | 0x80, 0xff, 0x3f, 0x00, 0xcf, 0xf3, 0x1e, 0x3f, 0xbf, 0xe7, 0xe1, 0x01, 54 | 0xe0, 0xff, 0x1f, 0x80, 0xff, 0xf3, 0xfc, 0x3f, 0xfe, 0xe7, 0xe1, 0x03, 55 | 0xf8, 0xff, 0x3e, 0x00, 0xff, 0xf1, 0xf8, 0x3d, 0xfc, 0xe7, 0xc1, 0x03, 56 | 0xf8, 0x1f, 0x3e, 0x00, 0x30, 0x00, 0x60, 0x00, 0xe0, 0x00, 0x80, 0x00, 57 | 0xf8, 0x0f, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 58 | 0x70, 0x1f, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 59 | 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 60 | 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 61 | 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 62 | }; 63 | 64 | long nextCmdId = 1; 65 | bool connected = false; 66 | 67 | /** 68 | Sends a ping message to Slack. Call this function immediately after establishing 69 | the WebSocket connection, and then every 5 seconds to keep the connection alive. 70 | */ 71 | void sendPing() { 72 | DynamicJsonBuffer jsonBuffer; 73 | JsonObject& root = jsonBuffer.createObject(); 74 | root["type"] = "ping"; 75 | root["id"] = nextCmdId++; 76 | String json; 77 | root.printTo(json); 78 | webSocket.sendTXT(json); 79 | } 80 | 81 | void processSlackMessage(char *payload) { 82 | Serial.printf("Payload: %s\n", payload); 83 | 84 | StaticJsonBuffer<600> JSONBuffer; 85 | 86 | JsonObject& rootJSON = JSONBuffer.parseObject(payload); 87 | //Serial.printf("Type of message: %s\n", msgType); 88 | const char* msgType = rootJSON["type"]; 89 | 90 | if (strcmp(msgType,"message")==0){ 91 | display.clear(); 92 | display.setTextAlignment(TEXT_ALIGN_LEFT); 93 | display.drawStringMaxWidth(0, 0, 128, rootJSON["text"].asString()); 94 | display.display(); 95 | } 96 | } 97 | 98 | /** 99 | Called on each web socket event. Handles disconnection, and also 100 | incoming messages from slack. 101 | */ 102 | void webSocketEvent(WStype_t type, uint8_t *payload, size_t len) { 103 | switch (type) { 104 | case WStype_DISCONNECTED: 105 | Serial.printf("[WebSocket] Disconnected :-( \n"); 106 | connected = false; 107 | break; 108 | 109 | case WStype_CONNECTED: 110 | Serial.printf("[WebSocket] Connected to: %s\n", payload); 111 | sendPing(); 112 | break; 113 | 114 | case WStype_TEXT: 115 | Serial.printf("[WebSocket] Message: %s\n", payload); 116 | processSlackMessage((char*)payload); 117 | break; 118 | } 119 | } 120 | 121 | /** 122 | Establishes a bot connection to Slack: 123 | 1. Performs a REST call to get the WebSocket URL 124 | 2. Connects the WebSocket 125 | Returns true if the connection was established successfully. 126 | */ 127 | bool connectToSlack() { 128 | // Step 1: Find WebSocket address via RTM API (https://api.slack.com/methods/rtm.start) 129 | HTTPClient http; 130 | 131 | Serial.println("Start request"); 132 | http.begin("https://slack.com/api/rtm.start?token=" + SLACK_TOKEN, SLACK_SSL_FINGERPRINT); 133 | int httpCode = http.GET(); 134 | 135 | if (httpCode != HTTP_CODE_OK) { 136 | Serial.printf("HTTP GET failed with code %d\n", httpCode); 137 | display.clear(); 138 | display.setTextAlignment(TEXT_ALIGN_LEFT); 139 | display.drawString(0, 0, "Initial request failed." ); 140 | display.display(); 141 | return false; 142 | } 143 | 144 | // Grab the URL to websocket 145 | WiFiClient *client = http.getStreamPtr(); 146 | client->find("wss:\\/\\/"); 147 | String host = client->readStringUntil('\\'); 148 | String path = client->readStringUntil('"'); 149 | path.replace("\\/", "/"); 150 | 151 | http.end(); 152 | 153 | // Step 2: Open WebSocket connection and register event handler 154 | Serial.println("WebSocket Host=" + host + " Path= " + path); 155 | webSocket.beginSSL(host, 443, path, "", ""); 156 | webSocket.onEvent(webSocketEvent); 157 | 158 | display.clear(); 159 | display.setTextAlignment(TEXT_ALIGN_LEFT); 160 | display.drawStringMaxWidth(0, 0, 128, "Connected to Slack!" ); 161 | display.display(); 162 | 163 | return true; 164 | } 165 | 166 | void setup() { 167 | Serial.begin(115200); 168 | Serial.setDebugOutput(true); 169 | // Initialize display 170 | 171 | WiFiMulti.addAP(WIFI_SSID, WIFI_PASSWORD); 172 | while (WiFiMulti.run() != WL_CONNECTED) { 173 | delay(500); 174 | } 175 | 176 | configTime(-7 * 3600, 0, "pool.ntp.org", "time.nist.gov"); 177 | 178 | // Show Slack Logo 179 | display.init(); 180 | display.setFont(ArialMT_Plain_10); 181 | display.setTextAlignment(TEXT_ALIGN_LEFT); 182 | display.drawXbm(15, 14, Slack_Logo_width, Slack_Logo_height, Slack_Logo_bits); 183 | display.display(); 184 | } 185 | 186 | unsigned long lastPing = 0; 187 | 188 | /** 189 | Sends a ping every 5 seconds, and handles reconnections 190 | */ 191 | void loop() { 192 | webSocket.loop(); 193 | 194 | if (connected) { 195 | // Send ping every 5 seconds, to keep the connection alive 196 | if (millis() - lastPing > 5000) { 197 | sendPing(); 198 | lastPing = millis(); 199 | } 200 | } else { 201 | // Try to connect / reconnect to slack 202 | connected = connectToSlack(); 203 | if (!connected) { 204 | delay(500); 205 | } 206 | } 207 | } 208 | --------------------------------------------------------------------------------