El RTU-X puede ejecutar un programa que permite incorporar inteligencia local en el equipo. Este script, que se escribe en la interfaz de usuario en un lenguaje intuitivo y que guarda muchas similitudes con el lenguaje de programación C, permite realizar todo tipo de operaciones con las entradas y salidas, esclavos, log, etc.
A continuación, se describen las características del lenguaje y su utilización en la interfaz de usuario.
El script debe comenzar con la declaración de todas las variables que vayan a utilizarse. No se permite declarar variables en el medio del programa.
Luego de la declaración de variables se debe escribir el código del programa. Este código se ejecuta una única vez, por lo que si se desea que se ejecute continuamente se debe colocar dentro de un bucle infinito.
La sintaxis es la siguiente:
if (Condición1)
{
Instrucciones1
}
else if (Condición2)
{
Instrucciones2
}
else
{
Instrucciones3
}
Si se cumple Condición1
, entonces se ejecuta el bloque Instrucciones1
, de lo contrario si se cumple Condición2
se ejecuta el bloque Instrucciones2
, y de lo contrario se ejecuta el bloque Instrucciones3
.
Los bloques else if
y else
son opcionales.
La sintaxis es la siguiente:
while (Condición)
{
Instrucciones
}
Las instrucciones dentro del bloque while
se ejecutan continuamente mientras se cumpla la Condición
. El bloque de instrucciones puede ser vacío.
La sintaxis es la siguiente:
for (Exp1; Exp2; Exp3)
{
Instrucciones
}
Exp1
es una expresión que solo se ejecuta una vez al principio del bucle. Generalmente Exp1
suele contener una expresión que inicializa el contador utilizado en el bucle for.
Exp2
es la expresión que indica cuando debe finalizar el bucle, y por lo tanto se trata de una expresión condicional. Esta expresión se evalúa al inicio de cada ciclo del bucle, y el bucle deja de ejecutarse cuando esta expresión deja de cumplirse. Por lo tanto el bucle podría no ejecutarse ninguna vez.
Exp3
es una expresión que se ejecuta al final de cada iteración, y generalmente se utiliza para actualizar el contador utilizado en el bucle.
En cada una de las iteraciones, se ejecutan todas las instrucciones dentro del for.
Existen dos tipos de variables, las variables de propósito general y las variables del sistema.
Las variables de propósito general son las definidas por el usuario en el script y pueden ser de uno de los siguientes tipos:
Tipo | Descripción |
int | Entero de 16 bits con signo. Puede ir desde -32.768 a 32.767 |
uint | Entero de 16 bits sin signo. Puede ir desde 0 a 65.535 |
long | Entero de 32 bits con signo. Puede ir desde -2.147.483.647 a 2.147.483.647 |
ulong | Entero de 32 bits sin signo. Puede ir desde 0 a 4.294.967.295 |
float | Real con coma flotante de 32 bits. Representación IEEE 754 de precisión simple |
Las variables de propósito general pueden definirse como arrays.
Algunos ejemplos de definición de variables:
int i; // Variable de tipo int llamada i
float f; // Variable de tipo float llamada f
int a[10]; // Array de 10 lugares
Cualquier variable de propósito general puede ser definida con el prefijo telemetry
, attribute
o shared
.
Las variables se deben definir como telemetry
o attribute
para poder ser utilizadas con las funciones log
y report
.
El prefijo shared
se utiliza exclusivamente cuando el RTU-X se integra por MQTT al sistema Telemetry+ de Nettra, para el caso de variables que guardan parámetros de configuración que se ajustan desde el sistema.
Algunos ejemplos de definición de variables:
attribute float f; // Variable float de tipo type attribute llamada f
telemetry int t; // Variable entera de tipo telemetry llamada t
Cualquier variable de propósito general puede ser definida con el prefijo static
.
Al definirla de esta forma, la variable se almacena en memoria no volátil del RTU-X, de tal forma que su valor persista incluso en caso de pérdida de alimentación y batería.
Para darle un valor inicial a una variable static
, se la debe inicializar al declararla. De esta manera, el valor inicial solo se carga en la primera ejecución del script, pero no en las siguientes ejecuciones.
Por ejemplo:
static int a = 10;
static int b[3] = {1,2,3};
Existe un conjunto de variables predefinidas relacionadas al funcionamiento del RTU-X y sus periféricos que pueden ser utilizadas desde el script.
Las siguientes variables se encuentran disponibles:
Variable | Descripción | Tipo |
ENTRADAS | ||
ain | Entradas analógicas Representa el voltaje en mV (0 – 10.000) para entradas de tensión o la corriente en uA (0 – 20.000) para entradas de corriente. | int[8] |
pulses | Conteo de pulsos de las entradas digitales | ulong[2] |
MODBUS | ||
slave_error | Refleja el estado de conexión entre la RTU-X y los esclavos. Se usa un bit por esclavo, donde 1 indica error. Por ejemplo, el bit 2 de la variable representa el estado de conexión con el esclavo 2. | int |
TIEMPO | ||
hours | Hora actual (0 – 24) | uint |
minutes | Minutos actuales (0 – 59) | uint |
seconds | Segundos actuales (0 – 59) | uint |
day | Día del mes (0 - 31) | uint |
month | Mes actual (1 – 12) | uint |
year | Año actual | uint |
week_day | Día de la semana (0 = domingo) | uint |
time_synced | Indica si la hora se encuentra sincronizada. La variable puede valer 0 o 1. | int |
unix_ts_utc | Cantidad de segundos en formato Unix de la hora UTC del dispositivo | ulong |
unix_ts_local | Cantidad de segundos en formato Unix de la hora local del dispositivo | ulong |
SMS | ||
sms_message |
Índice del mensaje recibido. Si no hay mensajes recibidos, la variable vale -1. Luego de recibir y procesar un mensaje, el usuario debe poner esta variable nuevamente en -1. |
int |
sms_parameter | Parámetro del mensaje recibido | float |
sms_phone | Índice del teléfono que envió el mensaje recibido | int |
MODEM | ||
modem_on | En caso de que el módulo WAN no esté configurado para encenderse automáticamente al encenderse la RTU-X, poniendo esta variable en 1 o 0 permite encender o apagar el módem desde el script. | int |
modem_status |
Refleja el estado de funcionamiento del módem. Los posibles valores son:
|
int |
modem_signal | Nivel de señal del módem en dBm | int |
LORA | ||
lora_on | En caso de que el módulo WAN no esté configurado para encenderse automáticamente al encenderse la RTU-X, poniendo esta variable en 1 o 0 permite encender o apagar el módulo LoraWan desde el script. | int |
lora_status |
Refleja el estado de funcionamiento del módulo LoraWan. Los posibles valores son:
|
int |
lora_snr | Relación señal a ruido en dB medida por el módulo LoraWan en la última transmisión | int |
lora_rssi | Potencia de señal a ruido en dBm medida por el módulo LoraWan en la última transmisión | int |
WIFI | ||
wifi_status |
Refleja el estado de funcionamiento del wifi. Los posibles valores son:
|
int |
wifi_signal | Nivel de señal del wifi en dBm | int |
MQTT | ||
mqtt_status |
Refleja el estado de la conexión MQTT. Los posibles valores son:
|
int |
mqtt_pending_log | Cantidad de registros pendientes de ser enviados por mqtt. | ulong |
GPS | ||
latitude | Latitud del GPS | float |
longitude | Longitud del GPS | float |
altitude | Altitud del GPS | float |
speed | Velocidad del GPS | float |
BATERÍA | ||
battery_percentage | Porcentaje de batería | uint |
battery_protection | La bateria interna del RTU-X cuenta con una protección contra sobre calentamiento que corta el proceso de carga cuando la temperatura supera los 40°C aproximadamente. Si bien esta protección es necesaria, cuando la temperatura ambiente es alta se genera el efecto no deseado de corte del proceso de carga. Con valor 1 (por defecto) la protección está habilitada mientras que con valor 0 la protección se deshabilita. | int |
battery_status |
Refleja el estado de funcionamiento de la batería. Los posibles valores son:
|
int |
SCANNER | ||
scanner_wifi_current_count | Cantidad actual de dispositivos wifi | uint |
scanner_wifi_total_count | Cantidad total de dispositivos wifi | uint |
scanner_bluetooth_current_count | Cantidad actual de dispositivos bluetooth | uint |
scanner_bluetooth_total_count | Cantidad total de dispositivos bluetooth | uint |
Se puede acceder a cualquier bit individual de cualquier variable colocando un punto y el número de bit luego del nombre de la variable.
Ejemplo que guarda el bit 2 de la variable x en el bit 3 de la variable y:
y.3 = x.2;
Es posible utilizar un alias para referirse tanto a las variables de propósito general como a las variables del sistema. Se puede definir un alias a cualquier variable, a un elemento de un array o incluso a un bit de una variable.
Los siguientes ejemplos muestras la definición de diferentes alias:
alias error as slave_error.2; // Alias al error del esclavo 3
alias analogica2 as ain[1]; // Alias a la entrada analógica 2
Si se van a utilizar constantes en el script, éstas pueden definirse también al comienzo del mismo. Por ejemplo, para definir una constante llamada UMBRAL
cuyo valor sea 30 se debe hacer lo siguiente:
const UMBRAL = 30;
El compilador remplazará la palabra UMBRAL
por el valor 30 siempre que ésta aparezca en el script.
Al igual que en el caso de las variables, existe un conjunto de constantes predefinidas relacionadas al funcionamiento del RTU-X y sus periféricos que pueden ser utilizadas desde el script cuando sea pertinente.
Operación | Descripción |
+ | Suma |
- | Resta |
* | Multiplicación |
/ | División |
% | Resto de la división entera |
& | AND (bit a bit) |
| | OR (bit a bit) |
^ | XOR (bit a bit) |
~ | NOT (bit a bit) |
&& | AND lógico |
|| | OR lógico |
! | NOT lógico |
== | Igual |
! = | Distinto |
> | Mayor |
> = | Mayor o igual |
< | Menor |
< = | Menor o igual |
El RTU-X puede funcionar como maestro de hasta 32 dispositivos (a partir de la versión de firmware 3.3.02). Actualmente, estos esclavos pueden ser Modbus sobre RS-485 o BLE (Bluetooth).
Para cada esclavo se pueden definir consultas. Los tipos de consulta dependen del tipo de esclavo.
La configuración de los esclavos y las consultas se debe hacer al principio del script, antes de la definición de las variables y el código.
Para definir un esclavo se usa la palabra reservada slave
, indicando los parámetros de configuración, y luego dentro del bloque de ese esclavo se definen las consultas usando la palabra reservada query
.
A continuación, se muestra la forma general de definir un esclavo y sus consultas:
slave(interface, …)
{
var_type var1 = query(query_type, …);
var_type var2 = query(query_type, …);
}
Los parámetros de las funciones slave
y query
dependen de la interface y query_type, y a continuación se detalla cada caso.
La definición del esclavo se realiza de la siguiente forma:
slave(modbus_rs485_ext1, slave_id, polling_period, format)
slave(modbus_rs485_ext2, slave_id, polling_period, format)
donde:
slave_id
: es el número de slave id Modbus del esclavopolling_period
: es el intervalo de tiempo entre dos consultas consecutivas, en segundosformat
: puede ser little_endian
o big_endian
. Define la forma en que se ordenan los bytes para variables de más de un byte. Lo más usual es little_endian
.La definición de las consultas se realiza de la siguiente forma:
var_type var1 = query(coils, address, r/w/rw);
var_type var2 = query(inputs, address);
var_type var3 = query(input_registers, address);
var_type var4 = query(holding_registers, address, r/w/rw);
donde:
address
: es la dirección dentro del bloque Modbus (comenzando en cero).r/w/rw
: indica si la variable a definir es solo lectura (r
), solo escritura (w
) o lectura y escritura (rw
).
slave(ble, “mac”, timeout)
donde:
mac
: es la dirección mac del dispositivo Bluetooth.timeout
: es el tiempo que debe pasar, en segundos, sin recibir ningún mensaje para considerar que hay un error de comunicación con el esclavo.Hasta el momento se ha integrado la posibilidad de definir esclavos de sensores BLE de marca Efento.
La definición de las consultas se realiza de la siguiente forma:
float var1 = query(efento, temperature); // Consulta de temperatura
uint var2 = query(efento, humidity); // Consulta de humedad relativa
float var3 = query(efento, pressure); // Consulta de presión atmosférica
uint var4 = query(efento, onoff); // Consulta de sensores on/off
uint var5 = query(efento, iaq); // Consulta de calidad de aire
slave(sdi12_ext1,“address”, polling_period)
slave(sdi12_ext2,“address”, polling_period)
donde:
address
: es la dirección del dispositivo SDI-12 en el bus.polling_period
: es el tiempo entre consultas al sensor.La definición de las consultas se realiza de la siguiente forma:
float var = query(sdi12, index);
Las consultas a los sensores se implementan mediante comandos C de acuerdo a la versión 1.4 del estándar SDI-12, publicado en Enero de 2019. Donde se establece que el formato del comando de inicio de medición es de la forma <address>C<index>!
Todas las variables que resultan de un query de sdi12
deben ser declaradas como float
.
El lenguaje soporta la definición de varias tareas que se ejecutan en forma de multitarea cooperativa.
En un esquema de multitarea cooperativa, las tareas ceden voluntariamente el control en forma periódica o cuando están inactivos o bloqueados lógicamente.
La forma en que se implementa la multitarea en el script es mediante el uso de la palabra reservada task
.
Al llegar a una tarea, la misma se ejecuta hasta terminar o ceder el control. La forma de ceder el control es mediante el uso de funciones alguna de las siguientes funciones: wait
, delay
, delay_loop
. Estas funciones se explican más adelante.
A continuación, un ejemplo de código donde conviven dos tareas ejecutándose "en paralelo":
while (1)
{
task
{
// Esta sección de código se ejecuta periódicamente cada 1 segundo
delay(1000);
}
task
{
// Esta sección de código se ejecuta periódicamente cada 3 segundos
delay(3000);
}
}
No está permitido definir una tarea dentro de otra tarea.
Todas las variables son globales. No está permitido definir variables locales dentro de una tarea.
El máximo número de tareas que se pueden definir es 16.
Función | Descripción | |
delay(ulong time) |
Realiza una espera de time milisegundos. Ejemplo:
|
|
delay_loop(ulong time) |
Al igual que la funcion Ejemplo:
|
|
sleep(ulong time) |
Hace que el RTU-X pase a modo bajo consumo por time milisegundos. Ejemplo:
|
|
reset() |
Reinicia el RTU-X. Ejemplo:
|
|
set_green_led(uint mode) |
Esta función es válida solo en el caso que el led verde esté configurado para ser controlado desde el script. Ejemplo:
|
|
set_red_led(uint mode) |
Esta función es válida solo en el caso que el led rojo esté configurado para ser controlado desde el script. Ejemplo:
|
|
set_output(uint output, uint value) |
Enciende o apaga la salida digital output dependiendo de value (value puede valer 1 o 0). Ejemplo:
|
|
int = get_output(uint output) |
Devuelve el valor (0 o 1) de la salida output. Ejemplo:
|
|
int = get_input(uint input) |
Devuelve el valor (0 o 1) de la entrada input. Ejemplo:
|
|
set_power(uint value) |
Apaga o enciende la salida de alimentación dependiendo de value. Ejemplo:
|
|
log(telemetry/attribute value, ...) |
Guarda las variables especificadas en el log. Para ello deben ser de tipo Ejemplo:
|
|
report(telemetry/attribute value, ...) |
Guarda las variables especificadas en una lista en RAM para enviar por MQTT. Ejemplo:
|
|
log_on_change(telemetry/attribute value) |
Guarda las variable especificada en el log solamente cuando la variable cambia. La variable debe ser de tipo Ejemplo:
|
|
report_on_change(telemetry/attribute value) |
Guarda la variable especificada en el log de RAM para enviar por MQTT solamente cuando la variable cambia. La variable debe ser de tipo Ejemplo:
|
|
ulong = set_timeout(ulong timeout) |
Configura un timer para hacer timeout en timeout milisegundos. Devuelve un valor ulong configurado para utilizar luego en la función check_timeout . |
|
check_timeout(ulong timer) |
Devuelve 1 si pasó el tiempo configurado con la función Ejemplo:
|
|
send_sms( uint phone_index, uint message_index, long/float param1, long/float param2) |
Envía un SMS al número configurado en el índice Los mensajes salientes pueden incluir hasta 2 parámetros numéricos en cualquier lugar del mensaje. Para insertar una variable entera (int o long) deberá escribir
Ejemplo:
|
|
float = flow( bool value, float liters_per_pulse, ulong debounce) |
La función calcula el caudal de agua a partir de los pulsos de un caudalímetro. Se debe llamar pasando en value el valor de la entrada de pulsos, en liters_per_pulse la cantidad de litros entre pulsos del caudalímetro y un tiempo debounce en milisegundos para filtrar rebotes y ruido en el caudalímetro. |
|
float = filter( float value, uint size, uint average, ulong timeout) |
Esta función implementa un filtro de mediana y promedio. El RTU-X dispone de 8 filtros idénticos que se pueden utilizar simultáneamente. El parámetro El parámetro La función implementa una ventana deslizante con Durante las primeras Ejemplo:
|
|
int = wait( bool condition, ulong timeout) |
La función espera a que se cumpla Ejemplo:
|
|
scale( float value, float x0, float y0, float x1, float y1) |
Realiza una interpolación lineal de Ejemplo:
|
|
float = pid( float value, float set_point, float kp, float ki, float kd) |
Implementa un control PID, donde ![]()
Ejemplo:
|
|
bool = alarm( bool condition, ulong timeout_start, ulong timeout_end) |
Permite implementar una alarma con retardo de comienzo y fin. Si condition se cumple por más de timeout_start milisegundos, se pone en estado de alarma. Si condition deja de cumplirse por más de timeout_end milisegundos, finaliza el estado de alarma. La función siempre devuelve 0 o 1 si se encuentra en estado de alarma o no. |
|
bool = interval( ulong value, ulong start, ulong end) |
Verifica si value se encuentra dentro del intervalo entre start y end .Si start <= end , entonces simplemente verifica que start <= value < end , y devuelve 1 si se cumple o 0 si no.Si start > end , entonces verifica que value >= start o value < end , y devuelve 1 si se cumple cualquiera de las dos condiciones o 0 si no. |
|
ulong = sunrise( ulong day, ulong month, ulong year, float latitude, float longitude) |
Por medio de un reloj astronómico interno, devuelve el segundo del día en el que saldrá el sol en función de la fecha ( day , month , year ) y la posición (latitude , longitude ). Devuelve como ulong el instante en segundos del día en hora local. El segundo actual se puede calcular como: actual = hours * 3600 + minutes * 60 + seconds; En combinación con la función interval y sunset , se puede saber fácilmente si es de día o de noche. |
|
ulong = sunset( ulong day, ulong month, ulong year, float latitude, float longitude) |
Por medio de un reloj astronómico interno, devuelve el segundo del día en el que se ocultará el sol en función de la fecha ( day , month , year ) y la posición (latitude , longitude ). Devuelve como ulong el instante en segundos del día en hora local. El segundo actual se puede calcular como: actual = hours * 3600 + minutes * 60 + seconds; En combinación con la función interval y surise , se puede saber fácilmente si es de día o de noche. |
|
pow(float x,float y) |
Devuelve el resultado de elevar x a la y . |
|
log_e(float x) |
Devuelve el logaritmo base e de x . |
|
log_10(float x) |
Devuelve el logaritmo base 10 de x . |
|
cos(float x) |
Devuelve el coseno de x . Con x en radianes. |
|
acos(float x) |
Devuelve el arco-coseno de x en radianes. |
|
sin(float x) |
Devuelve el seno de x . Con x en radianes. |
|
asin(float x) |
Devuelve el arcoseno de x en radianes. |
|
tan(float x) |
Devuelve la tangente de x . Con x en radianes. |
|
atan(float x) |
Devuelve el arcotangente de x en radianes. |
|
sqrt(float x) |
Devuelve la raíz cuadrada de x . |
|
abs(float x) |
Devuelve el valor absoluto de x . |