├── .DS_Store ├── src ├── .DS_Store ├── easyMeshSync.h ├── easyMeshDebug.cpp ├── easyMeshAP.cpp ├── easyMesh.cpp ├── easyMeshComm.cpp ├── easyMesh.h ├── easyMeshSTA.cpp ├── easyMeshSync.cpp └── eashMeshConnection.cpp ├── examples ├── .DS_Store ├── startHere │ ├── .DS_Store │ └── startHere.ino └── demoToy │ ├── data │ ├── index.html │ ├── main.css │ └── main.js │ ├── animations.h │ ├── demoToy.ino │ └── animations.cpp ├── library.properties ├── library.json ├── LICENSE.md └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Coopdis/easyMesh/HEAD/.DS_Store -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Coopdis/easyMesh/HEAD/src/.DS_Store -------------------------------------------------------------------------------- /examples/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Coopdis/easyMesh/HEAD/examples/.DS_Store -------------------------------------------------------------------------------- /examples/startHere/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Coopdis/easyMesh/HEAD/examples/startHere/.DS_Store -------------------------------------------------------------------------------- /src/easyMeshSync.h: -------------------------------------------------------------------------------- 1 | #ifndef _MESH_SYNC_H_ 2 | #define _MESH_SYNC_H_ 3 | 4 | #include 5 | 6 | #define SCAN_INTERVAL 10000 7 | #define SYNC_INTERVAL 7000 8 | #define TIME_SYNC_CYCLES 10 // should (must?) be an even number 9 | 10 | //uint32_t getNodeTime( void ); 11 | 12 | class timeSync { 13 | public: 14 | uint32_t times[TIME_SYNC_CYCLES]; 15 | int8_t num = -1; 16 | bool adopt; 17 | 18 | String buildTimeStamp( void ); 19 | bool processTimeStamp( String &str); 20 | void calcAdjustment ( bool even ); 21 | }; 22 | 23 | #endif // _MESH_SYNC_H_ 24 | 25 | -------------------------------------------------------------------------------- /examples/demoToy/data/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Mesh Demo 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 | 12 |
13 | 14 |
15 |
16 | 17 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name= easyMesh by Coopdis 2 | version=1.0.0 3 | author=Bill Gray (coopdisdev@gmail.com) 4 | maintainer=Bill Gray (coopdisdev@gmail.com) 5 | sentence=A simple mesh networking library for esp8266 6 | paragraph=easyMesh is very simple to implement and establishes a simple and highly adaptable mesh network for any distributed application. Network synchronization, network mapping, time synchronization, package delivery, and dynamic network adaptation are all handled behind the scenes in the library. Just run the init() function and set up the callbacks, then you are off to the races with a dynamic, adaptable, masterless mesh network. 7 | category=Communication 8 | url=https://github.com/Coopdis/easyMesh 9 | architectures=esp8266 -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": “easyMesh”, 3 | "keywords": “esp8266 mesh network networking”, 4 | "description": “A simple mesh networking library for esp8266. easyMesh is very simple to implement and establishes a simple and highly adaptable mesh network for any distributed application. Network synchronization, network mapping, time synchronization, package delivery, and dynamic network adaptation are all handled behind the scenes in the library. Just run the init() function and set up the callbacks, then you are off to the races with a dynamic, adaptable, masterless mesh network.“, 5 | "repository": 6 | { 7 | "type": "git", 8 | "url": "https://github.com/Coopdis/easyMesh" 9 | }, 10 | "version": “1.0.0”, 11 | "frameworks": “*”, 12 | "platforms": "esp8266" 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/easyMeshDebug.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // easyMeshDebug.cpp 3 | // 4 | // 5 | // Created by Bill Gray on 8/18/16. 6 | // 7 | // 8 | 9 | #include 10 | #include 11 | 12 | #include "easyMesh.h" 13 | 14 | uint16_t types = 0; 15 | 16 | void easyMesh::setDebugMsgTypes( uint16_t newTypes ) { 17 | // set the different kinds of debug messages you want to generate. 18 | types = newTypes; 19 | Serial.printf("setDebugTypes 0x%x\n", types); 20 | } 21 | 22 | void easyMesh::debugMsg( debugType type, const char* format ... ) { 23 | 24 | if ( type & types ) { //Print only the message types set for output 25 | char str[200]; 26 | 27 | va_list args; 28 | va_start(args, format); 29 | 30 | vsnprintf(str, sizeof(str), format, args); 31 | 32 | if ( types && MSG_TYPES) 33 | Serial.printf("0x%x\t", type, types ); 34 | 35 | Serial.print( str ); 36 | 37 | va_end(args); 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Bill Gray 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/demoToy/animations.h: -------------------------------------------------------------------------------- 1 | #ifndef _ANIMATIONS_H_ 2 | #define _ANIMATIONS_H_ 3 | 4 | #include 5 | #include 6 | 7 | #define PIXEL_COUNT 16 // make sure to set this to the number of pixels in your strip 8 | #define PIXEL_PIN 2 // make sure to set this to the correct pin, ignored for Esp8266 9 | #define ANIMATION_COUNT 3 10 | 11 | #define MAX_BLIPS 4 12 | 13 | struct AnimationController { 14 | float dimmer; // controls overall brightness 0-1f 15 | float width; // width of the blip 16 | float hue[MAX_BLIPS]; // color of the blip 17 | float offset; // relative offset of this blip 18 | bool direction; 19 | uint16_t nextAnimation; 20 | }; 21 | 22 | enum animationNames { 23 | turnOnIdx = 0, 24 | searchingIdx = 1, 25 | smoothIdx = 2 26 | }; 27 | 28 | void animationsInit(); 29 | 30 | void turnOn(const AnimationParam& param); 31 | void searchingBlip(const AnimationParam& param); 32 | void smoothBlip(const AnimationParam& param); 33 | 34 | void placeBlip(float& blipPos, AnimationController* controller, uint8_t hueIdx); 35 | void allDark( void ); 36 | 37 | 38 | 39 | 40 | #endif // _ANIMATIONS_H_ 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /examples/startHere/startHere.ino: -------------------------------------------------------------------------------- 1 | //************************************************************ 2 | // this is a simple example that uses the easyMesh library 3 | // 4 | // 1. blinks led once for every node on the mesh 5 | // 2. blink cycle repeats every BLINK_PERIOD 6 | // 3. sends a silly message to every node on the mesh at a random time betweew 1 and 5 seconds 7 | // 4. prints anything it recieves to Serial.print 8 | // 9 | // 10 | //************************************************************ 11 | #include 12 | 13 | // some gpio pin that is connected to an LED... 14 | // on my rig, this is 5, change to the right number of your LED. 15 | #define LED 5 // GPIO number of connected LED 16 | 17 | #define BLINK_PERIOD 1000000 // microseconds until cycle repeat 18 | #define BLINK_DURATION 100000 // microseconds LED is on for 19 | 20 | #define MESH_PREFIX "whateverYouLike" 21 | #define MESH_PASSWORD "somethingSneeky" 22 | #define MESH_PORT 5555 23 | 24 | easyMesh mesh; 25 | 26 | uint32_t sendMessageTime = 0; 27 | 28 | void setup() { 29 | Serial.begin(115200); 30 | 31 | pinMode( LED, OUTPUT ); 32 | 33 | //mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // all types on 34 | mesh.setDebugMsgTypes( ERROR | STARTUP ); // set before init() so that you can see startup messages 35 | 36 | mesh.init( MESH_PREFIX, MESH_PASSWORD, MESH_PORT ); 37 | mesh.setReceiveCallback( &receivedCallback ); 38 | mesh.setNewConnectionCallback( &newConnectionCallback ); 39 | 40 | randomSeed( analogRead( A0 ) ); 41 | } 42 | 43 | void loop() { 44 | mesh.update(); 45 | 46 | // run the blinky 47 | bool onFlag = false; 48 | uint32_t cycleTime = mesh.getNodeTime() % BLINK_PERIOD; 49 | for ( uint8_t i = 0; i < ( mesh.connectionCount() + 1); i++ ) { 50 | uint32_t onTime = BLINK_DURATION * i * 2; 51 | 52 | if ( cycleTime > onTime && cycleTime < onTime + BLINK_DURATION ) 53 | onFlag = true; 54 | } 55 | digitalWrite( LED, onFlag ); 56 | 57 | // get next random time for send message 58 | if ( sendMessageTime == 0 ) { 59 | sendMessageTime = mesh.getNodeTime() + random( 1000000, 5000000 ); 60 | } 61 | 62 | // if the time is ripe, send everyone a message! 63 | if ( sendMessageTime != 0 && sendMessageTime < mesh.getNodeTime() ){ 64 | String msg = "Hello from node "; 65 | msg += mesh.getChipId(); 66 | mesh.sendBroadcast( msg ); 67 | sendMessageTime = 0; 68 | } 69 | } 70 | 71 | void receivedCallback( uint32_t from, String &msg ) { 72 | Serial.printf("startHere: Received from %d msg=%s\n", from, msg.c_str()); 73 | } 74 | 75 | void newConnectionCallback( bool adopt ) { 76 | Serial.printf("startHere: New Connection, adopt=%d\n", adopt); 77 | } 78 | 79 | -------------------------------------------------------------------------------- /src/easyMeshAP.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // easyMeshAP.cpp 3 | // 4 | // 5 | // Created by Bill Gray on 7/26/16. 6 | // 7 | // 8 | 9 | #include 10 | 11 | extern "C" { 12 | #include "user_interface.h" 13 | #include "espconn.h" 14 | } 15 | 16 | #include "easyMesh.h" 17 | 18 | 19 | // AP functions 20 | //*********************************************************************** 21 | void ICACHE_FLASH_ATTR easyMesh::apInit( void ) { 22 | // String password( MESH_PASSWORD ); 23 | 24 | ip_addr ip, netmask; 25 | IP4_ADDR( &ip, 192, 168, ( _chipId & 0xFF ), 1); 26 | IP4_ADDR( &netmask, 255, 255, 255, 0); 27 | 28 | ip_info ipInfo; 29 | ipInfo.ip = ip; 30 | ipInfo.gw = ip; 31 | ipInfo.netmask = netmask; 32 | if ( !wifi_set_ip_info( SOFTAP_IF, &ipInfo ) ) { 33 | debugMsg( ERROR, "wifi_set_ip_info() failed\n"); 34 | } 35 | 36 | debugMsg( STARTUP, "apInit(): Starting AP with SSID=%s IP=%d.%d.%d.%d GW=%d.%d.%d.%d NM=%d.%d.%d.%d\n", 37 | _mySSID.c_str(), 38 | IP2STR( &ipInfo.ip ), 39 | IP2STR( &ipInfo.gw ), 40 | IP2STR( &ipInfo.netmask ) ); 41 | 42 | softap_config apConfig; 43 | wifi_softap_get_config( &apConfig ); 44 | 45 | memset( apConfig.ssid, 0, 32 ); 46 | memset( apConfig.password, 0, 64); 47 | memcpy( apConfig.ssid, _mySSID.c_str(), _mySSID.length()); 48 | memcpy( apConfig.password, _meshPassword.c_str(), _meshPassword.length() ); 49 | apConfig.authmode = AUTH_WPA2_PSK; 50 | apConfig.ssid_len = _mySSID.length(); 51 | apConfig.beacon_interval = 100; 52 | apConfig.max_connection = 4; // how many stations can connect to ESP8266 softAP at most. 53 | 54 | wifi_softap_set_config(&apConfig);// Set ESP8266 softap config . 55 | if ( !wifi_softap_dhcps_start() ) 56 | debugMsg( ERROR, "DHCP server failed\n"); 57 | else 58 | debugMsg( STARTUP, "DHCP server started\n"); 59 | 60 | // establish AP tcpServers 61 | tcpServerInit( _meshServerConn, _meshServerTcp, meshConnectedCb, _meshPort ); 62 | } 63 | 64 | //*********************************************************************** 65 | void ICACHE_FLASH_ATTR easyMesh::tcpServerInit(espconn &serverConn, esp_tcp &serverTcp, espconn_connect_callback connectCb, uint32 port) { 66 | 67 | debugMsg( GENERAL, "tcpServerInit():\n"); 68 | 69 | serverConn.type = ESPCONN_TCP; 70 | serverConn.state = ESPCONN_NONE; 71 | serverConn.proto.tcp = &serverTcp; 72 | serverConn.proto.tcp->local_port = port; 73 | espconn_regist_connectcb(&serverConn, connectCb); 74 | sint8 ret = espconn_accept(&serverConn); 75 | if ( ret == 0 ) 76 | debugMsg( STARTUP, "AP tcp server established on port %d\n", port ); 77 | else 78 | debugMsg( ERROR, "AP tcp server on port %d FAILED ret=%d\n", port, ret); 79 | 80 | return; 81 | } 82 | -------------------------------------------------------------------------------- /src/easyMesh.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | extern "C" { 6 | #include "user_interface.h" 7 | #include "espconn.h" 8 | } 9 | 10 | #include "easyMesh.h" 11 | #include "easyMeshSync.h" 12 | 13 | 14 | easyMesh* staticThis; 15 | uint16_t count = 0; 16 | 17 | 18 | // general functions 19 | //*********************************************************************** 20 | /*void ICACHE_FLASH_ATTR easyMesh::init( void ) { 21 | // shut everything down, start with a blank slate. 22 | debugMsg( STARTUP, "init():\n", wifi_station_set_auto_connect( 0 )); 23 | 24 | if ( wifi_station_get_connect_status() != STATION_IDLE ) { 25 | debugMsg( ERROR, "Station is doing something... wierd!? status=%d\n", wifi_station_get_connect_status()); 26 | wifi_station_disconnect(); 27 | } 28 | wifi_softap_dhcps_stop(); 29 | 30 | wifi_set_event_handler_cb( wifiEventCb ); 31 | 32 | staticThis = this; // provides a way for static callback methods to access "this" object; 33 | 34 | // start configuration 35 | debugMsg( GENERAL, "wifi_set_opmode(STATIONAP_MODE) succeeded? %d\n", wifi_set_opmode( STATIONAP_MODE ) ); 36 | 37 | _chipId = system_get_chip_id(); 38 | _mySSID = String( MESH_PREFIX ) + String( _chipId ); 39 | 40 | apInit(); // setup AP 41 | stationInit(); // setup station 42 | 43 | debugMsg( GENERAL, "init(): tcp_max_con=%u\n", espconn_tcp_get_max_con() ); 44 | } 45 | */ 46 | //*********************************************************************** 47 | void ICACHE_FLASH_ATTR easyMesh::init( String prefix, String password, uint16_t port ) { 48 | // shut everything down, start with a blank slate. 49 | debugMsg( STARTUP, "init():\n", wifi_station_set_auto_connect( 0 )); 50 | 51 | if ( wifi_station_get_connect_status() != STATION_IDLE ) { 52 | debugMsg( ERROR, "Station is doing something... wierd!? status=%d\n", wifi_station_get_connect_status()); 53 | wifi_station_disconnect(); 54 | } 55 | wifi_softap_dhcps_stop(); 56 | 57 | wifi_set_event_handler_cb( wifiEventCb ); 58 | 59 | staticThis = this; // provides a way for static callback methods to access "this" object; 60 | 61 | // start configuration 62 | debugMsg( GENERAL, "wifi_set_opmode(STATIONAP_MODE) succeeded? %d\n", wifi_set_opmode( STATIONAP_MODE ) ); 63 | 64 | _meshPrefix = prefix; 65 | _meshPassword = password; 66 | _meshPort = port; 67 | _chipId = system_get_chip_id(); 68 | _mySSID = _meshPrefix + String( _chipId ); 69 | 70 | apInit(); // setup AP 71 | stationInit(); // setup station 72 | 73 | debugMsg( GENERAL, "init(): tcp_max_con=%u\n", espconn_tcp_get_max_con() ); 74 | } 75 | 76 | //*********************************************************************** 77 | void ICACHE_FLASH_ATTR easyMesh::update( void ) { 78 | manageStation(); 79 | manageConnections(); 80 | return; 81 | } 82 | 83 | //*********************************************************************** 84 | bool ICACHE_FLASH_ATTR easyMesh::sendSingle( uint32_t &destId, String &msg ){ 85 | debugMsg( COMMUNICATION, "sendSingle(): dest=%d msg=%s\n", destId, msg.c_str()); 86 | sendMessage( destId, SINGLE, msg ); 87 | } 88 | 89 | //*********************************************************************** 90 | bool ICACHE_FLASH_ATTR easyMesh::sendBroadcast( String &msg ) { 91 | debugMsg( COMMUNICATION, "sendBroadcast(): msg=%s\n", msg.c_str()); 92 | broadcastMessage( _chipId, BROADCAST, msg ); 93 | } 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/easyMeshComm.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // easyMeshComm.cpp 3 | // 4 | // 5 | // Created by Bill Gray on 7/26/16. 6 | // 7 | // 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "easyMesh.h" 14 | 15 | extern easyMesh* staticThis; 16 | 17 | // communications functions 18 | //*********************************************************************** 19 | bool ICACHE_FLASH_ATTR easyMesh::sendMessage( meshConnectionType *conn, uint32_t destId, meshPackageType type, String &msg ) { 20 | debugMsg( COMMUNICATION, "sendMessage(conn): conn-chipId=%d destId=%d type=%d msg=%s\n", 21 | conn->chipId, destId, (uint8_t)type, msg.c_str()); 22 | 23 | String package = buildMeshPackage( destId, type, msg ); 24 | 25 | return sendPackage( conn, package ); 26 | } 27 | 28 | //*********************************************************************** 29 | bool ICACHE_FLASH_ATTR easyMesh::sendMessage( uint32_t destId, meshPackageType type, String &msg ) { 30 | debugMsg( COMMUNICATION, "In sendMessage(destId): destId=%d type=%d, msg=%s\n", 31 | destId, type, msg.c_str()); 32 | 33 | meshConnectionType *conn = findConnection( destId ); 34 | if ( conn != NULL ) { 35 | return sendMessage( conn, destId, type, msg ); 36 | } 37 | else { 38 | debugMsg( ERROR, "In sendMessage(destId): findConnection( destId ) failed\n"); 39 | return false; 40 | } 41 | } 42 | 43 | 44 | //*********************************************************************** 45 | bool ICACHE_FLASH_ATTR easyMesh::broadcastMessage(uint32_t from, 46 | meshPackageType type, 47 | String &msg, 48 | meshConnectionType *exclude ) { 49 | 50 | // send a message to every node on the mesh 51 | 52 | if ( exclude != NULL ) 53 | debugMsg( COMMUNICATION, "broadcastMessage(): from=%d type=%d, msg=%s exclude=%d\n", 54 | from, type, msg.c_str(), exclude->chipId); 55 | else 56 | debugMsg( COMMUNICATION, "broadcastMessage(): from=%d type=%d, msg=%s exclude=NULL\n", 57 | from, type, msg.c_str()); 58 | 59 | SimpleList::iterator connection = _connections.begin(); 60 | while ( connection != _connections.end() ) { 61 | if ( connection != exclude ) { 62 | sendMessage( connection, connection->chipId, type, msg ); 63 | } 64 | connection++; 65 | } 66 | return true; // hmmm... ought to be smarter than this! 67 | } 68 | 69 | //*********************************************************************** 70 | bool ICACHE_FLASH_ATTR easyMesh::sendPackage( meshConnectionType *connection, String &package ) { 71 | debugMsg( COMMUNICATION, "Sending to %d-->%s<--\n", connection->chipId, package.c_str() ); 72 | 73 | if ( package.length() > 1400 ) 74 | debugMsg( ERROR, "sendPackage(): err package too long length=%d\n", package.length()); 75 | 76 | if ( connection->sendReady == true ) { 77 | sint8 errCode = espconn_send( connection->esp_conn, (uint8*)package.c_str(), package.length() ); 78 | connection->sendReady = false; 79 | 80 | if ( errCode == 0 ) { 81 | return true; 82 | } 83 | else { 84 | debugMsg( ERROR, "sendPackage(): espconn_send Failed err=%d\n", errCode ); 85 | return false; 86 | } 87 | } 88 | else { 89 | connection->sendQueue.push_back( package ); 90 | } 91 | } 92 | 93 | //*********************************************************************** 94 | String ICACHE_FLASH_ATTR easyMesh::buildMeshPackage( uint32_t destId, meshPackageType type, String &msg ) { 95 | debugMsg( GENERAL, "In buildMeshPackage(): msg=%s\n", msg.c_str() ); 96 | 97 | DynamicJsonBuffer jsonBuffer( JSON_BUFSIZE ); 98 | JsonObject& root = jsonBuffer.createObject(); 99 | root["dest"] = destId; 100 | root["from"] = _chipId; 101 | root["type"] = (uint8_t)type; 102 | 103 | switch( type ) { 104 | case NODE_SYNC_REQUEST: 105 | case NODE_SYNC_REPLY: 106 | { 107 | JsonArray& subs = jsonBuffer.parseArray( msg ); 108 | if ( !subs.success() ) { 109 | debugMsg( GENERAL, "buildMeshPackage(): subs = jsonBuffer.parseArray( msg ) failed!"); 110 | } 111 | root["subs"] = subs; 112 | break; 113 | } 114 | case TIME_SYNC: 115 | root["msg"] = jsonBuffer.parseObject( msg ); 116 | break; 117 | default: 118 | root["msg"] = msg; 119 | } 120 | 121 | String ret; 122 | root.printTo( ret ); 123 | return ret; 124 | } 125 | -------------------------------------------------------------------------------- /examples/demoToy/demoToy.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "animations.h" 7 | 8 | #define MESH_PREFIX "whateverYouLike" 9 | #define MESH_PASSWORD "somethingSneeky" 10 | #define MESH_PORT 5555 11 | 12 | // globals 13 | easyMesh mesh; // mesh global 14 | extern NeoPixelBus strip; // using the method that works for sparkfun thing 15 | extern NeoPixelAnimator animations; // NeoPixel animation management object 16 | extern AnimationController controllers[]; // array of add-on controllers for my animations 17 | os_timer_t yerpTimer; 18 | 19 | void setup() { 20 | Serial.begin( 115200 ); 21 | 22 | // setup mesh 23 | // mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE | APPLICATION ); // all types on 24 | mesh.setDebugMsgTypes( ERROR | STARTUP | APPLICATION ); // set before init() so that you can see startup messages 25 | mesh.init( MESH_PREFIX, MESH_PASSWORD, MESH_PORT); 26 | mesh.setReceiveCallback( &receivedCallback ); 27 | mesh.setNewConnectionCallback( &newConnectionCallback ); 28 | 29 | // setups webServer 30 | webServerInit(); 31 | 32 | // setup webSocket 33 | webSocketInit(); 34 | webSocketSetReceiveCallback( &wsReceiveCallback ); 35 | webSocketSetConnectionCallback( &wsConnectionCallback ); 36 | 37 | mesh.debugMsg( STARTUP, "\nIn setup() my chipId=%d\n", mesh.getChipId()); 38 | 39 | strip.Begin(); 40 | strip.Show(); 41 | 42 | animationsInit(); 43 | 44 | os_timer_setfn( &yerpTimer, yerpCb, NULL ); 45 | os_timer_arm( &yerpTimer, 1000, 1 ); 46 | } 47 | 48 | void loop() { 49 | mesh.update(); 50 | 51 | static uint16_t previousConnections; 52 | uint16_t numConnections = mesh.connectionCount(); 53 | if( countWsConnections() > 0 ) 54 | numConnections++; 55 | 56 | if ( previousConnections != numConnections ) { 57 | mesh.debugMsg( GENERAL, "loop(): numConnections=%d\n", numConnections); 58 | 59 | if ( numConnections == 0 ) { 60 | controllers[smoothIdx].nextAnimation = searchingIdx; 61 | controllers[searchingIdx].nextAnimation = searchingIdx; 62 | controllers[searchingIdx].hue[0] = 0.0f; 63 | } else { 64 | controllers[searchingIdx].nextAnimation = smoothIdx; 65 | controllers[smoothIdx].nextAnimation = smoothIdx; 66 | } 67 | 68 | sendWsControl(); 69 | 70 | previousConnections = numConnections; 71 | } 72 | 73 | animations.UpdateAnimations(); 74 | strip.Show(); 75 | } 76 | 77 | void yerpCb( void *arg ) { 78 | static int yerpCount; 79 | int connCount = 0; 80 | 81 | String msg = "Yerp="; 82 | msg += yerpCount++; 83 | 84 | mesh.debugMsg( APPLICATION, "msg-->%s<-- stationStatus=%u numConnections=%u\n", msg.c_str(), wifi_station_get_connect_status(), mesh.connectionCount( NULL ) ); 85 | 86 | SimpleList::iterator connection = mesh._connections.begin(); 87 | while ( connection != mesh._connections.end() ) { 88 | mesh.debugMsg( APPLICATION, "\tconn#%d, chipId=%d subs=%s\n", connCount++, connection->chipId, connection->subConnections.c_str() ); 89 | connection++; 90 | } 91 | 92 | // send ping to webSockets 93 | String ping("ping"); 94 | broadcastWsMessage(ping.c_str(), ping.length(), OPCODE_TEXT); 95 | //sendWsControl(); 96 | } 97 | 98 | void newConnectionCallback( bool adopt ) { 99 | if ( adopt == false ) { 100 | String control = buildControl(); 101 | mesh.sendBroadcast( control ); 102 | } 103 | } 104 | 105 | void receivedCallback( uint32_t from, String &msg ) { 106 | mesh.debugMsg( APPLICATION, "receivedCallback():\n"); 107 | 108 | DynamicJsonBuffer jsonBuffer(50); 109 | JsonObject& control = jsonBuffer.parseObject( msg ); 110 | 111 | broadcastWsMessage(msg.c_str(), msg.length(), OPCODE_TEXT); 112 | 113 | mesh.debugMsg( APPLICATION, "control=%s\n", msg.c_str()); 114 | 115 | for ( int i = 0; i < ( mesh.connectionCount( NULL ) + 1 ); i++) { 116 | float hue = control[String(i)]; 117 | controllers[smoothIdx].hue[i] = hue; 118 | } 119 | } 120 | 121 | void wsConnectionCallback( void ) { 122 | mesh.debugMsg( APPLICATION, "wsConnectionCallback():\n"); 123 | } 124 | 125 | void wsReceiveCallback( char *payloadData ) { 126 | mesh.debugMsg( APPLICATION, "wsReceiveCallback(): payloadData=%s\n", payloadData ); 127 | 128 | String msg( payloadData ); 129 | mesh.sendBroadcast( msg ); 130 | 131 | if ( strcmp( payloadData, "wsOpened") == 0) { // hack to give the browser time to get the ws up and running 132 | mesh.debugMsg( APPLICATION, "wsReceiveCallback(): received wsOpened\n" ); 133 | sendWsControl(); 134 | return; 135 | } 136 | 137 | StaticJsonBuffer<200> jsonBuffer; 138 | JsonObject& control = jsonBuffer.parseObject(payloadData); 139 | 140 | if (!control.success()) { // Test if parsing succeeded. 141 | mesh.debugMsg( APPLICATION, "wsReceiveCallback(): parseObject() failed. payload=%s<--\n", payloadData); 142 | return; 143 | } 144 | 145 | uint16_t blips = mesh.connectionCount() + 1; 146 | if ( blips > MAX_BLIPS ) 147 | blips = MAX_BLIPS; 148 | 149 | for ( int i = 0; i < blips; i++) { 150 | String temp(i); 151 | float hue = control[temp]; 152 | controllers[smoothIdx].hue[i] = hue; 153 | } 154 | } 155 | 156 | void sendWsControl( void ) { 157 | mesh.debugMsg( APPLICATION, "sendWsControl():\n"); 158 | 159 | String control = buildControl(); 160 | broadcastWsMessage(control.c_str(), control.length(), OPCODE_TEXT); 161 | } 162 | 163 | String buildControl ( void ) { 164 | uint16_t blips = mesh.connectionCount() + 1; 165 | mesh.debugMsg( APPLICATION, "buildControl(): blips=%d\n", blips); 166 | 167 | if ( blips > 3 ) { 168 | mesh.debugMsg( APPLICATION, " blips out of range =%d\n", blips); 169 | blips = 3; 170 | } 171 | 172 | StaticJsonBuffer<200> jsonBuffer; 173 | JsonObject& control = jsonBuffer.createObject(); 174 | for (int i = 0; i < blips; i++ ) { 175 | control[String(i)] = String(controllers[smoothIdx].hue[i]); 176 | } 177 | 178 | String ret; 179 | control.printTo(ret); 180 | return ret; 181 | } 182 | 183 | 184 | -------------------------------------------------------------------------------- /examples/demoToy/data/main.css: -------------------------------------------------------------------------------- 1 | 2 | .body { 3 | background-color: #111; 4 | overflow-x: hidden; 5 | } 6 | 7 | .screen { 8 | width: 95%; 9 | margin-left: auto; 10 | margin-right: auto; 11 | color: aqua; 12 | text-align: center; 13 | font-family:sans-serif; 14 | } 15 | 16 | input[type=range] { 17 | -webkit-appearance: none; /* Hides the slider so that custom slider can be made */ 18 | width: 100%; /* Specific width is required for Firefox. */ 19 | background: transparent; /* Otherwise white in Chrome */ 20 | } 21 | 22 | input[type=range]:focus { 23 | outline: none; /* Removes the blue border. You should probably do some kind of focus styling for accessibility reasons though. */ 24 | } 25 | 26 | input[type=range]::-webkit-slider-thumb { 27 | width: 200px; 28 | height: 230px; 29 | -webkit-appearance: none; 30 | background: transparent; 31 | z-index: -150; 32 | } 33 | 34 | .sliderRange { 35 | -webkit-appearance: none; 36 | -moz-appearance: none; 37 | color: transparent; 38 | background-color: transparent; 39 | width: 100%; 40 | height: 100%; 41 | position: absolute; 42 | top: 0; 43 | left: 0; 44 | z-index: 150; 45 | } 46 | .sliderThumb { 47 | position: absolute; 48 | width: 200px; 49 | height: 230px; 50 | top: 0px; 51 | border-radius: 90px; 52 | border: 4px solid #aaa; 53 | box-shadow: 3px 3px 3px #111; 54 | z-index: 0; 55 | } 56 | 57 | .sliderContainer { 58 | width: 100%; 59 | margin-top: 75px; 60 | margin-bottom: 75px; 61 | position: relative; 62 | } 63 | 64 | .sliderTrack { 65 | position: absolute; 66 | height: 30px; 67 | border: 2px solid #999; 68 | border-radius: 20px; 69 | z-index: -50; 70 | } 71 | 72 | .hero { 73 | height: 50px; 74 | /*visibility: hidden; 75 | */ 76 | } 77 | 78 | .footer { 79 | height: 50px; 80 | /*visibility: hidden; 81 | */ 82 | } 83 | 84 | .header { 85 | height: 50px; 86 | visibility: hidden; 87 | } 88 | 89 | .meshMsg { 90 | text-align: left; 91 | /* visibility: hidden; 92 | */ 93 | } 94 | 95 | .connectButton { 96 | height: 200px; 97 | width: 200px; 98 | margin-left: auto; 99 | margin-right: auto; 100 | border-radius: 60px; 101 | border: 4px solid #aaa; 102 | box-shadow: 3px 3px 3px #111; 103 | background-color: #262; 104 | font-family: sans-serif; 105 | font-size: 30px; 106 | 107 | } 108 | 109 | .buttonText { 110 | color: #fff; 111 | margin-top: 48px; 112 | } 113 | 114 | 115 | /* ========================================================== 116 | * Loading Bars 117 | * =========================================================*/ 118 | .loading{ 119 | width:100px; 120 | height:100px; 121 | margin:30px auto; 122 | position:relative; 123 | } 124 | 125 | .loading.bar div{ 126 | width: 10px; 127 | height: 30px; 128 | background: black; 129 | position: absolute; 130 | top: 35px; 131 | left: 45px; 132 | opacity:0.05; 133 | -webkit-animation: fadeit 1.1s linear infinite; 134 | -moz-animation: fadeit 1.1s linear infinite; 135 | animation: fadeit 1.1s linear infinite; 136 | } 137 | 138 | .loading.bar div:nth-child(1){ 139 | -webkit-transform: rotate(0deg) translate(0, -30px); 140 | -moz-transform: rotate(0deg) translate(0, -30px); 141 | transform: rotate(0deg) translate(0, -30px); 142 | 143 | -webkit-animation-delay:0.39s; 144 | -moz-animation-delay:0.39s; 145 | animation-delay:0.39s; 146 | } 147 | 148 | .loading.bar div:nth-child(2){ 149 | -webkit-transform: rotate(45deg) translate(0, -30px); 150 | -moz-transform: rotate(45deg) translate(0, -30px); 151 | transform: rotate(45deg) translate(0, -30px); 152 | 153 | -webkit-animation-delay:0.52s; 154 | -moz-animation-delay:0.52s; 155 | animation-delay:0.52s; 156 | } 157 | 158 | .loading.bar div:nth-child(3){ 159 | -webkit-transform: rotate(90deg) translate(0, -30px); 160 | -moz-transform: rotate(90deg) translate(0, -30px); 161 | transform: rotate(90deg) translate(0, -30px); 162 | 163 | -webkit-animation-delay:0.65s; 164 | -moz-animation-delay:0.65s; 165 | animation-delay:0.65s; 166 | } 167 | 168 | .loading.bar div:nth-child(4){ 169 | -webkit-transform: rotate(135deg) translate(0, -30px); 170 | -moz-transform: rotate(135deg) translate(0, -30px); 171 | transform: rotate(135deg) translate(0, -30px); 172 | 173 | -webkit-animation-delay:0.78s; 174 | -moz-animation-delay:0.78s; 175 | animation-delay:0.78s; 176 | } 177 | 178 | .loading.bar div:nth-child(5){ 179 | -webkit-transform: rotate(180deg) translate(0, -30px); 180 | -moz-transform: rotate(180deg) translate(0, -30px); 181 | transform: rotate(180deg) translate(0, -30px); 182 | 183 | -webkit-animation-delay:0.91s; 184 | -moz-animation-delay:0.91s; 185 | animation-delay:0.91s; 186 | } 187 | 188 | .loading.bar div:nth-child(6){ 189 | -webkit-transform: rotate(225deg) translate(0, -30px); 190 | -moz-transform: rotate(225deg) translate(0, -30px); 191 | transform: rotate(225deg) translate(0, -30px); 192 | 193 | -webkit-animation-delay:1.04s; 194 | -moz-animation-delay:1.04s; 195 | animation-delay:1.04s; 196 | } 197 | 198 | .loading.bar div:nth-child(7){ 199 | -webkit-transform: rotate(270deg) translate(0, -30px); 200 | -moz-transform: rotate(270deg) translate(0, -30px); 201 | transform: rotate(270deg) translate(0, -30px); 202 | 203 | -webkit-animation-delay:1.17s; 204 | -moz-animation-delay:1.17s; 205 | animation-delay:1.17s; 206 | } 207 | 208 | .loading.bar div:nth-child(8){ 209 | -webkit-transform: rotate(315deg) translate(0, -30px); 210 | -moz-transform: rotate(315deg) translate(0, -30px); 211 | transform: rotate(315deg) translate(0, -30px); 212 | 213 | -webkit-animation-delay:1.3s; 214 | -moz-animation-delay:1.3s; 215 | animation-delay:1.3s; 216 | } 217 | 218 | @-webkit-keyframes fadeit{ 219 | 0%{ 220 | opacity:1; 221 | } 222 | 100%{ 223 | opacity:0; 224 | } 225 | } 226 | @-moz-keyframes fadeit{ 227 | 0%{ 228 | opacity:1; 229 | } 230 | 100%{ 231 | opacity:0; 232 | } 233 | } 234 | @keyframes fadeit{ 235 | 0%{ 236 | opacity:1; 237 | } 238 | 100%{ 239 | opacity:0; 240 | } 241 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Intro to easyMesh 2 | easyMesh is a library that takes care of the particulars for creating a simple mesh network using Arduino and esp8266. The goal is to allow the programmer to work with a mesh network without having to worry about how the network is structured or managed. 3 | 4 | ###True ad-hoc netoworking 5 | easyMesh is a true ad-hoc network, meaning that no-planning, central controller, or router is required. Any system of 1 or more nodes will self-organize into fully functional mesh. The maximum size of the mesh is limited (i think) by the amount of memory in the heap that can be allocated to the sub-connections buffer… and so should be really quite high. 6 | 7 | ###JSON based 8 | easyMesh uses JSON objects for all its messaging. There a couple of reasons for this. First, it makes the code and the messages human readable and easy to understand and second, it makes it easy to integrate easyMesh with javascript front-ends, web applications, and other apps. Some performance is lost, but I haven’t been running into performance issues yet. Converting to binary messaging would be fairly straight forward if someone wants to contribute. 9 | 10 | ###Wifi & Networking 11 | easyMesh is designed to be used with Arduino, but it does not use the Arduino WiFi libraries, as I was running into performance issues (primarily latency) with them. Rather the networking is all done using the native esp8266 SDK libraries, which are available through the Arduino IDE. Hopefully though, which networking libraries are used won’t matter to most users much as you can just include the .h, run the init() and then work the library through the API. 12 | 13 | ###easyMesh is not IP networking 14 | easyMesh does not create a TCP/IP network of nodes. Rather each of the nodes is uniquely identified by its 32bit chipId which is retrieved from the esp8266 using the system_get_chip_id() call in the SDK. Every esp8266 will have a unique number. Messages can either be broadcast to all of the nodes on the mesh, or sent specifically to an individual node which is identified by its chipId. 15 | 16 | ###Examples 17 | demoToy is currently the only example. It is kind of complex, uses a web server, web sockets, and neopixel animations, so it is not really a great entry level example. That said, it does some pretty cools stuff… here is a video of the demo. 18 | 19 | https://www.youtube.com/watch?v=hqjOY8YHdlM&feature=youtu.be 20 | 21 | ###Dependencies 22 | easyMesh makes use of the following libraries. They can both be installed through Arduino Library Manager 23 | - SimpleList *** Available here... https://github.com/blackhack/ArduLibraries/tree/master/SimpleList 24 | - ArduinoJson *** Available here... https://github.com/bblanchon/ArduinoJson 25 | 26 | #easyMesh API 27 | Using easyMesh is easy! 28 | 29 | First include the library and create an easyMesh object like this… 30 | 31 | ``` 32 | #include 33 | easyMesh mesh; 34 | ``` 35 | 36 | ##Member Functions 37 | 38 | ###void easyMesh::init( String prefix, String password, uint16_t port ) 39 | Add this to your setup() function. 40 | Initialize the mesh network. This routine does the following things… 41 | - Starts a wifi network 42 | - Begins searching for other wifi networks that are part of the mesh 43 | - Logs on to the best mesh network node it finds… if it doesn’t find anything, it starts a new search in 5 seconds. 44 | 45 | prefix = the name of your mesh. The wifi ssid of this node will be prefix + chipId 46 | password = wifi password to your mesh 47 | port = the TCP port that you want the mesh server to run on 48 | 49 | ###void easyMesh::update( void ) 50 | Add this to your loop() function 51 | This routine runs various maintainance tasks... Not super interesting, but things don't work without it. 52 | 53 | 54 | ###void easyMesh::setReceiveCallback( &receivedCallback ) 55 | Set a callback routine for any messages that are addressed to this node. The callback routine has the following structure… 56 | 57 | `void receivedCallback( uint32_t from, String &msg )` 58 | 59 | Every time this node receives a message, this callback routine will the called. “from” is the id of the original sender of the message, and “msg” is a string that contains the message. The message can be anything. A JSON, some other text string, or binary data. 60 | 61 | 62 | ###void easyMesh::setNewConnectionCallback( &newConnectionCallback ) 63 | This fires every time the local node makes a new connection. The callback has the following structure… 64 | 65 | `void newConnectionCallback( bool adopt )` 66 | 67 | `adopt` is a boolean value that indicates whether the mesh has determined to adopt the remote nodes timebase or not. If `adopt == true`, then this node has adopted the remote node’s timebase. 68 | 69 | The mesh does a simple calculation to determine which nodes adopt and which nodes don’t. When a connection is made, the node with the smaller number of connections to other nodes adopts the timebase of the node with the larger number of connections to other nodes. If there is a tie, then the AP (access point) node wins. 70 | 71 | ######Example 1: 72 | There are two separate meshes (Mesh A and Mesh B) that have discovered each other and are connecting. Mesh A has 7 nodes and Mesh B has 8 nodes. When the connection is made, Mesh B has more nodes in it, so Mesh A adopts the timebase of Mesh B. 73 | 74 | ######Example 2: 75 | A brand new mesh is starting. There are only 2 nodes (Node X and Node Y) and they both just got turned on. They find each other, and as luck would have it, Node X connects as a Station to the wifi network established by Node Y’s AP (access point)… which means that Node X is the wifi client and Node Y is the wifi server in the particular relationship. In this case, since both nodes have zero (0) other connections, Node X adopts Node Y’s timebase because the tie (0 vs 0) goes to the AP. 76 | 77 | ###bool easyMesh::sendBroadcast( String &msg) 78 | Sends msg to every node on the entire mesh network. 79 | 80 | returns true if everything works, false if not. Prints an error message to Serial.print, if there is a failure. 81 | 82 | ###bool easyMesh::sendSingle(uint32_t dest, String &msg) 83 | Sends msg to the node with Id == dest. 84 | 85 | returns true if everything works, false if not. Prints an error message to Serial.print, if there is a failure. 86 | 87 | ###uint16_t easyMesh::connectionCount() 88 | Returns the total number of nodes connected to this mesh. 89 | 90 | ###uint32_t easyMesh::getChipId( void ) 91 | Return the chipId of the node that we are running on. 92 | 93 | ###uint32_t easyMesh::getNodeTime( void ) 94 | Returns the mesh timebase microsecond counter. Rolls over 71 minutes from startup of the first node. 95 | -------------------------------------------------------------------------------- /examples/demoToy/animations.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "animations.h" 7 | 8 | NeoGamma colorGamma; // for any fade animations, best to correct gamma 9 | NeoPixelBus strip(PIXEL_COUNT, PIXEL_PIN); 10 | NeoPixelAnimator animations(ANIMATION_COUNT); // NeoPixel animation management object 11 | AnimationController controllers[ANIMATION_COUNT]; 12 | 13 | extern easyMesh mesh; 14 | 15 | void animationsInit() { 16 | animations.StartAnimation(turnOnIdx, 3000, turnOn); 17 | controllers[turnOnIdx].dimmer = 0.1f; // controls overall brightness 0-1f 18 | controllers[turnOnIdx].width = 3.0f; // width of the blip 19 | controllers[turnOnIdx].nextAnimation = searchingIdx; // run once and then run search 20 | 21 | animations.StartAnimation(searchingIdx, 1000, searchingBlip); 22 | animations.StopAnimation(searchingIdx); 23 | controllers[searchingIdx].dimmer = 0.15f; // controls overall brightness 0-1f 24 | controllers[searchingIdx].width = 2.0f; // width of the blip 25 | controllers[searchingIdx].hue[0] = 0.0f; // color of the blip 26 | controllers[searchingIdx].nextAnimation = searchingIdx; // run itself over and over until this changes. 27 | 28 | animations.StartAnimation(smoothIdx, 2000, smoothBlip); 29 | animations.StopAnimation(smoothIdx); 30 | controllers[smoothIdx].dimmer = 0.1f; // controls overall brightness 0-1f 31 | controllers[smoothIdx].width = 2.5f; // width of the blip 32 | 33 | controllers[smoothIdx].hue[0] = 0.22f; // color of the blip 34 | controllers[smoothIdx].hue[1] = 0.44f; // color of the blip 35 | controllers[smoothIdx].hue[2] = 0.66f; // color of the blip 36 | 37 | controllers[smoothIdx].offset = 0.0f; // relative offset of this blip 38 | controllers[smoothIdx].direction = true; // true == clockwise, false == counterclockwise 39 | controllers[smoothIdx].nextAnimation = smoothIdx; // run itself over and over until this changes. 40 | } 41 | 42 | 43 | void turnOn(const AnimationParam& param) { 44 | if (param.state == AnimationState_Completed) { // animation finished, restart 45 | animations.RestartAnimation(controllers[param.index].nextAnimation); 46 | } 47 | 48 | float lightness = controllers[param.index].dimmer; 49 | 50 | // fade in, fade out 51 | float fadeTime = 0.45f; 52 | if ( param.progress < fadeTime ) { // fade in 53 | lightness *= param.progress / fadeTime; 54 | } 55 | if ( param.progress > ( 1 - fadeTime ) ) { // fade out 56 | lightness *= ( 1 - param.progress ) / fadeTime; 57 | } 58 | 59 | // rotating rainbow 60 | float hue; 61 | float rotation; 62 | if ( param.progress > 0.5f ) //rotate clockwise 63 | rotation = param.progress * 4; 64 | else 65 | rotation = (1 - param.progress ) * 4; // rotate counter clockwise 66 | 67 | for (uint8_t index = 0 ; index < strip.PixelCount(); index++) 68 | { 69 | hue = (float)index / (float)strip.PixelCount(); 70 | hue += rotation; 71 | 72 | while ( hue > 1) //kluge to get around lack of functioning fmod 73 | hue -= 1; 74 | 75 | RgbColor color = HslColor( hue, 1.0f, lightness); 76 | 77 | strip.SetPixelColor( index, colorGamma.Correct(color)); 78 | } 79 | } 80 | 81 | void searchingBlip(const AnimationParam& param) { 82 | static uint8_t currentGoal, previousGoal; 83 | float blipPos; 84 | 85 | if (param.state == AnimationState_Completed) { // animation finished, restart 86 | previousGoal = currentGoal; 87 | currentGoal = ( currentGoal + strip.PixelCount() / 4 + random( strip.PixelCount() / 2 ) ) % strip.PixelCount(); 88 | animations.RestartAnimation(controllers[param.index].nextAnimation); 89 | } 90 | 91 | float movementDuration = 0.3f; 92 | if ( param.progress < movementDuration ) 93 | blipPos = abs( previousGoal + ( currentGoal - previousGoal ) * param.progress / movementDuration ); 94 | else 95 | blipPos = (float)currentGoal; 96 | 97 | placeBlip( blipPos, controllers + param.index, 0 ); 98 | } 99 | 100 | void smoothBlip(const AnimationParam& param) { 101 | uint16_t duration = animations.AnimationDuration(param.index); 102 | float progress = (float)( ( mesh.getNodeTime() / 1000 ) % duration ) / (float)duration; 103 | 104 | float offsetProgress = ( abs( controllers[param.index].direction - progress ) + controllers[param.index].offset); 105 | 106 | while ( offsetProgress > 1 ) // cheap float modulo 1 107 | offsetProgress -= 1; 108 | 109 | float blipPeak = offsetProgress * strip.PixelCount(); 110 | 111 | if (param.state == AnimationState_Completed) { // animation finished, restart 112 | animations.RestartAnimation(controllers[param.index].nextAnimation); 113 | } 114 | 115 | allDark(); 116 | 117 | uint16_t blips = mesh.connectionCount() + 1; 118 | if ( blips > MAX_BLIPS ) 119 | blips = MAX_BLIPS; 120 | 121 | for ( int i = 0; i < blips; i++ ) { 122 | float delta = (float)i * (float)strip.PixelCount() / (float)blips; 123 | float tempPeak = blipPeak + delta; 124 | while ( tempPeak > strip.PixelCount() ) //hack float modulo 125 | tempPeak -= strip.PixelCount(); 126 | 127 | placeBlip( tempPeak, controllers + param.index, i ); 128 | } 129 | } 130 | 131 | void placeBlip(float& blipPos, AnimationController* controller, uint8_t hueIdx) { 132 | float lightness, sum, roldex; 133 | 134 | for (uint8_t index = 0 ; index < strip.PixelCount(); index++) 135 | { 136 | if ( blipPos + controller->width > strip.PixelCount() + index ) { // if peak is within a width of the last pixel 137 | roldex = index + strip.PixelCount(); 138 | } 139 | else if ( blipPos - controller->width < index - strip.PixelCount() ) { // if peak is withing a width of the first pixel 140 | roldex = index - strip.PixelCount(); 141 | } 142 | else { 143 | roldex = index; 144 | } 145 | 146 | sum = ( blipPos - roldex ) / controller->width; 147 | 148 | if ( 0 <= sum && sum < 1 ) { 149 | lightness = 1 - sum; 150 | } 151 | else if ( 0 > sum && sum > -1 ) { 152 | lightness = sum + 1; 153 | } 154 | else { 155 | lightness = 0; 156 | } 157 | 158 | if ( lightness != 0 ) { 159 | lightness *= controller->dimmer; 160 | RgbColor color = HslColor( controller->hue[hueIdx], 1.0f, lightness); 161 | strip.SetPixelColor( index, colorGamma.Correct(color)); 162 | } 163 | } 164 | } 165 | 166 | void allDark( void ) { 167 | RgbColor color = HslColor( 0, 1.0f, 0); 168 | 169 | for (uint8_t index = 0 ; index < strip.PixelCount(); index++) { 170 | strip.SetPixelColor( index, colorGamma.Correct(color)); 171 | } 172 | } 173 | 174 | -------------------------------------------------------------------------------- /src/easyMesh.h: -------------------------------------------------------------------------------- 1 | #ifndef _EASY_MESH_H_ 2 | #define _EASY_MESH_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | extern "C" { 9 | #include "user_interface.h" 10 | #include "espconn.h" 11 | } 12 | 13 | #include "easyMeshSync.h" 14 | 15 | //#define MESH_PREFIX "mesh" 16 | //#define MESH_PASSWORD "bootyboo" 17 | //#define MESH_PORT 4444 18 | #define NODE_TIMEOUT 3000000 //uSecs 19 | 20 | #define JSON_BUFSIZE 300 // initial size for the DynamicJsonBuffers. 21 | 22 | 23 | enum nodeStatusType { 24 | INITIALIZING = 0, 25 | SEARCHING = 1, 26 | FOUND_MESH = 2, 27 | CONNECTED = 3 28 | }; 29 | 30 | enum scanStatusType { 31 | IDLE = 0, 32 | SCANNING = 1 33 | }; 34 | 35 | enum syncStatusType { 36 | NEEDED = 0, 37 | REQUESTED = 1, 38 | IN_PROGRESS = 2, 39 | COMPLETE = 3 40 | }; 41 | 42 | enum meshPackageType { 43 | DROP = 3, 44 | TIME_SYNC = 4, 45 | NODE_SYNC_REQUEST = 5, 46 | NODE_SYNC_REPLY = 6, 47 | CONTROL = 7, //deprecated 48 | BROADCAST = 8, //application data for everyone 49 | SINGLE = 9 //application data for a single node 50 | }; 51 | 52 | 53 | 54 | enum debugType { 55 | ERROR = 0x0001, 56 | STARTUP = 0x0002, 57 | MESH_STATUS = 0x0004, 58 | CONNECTION = 0x0008, 59 | SYNC = 0x0010, 60 | COMMUNICATION = 0x0020, 61 | GENERAL = 0x0040, 62 | MSG_TYPES = 0x0080, 63 | REMOTE = 0x0100, // not yet implemented 64 | APPLICATION = 0x0200 65 | // add types if you like, room for a total of 16 types 66 | }; 67 | 68 | 69 | struct meshConnectionType { 70 | espconn *esp_conn; 71 | uint32_t chipId = 0; 72 | String subConnections; 73 | timeSync time; 74 | uint32_t lastRecieved = 0; 75 | bool newConnection = true; 76 | 77 | syncStatusType nodeSyncStatus = NEEDED; 78 | uint32_t nodeSyncRequest = 0; 79 | 80 | syncStatusType timeSyncStatus = NEEDED; 81 | uint32_t lastTimeSync = 0; 82 | // bool needsNodeSync = true; 83 | // bool needsTimeSync = false; 84 | 85 | bool sendReady = true; 86 | SimpleList sendQueue; 87 | }; 88 | 89 | 90 | class easyMesh { 91 | public: 92 | //inline functions 93 | uint32 getChipId( void ) { return _chipId;}; 94 | 95 | // in easyMeshDebug.cpp 96 | void setDebugMsgTypes( uint16_t types ); 97 | void debugMsg( debugType type, const char* format ... ); 98 | 99 | // in easyMesh.cpp 100 | // void init( void ); 101 | void init( String prefix, String password, uint16_t port ); 102 | 103 | void update( void ); 104 | bool sendSingle( uint32_t &destId, String &msg ); 105 | bool sendBroadcast( String &msg ); 106 | 107 | // in easyMeshConnection.cpp 108 | void setReceiveCallback( void(*onReceive)(uint32_t from, String &msg) ); 109 | void setNewConnectionCallback( void(*onNewConnection)(bool adopt) ); 110 | uint16_t connectionCount( meshConnectionType *exclude = NULL ); 111 | 112 | // in easyMeshSync.cpp 113 | uint32_t getNodeTime( void ); 114 | 115 | // should be prototected, but public for debugging 116 | scanStatusType _scanStatus = IDLE; 117 | nodeStatusType _nodeStatus = INITIALIZING; 118 | SimpleList _meshAPs; 119 | SimpleList _connections; 120 | 121 | protected: 122 | 123 | // in easyMeshComm.cpp 124 | //must be accessable from callback 125 | bool sendMessage( meshConnectionType *conn, uint32_t destId, meshPackageType type, String &msg ); 126 | bool sendMessage( uint32_t destId, meshPackageType type, String &msg ); 127 | bool broadcastMessage( uint32_t fromId, meshPackageType type, String &msg, meshConnectionType *exclude = NULL ); 128 | 129 | bool sendPackage( meshConnectionType *connection, String &package ); 130 | String buildMeshPackage(uint32_t destId, meshPackageType type, String &msg); 131 | 132 | 133 | // in easyMeshSync.cpp 134 | //must be accessable from callback 135 | void startNodeSync( meshConnectionType *conn ); 136 | void handleNodeSync( meshConnectionType *conn, JsonObject& root ); 137 | void startTimeSync( meshConnectionType *conn ); 138 | void handleTimeSync( meshConnectionType *conn, JsonObject& root ); 139 | bool adoptionCalc( meshConnectionType *conn ); 140 | 141 | // in easyMeshConnection.cpp 142 | void manageConnections( void ); 143 | String subConnectionJson( meshConnectionType *exclude ); 144 | meshConnectionType* findConnection( uint32_t chipId ); 145 | meshConnectionType* findConnection( espconn *conn ); 146 | void cleanDeadConnections( void ); 147 | void tcpConnect( void ); 148 | bool connectToBestAP( void ); 149 | uint16_t jsonSubConnCount( String& subConns ); 150 | meshConnectionType* closeConnection( meshConnectionType *conn ); 151 | 152 | // in easyMeshSTA.cpp 153 | void manageStation( void ); 154 | 155 | // in ? 156 | static void stationScanCb(void *arg, STATUS status); 157 | static void scanTimerCallback( void *arg ); 158 | void stationInit( void ); 159 | bool stationConnect( void ); 160 | void startStationScan( void ); 161 | 162 | void apInit( void ); 163 | void tcpServerInit(espconn &serverConn, esp_tcp &serverTcp, espconn_connect_callback connectCb, uint32 port); 164 | 165 | // callbacks 166 | // in easyMeshConnection.cpp 167 | static void wifiEventCb(System_Event_t *event); 168 | static void meshConnectedCb(void *arg); 169 | static void meshSentCb(void *arg); 170 | static void meshRecvCb(void *arg, char *data, unsigned short length); 171 | static void meshDisconCb(void *arg); 172 | static void meshReconCb(void *arg, sint8 err); 173 | 174 | 175 | // variables 176 | uint32_t _chipId; 177 | String _mySSID; 178 | String _meshPrefix; 179 | String _meshPassword; 180 | uint16_t _meshPort; 181 | 182 | os_timer_t _scanTimer; 183 | 184 | espconn _meshServerConn; 185 | esp_tcp _meshServerTcp; 186 | 187 | espconn _stationConn; 188 | esp_tcp _stationTcp; 189 | }; 190 | 191 | 192 | #endif // _EASY_MESH_H_ 193 | 194 | -------------------------------------------------------------------------------- /examples/demoToy/data/main.js: -------------------------------------------------------------------------------- 1 | var ws, socketStr, tempIndex, lastPing, timeOut; 2 | 3 | timeOut = 5000; //milliseconds 4 | 5 | socketStr = window.location.host; 6 | tempIndex = socketStr.indexOf(":"); 7 | socketStr = "ws://" + socketStr + ":2222/"; 8 | 9 | var control = new Object(); 10 | 11 | var outPackage = 0; 12 | 13 | function decimel2HexStr(dec) { 14 | 'use strict'; 15 | var ret = Math.round(dec * 255).toString(16); 16 | 17 | while (ret.length < 2) { ret = "0" + ret; } 18 | 19 | return ret; 20 | } 21 | 22 | function hslHue2Rgb(h) { // assumes full satruation and lightness 23 | 'use strict'; 24 | var r, g, b, x, ret; 25 | 26 | r = g = b = 0; 27 | h = h * 360; 28 | x = (h % 60) / 60; 29 | 30 | if (h <= 60) { r = 1; g = x; } 31 | if (60 < h && h <= 120) { r = 1 - x; g = 1; } 32 | if (120 < h && h <= 180) { g = 1; b = x; } 33 | if (180 < h && h <= 240) { g = 1 - x; b = 1; } 34 | if (240 < h && h <= 300) { r = x; b = 1; } 35 | if (300 < h && h < 360) { r = 1; b = 1 - x; } 36 | if (h === 360) { r = 1; } 37 | 38 | ret = "#" + decimel2HexStr(r) + decimel2HexStr(g) + decimel2HexStr(b); 39 | return ret; 40 | } 41 | 42 | 43 | function onOpenFunction(event) { 44 | 'use strict'; 45 | document.getElementById("hero").innerHTML = "WebSocket Open"; 46 | outPackage = "wsOpened"; 47 | } 48 | 49 | function onErrorFunction(event) { 50 | 'use strict'; 51 | var meshMsg = document.getElementById("meshMsg"); 52 | meshMsg.innerHTML = "onErrorFunction
" + meshMsg.innerHTML; 53 | } 54 | 55 | 56 | 57 | function sliderChange(sliderNum) { 58 | 'use strict'; 59 | var newValue, thumb, width; 60 | 61 | newValue = document.getElementById("slider" + sliderNum).value; 62 | document.getElementById("footer").innerHTML = newValue.toString(); 63 | 64 | control[sliderNum] = newValue; 65 | 66 | thumb = document.getElementById("sliderThumb" + sliderNum); 67 | width = document.getElementById("sliderContainer" + sliderNum).offsetWidth; 68 | width -= thumb.offsetWidth; 69 | 70 | thumb.style.left = width * newValue + 2; // 2px is for boarder width 71 | thumb.style.backgroundColor = hslHue2Rgb(newValue); 72 | 73 | newValue = JSON.stringify(control); 74 | document.getElementById("header").innerHTML = newValue.toString(); 75 | 76 | outPackage = newValue; 77 | } 78 | 79 | 80 | function populateSliders(inControl) { 81 | 'use strict'; 82 | var sliderDiv, width, i, inputDiv, top, track, container, thumb, newSlider, sliderContainer, sliderTrack, sliderThumb, sliderRange; 83 | 84 | document.getElementById("hero").innerHTML = ""; // remove connectButton 85 | 86 | sliderDiv = document.getElementById("sliderDiv"); 87 | sliderDiv.innerHTML = ""; 88 | 89 | top = Object.keys(inControl).length; 90 | for (i = 0; i < top; i += 1) { // dynamically place sliders 91 | inputDiv = "
"; 92 | inputDiv += ""; 93 | inputDiv += "
"; 94 | inputDiv += "
"; 97 | 98 | sliderDiv.innerHTML += inputDiv; 99 | 100 | 101 | /* sliderContainer = document.createElement("div"); 102 | sliderContainer.className = "sliderContainer"; 103 | sliderContainer.id = "sliderContainer" + i; 104 | 105 | sliderTrack = document.createElement("img"); 106 | sliderTrack.className = "sliderTrack"; 107 | sliderTrack.id = "sliderTrack" + i; 108 | sliderTrack.src = "hsl.png"; 109 | 110 | sliderThumb = document.createElement("div"); 111 | sliderThumb.className = "sliderThumb"; 112 | sliderThumb.id = "sliderThumb" + i; 113 | 114 | sliderRange = document.createElement("input"); 115 | sliderRange.className = "sliderRange"; 116 | sliderRange.id = "slider" + i; 117 | sliderRange.oninput = function () { sliderChange( i ); }; 118 | sliderRange.min = 0; 119 | sliderRange.max = 1; 120 | sliderRange.step = 0.001; 121 | 122 | sliderContainer.appendChild( sliderTrack ); 123 | sliderContainer.appendChild( sliderThumb ); 124 | sliderContainer.appendChild( sliderRange ); 125 | 126 | sliderDiv.appendChild( sliderContainer ); 127 | */ 128 | } 129 | 130 | track = document.getElementsByClassName("sliderTrack"); 131 | container = document.getElementsByClassName("sliderContainer"); 132 | thumb = document.getElementsByClassName("sliderThumb"); 133 | 134 | for (i = 0; i < track.length; i += 1) { 135 | container[i].style.height = thumb[i].offsetHeight; 136 | track[i].width = container[i].offsetWidth - thumb[i].offsetWidth; 137 | 138 | track[i].style.left = (thumb[i].offsetWidth / 2); 139 | track[i].style.top = (thumb[i].offsetHeight - track[i].offsetHeight) / 2; 140 | 141 | track[i].style.top = (thumb[i].offsetHeight - track[i].offsetHeight) / 2; 142 | 143 | sliderChange(i); 144 | } 145 | } 146 | 147 | function onMessageFunction(event) { 148 | 'use strict'; 149 | var inControl, meshMsg; 150 | // add debug data 151 | meshMsg = document.getElementById("meshMsg"); 152 | meshMsg.innerHTML = event.data + "
" + meshMsg.innerHTML; 153 | 154 | lastPing = Date.now(); 155 | 156 | if ( event.data === "ping") { 157 | return; 158 | } 159 | 160 | inControl = JSON.parse(event.data); 161 | if (inControl.hasOwnProperty("0")) { 162 | // control msg 163 | populateSliders(inControl); 164 | } 165 | } 166 | 167 | function sendData() { 168 | 'use strict'; 169 | if (outPackage !== 0) { 170 | ws.send(outPackage); 171 | outPackage = 0; 172 | } 173 | } 174 | 175 | function keepAlive() { 176 | 'use strict'; 177 | if ( typeof ws !== 'undefined' ) { 178 | if ( ws.readyState === 1 && document.getElementsByClassName("sliderDiv").length > 0 ) { //OPEN 179 | sliderChange(0); 180 | } 181 | } 182 | } 183 | 184 | function checkAlive() { 185 | if ( (lastPing + timeOut) < Date.now() ){ 186 | if ( typeof ws !== 'undefined' ) { 187 | if ( ws.readyState === 1 ) { //OPEN 188 | var meshMsg = document.getElementById("meshMsg"); 189 | meshMsg.innerHTML = "Timeout
" + meshMsg.innerHTML; 190 | ws.close(); 191 | addConnectToButton(); 192 | } 193 | } 194 | } 195 | } 196 | 197 | function startWebSocket() { 198 | //ws = new WebSocket(socketStr); 199 | //ws = new WebSocket('ws://192.168.107.1:2222/'); 200 | //ws = new WebSocket('ws://192.168.10.1:2222/'); 201 | ws = new WebSocket('ws://192.168.167.1:2222/'); 202 | 203 | ws.onmessage = function (event) {onMessageFunction(event); }; 204 | ws.onopen = function (event) {onOpenFunction(event); }; 205 | ws.onclose = function () {addConnectToButton(); }; 206 | ws.onerror = function (event) {onErrorFunction(event); }; 207 | 208 | document.getElementById("buttonText").innerHTML = '
'; 209 | 210 | lastPing = Date.now(); 211 | } 212 | 213 | function addConnectToButton() { 214 | var button, buttonText; 215 | 216 | document.getElementById("sliderDiv").innerHTML = ""; // remove any sliders that might be hanging out 217 | document.getElementById("hero").innerHTML = ""; // clean up. 218 | 219 | button = document.createElement("div"); 220 | button.className = "connectButton"; 221 | button.id = "connectButton"; 222 | button.onclick = function () { startWebSocket() }; 223 | 224 | buttonText = document.createElement("div"); 225 | buttonText.className = "buttonText"; 226 | buttonText.id = "buttonText"; 227 | buttonText.innerHTML = "Connect
to
Mesh"; 228 | 229 | button.appendChild( buttonText ); 230 | document.getElementById("hero").appendChild( button ); 231 | } 232 | 233 | setInterval(sendData, 200); 234 | setInterval(keepAlive, 5000); 235 | setInterval(checkAlive, 600); 236 | 237 | //setInterval( addConnectToButton, 100 ) 238 | 239 | window.onload = addConnectToButton; 240 | 241 | -------------------------------------------------------------------------------- /src/easyMeshSTA.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // easyMeshSTA.cpp 3 | // 4 | // 5 | // Created by Bill Gray on 7/26/16. 6 | // 7 | // 8 | 9 | #include 10 | #include 11 | 12 | extern "C" { 13 | #include "user_interface.h" 14 | #include "espconn.h" 15 | } 16 | 17 | #include "easyMesh.h" 18 | 19 | 20 | 21 | extern easyMesh* staticThis; 22 | 23 | // Station functions 24 | //*********************************************************************** 25 | void ICACHE_FLASH_ATTR easyMesh::stationInit( void ) { 26 | debugMsg( STARTUP, "stationInit():\n"); 27 | startStationScan(); 28 | return; 29 | } 30 | 31 | //*********************************************************************** 32 | void ICACHE_FLASH_ATTR easyMesh::manageStation( void ) { 33 | debugMsg( GENERAL, "manageStation():\n"); 34 | 35 | static uint8_t previousStatus; 36 | uint8_t stationStatus = wifi_station_get_connect_status(); 37 | 38 | if( stationStatus != previousStatus ) { 39 | switch( stationStatus ) { 40 | case STATION_IDLE: 41 | debugMsg( MESH_STATUS, "stationStatus Changed to STATION_IDLE\n"); 42 | break; 43 | case STATION_CONNECTING: 44 | debugMsg( MESH_STATUS, "stationStatus Changed to STATION_CONNECTING\n"); 45 | break; 46 | 47 | case STATION_WRONG_PASSWORD: 48 | debugMsg( MESH_STATUS, "stationStatus Changed to STATION_WRONG_PASSWORD\n"); 49 | break; 50 | 51 | case STATION_NO_AP_FOUND: 52 | debugMsg( MESH_STATUS, "stationStatus Changed to STATION_NO_AP_FOUND\n"); 53 | break; 54 | 55 | case STATION_CONNECT_FAIL: 56 | debugMsg( MESH_STATUS, "stationStatus Changed to STATION_CONNECT_FAIL\n"); 57 | break; 58 | 59 | case STATION_GOT_IP: 60 | debugMsg( MESH_STATUS, "stationStatus Changed to STATION_GOT_IP\n"); 61 | break; 62 | 63 | } 64 | previousStatus = stationStatus; 65 | } 66 | } 67 | 68 | //*********************************************************************** 69 | void ICACHE_FLASH_ATTR easyMesh::startStationScan( void ) { 70 | debugMsg( GENERAL, "startStationScan():\n"); 71 | 72 | if ( _scanStatus != IDLE ) { 73 | return; 74 | } 75 | 76 | if ( !wifi_station_scan(NULL, stationScanCb) ) { 77 | debugMsg( ERROR, "wifi_station_scan() failed!?\n"); 78 | return; 79 | } 80 | _scanStatus = SCANNING; 81 | debugMsg( CONNECTION, "-->scan started @ %d<--\n", system_get_time()); 82 | return; 83 | } 84 | 85 | //*********************************************************************** 86 | void ICACHE_FLASH_ATTR easyMesh::scanTimerCallback( void *arg ) { 87 | staticThis->startStationScan(); 88 | 89 | // this function can be totally elimiated! 90 | } 91 | 92 | //*********************************************************************** 93 | void ICACHE_FLASH_ATTR easyMesh::stationScanCb(void *arg, STATUS status) { 94 | char ssid[32]; 95 | bss_info *bssInfo = (bss_info *)arg; 96 | staticThis->debugMsg( CONNECTION, "stationScanCb():-- > scan finished @ % d < --\n", system_get_time()); 97 | staticThis->_scanStatus = IDLE; 98 | 99 | staticThis->_meshAPs.clear(); 100 | while (bssInfo != NULL) { 101 | staticThis->debugMsg( CONNECTION, "\tfound : % s, % ddBm", (char*)bssInfo->ssid, (int16_t) bssInfo->rssi ); 102 | if ( strncmp( (char*)bssInfo->ssid, staticThis->_meshPrefix.c_str(), staticThis->_meshPrefix.length() ) == 0 ) { 103 | staticThis->debugMsg( CONNECTION, " MESH_PRE< ---"); 104 | staticThis->_meshAPs.push_back( *bssInfo ); 105 | } 106 | staticThis->debugMsg( CONNECTION, "\n"); 107 | bssInfo = STAILQ_NEXT(bssInfo, next); 108 | } 109 | staticThis->debugMsg( CONNECTION, "\tFound % d nodes with _meshPrefix = \"%s\"\n", staticThis->_meshAPs.size(), staticThis->_meshPrefix.c_str() ); 110 | 111 | staticThis->connectToBestAP(); 112 | } 113 | 114 | //*********************************************************************** 115 | bool ICACHE_FLASH_ATTR easyMesh::connectToBestAP( void ) { 116 | debugMsg( CONNECTION, "connectToBestAP():"); 117 | 118 | // drop any _meshAP's we are already connected to 119 | SimpleList::iterator ap = _meshAPs.begin(); 120 | while( ap != _meshAPs.end() ) { 121 | String apChipId = (char*)ap->ssid + _meshPrefix.length(); 122 | // debugMsg( GENERAL, "connectToBestAP: sort - ssid=%s, apChipId=%s", ap->ssid, apChipId.c_str()); 123 | 124 | if ( findConnection( apChipId.toInt() ) != NULL ) { 125 | ap = _meshAPs.erase( ap ); 126 | // debugMsg( GENERAL, "<--already connected\n"); 127 | } 128 | else { 129 | ap++; 130 | // debugMsg( GENERAL, "\n"); 131 | } 132 | } 133 | 134 | uint8 statusCode = wifi_station_get_connect_status(); 135 | if ( statusCode != STATION_IDLE ) { 136 | debugMsg( CONNECTION, "connectToBestAP(): station not idle. code=%d\n", statusCode); 137 | return false; 138 | } 139 | 140 | if ( staticThis->_meshAPs.empty() ) { // no meshNodes left in most recent scan 141 | // debugMsg( GENERAL, "connectToBestAP(): no nodes left in list\n"); 142 | // wait 5 seconds and rescan; 143 | debugMsg( CONNECTION, "connectToBestAP(): no nodes left in list, rescanning\n"); 144 | os_timer_setfn( &_scanTimer, scanTimerCallback, NULL ); 145 | os_timer_arm( &_scanTimer, SCAN_INTERVAL, 0 ); 146 | return false; 147 | } 148 | 149 | // if we are here, then we have a list of at least 1 meshAPs. 150 | // find strongest signal of remaining meshAPs... that is not already connected to our AP. 151 | _nodeStatus = FOUND_MESH; 152 | SimpleList::iterator bestAP = staticThis->_meshAPs.begin(); 153 | SimpleList::iterator i = staticThis->_meshAPs.begin(); 154 | while ( i != staticThis->_meshAPs.end() ) { 155 | if ( i->rssi > bestAP->rssi ) { 156 | bestAP = i; 157 | } 158 | ++i; 159 | } 160 | 161 | // connect to bestAP 162 | debugMsg( CONNECTION, "connectToBestAP(): Best AP is %s<---\n", (char*)bestAP->ssid ); 163 | struct station_config stationConf; 164 | stationConf.bssid_set = 0; 165 | memcpy(&stationConf.ssid, bestAP->ssid, 32); 166 | memcpy(&stationConf.password, _meshPassword.c_str(), 64); 167 | wifi_station_set_config(&stationConf); 168 | wifi_station_connect(); 169 | 170 | _meshAPs.erase( bestAP ); // drop bestAP from mesh list, so if doesn't work out, we can try the next one 171 | return true; 172 | } 173 | 174 | //*********************************************************************** 175 | void ICACHE_FLASH_ATTR easyMesh::tcpConnect( void ) { 176 | debugMsg( GENERAL, "tcpConnect():\n"); 177 | 178 | struct ip_info ipconfig; 179 | wifi_get_ip_info(STATION_IF, &ipconfig); 180 | 181 | if ( wifi_station_get_connect_status() == STATION_GOT_IP && ipconfig.ip.addr != 0 ) { 182 | // we have successfully connected to wifi as a station. 183 | debugMsg( CONNECTION, "tcpConnect(): Got local IP=%d.%d.%d.%d\n", IP2STR(&ipconfig.ip) ); 184 | debugMsg( CONNECTION, "tcpConnect(): Dest IP=%d.%d.%d.%d\n", IP2STR( &ipconfig.gw ) ); 185 | 186 | // establish tcp connection 187 | _stationConn.type = ESPCONN_TCP; 188 | _stationConn.state = ESPCONN_NONE; 189 | _stationConn.proto.tcp = &_stationTcp; 190 | _stationConn.proto.tcp->local_port = espconn_port(); 191 | _stationConn.proto.tcp->remote_port = _meshPort; 192 | os_memcpy(_stationConn.proto.tcp->local_ip, &ipconfig.ip, 4); 193 | os_memcpy(_stationConn.proto.tcp->remote_ip, &ipconfig.gw, 4); 194 | espconn_set_opt( &_stationConn, ESPCONN_NODELAY ); // low latency, but soaks up bandwidth 195 | 196 | debugMsg( CONNECTION, "tcpConnect(): connecting type=%d, state=%d, local_ip=%d.%d.%d.%d, local_port=%d, remote_ip=%d.%d.%d.%d remote_port=%d\n", 197 | _stationConn.type, 198 | _stationConn.state, 199 | IP2STR(_stationConn.proto.tcp->local_ip), 200 | _stationConn.proto.tcp->local_port, 201 | IP2STR(_stationConn.proto.tcp->remote_ip), 202 | _stationConn.proto.tcp->remote_port ); 203 | 204 | espconn_regist_connectcb(&_stationConn, meshConnectedCb); 205 | espconn_regist_recvcb(&_stationConn, meshRecvCb); 206 | espconn_regist_sentcb(&_stationConn, meshSentCb); 207 | espconn_regist_reconcb(&_stationConn, meshReconCb); 208 | espconn_regist_disconcb(&_stationConn, meshDisconCb); 209 | 210 | sint8 errCode = espconn_connect(&_stationConn); 211 | if ( errCode != 0 ) { 212 | debugMsg( ERROR, "tcpConnect(): err espconn_connect() falied=%d\n", errCode ); 213 | } 214 | } 215 | else { 216 | debugMsg( ERROR, "tcpConnect(): err Something un expected in tcpConnect()\n"); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/easyMeshSync.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "easyMesh.h" 6 | #include "easyMeshSync.h" 7 | 8 | extern easyMesh* staticThis; 9 | uint32_t timeAdjuster = 0; 10 | 11 | // timeSync Functions 12 | //*********************************************************************** 13 | uint32_t ICACHE_FLASH_ATTR easyMesh::getNodeTime( void ) { 14 | uint32_t ret = system_get_time() + timeAdjuster; 15 | 16 | debugMsg( GENERAL, "getNodeTime(): time=%d\n", ret); 17 | 18 | return ret; 19 | } 20 | 21 | //*********************************************************************** 22 | String ICACHE_FLASH_ATTR timeSync::buildTimeStamp( void ) { 23 | staticThis->debugMsg( SYNC, "buildTimeStamp(): num=%d\n", num); 24 | 25 | if ( num > TIME_SYNC_CYCLES ) 26 | staticThis->debugMsg( ERROR, "buildTimeStamp(): timeSync not started properly\n"); 27 | 28 | StaticJsonBuffer<75> jsonBuffer; 29 | JsonObject& timeStampObj = jsonBuffer.createObject(); 30 | times[num] = staticThis->getNodeTime(); 31 | timeStampObj["time"] = times[num]; 32 | timeStampObj["num"] = num; 33 | bool remoteAdopt = !adopt; 34 | timeStampObj["adopt"] = remoteAdopt; 35 | 36 | String timeStampStr; 37 | timeStampObj.printTo( timeStampStr ); 38 | 39 | staticThis->debugMsg( SYNC, "buildTimeStamp(): timeStamp=%s\n", timeStampStr.c_str() ); 40 | return timeStampStr; 41 | } 42 | 43 | //*********************************************************************** 44 | bool ICACHE_FLASH_ATTR timeSync::processTimeStamp( String &str ) { 45 | staticThis->debugMsg( SYNC, "processTimeStamp(): str=%s\n", str.c_str()); 46 | 47 | DynamicJsonBuffer jsonBuffer(50 ); 48 | JsonObject& timeStampObj = jsonBuffer.parseObject(str); 49 | 50 | if ( !timeStampObj.success() ) { 51 | staticThis->debugMsg( ERROR, "processTimeStamp(): out of memory1?\n" ); 52 | return false; 53 | } 54 | 55 | num = timeStampObj.get("num"); 56 | 57 | times[num] = timeStampObj.get("time"); 58 | adopt = timeStampObj.get("adopt"); 59 | 60 | num++; 61 | 62 | if ( num < TIME_SYNC_CYCLES ) { 63 | str = buildTimeStamp(); 64 | return true; 65 | } 66 | else { 67 | return false; 68 | } 69 | } 70 | 71 | //*********************************************************************** 72 | void ICACHE_FLASH_ATTR timeSync::calcAdjustment ( bool odd ) { 73 | staticThis->debugMsg( SYNC, "calcAdjustment(): odd=%u\n", odd); 74 | 75 | uint32_t bestInterval = 0xFFFFFFFF; 76 | uint8_t bestIndex; 77 | uint32_t temp; 78 | 79 | for (int i = 0; i < TIME_SYNC_CYCLES; i++) { 80 | // debugMsg( GENERAL, "times[%d]=%u\n", i, times[i]); 81 | 82 | if ( i % 2 == odd ) { 83 | temp = times[i + 2] - times[i]; 84 | 85 | if ( i < TIME_SYNC_CYCLES - 2 ){ 86 | // debugMsg( GENERAL, "\tinterval=%u\n", temp); 87 | 88 | if ( temp < bestInterval ) { 89 | bestInterval = temp; 90 | bestIndex = i; 91 | } 92 | } 93 | } 94 | } 95 | staticThis->debugMsg( SYNC, "best interval=%u, best index=%u\n", bestInterval, bestIndex); 96 | 97 | // find number that turns local time into remote time 98 | uint32_t adopterTime = times[ bestIndex ] + (bestInterval / 2); 99 | uint32_t adjustment = times[ bestIndex + 1 ] - adopterTime; 100 | 101 | staticThis->debugMsg( SYNC, "new calc time=%u, adoptedTime=%u\n", adopterTime + adjustment, times[ bestIndex + 1 ]); 102 | 103 | timeAdjuster += adjustment; 104 | } 105 | 106 | 107 | // easyMesh Syncing functions 108 | //*********************************************************************** 109 | void ICACHE_FLASH_ATTR easyMesh::startNodeSync( meshConnectionType *conn ) { 110 | debugMsg( SYNC, "startNodeSync(): with %u\n", conn->chipId); 111 | 112 | String subs = subConnectionJson( conn ); 113 | sendMessage( conn, conn->chipId, NODE_SYNC_REQUEST, subs ); 114 | conn->nodeSyncRequest = getNodeTime(); 115 | conn->nodeSyncStatus = IN_PROGRESS; 116 | } 117 | 118 | //*********************************************************************** 119 | void ICACHE_FLASH_ATTR easyMesh::handleNodeSync( meshConnectionType *conn, JsonObject& root ) { 120 | debugMsg( SYNC, "handleNodeSync(): with %u\n", conn->chipId); 121 | 122 | meshPackageType type = (meshPackageType)(int)root["type"]; 123 | uint32_t remoteChipId = (uint32_t)root["from"]; 124 | uint32_t destId = (uint32_t)root["dest"]; 125 | bool reSyncAllSubConnections = false; 126 | 127 | if( (destId == 0) && (findConnection( remoteChipId ) != NULL) ) { 128 | // this is the first NODE_SYNC_REQUEST from a station 129 | // is we are already connected drop this connection 130 | debugMsg( SYNC, "handleNodeSync(): Already connected to node %d. Dropping\n", conn->chipId); 131 | closeConnection( conn ); 132 | return; 133 | } 134 | 135 | if ( conn->chipId != remoteChipId ) { 136 | debugMsg( SYNC, "handleNodeSync(): conn->chipId updated from %d to %d\n", conn->chipId, remoteChipId ); 137 | conn->chipId = remoteChipId; 138 | 139 | } 140 | 141 | // check to see if subs have changed. 142 | String inComingSubs = root["subs"]; 143 | if ( !conn->subConnections.equals( inComingSubs ) ) { // change in the network 144 | reSyncAllSubConnections = true; 145 | conn->subConnections = inComingSubs; 146 | } 147 | 148 | switch ( type ) { 149 | case NODE_SYNC_REQUEST: 150 | { 151 | debugMsg( SYNC, "handleNodeSync(): valid NODE_SYNC_REQUEST %d sending NODE_SYNC_REPLY\n", conn->chipId ); 152 | String myOtherSubConnections = subConnectionJson( conn ); 153 | sendMessage( conn, _chipId, NODE_SYNC_REPLY, myOtherSubConnections ); 154 | break; 155 | } 156 | case NODE_SYNC_REPLY: 157 | debugMsg( SYNC, "handleNodeSync(): valid NODE_SYNC_REPLY from %d\n", conn->chipId ); 158 | conn->nodeSyncRequest = 0; //reset nodeSyncRequest Timer ???? 159 | if ( conn->lastTimeSync == 0 ) 160 | startTimeSync( conn ); 161 | break; 162 | default: 163 | debugMsg( ERROR, "handleNodeSync(): weird type? %d\n", type ); 164 | } 165 | 166 | if ( reSyncAllSubConnections == true ) { 167 | SimpleList::iterator connection = _connections.begin(); 168 | while ( connection != _connections.end() ) { 169 | connection->nodeSyncStatus = NEEDED; 170 | connection++; 171 | } 172 | } 173 | 174 | conn->nodeSyncStatus = COMPLETE; // mark this connection nodeSync'd 175 | } 176 | 177 | //*********************************************************************** 178 | void ICACHE_FLASH_ATTR easyMesh::startTimeSync( meshConnectionType *conn ) { 179 | debugMsg( SYNC, "startTimeSync(): with %d\n", conn->chipId ); 180 | 181 | if ( conn->time.num > TIME_SYNC_CYCLES ) { 182 | debugMsg( ERROR, "startTimeSync(): Error timeSync.num not reset conn->time.num=%d\n", conn->time.num ); 183 | } 184 | 185 | conn->time.num = 0; 186 | 187 | conn->time.adopt = adoptionCalc( conn ); // do I adopt the estblished time? 188 | // debugMsg( GENERAL, "startTimeSync(): remoteSubCount=%d adopt=%d\n", remoteSubCount, conn->time.adopt); 189 | 190 | String timeStamp = conn->time.buildTimeStamp(); 191 | staticThis->sendMessage( conn, _chipId, TIME_SYNC, timeStamp ); 192 | 193 | conn->timeSyncStatus = IN_PROGRESS; 194 | } 195 | 196 | //*********************************************************************** 197 | bool ICACHE_FLASH_ATTR easyMesh::adoptionCalc( meshConnectionType *conn ) { 198 | // make the adoption calulation. Figure out how many nodes I am connected to exclusive of this connection. 199 | 200 | uint16_t mySubCount = connectionCount( conn ); //exclude this connection. 201 | uint16_t remoteSubCount = jsonSubConnCount( conn->subConnections ); 202 | 203 | bool ret = ( mySubCount > remoteSubCount ) ? false : true; 204 | 205 | debugMsg( GENERAL, "adoptionCalc(): mySubCount=%d remoteSubCount=%d ret = %d\n", mySubCount, remoteSubCount, ret); 206 | 207 | return ret; 208 | } 209 | 210 | //*********************************************************************** 211 | void ICACHE_FLASH_ATTR easyMesh::handleTimeSync( meshConnectionType *conn, JsonObject& root ) { 212 | 213 | String timeStamp = root["msg"]; 214 | debugMsg( SYNC, "handleTimeSync(): with %d in timestamp=%s\n", conn->chipId, timeStamp.c_str()); 215 | 216 | conn->time.processTimeStamp( timeStamp ); //varifies timeStamp and updates it with a new one. 217 | 218 | debugMsg( SYNC, "handleTimeSync(): with %d out timestamp=%s\n", conn->chipId, timeStamp.c_str()); 219 | 220 | 221 | if ( conn->time.num < TIME_SYNC_CYCLES ) { 222 | staticThis->sendMessage( conn, _chipId, TIME_SYNC, timeStamp ); 223 | } 224 | 225 | uint8_t odd = conn->time.num % 2; 226 | 227 | if ( (conn->time.num + odd) >= TIME_SYNC_CYCLES ) { // timeSync completed 228 | if ( conn->time.adopt ) { 229 | conn->time.calcAdjustment( odd ); 230 | 231 | // flag all connections for re-timeSync 232 | SimpleList::iterator connection = _connections.begin(); 233 | while ( connection != _connections.end() ) { 234 | if ( connection != conn ) { // exclude this connection 235 | connection->timeSyncStatus = NEEDED; 236 | } 237 | connection++; 238 | } 239 | } 240 | conn->lastTimeSync = getNodeTime(); 241 | conn->timeSyncStatus = COMPLETE; 242 | } 243 | } 244 | 245 | 246 | 247 | 248 | -------------------------------------------------------------------------------- /src/eashMeshConnection.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // eashMeshConnection.cpp 3 | // 4 | // 5 | // Created by Bill Gray on 7/26/16. 6 | // 7 | // 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | extern "C" { 14 | #include "user_interface.h" 15 | #include "espconn.h" 16 | } 17 | 18 | #include "easyMesh.h" 19 | 20 | static void (*receivedCallback)( uint32_t from, String &msg); 21 | static void (*newConnectionCallback)( bool adopt ); 22 | 23 | extern easyMesh* staticThis; 24 | 25 | // connection managment functions 26 | //*********************************************************************** 27 | void ICACHE_FLASH_ATTR easyMesh::setReceiveCallback( void(*onReceive)(uint32_t from, String &msg) ) { 28 | debugMsg( GENERAL, "setReceiveCallback():\n"); 29 | receivedCallback = onReceive; 30 | } 31 | 32 | //*********************************************************************** 33 | void ICACHE_FLASH_ATTR easyMesh::setNewConnectionCallback( void(*onNewConnection)(bool adopt) ) { 34 | debugMsg( GENERAL, "setNewConnectionCallback():\n"); 35 | newConnectionCallback = onNewConnection; 36 | } 37 | 38 | //*********************************************************************** 39 | meshConnectionType* ICACHE_FLASH_ATTR easyMesh::closeConnection( meshConnectionType *conn ) { 40 | // It seems that more should be done here... perhas send off a packette to 41 | // make an attempt to tell the other node that we are closing this conneciton? 42 | debugMsg( CONNECTION, "closeConnection(): conn-chipId=%d\n", conn->chipId ); 43 | espconn_disconnect( conn->esp_conn ); 44 | return _connections.erase( conn ); 45 | } 46 | 47 | //*********************************************************************** 48 | void ICACHE_FLASH_ATTR easyMesh::manageConnections( void ) { 49 | debugMsg( GENERAL, "manageConnections():\n"); 50 | SimpleList::iterator connection = _connections.begin(); 51 | while ( connection != _connections.end() ) { 52 | if ( connection->lastRecieved + NODE_TIMEOUT < getNodeTime() ) { 53 | debugMsg( CONNECTION, "manageConnections(): dropping %d NODE_TIMEOUT last=%u node=%u\n", connection->chipId, connection->lastRecieved, getNodeTime() ); 54 | 55 | connection = closeConnection( connection ); 56 | continue; 57 | } 58 | 59 | if( connection->esp_conn->state == ESPCONN_CLOSE ) { 60 | debugMsg( CONNECTION, "manageConnections(): dropping %d ESPCONN_CLOSE\n",connection->chipId); 61 | connection = closeConnection( connection ); 62 | continue; 63 | } 64 | 65 | switch ( connection->nodeSyncStatus ) { 66 | case NEEDED: // start a nodeSync 67 | debugMsg( SYNC, "manageConnections(): start nodeSync with %d\n", connection->chipId); 68 | startNodeSync( connection ); 69 | connection->nodeSyncStatus = IN_PROGRESS; 70 | 71 | case IN_PROGRESS: 72 | connection++; 73 | continue; 74 | } 75 | 76 | switch ( connection->timeSyncStatus ) { 77 | case NEEDED: 78 | debugMsg( SYNC, "manageConnections(): starting timeSync with %d\n", connection->chipId); 79 | startTimeSync( connection ); 80 | connection->timeSyncStatus = IN_PROGRESS; 81 | 82 | case IN_PROGRESS: 83 | connection++; 84 | continue; 85 | } 86 | 87 | if ( connection->newConnection == true ) { // we should only get here once first nodeSync and timeSync are complete 88 | newConnectionCallback( adoptionCalc( connection ) ); 89 | connection->newConnection = false; 90 | 91 | connection++; 92 | continue; 93 | } 94 | 95 | // check to see if we've recieved something lately. Else, flag for new sync. 96 | // Stagger AP and STA so that they don't try to start a sync at the same time. 97 | uint32_t nodeTime = getNodeTime(); 98 | if ( connection->nodeSyncRequest == 0 ) { // nodeSync not in progress 99 | if ( (connection->esp_conn->proto.tcp->local_port == _meshPort // we are AP 100 | && 101 | connection->lastRecieved + ( NODE_TIMEOUT / 2 ) < nodeTime ) 102 | || 103 | (connection->esp_conn->proto.tcp->local_port != _meshPort // we are the STA 104 | && 105 | connection->lastRecieved + ( NODE_TIMEOUT * 3 / 4 ) < nodeTime ) 106 | ) { 107 | connection->nodeSyncStatus = NEEDED; 108 | } 109 | } 110 | connection++; 111 | } 112 | } 113 | 114 | //*********************************************************************** 115 | meshConnectionType* ICACHE_FLASH_ATTR easyMesh::findConnection( uint32_t chipId ) { 116 | debugMsg( GENERAL, "In findConnection(chipId)\n"); 117 | 118 | SimpleList::iterator connection = _connections.begin(); 119 | while ( connection != _connections.end() ) { 120 | 121 | if ( connection->chipId == chipId ) { // check direct connections 122 | debugMsg( GENERAL, "findConnection(chipId): Found Direct Connection\n"); 123 | return connection; 124 | } 125 | 126 | String chipIdStr(chipId); 127 | if ( connection->subConnections.indexOf(chipIdStr) != -1 ) { // check sub-connections 128 | debugMsg( GENERAL, "findConnection(chipId): Found Sub Connection\n"); 129 | return connection; 130 | } 131 | 132 | connection++; 133 | } 134 | debugMsg( CONNECTION, "findConnection(%d): did not find connection\n", chipId ); 135 | return NULL; 136 | } 137 | 138 | //*********************************************************************** 139 | meshConnectionType* ICACHE_FLASH_ATTR easyMesh::findConnection( espconn *conn ) { 140 | debugMsg( GENERAL, "In findConnection(esp_conn) conn=0x%x\n", conn ); 141 | 142 | int i=0; 143 | 144 | SimpleList::iterator connection = _connections.begin(); 145 | while ( connection != _connections.end() ) { 146 | if ( connection->esp_conn == conn ) { 147 | return connection; 148 | } 149 | connection++; 150 | } 151 | 152 | debugMsg( CONNECTION, "findConnection(espconn): Did not Find\n"); 153 | return NULL; 154 | } 155 | 156 | //*********************************************************************** 157 | String ICACHE_FLASH_ATTR easyMesh::subConnectionJson( meshConnectionType *exclude ) { 158 | debugMsg( GENERAL, "subConnectionJson(), exclude=%d\n", exclude->chipId ); 159 | 160 | DynamicJsonBuffer jsonBuffer( JSON_BUFSIZE ); 161 | JsonArray& subArray = jsonBuffer.createArray(); 162 | if ( !subArray.success() ) 163 | debugMsg( ERROR, "subConnectionJson(): ran out of memory 1"); 164 | 165 | SimpleList::iterator sub = _connections.begin(); 166 | while ( sub != _connections.end() ) { 167 | if ( sub != exclude && sub->chipId != 0 ) { //exclude connection that we are working with & anything too new. 168 | JsonObject& subObj = jsonBuffer.createObject(); 169 | if ( !subObj.success() ) 170 | debugMsg( ERROR, "subConnectionJson(): ran out of memory 2"); 171 | 172 | subObj["chipId"] = sub->chipId; 173 | 174 | if ( sub->subConnections.length() != 0 ) { 175 | //debugMsg( GENERAL, "subConnectionJson(): sub->subConnections=%s\n", sub->subConnections.c_str() ); 176 | 177 | JsonArray& subs = jsonBuffer.parseArray( sub->subConnections ); 178 | if ( !subs.success() ) 179 | debugMsg( ERROR, "subConnectionJson(): ran out of memory 3"); 180 | 181 | subObj["subs"] = subs; 182 | } 183 | 184 | if ( !subArray.add( subObj ) ) 185 | debugMsg( ERROR, "subConnectionJson(): ran out of memory 4"); 186 | } 187 | sub++; 188 | } 189 | 190 | String ret; 191 | subArray.printTo( ret ); 192 | debugMsg( GENERAL, "subConnectionJson(): ret=%s\n", ret.c_str()); 193 | return ret; 194 | } 195 | 196 | //*********************************************************************** 197 | uint16_t ICACHE_FLASH_ATTR easyMesh::connectionCount( meshConnectionType *exclude ) { 198 | uint16_t count = 0; 199 | 200 | SimpleList::iterator sub = _connections.begin(); 201 | while ( sub != _connections.end() ) { 202 | if ( sub != exclude ) { //exclude this connection in the calc. 203 | count += ( 1 + jsonSubConnCount( sub->subConnections ) ); 204 | } 205 | sub++; 206 | } 207 | 208 | debugMsg( GENERAL, "connectionCount(): count=%d\n", count); 209 | return count; 210 | } 211 | 212 | //*********************************************************************** 213 | uint16_t ICACHE_FLASH_ATTR easyMesh::jsonSubConnCount( String& subConns ) { 214 | debugMsg( GENERAL, "jsonSubConnCount(): subConns=%s\n", subConns.c_str() ); 215 | 216 | uint16_t count = 0; 217 | 218 | if ( subConns.length() < 3 ) 219 | return 0; 220 | 221 | DynamicJsonBuffer jsonBuffer( JSON_BUFSIZE ); 222 | JsonArray& subArray = jsonBuffer.parseArray( subConns ); 223 | 224 | if ( !subArray.success() ) { 225 | debugMsg( ERROR, "subConnCount(): out of memory1\n"); 226 | } 227 | 228 | String str; 229 | for ( uint8_t i = 0; i < subArray.size(); i++ ) { 230 | str = subArray.get(i); 231 | debugMsg( GENERAL, "jsonSubConnCount(): str=%s\n", str.c_str() ); 232 | JsonObject& obj = jsonBuffer.parseObject( str ); 233 | if ( !obj.success() ) { 234 | debugMsg( ERROR, "subConnCount(): out of memory2\n"); 235 | } 236 | 237 | str = obj.get("subs"); 238 | count += ( 1 + jsonSubConnCount( str ) ); 239 | } 240 | 241 | debugMsg( CONNECTION, "jsonSubConnCount(): leaving count=%d\n", count ); 242 | 243 | return count; 244 | } 245 | 246 | //*********************************************************************** 247 | void ICACHE_FLASH_ATTR easyMesh::meshConnectedCb(void *arg) { 248 | staticThis->debugMsg( CONNECTION, "meshConnectedCb(): new meshConnection !!!\n"); 249 | meshConnectionType newConn; 250 | newConn.esp_conn = (espconn *)arg; 251 | espconn_set_opt( newConn.esp_conn, ESPCONN_NODELAY ); // removes nagle, low latency, but soaks up bandwidth 252 | newConn.lastRecieved = staticThis->getNodeTime(); 253 | 254 | espconn_regist_recvcb(newConn.esp_conn, meshRecvCb); 255 | espconn_regist_sentcb(newConn.esp_conn, meshSentCb); 256 | espconn_regist_reconcb(newConn.esp_conn, meshReconCb); 257 | espconn_regist_disconcb(newConn.esp_conn, meshDisconCb); 258 | 259 | staticThis->_connections.push_back( newConn ); 260 | 261 | if( newConn.esp_conn->proto.tcp->local_port != staticThis->_meshPort ) { // we are the station, start nodeSync 262 | staticThis->debugMsg( CONNECTION, "meshConnectedCb(): we are STA, start nodeSync\n"); 263 | staticThis->startNodeSync( staticThis->_connections.end() - 1 ); 264 | newConn.timeSyncStatus = NEEDED; 265 | } 266 | else 267 | staticThis->debugMsg( CONNECTION, "meshConnectedCb(): we are AP\n"); 268 | 269 | staticThis->debugMsg( GENERAL, "meshConnectedCb(): leaving\n"); 270 | } 271 | 272 | //*********************************************************************** 273 | void ICACHE_FLASH_ATTR easyMesh::meshRecvCb(void *arg, char *data, unsigned short length) { 274 | meshConnectionType *receiveConn = staticThis->findConnection( (espconn *)arg ); 275 | 276 | staticThis->debugMsg( COMMUNICATION, "meshRecvCb(): data=%s fromId=%d\n", data, receiveConn->chipId ); 277 | 278 | if ( receiveConn == NULL ) { 279 | staticThis->debugMsg( ERROR, "meshRecvCb(): recieved from unknown connection 0x%x ->%s<-\n", arg, data); 280 | staticThis->debugMsg( ERROR, "dropping this msg... see if we recover?\n"); 281 | return; 282 | } 283 | 284 | 285 | 286 | DynamicJsonBuffer jsonBuffer( JSON_BUFSIZE ); 287 | JsonObject& root = jsonBuffer.parseObject( data ); 288 | if (!root.success()) { // Test if parsing succeeded. 289 | staticThis->debugMsg( ERROR, "meshRecvCb: parseObject() failed. data=%s<--\n", data); 290 | return; 291 | } 292 | 293 | staticThis->debugMsg( GENERAL, "Recvd from %d-->%s<--\n", receiveConn->chipId, data); 294 | 295 | String msg = root["msg"]; 296 | 297 | switch( (meshPackageType)(int)root["type"] ) { 298 | case NODE_SYNC_REQUEST: 299 | case NODE_SYNC_REPLY: 300 | staticThis->handleNodeSync( receiveConn, root ); 301 | break; 302 | 303 | case TIME_SYNC: 304 | staticThis->handleTimeSync( receiveConn, root ); 305 | break; 306 | 307 | case SINGLE: 308 | if ( (uint32_t)root["dest"] == staticThis->getChipId() ) { // msg for us! 309 | receivedCallback( (uint32_t)root["from"], msg); 310 | } else { // pass it along 311 | //staticThis->sendMessage( (uint32_t)root["dest"], (uint32_t)root["from"], SINGLE, msg ); //this is ineffiecnt 312 | String tempStr( data ); 313 | staticThis->sendPackage( staticThis->findConnection( (uint32_t)root["dest"] ), tempStr ); 314 | } 315 | break; 316 | 317 | case BROADCAST: 318 | staticThis->broadcastMessage( (uint32_t)root["from"], BROADCAST, msg, receiveConn); 319 | receivedCallback( (uint32_t)root["from"], msg); 320 | break; 321 | 322 | default: 323 | staticThis->debugMsg( ERROR, "meshRecvCb(): unexpected json, root[\"type\"]=%d", (int)root["type"]); 324 | return; 325 | } 326 | 327 | // record that we've gotten a valid package 328 | receiveConn->lastRecieved = staticThis->getNodeTime(); 329 | return; 330 | } 331 | 332 | //*********************************************************************** 333 | void ICACHE_FLASH_ATTR easyMesh::meshSentCb(void *arg) { 334 | staticThis->debugMsg( GENERAL, "meshSentCb():\n"); //data sent successfully 335 | espconn *conn = (espconn*)arg; 336 | meshConnectionType *meshConnection = staticThis->findConnection( conn ); 337 | 338 | if ( meshConnection == NULL ) { 339 | staticThis->debugMsg( ERROR, "meshSentCb(): err did not find meshConnection? Likely it was dropped for some reason\n"); 340 | return; 341 | } 342 | 343 | if ( !meshConnection->sendQueue.empty() ) { 344 | String package = *meshConnection->sendQueue.begin(); 345 | meshConnection->sendQueue.pop_front(); 346 | sint8 errCode = espconn_send( meshConnection->esp_conn, (uint8*)package.c_str(), package.length() ); 347 | //connection->sendReady = false; 348 | 349 | if ( errCode != 0 ) { 350 | staticThis->debugMsg( ERROR, "meshSentCb(): espconn_send Failed err=%d\n", errCode ); 351 | } 352 | } 353 | else { 354 | meshConnection->sendReady = true; 355 | } 356 | } 357 | //*********************************************************************** 358 | void ICACHE_FLASH_ATTR easyMesh::meshDisconCb(void *arg) { 359 | struct espconn *disConn = (espconn *)arg; 360 | 361 | staticThis->debugMsg( CONNECTION, "meshDisconCb(): "); 362 | 363 | //test to see if this connection was on the STATION interface by checking the local port 364 | if ( disConn->proto.tcp->local_port == staticThis->_meshPort ) { 365 | staticThis->debugMsg( CONNECTION, "AP connection. No new action needed. local_port=%d\n", disConn->proto.tcp->local_port); 366 | } 367 | else { 368 | staticThis->debugMsg( CONNECTION, "Station Connection! Find new node. local_port=%d\n", disConn->proto.tcp->local_port); 369 | // should start up automatically when station_status changes to IDLE 370 | wifi_station_disconnect(); 371 | } 372 | 373 | return; 374 | } 375 | 376 | //*********************************************************************** 377 | void ICACHE_FLASH_ATTR easyMesh::meshReconCb(void *arg, sint8 err) { 378 | staticThis->debugMsg( ERROR, "In meshReconCb(): err=%d\n", err ); 379 | } 380 | 381 | //*********************************************************************** 382 | void ICACHE_FLASH_ATTR easyMesh::wifiEventCb(System_Event_t *event) { 383 | switch (event->event) { 384 | case EVENT_STAMODE_CONNECTED: 385 | staticThis->debugMsg( CONNECTION, "wifiEventCb(): EVENT_STAMODE_CONNECTED ssid=%s\n", (char*)event->event_info.connected.ssid ); 386 | break; 387 | case EVENT_STAMODE_DISCONNECTED: 388 | staticThis->debugMsg( CONNECTION, "wifiEventCb(): EVENT_STAMODE_DISCONNECTED\n"); 389 | staticThis->connectToBestAP(); 390 | break; 391 | case EVENT_STAMODE_AUTHMODE_CHANGE: 392 | staticThis->debugMsg( CONNECTION, "wifiEventCb(): EVENT_STAMODE_AUTHMODE_CHANGE\n"); 393 | break; 394 | case EVENT_STAMODE_GOT_IP: 395 | staticThis->debugMsg( CONNECTION, "wifiEventCb(): EVENT_STAMODE_GOT_IP\n"); 396 | staticThis->tcpConnect(); 397 | break; 398 | 399 | case EVENT_SOFTAPMODE_STACONNECTED: 400 | staticThis->debugMsg( CONNECTION, "wifiEventCb(): EVENT_SOFTAPMODE_STACONNECTED\n"); 401 | break; 402 | 403 | case EVENT_SOFTAPMODE_STADISCONNECTED: 404 | staticThis->debugMsg( CONNECTION, "wifiEventCb(): EVENT_SOFTAPMODE_STADISCONNECTED\n"); 405 | break; 406 | case EVENT_STAMODE_DHCP_TIMEOUT: 407 | staticThis->debugMsg( CONNECTION, "wifiEventCb(): EVENT_STAMODE_DHCP_TIMEOUT\n"); 408 | break; 409 | case EVENT_SOFTAPMODE_PROBEREQRECVED: 410 | // debugMsg( GENERAL, "Event: EVENT_SOFTAPMODE_PROBEREQRECVED\n"); // dont need to know about every probe request 411 | break; 412 | default: 413 | staticThis->debugMsg( ERROR, "Unexpected WiFi event: %d\n", event->event); 414 | break; 415 | } 416 | } 417 | 418 | --------------------------------------------------------------------------------