mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-03-12 16:46:35 +00:00
add new modules 37_SHC.pm and 37_SHCdev.pm to support smarthomatic devices
git-svn-id: https://svn.fhem.de/fhem/trunk@6109 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
1a9a0f3109
commit
9baef8b190
@ -1,5 +1,6 @@
|
||||
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide.
|
||||
# Do not insert empty lines here, update check depends on it.
|
||||
- feature: new modules 37_SHC.pm and 37_SHCdev.pm added (rr2000)
|
||||
- ad-hoc: (betateilchen)
|
||||
reverted 10_CUL_HM.pm to previous version 6096
|
||||
due to nonworking version 6098 and no reaction from developer
|
||||
|
414
fhem/FHEM/37_SHC.pm
Normal file
414
fhem/FHEM/37_SHC.pm
Normal file
@ -0,0 +1,414 @@
|
||||
##########################################################################
|
||||
# This file is part of the smarthomatic module for FHEM.
|
||||
#
|
||||
# Copyright (c) 2014 Stefan Baumann
|
||||
#
|
||||
# You can find smarthomatic at www.smarthomatic.org.
|
||||
# You can find FHEM at www.fhem.de.
|
||||
#
|
||||
# This file is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the
|
||||
# Free Software Foundation, either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This file is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
# Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with smarthomatic. If not, see <http://www.gnu.org/licenses/>.
|
||||
###########################################################################
|
||||
# $Id: 37_SHC.pm xxxx 2014-xx-xx xx:xx:xx rr2000 $
|
||||
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Time::HiRes qw(gettimeofday);
|
||||
|
||||
sub SHC_Parse($$$$);
|
||||
sub SHC_Read($);
|
||||
sub SHC_ReadAnswer($$$$);
|
||||
sub SHC_Ready($);
|
||||
|
||||
sub SHC_SimpleWrite(@);
|
||||
|
||||
my $clientsSHC = ":SHCdev:BASE:xxx:";
|
||||
|
||||
my %matchListSHC = (
|
||||
"1:SHCdev" => "^Packet Data: SenderID=[1-9]|0[1-9]|[1-9][0-9]|[0-9][0-9][0-9]|[0-3][0-9][0-9][0-9]|40[0-8][0-9]|409[0-6]", #1-4096 with leading zeros
|
||||
"2:xxx" => "^\\S+\\s+22",
|
||||
"3:xxx" => "^\\S+\\s+11",
|
||||
"4:xxx" => "^\\S+\\s+9 ",
|
||||
);
|
||||
|
||||
sub SHC_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
require "$attr{global}{modpath}/FHEM/DevIo.pm";
|
||||
|
||||
# Provider
|
||||
$hash->{ReadFn} = "SHC_Read";
|
||||
$hash->{WriteFn} = "SHC_Write";
|
||||
$hash->{ReadyFn} = "SHC_Ready";
|
||||
|
||||
# Normal devices
|
||||
$hash->{DefFn} = "SHC_Define";
|
||||
$hash->{UndefFn} = "SHC_Undef";
|
||||
$hash->{GetFn} = "SHC_Get";
|
||||
$hash->{SetFn} = "SHC_Set";
|
||||
$hash->{ShutdownFn} = "SHC_Shutdown";
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub SHC_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
if (@a != 3) {
|
||||
my $msg = "wrong syntax: define <name> SHC {devicename[\@baudrate] " . "| devicename\@directio}";
|
||||
Log3 undef, 2, $msg;
|
||||
return $msg;
|
||||
}
|
||||
|
||||
DevIo_CloseDev($hash);
|
||||
|
||||
my $name = $a[0];
|
||||
my $dev = $a[2];
|
||||
$dev .= "\@19200" if ($dev !~ m/\@/);
|
||||
|
||||
$hash->{Clients} = $clientsSHC;
|
||||
$hash->{MatchList} = \%matchListSHC;
|
||||
$hash->{DeviceName} = $dev;
|
||||
|
||||
my $ret = DevIo_OpenDev($hash, 0, "SHC_DoInit");
|
||||
return $ret;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub SHC_Undef($$)
|
||||
{
|
||||
my ($hash, $arg) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
foreach my $d (sort keys %defs) {
|
||||
if ( defined($defs{$d})
|
||||
&& defined($defs{$d}{IODev})
|
||||
&& $defs{$d}{IODev} == $hash)
|
||||
{
|
||||
my $lev = ($reread_active ? 4 : 2);
|
||||
Log3 $name, $lev, "$name: deleting port for $d";
|
||||
delete $defs{$d}{IODev};
|
||||
}
|
||||
}
|
||||
|
||||
SHC_Shutdown($hash);
|
||||
DevIo_CloseDev($hash);
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub SHC_Shutdown($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub SHC_Set($@)
|
||||
{
|
||||
my ($hash, @a) = @_;
|
||||
|
||||
my $name = shift @a;
|
||||
my $cmd = shift @a;
|
||||
my $arg = join("", @a);
|
||||
|
||||
my $list = "raw:noArg";
|
||||
return $list if ($cmd eq '?');
|
||||
|
||||
if ($cmd eq "raw") {
|
||||
|
||||
#return "\"set SHC $cmd\" needs exactly one parameter" if(@_ != 4);
|
||||
#return "Expecting a even length hex number" if((length($arg)&1) == 1 || $arg !~ m/^[\dA-F]{12,}$/ );
|
||||
Log3 $name, 4, "$name: set $name $cmd $arg";
|
||||
SHC_SimpleWrite($hash, $arg);
|
||||
|
||||
} else {
|
||||
return "Unknown argument $cmd, choose one of " . $list;
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub SHC_Get($@)
|
||||
{
|
||||
my ($hash, $name, $cmd) = @_;
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub SHC_DoInit($)
|
||||
{
|
||||
my $hash = shift;
|
||||
my $name = $hash->{NAME};
|
||||
my $err;
|
||||
my $msg = undef;
|
||||
|
||||
$hash->{STATE} = "Initialized";
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
# This is a direct read for commands like get
|
||||
# Anydata is used by read file to get the filesize
|
||||
sub SHC_ReadAnswer($$$$)
|
||||
{
|
||||
# TODO: Not adapted to SHC, copy from 36_JeeLink.pm
|
||||
my ($hash, $arg, $anydata, $regexp) = @_;
|
||||
my $type = $hash->{TYPE};
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
return ("No FD", undef)
|
||||
if (!$hash || ($^O !~ /Win/ && !defined($hash->{FD})));
|
||||
|
||||
my ($mpandata, $rin) = ("", '');
|
||||
my $buf;
|
||||
my $to = 3; # 3 seconds timeout
|
||||
$to = $hash->{RA_Timeout} if ($hash->{RA_Timeout}); # ...or less
|
||||
for (; ;) {
|
||||
|
||||
if ($^O =~ m/Win/ && $hash->{USBDev}) {
|
||||
$hash->{USBDev}->read_const_time($to * 1000); # set timeout (ms)
|
||||
# Read anstatt input sonst funzt read_const_time nicht.
|
||||
$buf = $hash->{USBDev}->read(999);
|
||||
return ("Timeout reading answer for get $arg", undef)
|
||||
if (length($buf) == 0);
|
||||
|
||||
} else {
|
||||
return ("Device lost when reading answer for get $arg", undef)
|
||||
if (!$hash->{FD});
|
||||
|
||||
vec($rin, $hash->{FD}, 1) = 1;
|
||||
my $nfound = select($rin, undef, undef, $to);
|
||||
if ($nfound < 0) {
|
||||
next if ($! == EAGAIN() || $! == EINTR() || $! == 0);
|
||||
my $err = $!;
|
||||
DevIo_Disconnected($hash);
|
||||
return ("SHC_ReadAnswer $arg: $err", undef);
|
||||
}
|
||||
return ("Timeout reading answer for get $arg", undef)
|
||||
if ($nfound == 0);
|
||||
$buf = DevIo_SimpleRead($hash);
|
||||
return ("No data", undef) if (!defined($buf));
|
||||
|
||||
}
|
||||
|
||||
if ($buf) {
|
||||
Log3 $hash->{NAME}, 5, "$name: SHC/RAW (ReadAnswer): $buf";
|
||||
$mpandata .= $buf;
|
||||
}
|
||||
|
||||
chop($mpandata);
|
||||
chop($mpandata);
|
||||
|
||||
return (
|
||||
undef, $mpandata
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub SHC_Write($$)
|
||||
{
|
||||
# TODO: Not adapted to SHC, copy from 36_JeeLink.pm
|
||||
my ($hash, $msg) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
Log3 $name, 5, "$name: sending $msg";
|
||||
|
||||
SHC_SimpleWrite($hash, $msg);
|
||||
}
|
||||
|
||||
#####################################
|
||||
# called from the global loop, when the select for hash->{FD} reports data
|
||||
sub SHC_Read($)
|
||||
{
|
||||
# TODO: Verify if partial data handling is required for SHC
|
||||
my ($hash) = @_;
|
||||
|
||||
my $buf = DevIo_SimpleRead($hash);
|
||||
return "" if (!defined($buf));
|
||||
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
my $pandata = $hash->{PARTIAL};
|
||||
Log3 $name, 5, "$name: SHC/RAW: $pandata/$buf";
|
||||
$pandata .= $buf;
|
||||
|
||||
while ($pandata =~ m/\n/) {
|
||||
my $rmsg;
|
||||
($rmsg, $pandata) = split("\n", $pandata, 2);
|
||||
$rmsg =~ s/\r//;
|
||||
SHC_Parse($hash, $hash, $name, $rmsg) if ($rmsg);
|
||||
}
|
||||
$hash->{PARTIAL} = $pandata;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub SHC_Parse($$$$)
|
||||
{
|
||||
my ($hash, $iohash, $name, $rmsg) = @_;
|
||||
my $dmsg = $rmsg;
|
||||
|
||||
next if (!$dmsg || length($dmsg) < 1); # Bogus messages
|
||||
|
||||
if ($dmsg !~ m/^Packet Data: SenderID=/) {
|
||||
|
||||
# Messages just to dipose
|
||||
if ( $dmsg =~ m/^\*\*\* Enter AES key nr/
|
||||
|| $dmsg =~ m/^\*\*\* Received character/)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
# -Verbosity level 5
|
||||
if ( $dmsg =~ m/^Received \(AES key/
|
||||
|| $dmsg =~ m/^Received garbage/
|
||||
|| $dmsg =~ m/^Before encryption/
|
||||
|| $dmsg =~ m/^After encryption/
|
||||
|| $dmsg =~ m/^Repeating request./
|
||||
|| $dmsg =~ m/^Request Queue empty/
|
||||
|| $dmsg =~ m/^Removing request from request buffer/)
|
||||
{
|
||||
Log3 $name, 5, "$name: $dmsg";
|
||||
return;
|
||||
}
|
||||
|
||||
# -Verbosity level 4
|
||||
if ( $dmsg =~ m/^Request added to queue/
|
||||
|| $dmsg =~ m/^Request Buffer/
|
||||
|| $dmsg =~ m/^Request (q|Q)ueue/)
|
||||
{
|
||||
Log3 $name, 4, "$name: $dmsg";
|
||||
return;
|
||||
}
|
||||
|
||||
# Anything else in verbosity level 3
|
||||
Log3 $name, 3, "$name: $dmsg";
|
||||
return;
|
||||
}
|
||||
|
||||
$hash->{"${name}_MSGCNT"}++;
|
||||
$hash->{"${name}_TIME"} = TimeNow();
|
||||
$hash->{RAWMSG} = $rmsg;
|
||||
my %addvals = (RAWMSG => $rmsg);
|
||||
|
||||
Dispatch($hash, $dmsg, \%addvals);
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub SHC_Ready($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
return DevIo_OpenDev($hash, 1, "SHC_DoInit")
|
||||
if ($hash->{STATE} eq "disconnected");
|
||||
|
||||
# This is relevant for windows/USB only
|
||||
my $po = $hash->{USBDev};
|
||||
my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags);
|
||||
if ($po) {
|
||||
($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags) = $po->status;
|
||||
}
|
||||
return ($InBytes && $InBytes > 0);
|
||||
}
|
||||
|
||||
########################
|
||||
sub SHC_SimpleWrite(@)
|
||||
{
|
||||
my ($hash, $msg) = @_;
|
||||
return if (!$hash);
|
||||
|
||||
my $name = $hash->{NAME};
|
||||
Log3 $name, 5, "$name: SW: $msg";
|
||||
|
||||
$msg .= "\r";
|
||||
|
||||
$hash->{USBDev}->write($msg) if ($hash->{USBDev});
|
||||
syswrite($hash->{DIODev}, $msg) if ($hash->{DIODev});
|
||||
|
||||
# Some linux installations are broken with 0.001, T01 returns no answer
|
||||
select(undef, undef, undef, 0.01);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=pod
|
||||
=begin html
|
||||
|
||||
<a name="SHC"></a>
|
||||
<h3>SHC</h3>
|
||||
<ul>
|
||||
SHC is the basestation module that supports a family of RF devices available
|
||||
at <a href="http://http://www.smarthomatic.org">www.smarthomatic.org</a>.
|
||||
|
||||
This module provides the IODevice for the <a href="#SHCdev">SHCdev</a>
|
||||
modules that implement the SHCdev protocol.<br><br>
|
||||
|
||||
Note: this module may require the Device::SerialPort or Win32::SerialPort
|
||||
module if you attach the device via USB and the OS sets strange default
|
||||
parameters for serial devices.<br><br>
|
||||
|
||||
<a name="SHC_Define"></a>
|
||||
<b>Define</b>
|
||||
<ul>
|
||||
<code>define <name> SHC <device></code><br>
|
||||
<br>
|
||||
<device> specifies the serial port to communicate with the SHC.
|
||||
The name of the serial-device depends on your distribution, under
|
||||
linux usually a /dev/ttyUSB0 device will be created.<br><br>
|
||||
|
||||
You can also specify a baudrate if the device name contains the @
|
||||
character, e.g.: /dev/ttyUSB0@57600. Please note that the default
|
||||
baudrate for the SHC base station is 19200 baud.<br><br>
|
||||
|
||||
Example:<br>
|
||||
<ul>
|
||||
<code>define shc_base SHC /dev/ttyUSB0</code><br><br>
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
<a name="SHC_Set"></a>
|
||||
<b>Set</b>
|
||||
<ul>
|
||||
<li>raw <data><br>
|
||||
not supported yet
|
||||
</li><br>
|
||||
</ul>
|
||||
|
||||
<a name="SHC_Get"></a>
|
||||
<b>Get</b>
|
||||
<ul>
|
||||
<li>
|
||||
N/A
|
||||
</li><br>
|
||||
</ul>
|
||||
|
||||
<a name="SHC_Attr"></a>
|
||||
<b>Attributes</b>
|
||||
<ul>
|
||||
<li>
|
||||
N/A
|
||||
</li><br>
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
=end html
|
||||
=cut
|
640
fhem/FHEM/37_SHCdev.pm
Normal file
640
fhem/FHEM/37_SHCdev.pm
Normal file
@ -0,0 +1,640 @@
|
||||
##########################################################################
|
||||
# This file is part of the smarthomatic module for FHEM.
|
||||
#
|
||||
# Copyright (c) 2014 Stefan Baumann, Uwe Freese
|
||||
#
|
||||
# You can find smarthomatic at www.smarthomatic.org.
|
||||
# You can find FHEM at www.fhem.de.
|
||||
#
|
||||
# This file is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the
|
||||
# Free Software Foundation, either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This file is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
# Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with smarthomatic. If not, see <http://www.gnu.org/licenses/>.
|
||||
###########################################################################
|
||||
# $Id: 37_SHCdev.pm xxxx 2014-xx-xx xx:xx:xx rr2000 $
|
||||
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use feature qw(switch);
|
||||
use warnings;
|
||||
use SetExtensions;
|
||||
|
||||
use SHC_parser;
|
||||
|
||||
my $parser = new SHC_parser();
|
||||
|
||||
my %dev_state_icons = (
|
||||
"PowerSwitch" => "on:on:toggle off:off:toggle set.*:light_question:off",
|
||||
"Dimmer" => "on:on off:off set.*:light_question:off",
|
||||
"EnvSensor" => undef
|
||||
);
|
||||
|
||||
my %web_cmds = (
|
||||
"PowerSwitch" => "on:off:toggle:statusRequest",
|
||||
"Dimmer" => "on:off:statusRequest",
|
||||
"EnvSensor" => undef
|
||||
);
|
||||
|
||||
# Array format: [ reading1, str_format1, reading2, str_format2 ... ]
|
||||
# "on" reading translates 0 -> "off"
|
||||
# 1 -> "on"
|
||||
my %dev_state_format = (
|
||||
"PowerSwitch" => ["on", ""],
|
||||
"Dimmer" => ["on", "", "brightness", "B: "],
|
||||
"EnvSensor" => [ # Results in "T: 23.4 H: 27.3 Baro: 978.34 B: 45"
|
||||
"temperature", "T: ",
|
||||
"humidity", "H: ",
|
||||
"barometric_pressure", "Baro: ",
|
||||
"brightness", "B: ",
|
||||
"distance", "D: "
|
||||
]
|
||||
);
|
||||
|
||||
# Supported set commands
|
||||
# use "" if no set commands are available for device type
|
||||
# use "cmd_name:cmd_additional_info"
|
||||
# cmd_additional_info: Description available at http://www.fhemwiki.de/wiki/DevelopmentModuleIntro#X_Set
|
||||
my %sets = (
|
||||
"PowerSwitch" => "on:noArg off:noArg toggle:noArg statusRequest:noArg " .
|
||||
# Used from SetExtensions.pm
|
||||
"blink on-for-timer on-till off-for-timer off-till intervals",
|
||||
"Dimmer" => "on:noArg off:noArg toggle:noArg statusRequest:noArg pct:slider,0,1,100 ani " .
|
||||
# Used from SetExtensions.pm
|
||||
"blink on-for-timer on-till off-for-timer off-till intervals",
|
||||
"EnvSensor" => "",
|
||||
"Custom" => "PowerSwitch.SwitchState " .
|
||||
"PowerSwitch.SwitchStateExt " .
|
||||
"Dimmer.Brightness " .
|
||||
"Dimmer.Animation"
|
||||
);
|
||||
|
||||
# Supported get commands
|
||||
# use syntax from set commands
|
||||
my %gets = (
|
||||
"PowerSwitch" => "",
|
||||
"Dimmer" => "",
|
||||
"EnvSensor" => "input:all,1,2,3,4,5,6,7,8 ",
|
||||
"Custom" => ""
|
||||
);
|
||||
|
||||
# Hashtable for automatic device type assignment
|
||||
# Format:
|
||||
# "MessageGroupName:MessageName" => "Auto Device Type"
|
||||
my %auto_devtype = (
|
||||
"Weather.Temperature" => "EnvSensor",
|
||||
"Weather.HumidityTemperature" => "EnvSensor",
|
||||
"Weather.BarometricPressureTemperature" => "EnvSensor",
|
||||
"Environment.Brightness" => "EnvSensor",
|
||||
"Environment.Distance" => "EnvSensor",
|
||||
"GPIO.DigitalPin" => "EnvSensor",
|
||||
"PowerSwitch.SwitchState" => "PowerSwitch",
|
||||
"Dimmer.Brightness" => "Dimmer"
|
||||
);
|
||||
|
||||
sub SHCdev_Parse($$);
|
||||
|
||||
#####################################
|
||||
sub SHCdev_Initialize($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{Match} = "^Packet Data: SenderID=[1-9]|0[1-9]|[1-9][0-9]|[0-9][0-9][0-9]|[0-3][0-9][0-9][0-9]|40[0-8][0-9]|409[0-6]";
|
||||
$hash->{SetFn} = "SHCdev_Set";
|
||||
$hash->{GetFn} = "SHCdev_Get";
|
||||
$hash->{DefFn} = "SHCdev_Define";
|
||||
$hash->{UndefFn} = "SHCdev_Undef";
|
||||
$hash->{ParseFn} = "SHCdev_Parse";
|
||||
$hash->{AttrList} = "IODev"
|
||||
." readonly:1"
|
||||
." forceOn:1"
|
||||
." $readingFnAttributes"
|
||||
." devtype:EnvSensor,Dimmer,PowerSwitch";
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub SHCdev_Define($$)
|
||||
{
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
if (@a < 3 || @a > 4) {
|
||||
my $msg = "wrong syntax: define <name> SHCdev <SenderID> [<AesKey>] ";
|
||||
Log3 undef, 2, $msg;
|
||||
return $msg;
|
||||
}
|
||||
|
||||
# Correct SenderID for SHC devices is from 1 - 4096 (leading zeros allowed)
|
||||
$a[2] =~ m/^([1-9]|0[1-9]|[1-9][0-9]|[0-9][0-9][0-9]|[0-3][0-9][0-9][0-9]|40[0-8][0-9]|409[0-6])$/i;
|
||||
return "$a[2] is not a valid SHCdev SenderID" if (!defined($1));
|
||||
|
||||
my $aeskey;
|
||||
|
||||
if (@a == 3) {
|
||||
$aeskey = 0;
|
||||
} else {
|
||||
return "$a[3] is not a valid SHCdev AesKey" if ($a[3] lt 0 || $a[3] gt 15);
|
||||
$aeskey = $a[3];
|
||||
}
|
||||
|
||||
my $name = $a[0];
|
||||
my $addr = $a[2];
|
||||
|
||||
return "SHCdev device $addr already used for $modules{SHCdev}{defptr}{$addr}->{NAME}." if ($modules{SHCdev}{defptr}{$addr}
|
||||
&& $modules{SHCdev}{defptr}{$addr}->{NAME} ne $name);
|
||||
|
||||
$hash->{addr} = $addr;
|
||||
$hash->{aeskey} = $aeskey;
|
||||
|
||||
$modules{SHCdev}{defptr}{$addr} = $hash;
|
||||
|
||||
AssignIoPort($hash);
|
||||
if (defined($hash->{IODev}->{NAME})) {
|
||||
Log3 $name, 3, "$name: I/O device is " . $hash->{IODev}->{NAME};
|
||||
} else {
|
||||
Log3 $name, 1, "$name: no I/O device";
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub SHCdev_Undef($$)
|
||||
{
|
||||
my ($hash, $arg) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
my $addr = $hash->{addr};
|
||||
|
||||
delete($modules{SHCdev}{defptr}{$addr});
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub SHCdev_Parse($$)
|
||||
{
|
||||
my ($hash, $msg) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
if (!$parser->parse($msg)) {
|
||||
Log3 $hash, 4, "SHC_TEMP: parser error: $msg";
|
||||
return "";
|
||||
}
|
||||
|
||||
my $msgtypename = $parser->getMessageTypeName();
|
||||
my $msggroupname = $parser->getMessageGroupName();
|
||||
my $msgname = $parser->getMessageName();
|
||||
my $raddr = $parser->getSenderID();
|
||||
my $rhash = $modules{SHCdev}{defptr}{$raddr};
|
||||
my $rname = $rhash ? $rhash->{NAME} : $raddr;
|
||||
|
||||
if (!$modules{SHCdev}{defptr}{$raddr}) {
|
||||
Log3 $name, 3, "SHC_TEMP: Unknown device $rname, please define it";
|
||||
return "UNDEFINED SHCdev_$rname SHCdev $raddr";
|
||||
}
|
||||
|
||||
if (($msgtypename ne "Status") && ($msgtypename ne "AckStatus")) {
|
||||
Log3 $name, 3, "$rname: Ignoring MessageType $msgtypename";
|
||||
return "";
|
||||
}
|
||||
|
||||
Log3 $name, 4, "$rname: Msg: $msg";
|
||||
Log3 $name, 4, "$rname: MsgType: $msgtypename, MsgGroupName: $msggroupname, MsgName: $msgname";
|
||||
|
||||
my @list;
|
||||
push(@list, $rname);
|
||||
$rhash->{SHCdev_lastRcv} = TimeNow();
|
||||
$rhash->{SHCdev_msgtype} = "$msggroupname : $msgname : $msgtypename";
|
||||
|
||||
my $readonly = AttrVal($rname, "readonly", "0");
|
||||
|
||||
readingsBeginUpdate($rhash);
|
||||
|
||||
given ($msggroupname) {
|
||||
when ('Generic') {
|
||||
given ($msgname) {
|
||||
when ('BatteryStatus') {
|
||||
readingsBulkUpdate($rhash, "battery", $parser->getField("Percentage"));
|
||||
}
|
||||
when ('Version') {
|
||||
my $major = $parser->getField("Major");
|
||||
my $minor = $parser->getField("Minor");
|
||||
my $patch = $parser->getField("Patch");
|
||||
my $vhash = $parser->getField("Hash");
|
||||
|
||||
readingsBulkUpdate($rhash, "version", "$major.$minor.$patch-$vhash");
|
||||
}
|
||||
}
|
||||
}
|
||||
when ('GPIO') {
|
||||
given ($msgname) {
|
||||
when ('DigitalPin') {
|
||||
my $pins = "";
|
||||
for (my $i = 0 ; $i < 8 ; $i++) {
|
||||
my $pinx = $parser->getField("On", $i);
|
||||
my $channel = $i + 1;
|
||||
readingsBulkUpdate($rhash, "pin" . $channel, $pinx);
|
||||
$pins .= $pinx;
|
||||
}
|
||||
readingsBulkUpdate($rhash, "pins", $pins);
|
||||
}
|
||||
}
|
||||
}
|
||||
when ('Weather') {
|
||||
given ($msgname) {
|
||||
when ('Temperature') {
|
||||
my $tmp = $parser->getField("Temperature") / 100; # parser returns centigrade
|
||||
|
||||
readingsBulkUpdate($rhash, "temperature", $tmp);
|
||||
}
|
||||
when ('HumidityTemperature') {
|
||||
my $hum = $parser->getField("Humidity") / 10; # parser returns 1/10 percent
|
||||
my $tmp = $parser->getField("Temperature") / 100; # parser returns centigrade
|
||||
|
||||
readingsBulkUpdate($rhash, "humidity", $hum);
|
||||
readingsBulkUpdate($rhash, "temperature", $tmp);
|
||||
}
|
||||
when ('BarometricPressureTemperature') {
|
||||
my $bar = $parser->getField("BarometricPressure") / 100; # parser returns pascal, use hPa
|
||||
my $tmp = $parser->getField("Temperature") / 100; # parser returns centigrade
|
||||
|
||||
readingsBulkUpdate($rhash, "barometric_pressure", $bar);
|
||||
readingsBulkUpdate($rhash, "temperature", $tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
when ('Environment') {
|
||||
given ($msgname) {
|
||||
when ('Brightness') {
|
||||
my $brt = $parser->getField("Brightness");
|
||||
readingsBulkUpdate($rhash, "brightness", $brt);
|
||||
}
|
||||
when ('Distance') {
|
||||
my $brt = $parser->getField("Distance");
|
||||
readingsBulkUpdate($rhash, "distance", $brt);
|
||||
}
|
||||
}
|
||||
}
|
||||
when ('PowerSwitch') {
|
||||
given ($msgname) {
|
||||
when ('SwitchState') {
|
||||
my $on = $parser->getField("On");
|
||||
my $timeout = $parser->getField("TimeoutSec");
|
||||
|
||||
readingsBulkUpdate($rhash, "on", $on);
|
||||
readingsBulkUpdate($rhash, "timeout", $timeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
when ('Dimmer') {
|
||||
given ($msgname) {
|
||||
when ('Brightness') {
|
||||
my $brightness = $parser->getField("Brightness");
|
||||
my $on = $brightness == 0 ? 0 : 1;
|
||||
|
||||
readingsBulkUpdate($rhash, "on", $on);
|
||||
readingsBulkUpdate($rhash, "brightness", $brightness);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Autoassign device type
|
||||
my $devtype = AttrVal( $rname, "devtype", undef );
|
||||
if (!defined($devtype) && (defined($auto_devtype{"$msggroupname.$msgname"}))) {
|
||||
$attr{$rname}{devtype} = $auto_devtype{"$msggroupname.$msgname"};
|
||||
Log3 $name, 3, "$rname: Autoassign device type = " . $attr{$rname}{devtype};
|
||||
}
|
||||
|
||||
# If the devtype is defined add, if not already done, the according webCmds and devStateIcons
|
||||
my $devtype2 = AttrVal( $rname, "devtype", undef );
|
||||
if (defined($devtype2)) {
|
||||
if (!defined($attr{$rname}{devStateIcon}) && defined($dev_state_icons{$devtype2})) {
|
||||
$attr{$rname}{devStateIcon} = $dev_state_icons{$devtype2};
|
||||
}
|
||||
if (!defined($attr{$rname}{webCmd}) && defined($web_cmds{$devtype2})) {
|
||||
$attr{$rname}{webCmd} = $web_cmds{$devtype2};
|
||||
}
|
||||
}
|
||||
|
||||
# Assemble state string according to %dev_state_format
|
||||
my $devtype3 = AttrVal( $rname, "devtype", undef );
|
||||
if (defined($devtype3) && defined($dev_state_format{$devtype3})) {
|
||||
my $state_format_arr = $dev_state_format{$devtype3};
|
||||
|
||||
# Iterate over state_format array, if readings are available append it to the state string
|
||||
my $state_str = "";
|
||||
for (my $i = 0 ; $i < @$state_format_arr ; $i = $i + 2) {
|
||||
if ( defined($rhash->{READINGS}{$state_format_arr->[$i]})
|
||||
&& defined($rhash->{READINGS}{$state_format_arr->[$i]}{VAL}))
|
||||
{
|
||||
my $val = $rhash->{READINGS}{$state_format_arr->[$i]}{VAL};
|
||||
|
||||
if ($state_str ne "") {
|
||||
$state_str .= " ";
|
||||
}
|
||||
|
||||
# "on" reading requires a special treatment because 0 translates to off, 1 translates to on
|
||||
if ($state_format_arr->[$i] eq "on") {
|
||||
$state_str .= $val == 0 ? "off" : "on";
|
||||
} else {
|
||||
$state_str .= $state_format_arr->[$i + 1] . $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
readingsBulkUpdate($rhash, "state", $state_str);
|
||||
}
|
||||
|
||||
readingsEndUpdate($rhash, 1); # Do triggers to update log file
|
||||
|
||||
return @list;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub SHCdev_Set($@)
|
||||
{
|
||||
my ($hash, $name, @aa) = @_;
|
||||
my $cnt = @aa;
|
||||
|
||||
my $cmd = $aa[0];
|
||||
my $arg = $aa[1];
|
||||
my $arg2 = $aa[2];
|
||||
my $arg3 = $aa[3];
|
||||
my $arg4 = $aa[4];
|
||||
|
||||
return "\"set $name\" needs at least one parameter" if ($cnt < 1);
|
||||
|
||||
# Return list of device-specific set-commands.
|
||||
# This list is used to provide the set commands in the web interface
|
||||
my $devtype = AttrVal( $name, "devtype", undef );
|
||||
if ($cmd eq "?") {
|
||||
if (!defined($devtype)) {
|
||||
return;
|
||||
} else {
|
||||
return $sets{$devtype};
|
||||
}
|
||||
}
|
||||
|
||||
if (!defined($devtype)) {
|
||||
return "devtype not yet specifed. Currently supported device types are " . join(", ", sort keys %sets);
|
||||
}
|
||||
|
||||
if (!defined($sets{$devtype})) {
|
||||
return "No set commands for " . $devtype . "device type supported ";
|
||||
}
|
||||
|
||||
# TODO:
|
||||
# Currently the commands for every device type are defined in %sets but not used to verify the commands. Instead
|
||||
# the SetExtension.pm modulesis used for this purpose.
|
||||
# For more sophisticated device types this has to be revisited
|
||||
|
||||
my $readonly = AttrVal($name, "readonly", "0");
|
||||
|
||||
given ($devtype) {
|
||||
when ('PowerSwitch') {
|
||||
|
||||
# Timeout functionality for SHCdev is not implemented, because FHEMs internal notification system
|
||||
# is able to do this as well. Even more it supports intervals, off-for-timer, off-till ...
|
||||
|
||||
if ($cmd eq 'toggle') {
|
||||
$cmd = ReadingsVal($name, "state", "on") eq "off" ? "on" : "off";
|
||||
}
|
||||
|
||||
if (!$readonly && $cmd eq 'off') {
|
||||
readingsSingleUpdate($hash, "state", "set-$cmd", 1);
|
||||
$parser->initPacket("PowerSwitch", "SwitchState", "SetGet");
|
||||
$parser->setField("PowerSwitch", "SwitchState", "TimeoutSec", 0);
|
||||
$parser->setField("PowerSwitch", "SwitchState", "On", 0);
|
||||
SHCdev_Send($hash);
|
||||
} elsif (!$readonly && $cmd eq 'on') {
|
||||
readingsSingleUpdate($hash, "state", "set-$cmd", 1);
|
||||
$parser->initPacket("PowerSwitch", "SwitchState", "SetGet");
|
||||
$parser->setField("PowerSwitch", "SwitchState", "TimeoutSec", 0);
|
||||
$parser->setField("PowerSwitch", "SwitchState", "On", 1);
|
||||
SHCdev_Send($hash);
|
||||
} elsif ($cmd eq 'statusRequest') {
|
||||
$parser->initPacket("PowerSwitch", "SwitchState", "Get");
|
||||
SHCdev_Send($hash);
|
||||
} else {
|
||||
return SetExtensions($hash, "", $name, @aa);
|
||||
}
|
||||
}
|
||||
when ('Dimmer') {
|
||||
|
||||
# Timeout functionality for SHCdev is not implemented, because FHEMs internal notification system
|
||||
# is able to do this as well. Even more it supports intervals, off-for-timer, off-till ...
|
||||
|
||||
if ($cmd eq 'toggle') {
|
||||
$cmd = ReadingsVal($name, "state", "on") eq "off" ? "on" : "off";
|
||||
}
|
||||
|
||||
if (!$readonly && $cmd eq 'off') {
|
||||
readingsSingleUpdate($hash, "state", "set-$cmd", 1);
|
||||
$parser->initPacket("Dimmer", "Brightness", "SetGet");
|
||||
$parser->setField("Dimmer", "Brightness", "Brightness", 0);
|
||||
SHCdev_Send($hash);
|
||||
} elsif (!$readonly && $cmd eq 'on') {
|
||||
readingsSingleUpdate($hash, "state", "set-$cmd", 1);
|
||||
$parser->initPacket("Dimmer", "Brightness", "SetGet");
|
||||
$parser->setField("Dimmer", "Brightness", "Brightness", 100);
|
||||
SHCdev_Send($hash);
|
||||
} elsif (!$readonly && $cmd eq 'pct') {
|
||||
my $brightness = $arg;
|
||||
|
||||
# DEBUG
|
||||
# Log3 $name, 3, "$name: Args: $arg, $arg2, $arg3, $brightness";
|
||||
|
||||
readingsSingleUpdate($hash, "state", "set-pct:$brightness", 1);
|
||||
$parser->initPacket("Dimmer", "Brightness", "SetGet");
|
||||
$parser->setField("Dimmer", "Brightness", "Brightness", $brightness);
|
||||
SHCdev_Send($hash);
|
||||
} elsif (!$readonly && $cmd eq 'ani') {
|
||||
|
||||
#TODO Verify argument values
|
||||
my $brightness = $arg;
|
||||
|
||||
# DEBUG
|
||||
# Log3 $name, 3, "$name: ani args: $arg, $arg2, $arg3, $arg4, $brightness";
|
||||
|
||||
readingsSingleUpdate($hash, "state", "set-ani", 1);
|
||||
$parser->initPacket("Dimmer", "Animation", "SetGet");
|
||||
$parser->setField("Dimmer", "Animation", "AnimationMode", $arg);
|
||||
$parser->setField("Dimmer", "Animation", "TimeoutSec", $arg2);
|
||||
$parser->setField("Dimmer", "Animation", "StartBrightness", $arg3);
|
||||
$parser->setField("Dimmer", "Animation", "EndBrightness", $arg4);
|
||||
SHCdev_Send($hash);
|
||||
} elsif ($cmd eq 'statusRequest') {
|
||||
$parser->initPacket("Dimmer", "Brightness", "Get");
|
||||
SHCdev_Send($hash);
|
||||
} else {
|
||||
return SetExtensions($hash, "", $name, @aa);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub SHCdev_Get($@)
|
||||
{
|
||||
my ($hash, $name, @aa) = @_;
|
||||
my $cnt = @aa;
|
||||
|
||||
my $cmd = $aa[0];
|
||||
my $arg = $aa[1];
|
||||
|
||||
return "\"get $name\" needs at least one parameter" if ($cnt < 1);
|
||||
|
||||
my $devtype = AttrVal( $name, "devtype", undef );
|
||||
if (!defined($devtype)) {
|
||||
return "\"devtype\" not yet specifed. Currently supported device types are " . join(", ", sort keys %sets);
|
||||
}
|
||||
|
||||
if (!defined($gets{$devtype})) {
|
||||
return "No get commands for " . $devtype . " device type supported ";
|
||||
}
|
||||
|
||||
given ($devtype) {
|
||||
when ('EnvSensor') {
|
||||
if ($cmd eq 'input') {
|
||||
if ($arg =~ /[1-8]/) {
|
||||
my $channel = "pin" . $arg;
|
||||
if ( defined($hash->{READINGS}{$channel})
|
||||
&& defined($hash->{READINGS}{$channel}{VAL}))
|
||||
{
|
||||
return "$name.$channel => " . $hash->{READINGS}{$channel}{VAL};
|
||||
}
|
||||
return "Error: \"input " . $channel . "\" readings not yet available or not supported by device";
|
||||
}
|
||||
elsif ($arg eq "all")
|
||||
{
|
||||
if ( defined($hash->{READINGS}{pins})
|
||||
&& defined($hash->{READINGS}{pins}{VAL}))
|
||||
{
|
||||
return "$name.pins => " . $hash->{READINGS}{pins}{VAL};
|
||||
}
|
||||
return "Error: \"input all\" readings not yet available or not supported by device";
|
||||
}
|
||||
}
|
||||
|
||||
# This return is required to provide the get commands in the web interface
|
||||
return "Unknown argument $cmd, choose one of " . $gets{$devtype};
|
||||
}
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub SHCdev_Send($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
$hash->{SHCdev_lastSend} = TimeNow();
|
||||
|
||||
my $msg = $parser->getSendString($hash->{addr}, $hash->{aeskey});
|
||||
|
||||
Log3 $name, 3, "$name: Sending $msg";
|
||||
|
||||
IOWrite($hash, $msg);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=pod
|
||||
=begin html
|
||||
|
||||
<a name="SHCdev"></a>
|
||||
<h3>SHCdev</h3>
|
||||
<ul>
|
||||
SHC is the device module that supports several device types available
|
||||
at <a href="http://http://www.smarthomatic.org">www.smarthomatic.org</a>.<br><br>
|
||||
|
||||
These device are connected to the FHEM server through the SHC base station (<a href="#SHC">SHC</a>).<br><br>
|
||||
Currently supported are:<br>
|
||||
<ul>
|
||||
<li>EnvSensor</li>
|
||||
<li>PowerSwitch</li>
|
||||
<li>Dimmer</li>
|
||||
</ul><br>
|
||||
|
||||
<a name="SHCdev_Define"></a>
|
||||
<b>Define</b>
|
||||
<ul>
|
||||
<code>define <name> SHCdev <SenderID> [<AesKey>]</code><br>
|
||||
<br>
|
||||
<SenderID><br>
|
||||
is a number ranging from 0 .. 4095 to identify the SHCdev device.<br><br>
|
||||
|
||||
<AesKey><br>
|
||||
is a optional number ranging from 0 .. 15 to select an encryption key.
|
||||
It is required for the basestation to communicate with remote devides
|
||||
The default value is 0.<br><br>
|
||||
|
||||
Note: devices are autocreated on reception of the first message.<br>
|
||||
</ul>
|
||||
<br>
|
||||
|
||||
<a name="SHCdev_Set"></a>
|
||||
<b>Set</b>
|
||||
<ul>
|
||||
<li>on<br>
|
||||
Supported by Dimmer and PowerSwitch.
|
||||
</li><br>
|
||||
<li>off<br>
|
||||
Supported by Dimmer, PowerSwitch.
|
||||
</li><br>
|
||||
<li>pct <0..100><br>
|
||||
Sets the brightness in percent. Supported by Dimmer.
|
||||
</li><br>
|
||||
<li>ani <AnimationMode> <TimeoutSec> <StartBrightness> <EndBrightness><br>
|
||||
Description and details available at <a href="http://www.smarthomatic.org/basics/message_catalog.html#Dimmer_Animation">www.smarthomatic.org</a>
|
||||
Supported by Dimmer.
|
||||
</li><br>
|
||||
<li>statusRequest<br>
|
||||
Supported by Dimmer and PowerSwitch.
|
||||
</li><br>
|
||||
<li><a href="#setExtensions"> set extensions</a><br>
|
||||
Supported by Dimmer and PowerSwitch.</li>
|
||||
</ul><br>
|
||||
|
||||
<a name="SHCdev_Get"></a>
|
||||
<b>Get</b>
|
||||
<ul>
|
||||
<li>input <pin><br>
|
||||
Returns the state of the specified pin for pin = 1..8 or the state of all pins for pin = all.
|
||||
Supported by EnvSensor.
|
||||
</li><br>
|
||||
</ul><br>
|
||||
|
||||
<a name="SHCdev_Attr"></a>
|
||||
<b>Attributes</b>
|
||||
<ul>
|
||||
<li>devtype<br>
|
||||
The device type determines the command set, default web commands and the
|
||||
default devStateicon. Currently supported are: EnvSensor, Dimmer, PowerSwitch.<br><br>
|
||||
|
||||
Note: If the device is not set manually, it will be determined automatically
|
||||
on reception of a device type specific message. For example: If a
|
||||
temperature message is received, the device type will be set to EnvSensor.
|
||||
</li><br>
|
||||
<li>readonly<br>
|
||||
if set to a value != 0 all switching commands (on, off, toggle, ...) will be disabled.
|
||||
</li><br>
|
||||
<li>forceOn<br>
|
||||
try to switch on the device whenever an off status is received.
|
||||
</li><br>
|
||||
</ul><br>
|
||||
</ul>
|
||||
|
||||
=end html
|
||||
=cut
|
300
fhem/FHEM/SHC_datafields.pm
Normal file
300
fhem/FHEM/SHC_datafields.pm
Normal file
@ -0,0 +1,300 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
##########################################################################
|
||||
# This file is part of the smarthomatic module for FHEM.
|
||||
#
|
||||
# Copyright (c) 2014 Uwe Freese
|
||||
#
|
||||
# You can find smarthomatic at www.smarthomatic.org.
|
||||
# You can find FHEM at www.fhem.de.
|
||||
#
|
||||
# This file is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the
|
||||
# Free Software Foundation, either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This file is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
# Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with smarthomatic. If not, see <http://www.gnu.org/licenses/>.
|
||||
##########################################################################
|
||||
# $Id: 37_SHC_Dev.pm xxxx 2014-xx-xx xx:xx:xx rr2000 $
|
||||
|
||||
package SHC_util;
|
||||
|
||||
# ----------- helper functions -----------
|
||||
|
||||
sub max($$)
|
||||
{
|
||||
my ($x, $y) = @_;
|
||||
return $x >= $y ? $x : $y;
|
||||
}
|
||||
|
||||
sub min($$)
|
||||
{
|
||||
my ($x, $y) = @_;
|
||||
return $x <= $y ? $x : $y;
|
||||
}
|
||||
|
||||
# clear some bits within a byte
|
||||
sub clear_bits($$$)
|
||||
{
|
||||
my ($input, $bit, $bits_to_clear) = @_;
|
||||
my $mask = (~((((1 << $bits_to_clear) - 1)) << (8 - $bits_to_clear - $bit)));
|
||||
return ($input & $mask);
|
||||
}
|
||||
|
||||
# get some bits from a 32 bit value, counted from the left (MSB) side! The first bit is bit nr. 0.
|
||||
sub get_bits($$$)
|
||||
{
|
||||
my ($input, $bit, $len) = @_;
|
||||
return ($input >> (32 - $len - $bit)) & ((1 << $len) - 1);
|
||||
}
|
||||
|
||||
sub getUInt($$$)
|
||||
{
|
||||
my ($byteArrayRef, $offset, $length_bits) = @_;
|
||||
|
||||
my $byte = $offset / 8;
|
||||
my $bit = $offset % 8;
|
||||
|
||||
my $byres_read = 0;
|
||||
my $val = 0;
|
||||
my $shiftBits;
|
||||
|
||||
# read the bytes one after another, shift them to the correct position and add them
|
||||
while ($length_bits + $bit > $byres_read * 8) {
|
||||
$shiftBits = $length_bits + $bit - $byres_read * 8 - 8;
|
||||
my $zz = @$byteArrayRef[$byte + $byres_read];
|
||||
|
||||
if ($shiftBits >= 0) {
|
||||
$val += $zz << $shiftBits;
|
||||
} else {
|
||||
$val += $zz >> -$shiftBits;
|
||||
}
|
||||
|
||||
$byres_read++;
|
||||
}
|
||||
|
||||
# filter out only the wanted bits and clear unwanted upper bits
|
||||
if ($length_bits < 32) {
|
||||
$val = $val & ((1 << $length_bits) - 1);
|
||||
}
|
||||
|
||||
return $val;
|
||||
}
|
||||
|
||||
# write some bits to byte array only within one byte
|
||||
sub setUIntBits($$$$$)
|
||||
{
|
||||
my ($byteArrayRef, $byte, $bit, $length_bits, $val8) = @_;
|
||||
|
||||
my $b = 0;
|
||||
|
||||
# if length is smaller than 8 bits, get the old value from array
|
||||
if ($length_bits < 8) {
|
||||
$b = @$byteArrayRef[$byte];
|
||||
$b = clear_bits($b, $bit, $length_bits);
|
||||
}
|
||||
|
||||
# set bits from given value
|
||||
$b = $b | ($val8 << (8 - $length_bits - $bit));
|
||||
|
||||
@$byteArrayRef[$byte] = $b;
|
||||
}
|
||||
|
||||
# Write UIntValue to data array
|
||||
sub setUInt($$$$)
|
||||
{
|
||||
my ($byteArrayRef, $offset, $length_bits, $value) = @_;
|
||||
|
||||
my $byte = $offset / 8;
|
||||
my $bit = $offset % 8;
|
||||
|
||||
# move bits to the left border
|
||||
$value = $value << (32 - $length_bits);
|
||||
|
||||
# DEBUG print "Moved left: val " . $value . "\r\n";
|
||||
|
||||
# 1st byte
|
||||
my $src_start = 0;
|
||||
my $dst_start = $bit;
|
||||
my $len = min($length_bits, 8 - $bit);
|
||||
my $val8 = get_bits($value, $src_start, $len);
|
||||
|
||||
# DEBUG print " Write bits to byte " . $byte . ", dst_start " . $dst_start . ", len " . $len . ", val8 " . $val8 . "\r\n";
|
||||
|
||||
setUIntBits($byteArrayRef, $byte, $dst_start, $len, $val8);
|
||||
|
||||
$dst_start = 0;
|
||||
$src_start = $len;
|
||||
|
||||
while ($src_start < $length_bits) {
|
||||
$len = min($length_bits - $src_start, 8);
|
||||
$val8 = get_bits($value, $src_start, $len);
|
||||
$byte++;
|
||||
|
||||
# DEBUG print " Byte nr. " . $byte . ", src_start " . $src_start . ", len " . $len . ", val8 " . $val8 . "\r\n";
|
||||
|
||||
setUIntBits($byteArrayRef, $byte, $dst_start, $len, $val8);
|
||||
|
||||
$src_start += $len;
|
||||
}
|
||||
}
|
||||
|
||||
sub getInt($$$)
|
||||
{
|
||||
my ($byteArrayRef, $offset, $length_bits) = @_;
|
||||
|
||||
# FIX ME! DOES NOT WORK WITH NEGATIVE VALUES!
|
||||
|
||||
$x = getUInt($byteArrayRef, $offset, $length_bits);
|
||||
|
||||
# If MSB is 1 (value is negative interpreted as signed int),
|
||||
# set all higher bits also to 1.
|
||||
if ((($x >> ($length_bits - 1)) & 1) == 1) {
|
||||
$x = $x | ~((1 << ($length_bits - 1)) - 1);
|
||||
}
|
||||
|
||||
$y = $x;
|
||||
|
||||
return $y;
|
||||
}
|
||||
|
||||
# ----------- UIntValue class -----------
|
||||
|
||||
package UIntValue;
|
||||
|
||||
sub new
|
||||
{
|
||||
my $class = shift;
|
||||
my $self = {
|
||||
_id => shift,
|
||||
_offset => shift,
|
||||
_bits => shift,
|
||||
};
|
||||
bless $self, $class;
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub getValue
|
||||
{
|
||||
my ($self, $byteArrayRef) = @_;
|
||||
|
||||
return SHC_util::getUInt($byteArrayRef, $self->{_offset}, $self->{_bits});
|
||||
}
|
||||
|
||||
sub setValue
|
||||
{
|
||||
my ($self, $byteArrayRef, $value) = @_;
|
||||
|
||||
SHC_util::setUInt($byteArrayRef, $self->{_offset}, $self->{_bits}, $value);
|
||||
}
|
||||
|
||||
# ----------- IntValue class -----------
|
||||
|
||||
package IntValue;
|
||||
|
||||
sub new
|
||||
{
|
||||
my $class = shift;
|
||||
my $self = {
|
||||
_id => shift,
|
||||
_offset => shift,
|
||||
_bits => shift,
|
||||
};
|
||||
bless $self, $class;
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub getValue
|
||||
{
|
||||
my ($self, $byteArrayRef) = @_;
|
||||
|
||||
return SHC_util::getUInt($byteArrayRef, $self->{_offset}, $self->{_bits});
|
||||
}
|
||||
|
||||
sub setValue
|
||||
{
|
||||
my ($self, $byteArrayRef, $value) = @_;
|
||||
|
||||
SHC_util::setUInt($byteArrayRef, $self->{_offset}, $self->{_bits}, $value);
|
||||
}
|
||||
|
||||
# ----------- BoolValue class -----------
|
||||
|
||||
package BoolValue;
|
||||
|
||||
sub new
|
||||
{
|
||||
my $class = shift;
|
||||
my $self = {
|
||||
_id => shift,
|
||||
_offset => shift,
|
||||
_length => shift,
|
||||
};
|
||||
bless $self, $class;
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub getValue
|
||||
{
|
||||
my ($self, $byteArrayRef, $index) = @_;
|
||||
|
||||
return SHC_util::getUInt($byteArrayRef, $self->{_offset} + $index, 1) == 1 ? 1 : 0;
|
||||
}
|
||||
|
||||
sub setValue
|
||||
{
|
||||
my ($self, $byteArrayRef, $value) = @_;
|
||||
|
||||
return SHC_util::setUInt($byteArrayRef, $self->{_offset}, 1, $value == 0 ? 0 : 1);
|
||||
}
|
||||
|
||||
# ----------- EnumValue class -----------
|
||||
|
||||
package EnumValue;
|
||||
|
||||
my %name2value = ();
|
||||
my %value2name = ();
|
||||
|
||||
sub new
|
||||
{
|
||||
my $class = shift;
|
||||
my $self = {
|
||||
_id => shift,
|
||||
_offset => shift,
|
||||
_bits => shift,
|
||||
};
|
||||
bless $self, $class;
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub addValue
|
||||
{
|
||||
my ($self, $name, $value) = @_;
|
||||
|
||||
$name2value{$name} = $value;
|
||||
$value2name{$value} = $name;
|
||||
}
|
||||
|
||||
sub getValue
|
||||
{
|
||||
my ($self, $byteArrayRef) = @_;
|
||||
|
||||
my $value = SHC_util::getUInt($byteArrayRef, $self->{_offset}, $self->{_bits});
|
||||
return $value2name{$value};
|
||||
}
|
||||
|
||||
sub setValue
|
||||
{
|
||||
my ($self, $byteArrayRef, $name) = @_;
|
||||
|
||||
my $value = $name2value{$name};
|
||||
SHC_util::setUInt($byteArrayRef, $self->{_offset}, $self->{_bits}, $value);
|
||||
}
|
||||
|
||||
1;
|
495
fhem/FHEM/SHC_packet_layout.xml
Normal file
495
fhem/FHEM/SHC_packet_layout.xml
Normal file
@ -0,0 +1,495 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Packet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="packet_metamodel.xsd">
|
||||
<Header>
|
||||
<UIntValue>
|
||||
<ID>CRC32</ID>
|
||||
<Description>CRC32 value of byte 4..n</Description>
|
||||
<Bits>32</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>4294967295</MaxVal>
|
||||
</UIntValue>
|
||||
<UIntValue>
|
||||
<ID>SenderID</ID>
|
||||
<Description>The SenderID is the DeviceID of the sending device. It's only allowed to send packets with the own DeviceID. 0 = base station, others are arbitrary.</Description>
|
||||
<Bits>12</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>4095</MaxVal>
|
||||
</UIntValue>
|
||||
<UIntValue>
|
||||
<ID>PacketCounter</ID>
|
||||
<Description>The PacketCounter is counted up throughout the whole lifetime of the device and is used to make the encrypted packets differently from each other every time. Packets received with the same or lower number must be ignored per SenderID.</Description>
|
||||
<Bits>24</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>16777215</MaxVal>
|
||||
</UIntValue>
|
||||
<EnumValue>
|
||||
<ID>MessageType</ID>
|
||||
<Description>The message type influences the behaviour with sending the packet. E.g. requests are acknowledged.</Description>
|
||||
<Bits>4</Bits>
|
||||
<Element>
|
||||
<Value>0</Value>
|
||||
<Name>Get</Name>
|
||||
</Element>
|
||||
<Element>
|
||||
<Value>1</Value>
|
||||
<Name>Set</Name>
|
||||
</Element>
|
||||
<Element>
|
||||
<Value>2</Value>
|
||||
<Name>SetGet</Name>
|
||||
</Element>
|
||||
<Element>
|
||||
<Value>8</Value>
|
||||
<Name>Status</Name>
|
||||
</Element>
|
||||
<Element>
|
||||
<Value>9</Value>
|
||||
<Name>Ack</Name>
|
||||
</Element>
|
||||
<Element>
|
||||
<Value>10</Value>
|
||||
<Name>AckStatus</Name>
|
||||
</Element>
|
||||
</EnumValue>
|
||||
</Header>
|
||||
<HeaderExtension>
|
||||
<MessageType>0</MessageType>
|
||||
<UIntValue>
|
||||
<ID>ReceiverID</ID>
|
||||
<Description>The ID of the device to process the request. Use 4095 for broadcasts.</Description>
|
||||
<Bits>12</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>4095</MaxVal>
|
||||
</UIntValue>
|
||||
<UIntValue>
|
||||
<ID>MessageGroupID</ID>
|
||||
<Description>0 = Generic, 1..9 = Reserved, others are arbitrary</Description>
|
||||
<Bits>7</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>127</MaxVal>
|
||||
</UIntValue>
|
||||
<UIntValue>
|
||||
<ID>MessageID</ID>
|
||||
<Description/>
|
||||
<Bits>4</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>15</MaxVal>
|
||||
</UIntValue>
|
||||
<ContainsMessageData>false</ContainsMessageData>
|
||||
</HeaderExtension>
|
||||
<HeaderExtension>
|
||||
<MessageType>1</MessageType>
|
||||
<MessageType>2</MessageType>
|
||||
<UIntValue>
|
||||
<ID>ReceiverID</ID>
|
||||
<Description>The ID of the device to process the request. Use 4095 for broadcasts.</Description>
|
||||
<Bits>12</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>4095</MaxVal>
|
||||
</UIntValue>
|
||||
<UIntValue>
|
||||
<ID>MessageGroupID</ID>
|
||||
<Description>0 = Generic, 1..9 = Reserved, others are arbitrary</Description>
|
||||
<Bits>7</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>127</MaxVal>
|
||||
</UIntValue>
|
||||
<UIntValue>
|
||||
<ID>MessageID</ID>
|
||||
<Description/>
|
||||
<Bits>4</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>15</MaxVal>
|
||||
</UIntValue>
|
||||
<ContainsMessageData>true</ContainsMessageData>
|
||||
</HeaderExtension>
|
||||
<HeaderExtension>
|
||||
<MessageType>8</MessageType>
|
||||
<UIntValue>
|
||||
<ID>MessageGroupID</ID>
|
||||
<Description>0 = Generic, 1..9 = Reserved, others are arbitrary</Description>
|
||||
<Bits>7</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>127</MaxVal>
|
||||
</UIntValue>
|
||||
<UIntValue>
|
||||
<ID>MessageID</ID>
|
||||
<Description/>
|
||||
<Bits>4</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>15</MaxVal>
|
||||
</UIntValue>
|
||||
<ContainsMessageData>true</ContainsMessageData>
|
||||
</HeaderExtension>
|
||||
<HeaderExtension>
|
||||
<MessageType>9</MessageType>
|
||||
<UIntValue>
|
||||
<ID>AckSenderID</ID>
|
||||
<Description>The ID of the requestor whose request is acknowledged.</Description>
|
||||
<Bits>12</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>4095</MaxVal>
|
||||
</UIntValue>
|
||||
<UIntValue>
|
||||
<ID>AckPacketCounter</ID>
|
||||
<Description>The PacketCounter of the request that is acknowledged.</Description>
|
||||
<Bits>24</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>16777215</MaxVal>
|
||||
</UIntValue>
|
||||
<BoolValue>
|
||||
<ID>Error</ID>
|
||||
<Description>Tells if there was an error fulfilling the request or not.</Description>
|
||||
</BoolValue>
|
||||
<ContainsMessageData>false</ContainsMessageData>
|
||||
</HeaderExtension>
|
||||
<HeaderExtension>
|
||||
<MessageType>10</MessageType>
|
||||
<UIntValue>
|
||||
<ID>AckSenderID</ID>
|
||||
<Description>The ID of the requestor whose request is acknowledged.</Description>
|
||||
<Bits>12</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>4095</MaxVal>
|
||||
</UIntValue>
|
||||
<UIntValue>
|
||||
<ID>AckPacketCounter</ID>
|
||||
<Description>The PacketCounter of the request that is acknowledged.</Description>
|
||||
<Bits>24</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>16777215</MaxVal>
|
||||
</UIntValue>
|
||||
<BoolValue>
|
||||
<ID>Error</ID>
|
||||
<Description>Tells if there was an error fulfilling the request or not.</Description>
|
||||
</BoolValue>
|
||||
<UIntValue>
|
||||
<ID>MessageGroupID</ID>
|
||||
<Description>0 = Generic, 1..9 = Reserved, others are arbitrary</Description>
|
||||
<Bits>7</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>127</MaxVal>
|
||||
</UIntValue>
|
||||
<UIntValue>
|
||||
<ID>MessageID</ID>
|
||||
<Description/>
|
||||
<Bits>4</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>15</MaxVal>
|
||||
</UIntValue>
|
||||
<ContainsMessageData>true</ContainsMessageData>
|
||||
</HeaderExtension>
|
||||
<MessageGroup>
|
||||
<Name>Generic</Name>
|
||||
<Description>This group contains messages useful for different devices.</Description>
|
||||
<MessageGroupID>0</MessageGroupID>
|
||||
<Message>
|
||||
<Name>Version</Name>
|
||||
<Description>Reports the current firmware version. Version information is only available when set in source code, which is usually only done for official builds by the build robot.</Description>
|
||||
<MessageID>1</MessageID>
|
||||
<MessageType>0</MessageType>
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<UIntValue>
|
||||
<ID>Major</ID>
|
||||
<Description>Different major version means incompatible changes.</Description>
|
||||
<Bits>8</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>255</MaxVal>
|
||||
</UIntValue>
|
||||
<UIntValue>
|
||||
<ID>Minor</ID>
|
||||
<Description>Different minor number means new functionality without breaking compatibility.</Description>
|
||||
<Bits>8</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>255</MaxVal>
|
||||
</UIntValue>
|
||||
<UIntValue>
|
||||
<ID>Patch</ID>
|
||||
<Description>The patch version is changed when backwards-compatible bug fixes are made.</Description>
|
||||
<Bits>8</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>255</MaxVal>
|
||||
</UIntValue>
|
||||
<UIntValue>
|
||||
<ID>Hash</ID>
|
||||
<Description>The beginning of the revision ID hash (as reported by Git).</Description>
|
||||
<Bits>32</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>4294967295</MaxVal>
|
||||
</UIntValue>
|
||||
</Message>
|
||||
<Message>
|
||||
<Name>BatteryStatus</Name>
|
||||
<Description>Tells the current battery status in percent. Please note that the "Get" may not be answered because a device does not listen to requests.</Description>
|
||||
<MessageID>5</MessageID>
|
||||
<MessageType>0</MessageType>
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<UIntValue>
|
||||
<ID>Percentage</ID>
|
||||
<Description>The remaining capacity of the battery from 0 (empty) to 100 (full).</Description>
|
||||
<Bits>7</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>100</MaxVal>
|
||||
</UIntValue>
|
||||
</Message>
|
||||
</MessageGroup>
|
||||
<MessageGroup>
|
||||
<Name>GPIO</Name>
|
||||
<Description>This group contains messages for general I/O functions. The meaning of the values is not known to the SHC devices. It depends on the connected parts (e.g. switches). The values have to be processed by the user/server software appropriately.</Description>
|
||||
<MessageGroupID>1</MessageGroupID>
|
||||
<Message>
|
||||
<Name>DigitalPin</Name>
|
||||
<Description>This is the state of up to 8 pins.</Description>
|
||||
<MessageID>1</MessageID>
|
||||
<MessageType>0</MessageType>
|
||||
<MessageType>1</MessageType>
|
||||
<MessageType>2</MessageType>
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>9</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<Array>
|
||||
<Length>8</Length>
|
||||
<BoolValue>
|
||||
<ID>On</ID>
|
||||
<Description>Tells if the pin is on (at high level) or not (low level).</Description>
|
||||
</BoolValue>
|
||||
</Array>
|
||||
</Message>
|
||||
</MessageGroup>
|
||||
<MessageGroup>
|
||||
<Name>Weather</Name>
|
||||
<Description>This message group contains messages for weather data.</Description>
|
||||
<MessageGroupID>10</MessageGroupID>
|
||||
<Message>
|
||||
<Name>Temperature</Name>
|
||||
<Description>This is a message containing temperature only.</Description>
|
||||
<MessageID>1</MessageID>
|
||||
<MessageType>0</MessageType>
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<IntValue>
|
||||
<ID>Temperature</ID>
|
||||
<Description>temperature [1/100 degree celsius], -50°C = -5000, 50°C = 5000</Description>
|
||||
<Bits>16</Bits>
|
||||
<MinVal>-32768</MinVal>
|
||||
<MaxVal>32767</MaxVal>
|
||||
</IntValue>
|
||||
</Message>
|
||||
<Message>
|
||||
<Name>HumidityTemperature</Name>
|
||||
<Description>This is a message containing humidity and temperature.</Description>
|
||||
<MessageID>2</MessageID>
|
||||
<MessageType>0</MessageType>
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<UIntValue>
|
||||
<ID>Humidity</ID>
|
||||
<Description>relative humidity permill, 0..1000 (other values not defined)</Description>
|
||||
<Bits>10</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>1000</MaxVal>
|
||||
</UIntValue>
|
||||
<IntValue>
|
||||
<ID>Temperature</ID>
|
||||
<Description>temperature [1/100 degree celsius], -50°C = -5000, 50°C = 5000</Description>
|
||||
<Bits>16</Bits>
|
||||
<MinVal>-32768</MinVal>
|
||||
<MaxVal>32767</MaxVal>
|
||||
</IntValue>
|
||||
</Message>
|
||||
<Message>
|
||||
<Name>BarometricPressureTemperature</Name>
|
||||
<Description>This is a message containing barometric pressure and temperature.</Description>
|
||||
<MessageID>3</MessageID>
|
||||
<MessageType>0</MessageType>
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<UIntValue>
|
||||
<ID>BarometricPressure</ID>
|
||||
<Description>barometric pressure in pascal</Description>
|
||||
<Bits>17</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>131071</MaxVal>
|
||||
</UIntValue>
|
||||
<IntValue>
|
||||
<ID>Temperature</ID>
|
||||
<Description>temperature [1/100 degree celsius], -50°C = -5000, 50°C = 5000</Description>
|
||||
<Bits>16</Bits>
|
||||
<MinVal>-32768</MinVal>
|
||||
<MaxVal>32767</MaxVal>
|
||||
</IntValue>
|
||||
</Message>
|
||||
</MessageGroup>
|
||||
<MessageGroup>
|
||||
<Name>Environment</Name>
|
||||
<Description>This message group contains messages for environmental data except weather data.</Description>
|
||||
<MessageGroupID>11</MessageGroupID>
|
||||
<Message>
|
||||
<Name>Brightness</Name>
|
||||
<Description/>
|
||||
<MessageID>1</MessageID>
|
||||
<MessageType>0</MessageType>
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<UIntValue>
|
||||
<ID>Brightness</ID>
|
||||
<Description>brightness in percent</Description>
|
||||
<Bits>7</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>100</MaxVal>
|
||||
</UIntValue>
|
||||
</Message>
|
||||
<Message>
|
||||
<Name>Distance</Name>
|
||||
<Description/>
|
||||
<MessageID>2</MessageID>
|
||||
<MessageType>0</MessageType>
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<UIntValue>
|
||||
<ID>Distance</ID>
|
||||
<Description>distance in cm</Description>
|
||||
<Bits>14</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>16383</MaxVal>
|
||||
</UIntValue>
|
||||
</Message>
|
||||
</MessageGroup>
|
||||
<MessageGroup>
|
||||
<Name>PowerSwitch</Name>
|
||||
<Description/>
|
||||
<MessageGroupID>20</MessageGroupID>
|
||||
<Message>
|
||||
<Name>SwitchState</Name>
|
||||
<Description>This is the state of the relais and its timeout value.</Description>
|
||||
<MessageID>1</MessageID>
|
||||
<MessageType>0</MessageType>
|
||||
<MessageType>1</MessageType>
|
||||
<MessageType>2</MessageType>
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>9</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<BoolValue>
|
||||
<ID>On</ID>
|
||||
<Description>Tells if the switch is on (active).</Description>
|
||||
</BoolValue>
|
||||
<UIntValue>
|
||||
<ID>TimeoutSec</ID>
|
||||
<Description>The time after which the switch is automatically toggled again. Use 0 to disable this.</Description>
|
||||
<Bits>16</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>65535</MaxVal>
|
||||
</UIntValue>
|
||||
</Message>
|
||||
<Message>
|
||||
<Name>SwitchStateExt</Name>
|
||||
<Description>This is the state of up to 8 relais and its timeout values.</Description>
|
||||
<MessageID>2</MessageID>
|
||||
<MessageType>0</MessageType>
|
||||
<MessageType>1</MessageType>
|
||||
<MessageType>2</MessageType>
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>9</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<Array>
|
||||
<Length>8</Length>
|
||||
<BoolValue>
|
||||
<ID>On</ID>
|
||||
<Description/>
|
||||
</BoolValue>
|
||||
</Array>
|
||||
<Array>
|
||||
<Length>8</Length>
|
||||
<UIntValue>
|
||||
<ID>TimeoutSec</ID>
|
||||
<Description>The time after which the switch is automatically toggled again. Use 0 to disable this.</Description>
|
||||
<Bits>16</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>65535</MaxVal>
|
||||
</UIntValue>
|
||||
</Array>
|
||||
</Message>
|
||||
</MessageGroup>
|
||||
<MessageGroup>
|
||||
<Name>Dimmer</Name>
|
||||
<Description/>
|
||||
<MessageGroupID>60</MessageGroupID>
|
||||
<Message>
|
||||
<Name>Brightness</Name>
|
||||
<Description>This is to set a fixed brightness.</Description>
|
||||
<MessageID>1</MessageID>
|
||||
<MessageType>0</MessageType>
|
||||
<MessageType>1</MessageType>
|
||||
<MessageType>2</MessageType>
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>9</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<UIntValue>
|
||||
<ID>Brightness</ID>
|
||||
<Description>The brightness in percent. 0 = Off.</Description>
|
||||
<Bits>7</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>100</MaxVal>
|
||||
</UIntValue>
|
||||
</Message>
|
||||
<Message>
|
||||
<Name>Animation</Name>
|
||||
<Description>This is the state of the dimmer output voltage and its timeout value.</Description>
|
||||
<MessageID>2</MessageID>
|
||||
<MessageType>0</MessageType>
|
||||
<MessageType>1</MessageType>
|
||||
<MessageType>2</MessageType>
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>9</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<EnumValue>
|
||||
<ID>AnimationMode</ID>
|
||||
<Description>If a time is set, use this animation mode to change the brightness over time (none = leave at start state for the whole time and switch to end state at the end).</Description>
|
||||
<Bits>2</Bits>
|
||||
<Element>
|
||||
<Value>0</Value>
|
||||
<Name>none</Name>
|
||||
</Element>
|
||||
<Element>
|
||||
<Value>1</Value>
|
||||
<Name>linear</Name>
|
||||
</Element>
|
||||
</EnumValue>
|
||||
<UIntValue>
|
||||
<ID>TimeoutSec</ID>
|
||||
<Description>The time for the animation. Use 0 to disable this.</Description>
|
||||
<Bits>16</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>65535</MaxVal>
|
||||
</UIntValue>
|
||||
<UIntValue>
|
||||
<ID>StartBrightness</ID>
|
||||
<Description>The brightness in percent at the beginning of the animation.</Description>
|
||||
<Bits>7</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>100</MaxVal>
|
||||
</UIntValue>
|
||||
<UIntValue>
|
||||
<ID>EndBrightness</ID>
|
||||
<Description>The brightness in percent at the end of the animation.</Description>
|
||||
<Bits>7</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>100</MaxVal>
|
||||
</UIntValue>
|
||||
</Message>
|
||||
</MessageGroup>
|
||||
</Packet>
|
334
fhem/FHEM/SHC_parser.pm
Normal file
334
fhem/FHEM/SHC_parser.pm
Normal file
@ -0,0 +1,334 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
##########################################################################
|
||||
# This file is part of the smarthomatic module for FHEM.
|
||||
#
|
||||
# Copyright (c) 2014 Uwe Freese
|
||||
#
|
||||
# You can find smarthomatic at www.smarthomatic.org.
|
||||
# You can find FHEM at www.fhem.de.
|
||||
#
|
||||
# This file is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the
|
||||
# Free Software Foundation, either version 3 of the License, or (at your
|
||||
# option) any later version.
|
||||
#
|
||||
# This file is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
# Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with smarthomatic. If not, see <http://www.gnu.org/licenses/>.
|
||||
##########################################################################
|
||||
# Usage:
|
||||
#
|
||||
# Init parser:
|
||||
# ------------
|
||||
# my $parser = new SHC_parser();
|
||||
#
|
||||
# Receiving packets:
|
||||
# ------------------
|
||||
# 1.) Receive string from base station (over UART).
|
||||
# 2.) Parse received string:
|
||||
# $parser->parse("Packet Data: SenderID=22;...");
|
||||
# 3.) Get MessageGroupName: my $grp = $parser->getMessageGroupName();
|
||||
# 4.) Get MessageName: my $msg = $parser->getMessageName();
|
||||
# 5.) Get data fields depending on MessageGroupname and MessageName, e.g.
|
||||
# $val = $parser->getField("Temperature");
|
||||
#
|
||||
# Sending packets:
|
||||
# ----------------
|
||||
# 1.) Init packet:
|
||||
# $parser->initPacket("PowerSwitch", "SwitchState", "Set");
|
||||
# 2.) Set fields:
|
||||
# $parser->setField("PowerSwitch", "SwitchState", "TimeoutSec", 8);
|
||||
# 3.) Get send string: $str = $parser->getSendString($receiverID);
|
||||
# 4.) Send string to base station (over UART).
|
||||
##########################################################################
|
||||
# $Id: 37_SHC_Dev.pm xxxx 2014-xx-xx xx:xx:xx rr2000 $
|
||||
|
||||
package SHC_parser;
|
||||
|
||||
use strict;
|
||||
use feature qw(switch);
|
||||
use XML::LibXML;
|
||||
use SHC_datafields;
|
||||
|
||||
# Hash for data field definitions.
|
||||
my %dataFields = ();
|
||||
|
||||
# Hashes used to translate between names and IDs.
|
||||
my %messageTypeID2messageTypeName = ();
|
||||
my %messageTypeName2messageTypeID = ();
|
||||
|
||||
my %messageGroupID2messageGroupName = ();
|
||||
my %messageGroupName2messageGroupID = ();
|
||||
|
||||
my %messageID2messageName = ();
|
||||
my %messageName2messageID = ();
|
||||
|
||||
my %messageID2bits = ();
|
||||
|
||||
# byte array to store data to send
|
||||
my @msgData = ();
|
||||
my $sendMode = 0;
|
||||
|
||||
sub new
|
||||
{
|
||||
my $class = shift;
|
||||
init_datafield_positions();
|
||||
my $self = {
|
||||
_senderID => 0,
|
||||
_packetCounter => 0,
|
||||
_messageTypeID => 0,
|
||||
_messageGroupID => 0,
|
||||
_messageGroupName => "",
|
||||
_messageID => 0,
|
||||
_messageName => "",
|
||||
_messageData => "",
|
||||
};
|
||||
bless $self, $class;
|
||||
return $self;
|
||||
}
|
||||
|
||||
# Read packet layout from XML file and remember the defined MessageGroups,
|
||||
# Messages and data fields (incl. positions, length).
|
||||
sub init_datafield_positions()
|
||||
{
|
||||
my $x = XML::LibXML->new() or die "new on XML::LibXML failed";
|
||||
my $d = $x->parse_file("FHEM/SHC_packet_layout.xml") or die "parsing XML file failed";
|
||||
|
||||
for my $element ($d->findnodes("/Packet/Header/EnumValue[ID='MessageType']/Element")) {
|
||||
my $value = ($element->findnodes("Value"))[0]->textContent;
|
||||
my $name = ($element->findnodes("Name"))[0]->textContent;
|
||||
|
||||
$messageTypeID2messageTypeName{$value} = $name;
|
||||
$messageTypeName2messageTypeID{$name} = $value;
|
||||
}
|
||||
|
||||
for my $messageGroup ($d->findnodes("/Packet/MessageGroup")) {
|
||||
my $messageGroupName = ($messageGroup->findnodes("Name"))[0]->textContent;
|
||||
my $messageGroupID = ($messageGroup->findnodes("MessageGroupID"))[0]->textContent;
|
||||
|
||||
$messageGroupID2messageGroupName{$messageGroupID} = $messageGroupName;
|
||||
$messageGroupName2messageGroupID{$messageGroupName} = $messageGroupID;
|
||||
|
||||
for my $message ($messageGroup->findnodes("Message")) {
|
||||
my $messageName = ($message->findnodes("Name"))[0]->textContent;
|
||||
my $messageID = ($message->findnodes("MessageID"))[0]->textContent;
|
||||
|
||||
$messageID2messageName{$messageGroupID . "-" . $messageID} = $messageName;
|
||||
$messageName2messageID{$messageGroupName . "-" . $messageName} = $messageID;
|
||||
|
||||
my $offset = 0;
|
||||
my $arrayLength = 1;
|
||||
|
||||
for my $field ($message->findnodes("Array|UIntValue|IntValue|BoolValue|EnumValue")) {
|
||||
|
||||
# When an array is detected, remember the array length and change the current field node
|
||||
# to the inner node for further processing.
|
||||
if ($field->nodeName eq 'Array') {
|
||||
$arrayLength = int(($field->findnodes("Length"))[0]->textContent);
|
||||
# DEBUG print "Next field is an array with " . $arrayLength . " elements!\n";
|
||||
|
||||
$field = ($field->findnodes("UIntValue|IntValue|BoolValue|EnumValue"))[0];
|
||||
}
|
||||
|
||||
given ($field->nodeName) {
|
||||
when ('UIntValue') {
|
||||
my $id = ($field->findnodes("ID"))[0]->textContent;
|
||||
my $bits = ($field->findnodes("Bits"))[0]->textContent;
|
||||
|
||||
# DEBUG print "Data field " . $id . " starts at " . $offset . " with " . $bits . " bits.\n";
|
||||
|
||||
$dataFields{$messageGroupID . "-" . $messageID . "-" . $id} = new UIntValue($id, $offset, $bits);
|
||||
|
||||
$offset += $bits * $arrayLength;
|
||||
}
|
||||
|
||||
when ('IntValue') {
|
||||
my $id = ($field->findnodes("ID"))[0]->textContent;
|
||||
my $bits = ($field->findnodes("Bits"))[0]->textContent;
|
||||
|
||||
# DEBUG print "Data field " . $id . " starts at " . $offset . " with " . $bits . " bits.\n";
|
||||
|
||||
$dataFields{$messageGroupID . "-" . $messageID . "-" . $id} = new IntValue($id, $offset, $bits);
|
||||
|
||||
$offset += $bits * $arrayLength;
|
||||
}
|
||||
|
||||
when ('BoolValue') {
|
||||
my $id = ($field->findnodes("ID"))[0]->textContent;
|
||||
my $bits = 1;
|
||||
|
||||
# DEBUG print "Data field " . $id . " starts at " . $offset . " with " . $bits . " bits.\n";
|
||||
|
||||
$dataFields{$messageGroupID . "-" . $messageID . "-" . $id} = new BoolValue($id, $offset, $arrayLength);
|
||||
|
||||
$offset += $bits * $arrayLength;
|
||||
}
|
||||
|
||||
when ('EnumValue') {
|
||||
my $id = ($field->findnodes("ID"))[0]->textContent;
|
||||
my $bits = ($field->findnodes("Bits"))[0]->textContent;
|
||||
|
||||
# DEBUG print "Data field " . $id . " starts at " . $offset . " with " . $bits . " bits.\n";
|
||||
|
||||
my $object = new EnumValue($id, $offset, $bits);
|
||||
$dataFields{$messageGroupID . "-" . $messageID . "-" . $id} = $object;
|
||||
|
||||
for my $element ($field->findnodes("Element")) {
|
||||
my $value = ($element->findnodes("Value"))[0]->textContent;
|
||||
my $name = ($element->findnodes("Name"))[0]->textContent;
|
||||
|
||||
$object->addValue($name, $value);
|
||||
}
|
||||
|
||||
$offset += $bits * $arrayLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$messageID2bits{$messageGroupID . "-" . $messageID} = $offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub parse
|
||||
{
|
||||
my ($self, $msg) = @_;
|
||||
|
||||
$sendMode = 0;
|
||||
|
||||
if (
|
||||
(
|
||||
$msg =~
|
||||
/^Packet Data: SenderID=(\d*);PacketCounter=(\d*);MessageType=(\d*);MessageGroupID=(\d*);MessageID=(\d*);MessageData=([^;]*);.*/
|
||||
)
|
||||
|| ($msg =~
|
||||
/^Packet Data: SenderID=(\d*);PacketCounter=(\d*);MessageType=(\d*);AckSenderID=\d*;AckPacketCounter=\d*;Error=\d*;MessageGroupID=(\d*);MessageID=(\d*);MessageData=([^;]*);.*/
|
||||
)
|
||||
)
|
||||
{
|
||||
$self->{_senderID} = $1;
|
||||
$self->{_packetCounter} = $2;
|
||||
$self->{_messageTypeID} = $3;
|
||||
$self->{_messageGroupID} = $4;
|
||||
$self->{_messageID} = $5;
|
||||
$self->{_messageData} = $6;
|
||||
}
|
||||
|
||||
else {
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
sub getSenderID
|
||||
{
|
||||
my ($self) = @_;
|
||||
return $self->{_senderID};
|
||||
}
|
||||
|
||||
sub getPacketCounter
|
||||
{
|
||||
my ($self) = @_;
|
||||
return $self->{_packetCounter};
|
||||
}
|
||||
|
||||
sub getMessageTypeName
|
||||
{
|
||||
my ($self) = @_;
|
||||
return $messageTypeID2messageTypeName{$self->{_messageTypeID}};
|
||||
}
|
||||
|
||||
sub getMessageGroupName
|
||||
{
|
||||
my ($self) = @_;
|
||||
return $messageGroupID2messageGroupName{$self->{_messageGroupID}};
|
||||
}
|
||||
|
||||
sub getMessageName
|
||||
{
|
||||
my ($self) = @_;
|
||||
return $messageID2messageName{$self->{_messageGroupID} . "-" . $self->{_messageID}};
|
||||
}
|
||||
|
||||
sub getMessageData
|
||||
{
|
||||
my ($self) = @_;
|
||||
|
||||
if ($sendMode) {
|
||||
my $res = "";
|
||||
|
||||
foreach (@msgData) {
|
||||
$res .= sprintf("%02X", $_);
|
||||
}
|
||||
|
||||
return $res;
|
||||
} else {
|
||||
return $self->{_messageData};
|
||||
}
|
||||
}
|
||||
|
||||
sub getField
|
||||
{
|
||||
my ($self, $fieldName, $index) = @_;
|
||||
|
||||
my $obj = $dataFields{$self->{_messageGroupID} . "-" . $self->{_messageID} . "-" . $fieldName};
|
||||
my @tmpArray = map hex("0x$_"), $self->{_messageData} =~ /(..)/g;
|
||||
|
||||
return $obj->getValue(\@tmpArray, $index);
|
||||
}
|
||||
|
||||
sub initPacket
|
||||
{
|
||||
my ($self, $messageGroupName, $messageName, $messageTypeName) = @_;
|
||||
|
||||
$self->{_senderID} = 0; # base station SenderID
|
||||
$self->{_messageTypeID} = $messageTypeName2messageTypeID{$messageTypeName};
|
||||
$self->{_messageGroupID} = $messageGroupName2messageGroupID{$messageGroupName};
|
||||
$self->{_messageID} = $messageName2messageID{$messageGroupName . "-" . $messageName};
|
||||
|
||||
my $lenBytes = $messageID2bits{$self->{_messageGroupID} . "-" . $self->{_messageID}} / 8;
|
||||
|
||||
@msgData = 0 x $lenBytes;
|
||||
|
||||
$sendMode = 1;
|
||||
}
|
||||
|
||||
sub setField
|
||||
{
|
||||
my ($self, $messageGroupName, $messageName, $fieldName, $value) = @_;
|
||||
|
||||
my $gID = $messageGroupName2messageGroupID{$messageGroupName};
|
||||
my $mID = $messageName2messageID{$messageGroupName . "-" . $messageName};
|
||||
|
||||
my $obj = $dataFields{$gID . "-" . $mID . "-" . $fieldName};
|
||||
|
||||
$obj->setValue(\@msgData, $value);
|
||||
}
|
||||
|
||||
# sKK01RRRRGGMMDD
|
||||
# s0001003D3C0164 = SET Dimmer Switch Brightness 50%
|
||||
sub getSendString
|
||||
{
|
||||
my ($self, $receiverID, $aesKeyNr) = @_;
|
||||
|
||||
# Right now the only way to set the AES key is by defining in in fhem.cfg
|
||||
# "define SHC_Dev_xx SHC_Dev xx aa" where xx = deviceID, aa = AES key
|
||||
#
|
||||
# TODO: Where to enter the AES key number? This is by device.
|
||||
# Add lookup table device -> AES key?
|
||||
# Automatically gather used AES key after reception from device?
|
||||
|
||||
my $s = "s"
|
||||
. sprintf("%02X", $aesKeyNr)
|
||||
. sprintf("%02X", $self->{_messageTypeID})
|
||||
. sprintf("%04X", $receiverID)
|
||||
. sprintf("%02X", $self->{_messageGroupID})
|
||||
. sprintf("%02X", $self->{_messageID})
|
||||
. getMessageData();
|
||||
}
|
||||
|
||||
1;
|
@ -575,3 +575,7 @@
|
||||
into readingsname, value and unit
|
||||
- added new module contrib/97_SprinkleControl.pm
|
||||
- added new module contrib/98_Sprinkle.pm
|
||||
|
||||
- Thu Jun 12 2014 (rr2000)
|
||||
- added new module 37_SHC.pm
|
||||
- added new module 37_SHCdev.pm to support smarthomatic devices
|
||||
|
@ -116,6 +116,8 @@ FHEM/36_PCA301.pm justme1968 http://forum.fhem.de Sonstige
|
||||
FHEM/36_LaCrosse.pm justme1968 http://forum.fhem.de Sonstige Systeme
|
||||
FHEM/36_EMT7110.pm HCS http://forum.fhem.de Sonstige Systeme
|
||||
FHEM/36_Level.pm HCS http://forum.fhem.de Sonstige Systeme
|
||||
FHEM/37_SHC.pm rr2000 http://forum.fhem.de Sonstige Systeme
|
||||
FHEM/37_SHCdev.pm rr2000 http://forum.fhem.de Sonstige Systeme
|
||||
FHEM/38_CO20.pm justme1968 http://forum.fhem.de Sonstiges
|
||||
FHEM/40_RFXCOM.pm wherzig http://forum.fhem.de RFXTRX
|
||||
FHEM/41_OREGON.pm wherzig http://forum.fhem.de Sonstiges
|
||||
|
Loading…
x
Reference in New Issue
Block a user