--- /dev/null
+/*
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) 2014 John Crispin <blogic@openwrt.org>
+ * Copyright (C) 2021 Nuno Goncalves <nunojpg@gmail.com>
+ */
+
+#include <fcntl.h>
+#include <termios.h>
+
+#include <libubox/ulog.h>
+#include <libubox/ustream.h>
+#include <libubox/utils.h>
+#include <libubox/uloop.h>
+#include <libubus.h>
+
+static struct ustream_fd stream;
+static struct ubus_auto_conn conn;
+static struct blob_buf b;
+
+struct Battery
+{
+ float temperature;
+ uint16_t cycles;
+ uint8_t soc;
+ bool charging;
+ bool set;
+} battery;
+
+static bool
+process(char *read)
+{
+ if (read[0] != '{' ||
+ read[1] != 'O' ||
+ read[2] != 'K' ||
+ read[3] != '}' ||
+ read[4] != ',')
+ return false;
+ const char *from = read + 5;
+ char *to;
+ battery.soc = strtoul(from, &to, 10);
+ if (from == to)
+ return false;
+ from = to + 1;
+ battery.temperature = strtoul(from, &to, 10) / 10.0f;
+ if (from == to)
+ return false;
+ if (to[0] != ',' || (to[1] != '0' && to[1] != '1') || to[2] != ',')
+ return false;
+ battery.charging = to[1] == '1';
+ from = to + 3;
+ battery.cycles = strtoul(from, &to, 10);
+ if (from == to)
+ return false;
+ return true;
+}
+
+static int
+consume(struct ustream *s, char **a)
+{
+ char *eol = strstr(*a, "\n");
+
+ if (!eol)
+ return -1;
+
+ *eol++ = '\0';
+
+ battery.set = process(*a);
+ if (!battery.set)
+ ULOG_ERR("failed to parse message from serial: %s", a);
+
+ ustream_consume(s, eol - *a);
+ *a = eol;
+
+ return 0;
+}
+
+static void
+msg_cb(struct ustream *s, int bytes)
+{
+ int len;
+ char *a = ustream_get_read_buf(s, &len);
+
+ while (!consume(s, &a))
+ ;
+}
+
+static void
+notify_cb(struct ustream *s)
+{
+ if (!s->eof)
+ return;
+
+ ULOG_ERR("tty error, shutting down\n");
+ exit(-1);
+}
+
+static int
+serial_open(char *dev)
+{
+ const int tty = open(dev, O_RDWR | O_NOCTTY | O_NONBLOCK);
+ if (tty < 0)
+ {
+ ULOG_ERR("%s: device open failed: %s\n", dev, strerror(errno));
+ return -1;
+ }
+
+ struct termios config;
+ tcgetattr(tty, &config);
+ cfmakeraw(&config);
+ cfsetispeed(&config, B9600);
+ cfsetospeed(&config, B9600);
+ tcsetattr(tty, TCSANOW, &config);
+
+ stream.stream.string_data = true;
+ stream.stream.notify_read = msg_cb;
+ stream.stream.notify_state = notify_cb;
+
+ ustream_fd_init(&stream, tty);
+
+ tcflush(tty, TCIFLUSH);
+
+ return 0;
+}
+
+static struct uloop_timeout serial_query_timer;
+static void
+serial_query_handler(struct uloop_timeout *timeout)
+{
+ const char cmd[] = "{ \"mcu_status\": \"1\" }\n";
+ const unsigned cmd_len = sizeof(cmd) - 1;
+ ustream_write(&stream.stream, cmd, cmd_len, false);
+ uloop_timeout_set(&serial_query_timer, 3000); // timeout in 3 sec
+ uloop_timeout_add(timeout);
+}
+
+static int
+battery_info(struct ubus_context *ctx, struct ubus_object *obj,
+ struct ubus_request_data *req, const char *method,
+ struct blob_attr *msg)
+{
+ blob_buf_init(&b, 0);
+
+ if (!battery.set)
+ {
+ blobmsg_add_u8(&b, "error", 1);
+ }
+ else
+ {
+ blobmsg_add_u16(&b, "soc", battery.soc);
+ blobmsg_add_u8(&b, "charging", battery.charging);
+ blobmsg_add_double(&b, "temperature", battery.temperature);
+ blobmsg_add_u16(&b, "cycles", battery.cycles);
+ }
+ ubus_send_reply(ctx, req, b.head);
+
+ return UBUS_STATUS_OK;
+}
+
+static const struct ubus_method battery_methods[] = {
+ UBUS_METHOD_NOARG("info", battery_info),
+};
+
+static struct ubus_object_type battery_object_type =
+ UBUS_OBJECT_TYPE("battery", battery_methods);
+
+static struct ubus_object battery_object = {
+ .name = "battery",
+ .type = &battery_object_type,
+ .methods = battery_methods,
+ .n_methods = ARRAY_SIZE(battery_methods),
+};
+
+static void
+ubus_connect_handler(struct ubus_context *ctx)
+{
+ int ret;
+
+ ret = ubus_add_object(ctx, &battery_object);
+ if (ret)
+ fprintf(stderr, "Failed to add object: %s\n", ubus_strerror(ret));
+}
+
+int
+main(int argc, char **argv)
+{
+
+ uloop_init();
+ conn.path = NULL;
+ conn.cb = ubus_connect_handler;
+ ubus_auto_connect(&conn);
+
+ if (serial_open("/dev/ttyUSB0") < 0)
+ return -1;
+
+ serial_query_timer.cb = serial_query_handler;
+ serial_query_handler(&serial_query_timer);
+ uloop_run();
+ uloop_done();
+
+ return 0;
+}