Skip to content

Getting Started for Developers

This guide will help you understand how the rhomb.IoT source code was built, its file structure and how the application works in general.

It is important that you first read the Getting Started For Users and the other user documentation to create a development environment.


Before Start

This guide for developers assumes that you have read the user guide and that you have used rhomb.IoT as a user at some point. It also assumes that you have worked with and are familiar with VS Code and PlatformIO.

File Structure

All the source code is inside the directory rhomb.iot. This is a list of the most important files:

Folder / File Module Description
./batt/ Battery Reads voltages of VBAT, VIN and VSYS. The info is added to the messages for the server and also used by sleep module to enable low and sleep modes.
./bl/ Bluetoorh Allows to work with different Bluetooth modules.
./conf/ Conf It contains the main configuration file and libraries to manage the configuration.
./core/ Core Main header files of the library. It contains other modules such as Debug, or Tempo among others
./gpio/ GPIOs Control for GPIOs
./gps/ GPS Allows to work with differente GPS modules.
./log/ Data Logger Allows to work with different data loggers
./modems/ Modems It contains generic functions of a particular modem. For example the SIM868 has GPS, GPRS and Bluetooth. The modem-sim800.h library contains functions that can be used by all 3 modules.
./msg/ Message Library for the creation of messages
./net/ Net Allows to work with different modems to connect to internet
./sensors/ Sensors Contains other libraries to manage sensors for temperature, humidity and so on
./serial/ Serial Library to work with serial ports
./storage/ Storage Different modules to persist data configuration
./rhoimb.iot.h Main header file It is included in the src/main.cpp file of the project

The main header file is rhomb.iot.h. This file is included in src/main.cpp. The rest of the directories are modules of rhomb.IoT. Each module can be composed by one or several headers.

For example, the net module contains libraries to work with the GPRS SIM868 modem (net/net-800.h) and the ESP32 Wifi modem (net/net-esp32.h). In this case, as there are several libraries, there is also a main module file, which we call the director (net/net.h), and it is the one in charge of selecting which library should be included in the code according to the configuration we have chosen in our configuration file.

The main header file: rhomb.iot.h

/// @file rhomb.iot.h
#pragma once

#define RHOMBIOT_VERSION "alpha-3"

// The order of factors sometimes matters
#include "core/utils.h"
#include "core/types.h"
#include "conf/conf-user.h"
#include "core/prototypes.h"
#include "core/global.h"
#include "core/names.h"
#include "core/rhio-pinmap.h"
#include "core/puf.h"
#include "serial/serial.h"
#include "core/tempo.h"
#include "core/debug.h"
#include "core/errors.h"
#include "core/sync.h"
#include "conf/cfg.h"
#include "storage/storage.h"
#include "conf/conf.h"
#include "gpio/gpio.h"
#include "core/delay.h"
#include "core/interruption.h"
#include "core/at.h"
#include "core/device.h"
#include "msg/msg.h"
#include "log/log.h"
#include "batt/batt.h"
#include "gps/gps.h"
#include "net/net.h"
#include "msg/msg-login.h"
#include "sensors/sensors.h"
#include "core/opmode.h"
#include "core/rhio-pinmap.h"

namespace core {

void setup() {

void loop() {
  puf::emit(EV_BEFORE_LOOP, 0);
  puf::emit(EV_AFTER_LOOP, 0);

}  // namespace core

The source code is commented in the original file. After including all the required headers we are creating two wrapping functions for the arduino main methods setup() and loop(). This doesn't mean that we can't use these methods in the main.cpp, in fact they are used, simply when the Arduino library calls those methods we will take control with ours.


You can see the puf library inside the loop() function. It is an important part of the code which allow us to implement and event driven architecture. We'll explain it later, keep your eyes open :)

The main.cpp file of our application:

/// @file src/main.cpp
#include <Arduino.h>
#include <Wire.h>
#include "rhomb.iot.h"

void setup() {

void loop() {

Simple enough? rhomb.IoT includes all the libraries it needs to work. In the setup() rhomb.IoT configures what is needed (using the configuration file as reference). Then it uses the Arduino loop to check the status of timers and interruptions at all times.

Configuration File config-user.h

If you have used rhomb.IoT you should already know this file. It is the main file where all the configuration of the application is stored. It is on conf/conf-user.h. Depending on the data in this file the core::setup() function will perform some actions or others.

You have to be careful when modifying this file manually. A wrong parameter can make the application stop working and debugging the error can be complicated (the voice of experience is speaking).

For more info read the User Configuration File guide.

Life cycle of the application

rhomb.IoT is an application designed to read data from sensors, creating messages with this data and transporting the information to a web server (using our communication protocol) or data logger.

An overview of the flow or application life cycle

In front of the Arduino loop is the opmode library, which will perform different tasks depending on the state of the application. Read more about the module opmode.

Most of the time we will be in normal opmode, reading data from sensors, creating messages and sending them to the server.

A simplified overview of message creation

  • The creation of messages is done through the TICK1 time interval, configured in seconds in the config-user.h file.
  • The main mission of the normal opmode is to check this interval and launch the message creation event EV_CREATE_MSG.
  • The modules listen to this event and read their sensors to add the information to the message.
  • When the message has been created, the msg module triggers the event EV_MSG_CREATED and the net module reacts by transmitting the message to the server or keeping it in the log.

Launching and listening to events is how the different modules communicate with each other.

Look at this example of the init() function in the gps-l86.h module

uint8_t init() {
  puf::on(EV_CREATE_MSG, onCreateMessage);

  const uint8_t callbacksSize = 6;
  uint8_t (*callbacks[callbacksSize])() = {addLocation, addAltitud, addSOG,
                                           addCOG,      addSats,    addHDOP};
  each.add(callbacks, callbacksSize);

  return 0;

The gps-l86.h module makes a subscription to the EV_CREATE_MSG event using the puf::on() method:

puf::on(EV_CREATE_MSG, onCreateMessage);

Meanwhile the msg module (msg/msg.h) will trigger the EV_CREATE_MSG event on the method msg::createMessage():

uint8_t createMessage(uint8_t param) {
  uint8_t err = puf::emit(EV_CREATE_MSG, EV_CUSTOM_MSG_SENSING);
  if (err) return err;
  puf::emit(EV_MSG_CREATED, 0);