[Nut-upsdev] Driver BETA for NHS UPS

Lucas Bocchi
Mon, 25 Nov 2024 13:04:37 -0000

Sorry

First send doesn't include GNU header. Please ignore first mail.


Em 25/11/2024 09:57, Lucas Bocchi escreveu:
> Hello everyone.
>
>
> My name is Lucas Willian Bocchi. After a few weeks of suffering, a lot
> of reverse engineering, creating serial port simulators in Python, C,
> etc., I managed to create a functional driver for the NHS UPS,
> sinusoidal line. It is a famous UPS in Brazil, but the company was
> never interested in developing something solid to integrate with Nut
> or ApCupsd. Since I had to choose between one or the other to start
> with, I chose Nut.
>
> The driver is functional, but unfortunately, I can no longer continue
> the development alone. I need help, because I will not be able to
> handle this project, but I also do not want to let it die. If anyone
> wants to take on the project, I can help with whatever I can in the
> development part when time allows. But I can no longer do it alone.
>
> I would be very grateful to anyone who can help. Below is the source
> code with the first "functional" version.
-------------- next part --------------
#include "main.h"
#include "common.h"
#include "nut_stdint.h"
#include "serial.h"
#include <stdio.h>
#include <linux/serial.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <time.h>
#include <stdbool.h>
#include <termios.h>

/*  Instructions to compile
        * apt update
        * apt install build-essential autoconf libtool pkg-config
libusb-1.0-0-dev libssl-dev git
        * Download NUT source code
        * ./autogen.sh
        * ./ci_build.sh
        * ./configure
        * make
        * cd drivers
        * gcc -g -o nhs-nut nhs-nut.c ../common/common.c -I../include (to test)
        * clang --analyze nhs-nut.c
        * cppcheck nhs-nut.c

*/

#define DEFAULTBAUD B2400
#define DEFAULTPORT "/dev/ttyACM0"
#define DRIVER_NAME "Nobreak NHS"
#define DRIVER_VERSION  "0.1"

/* driver description structure */
upsdrv_info_t upsdrv_info {
    DRIVER_NAME,
    DRIVER_VERSION,
    "Lucas Willian Bocchi <lucas at lucas.inf.br>",
    DRV_STABLE,
    { NULL }
};


// Struct to represent serial conventions in termios.h
typedef struct {
    speed_t rate;   // Constant in termios.h
    int speed;    // Numeric speed, used on NUT
    const char * description; // Description
} baud_rate_t;

baud_rate_t baud_rates[] = {
    { B50,      50,      "50 bps" },
    { B75,      75,      "75 bps" },
    { B110,     110,     "110 bps" },
    { B134,     134,     "134.5 bps" },
    { B150,     150,     "150 bps" },
    { B200,     200,     "200 bps" },
    { B300,     300,     "300 bps" },
    { B600,     600,     "600 bps" },
    { B1200,    1200,    "1200 bps" },
    { B2400,    2400,    "2400 bps" },
    { B4800,    4800,    "4800 bps" },
    { B9600,    9600,    "9600 bps" },
    { B19200,   19200,   "19200 bps" },
    { B38400,   38400,   "38400 bps" },
    { B57600,   57600,   "57600 bps" },
    { B115200,  115200,  "115200 bps" },
    { B230400,  230400,  "230400 bps" },
    { B460800,  460800,  "460800 bps" },
    { B921600,  921600,  "921600 bps" },
    { B1500000, 1500000, "1.5 Mbps" },
    { B2000000, 2000000, "2 Mbps" },
    { B2500000, 2500000, "2.5 Mbps" },
    { B3000000, 3000000, "3 Mbps" },
    { B3500000, 3500000, "3.5 Mbps" },
    { B4000000, 4000000, "4 Mbps" },
};
#define NUM_BAUD_RATES (sizeof(baud_rates) / sizeof(baud_rates[0]))

