aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKing Kévin <kingkevin@cuvoodoo.info>2019-09-30 13:16:49 +0200
committerKing Kévin <kingkevin@cuvoodoo.info>2019-09-30 13:16:49 +0200
commit538908b5eefc993475c546d25148282cac2adcd2 (patch)
tree3402c4a8fe5fd4c18cda74fa9bcdf2b27282a59c
add u2_usb and u2_bt C software
-rw-r--r--Makefile20
-rw-r--r--README31
-rw-r--r--u2_bt.c916
-rw-r--r--u2_usb.c264
4 files changed, 1231 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..8d6c20d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,20 @@
+# optimize for debug
+CFLAGS += -O0
+# to better detect programming errors
+CFLAGS += -fstack-protector-all
+# add debug symbols (remove for smaller release)
+CFLAGS += -ggdb
+# use GNU99 (POSIX not included in C99 is required for sockets)
+CFLAGS += -std=gnu99
+# have strict warning (for better code)
+CFLAGS += -Wpedantic -Wall -Werror -Wundef -Wextra -Wshadow -Wredundant-decls -Wmissing-prototypes -Wstrict-prototypes -Wstrict-overflow=5
+# add options for better code optimization
+CFLAGS += -fno-common -ffunction-sections -fdata-sections
+
+all: u2_usb u2_bt
+
+u2_usb: u2_usb.c
+ gcc ${CFLAGS} -lhidapi-libusb -o $@ $<
+
+u2_bt: u2_bt.c
+ gcc ${CFLAGS} `pkg-config --cflags dbus-1` `pkg-config --libs dbus-1` -o $@ $<
diff --git a/README b/README
new file mode 100644
index 0000000..46cd204
--- /dev/null
+++ b/README
@@ -0,0 +1,31 @@
+these Linux programs read out the measurements from the WEB/WITRN/GZUt/QWay U2/U2p USB power meter.
+
+for more information about the device and protocols: https://wiki.cuvoodoo.info/doku.php?id=web-u2
+
+USB
+===
+
+`u2_usb` reads the measurements using the USB HID interface.
+
+to compile the source code you require: gcc, make, hidapi library (libhidapi-dev).
+then just run `make` and it will output the `u2_usb` binary.
+
+run `u2_usb` to get the output in CSV format.
+run `u2_usb -d` to get the raw and decoded output (useful for reversing and debugging).
+
+Bluetooth
+=========
+
+to compile the source code you require: gcc, make, dbus library (libdbus-1-dev).
+then just run `make` and it will output the `u2_bt` binary.
+
+ensure the BlueZ Bluetooth stack is running:
+sudo systemctl start bluetooth
+don't forget to grant the permissions to access bluetooth devices:
+sudo adduser $USER bluetooth
+
+run `u2_bt` to get the output in CSV format.
+run `u2_bt -d` to get the raw and decoded output (useful for reversing and debugging).
+
+I did not implement the possibility to specify the Bluetooth adapter and device.
+the program uses the first Bluetooth adapter it finds, and first device with the name starting with Qway_U2.
diff --git a/u2_bt.c b/u2_bt.c
new file mode 100644
index 0000000..9c5fb16
--- /dev/null
+++ b/u2_bt.c
@@ -0,0 +1,916 @@
+/* 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 Qway U2 USB power meter over Bluetooth and reads out the measurements
+ *
+ * this uses BlueZ 5.
+ * with BlueZ 5 the main interface is D-Bus, but D-Bus is a pain and there are not a lot of C examples.
+ * here a list of useful resources:
+ * https://dbus.freedesktop.org/doc/api/html/group__DBusMessage.html
+ * http://www.cplusplus.com/forum/unices/127440/
+ * https://leonardoce.wordpress.com/2015/03/17/dbus-tutorial-part-3/
+ * https://github.com/jjjsmit/BluetoothBLEClient/blob/master/bleClient.c
+ */
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <assert.h>
+#include <dbus/dbus.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+// the prefix for Qway bluwtooth device names
+static const char* device_prefix = "QWAY_U2";
+// GATT characteristic UUID for data exchange
+static const char* uart_uuid = "0000ffe1-0000-1000-8000-00805f9b34fb"; // for HM-10 UART port
+
+// if debug messages should be output
+static bool debug = false;
+
+/* print message to standard error and
+ * @param[in] format format string
+ */
+static void printf_debug(const char* format, ...)
+{
+ if (!debug) {
+ return;
+ }
+ va_list args;
+ va_start(args, format);
+ vfprintf(stdout, format, args);
+ va_end(args);
+}
+
+/* print message to standard error and quit program
+ * @param[in] format format string
+ */
+static void die(const char* format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+ exit(1);
+}
+
+/* get all D-Bus object paths with matching interface
+ * @param[in] connection D-Bus connection
+ * @param[in] bus_name bus to get objects from
+ * @param[in] interface interface string to match
+ * @param[out] error D-Bus error set on error
+ * @param[out] object_paths array of object paths with matching interface
+ * @return number of objects found
+ * @note D-Bus error is set in case of error
+ * @warning it is up to the caller to free the object paths
+ */
+static unsigned int dbus_get_object_paths_by_interface(DBusConnection* connection, const char* bus_name, const char* interface, DBusError* error, char*** object_paths) {
+ assert(connection);
+ assert(bus_name);
+ assert(interface);
+
+ unsigned int paths_count = 0; // number of matching paths
+
+ DBusMessage* query = dbus_message_new_method_call(bus_name, "/", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); // prepare the call to get all bus objects
+ assert(query);
+ DBusMessage* reply = dbus_connection_send_with_reply_and_block(connection, query, -1, error); // request all bus objects
+ if (dbus_error_is_set(error)) {
+ return 0;
+ }
+ assert(reply);
+ if (query) {
+ dbus_message_unref(query);
+ query = NULL;
+ }
+
+ // check D-Bus objects array
+ DBusMessageIter iter_root;
+ if (!dbus_message_iter_init(reply, &iter_root)) {
+ dbus_set_error_const(error, "iter_init_error", "Could not initialise message iterator");
+ return 0;
+ }
+ if (DBUS_TYPE_ARRAY != dbus_message_iter_get_arg_type(&iter_root)) {
+ dbus_set_error_const(error, "arg_type_error", "Expected array type");
+ return 0;
+ }
+ // go through D-Bus objects array
+ DBusMessageIter iter_array;
+ dbus_message_iter_recurse(&iter_root, &iter_array); // got through array
+ while(true) { // iterate through array (will exit using break)
+ if (DBUS_TYPE_DICT_ENTRY == dbus_message_iter_get_arg_type(&iter_array)) { // it should be an array of entries (e.g. dictionary)
+ DBusMessageIter iter_entry;
+ dbus_message_iter_recurse(&iter_array, &iter_entry); // get dictionary entry
+ if (DBUS_TYPE_OBJECT_PATH == dbus_message_iter_get_arg_type(&iter_entry)) { // expect a path
+ char* path = NULL;
+ dbus_message_iter_get_basic(&iter_entry, &path); // remember path (key of entry)
+ dbus_message_iter_next(&iter_entry); // go to value if entry
+ if (DBUS_TYPE_ARRAY == dbus_message_iter_get_arg_type(&iter_entry)) { // value should be an array
+ DBusMessageIter iter_subarray;
+ dbus_message_iter_recurse(&iter_entry, &iter_subarray); // go through array
+ while (true) { // iterate through array (will exit using break)
+ if (DBUS_TYPE_DICT_ENTRY == dbus_message_iter_get_arg_type(&iter_subarray)) { // we are looking for an entry
+ DBusMessageIter iter_subentry;
+ dbus_message_iter_recurse(&iter_subarray, &iter_subentry); // get entry
+ if (DBUS_TYPE_STRING == dbus_message_iter_get_arg_type(&iter_subentry)) { // expect interface name as string
+ char* name = NULL;
+ dbus_message_iter_get_basic(&iter_subentry, &name); // get interface name
+ if (0 == strcmp(name, interface)) { // check if it is the wished interface
+ paths_count++;
+ //assert(paths_count < SIZE_MAX / sizeof(char*));
+ *object_paths = realloc(*object_paths, paths_count * sizeof(char*));
+ if (NULL == *object_paths) {
+ dbus_set_error_const(error, "memory_error", "Unable to allocated memory for paths");;
+ return paths_count - 1;
+ }
+ assert(strlen(path) < SIZE_MAX - 1);
+ (*object_paths)[paths_count - 1] = malloc(strlen(path) + 1);
+ if (NULL == (*object_paths)[paths_count - 1]) {
+ dbus_set_error_const(error, "memory_error", "Unable to allocated memory for path");;
+ return paths_count - 1;
+ }
+ strcpy((*object_paths)[paths_count - 1], path);
+ }
+ }
+ }
+ if (dbus_message_iter_has_next(&iter_subarray)) {
+ dbus_message_iter_next(&iter_subarray); // go to next element
+ } else {
+ break;
+ }
+ } // end going through inner array
+ } // end if array
+ } // end if path
+ } // end if entry
+ if(dbus_message_iter_has_next(&iter_array)) {
+ dbus_message_iter_next(&iter_array);
+ } else {
+ break;
+ }
+ } // end of adapter search
+ // clean buffers
+ if (reply) {
+ dbus_message_unref(reply);
+ reply = NULL;
+ }
+
+ return paths_count;
+}
+
+/* get property from D-Bus object
+ * @param[in] connection D-Bus connection
+ * @param[in] bus_name bus to get object from
+ * @param[in] path object patch to get get property from
+ * @param[in] interface interface to get property from
+ * @param[out] error D-Bus error set on error
+ * @param[in] property_type property type to get
+ * @param[out] property were to write to property to
+ * @note D-Bus error is set in case of error
+ */
+static void dbus_get_property(DBusConnection *connection, const char *bus_name, const char *path, const char *interface, const char *property_name, DBusError *error, int property_type, void* property) {
+
+ assert(connection);
+ assert(bus_name);
+ assert(path);
+ assert(interface);
+ assert(property_name);
+ assert(error);
+ assert(property);
+
+ // create Get query to read property
+ DBusMessage* query = dbus_message_new_method_call(bus_name, path, "org.freedesktop.DBus.Properties", "Get");
+ assert(query);
+ dbus_message_append_args(query, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property_name, DBUS_TYPE_INVALID);
+ DBusMessage* reply = dbus_connection_send_with_reply_and_block(connection, query, -1, error);
+ if (query) {
+ dbus_message_unref(query);
+ query = NULL;
+ }
+ if (dbus_error_is_set(error)) {
+ goto end;
+ }
+
+ // get property from response (stored in a variant)
+ DBusMessageIter iter;
+ dbus_message_iter_init(reply, &iter);
+ if (DBUS_TYPE_VARIANT != dbus_message_iter_get_arg_type(&iter)) {
+ dbus_set_error_const(error, "reply_should_be_variant", "This message hasn't a variant response type");
+ goto end;
+ }
+ DBusMessageIter sub;
+ dbus_message_iter_recurse(&iter, &sub);
+ if (property_type != dbus_message_iter_get_arg_type(&sub)) {
+ dbus_set_error_const(error, "variant_type_mismatch", "This variant reply message does not have the right content type");
+ goto end;
+ }
+ dbus_message_iter_get_basic(&sub, property);
+
+end:
+ if (reply) {
+ dbus_message_unref(reply);
+ reply = NULL;
+ }
+}
+
+/* set property of D-Bus object
+ * @param[in] connection D-Bus connection
+ * @param[in] bus_name bus to get object from
+ * @param[in] path object patch to get get property from
+ * @param[in] interface interface to get property from
+ * @param[out] error D-Bus error set on error
+ * @param[in] container_type type of the container/variant
+ * @param[in] property_type property type to get
+ * @param[in] property property value to set
+ */
+static void dbus_set_property(DBusConnection *connection, const char *bus_name, const char *path, const char *interface, const char *property_name, DBusError *error, const char* container_type, int property_type, const void* property) {
+ assert(connection);
+ assert(bus_name);
+ assert(path);
+ assert(interface);
+ assert(property_name);
+ assert(error);
+ assert(property);
+
+ // create Set query to write property
+ DBusMessage* query = dbus_message_new_method_call(bus_name, path, "org.freedesktop.DBus.Properties", "Set");
+ assert(query);
+ DBusMessageIter iter, sub_iter;
+ dbus_message_iter_init_append(query, &iter);
+ dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &interface);
+ dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &property_name);
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, container_type, &sub_iter);
+ dbus_message_iter_append_basic(&sub_iter, property_type, property);
+ dbus_message_iter_close_container(&iter, &sub_iter);
+ DBusMessage* reply = dbus_connection_send_with_reply_and_block(connection, query, -1, error);
+ if (query) {
+ dbus_message_unref(query);
+ query = NULL;
+ }
+ if (reply) {
+ dbus_message_unref(reply);
+ reply = NULL;
+ }
+}
+
+/* call D-Bus object method
+ * @param[in] connection D-Bus connection
+ * @param[in] bus_name bus to get object from
+ * @param[in] path object patch to get get property from
+ * @param[in] interface interface to get property from
+ * @param[in] method_name object method to call
+ * @param[out] error D-Bus error set on error
+ */
+static void dbus_call_method(DBusConnection *connection, const char *bus_name, const char *path, const char *interface, const char *method_name, DBusError *error) {
+ assert(connection);
+ assert(bus_name);
+ assert(path);
+ assert(interface);
+ assert(method_name);
+ assert(error);
+
+ DBusMessage* query = dbus_message_new_method_call(bus_name, path, interface, method_name);
+ assert(query);
+ DBusMessage* reply = dbus_connection_send_with_reply_and_block(connection, query, -1, error);
+ if (query) {
+ dbus_message_unref(query);
+ query = NULL;
+ }
+ if (reply) {
+ dbus_message_unref(reply);
+ reply = NULL;
+ }
+}
+
+/* get file descriptor from GATT characteristic
+ * @param[in] connection D-Bus connection
+ * @param[in] path object patch to get get property from
+ * @param[in] method acquire method returning wished file descriptor
+ * @param[out] error D-Bus error set on error
+ * @return file descriptor
+ * @note sets dbus_error on error
+ */
+static int bluez_gatt_acquire(DBusConnection *connection, const char *path, const char *method, DBusError *error) {
+ assert(connection);
+ assert(path);
+ assert(error);
+
+ int fd = 0;
+ uint16_t mtu;
+
+ DBusMessage* query = dbus_message_new_method_call("org.bluez", path, "org.bluez.GattCharacteristic1", method);
+ assert(query);
+ DBusMessageIter iter, dict;
+ dbus_message_iter_init_append(query, &iter);
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+ dbus_message_iter_close_container(&iter, &dict);
+ DBusMessage*reply = dbus_connection_send_with_reply_and_block(connection, query, -1, error);
+ if (query) {
+ dbus_message_unref(query);
+ query = NULL;
+ }
+ if (dbus_error_is_set(error)) {
+ return 0;
+ }
+ if (!dbus_message_get_args(reply, NULL, DBUS_TYPE_UNIX_FD, &fd, DBUS_TYPE_UINT16, &mtu, DBUS_TYPE_INVALID)) {
+ dbus_set_error_const(error, "invalid_argument", "invalid acquire response");
+ }
+ if (reply) {
+ dbus_message_unref(reply);
+ reply = NULL;
+ }
+
+ return fd;
+}
+
+/* write data to GATT characteristic
+ * @param[in] connection D-Bus connection
+ * @param[in] path object patch to get get property from
+ * @param[out] error D-Bus error set on error
+ * @param[in] bytes data to write
+ * @param[in] length length of data to write
+ * @note sets dbus_error on error
+ */
+static void bluez_gatt_write(DBusConnection *connection, const char *path, DBusError *error, const uint8_t** bytes, size_t length) {
+ if (!bytes || 0 == length) {
+ return;
+ }
+ assert(connection);
+ assert(path);
+ assert(error);
+
+ DBusMessage* query = dbus_message_new_method_call("org.bluez", path, "org.bluez.GattCharacteristic1", "WriteValue");
+ assert(query);
+ DBusMessageIter iter, array, dict;
+ dbus_message_iter_init_append(query, &iter);
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "y", &array);
+ dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, bytes, length);
+ dbus_message_iter_close_container(&iter, &array);
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+ dbus_message_iter_close_container(&iter, &dict);
+ DBusMessage*reply = dbus_connection_send_with_reply_and_block(connection, query, -1, error);
+ if (query) {
+ dbus_message_unref(query);
+ query = NULL;
+ }
+ if (dbus_error_is_set(error)) {
+ return;
+ }
+ if (reply) {
+ dbus_message_unref(reply);
+ reply = NULL;
+ }
+}
+
+struct u2_values_t {
+ float voltage_vbus;
+ float current_vbus;
+ float voltage_dp;
+ float voltage_dm;
+ float charge;
+ float energy;
+ float temperature_internal;
+ float temperature_external;
+ int8_t accel_x;
+ int8_t accel_y;
+ int8_t accel_z;
+ uint32_t time_on;
+ uint32_t time_rec;
+ uint8_t group;
+ uint8_t messages; // bit mask of which messages have been received
+};
+
+int main(int argc, char* argv[])
+{
+ if (argc >= 2) {
+ if (0 == strcmp(argv[1],"-h")) {
+ printf("%s [-d|-h] [host port]\n", argv[0]);
+ printf("connect to QWay U2 over Bluetooth and output measurements as CSV\n\n");
+ printf("\t-h\tprint help\n");
+ printf("\t-d\tenable debug output\n");
+ printf("\thost port\tinfluxDB UDP host and port to send energy measurements to\n");
+ exit(0);
+ } if (0 == strcmp(argv[1],"-d")) {
+ debug = true;
+ }
+ }
+
+ // 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 crete UDP socket\n");
+ }
+ }
+
+ // D-Bus variables
+ DBusConnection *connection = NULL;
+ DBusError error;
+ char** paths = NULL; // to get the object paths
+
+ // connect to system D-Bus
+ dbus_error_init(&error);
+ connection = dbus_bus_get(DBUS_BUS_SYSTEM, &error);
+ if (dbus_error_is_set(&error)) {
+ die("dbus: can't connect to system bus (%s)\n", error.message);
+ dbus_error_free(&error);
+ } else if (NULL == connection) {
+ die("dbus: can't connect to system bus\n");
+ }
+ printf_debug("connected to D-BUS\n");
+
+ // get adapter
+ unsigned int paths_count = dbus_get_object_paths_by_interface(connection, "org.bluez", "org.bluez.Adapter1", &error, &paths);
+ if (dbus_error_is_set(&error)) {
+ die("dbus: can't get bus objects (%s)\n", error.message);
+ dbus_error_free(&error);
+ }
+ if (paths_count < 1) {
+ die("dbus: no bluetooth adapter found\n");
+ }
+ if (!paths) {
+ die("dbus: no bluetooth adapter paths found\n");
+ }
+ if (!paths[0]) {
+ die("dbus: no bluetooth adapter path found\n");
+ }
+ char* adapter_path = paths[0]; // pick first adapter path
+ for (unsigned int i = 1; i < paths_count; i++) {
+ if (paths[i]) {
+ free(paths[i]);
+ paths[i] = NULL;
+ }
+ }
+ if (paths) {
+ free(paths);
+ paths = NULL;
+ }
+ printf_debug("BlueZ adapter found: %s\n", adapter_path);
+
+ // power on adapter
+ bool powered;
+ dbus_get_property(connection, "org.bluez", adapter_path, "org.bluez.Adapter1", "Powered", &error, DBUS_TYPE_BOOLEAN, &powered);
+ if (dbus_error_is_set(&error)) {
+ die("dbus: can't read adapter Powered property from %s (%s)\n", adapter_path, error.message);
+ dbus_error_free(&error);
+ }
+ printf_debug("adapter is %spowered\n", powered ? "" : "not ");
+
+ if (!powered) {
+ printf("powering adapter\n");
+ powered = true;
+ dbus_set_property(connection, "org.bluez", adapter_path, "org.bluez.Adapter1", "Powered", &error, DBUS_TYPE_BOOLEAN_AS_STRING, DBUS_TYPE_BOOLEAN, &powered);
+ if (dbus_error_is_set(&error)) {
+ die("dbus: can't write adapter Powered property to %s (%s)\n", adapter_path, error.message);
+ dbus_error_free(&error);
+ }
+ dbus_get_property(connection, "org.bluez", adapter_path, "org.bluez.Adapter1", "Powered", &error, DBUS_TYPE_BOOLEAN, &powered);
+ if (dbus_error_is_set(&error)) {
+ die("dbus: can't read adapter Powered property from %s (%s)\n", adapter_path, error.message);
+ dbus_error_free(&error);
+ }
+ if (!powered) {
+ die("could not power adapter\n");
+ }
+ }
+
+ // start discovering devices
+ bool already_discovering;
+ dbus_get_property(connection, "org.bluez", adapter_path, "org.bluez.Adapter1", "Discovering", &error, DBUS_TYPE_BOOLEAN, &already_discovering);
+ if (dbus_error_is_set(&error)) {
+ die("dbus: can't read adapter Discovering property from %s (%s)\n", adapter_path, error.message);
+ dbus_error_free(&error);
+ }
+ printf_debug("adapter is %sdiscovering\n", already_discovering ? "" : "not ");
+
+ if (!already_discovering) {
+ printf_debug("start discovery\n");
+ dbus_call_method(connection, "org.bluez", adapter_path, "org.bluez.Adapter1", "StartDiscovery", &error);
+ if (dbus_error_is_set(&error)) {
+ die("dbus: can't start discovery (%s)\n", error.message);
+ dbus_error_free(&error);
+ }
+
+ bool discovering;
+ dbus_get_property(connection, "org.bluez", adapter_path, "org.bluez.Adapter1", "Discovering", &error, DBUS_TYPE_BOOLEAN, &discovering);
+ if (dbus_error_is_set(&error)) {
+ die("dbus: can't read adapter Discovering property from %s (%s)\n", adapter_path, error.message);
+ dbus_error_free(&error);
+ }
+ if (!discovering) {
+ die("cannot discover devices\n");
+ }
+ }
+
+ // wait until Qway device is discovered (search by name and check if active)
+ char* device_path = NULL; // D-Bus path of the Qway device
+ while (!device_path) {
+ unsigned int device_count = dbus_get_object_paths_by_interface(connection, "org.bluez", "org.bluez.Device1", &error, &paths);
+ if (dbus_error_is_set(&error)) {
+ die("dbus: can't get bus objects (%s)\n", error.message);
+ dbus_error_free(&error);
+ }
+ for (unsigned int i = 0; paths && i < device_count; i++) {
+ if (!device_path && paths[i]) {
+ char* name = NULL;
+ dbus_get_property(connection, "org.bluez", paths[i], "org.bluez.Device1", "Name", &error, DBUS_TYPE_STRING, &name);
+ if (dbus_error_is_set(&error)) { // ignore device with unknown names
+ dbus_error_free(&error);
+ } else if (0 == strncmp(name, device_prefix, strlen(device_prefix))) { // check if it is a U2
+ printf_debug("device %s found: %s\n", name, paths[i]);
+ bool reachable = false;
+ bool connected = false;
+ dbus_get_property(connection, "org.bluez", paths[i], "org.bluez.Device1", "Connected", &error, DBUS_TYPE_BOOLEAN, &connected);
+ if (dbus_error_is_set(&error)) {
+ die("dbus: can't read device Connected property from %s (%s)\n", paths[i], error.message);
+ dbus_error_free(&error);
+ } else if (connected) {
+ reachable = true;
+ }
+ int16_t rssi;
+ dbus_get_property(connection, "org.bluez", paths[i], "org.bluez.Device1", "RSSI", &error, DBUS_TYPE_INT16, &rssi);
+ if (dbus_error_is_set(&error)) { // actual error not checked
+ dbus_error_free(&error);
+ } else {
+ reachable = true;
+ }
+ if (reachable) {
+ device_path = paths[i]; // found active device
+ } else {
+ printf_debug("device unreachable\n");
+ }
+ }
+ }
+ if (paths[i] && !(device_path && 0 == strcmp(device_path, paths[i]))) {
+ free(paths[i]);
+ paths[i] = NULL;
+ }
+ }
+ if (paths) {
+ free(paths);
+ paths = NULL;
+ }
+ if (!device_path) {
+ printf_debug("device not found, waiting a bit\n");
+ sleep(3);
+ }
+ }
+ if (!device_path) {
+ die("no device found\n");
+ }
+ if (!already_discovering) {
+ bool discovering;
+ dbus_get_property(connection, "org.bluez", adapter_path, "org.bluez.Adapter1", "Discovering", &error, DBUS_TYPE_BOOLEAN, &discovering);
+ if (dbus_error_is_set(&error)) {
+ die("dbus: can't read adapter Discovering property from %s (%s)\n", adapter_path, error.message);
+ dbus_error_free(&error);
+ }
+ if (discovering) {
+ printf_debug("stop discovery\n");
+ dbus_call_method(connection, "org.bluez", adapter_path, "org.bluez.Adapter1", "StopDiscovery", &error);
+ if (dbus_error_is_set(&error)) {
+ die("dbus: can't stop discovery (%s)\n", error.message);
+ dbus_error_free(&error);
+ }
+ dbus_get_property(connection, "org.bluez", adapter_path, "org.bluez.Adapter1", "Discovering", &error, DBUS_TYPE_BOOLEAN, &discovering);
+ if (dbus_error_is_set(&error)) {
+ die("dbus: can't read adapter Discovering property from %s (%s)\n", adapter_path, error.message);
+ dbus_error_free(&error);
+ }
+ if (discovering) {
+ die("cannot stop device discovery\n");
+ }
+ }
+ }
+
+ // connect to device
+ bool already_connected;
+ dbus_get_property(connection, "org.bluez", device_path, "org.bluez.Device1", "Connected", &error, DBUS_TYPE_BOOLEAN, &already_connected);
+ if (dbus_error_is_set(&error)) {
+ die("dbus: can't read device Connected property from %s (%s)\n", device_path, error.message);
+ dbus_error_free(&error);
+ }
+ printf_debug("device is %sconnected\n", already_connected ? "" : "not ");
+
+ if (!already_connected) {
+ printf_debug("connecting to device\n");
+ dbus_call_method(connection, "org.bluez", device_path, "org.bluez.Device1", "Connect", &error);
+ if (dbus_error_is_set(&error)) {
+ die("dbus: can't connect to %s (%s)\n", device_path, error.message);
+ dbus_error_free(&error);
+ }
+
+ bool connected;
+ dbus_get_property(connection, "org.bluez", device_path, "org.bluez.Device1", "Connected", &error, DBUS_TYPE_BOOLEAN, &connected);
+ if (dbus_error_is_set(&error)) {
+ die("dbus: can't read device Connected property from %s (%s)\n", device_path, error.message);
+ dbus_error_free(&error);
+ }
+ if (!connected) {
+ die("cannot connect to device\n");
+ }
+ }
+
+ // wait until the services are resolved
+ bool resolved = false;
+ while (!resolved) {
+ dbus_get_property(connection, "org.bluez", device_path, "org.bluez.Device1", "ServicesResolved", &error, DBUS_TYPE_BOOLEAN, &resolved);
+ if (dbus_error_is_set(&error)) {
+ die("dbus: can't read device ServicesResolved property from %s (%s)\n", device_path, error.message);
+ dbus_error_free(&error);
+ }
+ bool connected;
+ dbus_get_property(connection, "org.bluez", device_path, "org.bluez.Device1", "Connected", &error, DBUS_TYPE_BOOLEAN, &connected);
+ if (dbus_error_is_set(&error)) {
+ die("dbus: can't read device Connected property from %s (%s)\n", device_path, error.message);
+ dbus_error_free(&error);
+ }
+ if (!connected) {
+ die("connection to device lost\n");
+ }
+ if (!resolved) {
+ printf_debug("waiting for services to be resolved\n");
+ sleep(1);
+ }
+ }
+
+ // get path to HM-10 custom characteristic to exchange data 0000FFE1-0000-1000-8000-00805F9B34FB
+ char* char_path = NULL;
+ unsigned int char_count = dbus_get_object_paths_by_interface(connection, "org.bluez", "org.bluez.GattCharacteristic1", &error, &paths);
+ if (dbus_error_is_set(&error)) {
+ die("dbus: can't get GATT characteristics (%s)\n", error.message);
+ dbus_error_free(&error);
+ }
+ for (unsigned int i = 0; paths && i < char_count; i++) {
+ if (paths[i]) {
+ if (0 == strncmp(paths[i], device_path, strlen(device_path))) { // only check characteristic from our device
+ char* uuid = NULL;
+ dbus_get_property(connection, "org.bluez", paths[i], "org.bluez.GattCharacteristic1", "UUID", &error, DBUS_TYPE_STRING, &uuid);
+ if (dbus_error_is_set(&error)) {
+ die("dbus: can't read UUID from %s (%s)\n", paths[i], error.message);
+ dbus_error_free(&error);
+ } else if (0 == strcmp(uuid, uart_uuid)) { // check if it is the characteristic we need
+ printf_debug("characteristic found: %s\n", paths[i]);
+ char_path = paths[i];
+ }
+ }
+ if (!char_path || strcmp(char_path, paths[i])) {
+ free(paths[i]);
+ paths[i] = NULL;
+ }
+ }
+ }
+ if (paths) {
+ free(paths);
+ paths = NULL;
+ }
+ if (!char_path) {
+ printf_debug("device characteristic not found\n");
+ goto close;
+ }
+
+ // open read
+ bool notifying;
+ dbus_get_property(connection, "org.bluez", char_path, "org.bluez.GattCharacteristic1", "Notifying", &error, DBUS_TYPE_BOOLEAN, &notifying);
+ if (dbus_error_is_set(&error)) {
+ die("dbus: can't read GATT Notifying property from %s (%s)\n", char_path, error.message);
+ dbus_error_free(&error);
+ }
+ if (notifying) {
+ printf("device characteristic notification is already used by someone else\n");
+ goto close;
+ }
+
+ bool notify_acquired;
+ dbus_get_property(connection, "org.bluez", char_path, "org.bluez.GattCharacteristic1", "Notifying", &error, DBUS_TYPE_BOOLEAN, &notify_acquired);
+ if (dbus_error_is_set(&error)) {
+ die("dbus: can't read GATT Notifying property from %s (%s)\n", char_path, error.message);
+ dbus_error_free(&error);
+ }
+ if (notifying) {
+ printf_debug("device characteristic notification is already used by someone else\n");
+ goto close;
+ }
+
+ int notify_fd = bluez_gatt_acquire(connection, char_path, "AcquireNotify", &error);
+ if (dbus_error_is_set(&error)) {
+ die("dbus: can't acquire write file descriptor from %s (%s)\n", char_path, error.message);
+ dbus_error_free(&error);
+ }
+ // sadly this characteristic does not support AcquireWrite, thus we will hat to write through D-Bus
+
+ printf_debug("ready to receive data\n");
+ const uint8_t get_conf[] = {0xf1, 0x01, 0x00, 0x00, 0x00, 0xfe}; // message to get the configuration (also starts the measurements)
+ const uint8_t* packet_pointer = get_conf; // this is because &array == array
+ bluez_gatt_write(connection, char_path, &error, &packet_pointer, sizeof(get_conf));
+ if (dbus_error_is_set(&error)) {
+ fprintf(stderr, "dbus: can't write to %s (%s)\n", char_path, error.message);
+ // don't die since write error could be solved by disconnecting
+ dbus_error_free(&error);
+ goto close;
+ }
+ printf_debug("device information requested\n");
+
+ // read and parse 2 messages
+ uint8_t packet[20]; // to store the message read from U2
+ uint8_t packet_len = 0; // number of bytes read
+ struct u2_values_t u2_values; // to store latest U2 values
+ u2_values.messages = 0; // remember no message has been received
+ u2_values.group = 0xff; // to know it is not initialized
+ // print CSV header
+ printf("VBUS voltage (V), VBUS current (A), D+ voltage (V), D- voltage (V), temperature internal (°C), temperature external (°C), on time (s), recording time (s), acceleration x-axis, acceleration Y-axis, acceleration Z-axis, charge (Ah), energy (Wh), group\n");
+ while (true) {
+ fd_set readfds;
+ FD_ZERO(&readfds);
+ FD_SET(notify_fd, &readfds);
+ struct timeval timeout;
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 100000;
+ int activity = select(notify_fd + 1, &readfds, NULL, NULL, &timeout);
+ if (activity < 0) { // error received
+ die("select error\n");
+ } else if (activity > 0) { // data received
+ uint8_t rbuff[20]; // 20 bytes is the maximum BLE packet size
+ ssize_t count = read(notify_fd, rbuff, sizeof(rbuff));
+ if (count <= 0) {
+ fprintf(stderr, "can't read from device\n");
+ goto close;
+ }
+ for (ssize_t i = 0; i < count; i++) {
+ printf_debug("%02x ", rbuff[i]);
+ }
+ printf_debug("\n");
+ // copy data to packet
+ if (0xf1 == rbuff[0] || 0xfe == rbuff[0]) { // message header detected
+ for (packet_len = 0; packet_len < count && packet_len < sizeof(packet); packet_len++) {
+ packet[packet_len] = rbuff[packet_len];
+ }
+ } else if (packet_len < sizeof(packet)) { // copy rest of packet
+ for (uint8_t i = 0; i < count && packet_len + i < sizeof(packet); i++) {
+ packet[packet_len++] = rbuff[i];
+ }
+ }
+ // line containing values for influxdb
+ char line[21 * 4];
+ int line_len;
+ if (packet_len == sizeof(packet)) { // message is complete
+ if (0xf1 == packet[0] && 0x01 == packet[1]) { // device information packet
+ printf_debug("device serial: %c%c%c%c%c%c\n", packet[2], packet[3], packet[4], packet[5], packet[6], packet[7]);
+ printf_debug("firmware version: %u.%u\n", packet[8] / 16, packet[8] % 16);
+ float fvalue; // to store floating values
+ uint32_t uvalue; // to store unsigned integer values
+ memcpy(&uvalue, &packet[9], 4);
+ printf_debug("run: %u\n", uvalue);
+ memcpy(&uvalue, &packet[13], 1);
+ printf_debug("current threshold: %.04f A\n", uvalue / 1000.0);
+ memcpy(&fvalue, &packet[14], 4);
+ printf_debug("energy: %.04f Wh\n", fvalue);
+ printf_debug("group: %u\n", packet[18] + 1);
+ } else if (0xfe == packet[0] && packet[1] < 6 && packet[18] < 6 && 0x00 == packet[19]) { // measurement packet (use group limit and last byte to somehow detect transmission errors)
+ u2_values.messages |= (1 << packet[1]);
+ if (u2_values.group != (packet[18] + 1)) { // new group
+ u2_values.messages = 0; // remember not all message has been received
+ u2_values.group = (packet[18] + 1); // remember new group
+ printf_debug("new group\n");
+ }
+ // VBUS voltage and current are in every message
+ memcpy(&u2_values.voltage_vbus, &packet[2], 4);
+ memcpy(&u2_values.current_vbus, &packet[6], 4);
+ switch (packet[1]) {
+ case 1:
+ memcpy(&u2_values.voltage_dp, &packet[10], 4);
+ memcpy(&u2_values.voltage_dm, &packet[14], 4);
+ break;
+ case 2:
+ memcpy(&u2_values.temperature_internal, &packet[10], 4);
+ memcpy(&u2_values.temperature_external, &packet[14], 4);
+ break;
+ case 3:
+ memcpy(&u2_values.time_on, &packet[10], 4);
+ memcpy(&u2_values.time_rec, &packet[14], 4);
+ break;
+ case 4:
+ u2_values.accel_x = (uint8_t)packet[10];
+ u2_values.accel_y = (uint8_t)packet[11];
+ u2_values.accel_z = (uint8_t)packet[12];
+ memcpy(&u2_values.time_rec, &packet[14], 4);
+ break;
+ case 5:
+ memcpy(&u2_values.charge, &packet[10], 4);
+ memcpy(&u2_values.energy, &packet[14], 4);
+ // send values to influxDB
+ if (influxdb_fd) {
+ line_len = snprintf(line, sizeof(line), "voltage value=%.04f\ncurrent value=%.04f\ncharge value=%.04f\nenergy value=%.04f\n", u2_values.voltage_vbus, u2_values.current_vbus, u2_values.charge, u2_values.energy);
+ if (-1 == sendto(influxdb_fd, line, line_len, 0, res->ai_addr, res->ai_addrlen)) {
+ printf("could not send UDP packet\n");
+ goto close;
+ }
+ }
+ break;
+ default:
+ printf("unknown type %u\n", packet[1]);
+ }
+ if (0x3e == u2_values.messages) { // all messages received all least once
+ // print CSV
+ printf("%.04f,", u2_values.voltage_vbus);
+ printf("%.04f,", u2_values.current_vbus);
+ printf("%.04f,", u2_values.voltage_dp);
+ printf("%.04f,", u2_values.voltage_dm);
+ printf("%.01f,", u2_values.temperature_internal);
+ printf("%.01f,", u2_values.temperature_external);
+ printf("%02u:%02u:%02u,", u2_values.time_on / 60 / 60, (u2_values.time_on / 60) % 60, u2_values.time_on % 60);
+ printf("%02u:%02u:%02u,", u2_values.time_rec / 60 / 60, (u2_values.time_rec / 60) % 60, u2_values.time_rec % 60);
+ printf("%d,", u2_values.accel_x);
+ printf("%d,", u2_values.accel_y);
+ printf("%d,", u2_values.accel_z);
+ printf("%.04f,", u2_values.charge);
+ printf("%.04f,", u2_values.energy);
+ printf("%u", u2_values.group);
+ printf("\n");
+ }
+ }
+ packet_len = 0; // mark packet as read
+ }
+ } else if (0 == activity) { // timeout reached
+ const uint8_t start_meas[] = {0xf1, 0x02, 0x00, 0x00, 0x00, 0xfe}; // message to get measurements
+ packet_pointer = start_meas; // this is because &array == array
+ bluez_gatt_write(connection, char_path, &error, &packet_pointer, sizeof(start_meas));
+ if (dbus_error_is_set(&error)) {
+ fprintf(stderr, "dbus: can't write to %s (%s)\n", char_path, error.message);
+ dbus_error_free(&error);
+ goto close;
+ }
+ printf_debug("resume measurements\n");
+ }
+ }
+
+close:
+ // close file descriptors
+ if (notify_fd) {
+ close(notify_fd);
+ notify_fd = 0;
+ }
+ if (influxdb_fd) {
+ close(influxdb_fd);
+ influxdb_fd = 0;
+ }
+ // close device connection
+ if (!already_connected) {
+ bool connected = false;
+ dbus_get_property(connection, "org.bluez", device_path, "org.bluez.Device1", "Connected", &error, DBUS_TYPE_BOOLEAN, &connected);
+ if (dbus_error_is_set(&error)) {
+ die("dbus: can't read device Connected property from %s (%s)\n", device_path, error.message);
+ dbus_error_free(&error);
+ }
+ if (connected) {
+ printf_debug("disconnecting from device\n");
+ dbus_call_method(connection, "org.bluez", device_path, "org.bluez.Device1", "Disconnect", &error);
+ if (dbus_error_is_set(&error)) {
+ die("dbus: can't disconnect from %s (%s)\n", device_path, error.message);
+ dbus_error_free(&error);
+ }
+
+ dbus_get_property(connection, "org.bluez", device_path, "org.bluez.Device1", "Connected", &error, DBUS_TYPE_BOOLEAN, &connected);
+ if (dbus_error_is_set(&error)) {
+ die("dbus: can't read device Connected property from %s (%s)\n", device_path, error.message);
+ dbus_error_free(&error);
+ }
+ if (connected) {
+ die("cannot disconnect from device\n");
+ }
+ }
+ }
+
+ // close dbus connection
+ if (connection) {
+ dbus_connection_unref(connection); // unref instead of close for system bus connections
+ connection = NULL;
+ }
+}
diff --git a/u2_usb.c b/u2_usb.c
new file mode 100644
index 0000000..19862e9
--- /dev/null
+++ b/u2_usb.c
@@ -0,0 +1,264 @@
+/* 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 <hidapi/hidapi.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);
+}
+
+/* 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;
+ if (2 == argc && 0 == strcmp("-d", argv[1])) {
+ debug = true;
+ }
+
+ 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 8in °C),VBUS voltage 2 (in V),VBUS current 4 (in A)\n");
+ }
+
+ bool run = true;
+ struct u2_measurement_t meas;
+ 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");
+ }
+ }
+
+ // 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");
+ }
+ }
+ }
+
+ hid_close(handle);
+ handle = NULL;
+ rc = hid_exit();
+ if (rc < 0) {
+ die("could not exit HID\n");
+ }
+
+ return 0;
+}