Leer datos desde el puerto serie de una SAI / UPS

Buenas gente:

Los comandos y respuestas incluido supuestos errores te lo dice en este PDF que me dio el fabricante.

Ver comandos de la UPS/SAI. (Si alguien no puede ver el PDF lo subo por aquí).

No hay ningún problema.

Como puedes ver. Puedo enviar cualquier comando en el Terminal, por ejemplo, los B\r, X72\r los que sea. Y recibir sus respuestas. incluido errores.

Lo puede hacer como hice con este programa por probar.

Captura14.PNG

El problema está que todo comando a enviar, me aparece donde pone respuesta de la UPS, en string, hexadecimal y binario, lo hice así.

Lo que quiero hacer, que cada comando se guarde en una variable diferente para poder manipular dicha trama y incluir sus valores en sus label o etiquetas.

Eso es lo que no se hacer. Y si, hay que usar muchos if-else, switich-case y los Try-Catch, hasta ahí llego. ;)

Lo explico de otra manera con el dibujo o captura de abajo. Si envío el comando X72\r, me llega la tramas de Bytes y lo muestra en los labels marcado en rojo.

Si envío otro comando en este caso llamado X72\r muestra los valores en otras etiquetas indicado en el recuardro en verde.

SAI Csharp - copia.JPG

Por eso no puedo poner todas las tramas diferentes en la misma variable porque se mezclan y me muestra disparates.

¿Se entiende ahora donde quiero llegar? :):):)

Saludos.
 
Última edición:
Lo explico de otra manera con el dibujo o captura de abajo. Si envío el comando X72\r, me llega la tramas de Bytes y lo muestra en los labels marcado en rojo. Si envío otro comando en este caso llamado X72\r muestra los valores en otras etiquetas indicado en el recuardro en verde.

No se entiende eso, ¿para el mismo comando tenés dos respuestas diferentes?

Imagino que quisiste poner X27:

Client:X27<cr>
UPS:#output_voltage,high_transfer_voltage,low_transfer_voltage, battery_threshold,runtime_threshold<cr>
UPS:#-error_code<cr>

Client:X72<cr>
UPS:#apparent_power,active_power,rating_voltage,lowest_frequency, highest_frequency,rating_current,lower_frequency,higher_frequency<cr>
UPS:#-error_code<cr>

La comunicación se ve que es sincrónica (no me refiero al puerto serie en si, sino a la forma de obtener los datos), entonces yo mandaría un comando, espero la respuesta y lo guardo en una variable. Luego sigo con el próximo comando.

Además tenés la ventaja de saber desde el vamos el tipo de respuesta que vas a recibir como dijieron antes. El parseo sería sencillo, separar los campos por c/coma en el string recibido.
 
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.



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:
Hola
Me refería más a uno por software, como el Serial Port Sniffer
Lo he utilizado y si me dio buenos resultados, me imagino que hay mejores a prueba de todo.

Considera lo que andan diciendo, organiza todo y por partes.

Saludos
 
@cosmefulanito04

Es X72, tal como se muestra aquí abajo.

Tramas SAI UPS.JPG

Por eso hice este otro programa pero sin usar el puerto serie, sino que ya tengo las tramas de bytes o tramas de string guardadas en las variables, en sus variables, como ejemplo lo dejo aquí para que prueben el ejecutable bajo Windows en archivos adjuntos. El archivo adjunto es guardado por .zip y dentro tiene su ejecutable pero no le puse extensión .exe por temas de seguridad, lo tienes que poner tu y luego clickea.

Su código es este en C#.
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] + " 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]);

            #region Horas y minutos.
            // Cálculos.
            int horas = resultadoSalida7 / 60;
            int minutos = resultadoSalida7 % 60;

            // ¿0 horas y 1 minuto?
            if ((horas == 0) && (minutos == 1))
            {
                label_R.Text = $"{minutos} minuto.";
            }

            // ¿0 horas y 0 minuto?
            if ((horas == 0) && (minutos == 0))
            {
                label_R.Text = $"{minutos} minutos";
            }

            // ¿0 horas y más de 1 minuto?
            if ((horas == 0) && (minutos > 1))
            {
                label_R.Text = $"{minutos} minutos.";
            }

            // ¿1 hora y 0 minutos?
            if ((horas == 1) && (minutos == 0))
            {
                label_R.Text = $"{horas} hora.";
            }

            // ¿Más de una hora y 0 minutos?
            if ((horas > 1) && (minutos == 0))
            {
                label_R.Text = $"{horas} horas.";
            }

            // ¿1 hora y 1 minuto?
            if ((horas == 1) && (minutos == 1))
            {
                label_R.Text = $"{horas} hora y {minutos} minuto";
            }

            // ¿1 hora y más de 1 minuto?
            if ((horas == 1) && (minutos > 1))
            {
                label_R.Text = $"{horas} hora y {minutos} minutos.";
            }

            // ¿Más de 1 hora y 1 minuto?
            if ((horas > 1) && (minutos == 1))
            {
                label_R.Text = $"{horas} horas y {minutos} minuto.";
            }

            // ¿Más de 1 horas y más de 1 minuto?
            if ((horas > 1) && (minutos > 1))
            {
                label_R.Text = $"{horas} horas y {minutos} minutos.";
            }
            #endregion
        }

        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] + " Vac";
            label_Resultado_corriente_nominal.Text = salida2[5] + " A";
            label_Resultado_valores_nominales_de_frecuencia.Text = salida2[3] + " ~ " + salida2[4] + " Hz";
        }
    }
}

Entonces.

¿Cómo se hace lo que quiero?

Saludos.
 

Adjuntos

  • Interfaz UPS.zip
    95.9 KB · Visitas: 1
Última edición:
He leído el pdf y yo lo tengo claro.
Te has ido lo primero a por el comando más complejo, el B (Currently Status Query, Petición del Estado Actual), que devuelve nada menos que 12 valores, y encima el último (status_flags) son valores en decimal (que pueden ser de 1 hasta 6) que hay que convertir a binario, y entonces cada uno de los bits tiene un significado distinto.
Lo mismo de complejos son los comandos Xn y Kn:m

