Creating your own module

Instructions for creating your own and unique module for IoTmanager from Mitchel

General rules

The modules are located in the path IoTmanager \ src\modules\, namely:

  • in the sensor folder – Sensors
  • in the exec folder-Executive devices
  • in the virtual folder — Virtual elements
  • in the display – Screens folder

In the appropriate directory, create a directory with the module name in a capital letter (for example, AnalogAdc)
The module consists of at least two files:

  • *.cpp executable file (for example AnalogAdc.cpp) named by the module / folder name.
  • Required module description file modinfo. json

Third-party libraries are enabled in the modinfo file.json (see below) or placed directly in the module folder.

Executable file

The module class must be inherited from IoTItem.
Working with GPIO
If you need to work with GPIO, you should connect extern IoTGpio IoTgpio;
To get data from GPIO in the code, call the function IoTgpio.analogRead(_pin);
To be able to select a GPIO pin from the web, you must define the variable “pin"in modeinfo.json and read this variable in the class constructor.
To get parameters from the web, you need to request the parameter:

_pin = jsonReadInt(parameters, "pin"); // читается параметр pin и копируется в переменную _pin

Declaring variables

In the private section, we describe the variables used during class operation (similar to Arduino variables used in the setup and loop functions).
and variables loaded to / from the web (Module Configurations).
For example, unsigned int _pin; can be used to set and get the Pin value set by the user from the web.

Sensor Value

The module has a basic parameter Value – the value displayed in the web interface.
To get this value, the program code uses the function String getValue()
To change / save the main parameter Value the following functions are used:

  • setValue(const String& valStr, bool genEvent = true);
  • regEvent(const String& value, const String& consoleInfo, bool error = false, bool genEvent = true);
  • regEvent(float value, const String& consoleInfo, bool error = false, bool genEvent = true);

Given that the IoTmanager model is event-driven, it will not be possible to process the module values in the script without an event.
For an event to occur for changing a parameter in the module, you must call regEvent or setValue at least once with the value GenEvent = true (if GenEvent is not passed to the function, the event will be true by default)

Main functions of the module's parent class

  • String getId—) - getting the ID of this module (the id parameter)
  • virtual String getValue–) - retrieves the value of this module (the val parameter)
  • long getInterval–) - getting the interval for this module (int parameter)

They can be called from the parent class IoTItem anywhere in the code execution.

Output to the log

To display a log on a web page in the window (also duplicated in the serial port)
SerialPrint(«i», F(«ModuleName»), «User set default value: » + val);
The first parameter is the message type. It can be "i" – informational," E" - error (the message is highlighted in red).
The second parameter is the name of the module.
The third parameter is the message itself.

Constructor of the module class

Initializes the user module / class and plug-in libraries. Analogous to setup from arduino, for small modules, you can simply try to transfer the code from the setup function when writing a module based on the working code in Arduino.
The custom class constructor function must be inherited from IoTItem and accept string parameters, which will accept a string with parameters from the web

ExampleModule(String parameters) : IoTItem(parameters)

To get parameters from the web, you need to request a parameter with a specific name (they must be set in modeinfo. json)

_pin = jsonReadInt(parameters, "pin"); // читается параметр pin и копируется в переменную _pin

All parameters are stored in the variable parameters, you can read any parameter using the capabilities of the jsonRead function:

  • jsonReadStr - returns the parameter value as a string,
  • jsonReadBool - returns the parameter value as true|false,
  • jsonReadInt - returns the parameter value as a number.

Basic parameters of the module:

  • polling interval (int)
  • id (s)
  • value (val)

You don't need to read them. You can always get IoTItem from the parent class at any point in the code execution with the corresponding functions:  

  • String getID() - getting the id of this module (id parameter)
  • virtual String getValue() - getting the value of this module (val parameter)
  • long getInterval() - getting the interval for this module (int parameter)

Periodic execution of the module code

In these functions, for simple modules, you can try to insert code simply from the loop function based on the working code in Arduino
Option 1: function doByInterval

The main method of implementing periodically repeated actions. This is an analog of loop from arduino, but called every int seconds set in the settings (the "int" parameter is configurable on the web and described in modeinfo. json).
Here you must read your sensor, get data from the sensor. No need to think about delay delays or millis timer checks. The code in this function is executed periodically by calling from the IoTmanager core.
To register the sensor data update event, call the function
regEvent(value.valD, «exapmleVal»); specifying the variable where the received sensor value is stored and the name of the sensor parameter (described in modeinfo. json)

All variables from the variables section are also available here, and those obtained in setup, if the sensor has several values, then do several regEvent.
Don't use delay — remember that this loop is common for all modules. If you have a long operation planned, try to break it down into parts and complete it in a few clock cycles

Option 2: loop function

