Vamos a programar #98 - Ver el rendimiento del PC usando arduino y C#.

 Hola de nuevo a todos, el día de hoy vamos a ver cómo ver el rendimiento del PC usando C# y arduino.


En el post anterior vimos una versión beta de un programa que sirve para monitorear cual es el rendimiento del PC, y si bien ya era algo funcional, le he hecho algunas mejoras para que resulte más fácil de leer.

El código está conformado por dos partes, la primera, un programa en C# para windows que se encarga de obtener la información de cual es la carga del CPU en porcentaje, pero además de la RAM también en porcentaje, luego esa información la envía a un puerto al cual conectamos un arduino. La segunda parte consiste en un arduino que toma la información que recibió del programa y la muestra.

El programa en C#

El código fuente (del formulario principal) es el siguiente:

using System;
using System.Windows.Forms;
using System.IO.Ports;

namespace CPUMeterToArduino
{

    public partial class Form1 : Form
    {
        System.Diagnostics.PerformanceCounter CPULoad;
        System.Diagnostics.PerformanceCounter RAMInUse;
        System.Diagnostics.PerformanceCounter AvailableRAM;

        private SerialPort Port = new SerialPort();

        private void ArduinoMessage(string Message)
        {
            try
            {
                Port.Write(Message);        
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }

        }

        private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
        {
            string DATA = string.Concat(((int)CPULoad.NextValue()).ToString("D3") , "," , PercentRAM(RAMInUse.NextValue() , AvailableRAM.NextValue()).ToString("D3") , "\0");

            SerialPort sp = (SerialPort)sender;
            string indata = sp.ReadExisting();
            if (indata.Contains("\r\n"))
            {
                //ArduinoMessage("100,050\0");
                ArduinoMessage(DATA);
            }
        }

        Timer TimerMain;

        private int PercentRAM(float InUse, float Available)
        {
            if (Available == 0)
                return 100;
            else
            {
                float RamMBInUse = (InUse / 1024 / 1024);
                float TotalRam = RamMBInUse + Available;
                float Percent = ((RamMBInUse / TotalRam) * 100);
                return (int)Percent;
            }
        }

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, System.EventArgs e)
        {
            RAMInUse = new System.Diagnostics.PerformanceCounter("Memory", "Committed Bytes");
            AvailableRAM = new System.Diagnostics.PerformanceCounter("Memory", "Available MBytes");
            CPULoad = new System.Diagnostics.PerformanceCounter("Processor", "% Processor Time", "_Total");


            TimerMain = new Timer();
            TimerMain.Interval = 1000;
            TimerMain.Enabled = true;
            TimerMain.Tick += new System.EventHandler(TimerMain_Tick);


            string[] Ports = SerialPort.GetPortNames();
            cboportname.Items.AddRange(Ports);
            cboportname.Text = "COM3";
            Port.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
            TSSLbl.Text = Port.IsOpen ? "Conectado" : "No conectado";

        }

        private void TimerMain_Tick(object sender, EventArgs e)
        {
            PBCPUUsage.Value = (int)CPULoad.NextValue();
            int PR = PercentRAM(RAMInUse.NextValue(), AvailableRAM.NextValue());
            PBRAMUsage.Value = PR;
            this.Text = "Uso de RAM " + PR + "%" + " | CPU: " + PBCPUUsage.Value; ;
            NTFYMain.Text = "Uso de RAM " + PR + "%";
            TSSLbl.Text = Port.IsOpen ? "Conectado con " + Port.PortName : "No conectado";

        }

        private void Form1_Resize(object sender, EventArgs e)
        {
            if (this.WindowState == FormWindowState.Minimized)
            {
                Hide();
                NTFYMain.Visible = true;
            }
        }