Eso es mucha tarea y muchos campos de texto. Vas a tener que estructurar tu programa muy bien. Tener claro qué comando envías y dividir el string de su respuesta (si procede), asignando una variable descriptiva a cada valor, separado por comas, que devuelve. Y luego mostrar todo en campos distintos acompañados de labels descriptivas.
No va a ser sencillo, tienes valores en decimal, valores en coma flotante y valores en decimal que deben convertirse a binario y cada bit significa una cosa.
Yo que tú empezaría programando comandos menos complejos, como el AU o el M. Cuando te funcionen bien, entonces pasa a programar comandos más complejos.

Y revisa tu código. Algo tienes mal. No puede ser que envies un comando y te muestre los resultados en los campos en rojo y al mandar otro comando te los muestre en los campos en verde.
 
Última edición:
A ver si explico, voy a reducir tu métodos
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(string entrada)
        {
            //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] + " 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]);

            #region Horas y minutos.
            // Cálculos.
            int horas = resultadoSalida7 / 60;
            int minutos = resultadoSalida7 % 60;

            // ¿0 horas y 1 minuto?
            if ((horas == 0) && (minutos == 1))
            {
                label_R.Text = $"{minutos} minuto.";
            }

            // ¿0 horas y 0 minuto?
            if ((horas == 0) && (minutos == 0))
            {
                label_R.Text = $"{minutos} minutos";
            }

            // ¿0 horas y más de 1 minuto?
            if ((horas == 0) && (minutos > 1))
            {
                label_R.Text = $"{minutos} minutos.";
            }

            // ¿1 hora y 0 minutos?
            if ((horas == 1) && (minutos == 0))
            {
                label_R.Text = $"{horas} hora.";
            }

            // ¿Más de una hora y 0 minutos?
            if ((horas > 1) && (minutos == 0))
            {
                label_R.Text = $"{horas} horas.";
            }

            // ¿1 hora y 1 minuto?
            if ((horas == 1) && (minutos == 1))
            {
                label_R.Text = $"{horas} hora y {minutos} minuto";
            }

            // ¿1 hora y más de 1 minuto?
            if ((horas == 1) && (minutos > 1))
            {
                label_R.Text = $"{horas} hora y {minutos} minutos.";
            }

            // ¿Más de 1 hora y 1 minuto?
            if ((horas > 1) && (minutos == 1))
            {
                label_R.Text = $"{horas} horas y {minutos} minuto.";
            }

            // ¿Más de 1 horas y más de 1 minuto?
            if ((horas > 1) && (minutos > 1))
            {
                label_R.Text = $"{horas} horas y {minutos} minutos.";
            }
            #endregion
        }

        private void button_Ver_Tabla_Click(string entrada2)
        {
            //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] + " Vac";
            label_Resultado_corriente_nominal.Text = salida2[5] + " A";
            label_Resultado_valores_nominales_de_frecuencia.Text = salida2[3] + " ~ " + salida2[4] + " Hz";
        }
    }
}
Y cuando las uses solo pregunta, envías el comando, almacenas que comando enviaste, almacenas los datos recibidos, pasas a un if o switch-case según el comando que enviaste y entonces pasas la string almacenada a la función que corresponda para decodificarla y procesarla.

Si no quieres hacerlo así e insistes en que se detecte en lugar de esperar una respuesta especifica tendrás que pasar la string por un
C#:
 if(string.contains('#') && string.contains('I') && string.contains('O') && string.contains('L') && string.contains('B') && string.contains('V') && string.contains('F') && string.contains('H') && string.contains('R') && string.contains('S') ){
  button_Ver_datos_Click(string)
} else {
  button_Ver_Tabla_Click(string)
}
Pero cuidado donde dos códigos te vengan con la misma secuencia de separadores.
 
Pues si, elegí el comando más complicado, no pasa nada. Desde que entienda todo, pues al ataque.

El tema de que cada bit de un Byte de la parte S, ya lo había mirado y si, cada bit significa algo y cambia de estado.

Estaba haciendo prueba en modo consola el cambio de cada bit haber si me sale. Repito, estaba haciendo pruebas, el código lo verán que he hecho disparates, pero estaba probando.

SAI Azul.JPG

Código C#:

C#:
using System;
using System.Text;

