nut/drivers/bcmxcp_ser.c

429 lines
12 KiB
C
Raw Permalink Normal View History

2010-03-25 23:20:59 +00:00
#include "main.h"
#include "bcmxcp.h"
#include "bcmxcp_io.h"
2022-06-29 10:37:36 +00:00
#include "bcmxcp_ser.h"
2010-03-25 23:20:59 +00:00
#include "serial.h"
2022-06-29 10:37:36 +00:00
#include "nut_stdint.h"
2010-03-25 23:20:59 +00:00
2013-11-24 15:00:12 +00:00
#define SUBDRIVER_NAME "RS-232 communication subdriver"
2022-06-29 10:37:36 +00:00
#define SUBDRIVER_VERSION "0.21"
2010-03-25 23:20:59 +00:00
/* communication driver description structure */
upsdrv_info_t comm_upsdrv_info = {
SUBDRIVER_NAME,
SUBDRIVER_VERSION,
NULL,
0,
{ NULL }
};
2013-11-24 15:00:12 +00:00
#define PW_MAX_BAUD 5
2022-06-29 10:37:36 +00:00
/* NOT static: also used from nut-scanner, so extern'ed via bcmxcp_ser.h */
pw_baud_rate_t pw_baud_rates[] = {
2010-03-25 23:20:59 +00:00
{ B19200, 19200 },
2013-11-24 15:00:12 +00:00
{ B9600, 9600 },
{ B4800, 4800 },
{ B2400, 2400 },
{ B1200, 1200 },
/* end of structure. */
{ 0, 0 }
2010-03-25 23:20:59 +00:00
};
2022-06-29 10:37:36 +00:00
/* NOT static: also used from nut-scanner, so extern'ed via bcmxcp_ser.h */
unsigned char BCMXCP_AUTHCMD[4] = {0xCF, 0x69, 0xE8, 0xD5}; /* Authorisation command */
2010-03-25 23:20:59 +00:00
2022-06-29 10:37:36 +00:00
static void send_command(unsigned char *command, size_t command_length)
2010-03-25 23:20:59 +00:00
{
2022-06-29 10:37:36 +00:00
int retry = 0;
ssize_t sent;
unsigned char sbuf[1024];
if (command_length > UCHAR_MAX) {
upsdebugx (3, "%s: ERROR: command_length too long for the character protocol", __func__);
return;
}
2010-03-25 23:20:59 +00:00
/* Prepare the send buffer */
sbuf[0] = PW_COMMAND_START_BYTE;
sbuf[1] = (unsigned char)(command_length);
memcpy(sbuf+2, command, command_length);
command_length += 2;
/* Add checksum */
sbuf[command_length] = calc_checksum(sbuf);
command_length += 1;
2012-06-01 13:55:19 +00:00
upsdebug_hex (3, "send_command", sbuf, command_length);
2010-03-25 23:20:59 +00:00
while (retry++ < PW_MAX_TRY) {
2011-06-01 20:31:49 +00:00
if (retry == PW_MAX_TRY) {
2013-11-24 15:00:12 +00:00
ser_send_char(upsfd, 0x1d); /* last retry is preceded by a ESC.*/
2011-06-01 20:31:49 +00:00
usleep(250000);
}
2010-03-25 23:20:59 +00:00
sent = ser_send_buf(upsfd, sbuf, command_length);
2022-06-29 10:37:36 +00:00
if (sent < 0) {
upslogx(LOG_ERR, "%s(): error reading from ser_send_buf()", __func__);
return;
}
if ((size_t)sent == command_length) {
2010-03-25 23:20:59 +00:00
return;
}
}
}
void send_read_command(unsigned char command)
{
send_command(&command, 1);
}
2022-06-29 10:37:36 +00:00
void send_write_command(unsigned char *command, size_t command_length)
2010-03-25 23:20:59 +00:00
{
send_command(command, command_length);
}
/* get the answer of a command from the ups. And check that the answer is for this command */
2022-06-29 10:37:36 +00:00
ssize_t get_answer(unsigned char *data, unsigned char command)
2010-03-25 23:20:59 +00:00
{
2013-11-24 15:00:12 +00:00
unsigned char my_buf[128]; /* packet has a maximum length of 121+5 bytes */
2022-06-29 10:37:36 +00:00
ssize_t res;
size_t length, end_length = 0, endblock = 0, start = 0;
2010-03-25 23:20:59 +00:00
unsigned char block_number, sequence, pre_sequence = 0;
2022-06-29 10:37:36 +00:00
while (endblock != 1) {
2010-03-25 23:20:59 +00:00
do {
/* Read PW_COMMAND_START_BYTE byte */
res = ser_get_char(upsfd, my_buf, 1, 0);
if (res != 1) {
2022-06-29 10:37:36 +00:00
upsdebugx(1,
"Receive error (PW_COMMAND_START_BYTE): %zd, cmd=%x!!!\n",
res, command);
2010-03-25 23:20:59 +00:00
return -1;
}
start++;
} while ((my_buf[0] != PW_COMMAND_START_BYTE) && (start < 128));
2022-06-29 10:37:36 +00:00
2010-03-25 23:20:59 +00:00
if (start == 128) {
ser_comm_fail("Receive error (PW_COMMAND_START_BYTE): packet not on start!!%x\n", my_buf[0]);
return -1;
}
/* Read block number byte */
2022-06-29 10:37:36 +00:00
res = ser_get_char(upsfd, my_buf + 1, 1, 0);
2010-03-25 23:20:59 +00:00
if (res != 1) {
2022-06-29 10:37:36 +00:00
ser_comm_fail("Receive error (Block number): %zd!!!\n", res);
2010-03-25 23:20:59 +00:00
return -1;
}
block_number = (unsigned char)my_buf[1];
if (command <= 0x43) {
2022-06-29 10:37:36 +00:00
if ((command - 0x30) != block_number) {
2010-03-25 23:20:59 +00:00
ser_comm_fail("Receive error (Request command): %x!!!\n", block_number);
return -1;
}
}
if (command >= 0x89) {
2022-06-29 10:37:36 +00:00
if ((command == 0xA0) && (block_number != 0x01)) {
2010-03-25 23:20:59 +00:00
ser_comm_fail("Receive error (Requested only mode command): %x!!!\n", block_number);
return -1;
}
2022-06-29 10:37:36 +00:00
if ((command != 0xA0) && (block_number != 0x09)) {
2010-03-25 23:20:59 +00:00
ser_comm_fail("Receive error (Control command): %x!!!\n", block_number);
return -1;
}
}
/* Read data length byte */
2022-06-29 10:37:36 +00:00
res = ser_get_char(upsfd, my_buf + 2, 1, 0);
2010-03-25 23:20:59 +00:00
if (res != 1) {
2022-06-29 10:37:36 +00:00
ser_comm_fail("Receive error (length): %zd!!!\n", res);
2010-03-25 23:20:59 +00:00
return -1;
}
length = (unsigned char)my_buf[2];
if (length < 1) {
2022-06-29 10:37:36 +00:00
ser_comm_fail("Receive error (length): packet length %zx!!!\n", length);
2010-03-25 23:20:59 +00:00
return -1;
}
/* Read sequence byte */
2022-06-29 10:37:36 +00:00
res = ser_get_char(upsfd, my_buf + 3, 1, 0);
2010-03-25 23:20:59 +00:00
if (res != 1) {
2022-06-29 10:37:36 +00:00
ser_comm_fail("Receive error (sequence): %zd!!!\n", res);
2010-03-25 23:20:59 +00:00
return -1;
}
sequence = (unsigned char)my_buf[3];
if ((sequence & 0x80) == 0x80) {
endblock = 1;
}
if ((sequence & 0x07) != (pre_sequence + 1)) {
ser_comm_fail("Not the right sequence received %x!!!\n", sequence);
return -1;
}
pre_sequence = sequence;
2022-06-29 10:37:36 +00:00
/* Try to read all the remaining bytes */
res = ser_get_buf_len(upsfd, my_buf + 4, length, 1, 0);
if (res < 0) {
ser_comm_fail("%s(): ser_get_buf_len() returned error code %zd", __func__, res);
return res;
}
2010-03-25 23:20:59 +00:00
2022-06-29 10:37:36 +00:00
if ((size_t)res != length) {
ser_comm_fail("Receive error (data): got %zd bytes instead of %zu!!!\n", res, length);
2010-03-25 23:20:59 +00:00
return -1;
}
/* Get the checksum byte */
2022-06-29 10:37:36 +00:00
res = ser_get_char(upsfd, my_buf + (4 + length), 1, 0);
2010-03-25 23:20:59 +00:00
if (res != 1) {
2022-06-29 10:37:36 +00:00
ser_comm_fail("Receive error (checksum): %zx!!!\n", res);
2010-03-25 23:20:59 +00:00
return -1;
}
/* now we have the whole answer from the ups, we can checksum it */
if (!checksum_test(my_buf)) {
ser_comm_fail("checksum error! ");
return -1;
}
2022-06-29 10:37:36 +00:00
memcpy(data+end_length, my_buf + 4, length);
2010-03-25 23:20:59 +00:00
end_length += length;
}
2012-06-01 13:55:19 +00:00
upsdebug_hex (5, "get_answer", data, end_length);
2010-03-25 23:20:59 +00:00
ser_comm_good();
2022-06-29 10:37:36 +00:00
assert(end_length < SSIZE_MAX);
return (ssize_t)end_length;
2010-03-25 23:20:59 +00:00
}
2022-06-29 10:37:36 +00:00
static ssize_t command_sequence(unsigned char *command, size_t command_length, unsigned char *answer)
2010-03-25 23:20:59 +00:00
{
2022-06-29 10:37:36 +00:00
ssize_t bytes_read;
int retry = 0;
2011-09-29 18:14:46 +00:00
2010-03-25 23:20:59 +00:00
while (retry++ < PW_MAX_TRY) {
if (retry == PW_MAX_TRY) {
ser_flush_in(upsfd, "", 0);
}
send_write_command(command, command_length);
bytes_read = get_answer(answer, *command);
if (bytes_read > 0) {
return bytes_read;
}
}
return -1;
}
/* Sends a single command (length=1). and get the answer */
2022-06-29 10:37:36 +00:00
ssize_t command_read_sequence(unsigned char command, unsigned char *answer)
2010-03-25 23:20:59 +00:00
{
2022-06-29 10:37:36 +00:00
ssize_t bytes_read;
2010-03-25 23:20:59 +00:00
bytes_read = command_sequence(&command, 1, answer);
if (bytes_read < 1) {
ser_comm_fail("Error executing command");
}
return bytes_read;
}
/* Sends a setup command (length > 1) */
2022-06-29 10:37:36 +00:00
ssize_t command_write_sequence(unsigned char *command, size_t command_length, unsigned char *answer)
2010-03-25 23:20:59 +00:00
{
2022-06-29 10:37:36 +00:00
ssize_t bytes_read;
2010-03-25 23:20:59 +00:00
bytes_read = command_sequence(command, command_length, answer);
if (bytes_read < 1) {
ser_comm_fail("Error executing command");
}
return bytes_read;
}
void upsdrv_comm_good()
{
ser_comm_good();
}
2022-06-29 10:37:36 +00:00
#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_BESIDEFUNC) && (!defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_INSIDEFUNC) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS_BESIDEFUNC) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE_BESIDEFUNC) )
# pragma GCC diagnostic push
#endif
#if (!defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_INSIDEFUNC) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS_BESIDEFUNC)
# pragma GCC diagnostic ignored "-Wtype-limits"
#endif
#if (!defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_INSIDEFUNC) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE_BESIDEFUNC)
# pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare"
#endif
static void pw_comm_setup(const char *port)
2010-03-25 23:20:59 +00:00
{
2013-11-24 15:00:12 +00:00
unsigned char command = PW_SET_REQ_ONLY_MODE;
unsigned char id_command = PW_ID_BLOCK_REQ;
unsigned char answer[256];
2022-06-29 10:37:36 +00:00
int i = 0;
ssize_t ret = -1;
speed_t mybaud = 0, baud;
2010-03-25 23:20:59 +00:00
if (getval("baud_rate") != NULL)
{
2022-06-29 10:37:36 +00:00
int br = atoi(getval("baud_rate"));
/* Note that atoi() behavior on erroneous input is undefined */
if (br < 0) {
upslogx(LOG_ERR, "baud_rate option is invalid");
return;
}
/* FIXME: speed_t does not define a SPEED_MAX value nor
* guarantee that it is an int (just a typedef from
* termios.h happens to say that on some systems)...
* But since we convert this setting from int, we assume...
*/
#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) )
/* Note for gating macros above: unsuffixed HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP
* means support of contexts both inside and outside function body, so the push
* above and pop below (outside this finction) are not used.
*/
# pragma GCC diagnostic push
#endif
#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS
/* Note that the individual warning pragmas for use inside function bodies
* are named without a _INSIDEFUNC suffix, for simplicity and legacy reasons
*/
# pragma GCC diagnostic ignored "-Wtype-limits"
#endif
#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE
#pragma GCC diagnostic ignored "-Wunreachable-code"
#endif
#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE
# pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare"
#endif
/* Older CLANG (e.g. clang-3.4) seems to not support the GCC pragmas above */
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunreachable-code"
#pragma clang diagnostic ignored "-Wtautological-compare"
#pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare"
#endif
switch(sizeof(speed_t)) {
case 8: assert (br < INT64_MAX); break;
case 4: assert (br < INT32_MAX); break;
case 2: assert (br < INT16_MAX); break;
default: assert (br < INT_MAX);
}
#ifdef __clang__
#pragma clang diagnostic pop
#endif
#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) )
# pragma GCC diagnostic pop
#endif
baud = (speed_t)br;
for (i = 0; i < PW_MAX_BAUD; i++) {
2010-03-25 23:20:59 +00:00
if (baud == pw_baud_rates[i].name) {
mybaud = pw_baud_rates[i].rate;
break;
}
}
if (mybaud == 0) {
fatalx(EXIT_FAILURE, "Specified baudrate \"%s\" is invalid!", getval("baud_rate"));
}
ser_set_speed(upsfd, device_path, mybaud);
2013-11-24 15:00:12 +00:00
ser_send_char(upsfd, 0x1d); /* send ESC to take it out of menu */
2010-03-25 23:20:59 +00:00
usleep(90000);
2022-06-29 10:37:36 +00:00
send_write_command(BCMXCP_AUTHCMD, 4);
2010-03-25 23:20:59 +00:00
usleep(500000);
ret = command_sequence(&command, 1, answer);
2011-06-01 20:31:49 +00:00
if (ret <= 0) {
usleep(500000);
ret = command_sequence(&id_command, 1, answer);
}
2010-03-25 23:20:59 +00:00
if (ret > 0) {
2022-06-29 10:37:36 +00:00
/* Cast baud into max length unsigned, despite the POSIX
* standard some systems vary in definition of this type
*/
upslogx(LOG_INFO, "Connected to UPS on %s with baudrate %llu", port, (unsigned long long int)baud);
2010-03-25 23:20:59 +00:00
return;
}
2022-06-29 10:37:36 +00:00
upslogx(LOG_ERR, "No response from UPS on %s with baudrate %llu", port, (unsigned long long int)baud);
2010-03-25 23:20:59 +00:00
}
upslogx(LOG_INFO, "Attempting to autodect baudrate");
for (i=0; i<PW_MAX_BAUD; i++) {
ser_set_speed(upsfd, device_path, pw_baud_rates[i].rate);
2013-11-24 15:00:12 +00:00
ser_send_char(upsfd, 0x1d); /* send ESC to take it out of menu */
2010-03-25 23:20:59 +00:00
usleep(90000);
2022-06-29 10:37:36 +00:00
send_write_command(BCMXCP_AUTHCMD, 4);
2010-03-25 23:20:59 +00:00
usleep(500000);
ret = command_sequence(&command, 1, answer);
2011-06-01 20:31:49 +00:00
if (ret <= 0) {
usleep(500000);
ret = command_sequence(&id_command, 1, answer);
}
2010-03-25 23:20:59 +00:00
if (ret > 0) {
2022-06-29 10:37:36 +00:00
upslogx(LOG_INFO, "Connected to UPS on %s with baudrate %zu", port, pw_baud_rates[i].name);
2010-03-25 23:20:59 +00:00
return;
}
2022-06-29 10:37:36 +00:00
upsdebugx(2, "No response from UPS on %s with baudrate %zu", port, pw_baud_rates[i].name);
2010-03-25 23:20:59 +00:00
}
fatalx(EXIT_FAILURE, "Can't connect to the UPS on port %s!\n", port);
}
2022-06-29 10:37:36 +00:00
#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_BESIDEFUNC) && (!defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_INSIDEFUNC) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS_BESIDEFUNC) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE_BESIDEFUNC) )
# pragma GCC diagnostic pop
#endif
2010-03-25 23:20:59 +00:00
void upsdrv_initups(void)
{
upsfd = ser_open(device_path);
pw_comm_setup(device_path);
}
void upsdrv_cleanup(void)
{
/* free(dynamic_mem); */
ser_close(upsfd, device_path);
}
void upsdrv_reconnect(void)
{
}
2013-11-24 15:00:12 +00:00