Leer datos desde el puerto serie de una SAI / UPS

Pensando en ambos lenguajes, podrias usar un switch-case que, dependiendo del menu/opcion que estes visualizando, escriba y lea los datos que correspondan.
Ejemplo;
Menu temperaturas y voltajes.
Entonces dentro del "case", colocas los comando de lectura y escritura de esos datos, segun corresponda, y de ahi no te sales hasta cambiar de menu.

Algo como;

C#:
int menu = 0;
switch(menu){
    case 0:
        leerTempVolt();
        break;
    case 1:
        leerConsumo();
        break;
    default:
        break;
}

void leerTempVolt(){
    int volt = getVolt();
    print("Voltaje: ");
    println(volt);
}
...

No se si es lo ideal, pero para empezar a maquetar te sirve.
 
Buenas gente:

He comprado el SAI este modelo. Con un Hyper Terminar cualquier programa de este tipo para enviar y recibir datos del puerto serie. Puedo enviar el comando B<cr> y recibo datos como este #I230.6O230.0L011B100V25.7F50.2H50.2ò, si lo hago varias veces el mismo comando o otros diferentes, el SAI deja de responder.

Tengo que volver al servicio de PowerMaster Plus a reiniciar y ejecutar su programa para que se vuelva a restablecer una y otra vez cada vez que envío comandos por mi parte.

El programa del SAI funciona de maravilla.

Antes de comprar un Datalogger para el RS232, quiero saber el motivo por el cual el programa del SAI siempre funciona y eso que es en servicios y página Web y si lo hago yo con un terminal para RS232 se cuelga, incluso he hecho un programa con C# como puedes ver en la imagen.

¿Hay algo que no sepa o se me escapa?

El PDF del protocolo RS232 y comandos me lo dieron ustedes hace tiempo.

Espero una respuesta y muchas gracias.
 

Adjuntos

  • SAI 01.JPG
    SAI 01.JPG
    88.9 KB · Visitas: 9
  • SAI Normal.JPG
    SAI Normal.JPG
    51 KB · Visitas: 9
  • Protocolo Comandos Salicru SAI UPS.pdf
    561.7 KB · Visitas: 8
Hola, a falta de más datos, has probado algún sniffer para ello? o monitor de puerto serie.
Conectas al puerto RS232, ejecutas el programa del SAI y ejecutas el sniffer (depende), ahí podrías capturar la forma de los comandos que intercambian.
Puede que tengan algún patrón secuencial o una especie de consulta-respuesta entre ambos y si no coincide se bloquea.

Saludos
 
Puede ser.

El Sniffer (Husmeador) me imagino que te refieres a esto.


Debo encontrar uno moderno y bueno que haga estas cosas. No se si incluirán software del propio dispositivo o uno independiente, pero esto es otro tema.

Hasta hay un detector de velocidad de baudios y todo. Es bueno saberlo y si es posible, hacer uno con ese LCD incluido.

Hay que averiguar el protocolo exacto. El fabricante me dio el PDF del protocolo. Le hago preguntas y ya no me responde nada, nadie de ellos. Ni siquiera hay un SDK, solo te dan el software propio y encima usa servicios y web. No me gusta ese método de control de dispositivos RS232, si siento curiosidad de probarlo algún día. El SAI/UPS que tengo es este.

Si envío el comando B<cr> y X72<cr> como indica abajo, es verdad que cada trama de Bytes que recibo, siempre empieza con # y acaba con <cr>. Tal como indica abajo. Hay que mirar mucho la tabla ascii por si acaso.

RealTerm.JPG

Como indicas, tendrá alguna forma de comportarse la comunicación.

¿Se puede capturar datos solo usando software o no queda más remedio que usar hardware por medio si o si?

;)

Observando con el RealTerm, al enviar el comando B<cr> me devuelve esto:

#I227.0O227.4L006B100V25.6F50.2H50.2R0120S„€ˆ„À

Siempre empieza con # y acaba en <cr>.

A veces y no se el motivo, no acaba la trama completa, y nunca, llega el final que es este, <cr>.
#I227.0O227.4L006B100V25.

Otras veces, me devuelve esto:
#-10<cr>, otras #-1<cr> y #-3<cr>. Todas terminen con retorno de carro y empieza con # con su código de error.