namespace Delimitador_consola_06
{
    class Program
    {
        static void Main(string[] args)
        {
            #region Configuración ventana.
            // Título de la ventana.
            Console.Title = "Probando manipulación de cadena";

            // Tamaño de la ventana, x, y.
            Console.SetWindowSize(100, 35);

            // Color de fondo.
            Console.BackgroundColor = ConsoleColor.DarkBlue;

            // Color de las letras.
            Console.ForegroundColor = ConsoleColor.Yellow;

            // Limpiar pantalla y dejarlo todo en color de fondo.
            Console.Clear();

            // Visible el cursor.
            Console.CursorVisible = true;
            #endregion

            // Cree una codificación UTF-8.
            Encoding UTF_8 = Encoding.GetEncoding("UTF-8");

            #region Variables.
            // Partir a trocitos estos caracteres.
            char[] delimitadorComandoB = { '#', ',', 'O', 'I', 'L', 'B', 'V', 'F', 'H', 'R', 'S', };

            // Es la cadena de caracteres que me llegó desde el puerto serie.
            // En este ejemplo lo dejo en la varible directamente.
            // Una cadena completa empieza con # y termina en <cr>, o lo que es lo mismo, /r.
            string respuestaB = "#I223.3O224.0L000B100V26.4F50.2H50.2R0080S„€€„\r"; // Comando B.

            // Se guarga en este array tipo string los datos ya partidos a tozos.
            string[] palabrasB = respuestaB.Split(delimitadorComandoB);

            // Tabla S1 descripción.
            string[] DESCRIPCION_S1 = new string[8]
            {
                "Indica que este byte está disponible y es válido. [Siempre 1]",             // Posición [0].
                "Indica falla de energía de la red pública; ver detalles en S2.0 y S2.1.",
                "Indica que la capacidad de la batería es menor que el umbral de apagado.",
                "Indica que el zumbador está en estado de pitido.",
                "Indica que la prueba de batería se está procesando.",
                "Indica que el apagado programado está pendiente.",
                "Indica que la restauración programada está pendiente.",
                "Indica falla de hardware; ver detalles sobre el comando X71."               // Posición [7].
            };

            string[] DESCRIPCION_S2 =
            {
                "Para indicar que este byte está disponible y es válido. [Siempre 1].",
                "YY-- bit 6",
                "XX-- bit 5",
                "Indica sobretemperatura del inversor.",
                "Indica que el inversor tiene una falla.",
                "Indica que el inversor está apagado.",
                "Indica que la frecuencia de la utilidad está fuera de rango.",
                "Indica que el voltaje de la red pública está fuera de rango."
            };

            string[] DESCRIPCION_S3 =
            {
                "Para indicar que este byte está disponible y es válido. [Siempre 1].",
                "Indica que la batería está completamente cargada.",
                "Indica que la capacidad de la batería aún es menor que el umbral restaurado del UPS cuando la energía de la red pública restaurado.",
                "Indica que la batería se está cargando.",
                "Indica que la batería se está descargando.",
                "Indica que la capacidad de la batería es inferior al 80 por ciento.",
                "Reservado, debe ser 0.",
                "Indica que la batería no está presente."
            };

            string[] DESCRIPCION_S4 =
{
                "Para indicar que este byte está disponible y es válido. [Siempre 1].",
                "Indica que el bypass es una sobrecarga.",
                "Indica que la derivación está activa.",
                "Indica que la salida sufre un cortocircuito.",
                "Indica que la salida tiene carga.",
                "Indica que la salida está sobrecargada.",
                "Indica que la frecuencia de salida está fuera de rango en bypass.",
                "Indica que el voltaje de salida está fuera de rango en derivación."
            };

            string[] DESCRIPCION_S5 =
{
                "Para indicar que este byte está disponible y es válido. [Siempre 1].",
                "Indica que no hay salida.",
                "Para indicar que el tiempo de ejecución restante es inferior al umbral.",
                "Para indicar que el zumbador está silenciado (no permanente) en este momento.",
                "Para indicar falla de cableado.",
                "Para indicar SAI en modo ECO.",
                "Para indicar UPS en Bypass manual.",
                "Arreglar 0."
            };

            string[] DESCRIPCION_S6 =
            {
                "Para indicar que este byte está disponible y es válido. [Siempre 1].",
                "Indica UPS encendido.",
                "Reservado, debe ser 0.",
                "Reservado, debe ser 0.",
                "Reservado, debe ser 0.",
                "Reservado, debe ser 0.",
                "Reservado, debe ser 0.",
                "Reservado, debe ser 0."
            };

            bool boolS1 = true;
            bool boolS2 = false;
            bool boolS3 = false;
            bool boolS4 = false;
            bool boolS5 = false;
            bool boolS6 = false;
            bool boolContador = true;
            bool boolContador2 = false;
            #endregion

            // Comando B.
            Console.WriteLine();

            // Muestra los resultados en pantalla.
            Console.WriteLine();
            Console.WriteLine("El voltaje de la utilidad es de {0} voltios. ", palabrasB[2]); // I.
            Console.WriteLine("El voltaje de salida del UPS es de {0} voltios. ", palabrasB[3]); // O.
            Console.WriteLine("La carga actual de UPS es del {0} por ciento. ", palabrasB[4]); // L.
            Console.WriteLine("La capacidad de la batería es del {0} por ciento. ", palabrasB[5]); // B.
            Console.WriteLine("El voltaje de la batería es de {0} voltios. ", palabrasB[6]); // V.
            //Console.WriteLine("La temperatura del gabinete del UPS es de {0} grados centígrados. ", palabrasB[7]); // T. No hay T. en mi versión.
            Console.WriteLine("La frecuencia de salida del SAI es de {0} Hz. ", palabrasB[7]); // F.
            Console.WriteLine("La frecuencia de salida del SAI es de {0} Hz. ", palabrasB[8]); // H.
            Console.WriteLine("El tiempo de funcionamiento restante de la batería es de {0} minutos. ", palabrasB[9]);
            //Console.WriteLine(@"Estos son los bits de estados que no se entiende.S:
            //Aquí hay que leer cada bits como cuando se leyeron cada Byte arriba: ", (char)palabrasB[10]);
            Console.WriteLine();

            char[] bits = palabrasB[10].ToCharArray();

            int byteS1 = (byte)bits[0];
            int byteS2 = (byte)bits[1];
            int byteS3 = (byte)bits[2];
            int byteS4 = (byte)bits[3];
            int byteS5 = (byte)bits[4];
            int byteS6 = (byte)bits[5];

            Console.WriteLine("Byte S1: {0}", byteS1.ToString());
            Console.WriteLine("Byte S2: {0}", byteS2.ToString());
            Console.WriteLine("Byte S3: {0}", byteS3.ToString());
            Console.WriteLine("Byte S4: {0}", byteS4.ToString());
            Console.WriteLine("Byte S5: {0}", byteS5.ToString());
            Console.WriteLine("Byte S6: {0}", byteS6.ToString());


            Console.WriteLine();
            Console.WriteLine(((byteS2 & 64) == 64) ? "ACTIVADO" : "DESACTIVADO");
            Console.WriteLine(((byteS2 & 32) == 32) ? "1" : "0");
            Console.WriteLine();


            Tabla();
            int contador = 0;
            for (int b = 0; b < bits.Length; b++)
            {
                for (int a = 7; a >= 0; a--)
                {

                    Console.Write((((byte)bits[b]) & (1 << a)) >> a);
                    Console.Write(" | ");

                    if ((contador <= 7) && (boolS1 == true))
                    {
                        Console.WriteLine(DESCRIPCION_S1[contador]);

                        if (contador == 7)
                        {
                            boolS1 = false;
                            boolS2 = true;
                            boolContador2 = true;
                            contador = 0;
                        }
                    }
                    else if ((contador <= 7) && (boolS2 == true))
                    {
                        Console.WriteLine(DESCRIPCION_S2[contador]);
                        boolContador2 = false;
                        if (contador == 7)
                        {
                            boolS2 = false;
                            boolS3 = true;
                            boolContador2 = true;
                            contador = 0;
                        }
                    }
                    else if ((contador <= 7) && (boolS3 == true))
                    {
                        Console.WriteLine(DESCRIPCION_S3[contador]);
                        boolContador2 = false;
                        if (contador == 7)
                        {
                            boolS3 = false;
                            boolS4 = true;
                            boolContador2 = true;
                            contador = 0;
                        }
                    }
                    else if ((contador <= 7) && (boolS4 == true))
                    {
                        Console.WriteLine(DESCRIPCION_S4[contador]);
                        boolContador2 = false;
                        if (contador == 7)
                        {
                            boolS4 = false;
                            boolS5 = true;
                            boolContador2 = true;
                            contador = 0;
                        }
                    }
                    else if ((contador <= 7) && (boolS5 == true))
                    {
                        Console.WriteLine(DESCRIPCION_S5[contador]);
                        boolContador2 = false;
                        if (contador == 7)
                        {
                            boolS5 = false;
                            boolS6 = true;
                            boolContador2 = true;
                            contador = 0;
                        }
                    }
                    else if ((contador <= 7) && (boolS6 == true))
                    {
                        Console.WriteLine(DESCRIPCION_S6[contador]);
                        boolContador2 = false;
                        if (contador == 7)
                        {
                            boolS6 = true;
                            boolContador2 = true;
                            contador = 0;
                        }
                    }

                    if (boolContador == true)
                    {
                        contador++;

                        if (boolContador2 == true)
                        {
                            contador = 0;
                        }
                    }
                    //Console.WriteLine();
                }
                Console.WriteLine();
            }


            #region Tabla.
            void Tabla()
            {
                Console.WriteLine();
                Console.WriteLine("Byte | Bit | Estado | Descripción");
                Console.WriteLine("-----+-----+-----------------------------------------------------------------------");
            }
            #endregion

            // Pulse cualquier tecla para salir.
            Console.ReadKey();
        }
    }
}

