From 285a7be379d1d133f54f2dafbc36727050e91883 Mon Sep 17 00:00:00 2001 From: Konstantin Belyalov Date: Sun, 24 Feb 2019 16:16:37 -0800 Subject: [PATCH] Add new option: --print-topics-only --- README.md | 106 +++++++++++++++++++++-------------- src/mqtt.cpp | 43 +------------- src/node_value.cpp | 56 ++++++++++++++++++ src/node_value.h | 5 ++ src/options.cpp | 9 ++- src/options.h | 4 +- src/ozw-mqtt.cpp | 19 ++++--- src/process_notification.cpp | 9 ++- 8 files changed, 157 insertions(+), 94 deletions(-) diff --git a/README.md b/README.md index 78601c3..5bd1df7 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,69 @@ Connecting to MQTT Broker test.mosquitto.org:1883... 2018-01-21 20:58:21.939 Info, Trying to open serial port /dev/zzz (attempt 1) ``` +## Discover MQTT topics +Ok, now you know how to run `ozw-mqtt`, and so you may ask? How to I know where ZWave device will report to? +This is where `--print-topics-only` comes into picture, simply run `ozw-mqtt` with this option and you'll get something like this: +``` +Print MQTT topics only mode selected, gathering nodes information. +This will take awhile (up to few minutes, depending on amount of nodes)... +2019-02-24 16:03:00.758 Always, OpenZwave Version 1.4.3311 Starting Up + +(R/W) home/controller/basic/basic (1/32/0) +(R/W) home/living/window1/sensor/basic/basic (3/32/0) +(R) home/living/window1/sensor/sensor_binary/sensor (3/48/0) +(R) home/living/window1/sensor/alarm/access_control (3/113/9) +(R) home/living/window1/sensor/alarm/burglar (3/113/10) +(R) home/living/window1/sensor/alarm/power_management (3/113/11) +(R/W) home/living/window1/sensor/powerlevel/powerlevel (3/115/0) +(R/W) home/living/window1/sensor/powerlevel/timeout (3/115/1) +..... +``` + +## Topic Map +By default application will publish **all** topics from your OpenZWave configuration. However, sometimes you may want to filter some topics our / rename them. +That could be done by creating simple text file with mappings, like: +``` +# Comments start with "#" + +# Master bedroom +home/master/lights/alarm/burglar = home/master/motion +home/master/lights/switch_multilevel/1/level = home/master/lights +home/master/wall_lights/switch_multilevel/1/level = home/master/wall_lights +home/master/window/sensor/battery/battery_level = home/master/window/battery +home/master/window/sensor/sensor_binary/sensor = home/master/window/state + +# ^ empty lines are ignored. Spaces between topics are ignored too + +# You can just specify topic, without mapping, like: +home/living/window1/sensor/battery/battery_level + +``` +Then simply run: +```bash +$ ./ozw-mqtt --topic-filter-file mytopiclist.txt +``` +... And you'll see only topics defined in map! :-) + +## Options +#### Mandatory parameters +* `-d [--device]` - ZWave Device location (defaults to `/dev/zwave`) +* `-h [--mqtt-host]` - Address of MQTT Broker. (defaults to `localhost`) + +#### Optional parameters +* `-c [--config]` - *User* configuration file for OpenZWave library (defaults to current directory - `./`) +* `-u [--mqtt-user]` - MQTT Username (defaults to empty) +* `-p [--mqtt-passwd]` - MQTT Password (defaults to empty) +* `--mqtt-port` - MQTT Broker port (defaults to `1883`) +* `--mqtt-client-id` - Set MQTT Client-ID (defaults to `ozw-mqtt`) +* `--mqtt-prefix` - Add prefix to all ZWave subscriptions / publications topics. E.g. `living/motion/sensor_binary/sensor` will be prefixed to `/living/motion/sensor_binary/sensor`. Defaults to no prefix. +* `--system-config` - OpenZWave library system configuration dir (defaults to `/usr/local/etc/openzwave`) +* `--log-level` - Set OpenZWave library log level (can be `error`, `warning`, `info`, `debug`). Defaults to `info`. +* `--mqtt-no-name-topics` - Disables subscribe / publish to name-based topics (like `home/room/light`) +* `--mqtt-no-id-topics` - Disables subscribe / publish on id-based topics (like `1/2/33`). +* `--topic-filter-file` - Specifies file contains topic map/filter. +* `--print-topics-only` - Run OpenZwave, discover all nodes, then print all MQTT topics and exit. + ## Build ### Dependencies * CMake 2.8+ @@ -78,46 +141,3 @@ Now all your ZWave messages are replicated into MQTT network, e.g. let's say you ... ``` Whenever sensor detects movement a MQTT message will be sent to topic `living/motion/sensor_binary/sensor` with `True` or `False` string payload. - -## Topic Map -By default application will publish **all** topics from your OpenZWave configuration. However, sometimes you may want to filter some topics our / rename them. -That could be done by creating simple text file with mappings, like: -``` -# Comments start with "#" - -# Master bedroom -home/master/lights/alarm/burglar = home/master/motion -home/master/lights/switch_multilevel/1/level = home/master/lights -home/master/wall_lights/switch_multilevel/1/level = home/master/wall_lights -home/master/window/sensor/battery/battery_level = home/master/window/battery -home/master/window/sensor/sensor_binary/sensor = home/master/window/state - -# ^ empty lines are ignored. Spaces between topics are ignored too - -# You can just specify topic, without mapping, like: -home/living/window1/sensor/battery/battery_level - -``` -Then simply run: -```bash -$ ./ozw-mqtt --topic-filter-file mytopiclist.txt -``` -... And you'll see only topics defined in map! :-) - -## Options -#### Mandatory parameters -* `-d [--device]` - ZWave Device location (defaults to `/dev/zwave`) -* `-h [--mqtt-host]` - Address of MQTT Broker. (defaults to `localhost`) - -#### Optional parameters -* `-c [--config]` - *User* configuration file for OpenZWave library (defaults to current directory - `./`) -* `-u [--mqtt-user]` - MQTT Username (defaults to empty) -* `-p [--mqtt-passwd]` - MQTT Password (defaults to empty) -* `--mqtt-port` - MQTT Broker port (defaults to `1883`) -* `--mqtt-client-id` - Set MQTT Client-ID (defaults to `ozw-mqtt`) -* `--mqtt-prefix` - Add prefix to all ZWave subscriptions / publications topics. E.g. `living/motion/sensor_binary/sensor` will be prefixed to `/living/motion/sensor_binary/sensor`. Defaults to no prefix. -* `--system-config` - OpenZWave library system configuration dir (defaults to `/usr/local/etc/openzwave`) -* `--log-level` - Set OpenZWave library log level (can be `error`, `warning`, `info`, `debug`). Defaults to `info`. -* `--mqtt-no-name-topics` - Disables subscribe / publish to name-based topics (like `home/room/light`) -* `--mqtt-no-id-topics` - Disables subscribe / publish on id-based topics (like `1/2/33`). -* `--topic-filter-file` - Specifies file contains topic map/filter. diff --git a/src/mqtt.cpp b/src/mqtt.cpp index 908c639..9c89677 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -123,45 +123,6 @@ void mqtt_loop() } } - -// Make string from OpenZwave value -pair -make_value_path(const string& prefix, const OpenZWave::ValueID& v) -{ - auto n = node_find_by_id(v.GetNodeId()); - if (!n) { - throw invalid_argument("Node not found"); - } - - uint8_t cmd_class = v.GetCommandClassId(); - - // prefix/node_location/node_name/command_class_name - // prefix/node_id/command_class_id - string name_path; - string id_path; - - if (!prefix.empty()) { - name_path += prefix + "/"; - id_path += prefix + "/"; - } - if (!n->location.empty()) { - name_path += n->location + "/"; - } - name_path += n->name + "/" + command_class_str(cmd_class); - id_path += to_string(n->id) + "/" + to_string(cmd_class); - - // Several command types support multi instances (e.g. 2-relay binary switch), so, add instance as well - // Today we've found only 2 types that support it: SWITCH_MULTILEVEL and SWITCH_BINARY - if (cmd_class == 0x25 || cmd_class == 0x26) { - name_path += "/" + to_string(v.GetInstance()); - id_path += "/" + to_string(v.GetInstance()); - } - name_path += "/" + value_escape_label(OpenZWave::Manager::Get()->GetValueLabel(v)); - id_path += "/" + to_string(v.GetIndex()); - - return make_pair(name_path, id_path); -} - void publish_impl(const string& topic, const string& value) { int res = mosquitto_publish(mqtt_client, NULL, topic.c_str(), @@ -214,7 +175,7 @@ mqtt_publish(const options* opts, const OpenZWave::ValueID& v) // Make 2 topic variations: // 1. Name based // 2. ID based - auto topics = make_value_path(opts->mqtt_prefix, v); + auto topics = value_make_paths(opts->mqtt_prefix, v); // If name/id topic found in the filter list - publish // only to overridden destination @@ -258,7 +219,7 @@ mqtt_subscribe(const options* opts, const OpenZWave::ValueID& v) } // Make string representation of changeable parameter - auto topics = make_value_path(opts->mqtt_prefix, v); + auto topics = value_make_paths(opts->mqtt_prefix, v); // If name/id topic found in the filter list - publish // only to overridden destination diff --git a/src/node_value.cpp b/src/node_value.cpp index 8018727..2502c2f 100644 --- a/src/node_value.cpp +++ b/src/node_value.cpp @@ -2,6 +2,7 @@ #include #include #include "node_value.h" +#include "command_classes.h" using namespace std; @@ -121,3 +122,58 @@ value_escape_label(const string& lbl) return res; } + +// Make string from OpenZwave value +pair +value_make_paths(const string& prefix, const OpenZWave::ValueID& v) +{ + auto n = node_find_by_id(v.GetNodeId()); + if (!n) { + throw invalid_argument("Node not found"); + } + + uint8_t cmd_class = v.GetCommandClassId(); + + // prefix/node_location/node_name/command_class_name + // prefix/node_id/command_class_id + string name_path; + string id_path; + + if (!prefix.empty()) { + name_path += prefix + "/"; + id_path += prefix + "/"; + } + if (!n->location.empty()) { + name_path += n->location + "/"; + } + name_path += n->name + "/" + command_class_str(cmd_class); + id_path += to_string(n->id) + "/" + to_string(cmd_class); + + // Several command types support multi instances (e.g. 2-relay binary switch), so, add instance as well + // Today we've found only 2 types that support it: SWITCH_MULTILEVEL and SWITCH_BINARY + if (cmd_class == 0x25 || cmd_class == 0x26) { + name_path += "/" + to_string(v.GetInstance()); + id_path += "/" + to_string(v.GetInstance()); + } + name_path += "/" + value_escape_label(OpenZWave::Manager::Get()->GetValueLabel(v)); + id_path += "/" + to_string(v.GetIndex()); + + return make_pair(name_path, id_path); +} + +void +print_all_nodes() +{ + printf("\n"); + for (auto n = nodes_by_id.begin(); n != nodes_by_id.end(); ++n) { + for (auto v = n->second->values.begin(); v != n->second->values.end(); v++) { + string pref = "R"; + if (!OpenZWave::Manager::Get()->IsValueReadOnly(*v)) { + pref += "/W"; + } + auto names = value_make_paths("", *v); + printf("(%s) %s (%s)\n", pref.c_str(), names.first.c_str(), names.second.c_str()); + } + } + +} \ No newline at end of file diff --git a/src/node_value.h b/src/node_value.h index 07750e2..fa34945 100644 --- a/src/node_value.h +++ b/src/node_value.h @@ -34,4 +34,9 @@ void value_remove(const OpenZWave::ValueID& v); std::string value_escape_label(const std::string&); +std::pair + value_make_paths(const std::string& prefix, const OpenZWave::ValueID& v); + +void print_all_nodes(); + #endif diff --git a/src/options.cpp b/src/options.cpp index 057460e..27f008d 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -30,6 +30,7 @@ void print_help() printf("\t --system-config\t OpenZWave library system config dir (default /usr/local/etc/openzwave)\n"); printf("\t --topic-filter-file\t Publish only to topics from file separated by new line\n"); printf("\t --log-level\t\t Set log level (error, warning, info, debug) (default info)\n"); + printf("\t --print-topics-only\t Only print all available MQTT topics, then exit\n"); printf("\t --help\t\t\t Print this message\n"); printf("\n"); } @@ -43,7 +44,8 @@ options::options(): mqtt_port(1883), mqtt_name_topics(true), mqtt_id_topics(true), - log_level(OpenZWave::LogLevel_Info) + log_level(OpenZWave::LogLevel_Info), + print_topics_only(false) { } @@ -65,6 +67,11 @@ options::parse_argv(int argc, const char* argv[]) } else if (k == "--mqtt-no-id-topics") { mqtt_id_topics = false; continue; + } else if (k == "--print-topics-only") { + print_topics_only = true; + // We don't want noise for this mode + log_level = OpenZWave::LogLevel_Fatal; + continue; } // Next parameters requires value diff --git a/src/options.h b/src/options.h index b66c7e2..d71bc09 100644 --- a/src/options.h +++ b/src/options.h @@ -7,7 +7,7 @@ class param_error: public std::exception { std::string msg; public: - const char * what () const throw () { + virtual const char* what() const noexcept { return msg.c_str(); } param_error(const std::string& _msg, const std::string& _param) { @@ -41,6 +41,8 @@ struct options { bool mqtt_id_topics; // Log level uint32_t log_level; + // Print topics only mode + bool print_topics_only; }; void print_help(); diff --git a/src/ozw-mqtt.cpp b/src/ozw-mqtt.cpp index 81eae81..5ede3d0 100644 --- a/src/ozw-mqtt.cpp +++ b/src/ozw-mqtt.cpp @@ -44,7 +44,7 @@ int main(int argc, const char* argv[]) // Parse topics filter list, if desired if (!opt.topics_file.empty()) { opt.parse_topics_file(); - printf("Loaded %lu topics to publish to.\n", opt.topic_overrides.size()); + printf("Loaded %u topics to publish to.\n", opt.topic_overrides.size()); } // Setup signal handling @@ -55,14 +55,20 @@ int main(int argc, const char* argv[]) sigaction(SIGINT, &sigIntHandler, NULL); // Make a connection to MQTT broker - printf("Connecting to MQTT Broker %s:%d...", opt.mqtt_host.c_str(), opt.mqtt_port); - mqtt_connect(opt.mqtt_client_id, opt.mqtt_host, opt.mqtt_port, opt.mqtt_user, opt.mqtt_passwd); + if (!opt.print_topics_only) { + printf("Connecting to MQTT Broker %s:%d...", opt.mqtt_host.c_str(), opt.mqtt_port); + mqtt_connect(opt.mqtt_client_id, opt.mqtt_host, opt.mqtt_port, opt.mqtt_user, opt.mqtt_passwd); + // Register save config mqtt topic + mqtt_subscribe(opt.mqtt_prefix, "ozw/save_config", save_config); + } else { + printf("Print MQTT topics only mode selected, gathering nodes information.\n"); + printf("This will take awhile (up to few minutes, depending on amount of nodes)...\n"); + } // Create the OpenZWave Manager. // The first argument is the path to the config files (where the manufacturer_specific.xml file is located // The second argument is the path for saved Z-Wave network state and the log file. If you leave it NULL // the log file will appear in the program's working directory. - printf("Starting OpenZWave...\n"); OpenZWave::Options::Create(opt.system_config, opt.user_config, ""); OpenZWave::Options::Get()->AddOptionInt("SaveLogLevel", opt.log_level); OpenZWave::Options::Get()->AddOptionInt("QueueLogLevel", opt.log_level); @@ -79,11 +85,10 @@ int main(int argc, const char* argv[]) // NOTE: only devices explicitly enabled for polling will be polled. OpenZWave::Manager::Get()->SetPollInterval(500, true); - // Register save config mqtt topic - mqtt_subscribe(opt.mqtt_prefix, "ozw/save_config", save_config); - // Main loop mqtt_loop(); + } catch (param_error e) { + printf("Param error: %s\n", e.what()); } catch (exception e) { printf("Error: %s\n", e.what()); } diff --git a/src/process_notification.cpp b/src/process_notification.cpp index 49d0966..6de11d5 100644 --- a/src/process_notification.cpp +++ b/src/process_notification.cpp @@ -46,7 +46,9 @@ process_notification(const Notification* n, void* ctx) case Notification::Type_ValueAdded: { value_add(n->GetValueID()); - mqtt_subscribe(opts, n->GetValueID()); + if (!opts->print_topics_only) { + mqtt_subscribe(opts, n->GetValueID()); + } break; } @@ -80,6 +82,11 @@ process_notification(const Notification* n, void* ctx) case Notification::Type_NodeQueriesComplete: { + // Print all MQTT topics and exit, when --print-topics-only + if (opts->print_topics_only) { + print_all_nodes(); + exit(1); + } home_id = hid; publishing = true; break;