full analog loop() from arduino. Keep in mind that all modules have equal access to the central server in turn. loop(), therefore, it is necessary to monitor delays in the algorithm and not create pauses.
In addition, this version overloads the parent version, so doByInterval() disabled. If necessary, you can calculate intervals in loop and provide an explicit call to periodic actions doByInterval().

 void loop() {

   //здесь код пользователя 

//Ниже Блок вызова функции периодических действий

       currentMillis = millis();

       difference = currentMillis - prevMillis;

       if (difference >= getInterval()) {

           prevMillis = millis();

           this->doByInterval();

       }

   }

Processing script commands

To process commands called in the script, you must redefine the function execute.
Inside the function, the command name is checked and parameters are processed if necessary. There can be several parameters.
The commands used must be described in the modeinfo file.json (see the corresponding section)
In the script, call commands by name (for example, command2(11” " test”);)

 IoTValue execute(String command, std::vector<IoTValue> ¶m){
        if (param.size() > 0) {
            if (command == "command1"){
                if (param.size()){
                    SerialPrint("i", F("ModuleName"), "User set default value: " + param[0].valS);
                }
            }
            else if (command == " command2"){
                if (param.size()){
                    SerialPrint("i", F("ModuleName"), "User set default value: " + param[0].valS + param[1].valS);
                }
            }
        }
        return {};
    }

Processing buttons from the module configuration

To add a button to the configuration block on a page in the web interface, you need to register it in the modeinfo.json file see the corresponding section
You also need to redefine the function onModuleOrder.
This function checks the name of the button assigned in modeinfo.json without the "btn-" prefix
value – the value entered in the field next to the button

void onModuleOrder(String &key, String &value){
        if (key == " Calibration ") //имя кнопки «btn-Calibration»{
//пользовательский код по нажатию кнопки
        }
    }

Class destructor

The module destructor function, called when a module is removed from the configuration

 ~ExapmleModule(){};

Module creation function

The function of a custom module for its processing in the IoTmanager kernel is required for initializing the module from the kernel.
This function directly creates module objects by their name (subtype) described in the modeinfo.json file

void* ExapmleModule(String subtype, String param) {

   if (subtype == F("ExapmleModule ")) {

       return new ExapmleModule(param);

   } else {

       return nullptr;

   }

}

Module description file

Based on the description of the modeinfo file.json help is also generated on the site iotmanager.org

Module Type Block

In the "menuSection" block, the module type is defined. It can be "Sensors", "Actuators", "Screens", or "Virtual Elements"

Block of module elements

Ad block "configItem"defines the elements of this module, there can be several of them, see section Module implementation features
Each element can contain the following standard parameters that will be displayed on the web during configuration, on the right is the parameter name, on the left is the default value.
You can also add your own custom parameters so that they can be configured on the web page

"global": 0, — Whether this element will be visible in the network by other Units (IoTmanager devices) (1 – visible to all IoTmanager devices, 0 – not visible to anyone)
"name": "BME280 Temperature", — Name of the module element, usually the name of the sensor and the sensor parameter if there are several of them (for example, temperature, pressure, humidity, etc.)
"type": "Reading", — Element type (SPECIFY)
"subtype": "Bme280t", — Short name of the element (subtype), usually the name of the sensor and the sensor parameter if there are several of them (for example, temperature, pressure, humidity, etc.)
"id": "Tmp" — - ID of the module element (used, for example, in scripts). Only the prefix is set here. The IoTmanager core will add a random number to the end. The configuration ID must be unique
"widget": "anydataTmp", — Type of widget to display in the web or app. Possible widgets are defined in the data_svetle/widgets.json file. You can also add your own widgets there
"page": "Sensors", — Name of the block on the web page or page in the application. It is used to combine the display of modules on the page
"descr": "Temperature", — Description of the module element
"int": 15, — Interval of execution of the periodic function doByInterval
"round": 1, – Number of displayed decimal places (possible variants 0,1,2,3 – number of decimal places)
"needSave": 0, - Indicates the need to save the main value of the Value of this module element to non-volatile memory, 1-save. 0 - do not save
"map": "1,1024,1,100", - Convert (omap) the main value of the source value from 1 to 1024, convert to values from 1 to 100
"plus": 0 — - Offset. Add the specified number to the main value of Value (the number can be negative)
"multiply": 1, — Multiplier. Multiply the main value of Value by the specified value, if you need to divide, then specify the opposite number. I.e., to divide by 100, you need to specify a multiplier of 0.01.
"btn-defvalue": 0 — - A button displayed in the module configuration with the ability to set a value for processing, the default value is specified. An arbitrary name is specified after the "btn -" prefix (see the button handling function)
"btn-reset": "nil" - A button displayed in the module configuration without passing values. An arbitrary name is specified after the "btn -" prefix (see the button handling function)

Module Description Block

General Description

  "defActive": false, - Parameter indicating whether this module is included in the firmware by default (Used when forming the build settings of the MyProfile project.json)

The "about" block-contains a description of the module

"about": {
    "authorName": "NAME", - Имя автора
    "authorContact": "https://t.me/@NAME", - Контакт автора
    "authorGit": "https://github.com/NAME", - Ссылка на GitHub автора
    "specialThanks": "NAME", - отдельное спасибо другим разработчикам
    "moduleName": " MyModule ",
    "moduleVersion": "0.1", - Версия данного модуля
    "usedRam": { - Память занимаемая данным модулем в платах (узнается экспериментальным методом)
    "esp32_4mb": 15,
    "esp8266_4mb": 15
    },
    "title": "ЗАГОЛОВОК", - Заголовок Модуля
    "moduleDesc": "ОПИСАНИЕ", - Описание модуля.
    "retInfo": "Дополнительная ИНФОРМАЦИЯ", - Дополнительная информация по данному модулю

Element Description Block

"propInfo" - Блок описания параметров всех элементов данного модуля. Здесь разработчик описывает пользовательские параметры добавленные в в блоке configItem
"propInfo": {
   "pin": "Укажите GPIO номер пина для чтения состояний подключенной кнопки",
}

User Function Description block

"funcInfo" – Блок описания пользовательских функций вызываемых в сценарии
    "funcInfo": [
      {
        "name": "get", - Наименование функции, вызов в сценарии например get(“http…”)
        "descr": "Отправить http запрос методом GET.", - Описание функции
        "params": [ - Наименование параметра передаваемого в функцию
          "URL"
        ]
      },
      {
        "name": "post", - Наименование функции, вызов в сценарии например post (“http…”, “ok”)
        "descr": "Отправить http запрос методом POST.", - Описание функции
        "params": [ - Наименование параметров передаваемого в функцию
          "URL","message"
        ]
      }
    ]
  },

Block for connecting external libraries

usedLibs — A block for connecting external libraries. When connecting libraries, it is highly advisable to specify its version, since when updating, PlatformIO will reload the library and it is not a fact that the developers did not redo anything there.
When connecting multiple libraries to a module, they are separated by commas.
It also indicates for which boards this module has been tested and works. If the fee is not specified, the module will not participate in this assembly even if you select it in the project settings MyProfile. json
Example if THERE are NO third-party libraries

  "usedLibs": {
    "esp32_4mb": [],
    "esp8266_4mb": [],
    "esp8266_1mb": [],
    "esp8266_1mb_ota": [],
    "esp8285_1mb": [],
    "esp8285_1mb_ota": [],
    "esp8266_2mb": [],
    "esp8266_2mb_ota": []
  }
}