Se ve que depende del Terminal para recibir y enviar comandos, si lo hago repetidas veces seguidas da problemas pero que este RealTerm no me da mucho.

Si envía un comando que no conozca, por ejemplo esto dfsojosfjs<cr>, siempre me devuelve esto: #-1<cr>. Si envío culquier comando como el B o otro que no reconozca pero sin el <cr>, nunca devuelve nada, ni código de error.

Hay que diseñar bien el programa para que lea todo bien desde el principio.

Los programas que he hecho no tenía en cuenta cuando me llega el principio de la trama el #, el final de la trama como que ha llegado que es <cr> y los códigos de errores.

Los que me han hablado en privado les respondo aquí.

1. Si ya tengo el programa del SAI/UPS. ¿Para qué quiero hacer uno propio?
Respuesta: Por cuestiones didácticas, aprender manejar bien este protocolo RS232 usando tu propia interfaz y manejar dispositivos de terceros.

2. ¿Qué otra idea se te ocurre?
Respuesta: Desde hace tiempo, mi idea principal es manejar bien el puerto RS232 del SAI/UPS por si me llega algún error, por ejemplo, batería desconectada o descargada, me salte la alarma y me avise en un display como este los errores, tanto como el código de error y el motivo en pantalla. El SAI actual solo muestra código de error en su display, le pondré otro controlado con Arduino y su display con textos claros. Ahora mismo si salta algún error o aviso, me muestra en su display el código de error, luego tengo que ver el manual en pdf que significa.

3. ¿Tienes más ideas en mente?
Respuesta: Por ahora estoy haciendo pruebas, directamente como se maneja las tramas de bytes, controlar el SAI o visualizar los mensajes que me llegan y saberlo interpretar usando tecnología Windows Form, incluso hasta con la consola de Windows de toda la vida, un ejemplo que puedes ver aquí abajo. También se buscará un GLCD para arduino y se muestra como se ve abajo.

LCD consola SAI UPS.JPG

4. ¿Por qué usa la consola de Windows?
Respuesta: Me gusta experimentar con todo tipo de tecnologái, lo último que haré será usar modo servicios y web para el control del SAI. De paso, manejar la consola, es muy similar a manejar los LCD o GLCD que usa mucho Arduino. Se puede sacar programas similares entre Windows consola y LCD Arduino con el cual facilita la programación entre ambos.

5. Hablando de Arduino. ¿Qué se te ocurre hacer exactamente?
Respuesta: De entrada, conectar el SAI a Arduino por el puerto serie. Desde el principio todo por cable. Más adelante, será por radio frecuencia, sin cables y poner el LCD en otra parte de la vivienda por poner un ejemplo. Hay más ideas, pero en este caso voy por lo básico y después ya se verá.

¿Alguna otra pregunta?

Espero que entre todos saquemos esto.

Saludos.

Hola de nuevo: Ya me funciona bien y no se me cuelga. Me he pegado desde el 2021 sin resolver el problema, es verdad que también tiré la toalla porque no encontraba la solución.

Tiene que tener los DTS y RTS activados no dará problemas de comunicaciones. Si vas hacer un mini chat solo de RS232 que lo tengo hecho en modo consola, funciona de maravilla sin los DTS y RTS.

Ahora me queda otras partes.

Si empieza por # y acaba en <cr> sea del SAI o la propia Arduino por nombrar algún dispositivo. ¿Cuál es la acción más recomendable para programarlo bien?

Lo único que se me ocurre es que puedes enviar varios comandos y recibir respuestas muy diferentes, me refiero tramas de Bytes diferentes para encuadrarlo en ciertas variables diferentes y mostrar la información en zonas diferentes como en este ejemplo que hice, pero todavía no le pillo el truco. Según avance, publicaré.
 
Última edición:
Buenas.

En Arduino debo enviar varios comandos y cada uno tiene su tramas de Bytes difetentes, por ejemplo.

Si envío con un pulsador digital desde Arduino el comando B<cr>, en comando es B/r.

C++:
#I230.3O230.0L007B100V25.7F50.2H50.2R0080S„€ˆ„/r

Si envio con otro pulsador otro comando, por ejemplo este, X72/r desde Arduino, le devuelve y Arduino lee esta otra trama de Bytes.

C++:
#2000,1400,230,45.0,55.0,8.6/r

Si lee solo tramas directamente todo bien.