        private void NTFYMain_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            Show();
            this.WindowState = FormWindowState.Normal;
            NTFYMain.Visible = false;
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (Port.IsOpen == true)
            {
                Port.Close();
            }
            NTFYMain.Visible = false;
        }

        private void BtnConnect_Click(object sender, EventArgs e)
        {
            try
            {
                if (Port.IsOpen == false)
                {
                    Port.BaudRate = 9600;
                    Port.PortName = cboportname.Text;
                    Port.Open();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void BtnDis_Click(object sender, EventArgs e)
        {
            if (Port.IsOpen == true)
            {
                Port.Write("Se ha desconectado el cliente\0");
                Port.Close();
            }
        }

        private void TSMIRestore_Click(object sender, EventArgs e)
        {
            NTFYMain_MouseDoubleClick(null, null);
        }

        private void TSMIExit_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }
    }
}


Cómo podrás ver el código no sufrió tantos cambios por lo que recomiendo que leas el post anterior donde se detalla cada parte, de cualquier forma, lo puedes descargar al final del post.

El código para arduino.

La parte para arduino fue la que mas cambio, hay que recordar que hacemos uso de la libreria "LiquidCrystal_I2C.h" y la puedes encontrar en el administrados de librerías de arduino.
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
//SCL - A5
//SDA - A4
LiquidCrystal_I2C lcd(0x27,20,4);

char Texto[80];
char BuffCPU[20];
char BuffRAM[20];


byte BarBeginEmpty[] = {
    B11111,
    B10000,
    B10000,
    B10000,
    B10000,
    B10000,
    B10000,
    B11111
};

byte BarBeginFull[] = {
    B11111,
    B10000,
    B10111,
    B10111,
    B10111,
    B10111,
    B10000,
    B11111
};

byte BarSegmentFull[] = {
    B11111,
    B00000,
    B11111,
    B11111,
    B11111,
    B11111,
    B00000,
    B11111
};

byte BarSegmentEmpty[] = {
    B11111,
    B00000,
    B00000,
    B00000,
    B00000,
    B00000,
    B00000,
    B11111
};

byte BarEndEmpty[] = {
    B11111,
    B00001,
    B00001,
    B00001,
    B00001,
    B00001,
    B00001,
    B11111
};

byte BarEndFull[] = {
    B11111,
    B00001,
    B11101,
    B11101,
    B11101,
    B11101,
    B00001,
    B11111
};


void setup() {
    lcd.init();
    
    lcd.createChar(0 , BarBeginEmpty);
    lcd.createChar(1 , BarBeginFull);
    lcd.createChar(2 , BarEndEmpty);
    lcd.createChar(3 , BarEndFull);
    lcd.createChar(4 , BarSegmentEmpty);
    lcd.createChar(5 , BarSegmentFull);

    lcd.backlight();
    Serial.begin(9600);
}


void DrawProgressBar(uint8_t Value, uint8_t XLocation, uint8_t YLocation){
    
    uint8_t Progress = map(Value , 1 , 100 , 0 , 20);
    
    lcd.setCursor(XLocation , YLocation);
    
    for (uint8_t i = 0; i < 20; i++)
    {
        if (i == 0)
        {
            if (Progress == 0)
                lcd.write(0);
            else
                lcd.write(1);
        }
            
        else if (i == 19)
        {
            if (Progress == 20)
                lcd.write(3);
            else
                lcd.write(2);
        }
        else
        {
            if (Progress <= i)
                lcd.write(4);
            else
                lcd.write(5);
        }
            
    }
}

void loop() {

    int i = 0;
    if (Serial.available()) {
        while (Serial.available() > 0) {
            Texto[i] = Serial.read();
            i++;
        }
        Texto[i] = '\0';
    }


    String Text(Texto);

    int X = Text.substring(0 , 3).toInt();
    int Y = Text.substring(5 , 7).toInt();

    lcd.setCursor(0 , 0);
    //lcd.print(Texto);
    sprintf(BuffCPU , "CPU: %03d", X);
    sprintf(BuffRAM , "RAM: %03d", Y);
    lcd.print(BuffCPU);
    lcd.setCursor(0,2);
    lcd.print(BuffRAM);
    
    DrawProgressBar(X , 0 , 1);
    DrawProgressBar(Y , 0 , 3);

    Serial.println("\r\n"); 
    delay(500);
}

