diff --git a/base-firmware/Makefile-user.mk b/base-firmware/Makefile-user.mk index 73a4d8f..5711392 100644 --- a/base-firmware/Makefile-user.mk +++ b/base-firmware/Makefile-user.mk @@ -1,2 +1,3 @@ SPIFF_SIZE = 196600 DISABLE_SPIFFS = 0 +ENABLE_SSL = 1 diff --git a/base-firmware/app/application.cpp b/base-firmware/app/application.cpp index 291766e..05365ca 100644 --- a/base-firmware/app/application.cpp +++ b/base-firmware/app/application.cpp @@ -5,7 +5,7 @@ SpejsNode node("unconfigured-generic-device"); void init() { - node.init(); + node.init(true); node.loadJSON({ &OutputEndpoint::fromJSON, &DHTEndpoint::fromJSON, diff --git a/base-firmware/files/config.json b/base-firmware/files/config.json index df09c94..407d6f0 100644 --- a/base-firmware/files/config.json +++ b/base-firmware/files/config.json @@ -1,5 +1,5 @@ { - "name": "testdev", + "name": "unprovisioned", "extras": { "owner": "informatic", @@ -10,5 +10,11 @@ "out": {"type": "output", "gpio": 2}, "testInput": {"type": "input", "gpio": 3}, "testtemp": {"type": "dht"} - } + }, + + "broker": "mqtts://10.8.0.136:8883", + "brokerSHA1": "E7:FA:91:2B:E9:D6:7E:55:83:16:8D:09:68:BF:DD:4F:5D:F4:AC:DA", + "brokerSHA256": "29:10:5e:29:27:d6:2f:05:21:cc:8b:0e:f2:ca:e9:aa:1d:f4:1a:63:58:ed:4e:c8:92:b2:cb:9e:d7:71:a9:1d", + "brokerClient": "testing", + "brokerUseTLS": true } diff --git a/master/README.md b/master/README.md index f5b19a5..41e9911 100644 --- a/master/README.md +++ b/master/README.md @@ -6,4 +6,7 @@ openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out s # Extract SHA-1 openssl x509 -in server.crt -sha1 -noout -fingerprint | sed 's/.*=/0x/g; s/:/, 0x/g' + +# extract sha256 +openssl x509 -in mqtt/certs/server.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 | sed -r 's/.*= //g; s/([0-9a-z]{2})/\1:/g; s/:$//g' ``` diff --git a/spejsiot/Endpoint.cpp b/spejsiot/Endpoint.cpp index 2212992..cfe66c5 100644 --- a/spejsiot/Endpoint.cpp +++ b/spejsiot/Endpoint.cpp @@ -13,7 +13,9 @@ void Endpoint::notify(String property, String value) { void Endpoint::onConnected() { parent->subscribe(name + "/+/set"); - parent->notify(name + "/$type", type); + if (type.length()) { + parent->notify(name + "/$type", type); + } } void Endpoint::onMessage(String topic, String payload) { diff --git a/spejsiot/SpejsNode.cpp b/spejsiot/SpejsNode.cpp index 6fa7e91..256098f 100644 --- a/spejsiot/SpejsNode.cpp +++ b/spejsiot/SpejsNode.cpp @@ -7,11 +7,18 @@ #define CONFIG_FILE "config.json" +uint8_t hexToInt(char c) { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return c - 'a' + 10; +} + void SpejsNode::init(bool debug) { Serial.begin(115200); Serial.systemDebugOutput(debug); // Debug output to serial deviceID = WifiStation.getMAC().substring(6, 12); + brokerClient = "iot-" + deviceID; currentSlot = 0; if(!rboot_get_last_boot_rom(¤tSlot)) { @@ -20,16 +27,21 @@ void SpejsNode::init(bool debug) { debugf("*** SpejsNode init, runnning on: %s, current rom: %d", deviceID.c_str(), currentSlot); + System.onReady(SystemReadyDelegate(&SpejsNode::systemReady, this)); +} + +void SpejsNode::systemReady() { WifiAccessPoint.enable(false); WifiStation.enable(true); - WifiStation.config(WIFI_SSID, WIFI_PWD); + WifiStation.config(wifiSSID, wifiPassword); + WifiStation.connect(); WifiEvents.onStationGotIP(StationGotIPDelegate(&SpejsNode::gotIP, this)); registerEndpoint("$implementation", new ImplementationEndpoint()); // Keepalive Timer initialization - keepaliveTimer.initializeMs(10000, TimerDelegate(&SpejsNode::keepAliveHandler, this)).start(); + keepaliveTimer.initializeMs(10000, [=]() { keepAliveHandler(); }).start(); statusLED.high(); } @@ -47,15 +59,72 @@ void SpejsNode::loadJSON(std::vector initializers) { if (fileExist(CONFIG_FILE)) { int size = fileGetSize(CONFIG_FILE); debugf("Found config file, %d bytes", size); + char* jsonString = new char[size + 1]; + if (jsonString == nullptr) { + debugf("CAN'T CALLOCATE JSONSTRING"); + return; + } + debugf("allocated %08x",jsonString); + fileGetContent(CONFIG_FILE, jsonString, size + 1); - deserializeJson(root, jsonString); + debugf("loaded %s", jsonString); + DeserializationError err = deserializeJson(root, jsonString); + debugf("parsed: %d", err); + if ( err ) { + debugf("invalid config"); + return; + } - if (root.containsKey("name")) + if (root.containsKey("name")) { deviceType = (root["name"]).as(); + } + debugf("Loading networks..."); + JsonArray networks = root["networks"].as(); + if (networks.size()) { + debugf("got networks"); + JsonObject network = networks[0].as(); + + if (network.containsKey("ssid")) { + debugf("Loaded JSON network configuration"); + wifiSSID = network["ssid"].as(); + wifiPassword = network["psk"].as(); + } else { + debugf("invalid network configuration"); + } + } + + // Broker configuration + debugf("Loading broker configuration..."); + if (root.containsKey("broker")) { + auto brokerPort = root.containsKey("brokerPort") ? root["brokerPort"] : 1883; + brokerUseTLS = root["brokerUseTLS"]; + brokerURL = root["broker"].as(); + + String hash = root["brokerSHA256"].as(); + if ((hash.length() + 1) % 3 == 0) { + debugf("Loading sha256 certificate/pubkey hash"); + int hashLength = (hash.length() + 1) / 3; + uint8_t* hashBlob = new uint8_t[hashLength]; + + for (int i = 0; i < hashLength; i++) { + hashBlob[i] = hexToInt(hash[3*i]) << 4 | hexToInt(hash[3*i+1]); + } + + // TODO use onSslInit callback + //mqtt.pinCertificate(hashBlob, eSFT_PkSha256); + } + } + + debugf("Loading endpoints..."); + if (!root.containsKey("endpoints")) { + debugf("No endpoints found..."); + return; + } JsonObject data = root["endpoints"].as(); + for (auto it: data) { bool found = false; @@ -108,7 +177,7 @@ void SpejsNode::keepAliveHandler() { } } -inline String SpejsNode::DEV_TOPIC(String t) { +String SpejsNode::DEV_TOPIC(String t) { return TOPIC_PREFIX + deviceID + "/" + t; } @@ -132,42 +201,79 @@ void SpejsNode::onConnected() { debugf("Connection successful"); - // MQTT initialization - mqtt.setWill(DEV_TOPIC("$online"), "false", 1, true); - -#ifdef ENABLE_SSL - const uint8_t sha1Fingerprint[] = SSL_FINGERPRINT; - mqtt.connect( - "mqtts://" MQTT_BROKER ":" MQTT_PORT, - "iot-" + deviceID, "", "", true); - mqtt.addSslOptions(SSL_SERVER_VERIFY_LATER); - mqtt.setSslFingerprint(sha1Fingerprint, 20); -#else - mqtt.connect( - "mqtt://" MQTT_BROKER ":" MQTT_PORT, "iot-" + deviceID); -#endif - - for(unsigned int i = 0 ; i < endpoints.count() ; i++) { - endpoints.valueAt(i)->onConnected(); - } - - subscribe("$implementation/+"); - - // Say hello - notify("$online", "true"); - notify("$homie", "2"); - notify("$name", deviceType); - notify("$localip", WifiStation.getIP().toString()); - notify("$mac", WifiStation.getMAC()); - notify("$fw/name", "spejsiot"); - notify("$fw/version", BUILD_ID); - notify("$implementation/slot", String(currentSlot)); + debugf("http init"); // HTTP initialization http.listen(80); http.paths.set("/", HttpPathDelegate(&SpejsNode::httpIndex, this)); + http.paths.set("/config.json", HttpPathDelegate(&SpejsNode::httpConfig, this)); http.paths.setDefault(HttpPathDelegate(&SpejsNode::httpFile, this)); + http.setBodyParser("application/json", bodyToStringParser); + debugf("mqtt init"); + mqtt.setMessageHandler([=](MqttClient& client, mqtt_message_t* message) -> int { + if (message == nullptr) return -1; + + String topic = String((const char*)message->publish.topic_name.data, message->publish.topic_name.length); + String content; + if(message->publish.content.data) { + content.concat((const char*)message->publish.content.data, message->publish.content.length); + } + + mqttCallback(topic, content); + return 0; + }); + + // MQTT initialization + mqtt.setWill(DEV_TOPIC("$online"), "false", 1, true); + +#ifdef ENABLE_SSL + if (brokerUseTLS) { + debugf("Using TLS"); + mqtt.addSslOptions(SSL_SERVER_VERIFY_LATER); + //mqtt.pinCertificate(fingerprints); + } +#endif + debugf("Connecting: %s as %s", brokerURL.c_str(), brokerClient.c_str()); + + mqtt.setConnectedHandler([=](MqttClient& client, mqtt_message_t* message) { + debugf("Initializing endpoints"); + for(unsigned int i = 0 ; i < endpoints.count() ; i++) { + endpoints.valueAt(i)->onConnected(); + } + + debugf("subscribing"); + subscribe("$implementation/+"); + + debugf("say hello"); + + // Say hello + notify("$online", "true"); + notify("$homie", "2"); + notify("$name", deviceType); + notify("$localip", WifiStation.getIP().toString()); + notify("$mac", WifiStation.getMAC()); + notify("$fw/name", "spejsiot"); + notify("$fw/version", BUILD_ID); + notify("$fw/slot", String(currentSlot)); + + return 0; + }); + + mqtt.connect(brokerURL, brokerClient); +} + +void SpejsNode::httpConfig(HttpRequest &request, HttpResponse &response) +{ + if (request.method == HTTP_POST) { + debugf("settings data"); + String body = request.getBody(); + Serial.println(body); + fileSetContent("config.json", body); + response.sendString("{\"status\": 200}"); + } else { + response.sendFile("config.json"); + } } void SpejsNode::httpFile(HttpRequest &request, HttpResponse &response) @@ -198,6 +304,7 @@ void SpejsNode::httpFile(HttpRequest &request, HttpResponse &response) * Publish on device-specific topic */ bool SpejsNode::notify(String key, String value) { + debugf("%s [%d] = %s [%d]", key.c_str(), key.length(), value.c_str(), value.length()); mqtt.publish(DEV_TOPIC(key), value, true); return mqtt.getConnectionState() == eTCS_Connected; } diff --git a/spejsiot/SpejsNode.h b/spejsiot/SpejsNode.h index cc6d6ff..d6b6e47 100644 --- a/spejsiot/SpejsNode.h +++ b/spejsiot/SpejsNode.h @@ -43,7 +43,7 @@ public: highState = highState_; pinMode(pin, OUTPUT); - animateTimer.initializeMs(50, TimerDelegate(&LED::animate, this)).start(); + animateTimer.initializeMs(50, [=]() { animate(); }).start(); } void idle() { @@ -69,7 +69,22 @@ protected: void mqttCallback(String, String); void otaUpdateCallback(bool result); void httpFile(HttpRequest &request, HttpResponse &response); + void httpConfig(HttpRequest &request, HttpResponse &response); void httpIndex(HttpRequest &request, HttpResponse &response); + + void systemReady(void); + + String wifiSSID = WIFI_SSID; + String wifiPassword = WIFI_PWD; + + String brokerURL = "mqtt://" MQTT_BROKER; + String brokerClient = ""; + + bool brokerUseTLS = false; + +#ifdef ENABLE_SSL + SslFingerprints fingerprints; +#endif public: MqttClient mqtt; HttpServer http; diff --git a/spejsiot/endpoints/DHTEndpoint.cpp b/spejsiot/endpoints/DHTEndpoint.cpp index 319eedc..b5745b9 100644 --- a/spejsiot/endpoints/DHTEndpoint.cpp +++ b/spejsiot/endpoints/DHTEndpoint.cpp @@ -4,7 +4,7 @@ void DHTEndpoint::bind(String _name, SpejsNode* _parent) { Endpoint::bind(_name, _parent); sensor.setup(pin, sensor_type); - samplingTimer.initializeMs(samplingRate, TimerDelegate(&DHTEndpoint::sample, this)).start(); + samplingTimer.initializeMs(samplingRate, [=]() { sample(); }).start(); } void DHTEndpoint::sample() {