Leer puerto serie y almacenar en variable

Hola a tod@s, estoy metido en un proyecto para controlar un rotor de antena, para hacer seguimiento de satélites.
Soy novato en programación y aunque con esfuerzo he conseguido mover los motores de forma manual a través de un arduino uno y leer en el display los grados de apuntamiento vertical y horizontal.
Ahora intento leer el puerto serie, ya que hay software de seguimiento automático para hacer a través del PC.
Intento leer los datos de orientación y elevación en grados que me da el software, y guardarlos en una variable para que el apuntamiento sea automático, pero no se como darle forma al código.
El formato de datos que tengo que leer este....

W 1 7 5 0 3 5 .

Lo he leído con un monitor de puerto serie.
La W aparece siempre como principio de linea, el primer dato son los grados horizontales y el segundo dato, los grados de elevación.
El punto creo que es el retorno de linea.

¿Podéis por favor, echarme una mano para explicarme como tengo que leer esos datos?
Gracias de antemano

Hola:

¿Quieres controlar todo con una interfaz desde un ordenador o PC a Arduino por el puerto serie?

Busca en el buscador tutoriales de Metaconta, he hecho mucho de ello y dentro de unas horas presentaré otro tutorial en Documentación de este foro.

Al menos di que lenguaje y tecnología usas, a parte poner tu código de Arduino para ayudarte mejor.

Saludos.

Ante todo mucha gracias por tu respuesta Metaconta, buscare en los tutoriales que me dices.
Mas abajo pego el código, pero no verás nada respecto al puerto serie, porque es que no se que tengo que poner.

He visto muchos ejemplos en los tutoriales y he leido en un libro de Arduino que he comprado, pero lo que hacen es encender un led, leer el echo que envías a la placa, etc. Pero no he encontrado nada donde pueda entender como hacer lo que necesito, que es grabar y leer ordenadamente la buffer.
Antes que nada pedir disculpas porque el código seguro que se puede mejorar muchísimo, y estructurarlo mejor, pero es lo primero que hago llamemos serio y me queda mucho por aprender.

El programa de seguimiento de satélites me envía esta trama W 2 2 7 0 4 5 .
La W siempre es el comienzo de linea seguido de tres espacios entre los valores de orientacion horizontal, y ocho espacios que separan los datos de orientación vertical, mas los datos de vertical separados por tres espacios.

Lo que me gustaría hacer, es leer los valores pero no se como hacerlo.

Si almaceno en tres variable para horizontal y otras tres, o dos, para vertical porque el valor máximo serán 90, luego las leo por orden.
A la primera variable de horizontal la centena, la leo y le sumo 100, la decena la leo y le sumo 10 y a la unidad no le hago nada. Luego sumo todo.
Por ejemplo variable = (2+100)+(2+10)+7, si lo sumo todo son 227 que es valor que me te pongo como ejemplo, y lo mismo haría con los datos verticales.
Con la declaración “chart” puedo leer un carácter, pero como los leo ordenados habiendo espacios entre ellos?
Y como diferencio entre datos horizontales y verticales.
Os pido por favor, que me ayudéis para poder aprender a hacerlo y terminar el proyecto
Gracias de antemano.

Control_Rotor_230717.ino (9.74 KB)

¿Por qué espacio entre dígitos? ¿No sería más fácil que la trama tuviera el siguiente formato?

W###,###.

De esta forma extraer la información sería tan simple como:

char n[4]; // Tamaño de 4 asumiendo que máximo solo habrán tres dígitos y valores positivos
while (Serial.read() != 'W'); // Buscar el inicio de la trama

n[Serial.readBytesUntil(',', n, sizeof(n) - 1)] = 0; // Leo la primera cifra
unsigned int horizontal = atoi(n); // Conversión de texto a valor

n[Serial.readBytesUntil('.', n, sizeof(n) - 1)] = 0; // Leo la segunda cifra
unsigned int vertical = atoi(n); // Conversión de texto a valor

Gracias por tu respuesta Lucario.
El protocolo de comunicación del sotware de seguimiento, es el propio el programa que se instala en el PC, no es posible modificarlo.
Cuando hablo de espacios, es porque los veo como tal en el terminal del puerto serie.

¿Sabes cómo imprimir en hexadecimal? Necesito saber el valor de cada caracter de la trama.