Example if libraries registered in PlatformIO are used

   "usedLibs": {
        "esp32_4mb": [
            "openenergymonitor/EmonLib@1.1.0"
        ],
        "esp8266_4mb": [
            "openenergymonitor/EmonLib@1.1.0"
        ]
    }

Example if the c GitHub libraries are used

    "usedLibs": {
        "esp32_4mb": [
            "https://github.com/JonasGMorsch/GY-21.git"
        ],
        "esp8266_4mb": [
            "https://github.com/JonasGMorsch/GY-21.git"
        ]
    }

Example of enabling multiple libraries

    "usedLibs": {
        "esp32_4mb": [
            "https://github.com/robotclass/RobotClass_LiquidCrystal_I2C",
            "marcoschwartz/LiquidCrystal_I2C@^1.1.4"
        ]
}

Features of implementing modules


IoTmanager is a modular system, so when developing modules, you must follow the following rules.
If the sensor has several parameters, for example, it can output separately or together temperature, humidity, etc. , then you need to create a separate module element for each sensor parameter,
namely, several blocks configItem and for each sensor element, you need to describe your class in the executable file.
As an example, a sensor Bme280

Library usage method


Since the request to the same library (when connecting a third-party one) comes from different module classes (a class for the temperature element, a class for the humidity element, etc.),
then, to implement the use of the library and its single connection and initialization, we suggest using the following::

1. Define a global pointer to the library, for example

SensirionI2CScd4x *scd4x = nullptr; // create an object of the CSD40 class

2. Define a global library initialization function

// Функция инициализации библиотечного класса, возвращает единственный указать на библиотеку
SensirionI2CScd4x *Scd4x_instance()
{
   if (!scd4x)
   { // Если библиотека ранее инициализировалась, т о просто вернем указатель
       // Инициализируем библиотеку
//Здесь описываем всё, что нужно для инициализации библиотеки
// обычно что прописано в функции Setup() в Arduino
       scd4x = new SensirionI2CScd4x();
       Wire.begin();
       scd4x->begin(Wire);
   }
   return scd4x;
}

3. You can use this function in the code of any module element without thinking about whether there is another element in the configuration and who was the first to call the library and initialize it

//Запрашиваем библиотеку 

       int valT = Scd4x_instance()->getTemp ();

4. Since the above function and pointer are defined as global (before any classes, after defining the #include plug-ins), their names must be specified as unique (by the module name) to avoid conflicts with other modules.
If you use several identical sensors that use the same library and are separated by addresses (for example, i2c), you can use map to store multiple instances of libraries.
See the example in the bme280, bmp280, and Pzem modules.

Supported the project — saved a DIY maker! And we accept gifts...

X