[Nut-upsdev] Driver BETA for NHS UPS
Lucas Bocchi
lucas at lucas.inf.br
Mon Nov 25 13:04:37 GMT 2024
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;
}
More information about the Nut-upsdev
mailing list