Lo que parece un espacio podría ser más que eso (un tabulador horizontal, un cambio de línea incompleto o incluso cualquier otro caracter no imprimible).

Gracias por tu respuesta Lucario, tengo algunos datos que facilitar......
Con este código consigo imprimir en el display y ver en terminal serie los datos que envía el software de seguimiento.

//Incluye el codigo de la biblioteca
#include <LiquidCrystal.h>
//Inicializa la biblioteca con los numeros de los pines de interfaz
LiquidCrystal lcd(2, 3, 4, 5, 6, 7);

char c = 0;         // incoming serial byte

void setup() {
 
// Configura el numero de columnas y filas de la pantalla LCD
lcd.begin(16, 2);
  
// start serial port at 9600 bps:
Serial.begin(9600);
while (!Serial) 
 {
   ; // wait for serial port to connect. Needed for native USB port only
 }

}
void loop() {

if (Serial.available()>0)
{
 c = Serial.read(); //Leer 1 carácter
 delay(25);
 lcd.print (c);
 Serial.print(c);
}
}

El formato no es como parecía al principio, lo que leo es lo siguiente

W245 045

La W como cabecera, el primer valor orientación y el segundo elevación.
Ahora intento crear un String para guardar los valores y luego poderlos visualizar, pero algo o todo lo hago mal.
Adjunto el código por si me podéis corregir mis errores

String_puerto_serie.ino (1.22 KB)

Entones sí funciona lo que propuse, solo hay que cambiar los caracteres terminadores:

char n[4]; // Tamaño de 4 asumiendo que máximo solo habrán tres dígitos y valores positivos
while (Serial.read() != 'W'); // Buscar el inicio de la trama

n[Serial.readBytesUntil(' ', n, sizeof(n) - 1)] = 0; // Leo la primera cifra
unsigned int horizontal = atoi(n); // Conversión de texto a valor

n[Serial.readBytesUntil('\r', n, sizeof(n) - 1)] = 0; // Leo la segunda cifra
unsigned int vertical = atoi(n); // Conversión de texto a valor

Muy agradecido Lucario, acabo de probarlo y funciona bien.
Durante toda la órbita de un satélite, aprox 12 min, no ha fallado ningún dato.
Ahora lo integraré con el código que ya tengo hecho y os lo pasaré por si hay alguien a quien le pueda interesar.
Hay algo que me ha llamado la atención, y es que no he tenido que declarar la variable (horizontal) ni (vertical). Tiene algo que ver que le precede en el código "unsigned"?
Por otro lado y sin ánimo de abusar, solo intento ir aprendiendo poco a poco, ¿podrías explicarme esta parte del código?

(' ', n, sizeof(n) - 1)] = 0

En tu comentario indicas que es para leer la primera cifrar, ¿pero a que corresponde cada carácter?
Entendiéndolo, creo que podré usarlo en el futuro cuando tenga otras necesidades.

Gracias nuevamente.

Excelentes preguntas...

ea7ln:
Hay algo que me ha llamado la atención, y es que no he tenido que declarar la variable (horizontal) ni (vertical). Tiene algo que ver que le precede en el código "unsigned"?

Imposible referenciar una variable que nunca ha sido declarada, e imposible declararla más de una vez (cuando los nombres coinciden).
La palabra reservada "unsigned" es solo para darle una característica extra un tipo de dato de número entero: no molestarse con lidiar con el signo (sin negativos). Para efectos del programa hubiera sido igual con o sin esta palabra; sin embargo se usa para evitar el sobrecargo de cuidarse con el signo donde por contexto no se necesitan representar valores negativos. ¿Cierto o no que cuidar los signos es un dolor de cabeza al resolver ecuaciones en clase de matemáticas?

ea7ln:
Por otro lado y sin ánimo de abusar, solo intento ir aprendiendo poco a poco, ¿podrías explicarme esta parte del código?

(' ', n, sizeof(n) - 1)] = 0

En tu comentario indicas que es para leer la primera cifrar, ¿pero a que corresponde cada carácter?

Genial que lo preguntes, porque algunos se quedan con la mediocre idea de "si funciona, ¿por qué debo molestare en entenderlo?". En detalle sería así:

n[Serial.readBytesUntil(' ', n, sizeof(n) - 1)] = 0; // Leo la primera cifra

