web-u2/u2_usb.c

323 lines
10 KiB
C

/* Copyright 2019 King Kévin <kingkevin@cuvoodoo.info>
*
* 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 <https://www.gnu.org/licenses/>.
*/
/* 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 <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <signal.h>
#include <hidapi/hidapi.h>
#include <netdb.h>
#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;
}