/* * 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 #include #include #include #include #include #include /* ----- 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; }