/* Copyright 2019 King Kévin * * 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 . */ /* This program connects to the WEB/WITRN/Qway U2(p) USB meter using the USB HID interface and reads the measurement values. * * The device's USB VID:PID is 0716:5030. * Don't forget to grant USB permission to connect to the device. * This program does not support connecting to multiple devices, and will only connect to the first it finds. */ #include #include #include #include #include #include #include #include #include #include #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) static void die(const char* format, ...) { va_list args; va_start(args, format); vfprintf(stderr, format, args); va_end(args); exit(1); } /* watchdog called after alarm timeout * @param[in] sig alarm signal */ static void watchdog(int sig) { (void)sig; // argument not used die("exiting due to inactivity\n"); } /* original trigger message from vendor software "\xff\x55\x58\x8a\x13\x79\x06\x57\x1a\x01\x0a\x02\x00\x00\x00\x00" \ "\x5e\x00\x00\x00\xff\x55\x2f\xb2\x8b\xdc\x5a\xd4\x1a\x2c\xa4\x00" \ "\xa4\x40\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x74\x21\x4f\x40\x75" \ "\x23\x19\x40\x75\xcc\x01\x01\x00\x02\x04\x00\x00\x5e\x00\xe7\x06"; */ // simplified trigger message (without checksum) to get a couple of measurements static char trigger[] = \ "\xff\x55\x00\x00\x00\x00\x00\x00\x1a\x01\x0a\x00\x00\x00\x00\x00" \ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" \ "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; static hid_device *handle = NULL; struct u2_measurement_t { uint8_t payload[64]; uint8_t seconds; uint8_t increment1; uint8_t increment2; float increment3; uint8_t increment4; float vbus_voltage1; // in V float vbus_current1; // in A float vbus_current2; // in A float vbus_current3; // in A float vbus_power; // in W float dp_voltage; // in V float dm_voltage; // in V float temperature_internal; // in °C float temperature_external; // in °C float vbus_voltage2; // in V float vbus_current4; // in A bool checksum1; bool checksum2; }; static void trigger_mesurement(void) { if (NULL == handle) { die("open a HID device first\n"); } // update checksum uint8_t checksum = 0; for (uint8_t i = 8; i < 62; i++) { checksum += trigger[i]; } trigger[62] = checksum; checksum = 0; for (uint8_t i = 0; i < 62; i++) { checksum += trigger[i]; } trigger[63] = checksum; // add report ID prefix to message unsigned char report[65] = {0}; memcpy(&report[1], trigger, 64); int rc = hid_write(handle, report, ARRAY_SIZE(report)); if (rc < 0) { die("could not write\n"); } } int main(int argc, char* argv[]) { int rc; // return code char str[200]; // general strings wchar_t wstr[100]; // wide strings (used by hidapi) bool debug = false; // print debug information if (argc >= 2) { if (0 == strcmp(argv[1],"-h")) { printf("%s [-d|-h] [host port]\n", argv[0]); printf("connect to WEB/WITRN/QWay U2 over USB and output measurements as CSV\n\n"); printf("\t-h\tprint help\n"); printf("\t-d\tenable debug output (disables CSV output)\n"); printf("\thost port\tinfluxDB UDP host and port to send voltage/current measurements to\n"); exit(0); } if (0 == strcmp(argv[1],"-d")) { debug = true; } } signal(SIGALRM, watchdog); // setup watchdog alarm(10); // start watchdog (in case connecting to the U2 gets stuck) // UDP socket to send data to influxdb int influxdb_fd = 0; struct addrinfo* res = 0; if (argc >= 3) { struct addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = 0; hints.ai_flags = AI_ADDRCONFIG; if (0 != getaddrinfo(argv[argc -2], argv[argc - 1], &hints, &res)) { die("failed to resolve remote socket address\n"); } influxdb_fd = socket(res->ai_family,res->ai_socktype,res->ai_protocol); if (0 == influxdb_fd) { die("could not create UDP socket\n"); } } rc = hid_init(); if (rc < 0) { die("could not initialize HID\n"); } handle = hid_open(0x0716, 0x5030, NULL); // open Qway/WITRN.U2 if (handle) { if (debug) { printf("WEB-U2 opened\n"); } } else { die("could not open WEB-U2 (make sure it is connected and you have access rights)\n"); } rc = hid_get_manufacturer_string(handle, wstr, ARRAY_SIZE(wstr)); if (rc < 0) { die("could not get manufacturer string\n"); } if (debug) { wcstombs(str, wstr, ARRAY_SIZE(str)); printf("manufacturer: %s\n", str); } rc = hid_get_product_string(handle, wstr, ARRAY_SIZE(wstr)); if (rc < 0) { die("could not get product string\n"); } if (debug) { wcstombs(str, wstr, ARRAY_SIZE(str)); printf("product: %s\n", str); } // Read the Serial Number String rc = hid_get_serial_number_string(handle, wstr, ARRAY_SIZE(wstr)); if (rc < 0) { die("could not get serial string\n"); } if (debug) { wcstombs(str, wstr, ARRAY_SIZE(str)); printf("serial: %s\n", str); } if (!debug) { // print CSV header printf("timer (in s),super fast timer,fast timer,timer,slow timer,VBUS voltage 1 (in V),VBUS current 1 (in A),VBUS current 2 (in A),VBUS current 3 (in A),VBUS power (in W),D+ voltage (in V),D- voltage (in V),internal temperature (in °C),external temperature (in °C),VBUS voltage 2 (in V),VBUS current 4 (in A)\n"); } bool run = true; // read from USB as long as true struct u2_measurement_t meas; // to store the parsed measurement values // line containing values for influxdb char line[22 * 2]; int line_len; while (run) { rc = hid_read_timeout(handle, meas.payload, ARRAY_SIZE(meas.payload), 50); // wait for max 50 ms for a measurement if (rc < 0) { die("could not read\n"); } else if (0 == rc) { // timeout trigger_mesurement(); if (debug) { printf("trigger measurement\n"); } continue; } alarm(10); // restart watchdog // parse measurement values meas.seconds = meas.payload[2]; meas.increment1 = meas.payload[3]; meas.increment2 = meas.payload[4]; meas.increment3 = meas.payload[5] + meas.payload[6] / 100.0; meas.increment4 = meas.payload[7]; meas.vbus_voltage1 = *(float*)(&meas.payload[10]); // in V meas.vbus_current1 = *(float*)(&meas.payload[14]); // in A meas.vbus_current2 = *(float*)(&meas.payload[18]); // in A meas.vbus_current3 = *(float*)(&meas.payload[22]); // in A meas.vbus_power = *(float*)(&meas.payload[26]); // in W meas.dp_voltage = *(float*)(&meas.payload[30]); // in V meas.dm_voltage = *(float*)(&meas.payload[34]); // in V meas.temperature_internal = *(float*)(&meas.payload[38]); // in °C meas.temperature_external = *(float*)(&meas.payload[42]); // in °C meas.vbus_voltage2 = *(float*)(&meas.payload[46]); // in V meas.vbus_current4 = *(float*)(&meas.payload[50]); // in A // calculate checksums // the 64th byte is the sum of the first 62 bytes uint8_t checksum = 0; for (uint8_t i = 0; i < 62; i++) { checksum += meas.payload[i]; } meas.checksum2 = (checksum == meas.payload[63]); // the 63th byte is the sum of bytes 8 to 62 (without the timer information) checksum = 0; for (uint8_t i = 8; i < 62; i++) { checksum += meas.payload[i]; } meas.checksum1 = (checksum == meas.payload[62]); if (debug) { // print payload and values aligned for (uint8_t i = 0; i < ARRAY_SIZE(meas.payload); i++) { printf("%02x ", meas.payload[i]); } printf("\n"); if (meas.checksum1 && meas.checksum2) { printf("const "); printf("%03us ", meas.seconds); //printf("++"); // meas.increment1 is simply incrementing printf("++"); // meas.increment2 is simply incrementing printf("%03u.%02u ", (uint8_t)meas.increment3, (uint8_t)(meas.increment3 * 100) % 100); printf("++"); // meas.increment4 is simply incrementing printf(" const "); printf("U:%.04fV ", meas.vbus_voltage1); printf("I:%.04fA ", meas.vbus_current1); printf("I:%.04fA ", meas.vbus_current2); printf("I:%.04fA ", meas.vbus_current3); printf("P:%.04fW ", meas.vbus_power); printf("D+:%.04fV ", meas.dp_voltage); printf("D-:%.04fV ", meas.dm_voltage); printf("Int:%.02fC ", meas.temperature_internal); printf("Ext:%.02fC ", meas.temperature_external); printf("U:%.04fV ", meas.vbus_voltage2); printf("I:%.04fA ", meas.vbus_current4); printf("\n"); } else { printf("invalid checksum\n"); } } else { // print CSV line if (meas.checksum1 && meas.checksum2) { printf("%u,", meas.seconds); printf("%u,", meas.increment1); printf("%u,", meas.increment2); printf("%.02f,", meas.increment3); printf("%u,", meas.increment4); printf("%.04f,", meas.vbus_voltage1); printf("%.04f,", meas.vbus_current1); printf("%.04f,", meas.vbus_current2); printf("%.04f,", meas.vbus_current3); printf("%.04f,", meas.vbus_power); printf("%.04f,", meas.dp_voltage); printf("%.04f,", meas.dm_voltage); printf("%.02f,", meas.temperature_internal); printf("%.02f,", meas.temperature_external); printf("%.04f,", meas.vbus_voltage2); printf("%.04f", meas.vbus_current4); printf("\n"); } } if (influxdb_fd) { line_len = snprintf(line, sizeof(line), "voltage value=%.04f\ncurrent value=%.04f\n", meas.vbus_voltage1, meas.vbus_current1); if (-1 == sendto(influxdb_fd, line, line_len, 0, res->ai_addr, res->ai_addrlen)) { die("could not send UDP packet\n"); goto close; } } } close: hid_close(handle); handle = NULL; rc = hid_exit(); if (rc < 0) { die("could not exit HID\n"); } return 0; }