Debido a que los caracteres se codifican como ASCII, entiéndase caracter y byte como a la misma unidad básica para esta explicación.

Entiéndase por n como el nombre de una cadena de caracteres temporal.

Entiéndase por [] como índice del elemento al cual queremos accesar (adentro se coloca el equivalente a un número entero, inicia en cero).

Entiéndase por Serial a la referencia del objeto que manipula el puerto serial del Arduino.

Entiéndase por readBytesUntil a la función que recupera una secuencia de bytes desde un flujo ("stream" en inglés), y las almacena en un espacio contiguo de memoria llamado "vector" (a veces erróneamente llamado "arreglo" debido a la similitud en el nombre en inglés: "array"). Esta función se detiene cuando alguna de las siguientes se cumple:

  • El último valor recuperado coincide con el del primer parámetro (en este caso al caracter del espacio en blanco, con un valor decimal de 32).
  • La cantidad de bytes recuperados alcanzó el valor del tercer parámetro.
  • El flujo ya no tiene más bytes para leer y además se superó el tiempo de espera ("timeout" en inglés). Para efectos del programa, esto no debería ocurrir durante la lectura o de lo contrario se nos convierten números incompletos.

Retorna, en forma de int, la cantidad de bytes que realmente fueron leídos. Dicho valor es aprovechado para determinar un índice.

Entiéndase por ' ' como al caracter que debería poner fin a readBytesUntil. El delimitador se excluye del resultado.

Entiéndase por sizeof a la "función del compilador" que retorna, en bytes, el espacio de memoria que ocupa una estructura de datos. El espacio por cantidad de elementos, es relativo al tipo de dato que maneja.
Se dice "del compilador" debido a que la secuencia de instrucciones que ejecuta, nunca es parte del código compilado; sino que el resultado se coloca implícitamente como reemplazo donde fue llamada. Por ejemplo:

Lo que en el código fuente se ve:

n[Serial.readBytesUntil(' ', n, sizeof(n) - 1)] = 0; // Leo la primera cifra

Lo que el compilador ve (en parte):

n[Serial.readBytesUntil(' ', n, 3)] = 0;

Eso último fue una ligera simplificación ya que el compilador no ve estructuras de datos, sino punteros de memoria. Lo más realista es que lo vea así:

*(n + Serial.readBytesUntil(' ', n, 3)) = 0;

Entiéndase por = 0 como al valor que se le va a asignar a esa posición. Estamos hablando del terminador de cadenas de caracteres ('\0' == 0); sin este, quien sabe cuanta "basura" llega a formar parte de la cadena.

Se utiliza sizeof(n) - 1 debido a la obligatoriedad del terminador, y de que además es mala práctica modificar valores fuera de un vector. Si n es de tamaño 4, es por eso que advertí sobre de que los números tengan máximo tres dígitos y sean positivos.

En este ejemplo puntual, no se pueden permitir negativos por tres razones:

  • Tu sabes que para representar negativos necesitamos un caracter adicional: el guión ('-'); por lo tanto, no alcanza para el vector.
  • atoi aunque pueda manejar números negativos, juntarlos con variables unsigned no es la mejor idea. ¿Valores entre 32768 y 65535 son válidos son para tu programa?
  • De todas formas por contexto ni siquiera son necesarios los números negativos.

En resumen: recupera las partes que nos interesan (descartando los delimitadores), para luego convertirlos en variables.

Perdona que te haya inundado de tanta información, asimílala lento y con calma. Si te quedaste perdido en alguna parte, no dudes en preguntar por acá :wink:

Hola Lucario448, me ha gustado la forma en que has solucionado la lectura de los números por el puerto serie en una sola línea. Me gustan las soluciones "creativas". Aunque ya sabemos que deberíamos de hacer un código más legible, pero aveces puede quedar más ofuscado si tratas de hacerlo paso a paso. Y me ha gustado más aún que hayas explicado cómo funciona.