// Struct that contain nobreak info
typedef struct {
    unsigned int header;
    unsigned int size;
    char type;
    unsigned int model;
    unsigned int hardwareversion;
    unsigned int softwareversion;
    unsigned int configuration;
    unsigned int configuration_array[5];
    unsigned int numbatteries;
    unsigned int undedervoltagein120V;
    unsigned int overvoltagein120V;
    unsigned int undervoltagein220V;
    unsigned int overvoltagein220V;
    unsigned int tensionout120V;
    unsigned int tensionout220V;
    unsigned int statusval;
    unsigned int status[6];
    unsigned int chargercurrent;
    unsigned int checksum;
    char serial[17];
    unsigned int year;
    unsigned int month;
    unsigned int wday;
    unsigned int hour;
    unsigned int minute;
    unsigned int second;
    unsigned int alarmmonth;
    unsigned int alarmwday;
    unsigned int alarmday;
    unsigned int alarmhour;
    unsigned int alarmsecond;
    unsigned int end_marker;
} pkt_hwinfo;

typedef struct {
    unsigned int header;
    unsigned int length;
    char packet_type;
    unsigned int vacinrms_high;
    unsigned int vacinrms_low;
    unsigned int vdcmem_high;
    unsigned int vdcmem_low;
    unsigned int power_rms;
    unsigned int vacinrms_min_high;
    unsigned int vacinrms_min_low;
    unsigned int vacinrms_max_high;
    unsigned int vacinrms_max_low;
    unsigned int vacoutrms_high;
    unsigned int vacoutrms_low;
    unsigned int temp_high;
    unsigned int temp_low;
    unsigned int charger_current_high;
    unsigned int statusval;
    unsigned int status[8];
    unsigned int checksum;
    unsigned int end_marker;
} pkt_data;

const unsigned int string_initialization_long[9] = {0xFF, 0x09, 0x53, 0x83,
0x00, 0x00, 0x00, 0xDF, 0xFE};
const unsigned int string_initialization_short[9] = {0xFF, 0x09, 0x53, 0x03,
0x00, 0x00, 0x00, 0x5F, 0xFE};