También me dio por hacer pruebas en Windows Form.
SAI leer bit de un byte.JPG

Código en C#. Solo es de prueba para saber si me funciona. Pongo un número decimal, el que sea del 0 hasta el 255 y me muestra cada bit.

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

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

        int ascii;
        int resultado;

        // readonly int[] mascara =
        readonly int[] mascara = new int[8]
        {
            0b_0000_0001, // Posición [0].
            0b_0000_0010,
            0b_0000_0100,
            0b_0000_1000,
            0b_0001_0000,
            0b_0010_0000,
            0b_0100_0000,
            0b_1000_0000  // Posición [7].
        };
        private enum CONSTANTE_DE_BATERIA
        {
            BATERIA_CARGADA =               0b_0000_0001, // Posición [0].
            BATERIA_AL_80_POR_CIENTO =      0b_0000_0010,
            BATERIA_AGOTQADA =              0b_0000_0100,
            BATERIA_CARGANDOSE =            0b_0000_1000,
            BATERIA_DESCARGANDOSE =         0b_0001_0000,
            BATERIA_ESTADO_DE_FLOTACION =   0b_0010_0000,
            BATERIA_DESCONECTADA =          0b_0100_0000,
            BATERIA_EN_CORTO =              0b_1000_0000  // Posición [7].
        }

        private void textBox_ASCII_TextChanged(object sender, EventArgs e)
        {
            try
            {
                ascii = int.Parse(textBox_ASCII.Text);
                CONSTANTE_DE_BATERIA myBateria = CONSTANTE_DE_BATERIA.BATERIA_CARGADA;
                this.Text = myBateria.ToString();


                resultado = ascii & mascara[0];
                
                if (resultado == 0) { label_c0.Text = "0"; } else { label_c0.Text = "1"; }
                resultado = ascii & mascara[1];
                if (resultado == 0) { label_c1.Text = "0"; } else { label_c1.Text = "1"; }
                resultado = ascii & mascara[2];
                if (resultado == 0) { label_c2.Text = "0"; } else { label_c2.Text = "1"; }
                resultado = ascii & mascara[3];
                if (resultado == 0) { label_c3.Text = "0"; } else { label_c3.Text = "1"; }
                resultado = ascii & mascara[4];
                if (resultado == 0) { label_c4.Text = "0"; } else { label_c4.Text = "1"; }
                resultado = ascii & mascara[5];
                if (resultado == 0) { label_c5.Text = "0"; } else { label_c5.Text = "1"; }
                resultado = ascii & mascara[6];
                if (resultado == 0) { label_c6.Text = "0"; } else { label_c6.Text = "1"; }
                resultado = ascii & mascara[7];
                if (resultado == 0) { label_c7.Text = "0"; } else { label_c7.Text = "1"; }
            }
            catch (Exception error)
            {
                MessageBox.Show(error.Message, "Aviso:",
                MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
        }
    }
}

Al menos ya de como se recoge datos y mostrarlo en pantalla, de solo una trama de bytes de un comando.

Lo que no se hacer, es que por cada comando, se necesita su recuadro específico para mostrar datos, ejejjejejje.

;)
 
Tu problema es que no sabés diseñar software, y si bien el problema a resolver es sencillo, requiere estudio, análisis y diseño.
Lamentablemente la respuesta a tu pregunta:

Requiere que nosotros hagamos todo tu trabajo...
No te había leído.

No me interesa que me hagas nada, me interesa resolver problemas o hacerlo funcionar. Ahorrate tus malas interpretaciones. 🙂😊

Una vez que entienda, el trabajo lo hago yo, y nadie más.

La parte más pesada es colocar etiquetas en cada bits, paciencia y perseverancia. 😁

Cuando acabe todo esto, un aviso por e-mail cualquier aviso y alarma SAI que puedes leer desde el móvil. Lo he probado con Arduino y funciona.

Est lo implementará al final. 👌👌👌👌
 
Última edición:
Buen consejo que tendré que probar, como leer cada bits de cada Byte, no es difícil pero si laborioso, pesado y cansino.

En estos momentos solo me interesa detetar o identificar las tramas de Bytes recibidos para luego cuajarlo en sus labels correspondiente, solo me hace falta entener y hacerlo funcionar eso y nada más.
 