El problema está cuando son tramas diferentes y debe estar almacenados en variables diferentes, es decir, cada comando tiene su variable de su tramas al recibir.

¿Se entiende lo que quiero decir?

Partiendo la base del primer código de Arduino del primer post.

¿Cómo se puede hacer lo que pido?

Y si, dejaré de usar delay y me meto con los millis.

Gracias 🙂.
 
No. No se entiende nada.

Cada respuesta comienza con # y termina con \r.
Cual es el problema???

Esto está mal. No es

sino
B\r ó fijate bien...
El comando siempre solo termina en retorno de carro. B\r, tal cual, para nada usa
Lo que quiero contar arriba. Si una trama de Bytes me llega del comando B\r, se almacena en una variable llamada por ejemplo variableB.

Si envío otro comando me llega otra trama diferente y se llena en la variableX72.

Después de capturar los datos del puerto serie una almacenada en la variableB y la otra variableX72, luego se usará y se mostrará en LCD los valores.

¿Se entiende ahora?

Espero que si.😊
 
Última edición:
Lo que yo haría es almacenar las tramas en un buffer genérico (por ej. uno dedicado exclusivamente a la recepción del puerto serie) y a partir de ese buffer, analizar su contenido para tomar la acción correspondiente, en tu caso será asignarlo a un buffer diferente según la trama recibida.

Todo esto es válido, si el uC tiene la memoria RAM suficiente para manejar múltiples buffer (hoy en día, la mayoría lo puede hacer). De lo contrario, tendrás que buscar otras alternativas.
 
Buenas compañeros:

Para dejarlo más claro. Dejo esta captura de ejemplo. No funciona pero para que quede claro lo que intento hacer.

SAI-Csharp.jpg

Si pulsa los botones de esta interfaz creado con Visual Studio Community 2022 muestra los datos de dicha tramas de Bytes.

Así quiero mostrar en pantalla los datos recibidos recibidos desde el puerto serie.

Espero que esta vez se entienda.
👌
 
Ah, vas al revés, recibís los datos de un equipo y lo querés mostrar en PC.

Obviamente hay muchas formas de hacerlo, yo haría una clase de "parseo" (interpretación) de los datos que te llegan del puerto serie y para llenar los campos del mensaje en algo que sea sencillo de interpretar por tu interfaz, otra clase con los datos ya armados.

- Crearía clase base llamada "comando" y de ahí heredás para los distintos tipos de comandos que tengas.
- Crearía una clase de "parseo" y en ella me encargaría de interpretar el dato recibido por el puerto serie y generaría un objeto "comando" con el tipo heredado que corresponda según le mensaje.
- Luego desde la interfaz gráfica levantaría los distintos objetos "comandos" y los mostraría en pantalla.

¿Se puede obviar el uso de clase/herencias/etc y directamente usar un lindo y bonito switch? Si.
 
El ejemplo de la interfaz de arriba es desde PC y UPS.

Dejo el código de la interfaz aquí abajo en el lenguaje de C#.

Este código ya tiene las tramas en sus variables, es de ejemplo y no funciona el puerto serie porque no está programado, excepto otro programa que sólo funciona con una sola trama.

C#:
using System;
using System.Windows.Forms;

