--- /dev/null
+/****************************************************************************
+ * Receive and decode data from Scialys module
+ *
+ * main.c
+ *
+ *
+ * Copyright 2013-2023 Nathael Pajani <nathael.pajani@ed3l.fr>
+ *
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ ****************************************************************************/
+
+
+/*
+ * This host app decodes the frames continuously sent by the scialys module on his
+ * serial line (UART)
+ */
+
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include "serial_utils.h"
+
+
+#define PROG_NAME "Sensors polling and decode"
+#define VERSION "0.2"
+
+
+void help(char *prog_name)
+{
+ fprintf(stderr, "---------------- "PROG_NAME" --------------------------------\n");
+ fprintf(stderr, "Usage: %s [options]\n" \
+ " Available options:\n" \
+ " \t -d | --device : Serial device to use for serial communication with the module\n" \
+ " \t -l | --log : Base filename to use for log files\n" \
+ " \t -h | --help : Print this help\n" \
+ " \t -v | --version : Print programm version\n" \
+ " All other arguments are data for the command, handled depending on the command.\n", prog_name);
+ fprintf(stderr, "-----------------------------------------------------------------------\n");
+}
+
+#define MAX_LOGNAME_LEN 200
+
+
+/* Scialys data, as sent on UART1 or stored on SD card.
+ *
+ * Header fields :
+ * The start byte is dedicated to UART communication and is always '#"
+ * Note that this byte may be repeated within the data packet, so the rest of the header
+ * should be checked before handling a packet
+ * The first checksum byte is so that the sum of all header bytes is zero
+ * The version byte indicates the version of this structure.
+ * The length is fixed so knowing the version gives the length.
+ * The data checksum is equal to the sum of all the data bytes
+ *
+ * Data fields :
+ * ...
+ * The flags can be expanded to a "struct flags" (see below) by shifting one bit to each byte.
+ *
+ */
+
+#define DATA_START '#'
+#define DATA_VERSION 0x01
+#define DATA_HEADER_LEN 4
+
+struct scialys_data {
+ /* Header */
+ uint8_t start;
+ uint8_t cksum;
+ uint8_t version;
+ uint8_t data_cksum;
+ /* Data */
+ uint32_t solar_prod_value;
+ uint32_t home_conso_value;
+ int water_centi_degrees;
+ int deci_degrees_power;
+ int deci_degrees_disp;
+ uint16_t load_power_lowest;
+ uint16_t load_power_highest;
+ uint8_t command_val;
+ uint8_t act_cmd;
+ uint8_t mode;
+ uint8_t flags;
+} __attribute__ ((__packed__));
+
+
+struct flags {
+ uint8_t fan_on;
+ uint8_t force_fan;
+ uint8_t error_shutdown;
+ uint8_t temp_shutdown;
+ uint8_t overvoltage;
+ uint8_t external_disable;
+ uint8_t forced_heater_mode;
+ uint8_t manual_activation_request;
+};
+
+
+
+#define BUF_SIZE 80
+#define MSG_LEN 32
+uint8_t data[BUF_SIZE];
+int data_idx = 0;
+
+int protocol_decode(uint8_t c)
+{
+ /* Start of packet */
+ if (data_idx == 0) {
+ if (c == '#') {
+ data[0] = c;
+ data_idx = 1;
+ }
+ return 0;
+ }
+ if (data_idx > 0) {
+ data[data_idx++] = c;
+ /* Full header received, check for header validity */
+ if (data_idx == 4) {
+ uint8_t sum = data[0] + data[1] + data[2] + data[3];
+ if (sum != 0) {
+ data_idx = 0;
+ return -1;
+ }
+ }
+ /* Full message received, validate data checksum */
+ if (data_idx >= MSG_LEN) {
+ int i = 0;
+ uint8_t sum = 0;
+ for (i = 4; i < MSG_LEN; i++) {
+ sum += data[i];
+ }
+ data_idx = 0;
+ if (sum != data[3]) {
+ return -1;
+ }
+ return 1;
+ }
+ }
+ return 0;
+}
+
+int main(int argc, char* argv[])
+{
+ /* Serial */
+ char* device = NULL;
+ int slave_fd = 0;
+
+ /* Log file */
+ char* log_base_name = NULL;
+ int data_log_fd = 0;
+
+ /* Timestamp */
+ struct timeval now;
+ int start_sec = 0;
+
+ while(1) {
+ int option_index = 0;
+ int c = 0;
+
+ struct option long_options[] = {
+ {"device", required_argument, 0, 'd'},
+ {"log", required_argument, 0, 'l'},
+ {"help", no_argument, 0, 'h'},
+ {"version", no_argument, 0, 'v'},
+ {0, 0, 0, 0}
+ };
+
+ c = getopt_long(argc, argv, "d:l:hv", long_options, &option_index);
+
+ /* no more options to parse */
+ if (c == -1) break;
+ switch (c) {
+
+ /* d, device */
+ case 'd':
+ device = optarg;
+ break;
+
+ /* l, base name for log file */
+ case 'l':
+ log_base_name = optarg;
+ break;
+
+ /* v, version */
+ case 'v':
+ printf("%s Version %s\n", PROG_NAME, VERSION);
+ return 0;
+ break;
+
+ /* h, help */
+ case 'h':
+ default:
+ help(argv[0]);
+ return 0;
+ }
+ }
+
+
+ /* Need Serial port as parameter */
+ if (device == NULL) {
+ printf("Error, need serial (tty) device\n");
+ help(argv[0]);
+ return -1;
+ }
+
+ /* Open tty */
+ slave_fd = serial_setup(device);
+ if (slave_fd < 0) {
+ printf("Unable to open specified serial port %s\n", device);
+ return -2;
+ }
+
+ /* Log files ? */
+ if (log_base_name != NULL) {
+ char name[MAX_LOGNAME_LEN];
+ mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
+
+ snprintf(name, MAX_LOGNAME_LEN, "%s.data", log_base_name);
+ data_log_fd = open(name, O_RDWR|O_CREAT|O_APPEND, mode);
+ if (data_log_fd <= 0) {
+ printf("Unable to open Data log file '%s' : %s", name, strerror(errno));
+ }
+ snprintf(name, MAX_LOGNAME_LEN, "Mode Sol Home Teau Tpow Tdis Pmin/Max Cmd/act Fan/fo Terr TMos OV Dis Force Manual\n");
+ write(data_log_fd, name, strlen(name));
+ }
+
+ /* Save start second */
+ gettimeofday(&now, NULL);
+ start_sec = now.tv_sec;
+
+
+ /* ************************************************* */
+ /* And never stop handling data ! */
+ while (1) {
+ int len = 0, ret = 0;
+ char buf[BUF_SIZE];
+ int idx = 0;
+ int sec = 0, msec = 0;
+
+ memset(buf, 0, BUF_SIZE);
+ /* Get serial data and try to build a packet */
+ len = read(slave_fd, buf, BUF_SIZE);
+ if (len <= 0) {
+ if (len < 0) {
+ perror("serial read error");
+ } else {
+ /* Read returnd 0 ... EOF */
+ printf("Serial disapeared ... closing and re-opening\n");
+ }
+ close(slave_fd);
+ do {
+ slave_fd = serial_setup(device);
+ if (slave_fd < 0) {
+ printf("Waiting for serial port %s\n", device);
+ usleep(10 * 1000);
+ }
+ } while (slave_fd < 0);
+ continue;
+ } else {
+ write(2, buf, len);
+ }
+
+ /* timestamp */
+ gettimeofday(&now, NULL);
+ sec = now.tv_sec - start_sec;
+ msec = now.tv_usec / 1000;
+
+ while (idx < len) {
+ /* Extract frames from data stream */
+ ret = protocol_decode(buf[idx]);
+ /* Check return code to know if we have a valid packet */
+ if (ret == 1) {
+ /* Valid packet received, parse data */
+ struct scialys_data sc;
+ struct flags f;
+ float diff;
+
+ memcpy(&sc, data, sizeof(struct scialys_data));
+
+ diff = ((float)((int)sc.solar_prod_value - (int)sc.home_conso_value) / 1000);
+ f.fan_on = (sc.flags & (0x01 << 0)) >> 0;
+ f.force_fan = (sc.flags & (0x01 << 1)) >> 1;
+ f.error_shutdown = (sc.flags & (0x01 << 2)) >> 2;
+ f.temp_shutdown = (sc.flags & (0x01 << 3)) >> 3;
+ f.overvoltage = (sc.flags & (0x01 << 4)) >> 4;
+ f.external_disable = (sc.flags & (0x01 << 5)) >> 5;
+ f.forced_heater_mode = (sc.flags & (0x01 << 6)) >> 6;
+ f.manual_activation_request = (sc.flags & (0x01 << 7)) >> 7;
+
+ /* Display data */
+ printf("\e[H"); /* Goto terminal home (top left) */
+ printf("\e[KMode: %c\n", sc.mode);
+ printf("\e[KProd: % 2d.%02dA, Conso: % 2d.%02dA, delta: %.2fA\n",
+ (sc.solar_prod_value / 1000), ((sc.solar_prod_value % 1000) / 10),
+ (sc.home_conso_value / 1000), ((sc.home_conso_value % 1000) / 10),
+ diff);
+ printf("\e[KCmd: %03d/%03d, Fan:%d/%d\n", sc.command_val, sc.act_cmd, f.fan_on, f.force_fan);
+ printf("\e[KWater: % 2d.%02d°C\n", (sc.water_centi_degrees / 100), (sc.water_centi_degrees % 100));
+ printf("\e[KInternal TempP: % 2d.%d°C, TempD: % 2d.%d°C\n",
+ (sc.deci_degrees_power / 10), (sc.deci_degrees_power % 10),
+ (sc.deci_degrees_disp / 10), (sc.deci_degrees_disp % 10));
+ printf("\e[KLoad: %03d - %03d\n", sc.load_power_lowest, sc.load_power_highest);
+ printf("\e[KExtdis: %d, Forced: %d, Manual: %d, ErrSD: %d, TempSD: %d, OverV: %d\n",
+ f.external_disable, f.forced_heater_mode, f.manual_activation_request,
+ f.error_shutdown, f.temp_shutdown, f.overvoltage);
+ printf("\e[K\n");
+
+ /* Log ? */
+ if (data_log_fd > 0) {
+ char buf[256];
+ snprintf(buf, 13, "% 5d.%03d : ", sec, msec);
+ write(data_log_fd, buf, 12);
+ snprintf(buf, 256, "%c:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d\n",
+ sc.mode, sc.solar_prod_value, sc.home_conso_value,
+ sc.water_centi_degrees, sc.deci_degrees_power, sc.deci_degrees_disp,
+ sc.load_power_lowest, sc.load_power_highest, sc.command_val,
+ sc.act_cmd, f.fan_on, f.force_fan, f.error_shutdown, f.temp_shutdown, f.overvoltage,
+ f.external_disable, f.forced_heater_mode, f.manual_activation_request);
+ write(data_log_fd, buf, strnlen(buf, 256));
+ }
+ }
+
+ idx++;
+ }
+
+ } /* End of infinite loop */
+
+ close(slave_fd);
+ return 0;
+}
+
+