Ya con el último string obtenido luego de la separación de la 'S', obtenés: "€„€ˆ„À"

Ese string resultante, lo convertís a cadena de char:

C#:
...
string dataString = "€„€ˆ„À";
char[] charArr = dataString.ToCharArray();
byte auxValue;

foreach (char ch in charArr)
{
    Console.WriteLine($"Char [{ch}]");
    auxValue = (byte)(ch);
    Console.WriteLine($"Value [{auxValue}]");    //Este es el valor que te interesa
}
...
 
Gracias por el código.

La Á que muestra en la parte S, es decir, esto: "€„€ˆ„À";

En realidad es un \r o <cr>, Retorno de Carro.

No olvidar que recibe esto según el PDF.
C#:
S<192><129><136><136><128><192>

Aquí está en decimal, en el cual se pasa a binario.

De todas maneras, en el Terminal lo paso todos los datos en decimal y me muestra esto abajo. Marcado en rojo son los 6 decimales.

SAI decimal.JPG

El último que es el 13, es el retorno de carro que puedes ver abajo en la tabla ASCII.

Figure-1-15-1024x601.png
 
Lo que no se hacer, es que por cada comando, se necesita su recuadro específico para mostrar datos, ejejjejejje
Pues lo primero es hacerte unas tablas o arrays de cada comando.
Con eso ahorrarás mucho código y no te perderas en infinitos y larguísikos IF/THEN/ELSE y Swirch/Cases
Como sugerencia, pueden contener los siguientes datos:

-ID del comando (único para cada comando, sirve como índice o referencia)
-Texto del Comando
-Número de caracteres del comando
-Número de variables que devuelve
-Nombre de la etiqueta asociada al campo
-Nombre del campo
-Variable 1
-Tipo de la variable 1
-Variable 2
-Tipo de la variable 2
-Variable 3
-Tipo de la variable 3
.
.
.

