Skip to content

Commit

Permalink
bug fixes, cleaned up topic handling
Browse files Browse the repository at this point in the history
  • Loading branch information
DaveNeudoerffer committed Feb 23, 2021
1 parent df14d29 commit aea5880
Showing 1 changed file with 108 additions and 63 deletions.
171 changes: 108 additions & 63 deletions lib/mqtt_items.pm
Original file line number Diff line number Diff line change
Expand Up @@ -165,22 +165,24 @@ Usage:
# MQTT_BROKER, name, subscribe topic, host/ip, port, user, pwd, keepalive
MQTT_BROKER, mqtt_1, , localhost, 1883, , , 121
# MQTT_INSTMQTT, name, groups, broker, type, topicprefix, discoverable Friendly Name
MQTT_INSTMQTT, bootroom_switch, Lights, mqtt_1, switch, insteoncottage/bootroom, 1, Bootroom Light
# Used to define mqtt items as published by insteon-mqtt project
# TopicPattern should be of the form "<realm>/<mqtt name>/+".
# MQTT_INSTMQTT, name, groups, broker, type, topicpattern, discoverable Friendly Name
MQTT_INSTMQTT, bootroom_switch, Lights, mqtt_1, switch, insteon/bootroom/+, 1, Bootroom Light
# Define a Tasmota item. Note that the topicprefix must be in the order that the device will
# send. This is configured in the Tasmota MQTT configuration. The prefix listed can
# be any of stat/tele/cmnd.
# MQTT_REMOTEITEM, name, groups, broker, type, topicprefix, discoverable Friendly Name
MQTT_REMOTEITEM, tas_outdoor_plug, , mqtt_1, switch, tasmota_outdoor_plug/stat, 0, Tasmota Outdoor Plug
# Define a Tasmota item. Note that the topicpattern must be in the order that the device will
# send. This is configured in the Tasmota MQTT configuration.
# MQTT_REMOTEITEM, name, groups, broker, type, topicpattern, discoverable Friendly Name
MQTT_REMOTEITEM, tas_outdoor_plug, , mqtt_1, switch, tasmota_outdoor_plug/+/+, 0, Tasmota Outdoor Plug
# Say you have a local INSTEON item (could be any kind of misterhouse item)
INSTEON_SWITCHLINC, 52.9E.DD, shed_light, Lights|Outside
#
# then you can create an mqtt item to publish its state and receive mqtt commands
# MQTT_LOCALITEM, name, local item, broker, type, topicprefix, discoverable Friendly Name
MQTT_LOCALITEM, bootroom_switch, shed_light, mqtt_1, switch, insteoncottage/bootroom, 1, Bootroom Light
# Then you can create an mqtt item to publish its state and receive mqtt commands
# TopicPattern should be of the form "<realm>/<mqtt name>/+".
# MQTT_LOCALITEM, name, local item, broker, type, topicpattern, discoverable Friendly Name
MQTT_LOCALITEM, bootroom_switch, shed_light, mqtt_1, switch, insteon/bootroom/+, 1, Bootroom Light
#
Expand Down Expand Up @@ -488,13 +490,14 @@ sub decode_mqtt_payload {
if( $topic eq $self->{disc_info}->{command_topic} ) {
$value = $payload;
}
$self->debug( 3, "payload '$payload' decoded to: '$value'" );

if( $$self{mqtt_type} eq 'binary_sensor' || $$self{mqtt_type} eq 'sensor' ) {
if( $retained ) {
$self->debug( 1, "Retained message ignored for $$self{mqtt_type}:$$self{mqtt_name} device" );
return;
}
}
# if( $$self{mqtt_type} eq 'binary_sensor' || $$self{mqtt_type} eq 'sensor' ) {
# if( $retained ) {
# $self->debug( 1, "Retained message ignored for $$self{mqtt_type}:$$self{mqtt_name} device" );
# return;
# }
# }

if( $$self{mqtt_type} eq 'light' ) {
if( $self->{disc_info}->{schema} eq 'json' ) {
Expand Down Expand Up @@ -868,7 +871,7 @@ use Data::Dumper;
=cut

sub new { ### mqtt_LocalItem
my ( $class, $interface, $name, $type, $local_object, $topicprefix, $discoverable, $friendly_name ) = @_;
my ( $class, $interface, $name, $type, $local_object, $topicpattern, $discoverable, $friendly_name ) = @_;

my ($base_type, $device_class) = $type =~ m/^([^:]*):?(.*)$/;

Expand All @@ -882,8 +885,20 @@ sub new { ### mqtt_LocalItem
return;
}

my ($realm, $mqtt_name) = $topicprefix =~ m|^([^/]+)/([^/]+)$|;
my $listen_topic = "$topicprefix/+";
my (@topic_parts) = split( "/", $topicpattern );
my $realm = $topic_parts[0];
my $mqtt_name = $topic_parts[1];
my $topic_prefix = "$realm/$mqtt_name";
my $listen_topic;
if( $#topic_parts == 1 ) {
$listen_topic = "$topic_prefix/+";
} else {
$listen_topic = $topicpattern;
}
if( !$mqtt_name ) {
$interface->error( "Invalid topic pattern '$topicpattern' on object '$name'" );
return;
}

my $self = new mqtt_BaseItem( $interface, $mqtt_name, $base_type, $listen_topic, $discoverable );
return
Expand All @@ -892,29 +907,33 @@ sub new { ### mqtt_LocalItem
bless $self, $class;

$self->{realm} = $realm;
$self->debug( 1, "New mqtt_LocalItem( $interface->{instance}, '$mqtt_name', '$type', '$local_object', '$topicprefix', $discoverable, '$friendly_name' )" );
$self->debug( 1, "New mqtt_LocalItem( $interface->{instance}, '$mqtt_name', '$type', '$local_object', '$topicpattern', $discoverable, '$friendly_name' )" );

$self->{disc_info} = {};
if( !$friendly_name ) {
$friendly_name = $self->{mqtt_name};
$friendly_name =~ s/_/ /g;
}
$self->{disc_info}->{name} = $friendly_name;
$self->{disc_info}->{state_topic} = "$topicprefix/state";
$self->{disc_info}->{state_topic} = "$topic_prefix/state";
if( $base_type eq 'light' ) {
$self->{disc_info}->{command_topic} = "$topicprefix/level";
$self->{disc_info}->{command_topic} = "$topic_prefix/level";
$self->{disc_info}->{schema} = 'json';
$self->{disc_info}->{brightness} = "true";
$self->{disc_info}->{brightness_scale} = 100;
} elsif( $base_type eq 'switch' ) {
$self->{disc_info}->{command_topic} = "$topicprefix/set";
$self->{disc_info}->{command_topic} = "$topic_prefix/set";
} elsif( $base_type eq 'scene' ) {
$self->{disc_info}->{command_topic} = "$topicprefix/set";
$self->{disc_info}->{command_topic} = "$topic_prefix/set";
delete $self->{disc_info}->{state_topic};
} elsif( $base_type eq 'binary_sensor' ) {
$self->{disc_info}->{device_class} = $device_class;
if( $device_class ) {
$self->{disc_info}->{device_class} = $device_class;
}
} elsif( $base_type eq 'sensor' ) {
$self->{disc_info}->{device_class} = $device_class;
if( $device_class ) {
$self->{disc_info}->{device_class} = $device_class;
}
if( $device_class eq 'temperature' ) {
$self->{disc_info}->{unit_of_measurement} = 'C';
}
Expand Down Expand Up @@ -1287,43 +1306,55 @@ use Data::Dumper;
@mqtt_RemoteItem::ISA = ( 'mqtt_BaseRemoteItem' );


=item C<new(mqtt_interface, name, type, topicprefix, discoverable, friendly_name)>
=item C<new(mqtt_interface, name, type, topicpattern, discoverable, friendly_name)>
Creates a MQTT RemoteItem/object that will mirror the state of the object, and send commands to it.
=cut

sub make_topic {
my ( $prefixfirst, $prefix, $topic, $command ) = @_;
if( $prefixfirst ) {
return "$prefix/$topic/$command";
} else {
return "$topic/$prefix/$command";
my ( $topicpattern, @parms ) = @_;

my (@topic_parts) = split( "/", $topicpattern );
my $wildcard_count = 0;
for( my $i=0; $i <= $#topic_parts; $i += 1 ) {
my $part = $topic_parts[$i];
if( $part eq '+' ) {
$topic_parts[$i] = $parms[$wildcard_count];
$wildcard_count += 1;
}
}
return join( '/', @topic_parts );
}

sub new { ### mqtt_RemoteItem
my ( $class, $interface, $type, $topicprefix, $discoverable, $friendly_name ) = @_;
my ( $class, $interface, $type, $topicpattern, $discoverable, $friendly_name ) = @_;

my ($base_type, $device_class) = $type =~ m/^([^:]*):?(.*)$/;

if( !grep( /$base_type/, ('light','switch') ) ) {
if( !grep( /$base_type/, ('light','switch','sensor','binary_sensor') ) ) {
$interface->error( "Invalid InstMqttItem type '$type'" );
return;
}

my $prefixfirst = 1;
my ($prefix, $mqtt_name) = $topicprefix =~ m|^([^/]+)/([^/]+)$|;
$prefix = lc( $prefix );
if( $prefix && !grep( /$prefix/, ('tele', 'stat', 'cmnd') ) ) {
($mqtt_name, $prefix) = $topicprefix =~ m|^([^/]+)/([^/]+)$|;
$prefixfirst = 0;
my $mqtt_name;
my (@topic_parts) = split( "/", $topicpattern );
my $wildcard_count = 0;
for( my $i=0; $i <= $#topic_parts; $i += 1 ) {
my $part = $topic_parts[$i];
if( grep( /^$part$/, ('tele', 'stat', 'cmnd') ) ) {
$topic_parts[$i] = '+';
} elsif( $part eq '+' ) {
$wildcard_count += 1;
} else {
$mqtt_name = $part;
}
}
if( !$mqtt_name ) {
$interface->error( "Unrecognized topic prefix '$topicprefix'" );
$interface->error( "Unrecognized topic pattern '$topicpattern' for device '$friendly_name'" );
}

my $listen_topic = make_topic( $prefixfirst, '+', $mqtt_name, '+' );
my $listen_topic = join( '/', @topic_parts );

my $self = new mqtt_BaseRemoteItem( $interface, $mqtt_name, $base_type, $listen_topic, $discoverable );

Expand All @@ -1332,7 +1363,7 @@ sub new { ### mqtt_RemoteItem

bless $self, $class;

$self->debug( 1, "New mqtt_RemoteItem( $interface->{instance}, '$mqtt_name', '$type', '$topicprefix', $discoverable, '$friendly_name' )" );
$self->debug( 1, "New mqtt_RemoteItem( $interface->{instance}, '$mqtt_name', '$type', '$topicpattern', $discoverable, '$friendly_name' )" );

$self->{discovered} = 0;

Expand All @@ -1342,30 +1373,38 @@ sub new { ### mqtt_RemoteItem
$friendly_name =~ s/_/ /g;
}
$self->{disc_info}->{name} = $friendly_name;
$self->{disc_info}->{availability_topic} = make_topic( $prefixfirst, 'tele', $mqtt_name, 'LWT' );
$self->{disc_info}->{payload_available} = 'Online';
$self->{disc_info}->{payload_not_available} = 'Offline';
if( $wildcard_count == 2 ) {
$self->{disc_info}->{availability_topic} = make_topic( $listen_topic, 'tele', 'LWT' );
$self->{disc_info}->{payload_available} = 'Online';
$self->{disc_info}->{payload_not_available} = 'Offline';
}
if( $base_type eq 'switch' ) {
$self->{disc_info}->{command_topic} = make_topic( $prefixfirst, 'cmnd', $mqtt_name, 'POWER' );
$self->{disc_info}->{state_topic} = make_topic( $prefixfirst, 'stat', $mqtt_name, 'POWER' );
if( $wildcard_count != 2 ) {
$self->error( "Don't know how to create switch topics for '$friendly_name'" );
}
$self->{disc_info}->{command_topic} = make_topic( $listen_topic, 'cmnd', 'POWER' );
$self->{disc_info}->{state_topic} = make_topic( $listen_topic, 'stat', 'POWER' );
} elsif( $base_type eq 'light' ) {
$self->{disc_info}->{command_topic} = make_topic( $prefixfirst, 'cmnd', $mqtt_name, 'POWER' );
$self->{disc_info}->{state_topic} = make_topic( $prefixfirst, 'tele', $mqtt_name, 'STATE' );
if( $wildcard_count != 2 ) {
$self->error( "Don't know how to create light topics for '$friendly_name'" );
}
$self->{disc_info}->{command_topic} = make_topic( $listen_topic, 'cmnd', 'POWER' );
$self->{disc_info}->{state_topic} = make_topic( $listen_topic, 'tele', 'STATE' );
$self->{disc_info}->{state_value_template} = '{{value_json.POWER}}';
$self->{disc_info}->{brightness_state_topic} = make_topic( $prefixfirst, 'tele', $mqtt_name, 'STATE' );
$self->{disc_info}->{brightness_state_topic} = make_topic( $listen_topic, 'tele', 'STATE' );
$self->{disc_info}->{brightness_value_template} = '{{value_json.Dimmer}}';
$self->{disc_info}->{brightness_scale} = 100;
$self->{disc_info}->{brightness_command_topic} = make_topic( $prefixfirst, 'cmnd', $mqtt_name, 'Dimmer' );
$self->{disc_info}->{brightness_command_topic} = make_topic( $listen_topic, 'cmnd', 'Dimmer' );
$self->{disc_info}->{on_command_type} = 'brightness';
} elsif( $base_type eq 'binary_sensor' ) {
# Motion sensor config as defined here: https://blakadder.com/pir-in-tasmota/
$self->{disc_info}->{state_topic} = make_topic( $prefixfirst, 'tele', $mqtt_name, 'MOTION' );
$self->{disc_info}->{state_topic} = make_topic( $listen_topic, 'tele', 'MOTION' );
$self->{disc_info}->{payload_on} = 1;
$self->{disc_info}->{device_class} = $device_class;
$self->{disc_info}->{force_update} = 'true';
$self->{disc_info}->{off_delay} = 30;
} elsif( $base_type eq 'sensor' ) {
$self->{disc_info}->{state_topic} = make_topic( $prefixfirst, 'tele', $mqtt_name, 'STATE' );
$self->{disc_info}->{state_topic} = make_topic( $listen_topic, 'tele', 'STATE' );
$self->{disc_info}->{device_class} = $device_class;
$self->{disc_info}->{force_update} = 'true';
} else {
Expand All @@ -1384,7 +1423,7 @@ sub new { ### mqtt_RemoteItem
return $self;
}

# -[ Fini - mqtt_TasmotaItem ]---------------------------------------------------------
# -[ Fini - mqtt_RemoteItem ]---------------------------------------------------------

# ------------------------------------------------------------------------------

Expand All @@ -1397,14 +1436,14 @@ use Data::Dumper;
@mqtt_InstMqttItem::ISA = ( 'mqtt_BaseRemoteItem' );


=item C<new(mqtt_interface, name, type, topicprefix, discoverable, friendly_name)>
=item C<new(mqtt_interface, name, type, topicpattern, discoverable, friendly_name)>
Creates a MQTT BaseRemoteItem/object that will mirror the state of the object, and send commands to it.
=cut

sub new { ### mqtt_InstMqttItem
my ( $class, $interface, $type, $topicprefix, $discoverable, $friendly_name ) = @_;
my ( $class, $interface, $type, $topicpattern, $discoverable, $friendly_name ) = @_;

my ($base_type, $device_class) = $type =~ m/^([^:]*):?(.*)$/;

Expand All @@ -1413,8 +1452,14 @@ sub new { ### mqtt_InstMqttItem
return;
}

my ($realm, $mqtt_name) = $topicprefix =~ m|^([^/]+)/([^/]+)$|;
my $listen_topic = "$topicprefix/+";
my (@topic_parts) = split( "/", $topicpattern );
my $realm = $topic_parts[0];
my $mqtt_name = $topic_parts[1];
my $topic_prefix = "$realm/$mqtt_name";
if( !$mqtt_name ) {
$interface->error( "Unrecognized topic pattern '$topicpattern' for device '$friendly_name'" );
}
my $listen_topic = "$topic_prefix/+";

my $self = new mqtt_BaseRemoteItem( $interface, $mqtt_name, $base_type, $listen_topic, $discoverable );

Expand All @@ -1423,7 +1468,7 @@ sub new { ### mqtt_InstMqttItem

bless $self, $class;

$self->debug( 1, "New mqtt_InstMqttItem( $interface->{instance}, '$mqtt_name', '$type', '$topicprefix', $discoverable, '$friendly_name' )" );
$self->debug( 1, "New mqtt_InstMqttItem( $interface->{instance}, '$mqtt_name', '$type', '$topicpattern', $discoverable, '$friendly_name' )" );

$self->{realm} = $realm;
$self->{discovered} = 0;
Expand All @@ -1441,10 +1486,10 @@ sub new { ### mqtt_InstMqttItem
$self->{disc_info}->{payload_on} = "{ \"cmd\" : \"ON\", \"name\" : \"$mqtt_name\" }";
$self->{disc_info}->{payload_off} = "{ \"cmd\" : \"OFF\", \"name\" : \"$mqtt_name\" }";
} else {
$self->{disc_info}->{state_topic} = "$topicprefix/state";
$self->{disc_info}->{command_topic} = "$topicprefix/set";
$self->{disc_info}->{state_topic} = "$topic_prefix/state";
$self->{disc_info}->{command_topic} = "$topic_prefix/set";
if( $base_type eq 'light' ) {
$self->{disc_info}->{command_topic} = "$topicprefix/level";
$self->{disc_info}->{command_topic} = "$topic_prefix/level";
$self->{disc_info}->{schema} = 'json';
$self->{disc_info}->{brightness} = "true";
} elsif( $base_type eq 'binary_sensor' ) {
Expand Down

0 comments on commit aea5880

Please sign in to comment.