namespace Termite_SAI_03
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button_Ver_datos_Click(object sender, EventArgs e)
        {
            string entrada = "#I225.7O226.2L006B100V25.7F50.2H50.2R0080S€„€ˆ„À";

            char[] separadores = { '#', 'I', 'O', 'L', 'B', 'V', 'F', 'H', 'R', 'S' };

            string[] salida = entrada.Split(separadores, StringSplitOptions.RemoveEmptyEntries);

            label_I.Text = salida[0] + " V";
            label_O.Text = salida[1] + " V";
            label_L.Text = salida[2].TrimStart('0') + " %"; // Quita los ceros de la izquierda.
            label_B.Text = salida[3] + " %";
            label_V.Text = salida[4] + " V";
            label_F.Text = salida[5] + " Hz";
            label_H.Text = salida[6] + " Hz";

            // Convertir variable tipo string a tipo int, es decir, la variable tipo string "salida[7]"
            // se convierte en tipo int "resultadoSalida7".
            int resultadoSalida7 = Int32.Parse(salida[7]);

            // ¿Es igual a 1 minuto?
            if ((resultadoSalida7 % 60) == 1)
            {
                label_R.Text = resultadoSalida7 / 60 + " hora y " + resultadoSalida7 % 60 + " minuto.";
            }

            // ¿Es mayor a 60 segundos o 1 minuto?
            if ((resultadoSalida7 % 60) > 1)
            {
                label_R.Text = resultadoSalida7 / 60 + " hora y " + resultadoSalida7 % 60 + " minutos.";
            }

            // ¿Es igual a 60 segundos o 1 minuto?
            if ((resultadoSalida7 % 60) == 0)
            {
                label_R.Text = resultadoSalida7 / 60 + " hora y " + resultadoSalida7 % 60 + " minutos.";
            }
        }

        private void button_Ver_Tabla_Click(object sender, EventArgs e)
        {
            string entrada2 = "#2000,1400,230,45.0,55.0,8.6";

            char[] separadores2 = { '#', ',' };

            string[] salida2 = entrada2.Split(separadores2, StringSplitOptions.RemoveEmptyEntries);

            label_Resultado_valores_nonimales_de_alimentacion.Text = salida2[0] + " VA / " + salida2[1] + " W";
            label_Resultado_voltaje_nominal.Text = salida2[2] + " V";
            label_Resultado_corriente_nominal.Text = salida2[5] + " A";
            label_Resultado_valores_nominales_de_frecuencia.Text = salida2[3] + " ~ " + salida2[4] + " Hz";
        }
    }
}
Por ahora no te tocado nada del puerto serie, solo muestro con botones de prueba a la hora de recibir da

Como indiqué arriba, es de PC a UPS.

En el futuro será de Aruino con su GLCD grande y la UPS, la misma UPS.

Simulando una GLCD aquí abajo.


LCD consola SAI UPS.JPG

Espero que esta vez por fin se entienda lo que quiero decir y hacer. 🙂🙂🙂🙂😊
 
No entiendo el problema, si envías un dato y sabes cómo será la respuesta entonces puedes hacer un procesamiento distinto para cada comando enviado. Solo tienes que leer hasta encontrar el \r para procesar la cadena, puedes usar indexOf() para encontrar el separador, después substring() para separar los datos, luego toInt() o toFloat() para convertirlos a las variables que ocupes.
 
Por lo que entiendo, vos al mandarle esa trama, esperás la respuesta del UPS, entonces, ¿qué es lo que estarías esperando?
Esas tramas me la da la UPS. Los comandos se los puedo enviar en un terminal.

Ver ejemplo abajo.
Tramas SAI UPS.JPG

Luego lo lee el PC o Arduino, en el cual se muestra en el monitor o una LCD.

@Nuyel

El problema que si hago lo que dices, me da problemas por todas partes ya que son tramas diferentes. Si la misma trama cambia su valor, por ejemplo de #1000,477\r y cambia su valor respetando las comas en este caso, #1001,477\r No pasa nada.

Si la trama es diferente.
C#:
"#I225.7O226.2L006B100V25.7F50.2H50.2R0080S€„€ˆ„À";

La cosa cambia.

Y eso que hay muchos comandos y tramas muy distintas.
Quiero ver ejemplos porque no se hacerlo. Solo he hecho esto y lo pongo abajo. Solo lee el comando B\r.

Dejo abajo el código en Visual C# funcional del puerto serie. Si lo quieren probar por cualquier motivo, les paso el ejecutable. Si logro hacer esta interfaz que funcione como debe ser, hacerlo con Arduino será lo mismo, pero con GLCD.

C#:
using System;
using System.IO.Ports;
using System.Threading;
using System.Windows.Forms;

namespace Termite_SAI_04
{
    public partial class Form1 : Form
    {
        private delegate void DelegadoAcceso(String accion);
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            try
            {
                serialPort1 = new SerialPort("COM3", 2400, Parity.None, 8, StopBits.One);
                serialPort1.Handshake = Handshake.None;
                serialPort1.DataReceived += new SerialDataReceivedEventHandler(sp_DataReceived);
                serialPort1.ReadTimeout = 500;
                serialPort1.WriteTimeout = 500;
                serialPort1.DtrEnable = true;
                serialPort1.RtsEnable = true;
                serialPort1.Open();
                ComandoB();
                timer1.Start();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            if (this.Enabled == true)
            {
                Thread.Sleep(500);
                string entrada = serialPort1.ReadExisting();
                this.BeginInvoke(new DelegadoAcceso(si_DataReceived), new object[] { entrada });
                //serialPort1.Write("B\r"); // Envía comando.
            }
        }

