# YAK - Yet Another Klimasteuerungsgerät BASE Library implementing basic functionalities common to all YAK firmware projects. ## Include the library into your PlatformIO project To include the library to your PlatformIO project, add the following to your to the `lib_deps` list of your `platformio.ini`: ``` git+ssh://git@gitlab.synyx.de/internet-of-things/yak_base.git ``` For example: ``` lib_deps = git+ssh://git@gitlab.synyx.de/internet-of-things/yak_base.git ``` Note that the library relies on some dependencies. For that, if you also need them in your project, there is no need to declare in your dependency entries in your `platform.ini`. Those dependencies are: * PubSubClient * ArduinoJson * WifiManager * YAK_Logging ## Usage The library provides several features useful to YAK firmware projects. The following usage information is covers those features separately. ### I_MQTT_Client and MQTT_PubSubClient The interface `I_MQTT_Client` declares a low level MQTT client that provides the mean to subscribe to MQTT topics as well as to send MQTT messages. The class `MQTT_PubSubClient` implements that interface using the `PubSubClient` library. It also provides the functionality to keep the connection to the MQTT broker alive by transparently handling the initial connection as well as reconnection attempts should the connection be dropped. #### Setting up the MQTT client and maintaining a connection to the broker Create an instance of `MQTT_PubSubClient` providing the network `Client` to use, a unique (!) `clientId` and an instance of `MQTTConfig` encapsulating the connection information. ```cpp mqttClient = new MQTT_PubSubClient(wifiClient, nodeId, mqttConfig); ``` Note: If you are using the `ConfigManager` you can get an instance of `MQTTConfig` prefilled from your persistent configuration by calling the method `ConfigManager::getMQTTConfig()`. Add a call of `MQTT_PubSubClient::loop()` to your `loop()` function. ```cpp mqttClient->loop(); ``` The `MQTT_PubSubClient::loop()` returns the current status of the connection to the MQTT broker. This can be one of the following: * `mqtt_connection_state::CONNECTED` - Indicating that the client is connected using an existing connection * `mqtt_connection_state::RECONNECTED` - Indicating that the client is connected, but a new connection was established. This may be the initial connection or a reestablished connection after the previous connected was dropped. * `mqtt_connection_state::NOT_CONNECTED` - Indicating that the client is not connected. Note: This also indicates the intermediate state when the connection was dropped, but the new connection is not yet established (see `MQTT_RECONNECT_INTERVAL` below). Since `mqtt_connection_state::RECONNECTED` indicates a newly established connection (be it the initial or an reestablished one), encountering this state is the ideal place to subscribe to topics if you need so. For that, your `loop()` function may contain code like the following: ```cpp mqtt_connection_state connectionState = mqttClient->loop(); if (connectionState == mqtt_connection_state::RECONNECTED) { // subscribe to topics ... } ``` On the first call of `MQTT_PubSubClient::loop()` the initial connection is attempted, if that fails or the connection is dropped, connection attempts are not made on every call of `MQTT_PubSubClient::loop()`. Instead there is a delay between connection attempts defaulting to 5000ms that is configurable by defining `MQTT_RECONNECT_INTERVAL` with the amount of ms to wait between connection attempts. #### Subscribing to MQTT topics and receiving messages To subscribe to an MQTT topic use the method `bool I_MQTT_Client::subscribe(const char*)`, providing the topic you want to subscribe to. The method returns a `bool` with `true` indicating a successful subscription. To receive messages from the topics you are subscribed to, set a callback function using the method `I_MQTT_Client::setCallback(mqtt_callback_t)`. The signature defined by `mqtt_callback_t` is `void(char*, uint8_t*, unsigned int)`. The first parameter provides the topic the message arrived at. The second parameter is the binary message payload. The last parameter states the payload length in bytes. Note: There is only a single callback function to be set. If subscribed to several topics, the messages for each topic can be distinguished by checking the first parameter (the topic the message arrived at). For example, a callback function may be implemented like this: ```cpp void messageCallback(char *topic, byte *payload, unsigned int length) { char* json = strncpy((char*)malloc(length + 1), (char*)payload, length); json[length] = '\0'; if (strcmp(topic, TOPIC_FOO) == 0) { // handle messages arrived at the topic foo handleFoo(json); } else if (strcmp(topic, TOPIC_BAR) == 0) { // handle messages arrived at the topic bar handleBar(json); } free(json); }; ``` #### Sending messages to MQTT topics To sending MQTT messages to topics, simply call the method `bool I_MQTT_Client::publish(const char *topic, const char *payload)`. The method returns a `bool` with `true` indicating a successful sending. ### Config_Manager The class `Config_Manager` provides functionalities for retrieval and update of persistent configuration data. For the current implementation this data consists of information on the node, encapsulated in an instance of `NodeConfig` and information on MQTT connection parameters, encapsulated in an instance of `MQTTConfig`. Exchange of configuration data (may it be for retrieval or update) happens exclusively through instances of those `*Config` classes. For persistence, the configuration data is serialized to JSON. For the actual physical (and platform specific) persistence, the `Config_Manager` relays to an implementation of the `I_Config_Persistence` interface. With the class `Config_Persistence_SPIFFS` an implementation of that interface is provided, persisting the config data as a file on the NodeMCUs flash file system. For the current implementation, the JSON the configuration data is serialized to is structured as follows: ```json { "node": { "roomName": "foo", "nodeName": "bar" }, "mqtt": { "server": "broker.localdomain", "user": "brokerUser", "passwd": "brokerUserPass" } } ``` #### Setting up the Config_Manager and persistence The constructor of the `Config_Manager` class takes an instance of an implementation of `I_Config_Persistence` as parameter, which will be used for the actual physical persistence. With `Config_Persistence_SPIFFS` an implementation of that interface is provided, taking itself the name of the file used to store the configuration data as constructor parameter. ```cpp configManager = new Config_Manager(new Config_Persistence_SPIFFS("/config.json")); ``` #### Retrieving and updating configuration data Access to the configuration data is provided by the methods `const NodeConfig* Config_Manager::getNodeConfig()` and `const MQTTConfig* Config_Manager::getMQTTConfig()`. Note the `const`! The references returned by these methods are updated with every change to the configuration. That way, any reference you are holding always reflects the current state of the configuration data. On the other hand, you are not allowed to alter the references returned by these methods or any of the referenced instances member attributes. For updates to the configuration data, the methods `bool Config_Manager::setNodeConfig(NodeConfig* nodeConfig)` and `bool Config_Manager::setMQTTConfig(MQTTConfig* mqttConfig)` are provided. The methods return a `bool` with `true` indicating a successful update of the persistent configuration. Note: Never call the updating `set*` methods with instances returned by the `get*` methods! The current implementation does not prevent that (but may do so in the future) and you will encounter corrupt memory access failures (i.e. seg faults). ### NodeStatus The class `NodeStatus` implements the functionality to send status updates on the current node to the MQTT topic `node/{nodeId}` with the payload being a JSON document. For that, several sources of information are consulted. This includes the nodes current configuration as well es internal functions (i.e. wifi quality and MCU voltage). For the current implementation, the provided status information in it's JSON form is as follows: ```json { "nodeId": "the_node_id", "version": "current firmware version - ie. 1.1.0", "build_date": "current firmware build date - ie. Jul 11 2018", "build_time": "current firmware build time - ie. 16:45:49", "room_name": "the room name from the nodes configuration", "node_name": "the node name from the nodes configuration", "wifi_rssi": -62, "vcc": 3.45 } ``` #### Setup automatic status updates To setup automatic status updates, simply create an instance of `NodeStatus` and add a call to `bool NodeStatus::update()` to your `loop()` function. By default, status updates are send every 30000ms. The update interval can be configured by defining `STATUS_UPDATE_INTERVAL` with the desired update interval in ms. The constructor of `NodeStatus::NodeStatus(I_MQTT_Client* mqttClient, const NodeConfig* nodeConfig, const MQTTConfig* mqttConfig, const char* nodeId)` takes several parameters. These are the `Ì_MQTT_Client` (see above) to use for sending the status updates, the nodes `NodeConfig` and `MQTTConfig` (ie. as provided by the `Config_Manager` - see above) and the nodes `nodeId`. An exemplary setup may look similar to the following: ```cpp NodeStatus* nodeStatus; void setup() { // ... setup config manager and mqtt client // ... determine nodeId const NodeConfig* nodeConfig = configManager->getNodeConfig(); const MQTTConfig* mqttConfig = configManager->getMQTTConfig(); nodeStatus = new NodeStatus(mqttClient, nodeConfig, mqttConfig, nodeId); // ... }; void loop() { // ... nodeStatus->update(); // ... }; ``` ### Logging The library includes a transitive dependency to the `YAK_Logging` library, because each of the libraries functions make use of it. This means for your project, when using this library the logging functionalies of the `YAK_Logging` library can also be used. For details on the usage of the `YAK_Logging` library, see that libraries [project page](https://gitlab.synyx.de/internet-of-things/yak_logging). ### Over the Air Update/Config ### config JSON object ```json { "mqttServer": "iot-central-test.synyx.coffee", "mqtt User": "", "mqttPasswd": "", "roomName": "test", "nodeName": "anders" , "type" : "config" } ```