Para empezar, creamos seis "sprites"  que corresponden a los segmentos de la barra de progreso "BarBeginEmpty", "BarBeginFull", "BarSegmentFull", "BarSegmentEmpty", "BarEndEmpty" y "BarEndEmpty", aunque sus nombres pueden resultar auto-explanatorios para algunos, cada uno define cada estado para las diferentes partes de la barra de progreso. Cada uno de los segmentos de la pantalla están conformados por una matriz de 5x8, entonces (y por ahora no entrare en detalles) creamos un array de esa dimensión y para simplificarlo, podemos aprovechar que arduino acepta numero en binario siempre y cuando vayan precedidos por el prefijo "B", ahora observemos la siguiente imagen:

Está es la definición de "BarBeginFull"

Podemos observar que para formar el sprite, simplemente debemos de decidir si queremos o no usar cada cuadrado de la matriz. Si nos fijamos bien en la imagen, "BarBeginFull" es: "B11111,B10000,B10111,B10111,B10111,B10111,B10000,B11111"

Por lo tanto podemos usar una tabla e iluminar las celdas para formar el sprite que queramos.

Definición de "BarSegmentFull"


Definición de "BarSegmentEmpty"

Para poder hacer uso de los sprites, bastara con usar la función  "lcd.createChar()" que recibe cómo parámetros un valor del tipo "byte" que sirve para indicar en cual de los espacios reservados para caracteres personalizados vamos a almacenar nuestro sprite (esto hay de que detallarlo, pero será en otro post, por ahora solo toma en cuenta que los primeros ocho espacios son utilizables, es decir de 0 a 7, después de eso son los caracteres definidos dentro de la propia pantalla, pero cómo ya dije eso es para otro post). El segundo parámetro es el arreglo que contiene os datos de nuestro sprite.

Luego tenemos el procedimiento "DrawProgressBar()", este toma tres parámetros. El primero es el valor que tendrá la barra y para esto se asume que se recibirá siempre un valor entre 1 y 100 (para facilitar las cosas), el segundo parámetro sirve para indicar cual será la locación X de la barra, puede ser cualquier valor, pero ya que por lo general las pantallas tienen 16 o 20 espacios, lo recomendable es que el valor este dentro de estos, pero peor aun y es algo que note al escribir esto (y que voy a dejar asi cómo muestra de mi estupidez) es que la barra no se va a adaptar si se usa un valor distinto de 0, si se escribe otro, simplemente aparecerá incompleta. El tercer parámetro es la locación en el eje Y, al igual que el anterior, sirve para indicar la ubicación y puesto que la mayor parte de la pantallas son de 16x2 o 20x4, el parámetro debería de estar entre 0 y 1 para la primera y 0 y 3 para la segunda. Después se toma el parámetro "Values" y se le aplica la función "map" para que el rango este entre 0 y 20 que es el tamaño de la pantalla en mi caso, si tu pantalla es de 16 x 2 por ejemplo, esta línea la debes de modificar (y es la parte que olvide a la hora de escribir el código). Luego, en el ciclo "for", debemos de recorrer de 0  hasta el tamaño de pantalla y comprobar los distintos estados, puesto que la variable "Progress" ya tiene un valor que esta dentro del los limites de la pantalla, bastara con comparar, si el iterador es menor o igual que "Progress" dibujamos el sprite lleno, pero si no es el caso, dibujamos el vacío.

Y bien, por ahora es todo, aprovechando que no tome las previsiones necesarias para cuando las pantallas son diferentes a la mía, en los próximos días actualizaré el programa, pero de igual manera, el código fuente del programa de C# lo puedes descargar de mi dropbox y tambien el código de arduino lo puedes descargar de mismo lugar para que lo modifiques o lo pruebes mientras subo la version corregida.

Los leo luego.

No hay comentarios.