├── .gitignore ├── src ├── espresence.h ├── config.h.sample └── espresence.cpp ├── UNLICENSE.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .pioenvs 3 | .piolibdeps 4 | cmake-build-debug 5 | lib 6 | ~bak 7 | .clang_complete 8 | .gcc-flags.json 9 | .travis.yml 10 | CMakeLists.txt 11 | CMakeListsPrivate.txt 12 | platformio.ini 13 | config.h 14 | -------------------------------------------------------------------------------- /src/espresence.h: -------------------------------------------------------------------------------- 1 | #ifndef ESPRESENCE_ESPRESENCE_H 2 | #define ESPRESENCE_ESPRESENCE_H 3 | 4 | typedef struct { 5 | const char *ssid; 6 | const char *pwd; 7 | } ap_t; 8 | 9 | typedef struct { 10 | String mac; 11 | int32_t rssi; 12 | uint32_t ms; 13 | } clt_device_t; 14 | 15 | void checkList(); 16 | void printlist(); 17 | void onProbeRequest(const WiFiEventSoftAPModeProbeRequestReceived &evt); 18 | void wifiConnect(); 19 | void initSerial(); 20 | void initOTA(); 21 | void initWiFi(); 22 | void initMQTT(); 23 | void mqttConnect(); 24 | uint32_t getUptimeSecs(); 25 | String prettyBytes(uint32_t bytes); 26 | String macToString(const uint8 mac[6]); 27 | 28 | #endif //ESPRESENCE_ESPRESENCE_H 29 | -------------------------------------------------------------------------------- /src/config.h.sample: -------------------------------------------------------------------------------- 1 | #ifndef ESPRESENCE_CONFIG_H 2 | #define ESPRESENCE_CONFIG_H 3 | 4 | /** 5 | * ESPRESENCE VERSION 6 | */ 7 | 8 | const char *VERSION = "1.0"; 9 | 10 | 11 | /** 12 | * WIFI 13 | */ 14 | 15 | // Host name 16 | const char *ESP_NAME = "ESPresence"; 17 | 18 | // Name and password for our AP 19 | const char *AP_SSID = "ESPresence"; 20 | const char *AP_PASSWORD = "whocares"; 21 | 22 | // Access points list 23 | const ap_t AP_LIST[] = { 24 | {"__YOUR_SSID_1__>", "__YOUR_PASSWORD_1__"}, 25 | {"__YOUR_SSID_2__>", "__YOUR_PASSWORD_2__"}, 26 | {"__YOUR_SSID_3__>", "__YOUR_PASSWORD_3__"} 27 | }; 28 | 29 | 30 | /** 31 | * MQTT 32 | */ 33 | 34 | const char *MQTT_HOST = "__YOUR_MQTT_SERVER_IP__"; 35 | const uint16_t MQTT_PORT = 1883; 36 | const char *MQTT_OUT_TOPIC = "espresence/out"; 37 | 38 | 39 | /** 40 | * MISC 41 | */ 42 | 43 | // Activate core serial debug 44 | const bool SERIAL_SET_DEBUG_OUTPUT = false; 45 | 46 | // Max. number of devices to keep track off at a time 47 | const uint8_t MAX_DEVICES = 50; 48 | 49 | // Consider device away if not seen for this numner of seconds 50 | const uint16_t LIST_TIMEOUT = 300; 51 | 52 | #endif //ESPRESENCE_CONFIG_H 53 | -------------------------------------------------------------------------------- /UNLICENSE.txt: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESPresence 2 | 3 | Detect presence of Wifi devices and publish their mac/rssi to MQTT Server. 4 | 5 | Thanks to [QuickFix](http://www.esp8266.com/viewtopic.php?t=14894&p=66200#p66200) for having opened the way. 6 | 7 | 8 | ## Dependencies 9 | 10 | - [ESP8266 Arduino Core](https://github.com/esp8266/Arduino) (2.4.0-rc1 minimum) 11 | 12 | 13 | ## Licence 14 | 15 | This is free and unencumbered software released into the public domain. 16 | 17 | Anyone is free to copy, modify, publish, use, compile, sell, or 18 | distribute this software, either in source code form or as a compiled 19 | binary, for any purpose, commercial or non-commercial, and by any 20 | means. 21 | 22 | In jurisdictions that recognize copyright laws, the author or authors 23 | of this software dedicate any and all copyright interest in the 24 | software to the public domain. We make this dedication for the benefit 25 | of the public at large and to the detriment of our heirs and 26 | successors. We intend this dedication to be an overt act of 27 | relinquishment in perpetuity of all present and future rights to this 28 | software under copyright law. 29 | 30 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 31 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 32 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 33 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 34 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 35 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 36 | OTHER DEALINGS IN THE SOFTWARE. 37 | 38 | For more information, please refer to 39 | -------------------------------------------------------------------------------- /src/espresence.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Detect presence of Wifi devices and publish their mac/rssi to MQTT Server 3 | * Require Arduino Core for ESP8266 2.4.0-rc1 min 4 | * 5 | * Thanks to QuickFix for having opened the way 6 | * @see http://www.esp8266.com/viewtopic.php?t=14894&p=66200#p66200 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "espresence.h" 16 | #include "config.h" 17 | 18 | ESP8266WiFiMulti wifiMulti; 19 | WiFiClient wifiClient; 20 | PubSubClient mqttClient(wifiClient); 21 | WiFiEventHandler probeRequestHandler; 22 | 23 | bool otaInProgress = false; 24 | clt_device_t devicelist[MAX_DEVICES]; 25 | 26 | void setup() { 27 | initSerial(); 28 | initOTA(); 29 | initWiFi(); 30 | initMQTT(); 31 | 32 | // Set probe request handler 33 | probeRequestHandler = WiFi.onSoftAPModeProbeRequestReceived(&onProbeRequest); 34 | } 35 | 36 | void loop() { 37 | 38 | // Handle wifi connection 39 | if (wifiMulti.run() != WL_CONNECTED) { 40 | WiFi.disconnect(); 41 | wifiConnect(); 42 | return; 43 | } 44 | 45 | // Handle OTA 46 | ArduinoOTA.handle(); 47 | if (otaInProgress) { 48 | return; 49 | } 50 | 51 | // Handle MQTT 52 | if (!mqttClient.connected()) { 53 | mqttConnect(); 54 | } 55 | mqttClient.loop(); 56 | 57 | // Handle devices list 58 | checkList(); 59 | } 60 | 61 | void checkList() { 62 | 63 | uint8_t i; 64 | for (i = 0; i < MAX_DEVICES; i++) { 65 | if (devicelist[i].mac != "" && (millis() - devicelist[i].ms > LIST_TIMEOUT * 1000)) { 66 | 67 | // Publish "Out" event 68 | char msg[90] = ""; 69 | snprintf(msg, sizeof(msg), 70 | "{\"event\":\"out\",\"mac\":\"%s\",\"rssi\":%d,\"uptime\":%d,\"heap\":%d}", 71 | (devicelist[i].mac).c_str(), devicelist[i].rssi, getUptimeSecs(), ESP.getFreeHeap() 72 | ); 73 | mqttClient.publish(MQTT_OUT_TOPIC, msg); 74 | 75 | // Clear expired device 76 | Serial.printf("[Device Out] MAC: %s\n", (devicelist[i].mac).c_str()); 77 | devicelist[i] = {.mac = "", .rssi = 0, .ms = 0}; 78 | } 79 | } 80 | 81 | // Check console and print list if \n char is detected 82 | char c; 83 | while (Serial.available() > 0) { 84 | c = Serial.read(); 85 | if (c == '\n') { 86 | printlist(); 87 | } 88 | } 89 | } 90 | 91 | void printlist() { 92 | 93 | uint8_t i, c = 0; 94 | 95 | // Count devices 96 | for (i = 0; i < MAX_DEVICES; i++) { 97 | if ((devicelist[i].mac != "") && (devicelist[i].rssi != 0)) { 98 | c++; 99 | } 100 | } 101 | Serial.printf("\n%d registered devices (prober running for %d seconds)\n", c, millis() / 1000UL); 102 | 103 | // List devices 104 | if (c > 0) { 105 | for (i = 0; i < MAX_DEVICES; i++) { 106 | if (devicelist[i].mac != "") { 107 | Serial.printf( 108 | "MAC: %s, RSSI: %d, Last seen %d seconds ago\n", 109 | (devicelist[i].mac).c_str(), 110 | devicelist[i].rssi, 111 | (millis() - devicelist[i].ms) / 1000UL 112 | ); 113 | } 114 | } 115 | } 116 | } 117 | 118 | void onProbeRequest(const WiFiEventSoftAPModeProbeRequestReceived &evt) { 119 | 120 | String mac = macToString(evt.mac); 121 | //Serial.println(mac.c_str()); 122 | 123 | uint8_t i; 124 | for (i = 0; i < MAX_DEVICES; i++) { 125 | if (devicelist[i].mac == mac) { 126 | 127 | // Update device 128 | if (evt.rssi > devicelist[i].rssi) { 129 | devicelist[i].rssi = evt.rssi; // Keep only max rssi 130 | } 131 | devicelist[i].ms = millis(); 132 | 133 | return; 134 | } 135 | } 136 | 137 | for (i = 0; i < MAX_DEVICES; i++) { 138 | if (devicelist[i].mac == "") { 139 | 140 | //Add device 141 | devicelist[i] = {.mac = mac, .rssi = evt.rssi, .ms = millis()}; 142 | Serial.printf("[Device In] MAC: %s, RSSI: %d\n", mac.c_str(), evt.rssi); 143 | 144 | // Publish "In" event 145 | char msg[90] = ""; 146 | snprintf(msg, sizeof(msg), 147 | "{\"event\":\"in\",\"mac\":\"%s\",\"rssi\":%d,\"uptime\":%d,\"heap\":%d}", 148 | mac.c_str(), evt.rssi, getUptimeSecs(), ESP.getFreeHeap() 149 | ); 150 | mqttClient.publish(MQTT_OUT_TOPIC, msg); 151 | 152 | return; 153 | } 154 | } 155 | 156 | Serial.println("Unable to add new device to list"); 157 | } 158 | 159 | void initSerial() { 160 | Serial.begin(115200); 161 | Serial.setDebugOutput(SERIAL_SET_DEBUG_OUTPUT); 162 | Serial.println("\n\n*******************"); 163 | Serial.printf("%s v%s\n\n", ESP_NAME, VERSION); 164 | Serial.printf("STA MAC: %s\n", WiFi.macAddress().c_str()); 165 | Serial.printf("AP MAC: %s\n", WiFi.softAPmacAddress().c_str()); 166 | Serial.printf("Chip ID: %6X\n", ESP.getChipId()); 167 | Serial.printf("Free space: %s\n", prettyBytes(ESP.getFreeSketchSpace()).c_str()); 168 | Serial.printf("Sketch size: %s\n", prettyBytes(ESP.getSketchSize()).c_str()); 169 | Serial.printf("Chip size: %s\n", prettyBytes(ESP.getFlashChipRealSize()).c_str()); 170 | Serial.printf("SDK version: %s\n", ESP.getSdkVersion()); 171 | Serial.println("*******************"); 172 | } 173 | 174 | void initOTA() { 175 | 176 | ArduinoOTA.setHostname(ESP_NAME); 177 | 178 | ArduinoOTA.onStart([]() { 179 | otaInProgress = true; 180 | Serial.println("Start updating..."); 181 | }); 182 | 183 | ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { 184 | Serial.println(progress / (total / 100)); 185 | }); 186 | 187 | ArduinoOTA.onEnd([]() { 188 | Serial.println("\nend"); 189 | otaInProgress = false; 190 | }); 191 | 192 | ArduinoOTA.onError([](ota_error_t error) { 193 | String msg; 194 | if (error == OTA_AUTH_ERROR) msg = "auth failed"; 195 | else if (error == OTA_BEGIN_ERROR) msg = "begin failed"; 196 | else if (error == OTA_CONNECT_ERROR) msg = "connect failed"; 197 | else if (error == OTA_RECEIVE_ERROR) msg = "receive failed"; 198 | else if (error == OTA_END_ERROR) msg = "end failed"; 199 | Serial.printf("Error: %s\n", msg.c_str()); 200 | }); 201 | 202 | ArduinoOTA.begin(); 203 | } 204 | 205 | void wifiConnect() { 206 | while (wifiMulti.run() != WL_CONNECTED) { 207 | delay(500); 208 | } 209 | Serial.printf("Connected to %s with IP %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str()); 210 | } 211 | 212 | void initWiFi() { 213 | 214 | WiFi.hostname(ESP_NAME); 215 | WiFi.mode(WIFI_AP_STA); 216 | 217 | // Connect station 218 | Serial.println("Connecting to WiFi..."); 219 | uint8_t i, s = sizeof(AP_LIST) / sizeof AP_LIST[0]; 220 | for (i = 0; i < s; i++) { 221 | wifiMulti.addAP(AP_LIST[i].ssid, AP_LIST[i].pwd); 222 | } 223 | wifiConnect(); 224 | 225 | // Start AP 226 | WiFi.softAP(AP_SSID, AP_PASSWORD); 227 | } 228 | 229 | void mqttConnect() { 230 | while (!mqttClient.connected()) { 231 | Serial.println("Attempting MQTT connection..."); 232 | if (mqttClient.connect(ESP_NAME)) { 233 | Serial.printf("Connected to %s\n", MQTT_HOST); 234 | mqttClient.publish(MQTT_OUT_TOPIC, "{\"event\":\"connected\"}"); 235 | } else { 236 | Serial.print("failed, rc="); 237 | Serial.print(mqttClient.state()); 238 | Serial.println(" try again in 5 seconds"); 239 | delay(5000); 240 | } 241 | } 242 | } 243 | 244 | void initMQTT() { 245 | mqttClient.setServer(MQTT_HOST, MQTT_PORT); 246 | mqttConnect(); 247 | } 248 | 249 | uint32_t getUptimeSecs() { 250 | static uint32_t uptime = 0; 251 | static uint32_t previousMillis = 0; 252 | uint32_t now = millis(); 253 | 254 | uptime += (now - previousMillis) / 1000UL; 255 | previousMillis = now; 256 | return uptime; 257 | } 258 | 259 | String prettyBytes(uint32_t bytes) { 260 | 261 | const char *suffixes[7] = {"B", "KB", "MB", "GB", "TB", "PB", "EB"}; 262 | uint8_t s = 0; 263 | double count = bytes; 264 | 265 | while (count >= 1024 && s < 7) { 266 | s++; 267 | count /= 1024; 268 | } 269 | if (count - floor(count) == 0.0) { 270 | return String((int) count) + suffixes[s]; 271 | } else { 272 | return String(round(count * 10.0) / 10.0, 1) + suffixes[s]; 273 | }; 274 | } 275 | 276 | String macToString(const uint8 mac[6]) { 277 | char buf[20]; 278 | snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); 279 | return String(buf); 280 | } --------------------------------------------------------------------------------