App-OpenHAP
view release on metacpan or search on metacpan
lib/App/OpenHAP/Tasmota/Thermostat.pm view on Meta::CPAN
use v5.36;
package App::OpenHAP::Tasmota::Thermostat;
our $VERSION = '0.1.0';
use Fugu::Log;
require App::OpenHAP::Tasmota::Device;
our @ISA = qw(App::OpenHAP::Tasmota::Device);
use Protocol::HAP::Service;
use Protocol::HAP::Characteristic;
sub new ( $class, %args )
{
my $self = $class->SUPER::new(
%args,
model => 'Tasmota Thermostat',
manufacturer => 'OpenHAP',
serial => $args{serial} // 'TSTAT-001',
);
# Sensor configuration (H5)
$self->{sensor_type} = $args{sensor_type}; # undef = auto-detect
$self->{sensor_index} = $args{sensor_index}; # For indexed sensors
# Current state
$self->{current_temp} = 20.0;
$self->{target_temp} = 20.0;
$self->{heating_state} = 0; # 0=Off, 1=Heat, 2=Cool
$self->{target_heating_state} = 0;
# Add the Thermostat service. One row per characteristic:
# type, iid, format, perms, and the extra arguments.
my $thermostat = Protocol::HAP::Service->new(
logger => $self->{logger},
type => 'Thermostat',
iid => 10,
);
my @rows = ( [
'CurrentHeatingCoolingState', 11,
'uint8', [ 'pr', 'ev' ],
{ value => \$self->{heating_state} },
],
[
'TargetHeatingCoolingState',
12, 'uint8',
[ 'pr', 'pw', 'ev' ],
{
value => \$self->{target_heating_state},
on_set => sub { $self->_set_target_state(@_) },
},
],
[
'CurrentTemperature',
13, 'float',
[ 'pr', 'ev' ],
{
unit => 'celsius',
value => \$self->{current_temp},
min => -40,
max => 100,
},
],
[
'TargetTemperature',
14, 'float',
[ 'pr', 'pw', 'ev' ],
{
unit => 'celsius',
value => \$self->{target_temp},
min => 10,
max => 38,
step => 0.5,
on_set => sub { $self->_set_target_temp(@_) },
},
],
[
'TemperatureDisplayUnits', 15, 'uint8',
[ 'pr', 'pw', 'ev' ],
{ value => 0 }, # 0=Celsius, 1=Fahrenheit
],
);
for my $row (@rows) {
my ( $type, $iid, $format, $perms, $extra ) = @$row;
$thermostat->add_characteristic(
Protocol::HAP::Characteristic->new(
logger => $self->{logger},
type => $type,
iid => $iid,
format => $format,
perms => $perms,
%$extra,
) );
}
$self->add_service($thermostat);
return $self;
}
sub subscribe_mqtt ($self)
{
# Call the base class to set up the standard subscriptions
# (C1, C2, C3)
$self->SUPER::subscribe_mqtt();
return unless $self->{mqtt_client}->is_connected();
Fugu::Log->default->debug(
'Thermostat %s subscribing to additional MQTT topics',
$self->{name} );
# M2: The plain-text POWER response (SetOption4 support)
$self->_subscribe_plain_power( heating_state => 11 );
# STATUS8 answers an active query; the spec recommends STATUS10
$self->_subscribe_status_sns($_) for qw(STATUS8 STATUS10);
# Query the sensor status immediately
$self->query_status(10);
}
# Override the base method to process the sensor data from
# SENSOR messages and the STATUS8/STATUS10 responses (H4, H5)
sub _process_sensor_data ( $self, $data )
{
my ($temp) = $self->_find_sensor_values($data);
return unless defined $temp;
# Convert the value to Celsius if necessary (H4)
$temp = $self->convert_temperature($temp);
Fugu::Log->default->debug( 'Thermostat %s temperature updated: %.1f°C',
$self->{name}, $temp );
$self->{current_temp} = $temp;
$self->notify_change(13);
$self->_check_thermostat_logic();
}
# Override the base method to process the power state updates
sub _on_power_update ( $self, $state )
{
if ( $self->{heating_state} != $state ) {
$self->{heating_state} = $state;
Fugu::Log->default->debug( 'Thermostat %s heating updated: %s',
$self->{name}, $state ? 'ON' : 'OFF' );
$self->notify_change(11);
}
}
sub _set_target_temp ( $self, $temp )
{
Fugu::Log->default->debug(
'Thermostat %s target temperature set to %.1f°C',
$self->{name}, $temp );
$self->{target_temp} = $temp;
$self->_check_thermostat_logic();
}
sub _set_target_state ( $self, $state )
{
Fugu::Log->default->debug(
'Thermostat %s target heating state set to %d',
$self->{name}, $state );
$self->{target_heating_state} = $state;
( run in 0.805 second using v1.01-cache-2.11-cpan-54e63673c56 )