Modules

Of the firmware modules, we already looked at the plant module in Chapter 5, Example - Soil Humidity Monitor with Wi-Fi. Here we will look at the remaining modules, starting with the THP module:

#include "base_module.h"
class THPModule {
public:
static bool initialize();
static bool start();
static bool shutdown();
};
#include "thp_module.h"
#include "dht_module.h"
#include "bme280_module.h"
bool THPModule::initialize() {
BaseModule::registerModule(MOD_IDX_TEMPERATURE_HUMIDITY,
THPModule::start, THPModule::shutdown);
return true;
}
bool THPModule::start() {
BME280Module::init();
return true;
}
bool THPModule::shutdown() {
BME280Module::shutdown();
return true;
}

This module has the provisions to act as a generic interface to a wide variety of temperature, humidity, and air-pressure sensors. As this was not a requirement at the time, it merely acted as a pass-through for the BME280 module. It registers itself with the base module when called and calls the respective functions on the BME280 module when its own are called.

To make it more versatile, the class would be extended to allow for commands to be received—possibly over MQTT as well on its own topic—which would then enable a specific sensor module, or even a collection of them, when using separate temperature and air pressure sensors, for example.

Regardless of whether it is being used or not in this firmware, let's take a look at the DHT module so that we can compare it with the BME280 module later.

#include "ota_core.h"

#include <Libraries/DHTesp/DHTesp.h>

#define DHT_PIN 5 // DHT sensor: GPIO5 ('D1' on NodeMCU)


class DHTModule {
static DHTesp* dht;
static int dhtPin;
static Timer dhtTimer;

public:
static bool init();
static bool shutdown();
static void config(String cmd);
static void readDHT();
};

Of note is that while the class is static, any variables that would take up considerable memory—such as library class instances—are defined as pointers. This forms a compromise between having the module available for easy use and going for a more complicated, fully dynamic solution. As most MCUs tend to keep as much of the program code as possible in the ROM until it is used, this should keep both SRAM and ROM usage to a minimum.

#include "dht_module.h"

DHTesp* DHTModule::dht = 0;
int DHTModule::dhtPin = DHT_PIN;
Timer DHTModule::dhtTimer;


bool DHTModule::init() {
if (!OtaCore::claimPin(dhtPin)) { return false; }
if (!dht) { dht = new DHTesp(); dht->setup(dhtPin, DHTesp::DHT22); }
dhtTimer.initializeMs(2000, DHTModule::readDHT).start();
return true;
}

To initialize the module, we ensure that we can safely use the general-purpose input/output (GPIO) pins we intend to use, create a new instance of the sensor class from the library, and set it up before creating the 2-second timer that will perform the scheduled sensor read-out.

Since we create a new instance of the  sensor class upon initializing there should never be an existing instance of this class, but we check in case the init() function gets called again a second time for some reason. Calling the initialization function on the timer a second time could also be included in this block, but isn't strictly required as there is no harmful effect from initializing the timer again.

bool DHTModule::shutdown() {
dhtTimer.stop();
if (!OtaCore::releasePin((ESP8266_pins) dhtPin)) { delete dht; return false; }
delete dht;
dht = 0;
return true;
}

To shut down the module, we stop the timer and release the GPIO pin we were using, before cleaning up all resources we used. As we have claimed the pin we're using previously when we initialized the module we should have no issues releasing it again, but we check to make sure.


void DHTModule::config(String cmd) {
Vector<String> output;
int numToken = splitString(cmd, '=', output);
if (output[0] == "set_pin" && numToken > 1) {
dhtPin = output[1].toInt();
}
}

This is an example of how one could later change the GPIO pin used by a module, here using the old text-based command format that early versions of the BMaC firmware used to use. We could also receive this information via an MQTT topic, or by actively querying the command and control server. 

Note that to change the pin used by the sensor one would have to restart the sensor by deleting the class instance and creating a new instance.

 void DHTModule::readDHT() {
TempAndHumidity th;
th = dht->getTempAndHumidity();

OtaCore::publish("nsa/temperature", OtaCore::getLocation() + ";" + th.temperature);
OtaCore::publish("nsa/humidity", OtaCore::getLocation() + ";" + th.humidity);
}

Next, for the BME280 sensor module, its code looks like this:

#include "ota_core.h"

#include <Libraries/BME280/BME280.h>


class BME280Module {
static BME280* bme280;
static Timer timer;

public:
static bool init();
static bool shutdown();
static void config(String cmd);
static void readSensor();
};

Finally, it's familiar-looking implementation:

#include "bme280_module.h"

BME280* BME280Module::bme280 = 0;
Timer BME280Module::timer;


bool BME280Module::init() {
if (!OtaCore::starti2c()) { return false; }
if (!bme280) { bme280 = new BME280(); }

if (bme280->EnsureConnected()) {
OtaCore::log(LOG_INFO, "Connected to BME280 sensor.");
bme280->SoftReset();
bme280->Initialize();
}
else {
OtaCore::log(LOG_ERROR, "Not connected to BME280 sensor.");
return false;
}

timer.initializeMs(2000, BME280Module::readSensor).start();

return true;
}


bool BME280Module::shutdown() {
timer.stop();
delete bme280;
bme280 = 0;

return true;
}


void BME280Module::config(String cmd) {
Vector<String> output;
int numToken = splitString(cmd, '=', output);
if (output[0] == "set_pin" && numToken > 1) {
//
}
}


void BME280Module::readSensor() {
float t, h, p;
if (bme280->IsConnected) {
t = bme280->GetTemperature();
h = bme280->GetHumidity();
p = bme280->GetPressure
OtaCore::publish("nsa/temperature", OtaCore::getLocation() + ";" + t);
OtaCore::publish("nsa/humidity", OtaCore::getLocation() + ";" + h);
OtaCore::publish("nsa/pressure", OtaCore::getLocation() + ";" + p);
}
else {
OtaCore::log(LOG_ERROR, "Disconnected from BME280 sensor.");
}
}

As we can see, this module was basically copied from the DHT one, and then modified to fit the BME280 sensor. The similarities between those two modules was one of the motivations behind developing the THP module, in order to exploit these similarities.

As with the DHT module, we can see that we rely on an external library to do the heavy lifting for us, with us merely having to call the functions on the library class to set up the sensor and get the data from it.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.178.207