Independientemente de lo ofuscado del código, hay algo que me tiene mosca y de lo que me di cuenta cuando "desentrañé" tu línea de código para entender lo que hace. Se supone que readBytesUntil lee hasta que hay un timeout (que este no es el caso), que ha encontrado el carácter buscado (descartándolo del resultado, pero consumiéndolo del buffer) o que haya leído ya la cantidad de caracteres que se le ha indicado como máximo a leer (3 en este caso). Pues si trazamos el código que has planteado, una vez leído la W del buffer pasaría a leer 3 caracteres distintos al espacio (ya que este es el máximo que se le permite al readBytesUntil) y encontraría los 3 dígitos que hay antes del espacio, pero esto lo haría sin llegar a leer, consumir y descartar el espacio (ya que ha cumplido la condición de leer como máximo 3 caracteres distintos del espacio). Esto 3 caracteres se convertirían en un entero y luego pasamos a leer otros 3 caracteres distintos al retorno de carro. Estos 3 siguientes caracteres serían el espacio, que no se llegó a leer antes porque se llegó al máximo de 3 antes de llegar a él, y leerá también 2 dígitos del siguiente número (pero no el tercero). El espacio y los dos dígitos se convertirán sin problemas a un número (el espacio del principio es automáticamente descartado sin problemas por atoi). Pero el tercer dígito del segundo bloque de números no habrá sido leído aún. Ese tercer dígito, el retorno de carro y todo lo que no sea una W será descartado por while que busca el inicio de la trama. ¿Es así o me equivoco? Es que no me cuadra que funcione de otra manera.

Resumiendo, creo que si le llega W123 456 lo que se va a obtener es el valor 123 para horizontal y el valor erróneo 45 para vertical, "desapareciendo" el 6.

Para evitar el problema bastaría con cambiar el 4 por un 5 en la definición de n quedando así:

char n[5]; // Tamaño de 5 para asegurarnos de que lee los tres dígitos y consume el carácter buscado (pudiendo leer hasta 4 caracteres si no encuentra el carácter buscado)

Otro apunte, este para ea7ln que preguntaba:

ea7ln:
Hay algo que me ha llamado la atención, y es que no he tenido que declarar la variable (horizontal) ni (vertical). Tiene algo que ver que le precede en el código "unsigned"?

Lucario448 ha explicado lo del unsigned pero se le ha olvidado explicar que no necesitas declarar las variables, horizontal y vertical, porque ya se están declarando a la vez que se les asigna el valor:

unsigned int horizontal = atoi(n); // Conversión de texto a valor en una variable que se está declarando

es lo mismo que hacer en dos pasos:

unsigned int horizontal; // Declaración de la variable
horizontal = atoi(n); // Conversión de texto a valor

Vaya, otra cosa que me gusta: que no se queden solo en lo que yo digo; que vayan más profundo y llenen esos "vacíos" que puede haber dejado.

IgnoranteAbsoluto:
Aunque ya sabemos que deberíamos de hacer un código más legible, pero aveces puede quedar más ofuscado si tratas de hacerlo paso a paso.

En mi defensa, es la costumbre de los que vamos avanzados en estos asuntos: sugerir fragmentos de código "pre-optimizados"; algo que, naturalmente, requiere de esfuerzo por parte del principiante para comprenderlo.

IgnoranteAbsoluto:
Pues si trazamos el código que has planteado, una vez leído la W del buffer pasaría a leer 3 caracteres distintos al espacio (ya que este es el máximo que se le permite al readBytesUntil) y encontraría los 3 dígitos que hay antes del espacio, pero esto lo haría sin llegar a leer, consumir y descartar el espacio (ya que ha cumplido la condición de leer como máximo 3 caracteres distintos del espacio).

Estas equivocado. El caracter delimitador sí se descarta del búfer (flujo), sin embargo no se incluye en el resultado; en otras palabras, no se almacena en el vector especificado, ni tampoco se cuenta en la cantidad de bytes leídos (el retorno de la función).

IgnoranteAbsoluto:
Ese tercer dígito, el retorno de carro y todo lo que no sea una W será descartado por while que busca el inicio de la trama. ¿Es así o me equivoco? Es que no me cuadra que funcione de otra manera.

Correcto. El while se usa para evitar posibles tramas incompletas que puedan corromper algún dato. Es como decir: cualquier orden mal dada, será simplemente ignorada.

Asumo que la trama acaba en cambio de línea y que este sea el típico retorno del carro + nueva línea ("\r\n"); de lo contrario la función podría consumir incluso hasta un pedazo de otra trama.

IgnoranteAbsoluto:
Para evitar el problema bastaría con cambiar el 4 por un 5 en la definición de n quedando así:

char n[5]; // Tamaño de 5 para asegurarnos de que lee los tres dígitos y consume el carácter buscado (pudiendo leer hasta 4 caracteres si no encuentra el carácter buscado)

Como acabo de aclarar la duda, entonces puedo decir que sigues equivocado.

IgnoranteAbsoluto:
pero se le ha olvidado explicar que no necesitas declarar las variables, horizontal y vertical, porque ya se están declarando a la vez que se les asigna el valor:

unsigned int horizontal = atoi(n); // Conversión de texto a valor en una variable que se está declarando

es lo mismo que hacer en dos pasos:

unsigned int horizontal; // Declaración de la variable

horizontal = atoi(n); // Conversión de texto a valor

Con razón no entendía la parte de:

ea7ln:
Hay algo que me ha llamado la atención, y es que no he tenido que declarar la variable (horizontal) ni (vertical).

Lucario448:
Estas equivocado. El caracter delimitador sí se descarta del búfer (flujo), sin embargo no se incluye en el resultado; en otras palabras, no se almacena en el vector especificado, ni tampoco se cuenta en la cantidad de bytes leídos (el retorno de la función).

Retiro lo dicho; hasta ahorita capto el punto de IgnoranteAbsoluto: había que agrandar el vector con tal de mejorar la flexibilidad y eventualmente atinar hasta el delimitador.

Fue mera casualidad que con tamaño 4 aún así todo calzara (o quizá más bien algunas tramas se descartaban "silenciosamente").

Gracias por tu explicación Lucario, gracias a ella he podido resolver las dudas que tenia y poder entenderlo aunque con algo de esfuerzo.
Respecto al comentario que hacia Ignorante Absoluto, la variable Char la he tenido que dimensionar a 6.
Dimensionándola a 4, perdía valores de vertical.
Me ocurre una cosa curiosa que no se si entra dentro de lo normal.
El proyecto permite controlar el rotor de antena de forma manual, a través de un joystick en sus 4 posiciones, o bien desde el puerto serie para hacer el seguimiento automático.
Lo que ocurre en las prueba que he hecho hasta ahora, es que cuando el circuito esta funcionando y empiezo a mandar datos serie, el arduino se reinicia.
Me he dado cuenta porque antes de comenzar el Main del código, aparece un mensaje con la versión, y al mandar la primera trama, aparece en el display.
No es algo importante porque el funcionamiento después de reiniciarse es correcto, pero me queda la curiosidad de saber el motivo.
Gracias de nuevo por vuestra ayuda.

ea7ln:
Lo que ocurre en las prueba que he hecho hasta ahora, es que cuando el circuito esta funcionando y empiezo a mandar datos serie, el arduino se reinicia.

Creo que lo que te está pasando es que, cuando inicias el programa del ordenador, "abre" el puerto serie y activa una de las señales del puerto serie (el DTR) que está "conectado" al reset del Arduino, y lo resetea. Esto es útil para no tener que resetear a mano cada vez que queremos subir el programa al Arduino. Si no queres que esto suceda, aquí tienes cómo hacerlo dependiendo del Arduino: Android Apps: Evitar auto reset Arduino

Pero acuérdate de quitar el "apaño" cuando quieras volver a programar el Arduino. Seguro que te vas a olvidar quitarlo y vas a consultar en el foro qué te está sucediendo, que no puedes programarlo. :wink:

Entendido Ignorante Absoluto, no había caído en ese detalle y es bueno tenerlo en cuenta por si alguna vez necesito que no se reinicie.
Gracias por tu información.

Sigo trabajando en el proyecto y y en la última etapa vuelvo a necesitar vuestra ayuda.
Los datos serie leidos los almaceno en una variable, llamadas "serie_ori" y "serie_elev".
Tengo otra variable que es "ORI" y "ELEV", que son los valores en grados de angulo horizontal y vertical.
El valor de "ORI", (estoy probando solo con un movimiento), la comparo con el valor que me envía el PC almacenado en "serie_ori", para activar el relé de movimiento a izquierda o derecha según el valor sea mayor o menor y las antenas giren al punto de orientación que envía el PC
¿¿Que me ocurre??
Cuando recibo el dato serie se activa el relé, pero como los datos llegan cada segundo, el relé se desactiva porque el programa sale del bucle de lectura del puerto serie. Con lo cual, se activa y desactiva una vez por segundo.
Si pongo el "IF" fuera del bucle, el relé tabletea.
Se me ocurre hacer una especial timeout para que el programa se mantenga en el bucle hasta 2 seg esperando datos serie, y cuando lleguen, se resetee.
De esta manera además de resolver el problema, creo, al no recibir datos tras 2 segundos, se saldría del bucle de lectura del puerto serie, y podría mover los motores de forma manual.
¿Me podéis echar una mano?