Y para el diseño de la ventana:
-ID del comando
-Coordenada X de la label del campo
-Coordenada Y de la label del campo
-Lenght de la label del campo (calculable como LEN (texto del campo) * anchura media de cada carácter
-Width de la label del campo (puede ser una constante)
-Coordenada X del campo
-Coordenada Y del campo
-Lenght del campo (calculable como LEN (texto del campo) * anchura media de cada carácter
-Width del campo 1 (puede ser una constante)
-Color
-Tipo de borde
-Tipo de variable que contiene

Te sugiero que, para cada comando, según las variables que devuelva, crees las labels y los campos en tiempo de ejecución.
 
El problema está en que vos tenés caracteres que van por arriba del código ASCII tradicional (que va de 0 a 127), estas trabajando con un ASCII extendido, que habrá que ver como lo toma la configuración de tu entorno de PC para determinar el tipo de encoding que debería hacer al momento de leer los datos del puerto serie.

Por ej. podría ser un cp437:


O un Windows-1252:


Ese es el problema de leer datos binarios como strings. Para mi, lo mejor es leer del puerto serie como una cadena de bytes en vez de un string, es decir usar este método:


En vez de usar este otro:


Entonces, supongamos que levantaste correctamente la cadena de bytes, de ahí yo buscaría la letra 'S' que debería ser un 0x53 en hexa y a partir de ahí leo los datos binarios. Todo lo anterior que hiciste con el string, sigue siendo válido, incluso podrías forzar el encoding que más te convenga y buscar la letra 'S' como lo hacés ahora para reconvertir los datos binarios.

De hecho vos en algún punto del código forzas el encoding a "UTF-8", fijate lo que pasa cuando forzas ese enconding con los valores binarios que necesitás:

C#:
byte[] bytes = new byte[] { 83, 192, 129, 136, 136, 128, 192, 13};

Encoding UTF_8 = Encoding.GetEncoding("UTF-8");
string str = UTF_8.GetString(bytes);
Console.WriteLine(str); // RTA: S?????? (y te agrega una línea al final del CR)

Sin embargo, podrías ir para atrás y recuperar los binarios desde la cadena "str", aunque no la muestre correctamente.

C#:
...
byte[] bytes_salida = UTF_8.GetBytes(str);
foreach (byte b in bytes)
   Console.Write(b + " "); //RTA: 83 192 129 136 136 128 192

En cambio, usando cp437:

C#:
byte[] bytes = new byte[] { 83, 192, 129, 136, 136, 128, 192, 13};

Encoding cp437 = Encoding.GetEncoding(437);
string str = cp437.GetString(bytes);
Console.WriteLine(str);    //RTA: S+üêêÇ+ (y te agrega una línea al final del CR)

byte[] bytes_salida = cp437.GetBytes(str);
foreach (byte b in bytes)
   Console.Write(b + " "); //RTA: 83 192 129 136 136 128 192 13

Resumiendo, seguramente al momento de recibir por puerto serie, se hace una conversión a código de caracteres que no coinciden con el UTF-8 que usas después y por lo tanto la recuperación del dato binario es incorrecto. Fijate como se evitaría todo este despelote, si directamante te pasaran los datos "binarios" en string '1' + '9' + '2'.
 
Última edición:
Buenos días.

Ya me funciona. Empecé desde cero con el .net 8.0 frente al .net 4.8 de antes.

Todavía estoy en pruebas.

Leyendo los consejos dd ustedes desde hace tiempo me recomendaron recibir las tramas en string y siempre he creído mejor Byte a byte ya que es puro y no usa codificación o conversaciones de algún tipo.

Dejo el código de abajo que funciona, no está bien diseñado del todo, pero hace caso.

Intentaré conseguir que me lea todo en Bytes, no en string.

Y muchas gracias por su tiempo. Seguiré por aquí. Hay que hacer las cosas bien.

C#:
using System.IO.Ports;
 
namespace Termite_SAI_05_.NET_8._0
{
    public partial class Form1 : Form
    {
        SerialPort puertoSerie;
        bool verComandoBFlag = false;
        bool verComandoX72Flag = false;
        readonly string verComandoBReq = "B\r";
        readonly string verComandoX72Req = "X72\r";
 
        private void Form1_Load(object sender, EventArgs e)
        {
 
        }
 
        public Form1()
        {
            InitializeComponent();
            InitSerialPort();
        }
 
        /* Iniciar y configurar el puerto serie */
        private void InitSerialPort()
        {
            try
            {
                puertoSerie = new SerialPort("COM3", 2400,
                    Parity.None, 8, StopBits.One)
                {
                    Handshake = Handshake.None,
                    ReadTimeout = 500,
                    WriteTimeout = 500,
                    DtrEnable = true,
                    RtsEnable = true,
                    DiscardNull = false,
                    ParityReplace = 63,
                    ReadBufferSize = 4096,
                    WriteBufferSize = 2018,
                    ReceivedBytesThreshold = 1
                };
 
                // Abrir puerto serie.
                puertoSerie.Open();
 
                // Subscribir el evento Sp_DataReceived cuando lleguen datos
                puertoSerie.DataReceived += Sp_DataReceived;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                puertoSerie.Close(); // Cierra el puerto en caso de error
            }
        }
 
        /* Evento para leer asíncronamente datos del puerto serie */
        void Sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            if (puertoSerie.IsOpen)
            {
                Task.Run(async () =>
                {
                    try
                    {
                        await Task.Delay(500); // Pausa para recibir datos
                        string str = puertoSerie.ReadExisting();
                        if (!String.IsNullOrEmpty(str))
                        {
                            if (verComandoBFlag) { ProcessverComandoBReq(str); }
                            if (verComandoX72Flag) { ProcessverComandoX72Req(str); }
                        }
                        else
                        {
                            Console.Write("Datos leídos corruptos.");
                        }
                    }
                    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");
                    }
                });
            }
        }
 
        /* Procesar verComandoX72Req */
        private void ProcessverComandoX72Req(string str)
        {
            verComandoX72Flag = false;
            char[] separadores = { '#', ',','\r' };
            string[] salida = str.Split(separadores, StringSplitOptions.RemoveEmptyEntries);
 
            // Al manipular controles de Windows Forms desde un hilo diferente al
            // hilo de la interfaz de usuario, es necesario usar Invoke para evitar problemas de concurrencia
            if (InvokeRequired)
            {
                Invoke(new Action(() =>
                {
                    UpdateUIControlsComandoX72(salida);
                }));
            }
            else
            {
                UpdateUIControlsComandoX72(salida);
            }
        }
 
        /* Procesar verComandoBReq */
        private void ProcessverComandoBReq(string str)
        {
            verComandoBFlag = false;
            char[] separadores = { '#', 'I', 'O', 'L', 'B', 'V', 'F', 'H', 'R', 'S', '\r'};
            string[] salida = str.Split(separadores, StringSplitOptions.RemoveEmptyEntries);
 
            // Al manipular controles de Windows Forms desde un hilo diferente al
            // hilo de la interfaz de usuario, es necesario usar Invoke para evitar problemas de concurrencia
            if (InvokeRequired)
            {
                Invoke(new Action(() =>
                {
                    UpdateUIControlsComandoB(salida);
                }));
            }
            else
            {
                UpdateUIControlsComandoB(salida);
            }
        }
 
        /* Mostrar datos verComandoX72Req */
        private void UpdateUIControlsComandoX72(string[] salida)
        {
            label_Resultado_valores_nonimales_de_alimentacion.Text
               = salida[0] + " VA / " + salida[1] + " W";
            label_Resultado_voltaje_nominal.Text
                = salida[2] + " Vac";
            label_Resultado_corriente_nominal.Text
                = salida[5] + " A";
            label_Resultado_valores_nominales_de_frecuencia.Text
                = salida[3] + " ~ " + salida[4] + " Hz";
        }
 
        /* Mostrar datos verComandoBReq */
        private void UpdateUIControlsComandoB(string[] salida)
        {
            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]);
 
            #region Horas y minutos.
            // Cálculos.
            int horas = resultadoSalida7 / 60;
            int minutos = resultadoSalida7 % 60;
 
            // ¿0 horas y 1 minuto?
            if ((horas == 0) && (minutos == 1))
            {
                label_R.Text = $"{minutos} minuto.";
            }
 
            // ¿0 horas y 0 minuto?
            if ((horas == 0) && (minutos == 0))
            {
                label_R.Text = $"{minutos} minutos";
            }
 
            // ¿0 horas y más de 1 minuto?
            if ((horas == 0) && (minutos > 1))
            {
                label_R.Text = $"{minutos} minutos.";
            }
 
            // ¿1 hora y 0 minutos?
            if ((horas == 1) && (minutos == 0))
            {
                label_R.Text = $"{horas} hora.";
            }
 
            // ¿Más de una hora y 0 minutos?
            if ((horas > 1) && (minutos == 0))
            {
                label_R.Text = $"{horas} horas.";
            }
 
            // ¿1 hora y 1 minuto?
            if ((horas == 1) && (minutos == 1))
            {
                label_R.Text = $"{horas} hora y {minutos} minuto";
            }
 
            // ¿1 hora y más de 1 minuto?
            if ((horas == 1) && (minutos > 1))
            {
                label_R.Text = $"{horas} hora y {minutos} minutos.";
            }
 
            // ¿Más de 1 hora y 1 minuto?
            if ((horas > 1) && (minutos == 1))
            {
                label_R.Text = $"{horas} horas y {minutos} minuto.";
            }
 
            // ¿Más de 1 horas y más de 1 minuto?
            if ((horas > 1) && (minutos > 1))
            {
                label_R.Text = $"{horas} horas y {minutos} minutos.";
            }
            #endregion
        }
 
        /* Evento de botón: Abrir puerto serie y mandar "B" */
        private void button_Comando_B_Click(object sender, EventArgs e)
        {
            if (puertoSerie.IsOpen)
            {
                verComandoBFlag = true;
                //puertoSerie.Open();
                puertoSerie.Write(verComandoBReq);
            }
            else
            {
                MessageBox.Show("Puerto cerrado. " +
                "No se puede solicitar datos.");
 
            }
        }
 
        /* Evento de botón: Abrir puerto serie y mandar "X72" */
        private void button1_Click(object sender, EventArgs e)
        {
            if (puertoSerie.IsOpen)
            {
                verComandoX72Flag = true;
                //puertoSerie.Open();
                puertoSerie.Write(verComandoX72Req);
            }
            else
            {
                MessageBox.Show("Puerto cerrado. " +
                    "No se puede enviar datos.");
            }
        }
    }
}

