| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621 |
- # $Id: 34_panStamp.pm 12056 2016-08-22 19:30:31Z justme1968 $
- package main;
- use strict;
- use warnings;
- use Time::HiRes qw(gettimeofday);
- sub panStamp_Attr(@);
- sub panStamp_Clear($);
- sub panStamp_HandleWriteQueue($);
- sub panStamp_Parse($$$$);
- sub panStamp_Read($);
- sub panStamp_ReadAnswer($$$$);
- sub panStamp_Ready($);
- sub panStamp_Write($$$);
- sub panStamp_SimpleWrite(@);
- my $clientsPanStamp = ":SWAP:";
- my %matchListSWAP = (
- "1:SWAP" => "^.*",
- );
- sub
- panStamp_Initialize($)
- {
- my ($hash) = @_;
- require "$attr{global}{modpath}/FHEM/DevIo.pm";
- # Provider
- $hash->{ReadFn} = "panStamp_Read";
- $hash->{WriteFn} = "panStamp_Write";
- $hash->{ReadyFn} = "panStamp_Ready";
- # Normal devices
- $hash->{DefFn} = "panStamp_Define";
- $hash->{FingerprintFn} = "panStamp_Fingerprint";
- $hash->{UndefFn} = "panStamp_Undef";
- #$hash->{GetFn} = "panStamp_Get";
- $hash->{SetFn} = "panStamp_Set";
- #$hash->{AttrFn} = "panStamp_Attr";
- $hash->{AttrList}= "dummy:1,0";
- $hash->{ShutdownFn} = "panStamp_Shutdown";
- }
- sub
- panStamp_Fingerprint($$)
- {
- }
- #####################################
- sub
- panStamp_Define($$)
- {
- my ($hash, $def) = @_;
- my @a = split("[ \t][ \t]*", $def);
- if(@a < 3 || @a > 6) {
- my $msg = "wrong syntax: define <name> panStamp {none | devicename[\@baudrate] ".
- "| devicename\@directio | hostname:port} [<address> [<channel> [<syncword>]]]";
- Log3 undef, 2, $msg;
- return $msg;
- }
- my $address = $a[3];
- $address = "01" if( !defined($address) );
- my $channel = $a[4];
- $channel = "00" if( !defined($channel) );
- my $syncword = $a[5];
- $syncword = 'B547' if( !defined($syncword) );
- return "$address is not a 1 byte hex value" if( $address !~ /^[\da-f]{2}$/i );
- return "$address is not an allowed address" if( $address eq "00" );
- return "$channel is not a 1 byte hex value" if( $channel !~ /^[\da-f]{2}$/i );
- return "$syncword is not a 2 byte hex value" if( $syncword !~ /^[\da-f]{4}$/i );
- DevIo_CloseDev($hash);
- $hash->{Clients} = $clientsPanStamp;
- $hash->{MatchList} = \%matchListSWAP;
- my $name = $a[0];
- my $dev = $a[2];
- if($dev eq "none") {
- Log3 $name, 1, "$name device is none, commands will be echoed only";
- $attr{$name}{dummy} = 1;
- return undef;
- }
- $dev .= "\@38400" if( $dev !~ m/\@/ && $dev !~ m/:/ );
- $hash->{address} = uc($address);
- $hash->{channel} = uc($channel);
- $hash->{syncword} = uc($syncword);
- $hash->{nonce} = 0;
- $hash->{DeviceName} = $dev;
- my $ret = DevIo_OpenDev($hash, 0, "panStamp_DoInit");
- return $ret;
- }
- #####################################
- sub
- panStamp_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, "deleting port for $d";
- delete $defs{$d}{IODev};
- }
- }
- panStamp_Shutdown($hash);
- DevIo_CloseDev($hash);
- return undef;
- }
- #####################################
- sub
- panStamp_Shutdown($)
- {
- my ($hash) = @_;
- ###panStamp_SimpleWrite($hash, "X00");
- return undef;
- }
- #####################################
- sub
- panStamp_Set($@)
- {
- my ($hash, @a) = @_;
- my $name = shift @a;
- my $cmd = shift @a;
- my $arg = join("", @a);
- my $list = "discover raw";
- return $list if( $cmd eq '?' );
- if($cmd eq "raw") {
- return "\"set panStamp $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, "set $name $cmd $arg";
- panStamp_SimpleWrite($hash, $arg);
- } elsif($cmd eq "discover") {
- Log3 $name, 4, "set $name $cmd";
- panStamp_SimpleWrite($hash, "00".$hash->{address}."0000010000" );
- } else {
- return "Unknown argument $cmd, choose one of ".$list;
- }
- return undef;
- }
- #####################################
- sub
- panStamp_Get($@)
- {
- my ($hash, @a) = @_;
- my $name = $hash->{NAME};
- return "No $a[1] for dummies" if(IsDummy($name));
- #$hash->{READINGS}{$a[1]}{VAL} = $msg;
- $hash->{READINGS}{$a[1]}{TIME} = TimeNow();
- #return "$a[0] $a[1] => $msg";
- }
- sub
- panStamp_Clear($)
- {
- my $hash = shift;
- # Clear the pipe
- $hash->{RA_Timeout} = 0.1;
- for(;;) {
- my ($err, undef) = panStamp_ReadAnswer($hash, "Clear", 0, undef);
- last if($err && $err =~ m/^Timeout/);
- }
- delete($hash->{RA_Timeout});
- }
- #####################################
- sub
- panStamp_DoInit($)
- {
- my $hash = shift;
- my $name = $hash->{NAME};
- my $err;
- my $msg = undef;
- my $val;
- panStamp_Clear($hash);
- panStamp_ReadAnswer($hash, "ready?", 0, undef);
- panStamp_SimpleWrite($hash, "+++", 1 );
- sleep 2;
- panStamp_ReadAnswer($hash, "cmd mode?", 0, undef);
- panStamp_SimpleWrite($hash, "ATHV?" );
- ($err, $val) = panStamp_ReadAnswer($hash, "HW Version", 0, undef);
- return "$name: $err" if($err && ($err !~ m/Timeout/));
- $hash->{HWVersion} = $val;
- panStamp_SimpleWrite($hash, "ATFV?" );
- ($err, $val) = panStamp_ReadAnswer($hash, "FW Version", 0, undef);
- return "$name: $err" if($err && ($err !~ m/Timeout/));
- $hash->{FWVersion} = $val;
- panStamp_SimpleWrite($hash, "ATSW=$hash->{syncword}" );
- ($err, $val) = panStamp_ReadAnswer($hash, "sync word", 0, undef);
- return "$name: $err" if($err && ($err !~ m/Timeout/));
- panStamp_SimpleWrite($hash, "ATSW?" );
- ($err, $val) = panStamp_ReadAnswer($hash, "sync word", 0, undef);
- return "$name: $err" if($err && ($err !~ m/Timeout/));
- $hash->{syncword} = sprintf( "%04s", $val );
- panStamp_SimpleWrite($hash, "ATCH=$hash->{channel}" );
- ($err, $val) = panStamp_ReadAnswer($hash, "channel", 0, undef);
- return "$name: $err" if($err && ($err !~ m/Timeout/));
- panStamp_SimpleWrite($hash, "ATCH?" );
- ($err, $val) = panStamp_ReadAnswer($hash, "channel", 0, undef);
- return "$name: $err" if($err && ($err !~ m/Timeout/));
- $hash->{channel} = sprintf( "%02s", $val);
- panStamp_SimpleWrite($hash, "ATDA=$hash->{address}" );
- ($err, $val) = panStamp_ReadAnswer($hash, "address", 0, undef);
- return "$name: $err" if($err && ($err !~ m/Timeout/));
- panStamp_SimpleWrite($hash, "ATDA?" );
- ($err, $val) = panStamp_ReadAnswer($hash, "address", 0, undef);
- return "$name: $err" if($err && ($err !~ m/Timeout/));
- $hash->{address} = sprintf( "%02s", $val);
- panStamp_SimpleWrite($hash, "ATO" );
- panStamp_ReadAnswer($hash, "data mode?", 0, undef);
- panStamp_SimpleWrite($hash, "00".$hash->{address}."0000010000" );
- readingsSingleUpdate($hash, "state", "initialized", 1);
- # Reset the counter
- delete($hash->{XMIT_TIME});
- delete($hash->{NR_CMD_LAST_H});
- return undef;
- }
- #####################################
- # This is a direct read for commands like get
- # Anydata is used by read file to get the filesize
- sub
- panStamp_ReadAnswer($$$$)
- {
- my ($hash, $arg, $anydata, $regexp) = @_;
- my $type = $hash->{TYPE};
- 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("panStamp_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, "panStamp/RAW (ReadAnswer): $buf";
- $mpandata .= $buf;
- }
- chop($mpandata);
- chop($mpandata);
- return (undef, $mpandata)
- }
- }
- #####################################
- # Check if the 1% limit is reached and trigger notifies
- sub
- panStamp_XmitLimitCheck($$)
- {
- my ($hash,$fn) = @_;
- my $now = time();
- if(!$hash->{XMIT_TIME}) {
- $hash->{XMIT_TIME}[0] = $now;
- $hash->{NR_CMD_LAST_H} = 1;
- return;
- }
- my $nowM1h = $now-3600;
- my @b = grep { $_ > $nowM1h } @{$hash->{XMIT_TIME}};
- if(@b > 163) { # 163 comes from fs20. todo: verify if correct for panstamp modulation
- my $name = $hash->{NAME};
- Log3 $name, 2, "panStamp TRANSMIT LIMIT EXCEEDED";
- DoTrigger($name, "TRANSMIT LIMIT EXCEEDED");
- } else {
- push(@b, $now);
- }
- $hash->{XMIT_TIME} = \@b;
- $hash->{NR_CMD_LAST_H} = int(@b);
- }
- #####################################
- sub
- panStamp_Write($$$)
- {
- my ($hash,$addr,$msg) = @_;
- my $name = $hash->{NAME};
- Log3 $name, 5, "$name sending $msg";
- my $bstring = $addr.$hash->{address}.$msg;
- panStamp_AddQueue($hash, $bstring);
- #panStamp_SimpleWrite($hash, $bstring);
- }
- sub
- panStamp_SendFromQueue($$)
- {
- my ($hash, $bstring) = @_;
- my $name = $hash->{NAME};
- my $to = 0.05;
- if($bstring ne "") {
- my $sp = AttrVal($name, "sendpool", undef);
- if($sp) { # Is one of the panStamp-fellows sending data?
- my @fellows = split(",", $sp);
- foreach my $f (@fellows) {
- if($f ne $name &&
- $defs{$f} &&
- $defs{$f}{QUEUE} &&
- $defs{$f}{QUEUE}->[0] ne "")
- {
- unshift(@{$hash->{QUEUE}}, "");
- InternalTimer(gettimeofday()+$to, "panStamp_HandleWriteQueue", $hash, 0);
- return;
- }
- }
- }
- panStamp_XmitLimitCheck($hash,$bstring);
- panStamp_SimpleWrite($hash, $bstring);
- }
- InternalTimer(gettimeofday()+$to, "panStamp_HandleWriteQueue", $hash, 0);
- }
- sub
- panStamp_AddQueue($$)
- {
- my ($hash, $bstring) = @_;
- if(!$hash->{QUEUE}) {
- $hash->{QUEUE} = [ $bstring ];
- panStamp_SendFromQueue($hash, $bstring);
- } else {
- push(@{$hash->{QUEUE}}, $bstring);
- }
- }
- #####################################
- sub
- panStamp_HandleWriteQueue($)
- {
- my $hash = shift;
- my $arr = $hash->{QUEUE};
- if(defined($arr) && @{$arr} > 0) {
- shift(@{$arr});
- if(@{$arr} == 0) {
- delete($hash->{QUEUE});
- return;
- }
- my $bstring = $arr->[0];
- if($bstring eq "") {
- panStamp_HandleWriteQueue($hash);
- } else {
- panStamp_SendFromQueue($hash, $bstring);
- }
- }
- }
- #####################################
- # called from the global loop, when the select for hash->{FD} reports data
- sub
- panStamp_Read($)
- {
- my ($hash) = @_;
- my $buf = DevIo_SimpleRead($hash);
- return "" if(!defined($buf));
- my $name = $hash->{NAME};
- my $pandata = $hash->{PARTIAL};
- Log3 $name, 5, "panStamp/RAW: $pandata/$buf";
- $pandata .= $buf;
- while($pandata =~ m/\n/) {
- my $rmsg;
- ($rmsg,$pandata) = split("\n", $pandata, 2);
- $rmsg =~ s/\r//;
- panStamp_Parse($hash, $hash, $name, $rmsg) if($rmsg);
- }
- $hash->{PARTIAL} = $pandata;
- }
- sub
- panStamp_Parse($$$$)
- {
- my ($hash, $iohash, $name, $rmsg) = @_;
- my $dmsg = $rmsg;
- my $l = length($dmsg);
- my $rssi = hex(substr($dmsg, 1, 2));
- $rssi = ($rssi>=128 ? (($rssi-256)/2-74) : ($rssi/2-74));
- my $lqi = hex(substr($dmsg, 3, 2));
- $dmsg = substr($dmsg, 6, $l-6);
- Log3 $name, 5, "$name: $dmsg $rssi $lqi";
- next if(!$dmsg || length($dmsg) < 1); # Bogus messages
- $hash->{"${name}_MSGCNT"}++;
- $hash->{"${name}_TIME"} = TimeNow();
- readingsSingleUpdate($hash, "state", $hash->{READINGS}{state}{VAL}, 0);
- $hash->{RAWMSG} = $rmsg;
- my %addvals = (RAWMSG => $rmsg);
- if(defined($rssi)) {
- $hash->{RSSI} = $rssi;
- $addvals{RSSI} = $rssi;
- }
- if(defined($lqi)) {
- $hash->{LQI} = $lqi;
- $addvals{LQI} = $lqi;
- }
- Dispatch($hash, $dmsg, \%addvals);
- }
- #####################################
- sub
- panStamp_Ready($)
- {
- my ($hash) = @_;
- return DevIo_OpenDev($hash, 1, "panStamp_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
- panStamp_SimpleWrite(@)
- {
- my ($hash, $msg, $nocr) = @_;
- return if(!$hash);
- my $name = $hash->{NAME};
- Log3 $name, 5, "SW: $msg";
- $msg .= "\r" unless($nocr);
- $hash->{USBDev}->write($msg) if($hash->{USBDev});
- syswrite($hash->{TCPDev}, $msg) if($hash->{TCPDev});
- 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);
- }
- sub
- panStamp_Attr(@)
- {
- my @a = @_;
- return undef;
- }
- 1;
- =pod
- =item summary interface to a panStamp based SWAP network
- =item summary_DE Anbindung von panStamp basierten SWAP Netwerken
- =begin html
- <a name="panStamp"></a>
- <h3>panStamp</h3>
- <ul>
- The panStamp is a family of RF devices sold by <a href="http://www.panstamp.com">panstamp.com</a>.
- It is possible to attach more than one device in order to get better
- reception, fhem will filter out duplicate messages.<br><br>
- This module provides the IODevice for the <a href="#SWAP">SWAP</a> modules that implement the SWAP protocoll
- to communicate with the individual moths in a panStamp network.<br><br>
- Note: currently only panSticks are know to work. The panStamp shield for a Rasperry Pi is untested.
- <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="panStamp_Define"></a>
- <b>Define</b>
- <ul>
- <code>define <name> panStamp <device> [<address> [<channel> [<syncword>]]]</code> <br>
- <br>
- USB-connected devices:<br><ul>
- <device> specifies the serial port to communicate with the panStamp.
- The name of the serial-device depends on your distribution, under
- linux the cdc_acm kernel module is responsible, and usually a
- /dev/ttyACM0 device will be created. If your distribution does not have a
- cdc_acm module, you can force usbserial to handle the panStamp by the
- following command:<ul>modprobe usbserial vendor=0x0403
- product=0x6001</ul>In this case the device is most probably
- /dev/ttyUSB0.<br><br>
- You can also specify a baudrate if the device name contains the @
- character, e.g.: /dev/ttyACM0@38400<br><br>
- If the baudrate is "directio" (e.g.: /dev/ttyACM0@directio), then the
- perl module Device::SerialPort is not needed, and fhem opens the device
- with simple file io. This might work if the operating system uses sane
- defaults for the serial parameters, e.g. some Linux distributions and
- OSX. <br><br>
- </ul>
- <br>
- The address is a 2 digit hex number to identify the moth in the panStamp network. The default is 01.<br>
- The channel is a 2 digit hex number to define the channel. the default is 00.<br>
- The syncword is a 4 digit hex number to identify the panStamp network. The default is B547.<br><br>
- Uppon initialization a broadcast message is send to the panStamp network to try to
- autodetect and autocreate all listening SWAP devices (i.e. all devices not in power down mode).
- </ul>
- <br>
- <a name="panStamp_Set"></a>
- <b>Set</b>
- <ul>
- <li>raw data<br>
- send raw data to the panStamp to be transmitted over the RF link.
- </li><br>
- </ul>
- <a name="panStamp_Get"></a>
- <b>Get</b>
- <ul>
- </ul>
- <a name="panStamp_Attr"></a>
- <b>Attributes</b>
- <ul>
- </ul>
- <br>
- </ul>
- =end html
- =cut
|