diff --git a/.travis.yml b/.travis.yml index 6c16a5b39..4332fbf65 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,10 +6,10 @@ os: dist: trusty perl: + - "5.30" - "5.24" - "5.18" - "5.16" - - "5.08" install: true diff --git a/README.md b/README.md index ab4698402..e1c000854 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ MisterHouse =========== -[![Build Status](https://travis-ci.org/hollie/misterhouse.svg?branch=master)](https://travis-ci.org/hollie/misterhouse) +[![Build Status](https://travis-ci.com/hollie/misterhouse.svg?branch=master)](https://travis-ci.com/hollie/misterhouse) Perl open source home automation program. It's fun, it's free, and it's entirely geeky. diff --git a/lib/Tasmota_HTTP_Item.pm b/lib/Tasmota_HTTP_Item.pm new file mode 100644 index 000000000..6bdee37e9 --- /dev/null +++ b/lib/Tasmota_HTTP_Item.pm @@ -0,0 +1,170 @@ + +=begin comment + +Tasmota_HTTP_Item.pm + +Basic Tasmota support using the HTTP interface rather than MQTT +Copyright (C) 2020 Jeff Siddall (jeff@siddall.name) +Last modified: 2020-12-15 + +This module currently supports Tasmota switch type devices but other devices +can be added with extra packages added + +Requirements: + + The Tasmota device needs to be setup with a rule to send HTTP requests to MH + if two-way communication is desired. For example, a Sonoff Mini switch input + can be sent to MH with the rule: + Rule1 ON Power1#State DO WebSend [192.168.0.1:80] /SET;none?select_item=Kitchen_Light&select_state=%value% ENDON + +Setup: + +In your code define Tasmota_HTTP::Things in an MHT: + + TASMOTA_HTTP_SWITCH, 192.168.x.y, Kitchen_Light, Kitchen + +Or in a code file: + + $Kitchen_Light = new Tasmota_HTTP::Switch("192.168.x.y"); + Where: + 192.168.x.y is the IPv4 address or hostname of the Tasmota device + + $Kitchen_Light->set(ON); + +=cut + +#======================================================================================= +# +# Generic Tasmota_HTTP::Item +# +#======================================================================================= + +# The Tasmota_HTTP::Item is a base item for other real devices (see below) + +package Tasmota_HTTP::Item; +use strict; +use parent 'Generic_Item'; + +# Item class constructor +sub new { + my ( $class, $address ) = @_; + + # Call the parent class constructor to make sure all the important things are done + my $self = new Generic_Item(); + bless $self, $class; + + # Additional Tasmota variables + $self->{address} = $address; + $self->{output_name} = 'POWER1'; + $self->{ack} = 0; + $self->{last_http_status}; + + return $self; +} + +# Use HTTP get calls to set the Tasmota item, being sure to check that the set did not come +# from the device itself +sub set { + my ( $self, $state, $set_by, $respond ) = @_; + + # Debug logging + my $debug = $self->{debug} || $main::Debug{tasmota}; + + # Determine whether the update came from the Tasmota device itself and convert states + # and record the set as an ack + if ( $set_by eq "web [$self->{address}]" ) { + + # Convert Tasmota states to MH states + $state = $self->{tasmota_to_state}{$state}; + + # If the current state is the same as the received state, and ack=0 then consider + # this set an ack and do not update the state of the item + if ( ( $state eq $self->{state} ) && ( $self->{ack} == 0 ) ) { + &main::print_log("[Tasmota_HTTP::Item] DEBUG: Received ack from $self->{object_name} ($self->{address})") if $debug; + $self->{ack} = 1; + } + else { + &main::print_log("[Tasmota_HTTP::Item] DEBUG: Received set state to $state from $self->{object_name} ($self->{address})") if $debug; + + # Call the parent class set to make sure all the important things are done + $self->SUPER::set( $state, $set_by, $respond ); + } + + # Only send an update to the device if the set did not come from the device to prevent + # set loops + } + else { + use LWP::UserAgent (); + + # Use a small timeout since devices are typically local and should respond quickly + # 5 seconds should allow for 3 syn attempts plus another second to get a response + my $ua = LWP::UserAgent->new( timeout => 5 ); + + # Reset the ack flag + $self->{ack} = 0; + + # Send the HTTP request + my $response = $ua->get("http://$self->{address}/cm?cmnd=$self->{output_name}%20$self->{state_to_tasmota}{$state}"); + + # Record the status of the last request + $self->{last_http_status} = $response->status_line; + + # Log request failures + if ( !$response->is_success ) { + &main::print_log("[Tasmota_HTTP::Item] ERROR: Received HTTP response code $self->{last_http_status} from last command)"); + } + + # Call the parent class set to make sure all the important things are done + $self->SUPER::set( $state, $set_by, $respond ); + &main::print_log("[Tasmota_HTTP::Item] DEBUG: Set $self->{object_name} state to $state") if $debug; + } +} + +#======================================================================================= +# +# Basic Tasmota_HTTP::Switch +# +#======================================================================================= + +# To add table support, add these lines to the read_table_A.pl file: +# elsif ( $type eq "TASMOTA_HTTP_SWITCH" ) { +# require Tasmota_HTTP_Item; +# ( $address, $name, $grouplist ) = @item_info; +# $object = "Tasmota_HTTP::Switch('$address')"; +# } + +package Tasmota_HTTP::Switch; +use strict; +use parent-norequire, 'Tasmota_HTTP::Item'; + +# Switch class constructor +sub new { + my $class = shift; + + # Call the parent class constructor to make sure all the important things are done + my $self = $class->SUPER::new(@_); + + # Additional switch variables + # Add additional hash pairs (rows) to this variable to send other states to devices + $self->{state_to_tasmota} = { + "off" => "0", + "on" => "1", + }; + + # Add additional hash pairs (rows) to this variable to use other states from devices + $self->{tasmota_to_state} = { + "0" => "off", + "1" => "on", + }; + + # Initialize states + push( @{ $self->{states} }, keys( %{ $self->{state_to_tasmota} } ) ); + + # Log the setup of the item + &main::print_log("[Tasmota_HTTP::Switch] Created item with address $self->{address}"); + + return $self; +} + +# Perl modules need to return true +1; diff --git a/lib/read_table_A.pl b/lib/read_table_A.pl index f903f887c..af039344e 100644 --- a/lib/read_table_A.pl +++ b/lib/read_table_A.pl @@ -1901,7 +1901,39 @@ sub read_table_A { $object = ''; } #-------------- End AoGSmartHome Objects ---------------- - + #-------------- MQTT Objects ----------------- + elsif ( $type eq "MQTT_BROKER" ) { + # there must be one record for the broker above any MQTT_DEVICE definitions + # it takes the following format + # MQTT_BROKER, name_of_broker + # e.g.MQTT_BROKER, mqtt_1 + require 'mqtt.pm'; + ( $name ) = @item_info; + $code .= sprintf( "\n\$%-35s = new mqtt(\"%s\", \$config_parms{mqtt_host}, + \$config_parms{mqtt_server_port}, + \$config_parms{mqtt_topic}, + \$config_parms{mqtt_username}, + \$config_parms{mqtt_password}, 121);\n", + $name, + $name + ); + } + elsif ( $type eq "MQTT_DEVICE" ) { + # there is one record per mqtt device and it must be below the MQTT_BROKER definition + # it takes the following form + # MQTT_DEVICE, name_of_device, groups, name_of_broker, topic + # e.g. MQTT_DEVICE, MQTT_test, Kitchen, mqtt_1, stat/mh_mqtt_test/SENSOR + # if the device is to transmit to MH, its topic must match the + # config parameter mqtt_topic in the mh.ini file + require 'mqtt.pm'; + my ($MQTT_broker_name, $MQTT_topic); + ( $name, $grouplist, $MQTT_broker_name, $MQTT_topic ) = @item_info; + + $code .= sprintf( "\n\$%-35s = new mqtt_Item(\$%s\,\"%s\");\n", + $name, $MQTT_broker_name, $MQTT_topic ); + + } + #-------------- End MQTT Objects ---------------- elsif ( $type =~ /PLCBUS_.*/ ) { #<,PLCBUS_Scene,Address,Name,Groups,Default|Scenes># require PLCBUS; @@ -1929,6 +1961,11 @@ sub read_table_A { $object = "Kasa_Item('$address', '$type', $index)"; } } + elsif ( $type eq "TASMOTA_HTTP_SWITCH" ) { + require Tasmota_HTTP_Item; + ( $address, $name, $grouplist ) = @item_info; + $object = "Tasmota_HTTP::Switch('$address')"; + } else { print "\nUnrecognized .mht entry: $record\n"; return; diff --git a/lib/xAP_Items.pm b/lib/xAP_Items.pm index 984a65760..aa35cd95c 100644 --- a/lib/xAP_Items.pm +++ b/lib/xAP_Items.pm @@ -636,6 +636,9 @@ sub _process_incoming_xap_data { if $o->allow_empty_state() or ( defined $state_value and $state_value ne '' ); } + } else { + print "db1 xap discarded, source is mh: s=$source d=$data\n" + if $main::Debug{xap} and $main::Debug{xap} == 1; } }