-
Notifications
You must be signed in to change notification settings - Fork 1
Creating a basic Module
Author: Will Richards
In Loom each sensor, actuator or piece of hardware is considered a module. This is so all components can be interfaced simultaneously by the manager to allow for unified data manipulation. Note: The Loom Logger will be used in this tutorial so if you haven't already done so please follow this Logger Introduction
There are 5 key components of a module that must be included in some capacity (they can do nothing if they are unused)
virtual void initialize() = 0; // Initialize all functionality of the sensor
virtual void measure() = 0; // Collect data from the sensor
virtual void package() = 0; // Package collected data into JSON document
virtual void power_up() = 0; // Power the sensor up and come out of sleep
virtual void power_down() = 0; // Power the sensor down to prepare for sleep
Additional functionality is provided to all modules for setting and retrieving the name of the module
void setModuleName(const char* modName) // Set the name of the module
virtual const char* getModuleName() { return moduleName; }; // This is a virtual function that can be overridden within your module
virtual void display_data() {}; // Unused by default but can be overriden to allow device control when display_data is called
/* This is by deafult true for every module but can be set to false in the initialize method to inform other methods to not continue */
bool moduleInitialized = true;
For this example we will be using the SDI12 sensor module
Loom_SDI12.h
#include "Module.h"
#include "Loom_Manager.h"
/**
* Provides both a loomified in addition to a standard reliable library implementation
*
* @author Will Richards
*/
class Loom_SDI12 : public Module{
protected:
/* These should be called only by Manager.h */
void measure() override; // Generic Measure Call To Pull Sensor Data
void package() override; // Generic Package Call to Store Sensor Data
void power_down() override; // Power down the module
void power_up() override; // Power up the module
public:
Loom_SDI12(Manager& man, const int pinAddr = 11); // Loomified Constructor
Loom_SDI12(const int pinAddr = 11); // Standard Sensor Interaction Constructor
void initialize() override; // Initialize the sensor interface
float getTemperature() { return sensorData[0]; }; // Temperature of the soil
float getDielectricPerm() { return sensorData[1]; }; // Dielectric Permittivity of the soil
float getConductivity() { return sensorData[2]; }; // Conductivity of the soil
private:
Manager* manInst; // Instance of the Manager
SDI12 sdiInterface; // SDI-12 Library Interface
float sensorData[3]; // Array of floats to store sensor data
};
The code above is a heavily stripped version of the SDI12 library showing only the loomified pieces. Here you can see how each of the pieces in implemented in a broader scope before we rebuild it as an example.
Here we have a basic class structure...
Loom_ExampleModule.h
// Add this line to prevent multiple inclusions of the header file
#pragma once
// Include both the Module and Manager header files
#include "Module.h"
#include "Loom_Manager.h"
/**
* This is an example Module for the sake of this tutorial
*
* @author Will Richards
*/
class Loom_ExampleModule : public Module {
protected:
/* These state that we will provide and implementation for these methods in our .cpp file */
void initialize() override;
void measure() override;
void package() override;
/* While this denotes methods that we don't need to do anything */
void power_down() override {};
void power_up() override {};
public:
/* ALL CONSTRUCTORS MUST INCLUDE A REFRENCE TO THE MANAGER */
/* This one takes an int named 'pinNum'. Default values should be supplied in almost all cases */
Loom_ExampleModule(Manager& man, int pinNum = 11);
/* Provide generic getters for module data to allow for independent manipulation */
int getReading() { return pinReading; };
private:
Manager* manInst; // Instance to the manager that is supplied to the constructor
int pinNum; // Pin number we are using
bool pinReading = false; // Some data that we will update in the measure function
};
This structure provides the protype for our module next we need to add functionality within our Loom_ExampleModule.cpp
file.
Loom_ExampleModule.cpp
/* Don't forget to include the header file at the top*/
#include "Loom_ExampleModule.h"
/* Includes the logger component to allow for easy logging*/
#include "Logger.h"
/*
Here we have the implementation for the no parameter constructor
- First in our initialization list we have the module itself, this is required in all modules as it provides the name in this case 'ExampleModule'
- Next we need to initialize our manager instance by passing the reference into our pointer
*/
Loom_ExampleModule::Loom_ExampleModule(Manager& man): Module("ExampleModule"), manInst(&man){
// Use the value from the constructor, this could also be done in the initialization list
this->pinNum = pinNum;
/* THIS IS CRITICAL IN EVERY MODULE. This line registers the module with the manager */
manInst->registerModule(this);
}
/*
This is the piece of the module that is called when `manager.initialize()` is called in your sketch.
This should preform any actions required to initialize sensor hardware for taking
measurements.
*/
void Loom_ExampleModule::initialize(){
/* Do initialization stuff.. In this case set our pin 11 to be an input */
pinMode(pinNum, INPUT);
LOG("Doing init stuff.... successfully initialized!");
}
/*
This is the piece of the module that is called when `manager.measure()` is called in your sketch.
This should request and store any data from perpherials.
*/
void Loom_ExampleModule::measure(){
/* Take a reading from the passed in pin */
LOG("Measuring...");
pinReading = digitalRead(pinNum);
}
/*
This is the piece of the module that is called when `manager.packaage()` is called in your sketch.
This will then take that data and package it up into the JSON document.
*/
void Loom_ExampleModule::package(){
/* Request a new JSON object for our current module to add our measurements to*/
JsonObject json = manInst->get_data_object(getModuleName());
/* JSON keynames in our case cannot have spaces, this creates a key named 'Pin_State' with the previously read state */
json["Pin_State"] = pinReading;
}
That's pretty much it! You now have a basic module capable of reading in data from a designated pin and packaging it up into a JSON document