931 lines
34 KiB
C
931 lines
34 KiB
C
/* Copyright 2019-2020 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 <signal.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);
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
}
|