En esta sección se recogen las programaciones desarrolladas para los dispositivos Arduino MKR WAN 1310 utilizados en el proyecto VitiSensor.
El objetivo de este apartado es proporcionar el código necesario para el funcionamiento de los distintos módulos, incluyendo la lectura de sensores, el procesamiento de datos y la comunicación con el gateway + plataformas en la nube.
Cada programa ha sido diseñado de forma modular, facilitando su comprensión, modificación y reutilización. A través de estos códigos se implementan funcionalidades clave como:
Adquisición de datos de sensores ambientales
Gestión de comunicaciones mediante tecnologías IoT (LoRa)
Optimización del consumo energético
Envío de datos a sistemas de almacenamiento y visualización
Además del código, se incluyen indicaciones sobre su configuración, librerías necesarias y parámetros de funcionamiento, permitiendo que cualquier usuario pueda replicar el sistema o adaptarlo a sus propias necesidades.
Cabe aclarar que el bucle loop de Arduino está vacío porque el envío se realiza en el setup, al encenderse el Arduino, porque gracias al temporizador hardware instalado el sistema se enciende durante unos 15 segundos, luego se apaga durante el período fijado en el temporizador (en el presente caso 30 min) v vuelve a comenzar el ciclo (consiguiendo un gran ahorro energético).
Este repositorio de programación forma parte del enfoque del proyecto orientado a la transferencia de conocimiento, facilitando el acceso abierto a soluciones reales desarrolladas en el ámbito de la Formación Profesional.
PROGRAMA DEL MÓDULO CENTRAL
//LoRa//
#include <MKRWAN.h>
#include "arduino_secrets.h"
const int PIN_DONE = 6;
const int PIN_LS = 7;
LoRaModem modem;
String appEui = SECRET_APP_EUI;
String appKey = SECRET_APP_KEY;
uint8_t seq = 0;
int err = 0;
////
//Liberías SHT30
#include <Arduino.h>
#include <SensirionI2cSht3x.h>
#include <Wire.h>
//Variables SHT30
SensirionI2cSht3x sensor;
static char errorMessage[64];
static int16_t error;
float tempAMB = 0.0;
int tempAMBint; //Para enviarlo en forma de entero
float humAMB = 0.0;
int humAMBint; //Para enviarlo en forma de entero
//Liberías pluviómetro
#include "DFRobot_RainfallSensor.h"
//Variables pluviómetro
#define MODE_UART
#ifdef MODE_UART //UART communication
//#include "SoftwareSerial.h"
//SoftwareSerial mySerial(/*rx =*/10, /*tx =*/11);
//DFRobot_RainfallSensor_UART Sensor(/*Stream *=*/&mySerial);
DFRobot_RainfallSensor_UART Sensor(/*Stream *=*/&Serial1);
#else //I2C communication
DFRobot_RainfallSensor_I2C Sensor(&Wire);
#endif
float lluvia1h; //Lluvia acumulada en la última hora
int lluvia1hint; //Para enviarlo en forma de entero
float lluvia24h; //Lluvia acumulada en las últimas 24 horas
int lluvia24hint; //Para enviarlo en forma de entero
const int pinRAD = A1;
float radiacion;
int radiacionint; //Para enviarlo en forma de entero
const int pinVeleta = A2;
int dirV;
const int pinAnemometro = A3;
int velocidadV;
const int pinHayLLUVIA = A4;
int haylluvia;
bool join() {
Serial.println("Joining OTAA...");
int connected = modem.joinOTAA(appEui, appKey);
Serial.print("joinOTAA?: ");
Serial.println(connected);
return connected == 1;
}
void setup() {
Serial.begin(9600);
delay(150);
pinMode(PIN_DONE, OUTPUT);
pinMode(PIN_LS, OUTPUT);
digitalWrite(PIN_DONE, LOW);
digitalWrite(PIN_LS, HIGH);
#ifdef MODE_UART
Serial1.begin(9600);
#endif
Wire.begin();
delay(150);
inicializarSHT30();
if (!modem.begin(EU868)) {
Serial.println("Fallo al iniciar el módulo");
while (1) {}
}
Serial.print("Versión del módulo: ");
Serial.println(modem.version());
Serial.print("EUI: ");
Serial.println(modem.deviceEUI());
if (!join()) {
Serial.println("Error de conexión OTAA. Revisa gateway/downlink/credenciales.");
while (1) {}
}
modem.minPollInterval(60);
//1ª Lectura:
//TyH
leerSHT30();
tempAMBint=100*tempAMB;
humAMBint=100*humAMB;
Serial.print("Temperatura ambiente: ");
Serial.print(tempAMB);
Serial.println(" ºC");
Serial.print("Humedad ambiente: ");
Serial.print(humAMB);
Serial.println("%");
//RAD
radiacion=leerRAD(pinRAD);
radiacionint=100*radiacion;
Serial.print("Radiación: ");
Serial.print(radiacion);
Serial.println(" W/m2");
//VELETA
dirV=leerDIRV(pinVeleta);
Serial.print("Dirección del viento: ");
Serial.print(dirV);
Serial.println("º");
//ANEMOMETRO
velocidadV=leerVViento(pinAnemometro);
Serial.print("Velocidad viento: ");
Serial.print(velocidadV);
Serial.println(" Km/h");
//LLUEVE?
haylluvia=llueve(pinHayLLUVIA);
if (haylluvia==1) Serial.println("LLUEVE");
//PLUVIO
lluvia1h=Sensor.getRainfall(1);
lluvia1hint=100*lluvia1h;
lluvia24h=Sensor.getRainfall(24);
lluvia24hint=100*lluvia24h;
Serial.print("LLuvia 1h: ");
Serial.print(lluvia1h);
Serial.println("mm");
Serial.print("LLuvia 24h: ");
Serial.print(lluvia24h);
Serial.println("mm");
sendPayload(tempAMBint, humAMBint, radiacionint, dirV, velocidadV, haylluvia, lluvia1hint, lluvia24hint);
if (err > 0) {
Serial.println("Mensaje enviado correctamente");
} else {
Serial.print("Error al enviar (endPacket=");
Serial.print(err);
Serial.println("). Intentando rejoin...");
join();
}
delay(2000);
digitalWrite(PIN_LS, LOW); //APAGAMOS LEVEL SHIFTER
delay(3000); //PARA ASEGURARNOS DEL ENVIO DE LOS DATOS ANTES DE APAGAR EL MÓDULO
digitalWrite(PIN_DONE, HIGH); //ACTIVAMOS TPL5110 Y SE APAGARÁ DURANTE INTERVALO FIJADO
}
void loop() {
//SE HACE TODO EN EL SETUP DEBIDO AL TPL5110
}
void sendPayload(int tempAMBTH, int humAMBTH, int radiacionTH, int dirVTH, int velocidadVTH, int haylluviaTH, int lluvia1hTH, int lluvia24hTH) {
static uint8_t seq = 0;
uint8_t flags = 0;
uint8_t payload[18];
payload[0] = highByte(tempAMBTH);
payload[1] = lowByte(tempAMBTH);
payload[2] = highByte(humAMBTH);
payload[3] = lowByte(humAMBTH);
payload[4] = highByte(radiacionTH);
payload[5] = lowByte(radiacionTH);
payload[6] = highByte(dirVTH);
payload[7] = lowByte(dirVTH);
payload[8] = highByte(velocidadVTH);
payload[9] = lowByte(velocidadVTH);
payload[10] = highByte(haylluviaTH);
payload[11] = lowByte(haylluviaTH);
payload[12] = highByte(lluvia1hTH);
payload[13] = lowByte(lluvia1hTH);
payload[14] = highByte(lluvia24hTH);
payload[15] = lowByte(lluvia24hTH);
payload[16] = flags; //...
payload[17] = seq++; //Guarda el número de valores enviados
modem.beginPacket();
modem.write(payload, sizeof(payload));
err = modem.endPacket(false);
}
void inicializarSHT30()
{
sensor.begin(Wire, SHT30_I2C_ADDR_44);
sensor.stopMeasurement();
delay(1);
sensor.softReset();
delay(100);
uint16_t aStatusRegister = 0u;
error = sensor.readStatusRegister(aStatusRegister);
if (error != NO_ERROR) {
Serial.print("Error trying to execute readStatusRegister(): ");
errorToString(error, errorMessage, sizeof errorMessage);
Serial.println(errorMessage);
return;
}
Serial.print("aStatusRegister: ");
Serial.print(aStatusRegister);
Serial.println();
}
void leerSHT30()
{
error = sensor.measureSingleShot(REPEATABILITY_MEDIUM, false, tempAMB, humAMB);
}
float leerRAD(int pinSensor) {
const float VREF = 3.3; // Ajusta si mides otro valor real
const float ADC_MAX = 1023.0; // Resolución que estás usando
const float R = 6800.0; // ohmios
const float S = 0.521e-6; // A/(W/m2)
int adc = analogRead(pinSensor);
float v = adc * VREF / ADC_MAX;
float E = (VREF - v) / (R * S); // Irradiancia en W/m²
if (E < 0) E = 0;
return E;
}
float leerDIRV(int pinSensor) {
int vdir;
int direccionG;
vdir=analogRead(pinVeleta);
direccionG=359*vdir/784;
return direccionG;
}
float leerVViento(int pinSensor) {
int valorADC;
float velocidadViento = 0.0;
valorADC=analogRead(pinSensor);
velocidadViento=map(0,620,0.0,200.0,valorADC);
return velocidadViento;
}
int llueve(int pinSensor) { //Lo hacemos con int en vez de bool por facilitar el envio al modem LoRa
int valorADC;
int umbral=400;
int lloviendo;
valorADC=analogRead(pinSensor);
if (valorADC>umbral) {
lloviendo=1;
}
else {
lloviendo=0;
}
return lloviendo;
}
PROGRAMA DE LOS MÓDULOS PERIFÉRICOS
#include <MKRWAN.h>
#include "arduino_secrets.h"
const int PIN_DONE = 6;
LoRaModem modem;
String appEui = SECRET_APP_EUI;
String appKey = SECRET_APP_KEY;
uint8_t seq = 0;
int err = 0;
////
//Librería comunicación RS-485
#include <ArduinoModbus.h>
const int pinHHoja = A1;
float HUMhoja;
int HUMhojaint; //Para enviarlo en forma de entero
//Variables sensor suelo
int rawTemperaturaSuelo;
int rawHumedadSuelo;
int rawPHSuelo;
int rawEcSuelo;
float temperaturaSuelo;
int temperaturaSueloint; //Para enviarlo en forma de entero
float humedadSuelo;
int humedadSueloint; //Para enviarlo en forma de entero
float pHSuelo;
int pHSueloint; //Para enviarlo en forma de entero
int ecSuelo;
bool join() {
Serial.println("Joining OTAA...");
int connected = modem.joinOTAA(appEui, appKey);
Serial.print("joinOTAA?: ");
Serial.println(connected);
return connected == 1;
}
void setup() {
Serial.begin(9600);
delay(150);
inicializarSen0604();
pinMode(PIN_DONE, OUTPUT);
digitalWrite(PIN_DONE, LOW);
if (!modem.begin(EU868)) {
Serial.println("Fallo al iniciar el módulo");
while (1) {}
}
Serial.print("Versión del módulo: ");
Serial.println(modem.version());
Serial.print("EUI: ");
Serial.println(modem.deviceEUI());
if (!join()) {
Serial.println("Error de conexión OTAA. Revisa gateway/downlink/credenciales.");
while (1) {}
}
modem.minPollInterval(60);
//1º lectura
HUMhoja=leerHUMhoja(pinHHoja);
HUMhojaint=100*HUMhoja;
Serial.print("Humedad hoja: ");
Serial.println(HUMhoja);
Serial.println("%");
leerSen0604();
temperaturaSueloint=100*temperaturaSuelo;
humedadSueloint=100*humedadSuelo;
pHSueloint=100*pHSuelo;
Serial.print("Temperatura suelo: ");
Serial.print(temperaturaSuelo);
Serial.println("ºC");
Serial.print("Humedad suelo: ");
Serial.print(humedadSuelo);
Serial.println("%");
Serial.print("pH suelo: ");
Serial.println(pHSuelo);
Serial.print("Conductividad suelo: ");
Serial.println(ecSuelo);
Serial.println("uOhm/cm");
sendPayload(HUMhojaint, temperaturaSueloint, humedadSueloint, pHSueloint, ecSuelo);
if (err > 0) {
Serial.println("Mensaje enviado correctamente");
} else {
Serial.print("Error al enviar (endPacket=");
Serial.print(err);
Serial.println("). Intentando rejoin...");
join();
}
delay(3000); //PARA ASEGURARNOS DEL ENVÍO DE LOS DATOS ANTES DE APAGAR EL MÓDULO
digitalWrite(PIN_DONE, HIGH); //ACTIVAMOS TPL5110 Y SE APAGARÁ DURANTE INTERVALO FIJADO
}
void loop() {
//SE HACE TODO EN EL SETUP DEBIDO AL TPL5110
}
void sendPayload(int HUMhojaTH, int temperaturaSueloTH, int humedadSueloTH, int pHSueloTH, int ecSueloTH) {
static uint8_t seq = 0;
uint8_t flags = 0;
uint8_t payload[12];
payload[0] = highByte(HUMhojaTH);
payload[1] = lowByte(HUMhojaTH);
payload[2] = highByte(temperaturaSueloTH);
payload[3] = lowByte(temperaturaSueloTH);
payload[4] = highByte(humedadSueloTH);
payload[5] = lowByte(humedadSueloTH);
payload[6] = highByte(pHSueloTH);
payload[7] = lowByte(pHSueloTH);
payload[8] = highByte(ecSueloTH);
payload[9] = lowByte(ecSueloTH);
payload[10] = flags; //...
payload[11] = seq++; //Guarda el número de valores enviados
modem.beginPacket();
modem.write(payload, sizeof(payload));
err = modem.endPacket(false);
}
float leerHUMhoja(int pinSensor) {
int vhumedad;
float Hhoja;
int humedadlimite=400;
vhumedad=analogRead(pinSensor);
Hhoja=100*vhumedad/humedadlimite;
return Hhoja;
//return vhumedad;
}
void inicializarSen0604()
{
Serial.println("Iniciando sensor Modbus");
// start the Modbus RTU client
if (!ModbusRTUClient.begin(9600)) {
Serial.println("Fallo al iniciar");
while (1);
}
}
void leerSen0604()
{
// send a Holding registers read request to (slave) id 1, for 2 registers
if (!ModbusRTUClient.requestFrom(1, HOLDING_REGISTERS, 0x00, 4))
{
Serial.print("No se pueden leer los registros");
Serial.println(ModbusRTUClient.lastError());
}
else
{
rawHumedadSuelo = ModbusRTUClient.read();
rawTemperaturaSuelo = ModbusRTUClient.read();
rawEcSuelo = ModbusRTUClient.read();
rawPHSuelo = ModbusRTUClient.read();
// Hay que dividir los valores raw entre 10
temperaturaSuelo = rawTemperaturaSuelo / 10.0;
humedadSuelo = rawHumedadSuelo / 10.0;
pHSuelo = rawPHSuelo / 10.0;
ecSuelo = rawEcSuelo;
}
}
arduino_secrets.h
A ambos programas debe acompañarles este archivo, arduino_secrets.h (donde almacenaremos las claves fijadas en el gateway).
// Rellenar las claves fijadas en el Gateway
#define SECRET_APP_EUI ""
#define SECRET_APP_KEY ""