Se que es código chapuza. Cuando lo tenga en byte a byte las tramas al recibir, me pondré con la parte más tediosa del comando B\r.

La parte S de bit a bit. También los detección de errores desde la UPS.
 
Para mi no es un buen "diseño" por parte de los fabricantes de la UPS mezclar tipos de datos.

O se envía strings o se envía binarios, la mezcla de los dos en una misma trama, es para problemas.

De hecho, se ve la intención de forzar esos datos binarios con un uno en el bit más significativo, para evitar el caracter nulo y poder levantarlo como string.

Obviamente al string se lo puede leer como binario, de hecho en bajo nivel uno hace eso, pero perdés la interpretación directa.
 
Ya me he dado cuenta que son unos chapuceros en este aspecto.

Ahora me tengo que buscar la vida por sus chapuzas.

Como puedes ver, en marca del SAI APC es algo así.

(546455/_6_=__//_<6555

Siempre empieza con ( y no tiene final para indicar su cierre.

Salicru la que uso, empieza por # y acaba en retorno de carro.

Lo que no se si usarán en sus programas estas instrucciones para controlar tramas de Bytes que llegan y terminan.


Arduino también tiene las mismas funciones.

StartWith y EndsWith.

Cuando domine un poco esto, contrataré el GLCD y Arduino las tramas de la UPS.

Ahora me toca añadir más comandos, mejorar el programa y leer bien los bit de cada byte.

El diseñador o diseñadores de esas tramas de bits, tendrían que haberlo puesto en decimal y luego el programa lo pasa a bits, no de esa manera rara.
 
Última edición:
Lo más fácil, el dato recibido del comando B, es adaptarlo a decinamales la trama.

Tal como muestra aquí.

SAI decimal.JPG

O si no, que lea todo y cuando llegue a la S, con una condición típica de if-else, conviertes en binario a decimal como muestra arriba.

La S como dice en el manual en PDF, sería así.

C#:
S<192><129><136><136><128><192>

Sigo dándote la razón, el diseñador diseñó mal, por muy ingeniero que sea.

Saludos.
 
SAI con tercer comando.JPG

Probando con tercer comando incluido.

En realidad el comando B tiene que funcionar cada 3 segundos se envía automáticamente por un timer y mostrar los datos.
C#:
using System.IO.Ports;

namespace Termite_SAI_05_.NET_8._0
{
    public partial class Form1 : Form
    {
        SerialPort puertoSerie;
        bool verComandoBFlag = false;
        bool verComandoX72Flag = false;
        bool verComandoX5Flag = false;
        const string COMANDO_B = "B\r";
        const string COMANDO_X72 = "X72\r";
        const string COMANDO_X5 = "X5\r";

        public Form1()
        {
            InitializeComponent();
            InitSerialPort();
        }

        /* Iniciar y configurar el puerto serie */
        private void InitSerialPort()
        {
            try
            {
                puertoSerie = new SerialPort()
                {
                    // Configuración del puerto serie.
                    PortName = "COM3",           // Nombre del puerto serie.
                    BaudRate = 2400,             // Velocidad en baudios.
                    Parity = Parity.None,        // Esquema para comprobar la paridad de cada byte recibido.
                    StopBits = StopBits.One,     // Número de bits de parada por byte.
                    DataBits = 8,                // Número de bits de datos por byte.
                    Handshake = Handshake.None,  // Protocolo de establecimiento.
                    DtrEnable = true,            // Línea de terminal de datos.
                    RtsEnable = true,            // Línea de solicitud.

                    // Establecer los tiempos de espera de lectura / escritura.
                    ReadTimeout = 500,           // Tiempo de espera de escritura en ms.
                    WriteTimeout = 500,          // Tiempo de espera de escritura en ms.

                    // Más configuraciones.
                    DiscardNull = false,         // Descartar bytes nulos recibidos.
                    ParityReplace = 63,
                    ReadBufferSize = 4096,
                    WriteBufferSize = 2018,
                    ReceivedBytesThreshold = 1
                };

                // Abrir puerto serie.
                puertoSerie.Open();

                // Subscribir el evento DatosRecividos cuando lleguen datos.
                puertoSerie.DataReceived += DatosRecividos;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                puertoSerie.Close(); // Cierra el puerto en caso de error
            }
        }

        /* Evento para leer asíncronamente datos del puerto serie */
        void DatosRecividos(object sender, SerialDataReceivedEventArgs e)
        {
            Task.Run(async () =>
            {
                try
                {
                    await Task.Delay(500); // Pausa para recibir datos
                    string cadena = puertoSerie.ReadExisting();
                    if (!string.IsNullOrEmpty(cadena))
                    {
                        if (verComandoBFlag) { ProcessCOMANDO_B(cadena); }
                        if (verComandoX72Flag) { ProcessCOMANDO_X72(cadena); }
                        if (verComandoX5Flag) { ProcessCOMANDO_X5(cadena); }
                    }
                    else
                    {
                        Console.Write("Datos leídos corruptos.");
                    }
                }
                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");
                }
            });
        }

        /* Procesar COMANDO_X5 */
        private void ProcessCOMANDO_X5(string cadena)
        {
            verComandoX5Flag = false;
            char[] separadores = { '#', ',', '\r' };
            string[] salida = cadena.Split(separadores, StringSplitOptions.RemoveEmptyEntries);

            // Al manipular controles de Windows Forms desde un hilo diferente al
            // hilo de la interfaz de usuario, es necesario usar Invoke para evitar problemas de concurrencia.
            if (InvokeRequired)
            {
                Invoke(new Action(() =>
                {
                    UpdateUIControlsComandoX5(salida);
                }));
            }
            else
            {
                UpdateUIControlsComandoX5(salida);
            }
        }

        /* Procesar COMANDO_X72 */
        private void ProcessCOMANDO_X72(string str)
        {
            verComandoX72Flag = false;
            char[] separadores = { '#', ',', '\r' };
            string[] salida = str.Split(separadores, StringSplitOptions.RemoveEmptyEntries);

            // Al manipular controles de Windows Forms desde un hilo diferente al
            // hilo de la interfaz de usuario, es necesario usar Invoke para evitar problemas de concurrencia.
            if (InvokeRequired)
            {
                Invoke(new Action(() =>
                {
                    UpdateUIControlsComandoX72(salida);
                }));
            }
            else
            {
                UpdateUIControlsComandoX72(salida);
            }
        }

        /* Procesar COMANDO_B */
        private void ProcessCOMANDO_B(string str)
        {
            verComandoBFlag = false;
            char[] separadores = { '#', 'I', 'O', 'L', 'B', 'V', 'F', 'H', 'R', 'S', '\r' };
            string[] salida = str.Split(separadores, StringSplitOptions.RemoveEmptyEntries);

            // Al manipular controles de Windows Forms desde un hilo diferente al
            // hilo de la interfaz de usuario, es necesario usar Invoke para evitar problemas de concurrencia
            if (InvokeRequired)
            {
                Invoke(new Action(() =>
                {
                    UpdateUIControlsComandoB(salida);
                }));
            }
            else
            {
                UpdateUIControlsComandoB(salida);
            }
        }

        /* Mostrar datos COMANDO_X5 */
        private void UpdateUIControlsComandoX5(string[] salida)
        {
            label_Resultado_Tension_nominal.Text
                = salida[0] + " V";
            label_Resultado_Cantidad_del_paquete.Text
                = salida[1] + " Baterías";
            label_Resultado_Capacidad_del_paquete.Text
                = salida[2] + " Ah";
            label_Resultado_Cantidad_externa.Text
                = salida[3];
            label_Resultado_Descarga_maxima.Text
                = salida[4] + " Minutos";

            // Convertir variable tipo string a tipo uint, es decir, la variable tipo string "salida[5]"
            // se convierte en tipo uint "resultadoSalida5".
            uint resultadoSalida5 = UInt32.Parse(salida[5]);

            // Cálculos.
            uint horas = resultadoSalida5 / 60;
            uint minutos = resultadoSalida5 % 60;

            label_Resultado_Carga_maxima.Text
                = $"{horas} Horas";

        }
        /* Mostrar datos COMANDO_X72 */
        private void UpdateUIControlsComandoX72(string[] salida)
        {
            label_Resultado_valores_nonimales_de_alimentacion.Text
               = salida[0] + " VA / " + salida[1] + " W";
            label_Resultado_voltaje_nominal.Text
                = salida[2] + " Vac";
            label_Resultado_corriente_nominal.Text
                = salida[5] + " A";
            label_Resultado_valores_nominales_de_frecuencia.Text
                = salida[3] + " ~ " + salida[4] + " Hz";
        }

        /* Mostrar datos COMANDO_B */
        private void UpdateUIControlsComandoB(string[] salida)
        {
            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 uint, es decir, la variable tipo string "salida[7]"
            // se convierte en tipo uint "resultadoSalida7".
            uint resultadoSalida7 = UInt32.Parse(salida[7]);

            #region Horas y minutos.
            // Cálculos.
            uint horas = resultadoSalida7 / 60;
            uint minutos = resultadoSalida7 % 60;

            // ¿0 horas y 1 minuto?
            if ((horas == 0) && (minutos == 1))
            {
                label_R.Text = $"{minutos} minuto.";
            }

            // ¿0 horas y 0 minuto?
            if ((horas == 0) && (minutos == 0))
            {
                label_R.Text = $"{minutos} minutos";
            }

            // ¿0 horas y más de 1 minuto?
            if ((horas == 0) && (minutos > 1))
            {
                label_R.Text = $"{minutos} minutos.";
            }

            // ¿1 hora y 0 minutos?
            if ((horas == 1) && (minutos == 0))
            {
                label_R.Text = $"{horas} hora.";
            }

            // ¿Más de una hora y 0 minutos?
            if ((horas > 1) && (minutos == 0))
            {
                label_R.Text = $"{horas} horas.";
            }

            // ¿1 hora y 1 minuto?
            if ((horas == 1) && (minutos == 1))
            {
                label_R.Text = $"{horas} hora y {minutos} minuto";
            }

            // ¿1 hora y más de 1 minuto?
            if ((horas == 1) && (minutos > 1))
            {
                label_R.Text = $"{horas} hora y {minutos} minutos.";
            }

            // ¿Más de 1 hora y 1 minuto?
            if ((horas > 1) && (minutos == 1))
            {
                label_R.Text = $"{horas} horas y {minutos} minuto.";
            }

            // ¿Más de 1 horas y más de 1 minuto?
            if ((horas > 1) && (minutos > 1))
            {
                label_R.Text = $"{horas} horas y {minutos} minutos.";
            }
            #endregion
        }

        /* Evento de botón: Abrir puerto serie y mandar "B" */
        private void button_Comando_B_Click(object sender, EventArgs e)
        {
            verComandoBFlag = true;
            puertoSerie.Write(COMANDO_B);
        }

        /* Evento de botón: Abrir puerto serie y mandar "X72" */
        private void button_Comando_X72_Click(object sender, EventArgs e)
        {
            verComandoX72Flag = true;
            puertoSerie.Write(COMANDO_X72);
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            // Cerrar puerto serie.
            puertoSerie.Close();
        }

        private void button_Comando_X5_Click(object sender, EventArgs e)
        {
            verComandoX5Flag = true;
            puertoSerie.Write(COMANDO_X5);
        }
    }
}

Los comandos X71 y X87 son solo tramas de bit. Se puede probar.

La idea es hacer lograr leer cada bit del S del comando B que ya sabemos. ;)

Al menos la parte que quería funciona, gracias a las ayudas de ideas de aquí. :)

El comando B, como dije antes, se envía por cada 3 segundos por ahí. Si hay algún cambio de evento, los flag status del S, avisará como alarmas.

No me importa hacerlo funcionar también en modo consola el puerto serie. Total, con Arduino haré lo mismo, porque es muy parecido.
 
Atrás
Arriba