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.
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.
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 (seeMQTT_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:
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:
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:
{
"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.
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:
{
"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:
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.
Over the Air Update/Config
config JSON object
{
"mqttServer": "iot-central-test.synyx.coffee",
"mqtt User": "",
"mqttPasswd": "",
"roomName": "test",
"nodeName": "anders" ,
"type" : "config"
}