Implementaton of Filtrete 3M50 wifi thermostat module for fhem

Begonnen von amigob, 24 November 2013, 16:40:18

Vorheriges Thema - Nächstes Thema

amigob

Hi all

I started using Fhem a few weeks a go and start to get a hang of, it, already have some FHT,light,fans, electrical heaters running via fhem.
Now I want to integrate my wifi thermostat to switch on and off the Central heating system. I already have a simple version running that collects data from the Thermostat. The only problem is that the GET request blocks fhem for about 3 sec. But I will live with that for the time being.

I am now programing the module and what to start adding the set functionality. There is my problem :-)
In the UI I see with for instance an FHT a lot of possible sets, I also see these sets defined in the code of FHT, but I can figger out
how they go from the module to the UI.

Can any body help me with this ?

Bart

rudolfkoenig

> how they go from the module to the UI.

You have to implement a correct return value for "set device ?"

amigob

is that the list that comes out of
return SetExtensions($hash, $list, @a);

Don't see it in FHT, but a lot of other modules uses it

Bart,

rudolfkoenig

SetExtensions is only for additional functions described in http://fhem.de/commandref.html#setExtensions

Your SetFn/GetFn should return the string "Unknown argument ?, choose one of " followed by the space separated list of possible commands. If a possible command is followed by a colon, then you can specify some widgets, see http://fhem.de/commandref.html#webCmd

amigob

#4
Thanks,

That now also works, a strange solution  but it works, would expect a separate function for this.

I am really starting to like Fhem, I came from Domoticz, there you have to change the holl program to add an extra module. from HTML generation to the module, really a pain in the ass.
And as long as i is not in the repository I need to a difficult merge every time, for the Thermostat and  for the CUL, FS20, FHT. So I stopped.

Here just add one module and it starts working.

Ok, the interface is not so nice but that will be fixed with some dedicate HTML5 programs
And I need to get use to perl:-)

Bart,

amigob

here is a first version

I only trying to get that you can change desired temperature in the overview of a room?
The FHT has that option.

Logging is not ok , but I would like some feetback, what I did OK and not OK,
The programing data still needs to mad as a parameter, but for me it is now ok.
For people that are interested, it is 4 times times "minutes and temperature in F".

Bart,

##############################################
# $Id: 25_Therm3M50.pm 3738 2013-11-21 14:13:59Z Amigob $
package main;

use strict;
use warnings;
use JSON;
use LWP::UserAgent;

use constant false => 0;
use constant true  => 1;

my %sets =
(
   "Desire-temp"   => "6.0,6.5,7.0,7.5,8.0,8.5,9.0,9.5,10.0,10.5,11.0,11.5,12.0,12.5,13.0,13.5,14.0,14.5,15.0,15.5,16.0,16.5,17.0,17.5,18.0,18.5,19.0,19.5,20.0,20.5,21.0,21.5,22.0,22.5,23.0,23.5,24.0,24.5,25.0,25.5,26.0,26.5,27.0,27.5,28.0,28.5,29.0,29.5,30.0",
   "Overrule"  => "On,Off",
   "Holiday"   => "On,Off",
   "Away"      => "On,Off" 
);

my %ReadingsToReadable =
(
  "fmode" => "FanMode",
  "fstate" => "FanState",
  "hold" => "Hold",
  "override" => "TemperatueOverruled",
  "t_heat" => "DesiredHeatingTemp",
  "t_cool" => "DesiredCoolingTemp",
  "t_type_post" => "t_type_post",
  "temp" => "MeasuredTemprature",
  "tmode" => "TemperatureMode",
  "tstate" => "TemperatureState"
);

my %wdaytoday =
(
  "0" => ["sun",6, "[540,66,600,70,840,70,1410,59]"],
  "1" => ["mon",0, "[540,59,600,59,840,70,1410,59]"],
  "2" => ["tue",1, "[540,59,600,59,840,70,1410,59]"],
  "3" => ["wed",2, "[540,59,600,59,840,70,1410,59]"],
  "4" => ["thu",3, "[540,59,600,59,840,70,1410,59]"],
  "5" => ["fri",4, "[540,59,600,59,840,70,1410,59]"],
  "6" => ["sat",5, "[540,66,600,70,840,70,1410,59]"],
  "7" => ["hol",7, "[541,66,601,70,841,70,1411,59]"],
);

