Files
2026-06-16 04:59:02 +10:00

144 lines
4.5 KiB
C

/*
* sppro-console - Linux serial-console dashboard for the Selectronic SP PRO.
*
* Opens the SP PRO serial port (57600 8N1), logs in, then polls the key
* registers on an interval and prints a refreshing table. Demonstrates wiring
* the portable core (src/sppro.c) to a real transport via termios.
*
* make host
* ./build/sppro-console /dev/ttyUSB0 [password] [interval_seconds]
*
* The password is the SP PRO serial-port password (Settings -> Comms). Pass ""
* (or omit) if no password is configured. It may also be set via SPPRO_PASSWORD.
*/
#define _DEFAULT_SOURCE /* cfmakeraw, CRTSCTS, sleep() under -std=c99 */
#include "sppro.h"
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
/* ----- termios transport -------------------------------------------------- */
static int serial_read(void *ctx, uint8_t *buf, size_t len)
{
int fd = *(int *)ctx;
ssize_t n = read(fd, buf, len);
if (n < 0) {
if (errno == EINTR || errno == EAGAIN) return 0;
return -1;
}
return (int)n; /* 0 on VTIME timeout */
}
static int serial_write(void *ctx, const uint8_t *buf, size_t len)
{
int fd = *(int *)ctx;
ssize_t n = write(fd, buf, len);
if (n < 0) {
if (errno == EINTR || errno == EAGAIN) return 0;
return -1;
}
return (int)n;
}
static int serial_open(const char *path)
{
struct termios tio;
int fd = open(path, O_RDWR | O_NOCTTY);
if (fd < 0) { perror("open"); return -1; }
if (tcgetattr(fd, &tio) != 0) { perror("tcgetattr"); close(fd); return -1; }
cfmakeraw(&tio);
cfsetispeed(&tio, B57600);
cfsetospeed(&tio, B57600);
tio.c_cflag |= (CLOCAL | CREAD);
tio.c_cflag &= ~CSTOPB; /* 1 stop bit */
tio.c_cflag &= ~PARENB; /* no parity */
tio.c_cflag &= ~CRTSCTS; /* no hardware flow control */
tio.c_cc[VMIN] = 0; /* non-blocking with timeout */
tio.c_cc[VTIME] = 2; /* 0.2s read timeout */
if (tcsetattr(fd, TCSANOW, &tio) != 0) { perror("tcsetattr"); close(fd); return -1; }
tcflush(fd, TCIOFLUSH);
return fd;
}
/* ----- dashboard ---------------------------------------------------------- */
/* Registers shown on the dashboard (names must exist in SPPRO_REGISTERS). */
static const char *const DASHBOARD[] = {
"BatteryVolts", "BattSocPercent", "BatteryTemperature", "DCBatteryPower",
"LoadAcPower", "CombinedKacoAcPowerHiRes", "ACGeneratorPower",
"Shunt1Power", "Shunt2Power",
"DCkWhInToday", "DCkWhOutToday", "ACLoadkWhTotalAcc", "TotalKacokWhTotalAcc",
};
static void print_dashboard(const sppro_transport_t *t, const sppro_scales_t *scales)
{
size_t i;
printf("\033[2J\033[H"); /* clear screen, home cursor */
printf("== Selectronic SP PRO ==\n\n");
for (i = 0; i < sizeof(DASHBOARD) / sizeof(DASHBOARD[0]); i++) {
const sppro_reg_t *reg = sppro_reg_by_name(DASHBOARD[i]);
double value;
int rc;
if (!reg) continue;
rc = sppro_session_read(t, reg, scales, &value);
if (rc != SPPRO_OK) {
printf(" %-26s (read error %d)\n", reg->description, rc);
continue;
}
if (reg->conv == SPPRO_C_SHUNT_NAME)
printf(" %-26s %12s\n", reg->description, sppro_shunt_name((int)value));
else
printf(" %-26s %12.2f %-3s\n", reg->description, value, reg->units);
}
fflush(stdout);
}
int main(int argc, char **argv)
{
const char *path = (argc > 1) ? argv[1] : "/dev/ttyUSB0";
const char *password = (argc > 2) ? argv[2] : getenv("SPPRO_PASSWORD");
int interval = (argc > 3) ? atoi(argv[3]) : 5;
int fd, rc;
sppro_transport_t t;
sppro_scales_t scales;
if (!password) password = "";
if (interval < 1) interval = 1;
fd = serial_open(path);
if (fd < 0) return 1;
t.read = serial_read;
t.write = serial_write;
t.ctx = &fd;
rc = sppro_session_login(&t, password);
if (rc != SPPRO_OK) {
fprintf(stderr, "login failed (%d) - check cabling and serial password\n", rc);
close(fd);
return 1;
}
rc = sppro_session_read_scales(&t, &scales);
if (rc != SPPRO_OK) {
fprintf(stderr, "could not read scale factors (%d)\n", rc);
close(fd);
return 1;
}
for (;;) {
print_dashboard(&t, &scales);
sleep((unsigned)interval);
}
close(fd); /* not reached */
return 0;
}