//************************LECTURA PUERTO SERIE***********************

if (Serial.available()>0)

 {       
 while (Serial.read() != 'W'); // Buscar el inicio de la trama

 n[Serial.readBytesUntil(' ', n, sizeof(n) - 1)] = 0; // Leo la primera cifra. Datos orientacion
 int horizontal = atoi(n); // Conversión de texto a valor

serie_ori=horizontal;

 n[Serial.readBytesUntil('\r', n, sizeof(n) - 1)] = 0; // Leo la segunda cifra. Datos elevacion
 int vertical = atoi(n); // Conversión de texto a valor

 serie_elev=vertical;

 lcd.setCursor(7,1);
 lcd.print("PC"); //Parpadea cuando hay conexion serie
 
 if (horizontal<=ORI)digitalWrite(RIGHT, HIGH);
 if (horizontal>=ORI)digitalWrite(LEFT,HIGH);
 
 }

Se me olvidaba, os dejo el código completo.

Control_Rotor_080817.ino (11.2 KB)

if (horizontal<=ORI)digitalWrite(RIGHT, HIGH);
if (horizontal>=ORI)digitalWrite(LEFT,HIGH);

Si horizontal==ORI, entonces se te ejecutan las dos. ¿Esa era la idea?

Lucario448:

if (horizontal<=ORI)digitalWrite(RIGHT, HIGH);

if (horizontal>=ORI)digitalWrite(LEFT,HIGH);



Si **horizontal==ORI**, entonces se te ejecutan las dos. ¿Esa era la idea?

Gracias nuevamente por tu ayuda Lucario, creo que no me he explicado correctamente.
El proyecto puede controlar los motores de movimiento de la antena, de forma manual a través de un joystick leyendo los valores de sus potenciometros, por ejemplo, si muevo la palanca a la izquierda la tensión de la entrada analógica será menor de 2V y se activa el relé de movimiento a la izquierda.
Si muevo la palanca a la derecha, la tensión de entrada analógica será superior a 3V y activará el rele de giro a derecha. Al quedar en reposo los dos relés están desactivados porque la tensión será entre 2V y 3V.
Estos valores que te detallo son a modo de ejemplo.
Esto que te comento esta en el Main del codigo y corresponde a la linea 355, y está etiquetado como

LECTURA DEL JOYSTICK Y ACTIVACION DE RELES

La otra forma de poder mover los motores, es con los valores guardados en las variables serie_ori y serie_elev que son los datos leídos por el puerto serie.
Esto corresponde en el código a la línea 406 y está etiquetado como *LECTURA PUERTO SERIE
dentro del bucle

while (Serial.read() != 'W'); // Buscar el inicio de la trama

El problema es el siguiete....
Me centro en concreto con el movimiento horizontal, que es que intento conseguir primero.....

Si el valor de la variable serie_ori es menor que ORI, giro a derecha y si es al contrario, giro a izquierda.
ORI es la posición actual en grados horizontales de la antena.
Esto se cumple, pero como los datos serie llegan aprox cada segundo, entre dato y dato, el programa se sale del bucle de lectura de puerto serie, y al pasar por el Main, se sueltan los relés porque el Joystick está en reposo, hasta que vuelve a llegar un dato y se vuelve a activar nuevamente el relé, y así "click, click" cada segundo.
Lo que intento hacer, es que cuando llegue el primer dato serie, se quede en bucle siempre dentro de la lectura del puerto ejecutando lo que está dentro de

if (Serial.available()>0)

hasta que desconecte el PC o desconecte el cable físico del puerto.
De esta manera cuando mande datos serie, los motores funcionaran de forma automática, y si no los mando, los muevo a través del joystick.
Perdona por este comentario tan largo, que no se si mas que explicarme mejor, te he liado mas.

Siempre gracias Lucario.