diff --git a/README.md b/README.md index 1435e90..b95976e 100644 --- a/README.md +++ b/README.md @@ -30,39 +30,45 @@ vi include/secrets.h pio run ``` +Make a note of the gateway's MAC address: + +```bash +Initialized [82:7d:3a:79:14:79] +``` + **Note:** Bricks currently requires a WiFi network that uses channel 1. ### 3. Create some bricks -[WIP library here](/examples). -For example a button: +[WIP catalogue here](/examples). +For example a button and an LED: ```bash +export PLATFORMIO_BUILD_FLAGS="'-DBRICKS_GATEWAY_MAC=\"82:7d:3a:79:14:79\"'" + cd examples/button pio run -``` -and an LED: - -```bash cd examples/led pio run ``` -### 4. Scan for bricks +### 4. Communicate with bricks using MQTT + +Bricks announce themselves and their MAC addresses when they come online: ```mqtt -bricks/gateway/scan +bricks/in/ee:fa:bc:8e:89:1e/pong: Button +bricks/in/ee:fa:bc:8e:89:8f/pong: LED ``` -This will configure all active bricks to use the gateway, -each responding with a "pong" containing their MAC address and name: +Use the MAC address to send messages to the bricks: ```mqtt -bricks/in/ee:fa:bc:8e:89:1e/pong: Button +bricks/out/ee:fa:bc:8e:89:8f/setPattern: 1 ``` -### 5. Connect things in NodeRED +### 5. Connect bricks in NodeRED Here is a simple example that allows a button brick to control an LED brick: @@ -70,19 +76,14 @@ brick: -### Todo - -- [x] Button -- [ ] LED -- [ ] Try out [Wemos D1 hack](https://www.youtube.com/watch?v=rfPwOtoGO4E) - ## Software ### Todo - [ ] TravisCI - [ ] Turn "setRotation" into an action instead for LED Matrix - [ ] Move "ack" to top of stack, run actions in reverse, or split actions -- [ ] Use advertising instead of "scan" to claim bricks +- [ ] Node-RED list of bricks +- [ ] Verify tests after gateway hardcoding ### Nice to haves - [ ] Fix errors raised by `pio check` (mainly passing `Message` by value) @@ -91,10 +92,14 @@ brick: - [ ] More tests - [ ] Make aliexpress ble button send notifications - [ ] Idea: Move sender macAddr into message to reduce params to 1? -- [ ] Use same system for defining mqtt responses (instead of hardcoded "scan") - ``` - // Connect mqtt event stream - gEvents.init(); - gEvents.events[0] = new ScanEvent(); - gEvents.events[1] = new BroadcastEvent(); - ``` + +## Hardware + +### Todo +- [ ] Low profile headers +- [ ] Finish case +- [ ] Nicer stickers +- [ ] Button +- [ ] LED +- [ ] Try out [Wemos D1 hack](https://www.youtube.com/watch?v=rfPwOtoGO4E) + diff --git a/examples/README.md b/examples/README.md index c2a9e08..0639337 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,8 +1,7 @@ # Brick Examples - [Gateway](gateway) -- [Button (realtime)](button) -- [Button (sleeps)](button-sleep) +- [Button](button) - [LED](led) - [LED RGB](led-rgb) - [LED 8x8 Matrix](led-matrix) @@ -14,12 +13,13 @@ ### Inbox -| key | description | values | -|------------|-------------------------------------------|----------------------| -| * | Responds with `ack:` to all messages | | -| ping | Saves gateway MAC, responds with `pong` | | -| sleep | Sleep for `value` seconds | 0 (forever), 1-13612 | -| getBattery | Ask to send battery value | | +| key | description | values | +|------------|---------------------------------------------------------------------------|----------------------| +| * | Responds with `ack:` to all messages | | +| ping | Saves gateway MAC, responds with `pong` | | +| sleep | Sleep for `value` seconds | 0 (forever), 1-13612 | +| getBattery | Ask to send battery value | | +| setOta | Allows `pio run -t upload --upload-port 192.168.4.1` when connected to AP | | ### Outbox diff --git a/examples/ble/src/main.cpp b/examples/ble/src/main.cpp index 7cdee22..d389a56 100644 --- a/examples/ble/src/main.cpp +++ b/examples/ble/src/main.cpp @@ -8,7 +8,7 @@ using namespace Bricks; #include BLEScan *pBLEScan; -const int8_t MINIMUM_RSSI = -70; +const int8_t MINIMUM_RSSI = -80; class BLECallbacks: public BLEAdvertisedDeviceCallbacks { void onResult(BLEAdvertisedDevice* device) { diff --git a/examples/button-sleep/README.md b/examples/button-sleep/README.md deleted file mode 100644 index 272e8d6..0000000 --- a/examples/button-sleep/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Button - Sleep - -"Sleepable" button/trigger. -Should be put to sleep "permanently" and use RST wake-up as trigger. diff --git a/examples/button-sleep/platformio.ini b/examples/button-sleep/platformio.ini deleted file mode 100644 index 41ddb10..0000000 --- a/examples/button-sleep/platformio.ini +++ /dev/null @@ -1,9 +0,0 @@ -[platformio] -default_envs = d1_mini -extra_configs = - ../../shared.ini - -[env] -lib_extra_dirs = ../../ -lib_deps = - ${common.lib_deps} diff --git a/examples/button-sleep/src/main.cpp b/examples/button-sleep/src/main.cpp deleted file mode 100644 index b21f720..0000000 --- a/examples/button-sleep/src/main.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// Bricks -#include -#include -#include -using namespace Bricks; - -void setup() { - // Logging - Serial.begin(115200); - Log.begin(LOG_LEVEL_NOTICE, &Serial); - - // Configure ESPNow - gBrick.init(); - - // Configure inbox - gInbox.init("Button"); -} - -void loop() { - gInbox.loop(); -} diff --git a/examples/button/README.md b/examples/button/README.md index 8a859d1..a4e806d 100644 --- a/examples/button/README.md +++ b/examples/button/README.md @@ -1,7 +1,7 @@ # Button -"Always on" button. -Very responsive but will consume battery/should probably be plugged in. +If plugged in can use `pressed/release` messages. +Can also be put to sleep and use RST wake-up as trigger. ## Outbox diff --git a/examples/button/platformio.ini b/examples/button/platformio.ini index d573937..8c577ec 100644 --- a/examples/button/platformio.ini +++ b/examples/button/platformio.ini @@ -1,5 +1,5 @@ [platformio] -default_envs = d1_mini_pro +default_envs = d1_mini extra_configs = ../../shared.ini diff --git a/examples/button/src/main.cpp b/examples/button/src/main.cpp index 114b906..c927393 100644 --- a/examples/button/src/main.cpp +++ b/examples/button/src/main.cpp @@ -23,7 +23,7 @@ void setup() { gBrick.init(); // Configure inbox - gInbox.init("Button (Realtime)"); + gInbox.init("Button"); } void loop() { @@ -34,4 +34,6 @@ void loop() { if(button.onReleased()) { gOutbox.send("released"); } + + gInbox.loop(); } diff --git a/examples/gateway/platformio.ini b/examples/gateway/platformio.ini index 80b5df5..4859296 100644 --- a/examples/gateway/platformio.ini +++ b/examples/gateway/platformio.ini @@ -6,6 +6,7 @@ extra_configs = [env] build_flags= -include include/secrets.h + -DBRICKS_GATEWAY_MAC=\"00:00:00:00:00:00\" lib_extra_dirs = ../../ lib_deps = ${common.lib_deps} diff --git a/library.json b/library.json index 0592127..7e81816 100644 --- a/library.json +++ b/library.json @@ -8,7 +8,6 @@ }, "dependencies": { "ArduinoLog": "~1.0.3", - "EEPROM": ">0", "PubSubClient": "~2.8", "WifiEspNow": "~0.0.20190814" }, diff --git a/shared.ini b/shared.ini index 394c788..dcc1bbd 100644 --- a/shared.ini +++ b/shared.ini @@ -2,7 +2,6 @@ [common] lib_deps = ArduinoLog - EEPROM PubSubClient WifiEspNow diff --git a/src/Bricks.Brick.cpp b/src/Bricks.Brick.cpp index 467deaa..4a24b97 100644 --- a/src/Bricks.Brick.cpp +++ b/src/Bricks.Brick.cpp @@ -4,17 +4,30 @@ namespace Bricks { void Brick::init(const WiFiMode_t wifiMode) { // Configure Wi-Fi WiFi.mode(wifiMode); + WiFi.softAP("ESPNOW", NULL, BRICKS_WIFI_CHANNEL, true, 0); // hide AP // Connect ESPNOW if (WifiEspNow.begin()) { - String macStr = WiFi.softAPmacAddress(); - macStr.toLowerCase(); - Log.notice("ESPN: Initialized [%s]" CR, macStr.c_str()); + logMac(wifiMode); } else { Log.error("ESPN: Error initializing" CR); } } + void Brick::logMac(WiFiMode_t wifiMode) { + String macStr; + + if(wifiMode == WIFI_STA) { + macStr = WiFi.macAddress(); + } + else { + macStr = WiFi.softAPmacAddress(); + } + + macStr.toLowerCase(); + Log.notice("ESPN: Initialized [%s]" CR, macStr.c_str()); + } + Brick gBrick = Brick(); } diff --git a/src/Bricks.Brick.h b/src/Bricks.Brick.h index 226d209..e2d9e84 100644 --- a/src/Bricks.Brick.h +++ b/src/Bricks.Brick.h @@ -9,10 +9,14 @@ #endif #include +#include + namespace Bricks { class Brick { public: - void init(const WiFiMode_t wifiMode = WIFI_AP); + void init(const WiFiMode_t wifiMode = WIFI_AP); // AP is more stable that STA when communication with AT_STA + private: + void logMac(WiFiMode_t wifiMode); }; extern Brick gBrick; diff --git a/src/Bricks.Constants.h b/src/Bricks.Constants.h index 882ccef..5a3826c 100644 --- a/src/Bricks.Constants.h +++ b/src/Bricks.Constants.h @@ -2,12 +2,9 @@ #define BRICKS_CONSTANTS_H #define BRICKS_WIFI_CHANNEL 1 -#define BRICKS_NAME_PREFIX "Brick - " -#define BRICKS_PING_ACTION "ping" #define MAC_FORMAT "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx" #define MAC_ADDR_SIZE 6 #define MAC_STR_SIZE 18 -#define MAC_UNSET {0x00, 0x00, 0x00, 0x00, 0x00, 0x00} #define MICROSECONDS 1000000 #define KEY_SIZE 20 diff --git a/src/Bricks.Events.cpp b/src/Bricks.Events.cpp index 1c97c86..1df0a7b 100644 --- a/src/Bricks.Events.cpp +++ b/src/Bricks.Events.cpp @@ -46,15 +46,10 @@ namespace Bricks { char *value = (char *) bytes; Log.trace("MQTT: <- %s: %s" CR, topic, value); - if(strcmp(BRICKS_MESSAGES_SCAN, topic) == 0) { - scanForBricks(); - } - else { - uint8_t macAddr[MAC_ADDR_SIZE]; - char key[KEY_SIZE]; - parseTopic(topic, macAddr, key); - gOutbox.send(macAddr, key, value); - } + uint8_t macAddr[MAC_ADDR_SIZE]; + char key[KEY_SIZE]; + parseTopic(topic, macAddr, key); + gOutbox.send(macAddr, key, value); } void Events::parseTopic(const char *topic, uint8_t *macAddr, char *key) { @@ -83,7 +78,6 @@ namespace Bricks { if(mqtt.connect(BRICKS_MQTT_CLIENT, BRICKS_MQTT_USER, BRICKS_MQTT_PASSWORD)) { Log.notice("MQTT: Connected" CR); subscribe(BRICKS_MESSAGES_OUT "/#"); - subscribe(BRICKS_MESSAGES_SCAN); } else { Log.warning("MQTT: Failed [%s]. Retrying in 5 secs" CR, mqtt.state()); @@ -97,21 +91,5 @@ namespace Bricks { Log.trace("MQTT: Subscribed [%s]" CR, topic); } - void Events::scanForBricks() { - Log.notice("WIFI: Scanning for Bricks" CR); - const uint8_t scanResults = WiFi.scanNetworks(); - - for(int i = 0; i < scanResults; ++i) { - if(WiFi.SSID(i).indexOf(BRICKS_NAME_PREFIX) == 0) { - Log.trace("WIFI: Brick found on channel %d" CR, WiFi.channel(i)); - gOutbox.send(WiFi.BSSID(i), BRICKS_PING_ACTION); - } - } - - WiFi.scanDelete(); - Log.notice("WIFI: Scan complete" CR); - gEvents.publish(BRICKS_MESSAGES_SCAN "/done"); - } - Events gEvents = Events(); } diff --git a/src/Bricks.Events.h b/src/Bricks.Events.h index b796dc7..ad6189e 100644 --- a/src/Bricks.Events.h +++ b/src/Bricks.Events.h @@ -27,7 +27,6 @@ #define BRICKS_MESSAGES_IN BRICKS_MQTT_TOPIC_PREFIX "/in" #define BRICKS_MESSAGES_OUT BRICKS_MQTT_TOPIC_PREFIX "/out" -#define BRICKS_MESSAGES_SCAN BRICKS_MQTT_TOPIC_PREFIX "/gateway/scan" namespace Bricks { class Events { @@ -48,7 +47,6 @@ namespace Bricks { void connectWiFi(); void connectMQTT(); void subscribe(const char *topic); - static void scanForBricks(); }; extern Events gEvents; diff --git a/src/Bricks.Inbox.cpp b/src/Bricks.Inbox.cpp index 64f465c..28bfd4b 100644 --- a/src/Bricks.Inbox.cpp +++ b/src/Bricks.Inbox.cpp @@ -2,10 +2,9 @@ namespace Bricks { void Inbox::init(const char *name) { - actions[3] = new OtaAction(); - actions[4] = new ListAction(); - actions[5] = new PongAction(name); - actions[6] = new StoreGatewayAction(); + actions[4] = new OtaAction(); + actions[5] = new ListAction(); + actions[6] = new PongAction(name); actions[7] = new SleepAction(); actions[8] = new BatteryAction(); actions[9] = new AckAction(); diff --git a/src/Bricks.Inbox.h b/src/Bricks.Inbox.h index 2319ad2..97ae50a 100644 --- a/src/Bricks.Inbox.h +++ b/src/Bricks.Inbox.h @@ -16,7 +16,6 @@ #include #include #include -#include # define MAX_ACTIONS 10 diff --git a/src/Bricks.OtaAction.cpp b/src/Bricks.OtaAction.cpp index 2d8f8a2..483b26f 100644 --- a/src/Bricks.OtaAction.cpp +++ b/src/Bricks.OtaAction.cpp @@ -10,7 +10,7 @@ namespace Bricks { void OtaAction::callback(const uint8_t *macAddr, const Message message) { initOta(); - gOutbox.send(macAddr, "ota", WiFi.softAPIP().toString().c_str()); + startAP(); } void OtaAction::initOta() { @@ -42,4 +42,9 @@ namespace Bricks { }); ArduinoOTA.begin(); } + + void OtaAction::startAP() { + WiFi.mode(WIFI_AP); + WiFi.softAP("Brick - OTA", NULL, BRICKS_WIFI_CHANNEL, false, 1); + } } diff --git a/src/Bricks.OtaAction.h b/src/Bricks.OtaAction.h index e8d1bf6..36aa7f6 100644 --- a/src/Bricks.OtaAction.h +++ b/src/Bricks.OtaAction.h @@ -14,6 +14,7 @@ namespace Bricks { void callback(const uint8_t *macAddr, const Message message); private: void initOta(); + void startAP(); }; } #endif diff --git a/src/Bricks.Outbox.cpp b/src/Bricks.Outbox.cpp index 035eb9a..c853fda 100644 --- a/src/Bricks.Outbox.cpp +++ b/src/Bricks.Outbox.cpp @@ -1,6 +1,10 @@ #include "Bricks.Outbox.h" namespace Bricks { + Outbox::Outbox() { + setGatewayMac(); + } + void Outbox::send(const uint8_t *macAddr, const char* key, const char* value) { pair(macAddr); @@ -47,14 +51,9 @@ namespace Bricks { } } - void Outbox::setGatewayMac(const uint8_t *macAddr) { - for (int i = 0; i < MAC_ADDR_SIZE; ++i) { - gatewayMac[i] = macAddr[i]; - } - - char macStr[MAC_STR_SIZE]; - Bricks::Utils::macToStr(gatewayMac, macStr); - Log.notice("BRIC: Current gateway MAC [%s]" CR, macStr); + void Outbox::setGatewayMac() { + sscanf(BRICKS_GATEWAY_MAC, MAC_FORMAT, + &gatewayMac[0], &gatewayMac[1], &gatewayMac[2], &gatewayMac[3], &gatewayMac[4], &gatewayMac[5]); } Outbox gOutbox = Outbox(); diff --git a/src/Bricks.Outbox.h b/src/Bricks.Outbox.h index ac951ff..49bb55e 100644 --- a/src/Bricks.Outbox.h +++ b/src/Bricks.Outbox.h @@ -11,13 +11,13 @@ namespace Bricks { class Outbox { public: - Outbox() {} + Outbox(); void send(const uint8_t *macAddr, const char *key, const char *value = ""); void send(const char *key, const char *value = ""); - void setGatewayMac(const uint8_t *macAddr); private: void pair(const uint8_t *macAddr); - uint8_t gatewayMac[MAC_ADDR_SIZE] = MAC_UNSET; + void setGatewayMac(); + uint8_t gatewayMac[MAC_ADDR_SIZE]; }; extern Outbox gOutbox; diff --git a/src/Bricks.PongAction.cpp b/src/Bricks.PongAction.cpp index 498a902..0a8b84d 100644 --- a/src/Bricks.PongAction.cpp +++ b/src/Bricks.PongAction.cpp @@ -1,19 +1,12 @@ #include namespace Bricks { - PongAction::PongAction(const char *name) : Action(BRICKS_PING_ACTION) { + PongAction::PongAction(const char *name) : Action("ping") { this->name = name; - advertise(); gOutbox.send("pong", name); } void PongAction::callback(const uint8_t *macAddr, const Message message) { gOutbox.send(macAddr, "pong", name); } - - void PongAction::advertise() { - char apName[50]; // Need unique name? - sprintf(apName, BRICKS_NAME_PREFIX "%s", WiFi.softAPmacAddress().c_str()); - WiFi.softAP(apName, NULL, BRICKS_WIFI_CHANNEL); - } } diff --git a/src/Bricks.PongAction.h b/src/Bricks.PongAction.h index f5b1066..1aa3957 100644 --- a/src/Bricks.PongAction.h +++ b/src/Bricks.PongAction.h @@ -3,7 +3,6 @@ #include #include -#include #include namespace Bricks { @@ -12,7 +11,6 @@ namespace Bricks { PongAction(const char *name = "New Brick"); void callback(const uint8_t *macAddr, const Message message); private: - void advertise(); const char *name; }; } diff --git a/src/Bricks.StoreGatewayAction.cpp b/src/Bricks.StoreGatewayAction.cpp deleted file mode 100644 index 72c8290..0000000 --- a/src/Bricks.StoreGatewayAction.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include - -namespace Bricks { - StoreGatewayAction::StoreGatewayAction() : Action(BRICKS_PING_ACTION) { - EEPROM.begin(MAC_ADDR_SIZE); - readMac(); - } - - void StoreGatewayAction::callback(const uint8_t *macAddr, const Message message) { - writeMac(macAddr); - } - - void StoreGatewayAction::writeMac(const uint8_t *macAddr) { - Log.notice("EEPR: Storing gateway MAC" CR); - - for (int i = 0; i < MAC_ADDR_SIZE; ++i) { - EEPROM.write(i, macAddr[i]); - } - EEPROM.commit(); - - gOutbox.setGatewayMac(macAddr); - } - - void StoreGatewayAction::readMac() { - Log.notice("EEPR: Reading gateway MAC" CR); - - uint8_t macAddr[MAC_ADDR_SIZE]; - - for (int i = 0; i < MAC_ADDR_SIZE; ++i) { - macAddr[i] = EEPROM.read(i); - } - - gOutbox.setGatewayMac(macAddr); - } -} diff --git a/src/Bricks.StoreGatewayAction.h b/src/Bricks.StoreGatewayAction.h deleted file mode 100644 index 3872526..0000000 --- a/src/Bricks.StoreGatewayAction.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef BRICKS_STORE_GATEWAY_ACTION_H -#define BRICKS_STORE_GATEWAY_ACTION_H - -#include -#include - -#include -#include -#include -#include - -namespace Bricks { - class StoreGatewayAction : public Action { - public: - StoreGatewayAction(); - void callback(const uint8_t *macAddr, const Message message); - private: - void writeMac(const uint8_t *macAddr); - void readMac(); - }; -} -#endif diff --git a/test/inbox/test_inbox.cpp b/test/inbox/test_inbox.cpp index bdc1b1a..078a480 100644 --- a/test/inbox/test_inbox.cpp +++ b/test/inbox/test_inbox.cpp @@ -3,7 +3,6 @@ #include #include #include -#include using namespace Bricks; void setUp(void) { @@ -20,15 +19,6 @@ void test_pong() { gInbox.process(macAddr, message); } -void test_store_gateway() { - const uint8_t macAddr[] = {0x98, 0xF4, 0xAB, 0x6C, 0xE6, 0xB5}; - Message message; - strcpy(message.key, "ping"); - - gInbox.actions[0] = new StoreGatewayAction(); - gInbox.process(macAddr, message); -} - void test_list_actions() { gInbox.actions[0] = new PongAction(); @@ -40,7 +30,6 @@ void setup() { UNITY_BEGIN(); RUN_TEST(test_pong); - RUN_TEST(test_store_gateway); RUN_TEST(test_list_actions); UNITY_END(); }