spejsiot/spejsiot/SpejsNode.cpp

330 lines
9.9 KiB
C++

#include <SpejsNode.h>
#include <Endpoint.h>
#include <ver.h>
#include <endpoints/ImplementationEndpoint.h>
#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) {
deviceID = WifiStation.getMAC().substring(6, 12);
brokerClient = "iot-" + deviceID;
currentSlot = 0;
if(!rboot_get_last_boot_rom(&currentSlot)) {
currentSlot = rboot_get_current_rom();
}
Serial.begin(115200);
Serial.systemDebugOutput(debug); // Debug output to serial
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(wifiSSID, wifiPassword);
WifiStation.connect();
WifiEvents.onStationGotIP(StationGotIPDelegate(&SpejsNode::gotIP, this));
registerEndpoint("$implementation", new ImplementationEndpoint());
// Keepalive Timer initialization
keepaliveTimer.initializeMs(10000, [=]() { keepAliveHandler(); }).start();
statusLED.high();
}
void SpejsNode::loadJSON(std::vector<EndpointInitializer> initializers) {
#ifdef RBOOT_SPIFFS_0
debugf("trying to mount spiffs at 0x%08x, length %d", RBOOT_SPIFFS_0, SPIFF_SIZE);
spiffs_mount_manual(RBOOT_SPIFFS_0, SPIFF_SIZE);
#else
debugf("trying to mount spiffs at 0x%08x, length %d", 0x100000, SPIFF_SIZE);
spiffs_mount_manual(0x100000, SPIFF_SIZE);
#endif
DynamicJsonBuffer jsonBuffer;
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);
debugf("loaded %s", jsonString);
JsonObject& root = jsonBuffer.parseObject(jsonString);
debugf("parsed: %d", root.success());
if (!root.success()) {
debugf("invalid config");
return;
}
if (root.containsKey("name"))
deviceType = (root["name"]).asString();
debugf("Loading networks...");
JsonArray& networks = root.get<JsonArray&>("networks");
if (networks.success() && networks.size()) {
debugf("got networks");
JsonObject& network = networks.get<JsonObject&>(0);
if (network.success() && network.containsKey("ssid")) {
debugf("Loaded JSON network configuration");
wifiSSID = network["ssid"].as<String>();
wifiPassword = network["psk"].as<String>();
} 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>();
String hash = root.get<String>("brokerSHA256");
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]);
}
mqtt.pinCertificate(hashBlob, eSFT_PkSha256);
}
}
debugf("Loading endpoints...");
JsonObject& data = root.get<JsonObject&>("endpoints");
if (!data.success()) {
debugf("No endpoints found...");
return;
}
for (auto it: data) {
bool found = false;
for(auto init: initializers) {
Endpoint* ep = init(it.value);
if (ep != NULL) {
debugf("%s: got object", it.key);
registerEndpoint(it.key, ep);
found = true;
break;
}
}
if (!found) {
debugf("%s: nothing found", it.key);
}
}
} else {
debugf("No configuration");
}
}
void SpejsNode::keepAliveHandler() {
static int failureCounter = 0;
if(!WifiStation.isConnected()) {
statusLED.high();
debugf("keepalive: Network reconnect");
if(failureCounter++ < 5)
WifiStation.connect();
else
System.restart();
} else if(mqtt.getConnectionState() != eTCS_Connected) {
statusLED.high();
debugf("keepalive: MQTT reconnect");
if(failureCounter++ < 5)
onConnected();
else
System.restart();
} else {
statusLED.idle();
failureCounter = 0;
uint8_t mode;
if(rboot_get_last_boot_mode(&mode) && mode == MODE_TEMP_ROM) {
rboot_set_current_rom(currentSlot);
debugf("Successfuly connected, accepting temp rom");
}
}
}
String SpejsNode::DEV_TOPIC(String t) {
return TOPIC_PREFIX + deviceID + "/" + t;
}
void SpejsNode::httpIndex(HttpRequest &request, HttpResponse &response)
{
response.sendString("This is spejsiot device, take a look at: https://wiki.hackerspace.pl/projects:spejsiot\n"
"\nDevice type: " + deviceType +
"\nFirmware version: " + String(BUILD_ID) +
"\nMAC: " + WifiStation.getMAC());
}
/*
* Successful network connection handler
*/
void SpejsNode::gotIP(IPAddress ip, IPAddress netmask, IPAddress gateway) {
onConnected();
}
void SpejsNode::onConnected() {
statusLED.idle();
debugf("Connection successful");
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)
{
String file = request.uri.Path;
if (file[0] == '/')
file = file.substring(1);
if (file.startsWith("api/1/")) {
String req = file.substring(6);
String key = req.substring(0, req.indexOf("/"));
String value = req.substring(req.indexOf("/") + 1);
if(key.length() == 0 || value.length() == 0 || !endpoints.contains(key)) {
response.code = 400;
} else {
EndpointResult result = endpoints[key]->onValue(key, value);
JsonObjectStream* stream = new JsonObjectStream();
JsonObject& json = stream->getRoot();
json["status"] = result.status;
response.sendDataStream(stream, MIME_JSON);
}
}
}
/*
* 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;
}
/*
* Subsribe to device-specific topic
*/
bool SpejsNode::subscribe(String topic) {
mqtt.subscribe(DEV_TOPIC(topic));
return mqtt.getConnectionState() == eTCS_Connected;
}
/*
* Register new endpoint
*/
void SpejsNode::registerEndpoint(String key, Endpoint* endpoint) {
endpoints[key] = endpoint;
endpoint->bind(key, this);
}
void SpejsNode::mqttCallback(String origtopic, String value) {
for(unsigned int i = 0 ; i < endpoints.count() ; i++) {
endpoints.valueAt(i)->onMessage(origtopic, value);
}
}