int openfd(const char * porta, int BAUDRATE) {
    long unsigned int i = 0;
    int done = 0;

    int upsfd = ser_open(porta);
    if (upsfd < 0) {
        upsdebugx(1, "Erro on open %s", porta);
        return -1;
    }
    // select speed based on baud
    while ((i < NUM_BAUD_RATES) && (done == 0)) {
        if (baud_rates[i].speed == BAUDRATE) {
            done = baud_rates[i].rate;
            printf("Baud selecionado: %d --
%s\r\n",baud_rates[i].speed,baud_rates[i].description);
        }
        i++;
    }
    // if done is 0, no one speed has selected, then use default
    if (done == 0)
        done = DEFAULTBAUD;
    ser_set_speed(upsfd, porta, B9600);


    return upsfd;
}

// Fun??o para calcular o checksum
unsigned char calcula_checksum(unsigned char *pacote, int inicio, int fim) {
    int soma = 0;
    for (int i = inicio; i <= fim; i++) {
        soma += pacote[i];
    }
    return soma & 0xFF;
}

void pdatapacket(unsigned char * datapacket,int tamanho) {
    int i = 0;
    if (datapacket != NULL) {
        printf("\r\nDatapacket recebido: ");
        for (i = 0; i < tamanho; i++) {
            printf("\r\n\tPosicao %d -- 0x%02X -- Decimal %d -- Char
%c", i, datapacket[i], datapacket[i], datapacket[i]);
        }
    }
}

pkt_data mount_datapacket(unsigned char * datapacket, int tamanho, double
tempodecorrido)  {
    pkt_data pktdata = {
        .header = 0xFF,
        .length = 0x21,
        .packet_type = 'D',
        .vacinrms_high = 0,
        .vacinrms_low = 0,
        .vdcmem_high = 0,
        .vdcmem_low = 0,
        .power_rms = 0,
        .vacinrms_min_high = 0,
        .vacinrms_min_low = 0,
        .vacinrms_max_high = 0,
        .vacinrms_max_low = 0,
        .vacoutrms_high = 0,
        .vacoutrms_low = 0,
        .temp_high = 0,
        .temp_low = 0,
        .charger_current_high = 0,
        .statusval = 0,
        .status = { 0, 0, 0, 0, 0, 0, 0, 0 },
        .checksum = 0,
        .end_marker = 0xFE
    };
    pdatapacket(datapacket,tamanho);

    return pktdata;
}

pkt_hwinfo mount_hwinfo(unsigned char *datapacket, int tamanho) {
    pkt_hwinfo pkthwinfo = {
        .header = 0xFF,
        .size = 0,
        .type = 'S',
        .model = 0,
        .hardwareversion = 0,
        .softwareversion = 0,
        .configuration = 0,
        .configuration_array = {0, 0, 0, 0, 0},
        .numbatteries = 0,
        .undedervoltagein120V = 0,
        .overvoltagein120V = 0,
        .undervoltagein220V = 0,
        .overvoltagein220V = 0,
        .tensionout120V = 0,
        .tensionout220V = 0,
        .statusval = 0,
        .status = {0, 0, 0, 0, 0, 0},
        .chargercurrent = 0,
        .checksum = 0,
        .serial = "----------------",
        .year = 0,
        .month = 0,
        .wday = 0,
        .hour = 0,
        .minute = 0,
        .second = 0,
        .alarmmonth = 0,
        .alarmwday = 0,
        .alarmday = 0,
        .alarmhour = 0,
        .alarmsecond = 0,
        .end_marker = 0xFE
    };
    pdatapacket(datapacket,tamanho);

    return pkthwinfo;
}

int write_serial(int upsfd, const unsigned int * dados, int tamanho) {
    unsigned char message[tamanho+1];
    int i = 0;
    char data;
    for (i = 0;i < tamanho;i++) {
        data = message[i];
        printf("Dado convertido: %c", data);
        ssize_t bytes_written = write(upsfd, &data, 1);
        if (bytes_written < 0)
            return -1;
    }
    printf("\r\nPrinting %s to serial", message);
    //pdatapacket(dados,tamanho);
    if (tcdrain(upsfd) != 0) {
       return -2;
    }
    return i;
}

int write_serial_int(int upsfd, const unsigned int * data, int tamanho) {
    uint8_t message[tamanho];
    int i = 0;
    for (i = 0; i < tamanho; i++) {
        message[i] = (uint8_t)data[i];
        printf("%d %c %u %d %c
%u\r\n",message[i],message[i],data[i],data[i]);
    }
    ssize_t bytes_written = write(upsfd, message,tamanho);
    if (bytes_written < 0) {
        return -1;
    }

    if (tcdrain(upsfd) != 0) {
       return -2;
    }
    return i;
}

int main() {
    int upsfd;
    unsigned char c = '\0';
    unsigned int datapacket_index = 0;
    int bwritten = 0;
    bool datapacketstart = false;
    unsigned char datapacket[51];
    unsigned int checktime = 1000000; // 1 second
    unsigned int send_extended = 0;
    unsigned int obtainheader = 0;
    time_t lastdp = 0;

    pkt_hwinfo lastpkthwinfo = {
        .header = 0xFF,
        .size = 0,
        .type = 'S',
        .model = 0,
        .hardwareversion = 0,
        .softwareversion = 0,
        .configuration = 0,
        .configuration_array = {0, 0, 0, 0, 0},
        .numbatteries = 0,
        .undedervoltagein120V = 0,
        .overvoltagein120V = 0,
        .undervoltagein220V = 0,
        .overvoltagein220V = 0,
        .tensionout120V = 0,
        .tensionout220V = 0,
        .statusval = 0,
        .status = {0, 0, 0, 0, 0, 0},
        .chargercurrent = 0,
        .checksum = 0,
        .serial = "----------------",
        .year = 0,
        .month = 0,
        .wday = 0,
        .hour = 0,
        .minute = 0,
        .second = 0,
        .alarmmonth = 0,
        .alarmwday = 0,
        .alarmday = 0,
        .alarmhour = 0,
        .alarmsecond = 0,
        .end_marker = 0xFE
    };


    pkt_data lastpktdata = {
        .header = 0xFF,
        .length = 0x21,
        .packet_type = 'D',
        .vacinrms_high = 0,
        .vacinrms_low = 0,
        .vdcmem_high = 0,
        .vdcmem_low = 0,
        .power_rms = 0,
        .vacinrms_min_high = 0,
        .vacinrms_min_low = 0,
        .vacinrms_max_high = 0,
        .vacinrms_max_low = 0,
        .vacoutrms_high = 0,
        .vacoutrms_low = 0,
        .temp_high = 0,
        .temp_low = 0,
        .charger_current_high = 0,
        .statusval = 0,
        .status = { 0, 0, 0, 0, 0, 0, 0, 0 },
        .checksum = 0,
        .end_marker = 0xFE
    };

    upsfd = 0;
    // Loop principal
    while (1) {
        if (upsfd <= 0)
            printf("Problema com a serial...\r\n");
        while (upsfd <= 0) {
            upsfd = openfd(DEFAULTPORT,2400);
            usleep(checktime);
        }
        c = '\0';
        if (select_read(upsfd,&c,1,5,0) > 0) {
            if (c == 0xFF) { // In?cio do pacote
                datapacketstart = true;
                datapacket_index = 0;
            } // end for
            if (datapacketstart) {
                datapacket[datapacket_index] = c;
                datapacket_index++;
                // always insert a STRING TERMINATOR
                if (c == 0xFE) { // Fim do pacote
                    // always insert a STRING TERMINATOR
                    printf("\r\nDATAPACKET RECEBIDO. TAMANHO %d\r\n --
OBTAINHEADER %d", datapacket_index,obtainheader);
                    double tempodecorrido = 0.0;
                    time_t now = time(NULL);
                    if (lastdp != 0) {
                        tempodecorrido = difftime(now, lastdp);
                    }
                    lastdp = now;
                    // if size is 18 or 50, maybe a answer packet
                    if ((datapacket_index == 18) || (datapacket_index == 50))
                       lastpkthwinfo = mount_hwinfo(datapacket,
datapacket_index);
                    else {
                       lastpktdata = mount_datapacket(datapacket,
datapacket_index, tempodecorrido);
                    }
                    datapacket_index = 0;
                    memset(datapacket,0,sizeof(datapacket));
                    datapacketstart = false;

                    // We need a hw info packet to discover several variables,
like number of batteries, to calculate some data
                    if (lastpkthwinfo.size == 0) {
                        // Wait some interations to send request header again...
                        if (obtainheader % 10 == 0) {
                            printf("\r\npktwinfo loss --
Requesting\r\n");
                            // if size == 0, packet maybe not initizated, then
send a initialization packet to obtain data
                            // Send two times the extended initialization
string, but, on fail, try randomly send extended or normal
                            if (send_extended < 2) {
                                bwritten =
write_serial_int(upsfd,string_initialization_long,9);
                                send_extended++;
                            } // end if
                            else {
                                // randomly send
                                if (rand() % 2 == 0)
                                    bwritten =
write_serial_int(upsfd,string_initialization_long,9);
                                else
                                    bwritten =
write_serial_int(upsfd,string_initialization_short,9);
                            } // end else
                            if (bwritten < 0) {
                                if (bwritten == -1) {
                                    upsdebugx(1,"Problem to write data to
%s",DEFAULTPORT);
                                    printf("\r\nData problem\r\n");
                                }
                                if (bwritten == -2) {
                                    printf("\r\nFlush problem\r\n");
                                }
                                close(upsfd);
                                upsfd = -1;
                            } // end if
                        }
                        obtainheader++;
                    } // end if
                } // end if
            } // end if
        } // end if
        printf("\r\nCaractere %c -- %d",c,datapacket_index);
    } // end while

    // Fechando a porta serial
    close(upsfd);
    upsfd = -1;
    return 0;
}