323 lines
10 KiB
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;
|
|
}
|