Skip to content

Commit

Permalink
Add new option: --print-topics-only
Browse files Browse the repository at this point in the history
  • Loading branch information
belyalov committed Feb 25, 2019
1 parent 059cf1c commit 285a7be
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 94 deletions.
106 changes: 63 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<prefix>/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+
Expand Down Expand Up @@ -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 `<prefix>/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.
43 changes: 2 additions & 41 deletions src/mqtt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,45 +123,6 @@ void mqtt_loop()
}
}


// Make string from OpenZwave value
pair<string, string>
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(),
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
56 changes: 56 additions & 0 deletions src/node_value.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <algorithm>
#include <openzwave/Manager.h>
#include "node_value.h"
#include "command_classes.h"

using namespace std;

Expand Down Expand Up @@ -121,3 +122,58 @@ value_escape_label(const string& lbl)

return res;
}

// Make string from OpenZwave value
pair<string, string>
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());
}
}

}
5 changes: 5 additions & 0 deletions src/node_value.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,9 @@ void value_remove(const OpenZWave::ValueID& v);

std::string value_escape_label(const std::string&);

std::pair<std::string, std::string>
value_make_paths(const std::string& prefix, const OpenZWave::ValueID& v);

void print_all_nodes();

#endif
9 changes: 8 additions & 1 deletion src/options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand All @@ -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)
{
}

Expand All @@ -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
Expand Down
4 changes: 3 additions & 1 deletion src/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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();
Expand Down
19 changes: 12 additions & 7 deletions src/ozw-mqtt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand All @@ -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());
}
Expand Down
9 changes: 8 additions & 1 deletion src/process_notification.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 285a7be

Please sign in to comment.