/* Copyright 2019-2020 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 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include // 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); } /* 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"); } /* 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; } } signal(SIGALRM, watchdog); // setup watchdog alarm(30); // 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"); } } // 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, ¬ifying); 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, ¬ify_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[22 * 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) alarm(10); // restart watchdog 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; } }