2019-09-30 13:16:49 +02:00
/* 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>
2020-01-09 11:36:55 +01:00
# include <unistd.h>
# include <signal.h>
2019-09-30 13:16:49 +02:00
# include <hidapi/hidapi.h>
2020-01-09 11:21:02 +01:00
# include <netdb.h>
2019-09-30 13:16:49 +02:00
# 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 ) ;
}
2020-01-09 11:36:55 +01:00
/* 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 " ) ;
}
2019-09-30 13:16:49 +02:00
/* 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)
2020-01-09 11:21:02 +01:00
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 \t print help \n " ) ;
printf ( " \t -d \t enable debug output (disables CSV output) \n " ) ;
printf ( " \t host port \t influxDB UDP host and port to send voltage/current measurements to \n " ) ;
exit ( 0 ) ;
} if ( 0 = = strcmp ( argv [ 1 ] , " -d " ) ) {
debug = true ;
}
}
2019-09-30 13:16:49 +02:00
2020-01-09 11:36:55 +01:00
signal ( SIGALRM , watchdog ) ; // setup watchdog
2020-09-12 14:07:04 +02:00
alarm ( 10 ) ; // start watchdog (in case connecting to the U2 gets stuck)
2020-01-09 11:36:55 +01:00
2020-01-09 11:21:02 +01:00
// 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 " ) ;
}
2019-09-30 13:16:49 +02:00
}
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
2020-09-12 14:07:25 +02:00
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 " ) ;
2019-09-30 13:16:49 +02:00
}
2020-01-09 11:21:02 +01:00
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
2020-01-09 11:31:11 +01:00
char line [ 22 * 2 ] ;
2020-01-09 11:21:02 +01:00
int line_len ;
2019-09-30 13:16:49 +02:00
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 " ) ;
}
2020-01-09 11:36:55 +01:00
continue ;
2019-09-30 13:16:49 +02:00
}
2020-09-12 14:07:04 +02:00
alarm ( 10 ) ; // restart watchdog
2020-01-09 11:36:55 +01:00
2019-09-30 13:16:49 +02:00
// 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 " ) ;
}
}
2020-01-09 11:21:02 +01:00
if ( influxdb_fd ) {
line_len = snprintf ( line , sizeof ( line ) , " voltage value=%.04f \n current 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 ;
}
}
2019-09-30 13:16:49 +02:00
}
2020-01-09 11:21:02 +01:00
close :
2019-09-30 13:16:49 +02:00
hid_close ( handle ) ;
handle = NULL ;
rc = hid_exit ( ) ;
if ( rc < 0 ) {
die ( " could not exit HID \n " ) ;
}
return 0 ;
}