my %led_color =
(
  "None" => 0,
  "Green" => 1,
  "Yellow" => 2,
  "Red" =>  4, 
);

sub
Therm3M50_Initialize($)
{
  my ($hash) = @_;

  $hash->{SetFn}     = "Therm3M50_Set";
  $hash->{DefFn}     = "Therm3M50_Define";
  $hash->{UndefFn}   = "Therm3M50_Undefine";                     
  $hash->{AttrList}  = $readingFnAttributes;
}

###################################
sub
Therm3M50_Set($@)
{
  my ($hash, @a) = @_;
  my $name = shift @a;

  #return "\"set $a[0]\" needs at least two parameters" if(@a < 2);
 
  my $cmdList = join(" ",sort keys %sets);
   
  my @list = map { ($_.".0", $_+0.5) } (6..30);
  pop @list;
  my $tmpList=join(",",@list);
  $cmdList =~ s/-temp/-temp:$tmpList/g;     # FHEMWEB sugar
  $cmdList =~ s/(Overrule|Away|Holiday)/$1:On,Off/g; 
  return "Unknown argument ?, choose one of ".$cmdList if($a[0] eq "?");

  my $cmd = $a[0];
  my $value = $a[1];
 
  if      ( $cmd eq "Overrule" )
  { 
     #$hash->{$cmd} = $value;
     if ($value eq "On")
     {   
         Therm3M50_Overrule($hash);
     }
     else
     {
         Therm3M50_Program($hash);
     }
  }
  elsif( $cmd eq "Holiday" )
  {
     #$hash->{$cmd} = $value;
     if ($value eq "On")
     {   
         Therm3M50_Holiday($hash);
     }
     else
     {
         Therm3M50_Program($hash);
     }
  }
  elsif( $cmd eq "Away" )
  {
     #$hash->{$cmd} = $value;
     if ($value eq "On")
     {   
         Therm3M50_Away($hash);
     }
     else
     {
         Therm3M50_Program($hash);
     }
  }
  elsif( $cmd eq "Desire-temp" )
  {
      Therm3M50_Overrule_temp($hash, $value);
  }
  else
  {   
      return "illegal set value specified";
  }

  #Log3 $name, 0, "dummy set $name $v";
  return undef;
}

sub
Therm3M50_Define($$)
{
  my ($hash, $def) = @_;
  my @a = split("[ \t][ \t]*", $def);

  Therm3M50_Program($hash);
 
  InternalTimer(gettimeofday()+15, "Therm3M50_GetStatus", $hash, 0);
     
  Log3 "Therm3M50", 0, "Therm3M50 Defined";
 
  return "Wrong syntax: use define <name> Therm3M50 <'ip'>" if(int(@a) != 3); 
  return undef;
}

sub
Therm3M50_Undefine($$)
{
  my ($hash, $arg) = @_;
  my $name = $hash->{NAME};
 
  RemoveInternalTimer($hash);
 
  Log 0, "$name Undefine complete";
  return undef;
}

sub
Therm3M50_Overrule($)
{
  my ($hash) = @_;
  my $name = $hash->{NAME};

  my $response = decode_json(Therm3M50_POST($hash, 'http://192.168.181.112/tstat', '{"a_heat":80}' ));
  readingsSingleUpdate($hash,"state","Overrule",1);
  Log3 "$name", 0, "Therm3M50 Overrule: $response"; 
 
  Therm3M50_set_led($hash,"Red");
 
  return undef;
}

sub
Therm3M50_Overrule_temp($$)
{
  my ($hash, $temperatureC) = @_;
  my $name = $hash->{NAME};
   
  #convert to F and round it of.
  my $temperatureF = int((($temperatureC * 9.0) / 5.0) + 32.5); 

  my $response = decode_json(Therm3M50_POST($hash, 'http://192.168.181.112/tstat', '{"a_heat":'.$temperatureF.'}' ));
  readingsSingleUpdate($hash,"state","Overrule",1);
  Log3 "$name", 0, "Therm3M50 Overrule: $response"; 
 
  Therm3M50_set_led($hash,"Red");
 
  return undef;
}


