#include #include #include #include #include #include #include #include #include #include #include #include #include #include // sterowanie logiem wysyłanych danych do InfluxDB (przez --log) int log_influx_data = 0; /* Set to 1 when --log parameter is provided */ /* ============================================ */ /* STRUCTS FOR MEASUREMENTS */ /* ============================================ */ typedef struct { float U1, U2, U3; } s_voltage; typedef struct { float I1, I2, I3; } s_current; typedef struct { float P_Tot, P1, P2, P3; } s_power; typedef struct { float W_Tot, W1, W2, W3; } s_energy; typedef struct { float Freq; } s_frequency; /* ============================================ */ /* KONFIGURACJA PROGRAMU - EDYTUJ TU */ /* ============================================ */ #define READ_LOOP true /* false = jeden odczyt, true = pętla co 5s */ #define VOLTAGE_BUFFER_SIZE 12 #define VOLTAGE_FLUCTUATION_THRESHOLD 0.05 // 5% #define HIGH_FREQ_MODE_DURATION 300 // 300 seconds #define READ_ORNO true /* Czy odczytywać ORNO */ #define READ_SUN2K true /* Czy odczytywać SUN2K */ /* Adresy urządzeń Modbus RTU */ #define SUN2000_SLAVE_ADR 3 #define ORNO_SLAVE_ADR 2 /* Port szeregowy */ #define USB_DEV_DEFAULT "/dev/ttyUSB0" /* Parametry timing dla ORNO (mikrosekund) */ #define ORNO_RTS_DELAY 5000 /* RTS delay: 5ms */ #define ORNO_BYTE_TIMEOUT 2500 /* Byte timeout: 2.5ms */ /* Parametry timing dla SUN2K (mikrosekund) */ #define SUN2K_RTS_DELAY 5000 /* RTS delay: 5ms */ #define SUN2K_BYTE_TIMEOUT 2500 /* Byte timeout: 2.5ms */ /* Przerwa między ORNO a SUN2K (mikrosekund) */ #define DELAY_BETWEEN_DEVICES 10000 /* 10ms */ /* MQTT - ustaw 1 aby wyłączyć MQTT */ #define DISABLE_MQTT 0 /* 0 = MQTT włączony, 1 = wyłączony */ /* Broker MQTT */ #define MQTT_BROKER "10.1.1.1" #define MQTT_PORT 1883 /* InfluxDB Configuration */ #define DISABLE_INFLUX 0 /* 0 = InfluxDB włączony, 1 = wyłączony */ #define INFLUX_HOST "10.1.1.1" #define INFLUX_PORT 5086 #define INFLUX_ORG "sic" /* Zmień na swoją organizację */ #define INFLUX_BUCKET "energydb" /* Zmień na swój bucket */ #define INFLUX_TOKEN_DEFAULT "BCIZ6kcCAVgpcwSfU0PBS7m0Zb6an93kuOtizbEtlXub-uaoYp4dmIQBQYaJCv8_KE4QYYZ08jxtpaZ3TUWP-Q==" /* Fallback - preferuj zmienną INFLUX_TOKEN */ /* ============================================ */ /* 'Model' : 'ID' 'addr' : '30000', 'registers' : 15, 'name' : 'Model', 'scale' : 1, 'type' : 'str', 'units' : '' , 'use' : 'info', 'method' : 'hold' */ enum regtype { rgStr, rgU16, rgU32, rgI16, rgI32, rgB16, rgB32, rgData, rgFloat }; typedef struct s_mb_reg { const char *reg_id; int ireg; int num_reg; char *desc; int scale; enum regtype type; char *units; char *use; char *method; char buf_last_val[30]; } t_mb_reg; t_mb_reg mbReg[] = { {"Start", 32091, 2, "Startup time", 1, rgData, "s", "info", "hold", ""}, {"Shutdown", 32093, 2, "Shutdown time", 1, rgData, "s", "info", "hold", ""}, {"Time", 40000, 2, "Current time", 1, rgData, "s", "info", "hold", ""}, {"State1", 32000, 1, "Status 1", 1, rgB16, "", "stat", "hold", ""}, {"Alarm1", 32008, 1, "Alarm 1", 1, rgB16, "", "stat", "hold", ""}, {"Status", 32089, 1, "Device status", 1, rgU16, "", "stat", "hold", ""}, {"Fault", 32090, 1, "Fault code", 1, rgU16, "", "stat", "hold", ""}, {"PV_P", 32064, 2, "Input power", 1000, rgI32, "kW", "data", "hold", ""}, {"U_A", 32069, 1, "Phase Voltage A", 10, rgU16, "V", "data", "hold", ""}, {"U_B", 32070, 1, "Phase Voltage B", 10, rgU16, "V", "data", "hold", ""}, {"U_C", 32071, 1, "Phase Voltage C", 10, rgU16, "V", "ext", "hold", ""}, {"P_peak", 32078, 2, "Peak Power", 1000, rgI32, "kW", "data", "hold", ""}, {"P_active", 32080, 2, "Active power", 1000, rgI32, "kW", "data", "hold", ""}, {"P_reactive", 32082, 2, "Reactive power", 1000, rgI32, "kVar", "data", "hold", ""}, {"Frequency", 32085, 1, "Grid frequency", 100, rgU16, "Hz", "data", "hold", ""}, {"Temp", 32087, 1, "Internal temperature", 10, rgI16, "°C", "data", "hold", ""}, {"P_accum", 32106, 2, "Accumulated energy yield", 100, rgU32, "kWh", "data", "hold", ""}, {"P_daily", 32114, 2, "Daily energy yield", 100, rgU32, "kWh", "data", "hold", ""} }; void mosq_log_callback(struct mosquitto *mosq, void *userdata, int level, const char *str) { /* Pring all log messages regardless of level. */ switch (level) { //case MOSQ_LOG_DEBUG: //case MOSQ_LOG_INFO: //case MOSQ_LOG_NOTICE: case MOSQ_LOG_WARNING: case MOSQ_LOG_ERR: { printf("MQTT: %i:%s\n", level, str); } } } const char *influx_token = NULL; struct mosquitto *mosq = NULL; void mqtt_setup() { if (DISABLE_MQTT) { printf("MQTT: disabled by configuration\n"); return; } char *host = MQTT_BROKER; int port = MQTT_PORT; int keepalive = 60; bool clean_session = true; mosquitto_lib_init(); mosq = mosquitto_new(NULL, clean_session, NULL); if (!mosq) { fprintf(stderr, "MQTT: Error: Out of memory.\n"); exit(1); } mosquitto_log_callback_set(mosq, mosq_log_callback); if (mosquitto_connect(mosq, host, port, keepalive)) { fprintf(stderr, "MQTT: Unable to connect.\n"); exit(1); } int loop = mosquitto_loop_start(mosq); if (loop != MOSQ_ERR_SUCCESS) { fprintf(stderr, "MQTT: Unable to start loop: %i\n", loop); exit(1); } } int mqtt_send(char *topic, char *buf) { if (DISABLE_MQTT) return 0; return mosquitto_publish(mosq, NULL, topic, strlen(buf), buf, 0, 0); } int mqtt_send_SUN2K(t_mb_reg *reg_to_send, char *_str_buf) { if (DISABLE_MQTT) return 0; /* Don't send if str_buf is empty or NULL - means read failed */ if (!_str_buf || strlen(_str_buf) == 0) { printf("MQTT: Skipping %s - no valid data\n", reg_to_send->reg_id); return 0; } char topic[128]; char buf[128]; snprintf(topic, sizeof(topic), "/energy/SUN2K"); snprintf(buf, sizeof(buf), "{\"%s\":%s}", reg_to_send->reg_id, _str_buf); /* Save last valid value */ strncpy(reg_to_send->buf_last_val, _str_buf, sizeof(reg_to_send->buf_last_val)-1); reg_to_send->buf_last_val[sizeof(reg_to_send->buf_last_val)-1] = '\0'; printf("MQTT: Publishing %s\n", buf); return mosquitto_publish(mosq, NULL, topic, strlen(buf), buf, 0, 0); } int mqtt_send_U(float U1, float U2, float U3) { if (DISABLE_MQTT) return 0; char buf[128]; snprintf(buf, sizeof(buf), "{\"U1\":%f,\"U2\":%f,\"U3\":%f}", U1, U2, U3); return mosquitto_publish(mosq, NULL, "/energy/orno/U", strlen(buf), buf, 0, 0); } int mqtt_send_P(float P_Tot, float P1, float P2, float P3) { if (DISABLE_MQTT) return 0; char buf[128]; snprintf(buf, sizeof(buf), "{\"P_Tot\":%f,\"P1\":%f,\"P2\":%f,\"P3\":%f}", P_Tot, P1, P2, P3); return mosquitto_publish(mosq, NULL, "/energy/orno/P", strlen(buf), buf, 0, 0); } int mqtt_send_W(float W_Tot, float W1, float W2, float W3) { if (DISABLE_MQTT) return 0; char buf[128]; snprintf(buf, sizeof(buf), "{\"W_Tot\":%f,\"W1\":%f,\"W2\":%f,\"W3\":%f}", W_Tot, W1, W2, W3); return mosquitto_publish(mosq, NULL, "/energy/orno/W", strlen(buf), buf, 0, 0); } int mqtt_send_I(float I1, float I2, float I3) { if (DISABLE_MQTT) return 0; char buf[128]; snprintf(buf, sizeof(buf), "{\"I1\":%f,\"I2\":%f,\"I3\":%f}", I1, I2, I3); return mosquitto_publish(mosq, NULL, "/energy/orno/I", strlen(buf), buf, 0, 0); } int mqtt_send_Hz(float Hz) { if (DISABLE_MQTT) return 0; char buf[64]; snprintf(buf, sizeof(buf), "%f", Hz); return mosquitto_publish(mosq, NULL, "/energy/orno/Hz", strlen(buf), buf, 0, 0); } int mqtt_send_SUN2K_U(float U_A, float U_B, float U_C) { if (DISABLE_MQTT) return 0; char buf[128]; snprintf(buf, sizeof(buf), "{\"U_A\":%f,\"U_B\":%f,\"U_C\":%f}", U_A, U_B, U_C); return mosquitto_publish(mosq, NULL, "/energy/sun2k/U", strlen(buf), buf, 0, 0); } /* InfluxDB Functions */ int influx_send_post(char *data) { if (DISABLE_INFLUX) return 0; /* Log data to screen if --log parameter was provided */ if (log_influx_data) { printf("INFLUX: Sending data: %s\n", data); } int sock; struct sockaddr_in server; sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == -1) { perror("INFLUX: Could not create socket for InfluxDB"); return -1; } server.sin_addr.s_addr = inet_addr(INFLUX_HOST); server.sin_family = AF_INET; server.sin_port = htons(INFLUX_PORT); /* Set timeout for socket */ struct timeval tv; tv.tv_sec = 2; tv.tv_usec = 0; setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv); setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof tv); if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0) { perror("INFLUX: Connect to InfluxDB failed"); close(sock); return -1; } char header[1024]; /* Construct HTTP POST request for InfluxDB v2 Write API */ snprintf(header, sizeof(header), "POST /api/v2/write?org=%s&bucket=%s&precision=s HTTP/1.1\r\n" "Host: %s:%d\r\n" "Authorization: Token %s\r\n" "Content-Length: %ld\r\n" "Content-Type: text/plain; charset=utf-8\r\n" "Connection: close\r\n\r\n", INFLUX_ORG, INFLUX_BUCKET, INFLUX_HOST, INFLUX_PORT, influx_token, strlen(data)); if (send(sock, header, strlen(header), 0) < 0) { perror("INFLUX: Send header to InfluxDB failed"); close(sock); return -1; } if (send(sock, data, strlen(data), 0) < 0) { perror("INFLUX: Send body to InfluxDB failed"); close(sock); return -1; } /* Read HTTP response to check for errors */ char resp[512]; int resp_len = recv(sock, resp, sizeof(resp) - 1, 0); if (resp_len > 0) { resp[resp_len] = '\0'; /* Check HTTP status code (e.g. "HTTP/1.1 204") */ int http_status = 0; if (sscanf(resp, "HTTP/%*d.%*d %d", &http_status) == 1) { if (http_status < 200 || http_status >= 300) { printf("INFLUX: ERROR: HTTP %d response from InfluxDB\n", http_status); close(sock); return -1; } } } else if (resp_len < 0) { printf("INFLUX: WARNING: No response from InfluxDB (timeout)\n"); } close(sock); return 0; } int influx_send_orno_batch(s_voltage *v, int v_ok, s_current *i, int i_ok, s_power *p, int p_ok, s_energy *w, int w_ok, s_frequency *f, int f_ok) { if (DISABLE_INFLUX) return 0; char batch[1024]; int offset = 0; /* New format: group by phase with U, I, P, W fields */ /* For each phase, build field list based on available data */ // Phase L1 if (v_ok || i_ok || p_ok || w_ok) { char fields[256] = ""; int foff = 0; if (v_ok) foff += snprintf(fields + foff, sizeof(fields) - foff, "U=%.2f,", v->U1); if (i_ok) foff += snprintf(fields + foff, sizeof(fields) - foff, "I=%.2f,", i->I1); if (p_ok) foff += snprintf(fields + foff, sizeof(fields) - foff, "P=%.2f,", p->P1); if (w_ok) foff += snprintf(fields + foff, sizeof(fields) - foff, "W=%.2f,", w->W1); if (foff > 0) fields[foff - 1] = '\0'; // remove trailing comma offset += snprintf(batch + offset, sizeof(batch) - offset, "energydb,device=orno,phase=L1 %s\n", fields); } // Phase L2 if (v_ok || i_ok || p_ok || w_ok) { char fields[256] = ""; int foff = 0; if (v_ok) foff += snprintf(fields + foff, sizeof(fields) - foff, "U=%.2f,", v->U2); if (i_ok) foff += snprintf(fields + foff, sizeof(fields) - foff, "I=%.2f,", i->I2); if (p_ok) foff += snprintf(fields + foff, sizeof(fields) - foff, "P=%.2f,", p->P2); if (w_ok) foff += snprintf(fields + foff, sizeof(fields) - foff, "W=%.2f,", w->W2); if (foff > 0) fields[foff - 1] = '\0'; offset += snprintf(batch + offset, sizeof(batch) - offset, "energydb,device=orno,phase=L2 %s\n", fields); } // Phase L3 if (v_ok || i_ok || p_ok || w_ok) { char fields[256] = ""; int foff = 0; if (v_ok) foff += snprintf(fields + foff, sizeof(fields) - foff, "U=%.2f,", v->U3); if (i_ok) foff += snprintf(fields + foff, sizeof(fields) - foff, "I=%.2f,", i->I3); if (p_ok) foff += snprintf(fields + foff, sizeof(fields) - foff, "P=%.2f,", p->P3); if (w_ok) foff += snprintf(fields + foff, sizeof(fields) - foff, "W=%.2f,", w->W3); if (foff > 0) fields[foff - 1] = '\0'; offset += snprintf(batch + offset, sizeof(batch) - offset, "energydb,device=orno,phase=L3 %s\n", fields); } // Phase total if (p_ok || w_ok) { char fields[256] = ""; int foff = 0; if (p_ok) foff += snprintf(fields + foff, sizeof(fields) - foff, "P=%.2f,", p->P_Tot); if (w_ok) foff += snprintf(fields + foff, sizeof(fields) - foff, "W=%.2f,", w->W_Tot); if (foff > 0) fields[foff - 1] = '\0'; offset += snprintf(batch + offset, sizeof(batch) - offset, "energydb,device=orno,phase=total %s\n", fields); } if (f_ok) { offset += snprintf(batch + offset, sizeof(batch) - offset, "energydb,device=orno,frequency=Hz value=%.4f\n", f->Freq); } if (offset == 0) return 0; /* Remove trailing newline */ if (offset > 0 && batch[offset - 1] == '\n') batch[offset - 1] = '\0'; return influx_send_post(batch); } int influx_send_SUN2K(t_mb_reg *reg_to_send, char *_str_buf) { if (!_str_buf || strlen(_str_buf) == 0) return 0; char line[256]; char pomiar_tag[32] = ""; char field_name[32] = ""; /* Mapowanie rejestrów SUN2K do jednolitego formatu ORNO */ if (strcmp(reg_to_send->reg_id, "PV_P") == 0 || strcmp(reg_to_send->reg_id, "P_active") == 0 || strcmp(reg_to_send->reg_id, "P_peak") == 0) { /* Moc - mapujemy do pomiar=power, pole=total */ strcpy(pomiar_tag, ",pomiar=power"); strcpy(field_name, "total"); } else if (strcmp(reg_to_send->reg_id, "P_reactive") == 0) { /* Moc bierna - osobny pomiar */ strcpy(pomiar_tag, ",pomiar=reactive_power"); strcpy(field_name, "total"); } else if (strcmp(reg_to_send->reg_id, "Frequency") == 0) { /* Częstotliwość - mapujemy do pomiar=frequency, pole=frequency */ strcpy(pomiar_tag, ",pomiar=frequency"); strcpy(field_name, "frequency"); } else if (strcmp(reg_to_send->reg_id, "Temp") == 0) { /* Temperatura */ strcpy(pomiar_tag, ",pomiar=temperature"); strcpy(field_name, "value"); } else if (strcmp(reg_to_send->reg_id, "P_accum") == 0) { /* Energia zgromadzona */ strcpy(pomiar_tag, ",pomiar=energy"); strcpy(field_name, "total"); } else if (strcmp(reg_to_send->reg_id, "P_daily") == 0) { /* Energia dzienna - osobny pomiar */ strcpy(pomiar_tag, ",pomiar=daily_energy"); strcpy(field_name, "total"); } else if (strcmp(reg_to_send->reg_id, "State1") == 0 || strcmp(reg_to_send->reg_id, "Alarm1") == 0 || strcmp(reg_to_send->reg_id, "Status") == 0 || strcmp(reg_to_send->reg_id, "Fault") == 0) { /* Statusy */ strcpy(pomiar_tag, ",pomiar=status"); strcpy(field_name, reg_to_send->reg_id); } else if (strcmp(reg_to_send->reg_id, "Start") == 0 || strcmp(reg_to_send->reg_id, "Shutdown") == 0 || strcmp(reg_to_send->reg_id, "Time") == 0) { /* Info/czas */ strcpy(pomiar_tag, ",pomiar=info"); strcpy(field_name, reg_to_send->reg_id); } else { /* Domyślnie - nazwa rejestru jako nazwa pola */ strcpy(field_name, reg_to_send->reg_id); } snprintf(line, sizeof(line), "sun2k,device=sun2k%s %s=%s", pomiar_tag, field_name, _str_buf); return influx_send_post(line); } int influx_send_SUN2K_U_batch(float U_A, float U_B, float U_C, int valid) { if (DISABLE_INFLUX) return 0; if (!valid) return 0; char batch[256]; /* Ujednolicone z ORNO - L1, L2, L3 zamiast U_A, U_B, U_C */ snprintf(batch, sizeof(batch), "sun2k,device=sun2k,pomiar=voltage L1=%.2f,L2=%.2f,L3=%.2f", U_A, U_B, U_C); return influx_send_post(batch); } /* Validate float value - check if not NaN, Inf, or out of reasonable range */ int is_valid_float(float value, float min_val, float max_val) { if (isnan(value)) { printf("VALIDATE: WARNING: Value is NaN\n"); return 0; } if (isinf(value)) { printf("VALIDATE: WARNING: Value is Inf\n"); return 0; } if (value < min_val || value > max_val) { printf("VALIDATE: WARNING: Value %.2f out of range [%.2f, %.2f]\n", value, min_val, max_val); return 0; } return 1; } /* Timed wrapper for modbus_read_registers - logs start/stop and duration */ int modbus_read_timed(modbus_t *ctx, int addr, int nb, uint16_t *dest) { struct timeval t0, t1; gettimeofday(&t0, NULL); int res = modbus_read_registers(ctx, addr, nb, dest); gettimeofday(&t1, NULL); long ms = (t1.tv_sec - t0.tv_sec) * 1000 + (t1.tv_usec - t0.tv_usec) / 1000; if (res < 0) { printf("MBUS: modbus_read_registers addr=0x%X nb=%d -> ERR (%s) elapsed=%ldms\n", addr, nb, modbus_strerror(errno), ms); } else { printf("MBUS: modbus_read_registers addr=0x%X nb=%d -> OK (%d) elapsed=%ldms\n", addr, nb, res, ms); } return res; } int mosq_test() { mqtt_setup(); int i = -1000; int k = 10; char *buf = malloc(64); while (k-- > 0) { snprintf(buf, 64, "i=%i", i++); int snd = mqtt_send("/testtopic", buf); if (snd != 0) printf("TEST: mqtt_send error=%i\n", snd); usleep(5000000); } } int main(int argc, char *argv[]) { /* Parse command line arguments */ for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "--log") == 0) { log_influx_data = 1; printf("CFG: InfluxDB data logging enabled (--log)\n"); } } /* Użyj wartości z #define, token z env lub fallback */ influx_token = getenv("INFLUX_TOKEN"); if (!influx_token || strlen(influx_token) == 0) { influx_token = INFLUX_TOKEN_DEFAULT; printf("CFG: INFLUX_TOKEN env not set, using default from code\n"); } else { printf("CFG: INFLUX_TOKEN loaded from environment\n"); } const char *USB_DEV = USB_DEV_DEFAULT; int ORNO_SLAVE = ORNO_SLAVE_ADR; int SUN2000_SLAVE = SUN2000_SLAVE_ADR; int do_orno = READ_ORNO; int do_sun2k = READ_SUN2K; s_voltage prev_voltage = {0}, current_voltage = {0}; s_current prev_current = {0}, current_current = {0}; s_power prev_power = {0}, current_power = {0}; s_energy prev_energy = {0}, current_energy = {0}; s_frequency prev_freq = {0}, current_freq = {0}; int first_run = 1; float voltage_buffer_L1[VOLTAGE_BUFFER_SIZE] = {0}; float voltage_buffer_L2[VOLTAGE_BUFFER_SIZE] = {0}; float voltage_buffer_L3[VOLTAGE_BUFFER_SIZE] = {0}; int voltage_buffer_index = 0; int voltage_buffer_items = 0; time_t high_frequency_mode_end_time = 0; int high_frequency_mode_active = 0; /* Flag to track if we notified about high freq mode */ time_t rawtime; struct tm *timeinfo; time(&rawtime); timeinfo = localtime(&rawtime); printf("==============================================\n"); printf("Energy Meter Reader - ORNO & SUN2K\n"); printf("Start: %s", asctime(timeinfo)); printf("==============================================\n"); printf("CFG: Configuration:\n"); printf("CFG: Device: %s\n", USB_DEV); printf("CFG: ORNO slave address: %d\n", ORNO_SLAVE); printf("CFG: SUN2K slave address: %d\n", SUN2000_SLAVE); printf("CFG: MQTT: %s\n", DISABLE_MQTT ? "DISABLED" : "ENABLED"); printf("CFG: MQTT Broker: %s:%d\n", MQTT_BROKER, MQTT_PORT); printf("CFG: InfluxDB: %s (%s:%d)\n", DISABLE_INFLUX ? "DISABLED" : "ENABLED", INFLUX_HOST, INFLUX_PORT); printf("==============================================\n\n"); mqtt_setup(); do { if (do_orno) { //Create a new RTU context modbus_t *ctx = modbus_new_rtu(USB_DEV, 9600, 'E', 8, 1); if (!ctx) { fprintf(stderr, "ORNO: Failed to create the context: %s\n", modbus_strerror(errno)); exit(1); } //Set the Modbus address of the remote slave modbus_set_slave(ctx, ORNO_SLAVE); if (modbus_connect(ctx) == -1) { fprintf(stderr, "ORNO: Unable to connect: %s\n", modbus_strerror(errno)); modbus_free(ctx); exit(1); } modbus_rtu_set_rts_delay(ctx, ORNO_RTS_DELAY); modbus_set_response_timeout(ctx, 0, 900000); /* 0.9s */ modbus_set_byte_timeout(ctx, 0, ORNO_BYTE_TIMEOUT); printf("ORNO: RTS Delay and Timeouts configured.\n"); uint16_t reg[32]; int num; // ---- READ ALL ORNO VALUES ---- // VOLTAGES num = modbus_read_timed(ctx, 0xe, 6, reg); if (num == 6) { current_voltage.U1 = modbus_get_float_abcd(®[0]); current_voltage.U2 = modbus_get_float_abcd(®[2]); current_voltage.U3 = modbus_get_float_abcd(®[4]); } else { printf("ORNO: Failed to read voltages: %s\n", modbus_strerror(errno)); current_voltage = prev_voltage; // On error, use previous values } usleep(100000); // CURRENTS num = modbus_read_timed(ctx, 0x16, 6, reg); if (num == 6) { current_current.I1 = modbus_get_float_abcd(®[0]); current_current.I2 = modbus_get_float_abcd(®[2]); current_current.I3 = modbus_get_float_abcd(®[4]); } else { printf("ORNO: Failed to read currents: %s\n", modbus_strerror(errno)); current_current = prev_current; } usleep(100000); // POWER num = modbus_read_timed(ctx, 0x1c, 8, reg); if (num == 8) { current_power.P_Tot = modbus_get_float_abcd(®[0]); current_power.P1 = modbus_get_float_abcd(®[2]); current_power.P2 = modbus_get_float_abcd(®[4]); current_power.P3 = modbus_get_float_abcd(®[6]); } else { printf("ORNO: Failed to read power: %s\n", modbus_strerror(errno)); current_power = prev_power; } usleep(100000); // ENERGY num = modbus_read_timed(ctx, 0x100, 8, reg); if (num == 8) { current_energy.W_Tot = modbus_get_float_abcd(®[0]); current_energy.W1 = modbus_get_float_abcd(®[2]); current_energy.W2 = modbus_get_float_abcd(®[4]); current_energy.W3 = modbus_get_float_abcd(®[6]); } else { printf("ORNO: Failed to read energy: %s\n", modbus_strerror(errno)); current_energy = prev_energy; } usleep(10000); // FREQUENCY num = modbus_read_timed(ctx, 0x14, 2, reg); if (num == 2) { current_freq.Freq = modbus_get_float_abcd(®[0]); } else { printf("ORNO: Failed to read frequency: %s\n", modbus_strerror(errno)); current_freq = prev_freq; } modbus_close(ctx); modbus_free(ctx); // --- VOLTAGE FLUCTUATION DETECTION (uses fresh, unfiltered values) --- voltage_buffer_L1[voltage_buffer_index] = current_voltage.U1; voltage_buffer_L2[voltage_buffer_index] = current_voltage.U2; voltage_buffer_L3[voltage_buffer_index] = current_voltage.U3; voltage_buffer_index = (voltage_buffer_index + 1) % VOLTAGE_BUFFER_SIZE; if (voltage_buffer_items < VOLTAGE_BUFFER_SIZE) { voltage_buffer_items++; } float avg_L1 = 0, avg_L2 = 0, avg_L3 = 0; for (int i = 0; i < voltage_buffer_items; i++) { avg_L1 += voltage_buffer_L1[i]; avg_L2 += voltage_buffer_L2[i]; avg_L3 += voltage_buffer_L3[i]; } avg_L1 /= voltage_buffer_items; avg_L2 /= voltage_buffer_items; avg_L3 /= voltage_buffer_items; if (fabs(current_voltage.U1 - avg_L1) > avg_L1 * VOLTAGE_FLUCTUATION_THRESHOLD || fabs(current_voltage.U2 - avg_L2) > avg_L2 * VOLTAGE_FLUCTUATION_THRESHOLD || fabs(current_voltage.U3 - avg_L3) > avg_L3 * VOLTAGE_FLUCTUATION_THRESHOLD) { printf("ORNO: Voltage fluctuation detected! Switching to high frequency polling for %d seconds.\n", HIGH_FREQ_MODE_DURATION); mqtt_send("/energy/orno/fluct", "on"); influx_send_post("orno,device=orno,highfluct=start highfluct=1"); high_frequency_mode_end_time = time(NULL) + HIGH_FREQ_MODE_DURATION; high_frequency_mode_active = 1; } // --- DATA VALIDATION AND SENDING --- if (first_run) { prev_voltage = current_voltage; prev_current = current_current; prev_power = current_power; prev_energy = current_energy; prev_freq = current_freq; first_run = 0; printf("ORNO: First run, buffering initial measurements.\n"); } else { s_voltage send_voltage; s_current send_current; s_power send_power; s_energy send_energy; s_frequency send_freq; // VOLTAGE check int spike_U = 0; if (prev_voltage.U1 != 0 && (current_voltage.U1 > prev_voltage.U1 * 1.8 || current_voltage.U1 < prev_voltage.U1 * 0.2)) spike_U = 1; if (prev_voltage.U2 != 0 && (current_voltage.U2 > prev_voltage.U2 * 1.8 || current_voltage.U2 < prev_voltage.U2 * 0.2)) spike_U = 1; if (prev_voltage.U3 != 0 && (current_voltage.U3 > prev_voltage.U3 * 1.8 || current_voltage.U3 < prev_voltage.U3 * 0.2)) spike_U = 1; if(spike_U) { send_voltage = prev_voltage; printf("ORNO: Voltage spike detected. Sending previous values.\n"); } else { send_voltage = current_voltage; } // Always update prev_voltage to prevent getting stuck on old values prev_voltage = current_voltage; // CURRENT check int spike_I = 0; if (prev_current.I1 != 0 && (current_current.I1 > prev_current.I1 * 1.8 || current_current.I1 < prev_current.I1 * 0.2)) spike_I = 1; if (prev_current.I2 != 0 && (current_current.I2 > prev_current.I2 * 1.8 || current_current.I2 < prev_current.I2 * 0.2)) spike_I = 1; if (prev_current.I3 != 0 && (current_current.I3 > prev_current.I3 * 1.8 || current_current.I3 < prev_current.I3 * 0.2)) spike_I = 1; if(spike_I) { send_current = prev_current; printf("ORNO: Current spike detected. Sending previous values.\n"); } else { send_current = current_current; } // Always update prev_current to prevent getting stuck on old values prev_current = current_current; // POWER check int spike_P = 0; if (prev_power.P_Tot != 0 && (fabs(current_power.P_Tot - prev_power.P_Tot) > fabs(prev_power.P_Tot) * 0.8)) spike_P = 1; if(spike_P) { send_power = prev_power; printf("ORNO: Power spike detected. Sending previous values.\n"); } else { send_power = current_power; } // Always update prev_power to prevent getting stuck on old values prev_power = current_power; // ENERGY check (should only increase) int spike_W = 0; if (prev_energy.W_Tot != 0 && (current_energy.W_Tot > prev_energy.W_Tot * 1.8 || current_energy.W_Tot < prev_energy.W_Tot)) spike_W = 1; if(spike_W) { send_energy = prev_energy; printf("ORNO: Energy spike/drop detected. Sending previous values.\n"); } else { send_energy = current_energy; prev_energy = current_energy; } // FREQUENCY check (5% threshold) int spike_F = 0; if (prev_freq.Freq != 0 && (current_freq.Freq > prev_freq.Freq * 1.05 || current_freq.Freq < prev_freq.Freq * 0.95)) spike_F = 1; // 5% for frequency if(spike_F) { send_freq = prev_freq; printf("ORNO: Frequency spike detected. Sending previous values.\n"); } else { send_freq = current_freq; prev_freq = current_freq; } // --- Now send the selected values --- int valid_U = 0, valid_I = 0, valid_P = 0, valid_W = 0, valid_F = 0; printf("ORNO: Voltages: L1=%.1f V, L2=%.1f V, L3=%.1f V\n", send_voltage.U1, send_voltage.U2, send_voltage.U3); if (is_valid_float(send_voltage.U1, 150.0, 280.0) && is_valid_float(send_voltage.U2, 150.0, 280.0) && is_valid_float(send_voltage.U3, 150.0, 280.0)) { mqtt_send_U(send_voltage.U1, send_voltage.U2, send_voltage.U3); valid_U = 1; printf("ORNO: MQTT: Published voltages\n"); } else { printf("ORNO: MQTT: Skipping voltages - invalid values\n"); } printf("ORNO: Currents: L1=%.2f A, L2=%.2f A, L3=%.2f A\n", send_current.I1, send_current.I2, send_current.I3); if (is_valid_float(send_current.I1, 0.0, 100.0) && is_valid_float(send_current.I2, 0.0, 100.0) && is_valid_float(send_current.I3, 0.0, 100.0)) { mqtt_send_I(send_current.I1, send_current.I2, send_current.I3); valid_I = 1; printf("ORNO: MQTT: Published currents\n"); } else { printf("ORNO: MQTT: Skipping currents - invalid values\n"); } printf("ORNO: Power: Total=%.3f W, L1=%.3f W, L2=%.3f W, L3=%.3f W\n", send_power.P_Tot, send_power.P1, send_power.P2, send_power.P3); if (is_valid_float(send_power.P_Tot, -25000.0, 25000.0) && is_valid_float(send_power.P1, -10000.0, 10000.0) && is_valid_float(send_power.P2, -10000.0, 10000.0) && is_valid_float(send_power.P3, -10000.0, 10000.0)) { mqtt_send_P(send_power.P_Tot, send_power.P1, send_power.P2, send_power.P3); valid_P = 1; printf("ORNO: MQTT: Published power\n"); } else { printf("ORNO: MQTT: Skipping power - invalid values\n"); } printf("ORNO: Energy: Total=%.3f kWh, L1=%.3f kWh, L2=%.3f kWh, L3=%.3f kWh\n", send_energy.W_Tot, send_energy.W1, send_energy.W2, send_energy.W3); if (is_valid_float(send_energy.W_Tot, 0.0, 1000000.0) && is_valid_float(send_energy.W1, 0.0, 1000000.0) && is_valid_float(send_energy.W2, 0.0, 1000000.0) && is_valid_float(send_energy.W3, 0.0, 1000000.0)) { mqtt_send_W(send_energy.W_Tot, send_energy.W1, send_energy.W2, send_energy.W3); valid_W = 1; printf("ORNO: MQTT: Published energy\n"); } else { printf("ORNO: MQTT: Skipping energy - invalid values\n"); } printf("ORNO: Frequency: %.4f Hz\n", send_freq.Freq); if (is_valid_float(send_freq.Freq, 47.0, 53.0)) { mqtt_send_Hz(send_freq.Freq); valid_F = 1; printf("ORNO: MQTT: Published frequency\n"); } else { printf("ORNO: MQTT: Skipping frequency - invalid value\n"); } /* Send all ORNO data to InfluxDB in one batch (1 TCP connection instead of 5) */ influx_send_orno_batch( &send_voltage, valid_U, &send_current, valid_I, &send_power, valid_P, &send_energy, valid_W, &send_freq, valid_F); } } if (do_sun2k) { /* Delay between ORNO and SUN2K as configured */ if (do_orno) { printf("\nSUN2K: Waiting %d ms before SUN2K...\n\n", DELAY_BETWEEN_DEVICES/1000); usleep(DELAY_BETWEEN_DEVICES); } time_t rawtime; struct tm *timeinfo; time(&rawtime); timeinfo = localtime(&rawtime); printf("SUN2K: === SUN2K Reading Started: %s", asctime(timeinfo)); //Create a new RTU context with proper serial parameters (in this example, //device name /dev/ttyS0, baud rate 9600, no parity bit, 8 data bits, 1 stop bit) modbus_t *ctx = modbus_new_rtu(USB_DEV, 9600, 'N', 8, 1); if (!ctx) { fprintf(stderr, "SUN2K: Failed to create the context: %s\n", modbus_strerror(errno)); exit(1); } //Set the Modbus address of the remote slave (to 3) modbus_set_slave(ctx, SUN2000_SLAVE); if (modbus_connect(ctx) == -1) { fprintf(stderr, "SUN2K: Unable to connect: %s\n", modbus_strerror(errno)); modbus_free(ctx); exit(1); } /* Configure timeouts for SUN2K from #define */ modbus_rtu_set_rts_delay(ctx, SUN2K_RTS_DELAY); modbus_set_response_timeout(ctx, 0, 900000); /* 0.9s */ modbus_set_byte_timeout(ctx, 0, SUN2K_BYTE_TIMEOUT); /* Display current configuration */ int rts_delay = modbus_rtu_get_rts_delay(ctx); uint32_t response_timeout_sec, response_timeout_usec; uint32_t byte_timeout_sec, byte_timeout_usec; modbus_get_response_timeout(ctx, &response_timeout_sec, &response_timeout_usec); modbus_get_byte_timeout(ctx, &byte_timeout_sec, &byte_timeout_usec); printf("SUN2K: RTS Delay %u us\n", rts_delay); printf("SUN2K: Response Timeout %u,%06u s\n", response_timeout_sec, response_timeout_usec); printf("SUN2K: Byte Timeout %u,%06u s\n", byte_timeout_sec, byte_timeout_usec); /* Flush serial buffer and wait for device to be ready */ modbus_flush(ctx); usleep(100000); /* 100ms delay before first read */ /* Enable libmodbus debug to print raw frames */ /* libmodbus debug disabled for normal runs */ /* modbus_set_debug(ctx, TRUE); */ uint16_t reg[32]; // will store read registers values int num = 0; int proba = 0; char str_buf[32]; /* Read SUN2K voltages (U_A, U_B, U_C) in one batch - registers 32069-32071 */ float sun2k_U_A = 0, sun2k_U_B = 0, sun2k_U_C = 0; int sun2k_voltage_ok = 0; proba = 0; do { num = modbus_read_timed(ctx, 32069, 3, reg); } while ((num != 3) && (proba++ < 10)); if (num == 3) { sun2k_U_A = (float)reg[0] / 10.0; sun2k_U_B = (float)reg[1] / 10.0; sun2k_U_C = (float)reg[2] / 10.0; printf("SUN2K: Voltages: U_A=%.1f V, U_B=%.1f V, U_C=%.1f V\n", sun2k_U_A, sun2k_U_B, sun2k_U_C); if (is_valid_float(sun2k_U_A, 150.0, 280.0) && is_valid_float(sun2k_U_B, 150.0, 280.0) && is_valid_float(sun2k_U_C, 150.0, 280.0)) { mqtt_send_SUN2K_U(sun2k_U_A, sun2k_U_B, sun2k_U_C); sun2k_voltage_ok = 1; printf("SUN2K: MQTT: Published voltages\n"); } else { printf("SUN2K: MQTT: Skipping voltages - invalid values\n"); } influx_send_SUN2K_U_batch(sun2k_U_A, sun2k_U_B, sun2k_U_C, sun2k_voltage_ok); } else { printf("SUN2K: Failed to read voltages: %s\n", modbus_strerror(errno)); } usleep(10000); /* 10ms delay before continuing */ for (int i = 0; i < sizeof(mbReg) / sizeof(t_mb_reg); i++) { /* Skip U_A, U_B, U_C - already read and sent in batch */ if (mbReg[i].ireg == 32069 || mbReg[i].ireg == 32070 || mbReg[i].ireg == 32071) { continue; } memset(str_buf, 0, sizeof(str_buf)); proba = 0; do { num = modbus_read_timed(ctx, mbReg[i].ireg, mbReg[i].num_reg, reg); } while ((num != mbReg[i].num_reg) && (proba++ < 10)); printf("SUN2K: %10i\t", mbReg[i].ireg); printf("%u\t", reg[0]); if (mbReg[i].num_reg == 2) printf("%u\t", reg[1]); else printf("\t"); if (num != mbReg[i].num_reg) { // number of read registers is not the one expected printf("%20.20s \t", mbReg[i].reg_id); printf("SUN2K: Failed to %i read: %s\n", proba, modbus_strerror(errno)); continue; } else { printf("SUN2K: %20.20s \t", mbReg[i].reg_id); switch (mbReg[i].type) { case rgStr: { for (int j = 0; j < mbReg[i].num_reg; j++) { printf("%c%c", (reg[j] / 256), (reg[j] % 256)); } printf("\n"); break; } case rgData: { time_t td = reg[0] * 65536 + reg[1]; timeinfo = localtime(&td); printf("%s", asctime(timeinfo)); snprintf(str_buf, sizeof(str_buf), "\"%s\"", asctime(timeinfo)); str_buf[strlen(str_buf) - 2] = '"'; str_buf[strlen(str_buf) - 1] = '\0'; break; } case rgI32: { int32_t d = (int32_t)((uint32_t)reg[0] << 16 | reg[1]); if (mbReg[i].scale == 1) { printf("%i", d); snprintf(str_buf, sizeof(str_buf), "%i", d); } else { double f = d; f /= mbReg[i].scale; printf("%lf", f); snprintf(str_buf, sizeof(str_buf), "%lf", f); } break; } case rgI16: { int16_t d = (int16_t)reg[0]; if (mbReg[i].scale == 1) { printf("%i", d); snprintf(str_buf, sizeof(str_buf), "%i", d); } else { double f = d; f /= mbReg[i].scale; printf("%lf", f); snprintf(str_buf, sizeof(str_buf), "%lf", f); } break; } case rgU32: { uint32_t d = (uint32_t)reg[0] << 16 | reg[1]; if (mbReg[i].scale == 1) { printf("%u", d); snprintf(str_buf, sizeof(str_buf), "%u", d); } else { double f = d; f /= mbReg[i].scale; printf("%lf", f); snprintf(str_buf, sizeof(str_buf), "%lf", f); } break; } case rgU16: { uint16_t d = reg[0]; if (mbReg[i].scale == 1) { printf("%u", d); snprintf(str_buf, sizeof(str_buf), "%u", d); } else { double f = d; f /= mbReg[i].scale; printf("%lf", f); snprintf(str_buf, sizeof(str_buf), "%lf", f); } break; } default: { for (int j = 0; j < mbReg[i].num_reg; j++) { printf("%i:", reg[j]); snprintf(str_buf, sizeof(str_buf), "%i", reg[j]); } break; } } if (mbReg[i].type != rgData) printf(" %s\n", mbReg[i].units); } mqtt_send_SUN2K(&mbReg[i], str_buf); influx_send_SUN2K(&mbReg[i], str_buf); usleep(10000); /* 10ms delay between queries */ } modbus_close(ctx); modbus_free(ctx); } if (READ_LOOP) { if (time(NULL) < high_frequency_mode_end_time) { usleep(10000); // 10ms } else { // Check if high frequency mode just ended - send notification if (high_frequency_mode_active) { printf("ORNO: High frequency mode ended after %d seconds.\n", HIGH_FREQ_MODE_DURATION); mqtt_send("/energy/orno/fluct", "off"); influx_send_post("orno,device=orno,highfluct=end highfluct=0"); high_frequency_mode_active = 0; } sleep(5); // 5s } } } while (READ_LOOP); //mosq_test(); return 0; }