#include #include #include #include #include #include "inverter.h" #include "tools.h" #include "main.h" #include #include cInverter::cInverter(std::string devicename, int qpiri, int qpiws, int qmod, int qpigs) { device = devicename; status1[0] = 0; status2[0] = 0; warnings[0] = 0; mode = 0; buf_qpiri = qpiri; buf_qpiws = qpiws; buf_qmod = qmod; buf_qpigs = qpigs; lprintf("INVERTER: Initialized with buffer sizes - QPIRI:%d QPIWS:%d QMOD:%d QPIGS:%d", buf_qpiri, buf_qpiws, buf_qmod, buf_qpigs); } string *cInverter::GetQpigsStatus() { m.lock(); string *result = new string(status1); m.unlock(); return result; } string *cInverter::GetQpiriStatus() { m.lock(); string *result = new string(status2); m.unlock(); return result; } string *cInverter::GetWarnings() { m.lock(); string *result = new string(warnings); m.unlock(); return result; } void cInverter::SetMode(char newmode) { m.lock(); if (mode && newmode != mode) ups_status_changed = true; mode = newmode; m.unlock(); } int cInverter::GetMode() { int result; m.lock(); switch (mode) { case 'P': result = 1; break; // Power_On case 'S': result = 2; break; // Standby case 'L': result = 3; break; // Line case 'B': result = 4; break; // Battery case 'F': result = 5; break; // Fault case 'H': result = 6; break; // Power_Saving default: result = 0; break; // Unknown } m.unlock(); return result; } bool cInverter::query(const char *cmd, int replysize) { time_t started; int fd; int i=0, n; fd = open(this->device.data(), O_RDWR | O_NONBLOCK); if (fd == -1) { lprintf("INVERTER: Unable to open device file (errno=%d %s)", errno, strerror(errno)); sleep(5); return false; } // Once connected, set the baud rate and other serial config (Don't rely on this being correct on the system by default...) speed_t baud = B2400; // Speed settings (in this case, 2400 8N1) struct termios settings; tcgetattr(fd, &settings); cfsetospeed(&settings, baud); // baud rate cfsetispeed(&settings, baud); // input baud rate settings.c_cflag &= ~PARENB; // no parity settings.c_cflag &= ~CSTOPB; // 1 stop bit settings.c_cflag &= ~CSIZE; settings.c_cflag |= CS8 | CLOCAL; // 8 bits // settings.c_lflag = ICANON; // canonical mode settings.c_oflag &= ~OPOST; // raw output tcsetattr(fd, TCSANOW, &settings); // apply the settings // CRITICAL: Flush both input and output buffers to clear any residual data tcflush(fd, TCIOFLUSH); // Wait a bit for the device to settle after flush usleep(100000); // 100ms delay // Clear internal buffer memset(buf, 0, sizeof(buf)); // --------------------------------------------------------------- // Generating CRC for a command uint16_t crc = cal_crc_half((uint8_t*)cmd, strlen(cmd)); n = strlen(cmd); memcpy(&buf, cmd, n); lprintf("INVERTER: Current CRC: %X %X", crc >> 8, crc & 0xff); buf[n++] = crc >> 8; buf[n++] = crc & 0xff; buf[n++] = 0x0d; //send a command int written = write(fd, &buf, n); if (written != n) { lprintf("INVERTER: %s write failed (wrote %d of %d bytes)", cmd, written, n); close(fd); return false; } // Flush output to ensure command is sent tcdrain(fd); // Clear buffer again before reading memset(buf, 0, sizeof(buf)); time(&started); do { n = read(fd, (void*)buf+i, replysize-i); if (n < 0) { if (time(NULL) - started > 2) { lprintf("INVERTER: %s read timeout", cmd); break; } else { usleep(10000); // 10ms continue; } } if (n > 0) { i += n; // Check if we've received the terminator if (i > 0 && buf[i-1] == 0x0d) { lprintf("INVERTER: %s received terminator at byte %d", cmd, i); break; } } } while (i 0) { lprintf("INVERTER: %s reply size (%d bytes, expected %d)", cmd, i, replysize); // Dump raw buffer for debugging if (debugFlag) { lprintf("INVERTER: Raw buffer hex dump (first 50 bytes):"); for(int x = 0; x < (i < 50 ? i : 50); x++) { fprintf(stderr, "%02X ", buf[x]); if ((x+1) % 16 == 0) fprintf(stderr, "\n"); } fprintf(stderr, "\n"); } if (buf[0]!='(' ) { lprintf("INVERTER: %s: incorrect start byte (got 0x%02X, expected '('). Buffer: %s", cmd, buf[0], buf); return false; } if (buf[i-1]!=0x0d) { lprintf("INVERTER: %s: incorrect stop byte (got 0x%02X at pos %d, expected CR). Buffer: %s", cmd, buf[i-1], i-1, buf); return false; } if (!(CheckCRC(buf, i))) { lprintf("INVERTER: %s: CRC Failed! Reply size: %d Buffer: %s", cmd, i, buf); return false; } buf[i-3] = '\0'; //nullterminating on first CRC byte lprintf("INVERTER: %s: %d bytes read: %s", cmd, i, buf); lprintf("INVERTER: %s query finished", cmd); // If expected size doesn't match actual size, log it if (i != replysize) { lprintf("INVERTER: WARNING - %s actual size (%d) differs from configured size (%d)", cmd, i, replysize); lprintf("INVERTER: SUGGESTION - Update inverter.conf to set the buffer size to %d", i); } return true; } else { lprintf("INVERTER: %s reply too short (%d bytes)", cmd, i); return false; } } void cInverter::poll() { int n,j; fprintf(stderr, "[POLL] Thread started, runOnce=%s\n", runOnce ? "true" : "false"); while (true) { // Reading mode if (!ups_qmod_changed) { fprintf(stderr, "[POLL] Reading QMOD...\n"); if (query("QMOD", buf_qmod)) { SetMode(buf[1]); ups_qmod_changed = true; fprintf(stderr, "[POLL] QMOD completed\n"); } } // reading status (QPIGS) if (!ups_qpigs_changed) { fprintf(stderr, "[POLL] Reading QPIGS...\n"); if (query("QPIGS", buf_qpigs)) { m.lock(); strcpy(status1, (const char*)buf+1); m.unlock(); ups_qpigs_changed = true; fprintf(stderr, "[POLL] QPIGS completed\n"); } } // Reading QPIRI status if (!ups_qpiri_changed) { fprintf(stderr, "[POLL] Reading QPIRI...\n"); if (query("QPIRI", buf_qpiri)) { m.lock(); strcpy(status2, (const char*)buf+1); m.unlock(); ups_qpiri_changed = true; fprintf(stderr, "[POLL] QPIRI completed\n"); } } // Get any device warnings... if (!ups_qpiws_changed) { fprintf(stderr, "[POLL] Reading QPIWS...\n"); if (query("QPIWS", buf_qpiws)) { m.lock(); strcpy(warnings, (const char*)buf+1); m.unlock(); ups_qpiws_changed = true; fprintf(stderr, "[POLL] QPIWS completed\n"); } } // If runOnce mode and all data collected, exit the thread if (runOnce && ups_qmod_changed && ups_qpigs_changed && ups_qpiri_changed && ups_qpiws_changed) { fprintf(stderr, "[POLL] All data collected, exiting (run-once mode)\n"); return; } fprintf(stderr, "[POLL] Flags: QMOD=%d QPIGS=%d QPIRI=%d QPIWS=%d, sleeping...\n", ups_qmod_changed.load(), ups_qpigs_changed.load(), ups_qpiri_changed.load(), ups_qpiws_changed.load()); sleep(2); } } void cInverter::ExecuteCmd(const string cmd) { // Sending any command raw if (query(cmd.data(), 7)) { m.lock(); strcpy(status2, (const char*)buf+1); m.unlock(); } } uint16_t cInverter::cal_crc_half(uint8_t *pin, uint8_t len) { uint16_t crc; uint8_t da; uint8_t *ptr; uint8_t bCRCHign; uint8_t bCRCLow; uint16_t crc_ta[16]= { 0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7, 0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef }; ptr=pin; crc=0; while(len--!=0) { da=((uint8_t)(crc>>8))>>4; crc<<=4; crc^=crc_ta[da^(*ptr>>4)]; da=((uint8_t)(crc>>8))>>4; crc<<=4; crc^=crc_ta[da^(*ptr&0x0f)]; ptr++; } bCRCLow = crc; bCRCHign= (uint8_t)(crc>>8); if(bCRCLow==0x28||bCRCLow==0x0d||bCRCLow==0x0a) bCRCLow++; if(bCRCHign==0x28||bCRCHign==0x0d||bCRCHign==0x0a) bCRCHign++; crc = ((uint16_t)bCRCHign)<<8; crc += bCRCLow; return(crc); } bool cInverter::CheckCRC(unsigned char *data, int len) { uint16_t crc = cal_crc_half(data, len-3); return data[len-3]==(crc>>8) && data[len-2]==(crc&0xff); } // Auto-discover the correct buffer size for a command by reading until CR int cInverter::query_auto(const char *cmd, int max_size) { time_t started; int fd; int i=0, n; unsigned char temp_buf[1024]; memset(temp_buf, 0, sizeof(temp_buf)); fd = open(this->device.data(), O_RDWR | O_NONBLOCK); if (fd == -1) { lprintf("INVERTER: Unable to open device file for auto-discovery"); return -1; } // Configure serial port speed_t baud = B2400; struct termios settings; tcgetattr(fd, &settings); cfsetospeed(&settings, baud); cfsetispeed(&settings, baud); settings.c_cflag &= ~PARENB; settings.c_cflag &= ~CSTOPB; settings.c_cflag &= ~CSIZE; settings.c_cflag |= CS8 | CLOCAL; settings.c_oflag &= ~OPOST; tcsetattr(fd, TCSANOW, &settings); // Flush all buffers tcflush(fd, TCIOFLUSH); usleep(200000); // 200ms delay to ensure clean state // Generate and send command with CRC uint16_t crc = cal_crc_half((uint8_t*)cmd, strlen(cmd)); n = strlen(cmd); memcpy(&temp_buf, cmd, n); temp_buf[n++] = crc >> 8; temp_buf[n++] = crc & 0xff; temp_buf[n++] = 0x0d; write(fd, &temp_buf, n); tcdrain(fd); // Clear buffer for reading memset(temp_buf, 0, sizeof(temp_buf)); time(&started); // Read until we find CR (0x0d) or timeout while (i < max_size && (time(NULL) - started < 5)) { n = read(fd, temp_buf+i, 1); // Read one byte at a time if (n > 0) { i += n; // Found the terminator if (temp_buf[i-1] == 0x0d) { lprintf("INVERTER: Auto-discovery for %s: found CR at byte %d", cmd, i); break; } } else { usleep(10000); // 10ms between reads } } close(fd); // Validate the response if (i > 0 && temp_buf[0] == '(' && temp_buf[i-1] == 0x0d) { lprintf("INVERTER: Auto-discovery for %s successful: %d bytes", cmd, i); return i; } else { lprintf("INVERTER: Auto-discovery for %s failed (read %d bytes)", cmd, i); // Dump what we received for debugging if (i > 0) { lprintf("INVERTER: Received data (hex):"); for(int x = 0; x < i; x++) { fprintf(stderr, "%02X ", temp_buf[x]); } fprintf(stderr, "\n"); lprintf("INVERTER: Received data (ascii):"); for(int x = 0; x < i; x++) { fprintf(stderr, "%c", (temp_buf[x] >= 32 && temp_buf[x] < 127) ? temp_buf[x] : '.'); } fprintf(stderr, "\n"); } return -1; } } // Auto-discover buffer sizes for all commands void cInverter::AutoDiscoverBufferSizes() { printf("\n=== AUTO-DISCOVERY MODE ===\n"); printf("Testing inverter to find correct buffer sizes...\n\n"); int qmod_size = query_auto("QMOD", 20); if (qmod_size > 0) { printf("✓ QMOD buffer size: %d\n", qmod_size); } else { printf("✗ QMOD auto-discovery failed\n"); } sleep(1); int qpigs_size = query_auto("QPIGS", 150); if (qpigs_size > 0) { printf("✓ QPIGS buffer size: %d\n", qpigs_size); } else { printf("✗ QPIGS auto-discovery failed\n"); } sleep(1); int qpiri_size = query_auto("QPIRI", 150); if (qpiri_size > 0) { printf("✓ QPIRI buffer size: %d\n", qpiri_size); } else { printf("✗ QPIRI auto-discovery failed\n"); } sleep(1); int qpiws_size = query_auto("QPIWS", 100); if (qpiws_size > 0) { printf("✓ QPIWS buffer size: %d\n", qpiws_size); } else { printf("✗ QPIWS auto-discovery failed\n"); } printf("\n=== SUGGESTED CONFIGURATION ===\n"); printf("Update your /etc/inverter/inverter.conf with these values:\n\n"); if (qmod_size > 0) printf("qmod=%d\n", qmod_size); if (qpigs_size > 0) printf("qpigs=%d\n", qpigs_size); if (qpiri_size > 0) printf("qpiri=%d\n", qpiri_size); if (qpiws_size > 0) printf("qpiws=%d\n", qpiws_size); printf("\n"); // Output in parsable format for scripts printf("DISCOVERY_QMOD=%d\n", qmod_size > 0 ? qmod_size : 5); printf("DISCOVERY_QPIGS=%d\n", qpigs_size > 0 ? qpigs_size : 110); printf("DISCOVERY_QPIRI=%d\n", qpiri_size > 0 ? qpiri_size : 98); printf("DISCOVERY_QPIWS=%d\n", qpiws_size > 0 ? qpiws_size : 36); printf("DISCOVERY_SUCCESS=%s\n", (qmod_size > 0 && qpigs_size > 0 && qpiri_size > 0 && qpiws_size > 0) ? "true" : "false"); }