sub
Therm3M50_Holiday($)
{
  my ($hash) = @_;
  my $name = $hash->{NAME};
  my ($sec,$min,$hour,$mday,$month,$year,$wday,$yday,$isdst) = localtime;
   
  # set holiday(7) program for today
  my $response = decode_json(Therm3M50_POST($hash, "http://192.168.181.112/tstat/program/heat/".$wdaytoday{$wday}[0], "{\"".$wdaytoday{$wday}[1]."\":".$wdaytoday{7}[2]."}" ) );
  readingsSingleUpdate($hash,"state","Holiday",1);
  Log3 "$name", 0, "Therm3M50 Holiday: $response"; 
 
  Therm3M50_set_led($hash, "Yellow");

  return undef;
}

sub
Therm3M50_Away($)
{
  my ($hash) = @_;
  my $name = $hash->{NAME};
 
  my $response = decode_json(Therm3M50_POST($hash, "http://192.168.181.112/tstat", "{\"a_heat\":60,\"hold\":1}" ) );
  readingsSingleUpdate($hash,"state","Away",1);
  Log3 "$name", 0, "Therm3M50 Holiday: $response"; 
 
  Therm3M50_set_led($hash, "Yellow");

  return undef;
}

sub
Therm3M50_Program($)
{
  my ($hash) = @_;
  my $name = $hash->{NAME};
  my ($sec,$min,$hour,$mday,$month,$year,$wday,$yday,$isdst) = localtime;
 
  my $response = decode_json(Therm3M50_POST($hash, 'http://192.168.181.112/tstat', '{"a_mode":0,"hold":0}' ));
  # set correct program can have been overrled by holiday
  my $response = decode_json(Therm3M50_POST($hash, "http://192.168.181.112/tstat/program/heat/".$wdaytoday{$wday}[0], "{\"".$wdaytoday{$wday}[1]."\":".$wdaytoday{$wday}[2]."}" ) );
 
  Therm3M50_set_led($hash, "None");
   
  readingsSingleUpdate($hash,"state","Program",1);
  Log3 "$name", 0, "Therm3M50 Program: $response"; 
 
  return undef;
}

sub
Therm3M50_GetStatus($)
{
   my ($hash) = @_;
   my $ua = LWP::UserAgent->new;
   
   my $result = true;

   my $message = decode_json(Therm3M50_GET($hash,"http://192.168.181.112/tstat"));
     
   # check if messages is ook, it can returm -1 voor control values
   while( ( my ($k, $v) = each %$message ) && ($result) )
   { 
      if( ($k ne 'time') && ($k ne 't_heat') && ($k ne 't_cool') && ($k ne 'temp' ) )     
      {     
         if ( ($v == -1) || ($v eq "nok") )   
         {
            #message not ok
            Log3 "Therm3M50", 0, "Therm3M50 error $k $v ";
            $result = false;
         }
      }
   }   
   
   while(( my ($k, $v) = each %$message ) && ($result) )
   { 
      if( $k ne 'time' )     
      {     
         if (( $k eq "t_heat" ) || ( $k eq "t_cool" ) || ( $k eq "temp" ))
         {
            #convert to C and round it of.
            $v = int(((($v - 32.0) * (5/9)) * 10) + 0.5) / 10.0  ;
         }
         readingsSingleUpdate($hash,$ReadingsToReadable{$k},$v,1);
      }
   }
 
   InternalTimer(gettimeofday()+15, "Therm3M50_GetStatus", $hash, 0);
}


sub
Therm3M50_set_led($$)
{
my ($hash, $color) = @_;
my $response = decode_json(Therm3M50_POST($hash, 'http://192.168.181.112/tstat/led', '{"energy_led":'.$led_color{$color}.'}' ));
}