        private void si_DataReceived(string accion)
        {
            try
            {
                char[] separadores = { '#', 'I', 'O', 'L', 'B', 'V', 'F', 'H', 'R', 'S', '\r' };

                string[] salida = accion.Split(separadores, StringSplitOptions.RemoveEmptyEntries);

                label_I.Text = salida[0] + " Vac";
                label_O.Text = salida[1] + " Vac";
                label_L.Text = salida[2].TrimStart('0') + " %"; // Quita los ceros de la izquierda.
                label_B.Text = salida[3] + " %";
                label_V.Text = salida[4] + " Vdc";
                label_F.Text = salida[5].PadRight(5, '0') + " Hz"; // Añade ceros a la derecha.
                label_H.Text = salida[6].PadRight(5, '0') + " Hz";

                // Convertir variable tipo string a tipo int, es decir, la variable tipo string "salida[7]"
                // se convierte en tipo int "resultadoSalida7".
                int resultadoSalida7 = Int32.Parse(salida[7]);

                // ¿Es igual a 1 minuto?
                if ((resultadoSalida7 % 60) == 1)
                {
                    label_R.Text = resultadoSalida7 / 60 + " hora y " + resultadoSalida7 % 60 + " minuto.";
                }

                // ¿Es mayor a 60 segundos o 1 minuto?
                if ((resultadoSalida7 % 60) > 1)
                {
                    label_R.Text = resultadoSalida7 / 60 + " hora y " + resultadoSalida7 % 60 + " minutos.";
                }

                // ¿Es igual a 60 segundos o 1 minuto?
                if ((resultadoSalida7 % 60) == 0)
                {
                    label_R.Text = resultadoSalida7 / 60 + " hora y " + resultadoSalida7 % 60 + " minutos.";
                }
            }

            catch (IndexOutOfRangeException ex)
            {
                Console.Write("Índice fuera de los límites de la matriz.");
                MessageBox.Show(ex.Message);
            }

            catch (FormatException ex)
            {
                Console.Write("La cadena de entrada no tiene el formato correcto.");
                MessageBox.Show(ex.Message, "Error");
            }
        }

        private void button_Ver_datos_Click_1(object sender, EventArgs e)
        {
            ComandoB();
        }

        private void button_Ver_Tabla_Click_1(object sender, EventArgs e)
        {
            ComandoX72();
        }

        // Comandos.
        void ComandoB()
        {
            serialPort1.Write("B\r");
        }

        void ComandoX72()
        {
            serialPort1.Write("X72\r");
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            ComandoB();
        }

        // Al cerrar el formulario.
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            timer1.Stop();          // Detiene el temporizador.
            Thread.Sleep(700);      // 0.5 segudos. 500 milisegundos.
            serialPort1.Close();    // Cierra el puerto serie.
        }
    }
}
 
agrega una variable global Comando, cuando envíes el comando establece el valor de la variable, en la recepción entonces coloca un if (Comando = x(){ hacer esto} y ya pones el procesamiento según corresponda a tu respuesta esperada.
No sé por qué quieres adivinar que es la trama si ya sabes que estás preguntando.
 
Y eso que hay muchos comandos y tramas muy distintas.
[...]
Si lee solo tramas directamente todo bien.

El problema está cuando son tramas diferentes y debe estar almacenados en variables diferentes, es decir, cada comando tiene su variable de su tramas al recibir.
Es lo que te estamos diciendo:

Conoces el comando que vas a enviar, sus variables, sus tipos, cuántas son y si va con parámetros o no.
Por lo tanto, conoces la respuesta que deberías recibir de cada comando enviado.

Prepara un organigrama de comandos enviables y las de las tramas que deberías recibir. Con lápiz y papel.
Estructura tu programa en base a esto, siempre teniendo en cuenta lo que envías y lo que debes recibir.
Usa sabiamente las abundantes funciones de manejo de strings y arrays que existen en el lenguaje. Lee su documentación.
Los IF, los Switch/Case y los Try/Catch son tus aliados.

Si mandas un comando determinado y no recibes la trama que deberías recibir, asegurate del buen estado de la comunicación serie.
 
Atrás
Arriba