From 1aee39b3b4048ad8fc6e442cc5d0d79be1b6f233 Mon Sep 17 00:00:00 2001 From: Trygve Laugstøl Date: Mon, 11 Apr 2016 08:24:56 +0200 Subject: o Initial import of Diller library. --- src/diller.h | 2 + src/diller/client.h | 209 +++++++++++++++++++++++++ src/diller/core.h | 69 +++++++++ src/diller/core_impl.h | 159 +++++++++++++++++++ src/diller/serial.h | 44 ++++++ src/diller/serial_impl.h | 178 +++++++++++++++++++++ src/diller/utils.h | 348 ++++++++++++++++++++++++++++++++++++++++++ src/diller_client.h | 209 ------------------------- src/diller_core.h | 71 --------- src/diller_serial.h | 45 ------ src/diller_utils.h | 348 ------------------------------------------ src/impl/diller_core_impl.h | 150 ------------------ src/impl/diller_serial_impl.h | 183 ---------------------- 13 files changed, 1009 insertions(+), 1006 deletions(-) create mode 100644 src/diller.h create mode 100644 src/diller/client.h create mode 100644 src/diller/core.h create mode 100644 src/diller/core_impl.h create mode 100644 src/diller/serial.h create mode 100644 src/diller/serial_impl.h create mode 100644 src/diller/utils.h delete mode 100644 src/diller_client.h delete mode 100644 src/diller_core.h delete mode 100644 src/diller_serial.h delete mode 100644 src/diller_utils.h delete mode 100644 src/impl/diller_core_impl.h delete mode 100644 src/impl/diller_serial_impl.h diff --git a/src/diller.h b/src/diller.h new file mode 100644 index 0000000..3f59c93 --- /dev/null +++ b/src/diller.h @@ -0,0 +1,2 @@ +#pragma once + diff --git a/src/diller/client.h b/src/diller/client.h new file mode 100644 index 0000000..d10add9 --- /dev/null +++ b/src/diller/client.h @@ -0,0 +1,209 @@ +#pragma once + +#include +#include +#include "diller/utils.h" + +namespace diller { +namespace client { + +using namespace diller::utils; + +enum class diller_cmd : uint8_t { + UNKNOWN, + STATUS, + NETWORK, + WLAN, + PROPERTY, + PROPERTIES +}; + +class diller_event_listener { + public: + virtual void on_diller_event(const key_value_map ¶ms) = 0; +}; + +class client { + public: + client() : listener(nullptr) { + } + + virtual ~client() { + } + + virtual void property(const char *property, const char *value, const char *name) = 0; + virtual void property_value(const char *property, const char *value) = 0; + virtual void property_name(const char *property, const char *name) = 0; + virtual void property_description(const char *property, const char *description) = 0; + + void set_diller_event_listener(diller_event_listener *l) { + listener = l; + } + + protected: + diller_event_listener *listener; +}; + +template> +class client_software_serial : public client { + public: + client_software_serial() : client(), params(), parser(params) { + } + + void wlan(const char *ssid, const char *password) { + io_t::print("wlan ssid="); + escape(ssid); + io_t::print(" password="); + escape(password); + } + + void property(const char *property, const char *value, const char *name) { + io_t::print("property id="); + escape(property); + io_t::print(" value="); + escape(value); + io_t::print(" name="); + escape(name); + } + + void property_value(const char *property, const char *value) { + io_t::print("property id="); + escape(property); + io_t::print(" value="); + escape(value); + } + + void property_name(const char *property, const char *name) { + io_t::print("property id="); + escape(property); + io_t::print(" name="); + escape(name); + } + + void property_description(const char *property, const char *description) { + io_t::print("property id="); + escape(property); + io_t::print(" description="); + escape(description); + } + + void loop() { + auto x = tty.readline(); + // Serial.print("x="); + // Serial.println(static_cast(x)); + if (x == tty_status::FULL_LINE) { + parser.parse(tty.line); + + process_command(); + } + } + + protected: + void escape(const char *s) { + char c; + while ((c = *s++) != '\0') { + if (c == ' ') { + io_t::print('\\'); + } + io_t::print(c); + } + } + + void process_command() { + if (params.is_empty()) { + return; + } + + if (!listener) { + return; + } + + listener->on_diller_event(params); + params.clear(); + } + + private: + // SoftwareSerial serial; + key_value_map_t params; + diller::utils::diller_parser parser; + diller::utils::tty tty; +}; + +void print_diller_event(const key_value_map ¶ms) { + if (!params.size()) { + return; + } + + Serial.print("Diller: "); + Serial.print(params.key(0)); + Serial.print(' '); + + for (uint8_t i = 1; i < params.size(); i++) { + auto key = params.key(i); + auto value = params.value(i); + + Serial.print(key); + if (value) { + Serial.print("="); + Serial.print(value); + } + Serial.print(" "); + } + Serial.println(); +} + +class printing_diller_event_listener : public diller_event_listener { + public: + void on_diller_event(const key_value_map ¶ms) { + print_diller_event(params); + } +}; + +class noop_diller_event_listener : public diller_event_listener { + public: + void on_diller_event(const key_value_map ¶ms) { + auto key = params.key(0); + if (strcmp("network", key) == 0) { + on_network(params); + } else if (strcmp("status", key) == 0) { + on_status(params); + } else if (strcmp("property", key) == 0) { + on_property(params); + } else if (strcmp("properties", key) == 0) { + on_properties(params); + } else if (strcmp("debug", key) == 0) { + on_debug(params); + } else { + on_unknown(params); + } + } + + protected: + virtual void on_network(const key_value_map ¶ms) { + static_cast(params); + } + + virtual void on_status(const key_value_map ¶ms) { + static_cast(params); + } + + virtual void on_property(const key_value_map ¶ms) { + static_cast(params); + } + + virtual void on_properties(const key_value_map ¶ms) { + static_cast(params); + } + + virtual void on_unknown(const key_value_map ¶ms) { + static_cast(params); + } + + virtual void on_debug(const key_value_map ¶ms) { + static_cast(params); + } +}; + +} // namespace client +} // namespace diller + diff --git a/src/diller/core.h b/src/diller/core.h new file mode 100644 index 0000000..e9d5c28 --- /dev/null +++ b/src/diller/core.h @@ -0,0 +1,69 @@ +#pragma once + +#include "diller/utils.h" + +namespace diller { +namespace core { + +using diller::utils::property; + +template +using props = diller::utils::properties; + +enum class diller_error : uint8_t { + OK, INVAL, NOMEM +}; + +static +const char * to_string(diller_error err) { + switch (err) { + case diller_error::OK: + return "ok"; + case diller_error::INVAL: + return "inval"; + case diller_error::NOMEM: + return "nomem"; + default: + return "unknown"; + } +} + +enum class property_action : uint8_t { + VALUE, + NAME, + DESCRIPTION, +}; + +class property_action_listener { + public: + virtual void on_property_action(const property *, property_action) = 0; +}; + +template +class core { + public: + core(const String &mqtt_host, int mqtt_port); + void set_property_action_listener(property_action_listener *); + + void setup(); + void loop(); + + bool connected() const; + + diller_error cmd_property(const char *id, const char *value, const char *name); + props properties; + + const String mqtt_host; + const int mqtt_port = 1883; + String mac; + String client_id; + + private: + void callback(char* topic_, byte* payload, unsigned int length); + property_action_listener *property_action_listener_; +}; + +} // namespace core +} // namespace diller + +#include "diller/core_impl.h" diff --git a/src/diller/core_impl.h b/src/diller/core_impl.h new file mode 100644 index 0000000..acb367d --- /dev/null +++ b/src/diller/core_impl.h @@ -0,0 +1,159 @@ +/* +extern WiFiClient wifi_client; +extern PubSubClient mqtt_client; +*/ + +namespace diller { +namespace core { + +using diller::utils::tty_status; +using diller::utils::property; + +class esp_wifi { + public: + static auto macAddress() -> decltype(WiFi.macAddress()) { + return WiFi.macAddress(); + } + + static auto status() -> decltype(WiFi.status()) { + return WiFi.status(); + } +}; + +// core::core + +template +core::core(const String &mqtt_host, int mqtt_port) : + mqtt_host(mqtt_host), mqtt_port(mqtt_port), property_action_listener_(nullptr) { + mac = esp_wifi::macAddress(); + mac.toLowerCase(); + client_id = "diller-" + mac; +} + +template +void core::callback(char* topic_, byte* payload, unsigned int length) { + Serial.print("got message on "); + Serial.println(topic_); + Serial.println("payload"); + Serial.write(payload, length); + Serial.println(); + + String prefix = "/diller/" + mac + "/property/"; + String topic(topic_); + if (!topic.startsWith(prefix)) { + Serial.print("debug bad-prefix: "); + Serial.println(topic); + return; + } + + topic.remove(0, prefix.length()); + property *p = nullptr; + for (auto i = 0; i < properties.size(); i++) { + auto *x = properties[i]; + if (topic.startsWith(x->id())) { + p = x; + break; + } + } + + if (p) { + topic.remove(0, p->id().length() + 1); + + Serial.print("debug property="); + Serial.print(p->id()); + Serial.print(", topic="); + Serial.println(topic); + + property_action a; + + if (topic == "value") { + Serial.println("debug action=value"); + a = property_action::VALUE; + } else if (topic == "name") { + Serial.println("debug action=name"); + a = property_action::NAME; + } else if (topic == "description") { + Serial.println("debug action=description"); + a = property_action::DESCRIPTION; + } else { + return; + } + + if (property_action_listener_) { + property_action_listener_->on_property_action(p, a); + } + } else { + Serial.print("debug unknown-property"); + } +} + +template +bool core::connected() const { + return mqtt_client.connected(); +} + +template +void core::setup() { + mqtt_client.setServer(mqtt_host.c_str(), mqtt_port); + mqtt_client.setCallback([this](char* topic_, byte* payload, unsigned int length) { + callback(topic_, payload, length); + }); +} + +template +void core::loop() { + auto wl_status = esp_wifi::status(); + + if (wl_status == WL_CONNECTED) { + if (!mqtt_client.loop()) { + Serial.println("status mqtt=connecting"); + if (mqtt_client.connect(client_id.c_str())) { + Serial.println("status mqtt=connected"); + } else { + Serial.println("status mqtt=disconnected"); + } + } + } +} + +template +diller_error core::cmd_property(const char *id, const char *value, const char *name) { + if (!id) { + return diller_error::INVAL; + } + + auto p = properties.find(id); + + if (!p) { + return diller_error::NOMEM; + } + + if (value) { + String topic = "/diller/" + mac + "/property/" + id + "/value"; + mqtt_client.publish(topic.c_str(), value); + } + + if (name) { + String topic = "/diller/" + mac + "/property/" + id + "/name"; + mqtt_client.publish(topic.c_str(), name); + } + + if (p->is_new()) { + String topic = "/diller/" + mac + "/property/" + id + "/#"; + mqtt_client.subscribe(topic.c_str()); + Serial.print("debug subscribing="); + Serial.println(topic); + + p->set_old(); + } + + return diller_error::OK; +} + +template +void core::set_property_action_listener(property_action_listener *l) { + this->property_action_listener_ = l; +} + +} // namespace core +} // namespace diller diff --git a/src/diller/serial.h b/src/diller/serial.h new file mode 100644 index 0000000..f80939e --- /dev/null +++ b/src/diller/serial.h @@ -0,0 +1,44 @@ +#pragma once + +#include "diller/utils.h" +#include "diller/core.h" + +namespace diller { +namespace serial { + +using diller::utils::property; +using diller::core::diller_error; +using diller::core::property_action; + +template +class diller_serial : protected diller::core::property_action_listener { + public: + diller_serial(d_core &diller) : diller(diller), params(), diller_parser(params) { + } + + void setup(); + void loop(); + + private: + void process_command(); + void on_property_action(const property *, property_action); + + void cmd_network(); + void cmd_wlan(); + void cmd_wlan(const char* ssid, const char* password); + void cmd_property(const char *id, const char *value, const char *name); + void cmd_list_properties(); + void show_status(wl_status_t wl_status); + + d_core &diller; + diller::utils::fixed_size_key_value_map<10> params; + diller::utils::diller_parser diller_parser; + diller::utils::tty tty; + + static const bool send_wlan_password = false; +}; + +} // namespace serial +} // namespace diller + +#include "diller/serial_impl.h" diff --git a/src/diller/serial_impl.h b/src/diller/serial_impl.h new file mode 100644 index 0000000..e80ed71 --- /dev/null +++ b/src/diller/serial_impl.h @@ -0,0 +1,178 @@ +extern WiFiClient wifi_client; + +namespace diller { +namespace serial { + +using diller::utils::tty_status; + +template +void diller_serial::cmd_network() { + Serial.print("ok ip="); + WiFi.localIP().printTo(Serial); + Serial.print(" gateway="); + WiFi.gatewayIP().printTo(Serial); + Serial.print(" netmask="); + WiFi.subnetMask().printTo(Serial); + Serial.println(); +} + +template +void diller_serial::cmd_wlan() { + Serial.print("ok ssid="); + tty.quote(WiFi.SSID()); + if (send_wlan_password) { + Serial.print(" password="); + Serial.print(WiFi.psk()); + } + + Serial.print(" mac="); + Serial.println(diller.mac); +} + +template +void diller_serial::cmd_wlan(const char* ssid, const char* password) { + WiFi.begin(ssid, password); + Serial.println("ok"); +} + +template +void diller_serial::cmd_property(const char *id, const char *value, const char *name) { + auto ret = diller.cmd_property(id, value, name); + + if (ret != diller_error::OK) { + Serial.print("fail error="); + Serial.println(to_string(ret)); + } +} + +template +void diller_serial::cmd_list_properties() { + Serial.print("ok count="); + Serial.println(diller.properties.size()); + for (auto i = 0; i < diller.properties.size(); i++) { + auto p = diller.properties[i]; + Serial.print("property id="); + Serial.print(p->id()); + Serial.print(" name="); + Serial.print(p->name()); + Serial.print(" description="); + Serial.print(p->description()); + Serial.println(); + } +} + +template +void diller_serial::show_status(wl_status_t wl_status) { + const char *wl_status_s; + if (wl_status == WL_IDLE_STATUS) { + wl_status_s = "idle"; + } else if (wl_status == WL_NO_SSID_AVAIL) { + wl_status_s = "no-ssid"; + } else if (wl_status == WL_SCAN_COMPLETED) { + wl_status_s = "scan-completed"; + } else if (wl_status == WL_CONNECTED) { + wl_status_s = "connected"; + } else if (wl_status == WL_CONNECT_FAILED) { + wl_status_s = "connect-failed"; + } else if (wl_status == WL_CONNECTION_LOST) { + wl_status_s = "connection-lost"; + } else if (wl_status == WL_DISCONNECTED) { + wl_status_s = "disconnected"; + } else { + wl_status_s = "unknown"; + } + + const char *mqtt_status_s; + if (diller.connected()) { + mqtt_status_s = "connected"; + } else { + mqtt_status_s = "disconnected"; + } + + Serial.print("status wlan="); + Serial.print(wl_status_s); + Serial.print(" mqtt="); + Serial.println(mqtt_status_s); +} + +template +void diller_serial::process_command() { + if (params.is_empty()) { + return; + } + + auto cmd = params.key(0); + + if (strcmp("network", cmd) == 0) { + if (params.size() == 1) { + cmd_network(); + } else { + Serial.println("fail error=invalid_argument"); + } + } else if (strcmp("wlan", cmd) == 0) { + if (params.size() == 1) { + cmd_wlan(); + } else if (params.size() == 3) { + const char* ssid = params.find("ssid"); + const char* password = params.find("password"); + cmd_wlan(ssid, password); + } else { + Serial.println("fail error=invalid_argument"); + } + } else if (strcmp("property", cmd) == 0) { + auto id = params.find("id"); + auto value = params.find("value"); + auto name = params.find("name"); + cmd_property(id, value, name); + } else if (strcmp("list-properties", cmd) == 0) { + cmd_list_properties(); + } else { + Serial.print("fail error=unknown_command cmd="); + tty.escape(cmd); + Serial.println(); + } +} + +template +void diller_serial::setup() { + diller.set_property_action_listener(this); +} + +template +void diller_serial::loop() { + if (tty.readline() == tty_status::FULL_LINE) { + diller_parser.parse(tty.line); + + process_command(); + } + + static auto last_wl_status = WiFi.status(); + auto wl_status = WiFi.status(); + + static auto last_status = millis(); + auto now = millis(); + static const auto show_status_interval = 5000; + + if (last_wl_status != wl_status) { + show_status(wl_status); + last_wl_status = wl_status; + last_status = now; + } else if (now > (last_status + show_status_interval)) { + show_status(wl_status); + last_status = now; + } +} + +template +void diller_serial::on_property_action(const property *p, property_action) { + Serial.print("property id="); + tty.quote(p->id()); + Serial.print(" value="); + tty.quote(p->value()); + Serial.print(" description="); + tty.quote(p->description()); + Serial.println(); +} + +} // namespace serial +} // namespace diller diff --git a/src/diller/utils.h b/src/diller/utils.h new file mode 100644 index 0000000..5b1224d --- /dev/null +++ b/src/diller/utils.h @@ -0,0 +1,348 @@ +#pragma once + +#include + +namespace diller { +namespace utils { + +enum class tty_status : uint8_t { + NEED_MORE, + TIMEOUT, + EMPTY_LINE, + FULL_LINE, + LINE_OVERFLOW, +}; + +class serial_io { + public: + static auto available() -> decltype(Serial.available()) { + return Serial.available(); + } + + static auto read() -> decltype(Serial.read()) { + return Serial.read(); + } + + template + static auto print(A a) -> decltype(Serial.print(a)) { + return Serial.print(a); + } + + template + static auto println(A a) -> decltype(Serial.println(a)) { + return Serial.println(a); + } +}; + +template +class software_serial_io { + public: + static auto available() -> decltype(instance::software_serial.available()) { + return instance::software_serial.available(); + } + + static auto read() -> decltype(instance::software_serial.read()) { + return instance::software_serial.read(); + } + + template + static auto print(A a) -> decltype(instance::software_serial.print(a)) { + return instance::software_serial.print(a); + } + + template + static auto println(A a) -> decltype(instance::software_serial.println(a)) { + return instance::software_serial.println(a); + } +}; + +template +class tty { + public: + char line[buffer_size]; + uint8_t size = 0; + const bool debug = debug_; + const unsigned long timeout_ms = timeout_ms_; + + void init(bool reset = false) { + if (reset) { + line[0] = '\0'; + } + + size = 0; + last_char = 0; + } + + void quote(const String &s) { + Serial.print(s.c_str()); + } + + void quote(const char *str) { + Serial.print(str); + } + + void escape(const char *str) { + Serial.print(str); + } + + tty_status readline() { + while (io::available()) { + int c = io::read(); + + if (debug) { + Serial.print("read: c="); + Serial.println(c, HEX); + } + + if (c == '\n') { + // ignore + last_char = millis(); + } else if (c == '\r') { + line[size] = '\0'; + auto status = size > 0 ? tty_status::FULL_LINE : tty_status::EMPTY_LINE; + init(); + + if (debug) { + Serial.print("debug src=tty state=\"new line\" size="); + Serial.print(size, DEC); + Serial.print(" line="); + quote(line); + Serial.println(); + } + + return status; + } else { + if (size == buffer_size - 1) { + + if (debug) { + Serial.print("debug src=tty state=overflow size="); + Serial.print(size, DEC); + Serial.print(" line="); + quote(line); + Serial.println(); + } + init(); + + return tty_status::LINE_OVERFLOW; + } + line[size++] = static_cast(c); + last_char = millis(); + } + } + + unsigned long now = millis(); + + if (last_char > 0 && now >= (last_char + timeout_ms)) { + if (debug) { + line[size] = '\0'; + Serial.print("debug src=tty state=\"timeout\" line="); + quote(line); + Serial.println(); + } + + init(true); + return tty_status::TIMEOUT; + } + + return tty_status::NEED_MORE; + } + private: + unsigned long last_char = 0; +}; + +class key_value_map { + public: + virtual void clear(); + virtual void put(char *key, char *value) = 0; + virtual const char *key(uint8_t index) const = 0; + virtual const char *value(uint8_t index) const = 0; + virtual const char *find(const char *key) const = 0; + virtual uint8_t size() const = 0; + virtual bool is_empty() const = 0; + virtual bool is_full() const = 0; +}; + +template +class fixed_size_key_value_map : public key_value_map { + public: + uint8_t size_; + static_assert(max_args <= 256, "max_args is too big"); + char *keys[max_args]; + char *values[max_args]; + + void clear() { + size_ = 0; + } + + void put(char *key, char *value) { + if (size_ < max_args) { + keys[size_] = key; + values[size_] = value; + size_++; + } + } + + const char *key(uint8_t index) const { + return keys[index]; + } + + const char *value(uint8_t index) const { + return values[index]; + } + + const char *find(const char *key) const { + for (int i = 0; i < size_; i++) { + if (strcmp(keys[i], key) == 0) { + return values[i]; + } + } + + return nullptr; + } + + uint8_t size() const { + return size_; + } + + bool is_empty() const { + return size_ >= max_args; + } + + bool is_full() const { + return size_ >= max_args; + } +}; + +class diller_parser { + public: + diller_parser(key_value_map &map) : map(map) { + } + + void parse(char *line) { + char *c = line; + map.clear(); + + while (*c != '\0') { + while (*c == ' ') { + c++; + } + if (*c == '\0') { + break; + } + char *key = c, + *value = nullptr; + + while (*c != ' ' && *c != '\0') { + if (*c == '=') { + *c = '\0'; + c++; + value = c; + } + + c++; + } + + map.put(key, value); + + if (*c == '\0' || map.is_full()) { + break; + } + + *c++ = '\0'; + } + } + protected: + key_value_map ↦ +}; + +class property { + public: + property() : new_(true) { + } + + const String &id() const { + return id_; + } + + const String &value() const { + return value_; + } + + const String &name() const { + return name_; + } + + const String &description() const { + return description_; + } + + bool dirty() const { + return dirty_; + } + + void init(const String &id) { + id_ = id; + dirty_ = false; + } + + bool is_new() const { + return new_; + } + + void set_old() { + new_ = false; + } + + private: + String id_; + boolean dirty_; + boolean new_; + + String value_; + String name_; + String description_; +}; + +template +class properties { + public: + properties() : size_(0) { + } + + property* operator[](uint8_t i) { + if (i >= size_) { + return nullptr; + } + + return &properties_[i]; + } + + property* find(const char *id) { + for (int i = 0; i < size_; i++) { + if (properties_[i].id() == id) { + return &properties_[i]; + } + } + + if (size_ < max_property_count) { + auto p = &properties_[size_]; + size_++; + p->init(id); + return p; + } + + return nullptr; + } + + uint8_t size() { + return size_; + } + + private: + property properties_[max_property_count]; + uint8_t size_; +}; + +} // namespace utils +} // namespace diller + diff --git a/src/diller_client.h b/src/diller_client.h deleted file mode 100644 index 988db98..0000000 --- a/src/diller_client.h +++ /dev/null @@ -1,209 +0,0 @@ -#pragma once - -#include -#include -#include "diller_utils.h" - -namespace diller { -namespace client { - -using namespace diller::utils; - -enum class diller_cmd : uint8_t { - UNKNOWN, - STATUS, - NETWORK, - WLAN, - PROPERTY, - PROPERTIES -}; - -class diller_event_listener { - public: - virtual void on_diller_event(const key_value_map ¶ms) = 0; -}; - -class client { - public: - client() : listener(nullptr) { - } - - virtual ~client() { - } - - virtual void property(const char *property, const char *value, const char *name) = 0; - virtual void property_value(const char *property, const char *value) = 0; - virtual void property_name(const char *property, const char *name) = 0; - virtual void property_description(const char *property, const char *description) = 0; - - void set_diller_event_listener(diller_event_listener *l) { - listener = l; - } - - protected: - diller_event_listener *listener; -}; - -template> -class client_software_serial : public client { - public: - client_software_serial() : client(), params(), parser(params) { - } - - void wlan(const char *ssid, const char *password) { - io_t::print("wlan ssid="); - escape(ssid); - io_t::print(" password="); - escape(password); - } - - void property(const char *property, const char *value, const char *name) { - io_t::print("property id="); - escape(property); - io_t::print(" value="); - escape(value); - io_t::print(" name="); - escape(name); - } - - void property_value(const char *property, const char *value) { - io_t::print("property id="); - escape(property); - io_t::print(" value="); - escape(value); - } - - void property_name(const char *property, const char *name) { - io_t::print("property id="); - escape(property); - io_t::print(" name="); - escape(name); - } - - void property_description(const char *property, const char *description) { - io_t::print("property id="); - escape(property); - io_t::print(" description="); - escape(description); - } - - void loop() { - auto x = tty.readline(); - // Serial.print("x="); - // Serial.println(static_cast(x)); - if (x == tty_status::FULL_LINE) { - parser.parse(tty.line); - - process_command(); - } - } - - protected: - void escape(const char *s) { - char c; - while ((c = *s++) != '\0') { - if (c == ' ') { - io_t::print('\\'); - } - io_t::print(c); - } - } - - void process_command() { - if (params.is_empty()) { - return; - } - - if (!listener) { - return; - } - - listener->on_diller_event(params); - params.clear(); - } - - private: - // SoftwareSerial serial; - key_value_map_t params; - diller::utils::diller_parser parser; - diller::utils::tty tty; -}; - -void print_diller_event(const key_value_map ¶ms) { - if (!params.size()) { - return; - } - - Serial.print("Diller: "); - Serial.print(params.key(0)); - Serial.print(' '); - - for (uint8_t i = 1; i < params.size(); i++) { - auto key = params.key(i); - auto value = params.value(i); - - Serial.print(key); - if (value) { - Serial.print("="); - Serial.print(value); - } - Serial.print(" "); - } - Serial.println(); -} - -class printing_diller_event_listener : public diller_event_listener { - public: - void on_diller_event(const key_value_map ¶ms) { - print_diller_event(params); - } -}; - -class noop_diller_event_listener : public diller_event_listener { - public: - void on_diller_event(const key_value_map ¶ms) { - auto key = params.key(0); - if (strcmp("network", key) == 0) { - on_network(params); - } else if (strcmp("status", key) == 0) { - on_status(params); - } else if (strcmp("property", key) == 0) { - on_property(params); - } else if (strcmp("properties", key) == 0) { - on_properties(params); - } else if (strcmp("debug", key) == 0) { - on_debug(params); - } else { - on_unknown(params); - } - } - - protected: - virtual void on_network(const key_value_map ¶ms) { - static_cast(params); - } - - virtual void on_status(const key_value_map ¶ms) { - static_cast(params); - } - - virtual void on_property(const key_value_map ¶ms) { - static_cast(params); - } - - virtual void on_properties(const key_value_map ¶ms) { - static_cast(params); - } - - virtual void on_unknown(const key_value_map ¶ms) { - static_cast(params); - } - - virtual void on_debug(const key_value_map ¶ms) { - static_cast(params); - } -}; - -} // namespace client -} // namespace diller - diff --git a/src/diller_core.h b/src/diller_core.h deleted file mode 100644 index a38779c..0000000 --- a/src/diller_core.h +++ /dev/null @@ -1,71 +0,0 @@ -#pragma once - -#include "diller_utils.h" -#include -#include - -namespace diller { -namespace core { - -using diller::utils::property; - -template -using props = diller::utils::properties; - -enum class diller_error : uint8_t { - OK, INVAL, NOMEM -}; - -static -const char * to_string(diller_error err) { - switch (err) { - case diller_error::OK: - return "ok"; - case diller_error::INVAL: - return "inval"; - case diller_error::NOMEM: - return "nomem"; - default: - return "unknown"; - } -} - -enum class property_action : uint8_t { - VALUE, - NAME, - DESCRIPTION, -}; - -class property_action_listener { - public: - virtual void on_property_action(const property *, property_action) = 0; -}; - -template -class core { - public: - core(const String &mqtt_host, int mqtt_port); - void set_property_action_listener(property_action_listener *); - - void setup(); - void loop(); - - bool connected() const; - - diller_error cmd_property(const char *id, const char *value, const char *name); - props properties; - - const String mqtt_host; - const int mqtt_port = 1883; - String mac; - String client_id; - - private: - void callback(char* topic_, byte* payload, unsigned int length); - property_action_listener *property_action_listener_; -}; - -} // namespace core -} // namespace diller - -#include "impl/diller_core_impl.h" diff --git a/src/diller_serial.h b/src/diller_serial.h deleted file mode 100644 index a9a2de4..0000000 --- a/src/diller_serial.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include "diller_utils.h" -#include -#include - -namespace diller { -namespace serial { - -using diller::utils::property; -using diller::core::diller_error; -using diller::core::property_action; - -template -class diller_serial : protected diller::core::property_action_listener { - public: - diller_serial(d_core &diller) : diller(diller), params(), diller_parser(params) { - } - - void setup(); - void loop(); - - private: - void process_command(); - void on_property_action(const property *, property_action); - - void cmd_network(); - void cmd_wlan(); - void cmd_wlan(const char* ssid, const char* password); - void cmd_property(const char *id, const char *value, const char *name); - void cmd_list_properties(); - void show_status(wl_status_t wl_status); - - d_core &diller; - diller::utils::fixed_size_key_value_map<10> params; - diller::utils::diller_parser diller_parser; - diller::utils::tty tty; - - static const bool send_wlan_password = false; -}; - -} // namespace serial -} // namespace diller - -#include "impl/diller_serial_impl.h" diff --git a/src/diller_utils.h b/src/diller_utils.h deleted file mode 100644 index 5b1224d..0000000 --- a/src/diller_utils.h +++ /dev/null @@ -1,348 +0,0 @@ -#pragma once - -#include - -namespace diller { -namespace utils { - -enum class tty_status : uint8_t { - NEED_MORE, - TIMEOUT, - EMPTY_LINE, - FULL_LINE, - LINE_OVERFLOW, -}; - -class serial_io { - public: - static auto available() -> decltype(Serial.available()) { - return Serial.available(); - } - - static auto read() -> decltype(Serial.read()) { - return Serial.read(); - } - - template - static auto print(A a) -> decltype(Serial.print(a)) { - return Serial.print(a); - } - - template - static auto println(A a) -> decltype(Serial.println(a)) { - return Serial.println(a); - } -}; - -template -class software_serial_io { - public: - static auto available() -> decltype(instance::software_serial.available()) { - return instance::software_serial.available(); - } - - static auto read() -> decltype(instance::software_serial.read()) { - return instance::software_serial.read(); - } - - template - static auto print(A a) -> decltype(instance::software_serial.print(a)) { - return instance::software_serial.print(a); - } - - template - static auto println(A a) -> decltype(instance::software_serial.println(a)) { - return instance::software_serial.println(a); - } -}; - -template -class tty { - public: - char line[buffer_size]; - uint8_t size = 0; - const bool debug = debug_; - const unsigned long timeout_ms = timeout_ms_; - - void init(bool reset = false) { - if (reset) { - line[0] = '\0'; - } - - size = 0; - last_char = 0; - } - - void quote(const String &s) { - Serial.print(s.c_str()); - } - - void quote(const char *str) { - Serial.print(str); - } - - void escape(const char *str) { - Serial.print(str); - } - - tty_status readline() { - while (io::available()) { - int c = io::read(); - - if (debug) { - Serial.print("read: c="); - Serial.println(c, HEX); - } - - if (c == '\n') { - // ignore - last_char = millis(); - } else if (c == '\r') { - line[size] = '\0'; - auto status = size > 0 ? tty_status::FULL_LINE : tty_status::EMPTY_LINE; - init(); - - if (debug) { - Serial.print("debug src=tty state=\"new line\" size="); - Serial.print(size, DEC); - Serial.print(" line="); - quote(line); - Serial.println(); - } - - return status; - } else { - if (size == buffer_size - 1) { - - if (debug) { - Serial.print("debug src=tty state=overflow size="); - Serial.print(size, DEC); - Serial.print(" line="); - quote(line); - Serial.println(); - } - init(); - - return tty_status::LINE_OVERFLOW; - } - line[size++] = static_cast(c); - last_char = millis(); - } - } - - unsigned long now = millis(); - - if (last_char > 0 && now >= (last_char + timeout_ms)) { - if (debug) { - line[size] = '\0'; - Serial.print("debug src=tty state=\"timeout\" line="); - quote(line); - Serial.println(); - } - - init(true); - return tty_status::TIMEOUT; - } - - return tty_status::NEED_MORE; - } - private: - unsigned long last_char = 0; -}; - -class key_value_map { - public: - virtual void clear(); - virtual void put(char *key, char *value) = 0; - virtual const char *key(uint8_t index) const = 0; - virtual const char *value(uint8_t index) const = 0; - virtual const char *find(const char *key) const = 0; - virtual uint8_t size() const = 0; - virtual bool is_empty() const = 0; - virtual bool is_full() const = 0; -}; - -template -class fixed_size_key_value_map : public key_value_map { - public: - uint8_t size_; - static_assert(max_args <= 256, "max_args is too big"); - char *keys[max_args]; - char *values[max_args]; - - void clear() { - size_ = 0; - } - - void put(char *key, char *value) { - if (size_ < max_args) { - keys[size_] = key; - values[size_] = value; - size_++; - } - } - - const char *key(uint8_t index) const { - return keys[index]; - } - - const char *value(uint8_t index) const { - return values[index]; - } - - const char *find(const char *key) const { - for (int i = 0; i < size_; i++) { - if (strcmp(keys[i], key) == 0) { - return values[i]; - } - } - - return nullptr; - } - - uint8_t size() const { - return size_; - } - - bool is_empty() const { - return size_ >= max_args; - } - - bool is_full() const { - return size_ >= max_args; - } -}; - -class diller_parser { - public: - diller_parser(key_value_map &map) : map(map) { - } - - void parse(char *line) { - char *c = line; - map.clear(); - - while (*c != '\0') { - while (*c == ' ') { - c++; - } - if (*c == '\0') { - break; - } - char *key = c, - *value = nullptr; - - while (*c != ' ' && *c != '\0') { - if (*c == '=') { - *c = '\0'; - c++; - value = c; - } - - c++; - } - - map.put(key, value); - - if (*c == '\0' || map.is_full()) { - break; - } - - *c++ = '\0'; - } - } - protected: - key_value_map ↦ -}; - -class property { - public: - property() : new_(true) { - } - - const String &id() const { - return id_; - } - - const String &value() const { - return value_; - } - - const String &name() const { - return name_; - } - - const String &description() const { - return description_; - } - - bool dirty() const { - return dirty_; - } - - void init(const String &id) { - id_ = id; - dirty_ = false; - } - - bool is_new() const { - return new_; - } - - void set_old() { - new_ = false; - } - - private: - String id_; - boolean dirty_; - boolean new_; - - String value_; - String name_; - String description_; -}; - -template -class properties { - public: - properties() : size_(0) { - } - - property* operator[](uint8_t i) { - if (i >= size_) { - return nullptr; - } - - return &properties_[i]; - } - - property* find(const char *id) { - for (int i = 0; i < size_; i++) { - if (properties_[i].id() == id) { - return &properties_[i]; - } - } - - if (size_ < max_property_count) { - auto p = &properties_[size_]; - size_++; - p->init(id); - return p; - } - - return nullptr; - } - - uint8_t size() { - return size_; - } - - private: - property properties_[max_property_count]; - uint8_t size_; -}; - -} // namespace utils -} // namespace diller - diff --git a/src/impl/diller_core_impl.h b/src/impl/diller_core_impl.h deleted file mode 100644 index 1246e59..0000000 --- a/src/impl/diller_core_impl.h +++ /dev/null @@ -1,150 +0,0 @@ -#include -#include -#include - -extern WiFiClient wifi_client; -extern PubSubClient mqtt_client; - -namespace diller { -namespace core { - -using diller::utils::tty_status; -using diller::utils::property; - -// core::core - -template -core::core(const String &mqtt_host, int mqtt_port) : - mqtt_host(mqtt_host), mqtt_port(mqtt_port), property_action_listener_(nullptr) { - mac = WiFi.macAddress(); - mac.toLowerCase(); - client_id = "diller-" + mac; -} - -template -void core::callback(char* topic_, byte* payload, unsigned int length) { - Serial.print("got message on "); - Serial.println(topic_); - Serial.println("payload"); - Serial.write(payload, length); - Serial.println(); - - String prefix = "/diller/" + mac + "/property/"; - String topic(topic_); - if (!topic.startsWith(prefix)) { - Serial.print("debug bad-prefix: "); - Serial.println(topic); - return; - } - - topic.remove(0, prefix.length()); - property *p = nullptr; - for (auto i = 0; i < properties.size(); i++) { - auto *x = properties[i]; - if (topic.startsWith(x->id())) { - p = x; - break; - } - } - - if (p) { - topic.remove(0, p->id().length() + 1); - - Serial.print("debug property="); - Serial.print(p->id()); - Serial.print(", topic="); - Serial.println(topic); - - property_action a; - - if (topic == "value") { - Serial.println("debug action=value"); - a = property_action::VALUE; - } else if (topic == "name") { - Serial.println("debug action=name"); - a = property_action::NAME; - } else if (topic == "description") { - Serial.println("debug action=description"); - a = property_action::DESCRIPTION; - } else { - return; - } - - if (property_action_listener_) { - property_action_listener_->on_property_action(p, a); - } - } else { - Serial.print("debug unknown-property"); - } -} - -template -bool core::connected() const { - return mqtt_client.connected(); -} - -template -void core::setup() { - mqtt_client.setServer(mqtt_host.c_str(), mqtt_port); - mqtt_client.setCallback([this](char* topic_, byte* payload, unsigned int length) { - callback(topic_, payload, length); - }); -} - -template -void core::loop() { - auto wl_status = WiFi.status(); - - if (wl_status == WL_CONNECTED) { - if (!mqtt_client.loop()) { - Serial.println("status mqtt=connecting"); - if (mqtt_client.connect(client_id.c_str())) { - Serial.println("status mqtt=connected"); - } else { - Serial.println("status mqtt=disconnected"); - } - } - } -} - -template -diller_error core::cmd_property(const char *id, const char *value, const char *name) { - if (!id) { - return diller_error::INVAL; - } - - auto p = properties.find(id); - - if (!p) { - return diller_error::NOMEM; - } - - if (value) { - String topic = "/diller/" + mac + "/property/" + id + "/value"; - mqtt_client.publish(topic.c_str(), value); - } - - if (name) { - String topic = "/diller/" + mac + "/property/" + id + "/name"; - mqtt_client.publish(topic.c_str(), name); - } - - if (p->is_new()) { - String topic = "/diller/" + mac + "/property/" + id + "/#"; - mqtt_client.subscribe(topic.c_str()); - Serial.print("debug subscribing="); - Serial.println(topic); - - p->set_old(); - } - - return diller_error::OK; -} - -template -void core::set_property_action_listener(property_action_listener *l) { - this->property_action_listener_ = l; -} - -} // namespace core -} // namespace diller diff --git a/src/impl/diller_serial_impl.h b/src/impl/diller_serial_impl.h deleted file mode 100644 index 47c18b5..0000000 --- a/src/impl/diller_serial_impl.h +++ /dev/null @@ -1,183 +0,0 @@ -// #include -#include -// #include - -extern WiFiClient wifi_client; -extern PubSubClient mqtt_client; - -namespace diller { -namespace serial { - -using diller::utils::tty_status; - -template -void diller_serial::cmd_network() { - Serial.print("ok ip="); - WiFi.localIP().printTo(Serial); - Serial.print(" gateway="); - WiFi.gatewayIP().printTo(Serial); - Serial.print(" netmask="); - WiFi.subnetMask().printTo(Serial); - Serial.println(); -} - -template -void diller_serial::cmd_wlan() { - Serial.print("ok ssid="); - tty.quote(WiFi.SSID()); - if (send_wlan_password) { - Serial.print(" password="); - Serial.print(WiFi.psk()); - } - - Serial.print(" mac="); - Serial.println(diller.mac); -} - -template -void diller_serial::cmd_wlan(const char* ssid, const char* password) { - WiFi.begin(ssid, password); - Serial.println("ok"); -} - -template -void diller_serial::cmd_property(const char *id, const char *value, const char *name) { - auto ret = diller.cmd_property(id, value, name); - - if (ret != diller_error::OK) { - Serial.print("fail error="); - Serial.println(to_string(ret)); - } -} - -template -void diller_serial::cmd_list_properties() { - Serial.print("ok count="); - Serial.println(diller.properties.size()); - for (auto i = 0; i < diller.properties.size(); i++) { - auto p = diller.properties[i]; - Serial.print("property id="); - Serial.print(p->id()); - Serial.print(" name="); - Serial.print(p->name()); - Serial.print(" description="); - Serial.print(p->description()); - Serial.println(); - } -} - -template -void diller_serial::show_status(wl_status_t wl_status) { - const char *wl_status_s; - if (wl_status == WL_IDLE_STATUS) { - wl_status_s = "idle"; - } else if (wl_status == WL_NO_SSID_AVAIL) { - wl_status_s = "no-ssid"; - } else if (wl_status == WL_SCAN_COMPLETED) { - wl_status_s = "scan-completed"; - } else if (wl_status == WL_CONNECTED) { - wl_status_s = "connected"; - } else if (wl_status == WL_CONNECT_FAILED) { - wl_status_s = "connect-failed"; - } else if (wl_status == WL_CONNECTION_LOST) { - wl_status_s = "connection-lost"; - } else if (wl_status == WL_DISCONNECTED) { - wl_status_s = "disconnected"; - } else { - wl_status_s = "unknown"; - } - - const char *mqtt_status_s; - if (diller.connected()) { - mqtt_status_s = "connected"; - } else { - mqtt_status_s = "disconnected"; - } - - Serial.print("status wlan="); - Serial.print(wl_status_s); - Serial.print(" mqtt="); - Serial.println(mqtt_status_s); -} - -template -void diller_serial::process_command() { - if (params.is_empty()) { - return; - } - - auto cmd = params.key(0); - - if (strcmp("network", cmd) == 0) { - if (params.size() == 1) { - cmd_network(); - } else { - Serial.println("fail error=invalid_argument"); - } - } else if (strcmp("wlan", cmd) == 0) { - if (params.size() == 1) { - cmd_wlan(); - } else if (params.size() == 3) { - const char* ssid = params.find("ssid"); - const char* password = params.find("password"); - cmd_wlan(ssid, password); - } else { - Serial.println("fail error=invalid_argument"); - } - } else if (strcmp("property", cmd) == 0) { - auto id = params.find("id"); - auto value = params.find("value"); - auto name = params.find("name"); - cmd_property(id, value, name); - } else if (strcmp("list-properties", cmd) == 0) { - cmd_list_properties(); - } else { - Serial.print("fail error=unknown_command cmd="); - tty.escape(cmd); - Serial.println(); - } -} - -template -void diller_serial::setup() { - diller.set_property_action_listener(this); -} - -template -void diller_serial::loop() { - if (tty.readline() == tty_status::FULL_LINE) { - diller_parser.parse(tty.line); - - process_command(); - } - - static auto last_wl_status = WiFi.status(); - auto wl_status = WiFi.status(); - - static auto last_status = millis(); - auto now = millis(); - static const auto show_status_interval = 5000; - - if (last_wl_status != wl_status) { - show_status(wl_status); - last_wl_status = wl_status; - last_status = now; - } else if (now > (last_status + show_status_interval)) { - show_status(wl_status); - last_status = now; - } -} - -template -void diller_serial::on_property_action(const property *p, property_action) { - Serial.print("property id="); - tty.quote(p->id()); - Serial.print(" value="); - tty.quote(p->value()); - Serial.print(" description="); - tty.quote(p->description()); - Serial.println(); -} - -} // namespace serial -} // namespace diller -- cgit v1.2.3