sub
Therm3M50_GET($$)
{
   my ($hash, $server_endpoint) = @_;
   my $message = "";
     
   my $ua = LWP::UserAgent->new;
   $ua->timeout(5);

   # set custom HTTP request header fields
   my $req = HTTP::Request->new(GET => $server_endpoint);
   $req->header('content-type' => 'application/json');
   $req->header('x-auth-token' => 'kfksj48sdfj4jd9d');

   my $resp = $ua->request($req);
   if ($resp->is_success)
   {
     $message = $resp->decoded_content;     
     if ( trim($message) eq "")
     {
        $message = '{"result":"nok"}';         
     }     
   }
   else
   {
     $message = '{"result":"nok"}';         
   }
   Log3 "Therm3M50", 0, "Therm3M50 GET Response: $message";
   
   return $message;
}


sub
Therm3M50_POST($$$)
{
   my ($hash, $server_endpoint, $data) = @_;
   my $message = "";
   my $ua = LWP::UserAgent->new;   

   Log3 "Therm3M50", 0, "Therm3M50 POST:$server_endpoint, $data";

   # set custom HTTP request header fields
   my $req = HTTP::Request->new(POST => $server_endpoint);
   $req->header('content-type' => 'application/json');
   $req->header('x-auth-token' => 'kfksj48sdfj4jd9d');

   # add POST data to HTTP request body   
   $req->content($data);

   my $resp = $ua->request($req);
   if ($resp->is_success)
   {
     $message = $resp->decoded_content;     
     if ( trim($message) eq "")
     {
        $message = '{"result":"nok"}';         
     }
     else
     {
       $message = '{"result":"ok"}';
     }
   }
   else
   {
     $message = '{"result":"nok"}';             
   }
   
   Log3 "Therm3M50", 0, "Therm3M50 POST Response: $message";
   return $message;
}

1;

=pod
=begin html

<a name="Therm3M50"></a>
<h3>Therm3M50</h3>
<ul>

  Define a Therm3M50. A Therm3M50 can take via <a href="#set">set</a> any values.
  Used for programming.
  <br><br>

  <a name="Therm3M50define"></a>
  <b>Define</b>
  <ul>
    <code>define &lt;name&gt; Therm3M50</code>
    <br><br>

    Example:
    <ul>
      <code>define myvar Therm3M50</code><br>
      <code>set myvar 7</code><br>
    </ul>
  </ul>
  <br>

  <a name="Therm3M50set"></a>
  <b>Set</b>
  <ul>
    <code>set &lt;name&gt; &lt;value&gt</code><br>
    Set any value.
  </ul>
  <br>

  <a name="Therm3M50get"></a>
  <b>Get</b> <ul>N/A</ul><br>

  <a name="Therm3M50attr"></a>
  <b>Attributes</b>
  <ul>
    <li><a name="setList">setList</a><br>
        Space separated list of commands, which will be returned upon "set name ?",
        so the FHEMWEB frontend can construct a dropdown and offer on/off
        switches. Example: attr Therm3M50Name setList on off
        </li>
    <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
  </ul>
  <br>

</ul>

=end html
=cut

rudolfkoenig

The FHEMWEB overview will show a dropdown automatically, if it is called desired-temp (not "Desire-temp"), and show the "measured-temp" it it exists.

If your wording is different, you have to use webCmd and stateFormat.

amigob

Tried with the desired-temp and measured-temp but no success,

Should I put something special in the state, I see that in FHT that they put "measured-temp: 33.0"
Tried that also but no success either.

Bart,

rudolfkoenig

No, nothing special / no further action is needed. AFAIK.

amigob

stupid of me, still had a wrong name, 'desired-temp' now working, and added my own status so that I also see if it is overruled or not.

almost time to attach the wifi thermostat to the central heating :-).

Thanks,

amigob

So it is integrated now using the wifi thermostat to switch on and off the Central heating, in combination with 3 FHT's. Seems to work pretty good. :-). I still need to add some parameter so that interval of polling the Thermostat can be controlled, is now 60 sec.

I have one small problem with the FHT, the logging of the desired-temp is only done on desire temperature change. That is
a little bit annoying when you what to put it in a graph. :-0.

No, next step is integrate it with a calendar, Holiday ( at home ) and away days. And then I am going to implement that it needs to start heating earlier when the prediction is colder then 5 degrees outside and the wind speed is above 5 Beaufort.

Bart,

joho500

Hi Bart,

I have the Filtrete 3M50 too and it would be nice to use it with fhem. Can you attach the code to the thread?

Thanks.

Joost