From 1effc988e95a7c39ed673bbcc840ff20cec4bb75 Mon Sep 17 00:00:00 2001 From: Trygve Laugstøl Date: Sun, 13 Mar 2016 11:10:51 +0100 Subject: o Initial import of Diller Arduino library. --- 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 ++++++++++++++++++++++ 6 files changed, 1006 insertions(+) create mode 100644 src/diller_client.h create mode 100644 src/diller_core.h create mode 100644 src/diller_serial.h create mode 100644 src/diller_utils.h create mode 100644 src/impl/diller_core_impl.h create mode 100644 src/impl/diller_serial_impl.h (limited to 'src') diff --git a/src/diller_client.h b/src/diller_client.h new file mode 100644 index 0000000..988db98 --- /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..a38779c --- /dev/null +++ b/src/diller_core.h @@ -0,0 +1,71 @@ +#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 new file mode 100644 index 0000000..a9a2de4 --- /dev/null +++ b/src/diller_serial.h @@ -0,0 +1,45 @@ +#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 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/impl/diller_core_impl.h b/src/impl/diller_core_impl.h new file mode 100644 index 0000000..1246e59 --- /dev/null +++ b/src/impl/diller_core_impl.h @@ -0,0 +1,150 @@ +#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 new file mode 100644 index 0000000..47c18b5 --- /dev/null +++ b/src/impl/diller_serial_impl.h @@ -0,0 +1,183 @@ +// #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