From 2e41b0465607370749c81473edbec8975e3338e3 Mon Sep 17 00:00:00 2001 From: rudolfkoenig <> Date: Tue, 19 Jul 2011 09:31:20 +0000 Subject: [PATCH] JeeStuff von Parix git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@950 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- contrib/JeeStuff/00_JeeLink.pm | 465 +++++++++++++++++++++++++ contrib/JeeStuff/18_JME.pm | 261 ++++++++++++++ contrib/JeeStuff/18_JSN.pm | 238 +++++++++++++ contrib/JeeStuff/FHEM_JSN_BMP85.pde | 179 ++++++++++ contrib/JeeStuff/FHEM_JSN_LUX.pde | 167 +++++++++ contrib/JeeStuff/FHEM_JSN_RoomNode.pde | 334 ++++++++++++++++++ 6 files changed, 1644 insertions(+) create mode 100644 contrib/JeeStuff/00_JeeLink.pm create mode 100644 contrib/JeeStuff/18_JME.pm create mode 100644 contrib/JeeStuff/18_JSN.pm create mode 100644 contrib/JeeStuff/FHEM_JSN_BMP85.pde create mode 100644 contrib/JeeStuff/FHEM_JSN_LUX.pde create mode 100644 contrib/JeeStuff/FHEM_JSN_RoomNode.pde diff --git a/contrib/JeeStuff/00_JeeLink.pm b/contrib/JeeStuff/00_JeeLink.pm new file mode 100644 index 000000000..a2dbd6feb --- /dev/null +++ b/contrib/JeeStuff/00_JeeLink.pm @@ -0,0 +1,465 @@ +################################################################################ +# FHEM-Modul see www.fhem.de +# 00_JeeLink.pm +# Modul to use a JeeLink with RF12DEMO as FHEM-IO-Device +# +# Usage: define JeeLink NodeID +################################################################################ +# This program 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 program 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 this program. If not, see . +################################################################################ +# Autor: Axel Rieger +# Version: 1.0 +# Datum: 07.2011 +# Kontakt: fhem [bei] anax [punkt] info +################################################################################ +package main; + +use strict; +use warnings; +use Data::Dumper; +use vars qw(%defs); +use vars qw(%attr); +use vars qw(%data); +use vars qw(%modules); + +sub JeeLink_Initialize($); +sub JEE_Define($$); +sub JEE_CloseDev($); +sub JEE_OpenDev($$); +sub JEE_Ready($); +sub JEE_Read($); +sub JEE_Set($); +################################################################################ +sub JeeLink_Initialize($) +{ + my ($hash) = @_; + # Provider + $hash->{ReadFn} = "JEE_Read"; + $hash->{ReadyFn} = "JEE_Ready"; + $hash->{SetFn} = "JEE_Set"; + $hash->{WriteFn} = "JEE_Write"; + $hash->{Clients} = ":JSN:JME:JPU"; + $hash->{WriteFn} = "JEE_Write"; + + my %mc = ( + "1:JSN" => "^JSN", + "2:JME" => "^JME", + "3:JPU" => "^JPU"); + $hash->{MatchList} = \%mc; + + # Normal devices + $hash->{DefFn} = "JEE_Define"; + $hash->{AttrList} = "do_not_notify:1,0 dummy:1,0 loglevel:0,1 "; + return undef; +} +################################################################################ +sub JEE_Define($$) { + # define JEE0001 JeeLink /dev/tty.usbserial-A600cKlS NodeID + # defs = $a[0] $a[1] DEVICE-TYPE $a[2]; + my ($hash, $def) = @_; + my @a = split(/\s+/, $def); + + + + my $name = $a[0]; + my $dev = $a[2]; + my $NodeID = $a[3]; + + if($dev eq "none") { + Log 1, "$name device is none, commands will be echoed only"; + $hash->{TYPE} = 'JeeLink'; + $hash->{STATE} = TimeNow() . " Dummy-Device"; + $attr{$name}{dummy} = 1; + return undef; + } + + JEE_CloseDev($hash); + if($NodeID == 0 || $NodeID > 26 ) {return "JeeLink: NodeID between 1 and 26";} + Log 0, "JEE-Define: Name = $name dev=$dev"; + + $hash->{DeviceName} = $dev; + $hash->{TYPE} = 'JeeLink'; + my $ret = JEE_OpenDev($hash, 0); + my $msg = $NodeID . "i"; + $ret = &JEE_IOWrite($hash, $msg); + return undef; +} +################################################################################ +sub JEE_Set($){ + my ($hash, @a) = @_; + # Log 0, ("JEE-SET: " . Dumper(@_)); + my $fields .= "NodeID NetGRP Frequenz LED CollectMode SendMSG BroadcastMSG RAW"; + return "Unknown argument $a[1], choose one of ". $fields if($a[1] eq "?"); + # a[0] = DeviceName + # a[1] = Command + # Command + # nodeID: i set node ID (standard node ids are 1..26 or 'A'..'Z') + # netGRP: g set network group (RFM12 only allows 212) + # freq -> b set MHz band (4 = 433, 8 = 868, 9 = 915) + # cMode -> c - set collect mode (advanced 1, normally 0) + # bCast -> t - broadcast max-size test packet, with ack + # sendA -> ..., a - send data packet to node , with ack + # sendS -> ..., s - send data packet to node , no ack + # led -> l - turn activity LED on PB1 on or off + # Remote control commands: + # ,,, f - FS20 command (868 MHz) + # ,, k - KAKU command (433 MHz) + # Flash storage (JeeLink v2 only): + # d - dump all log markers + # ,,,,, r - replay from specified marker + # 123,, e - erase 4K block + # 12,34 w - wipe entire flash memory + my($name, $msg); + $name = $a[0]; + # LogLevel + my $ll = 0; + if(defined($attr{$name}{loglevel})) {$ll = $attr{$name}{loglevel};} + Log $ll,"$name/JEE-SET: " . $a[1] . " : " . $a[2]; + # @a ge 2 + # if(int(@a) ne 3) {return "JeeLink wrong Argument Count";} + $msg = ""; + if($a[1] eq "NodeID") { + if($a[2] == 0 || $a[2] > 26 ) {return "JeeLink: NodeID between 1 and 26";} + $msg = $a[2] . "i";} + if($a[1] eq "NetGRP") { + if($a[2] == 0 || $a[2] > 255 ) {return "JeeLink: NetGroup between 1 and 255";} + $msg = $a[2] . "g";} + if($a[1] eq "Frequenz") { + if($a[2] !~ m/433|868|933/) {return "JeeLink: Frquenz setting use 433, 868 or 933";} + my $mhz; + if($a[2] eq "433") {$msg = "4b";} + if($a[2] eq "868") {$msg = "8b";} + if($a[2] eq "915") {$msg = "9b";} + # 4 = 433, 8 = 868, 9 = 915 + } + # LED + if($a[1] eq "LED" && lc($a[2]) eq "on") { + $hash->{LED} = "ON"; + $msg = "1l";} + if($a[1] eq "LED" && lc($a[2]) eq "off") { + $hash->{LED} = "OFF"; + $msg = "0l";} + # CollectMode On or Off + if($a[1] eq "CollectMode" && lc($a[2]) eq "on") { + $hash->{CollectMode} = "ON"; + $msg = "1c";} + if($a[2] eq "CollectMode" && lc($a[2]) eq "off") { + $hash->{CollectMode} = "OFF"; + $msg = "0c";} + # RF12_MSG to Remote Node with NO ack + # set SendMSG NodeID Data + if($a[1] eq "SendMSG") {$msg = $a[2] . "," . $a[3] . "s"}; + # RF12- BroadcastMSG + if($a[1] eq "BroadcastMSG") {$msg = "t";} + # RAW + if($a[1] eq "RAW") {$msg = $a[2];} + # Send MSG + Log 0,"JEE-SET->WRITE: $msg"; + my $ret = &JEE_IOWrite($hash, $msg); + return undef; + +} +################################################################################ +sub JEE_CloseDev($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + my $dev = $hash->{DeviceName}; + + return if(!$dev); + $hash->{USBDev}->close() ; + delete($hash->{USBDev}); + delete($selectlist{"$name.$dev"}); + delete($readyfnlist{"$name.$dev"}); + delete($hash->{FD}); +} +################################################################################ +sub JEE_OpenDev($$) +{ + my ($hash, $reopen) = @_; + my $dev = $hash->{DeviceName}; + my $name = $hash->{NAME}; + my $po; + + $hash->{PARTIAL} = ""; + Log 3, "JeeLink opening $name device $dev" + if(!$reopen); + + my $baudrate; + ($dev, $baudrate) = split("@", $dev); + $baudrate = 57600; + + if ($^O=~/Win/) { + require Win32::SerialPort; + $po = new Win32::SerialPort ($dev); + } else { + require Device::SerialPort; + $po = new Device::SerialPort ($dev); + } + + if(!$po) { + return undef if($reopen); + Log(3, "Can't open $dev: $!"); + $readyfnlist{"$name.$dev"} = $hash; + $hash->{STATE} = "disconnected"; + return ""; + } + $hash->{USBDev} = $po; + if( $^O =~ /Win/ ) { + $readyfnlist{"$name.$dev"} = $hash; + } else { + $hash->{FD} = $po->FILENO; + delete($readyfnlist{"$name.$dev"}); + $selectlist{"$name.$dev"} = $hash; + } + + if($baudrate) { + $po->reset_error(); + Log 3, "$name: Setting baudrate to $baudrate"; + $po->baudrate($baudrate); + $po->databits(8); + $po->parity('none'); + $po->stopbits(1); + $po->handshake('none'); + } + + if($reopen) { + Log 1, "JeeLink $dev reappeared ($name)"; + } else { + Log 3, "JeeLink device opened"; + } + + # Set Defaults + # CollectMode on + my $ret = &JEE_IOWrite($hash, "1c"); + # QuietMode on + $ret = &JEE_IOWrite($hash, "1q"); + # LED On + $ret = &JEE_IOWrite($hash, "1l"); + # Set Frequenz to 868MHz + $ret = &JEE_IOWrite($hash, "8b"); + + $hash->{STATE}="connected"; # Allow InitDev to set the state + + DoTrigger($name, "CONNECTED") if($reopen); + return "Initialized"; +} +################################################################################ +sub JEE_Ready($) +{ + my ($hash) = @_; + + return JEE_OpenDev($hash, 1) + if($hash->{STATE} eq "disconnected"); + + # This is relevant for windows/USB only + my $po = $hash->{USBDev}; + my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags) = $po->status; + # Get Config + my $ret = &JEE_Write($hash, "i", undef); + return ($InBytes>0); +} +################################################################################ +sub JEE_Read($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + # LogLevel + # Default 4 + my $ll = 4; + if(defined($attr{$name}{loglevel})) { + $ll = $attr{$name}{loglevel}; + } + my $buf = $hash->{USBDev}->input(); + # + # Lets' try again: Some drivers return len(0) on the first read... + if(defined($buf) && length($buf) == 0) { + $buf = $hash->{USBDev}->input(); + } + my $jeedata = $hash->{PARTIAL}; + $jeedata .= $buf; + ############################################################################## + # Arduino/JeeLink + # Prints data to the serial port as human-readable ASCII text followed by + # a carriage return character (ASCII 13, or '\r') and + # a newline character (ASCII 10, or '\n'). + # HEX 0D AD \xf0 + if($jeedata =~ m/\n$/){ + chomp($jeedata); + chop($jeedata); + my $status = substr($jeedata, 0, 2); + Log $ll,("$name/JeeLink RAW:$status -> $jeedata"); + if($status =~/^OK/){ + Log $ll,("$name/JeeLink Dispatch RAW:$jeedata"); + &JEE_DispatchData($jeedata,$name,$ll); + } + elsif($jeedata =~ m/(^.*i)([0-9]{1,2})(\*.g)([0-9]{1,3})(.@.)([0-9]{1,3})(.MHz.*)/) { + JEE_RF12MSG($jeedata,$name,$ll); + } + elsif($jeedata =~/^DF/){JEE_RF12MSG($jeedata,$name,$ll);} + $jeedata = ""; + } + if($jeedata =~ m/^\x0A/) { + Log $ll,("$name/JeeLink RAW DEL HEX-0A:$jeedata -> $jeedata"); + $jeedata =~ s/\x0A//; + } + $hash->{PARTIAL} = $jeedata; +} +################################################################################ +sub JEE_DispatchData($){ + my ($rawdata,$name,$ll) = @_; + my @data = split(/\s+/,$rawdata); + my $status = shift(@data); + my $NodeID = shift(@data); + # see http://talk.jeelabs.net/topic/642#post-3622 + # The id +32 is what you see when the node requests an ACK. + if($NodeID > 31) {$NodeID = $NodeID - 32;} + Log $ll, "$name JEE-DISP: Status:$status NodeID:$NodeID Data:$rawdata"; + # normalize 0 => 00 without NodeID + for(my $i=0;$i<=$#data;$i++){ + if(length($data[$i]) == 1) { $data[$i] = "0" . $data[$i]} + } + # SensorData to Dispatch + my ($DispData,$SType,$SPre,@SData,$data_bytes,$slice_end); + for(my $i=0;$i<=$#data;$i++){ + # Get Number of DataBytes + $SType = $data[$i]; + if(defined($data{JEECONF}{$SType}{DataBytes})){ + $data_bytes = $data{JEECONF}{$SType}{DataBytes}; + ### + $SPre = $data{JEECONF}{$SType}{Prefix}; + $i++; + $slice_end = $i + $data_bytes - 1; + @SData = @data[$i..$slice_end]; + $DispData = $SPre . " " . $NodeID . " " . $SType . " " . join(" ",@SData); + } + else { + Log $ll, "$name JEE-DISP: -ERROR- SensorType $SType not defined"; + return undef; + } + # Dispacth-Data to FHEM-Dispatcher ----------------------------------------- + #foreach my $m (sort keys %{$modules{JeeLink}{MatchList}}) { + # my $match = $modules{JeeLink}{MatchList}{$m}; + $defs{$name}{"${name}_MSGCNT"}++; + $defs{$name}{"${name}_TIME"} = TimeNow(); + $defs{$name}{RAWMSG} = $DispData; + my %addvals = (RAWMSG => $DispData); + my $hash = $defs{$name}; + Log $ll,"$name JEE-DISP: SType=$SType -> DispData=$DispData"; + my $ret_disp = &Dispatch($hash, $DispData, \%addvals); + #} + # Dispacth-Data to FHEM-Dispatcher ----------------------------------------- + # Minimum 2Bytes left + # if((int(@data) - $i) < 2 ) { + # Log $ll,"$name JEE-DISP: 2Byte $i -> " . int(@data); + # $i = int(@data); + # } + #else {$i = $slice_end;} + $i = $slice_end; + } +} +################################################################################ +sub JEE_RF12MSG($$$){ + my ($rawdata,$name,$ll) = @_; + # ^ i23* g212 @ 868 MHz + # i -> NodeID + # g -> NetGroup + # @ -> 868MHz or 492MHz + Log $ll,("$name/JeeLink RF12MSG: $rawdata"); + my($NodeID,$NetGroup,$Freq); + if ( $rawdata =~ m/(^.*i)([0-9]{1,2})(\*.g)([0-9]{1,3})(.@.)([0-9]{1,3})(.MHz.*)/) { + ($NodeID,$NetGroup,$Freq) = ($2,$4,$6); + Log $ll,("$name/JeeLink RF12MSG-CONFIG: NodeId:$NodeID NetGroup:$NetGroup Freq:$Freq"); + $defs{$name}{RF12_NodeID} = $NodeID; + $defs{$name}{RF12_NetGroup} = $NetGroup; + $defs{$name}{RF12_Frequenz} = $Freq; + + } + if($rawdata =~ m/\s+DF/){ + Log $ll,("$name/JeeLink RF12MSG-FLASH: $rawdata"); + } + return undef; +} +################################################################################ +sub JEE_IOWrite() { + my ($hash, $msg,) = @_; + return if(!$hash); + # LogLevel + my $name = $hash->{NAME}; + my $ll = 4; + if(defined($attr{$name}{loglevel})) { + $ll = $attr{$name}{loglevel}; + } + if(defined($attr{$name}{dummy})){ + Log $ll ,"JEE-IOWRITE[DUMMY-MODE]: " . $hash->{NAME} . " $msg"; + } + else { + Log $ll ,"JEE-IOWRITE: " . $name . " $msg"; + $hash->{USBDev}->write($msg . "\n"); + select(undef, undef, undef, 0.001); + } +} +################################################################################ +sub JEE_Write() { + my ($hash, $msg1, $msg2) = @_; + # $hash -> Received form Device + # $msg1 -> Message Type ??? + # $msg2 -> Data + return if(!$hash); + # LogLevel + my $name = $hash->{NAME}; + my $ll = 4; + if(defined($attr{$name}{loglevel})) { + $ll = $attr{$name}{loglevel}; + } + Log $ll ,"JEE-WRITE: " . $hash->{NAME} . " MSG-1-: $msg1 MSG-2-: $msg2"; + # Default -------------------------------------------------------------------- + my $msg = $msg2; + # FS20 ----------------------------------------------------------------------- + # JEE-WRITE: JL01 MSG: 04 BTN: 010101 1234 33 00 + # FS20.pm + # IOWrite($hash, "04", "010101" . $hash->{XMIT} . $hash->{BTN} . $c); + # MSG-1-: 04 MSG-2-: 01010177770311 + # ,,, f - FS20 command (868 MHz) + # substr($jeedata, 0, 2); + my ($hchi,$hclo,$addr,$cmd); + if($msg2 =~ m/^010101/) { + $msg2 =~s/010101//; + Log 0, "JEE-IOWRITE-FS20: $msg2"; + + $hchi = hex(substr($msg2,0,2)); + $hclo = substr($msg2,2,2); + $addr = hex(substr($msg2,4,2)); + $cmd = hex(substr($msg2,6,2)); + + $msg = "$hchi,$hclo,$addr,$cmd f"; + Log $ll, "JEE-IOWRITE-FS20: hchi:$hchi hclo:$hclo addr:$addr cmd:$cmd"; + + $hash->{FS20_LastSend} = TimeNow() . ":" . $msg ; + } + # FS20 ----------------------------------------------------------------------- + + if(defined($attr{$name}{dummy})){ + Log $ll, "JEE_Write[DUMMY-MODE]: >$msg<"; + } + else { + # Send Message >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + $hash->{USBDev}->write($msg . "\n"); + Log $ll, "JEE_Write >$msg<"; + select(undef, undef, undef, 0.001); + # Send Message >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + } +} +################################################################################ +1; diff --git a/contrib/JeeStuff/18_JME.pm b/contrib/JeeStuff/18_JME.pm new file mode 100644 index 000000000..9e740c3cd --- /dev/null +++ b/contrib/JeeStuff/18_JME.pm @@ -0,0 +1,261 @@ +################################################################################ +# FHEM-Modul see www.fhem.de +# 18_JME.pm +# JeeMeterNode +# +# Usage: define JME +################################################################################ +# This program 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 program 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 this program. If not, see . +################################################################################ +# Autor: Axel Rieger +# Version: 1.0 +# Datum: 07.2011 +# Kontakt: fhem [bei] anax [punkt] info +################################################################################ +# READINGs +# MeterBase = MeterBase abgelesener Zaehlerstand...default 0 +# MeterNow = aktueller Zaehlerstand...wird hochgezÅ hlt +# Wenn MeterBase gesetzt ist, wird von dan an hochgezaehlt +# Wenn MeterBase gesetzt wird, werden +# AVG_Hour, AVG_Day, AVG_Month +################################################################################ +package main; +use strict; +use warnings; +use POSIX; +use Data::Dumper; +use vars qw(%defs); +use vars qw(%attr); +use vars qw(%data); +use vars qw(%modules); +################################################################################ +sub JME_Initialize($) +{ + my ($hash) = @_; + + # Match/Prefix + my $match = "JME"; + $hash->{Match} = "^JME"; + $hash->{DefFn} = "JME_Define"; + $hash->{UndefFn} = "JME_Undef"; + $hash->{SetFn} = "JME_Set"; + $hash->{ParseFn} = "JME_Parse"; + $hash->{AttrList} = "do_not_notify:0,1 loglevel:0,5 disable:0,1 TicksPerUnit avg_data_day avg_data_month"; + #----------------------------------------------------------------------------- + # Arduino/JeeNodes-Variables: + # http://arduino.cc/en/Reference/HomePage + # Integer = 2 Bytes -> form -32,768 to 32,767 + # Long (unsigned) = 4 Bytes -> from 0 to 4,294,967,295 + # Long (signed) = 4 Bytes -> from -2,147,483,648 to 2,147,483,647 + # + + # JeeConf + # $data{JEECONF}{}{ReadingName} + # $data{JEECONF}{}{DataBytes} + # $data{JEECONF}{}{Prefix} + # $data{JEECONF}{}{CorrFactor} + # $data{JEECONF}{}{Function} + # : 0-9 -> Reserved/not Used + # : 10-99 -> Default + # : 100-199 -> Userdifined + # : 200-255 -> Internal/Test + # Counter -------------------------------------------------------------------- + $data{JEECONF}{14}{ReadingName} = "counter"; + $data{JEECONF}{14}{DataBytes} = 2; + $data{JEECONF}{14}{Prefix} = $match; +} +################################################################################ +sub JME_Define($){ + # define J001 JME + # hash = New Device + # defs = $a[0] $a[1] DEVICE-TYPE $a[2] $a[3] + my ($hash, $def) = @_; + my @a = split(/\s+/, $def); + return "JME: Unknown argument count " . int(@a) . " , usage define + NodeID []" if(int(@a) != 3); + my $NodeID = $a[2]; + if(defined($modules{JME}{defptr}{$NodeID})) { + return "Node $NodeID allready define"; + } + $hash->{CODE} = $NodeID; + $hash->{STATE} = "NEW: " . TimeNow(); + $hash->{OrderID} = ord($NodeID); + $modules{JME}{defptr}{ord($NodeID)} = $hash; + + # Init + #$hash->{READINGS}{MeterBase}{TIME} = TimeNow(); + #$hash->{READINGS}{MeterBase}{VAL} = 0; + #$hash->{READINGS}{MeterNow}{TIME} = TimeNow(); + #$hash->{READINGS}{MeterNow}{VAL} = 0; + #$hash->{READINGS}{consumption}{TIME} = TimeNow(); + #$hash->{READINGS}{consumption}{VAL} = 0; + #$hash->{READINGS}{current}{TIME} = TimeNow(); + #$hash->{READINGS}{current}{VAL} = 0; + #$hash->{cnt_old} = 0; + return undef; +} +################################################################################ +sub JME_Undef($$){ + my ($hash, $name) = @_; + Log 4, "JME Undef: " . Dumper(@_); + my $NodeID = $hash->{NodeID}; + if(defined($modules{JME}{defptr}{$NodeID})) { + delete $modules{JME}{defptr}{$NodeID} + } + return undef; +} +################################################################################ +sub JME_Set($) +{ + my ($hash, @a) = @_; + my $fields = "MeterBase MeterNow counter avg_day avg_month"; + return "Unknown argument $a[1], choose one of $fields" if($a[1] eq "?"); + + if($fields =~ m/$a[1]/){ + $hash->{READINGS}{$a[1]}{VAL} = sprintf("%0.3f",$a[2]); + $hash->{READINGS}{$a[1]}{TIME} = TimeNow(); + } + return ""; +} +################################################################################ +sub JME_Parse($$) { + my ($iodev, $rawmsg) = @_; + # $rawmsg = JeeNodeID + SensorType + SensorData + # rawmsg = JME 03 252 03 65 + Log 4, "JME PARSE RAW-MSG: " . $rawmsg . " IODEV:" . $iodev->{NAME}; + # + my @data = split(/\s+/,$rawmsg); + my $NodeID = $data[1]; + # my $NodeID = sprintf("%02x" ,($data[1])); + # $NodeID = hex($NodeID); + # my $NodeID = chr(ord($data[1])); + my $SType = $data[2]; + my $data_bytes = $data{JEECONF}{$SType}{DataBytes}; + my $data_end = int(@data) - 1; + # $array[$#array]; + Log 4, "JME PARSE N:$NodeID S:$SType B:$data_bytes CNT:" . @data . " END:" . $data_end; + my @SData = @data[3..$data_end]; + + my ($hash,$name); + if(defined($modules{JME}{defptr}{ord($NodeID)})) { + $hash = $modules{JME}{defptr}{ord($NodeID)}; + $name = $hash->{NAME}; + } + else { + return "UNDEFINED JME_$NodeID JME $NodeID";}; + my %readings; + + # LogLevel + my $ll = 5; + if(defined($attr{$name}{loglevel})) { + $ll = $attr{$name}{loglevel}; + } + # Sensor-Data Bytes to Values + # lowBit HighBit reverse .... + @SData = reverse(@SData); + my $raw_value = join("",@SData); + my $value = ""; + map {$value .= sprintf "%02x",$_} @SData; + $value = hex($value); + Log $ll, "$name/JME-PARSE: $NodeID - $SType - " . join(" " , @SData) . " -> " . $value; + + + my $TicksPerUnit = 0.1; + if(defined($attr{$name}{TicksPerUnit})){ + $TicksPerUnit = $attr{$name}{TicksPerUnit}; + } + + my $counter = 0; + if(defined($defs{$name}{READINGS}{counter})){ + $counter = $defs{$name}{READINGS}{counter}{VAL}; + } + + # Counter Reset at 100 to 0 + if($counter > 100) { + $readings{counter} = 0; + } + else {$readings{counter} = $value;} + + my ($current,$cnt_delta); + $cnt_delta = $value - $counter; + + $current = sprintf("%0.3f", ($cnt_delta * $TicksPerUnit)); + $readings{current} = $current; + + + # Update only on Changes + my ($MeterNow,$consumption,$MeterBase); + $MeterNow = $defs{$name}{READINGS}{MeterNow}{VAL}; + if($current > 0 ){ + $MeterBase = $defs{$name}{READINGS}{MeterBase}{VAL}; + $readings{MeterNow} = sprintf("%0.3f", ($MeterNow + $current)); + $consumption = ($MeterNow + $current) - $MeterBase; + $readings{consumption} = sprintf("%0.3f", $consumption); + } + #----------------------------------------------------------------------------- + # Caculate AVG Day and Month + #----------------------------------------------------------------------------- + my $tsecs= time(); + my $d_now = (localtime($tsecs))[3]; + my $m_now = (localtime($tsecs))[4] + 1; + # avg_data_day = Day | Day_MeterNow + # avg_data_month = Month | Month_MeterNow + my ($d, $d_mn,$m,$m_mn); + if(defined($attr{$name}{avg_data_day})){ + ($d, $d_mn) = split(/\|/,$attr{$name}{avg_data_day}); + ($m,$m_mn) = split(/\|/,$attr{$name}{avg_data_month}); + } + else { + # INIT + $defs{$name}{READINGS}{avg_day}{VAL} = 0.000; + $defs{$name}{READINGS}{avg_day}{TIME} = TimeNow(); + $defs{$name}{READINGS}{avg_month}{VAL} = 0.000; + $defs{$name}{READINGS}{avg_month}{TIME} = TimeNow(); + $attr{$name}{avg_data_day} = "$d_now|$MeterNow"; + $attr{$name}{avg_data_month} = "$m_now|$MeterNow"; + ($d, $d_mn) = split(/\|/,$attr{$name}{avg_data_day}); + ($m,$m_mn) = split(/\|/,$attr{$name}{avg_data_month}); + } + Log $ll, "$name/JME-PARSE: D:NOW:$d_now/OLD:$d M:NOW:$m_now/OLD:$m"; + # AVG DAY + if($d_now ne $d) { + $consumption = ($MeterNow - $d_mn) + $defs{$name}{READINGS}{avg_day}{VAL} ; + $consumption = $consumption / 2; + $readings{avg_day} = sprintf("%0.3f", $consumption); + $attr{$name}{avg_data_day} = "$d_now|$MeterNow"; + } + # AVG Month + if($m_now ne $m) { + $consumption = ($MeterNow - $d_mn) + $defs{$name}{READINGS}{avg_month}{VAL} ; + $consumption = $consumption / 2; + $readings{avg_month} = sprintf("%0.3f", $consumption); + $attr{$name}{avg_data_month} = "$m_now|$MeterNow"; + } + #----------------------------------------------------------------------------- + # Readings + my $i = 0; + foreach my $r (sort keys %readings) { + Log 4, "JME $name $r:" . $readings{$r}; + $defs{$name}{READINGS}{$r}{VAL} = $readings{$r}; + $defs{$name}{READINGS}{$r}{TIME} = TimeNow(); + # Changed for Notify and Logs + $defs{$name}{CHANGED}[$i] = $r . ": " . $readings{$r}; + $i++; + } + $defs{$name}{STATE} = "M:" . $defs{$name}{READINGS}{MeterNow}{VAL} . " C:$value"; + + return $name; +} +################################################################################ +1; diff --git a/contrib/JeeStuff/18_JSN.pm b/contrib/JeeStuff/18_JSN.pm new file mode 100644 index 000000000..a5942f7e5 --- /dev/null +++ b/contrib/JeeStuff/18_JSN.pm @@ -0,0 +1,238 @@ +################################################################################ +# FHEM-Modul see www.fhem.de +# 18_JSN.pm +# JeeSensorNode +# +# Usage: define JSN +################################################################################ +# This program 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 program 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 this program. If not, see . +################################################################################ +# Autor: Axel Rieger +# Version: 1.0 +# Datum: 07.2011 +# Kontakt: fhem [bei] anax [punkt] info +################################################################################ +package main; +use strict; +use warnings; +use POSIX; +use Data::Dumper; +use vars qw(%defs); +use vars qw(%attr); +use vars qw(%data); +use vars qw(%modules); +################################################################################ +sub JSN_Initialize($) +{ + my ($hash) = @_; + + # Match/Prefix + my $match = "JSN"; + $hash->{Match} = "^JSN"; + $hash->{DefFn} = "JSN_Define"; + $hash->{UndefFn} = "JSN_Undef"; + $hash->{ParseFn} = "JSN_Parse"; + $hash->{AttrList} = "do_not_notify:0,1 loglevel:0,5 disable:0,1"; + #----------------------------------------------------------------------------- + # Arduino/JeeNodes-Variables: + # http://arduino.cc/en/Reference/HomePage + # Integer = 2 Bytes -> form -32,768 to 32,767 + # Long (unsigned) = 4 Bytes -> from 0 to 4,294,967,295 + # Long (signed) = 4 Bytes -> from -2,147,483,648 to 2,147,483,647 + # + + # JeeConf + # $data{JEECONF}{}{ReadingName} + # $data{JEECONF}{}{DataBytes} + # $data{JEECONF}{}{Prefix} + # $data{JEECONF}{}{CorrFactor} + # $data{JEECONF}{}{Function} + # : 0-9 -> Reserved/not Used + # : 10-99 -> Default + # : 100-199 -> Userdifined + # : 200-255 -> Internal/Test + # Default-2-Bytes------------------------------------------------------------- + $data{JEECONF}{12}{ReadingName} = "SensorData"; + $data{JEECONF}{12}{DataBytes} = 2; + $data{JEECONF}{12}{Prefix} = $match; + # Temperature ---------------------------------------------------------------- + $data{JEECONF}{11}{ReadingName} = "temperature"; + $data{JEECONF}{11}{DataBytes} = 2; + $data{JEECONF}{11}{Prefix} = $match; + $data{JEECONF}{11}{CorrFactor} = 0.1; + # Brightness- ---------------------------------------------------------------- + $data{JEECONF}{12}{ReadingName} = "brightness"; + $data{JEECONF}{12}{DataBytes} = 4; + $data{JEECONF}{12}{Prefix} = $match; + # Triple-Axis-X-Y-Z---------------------------------------------------------- + $data{JEECONF}{13}{ReadingName} = "rtiple_axis"; + $data{JEECONF}{13}{Function} = "JSN_parse_12"; + $data{JEECONF}{13}{DataBytes} = 12; + $data{JEECONF}{13}{Prefix} = $match; + #----------------------------------------------------------------------------- + # 14 Used by 18_JME + # Counter -------------------------------------------------------------------- + # $data{JEECONF}{14}{ReadingName} = "counter"; + # $data{JEECONF}{14}{DataBytes} = 4; + # $data{JEECONF}{14}{Prefix} = $match; + # Pressure ------------------------------------------------------------------- + $data{JEECONF}{15}{ReadingName} = "pressure"; + $data{JEECONF}{15}{DataBytes} = 4; + $data{JEECONF}{15}{CorrFactor} = 0.01; + $data{JEECONF}{15}{Prefix} = $match; + # Humidity ------------------------------------------------------------------- + $data{JEECONF}{16}{ReadingName} = "humidity"; + $data{JEECONF}{16}{DataBytes} = 1; + $data{JEECONF}{16}{Prefix} = $match; + # Light LDR ------------------------------------------------------------------ + $data{JEECONF}{17}{ReadingName} = "light_ldr"; + $data{JEECONF}{17}{DataBytes} = 1; + $data{JEECONF}{17}{Prefix} = $match; + # Motion --------------------------------------------------------------------- + $data{JEECONF}{18}{ReadingName} = "motion"; + $data{JEECONF}{18}{DataBytes} = 1; + $data{JEECONF}{18}{Prefix} = $match; + # JeeNode InternalTemperatur ------------------------------------------------- + $data{JEECONF}{251}{ReadingName} = "AtmelTemp"; + $data{JEECONF}{251}{DataBytes} = 2; + $data{JEECONF}{251}{Prefix} = $match; + # JeeNode InternalRefVolatge ------------------------------------------------- + $data{JEECONF}{252}{ReadingName} = "PowerSupply"; + $data{JEECONF}{252}{DataBytes} = 2; + $data{JEECONF}{252}{CorrFactor} = 0.0001; + $data{JEECONF}{252}{Prefix} = $match; + # JeeNode RF12 LowBat -------------------------------------------------------- + $data{JEECONF}{253}{ReadingName} = "RF12LowBat"; + $data{JEECONF}{253}{DataBytes} = 1; + $data{JEECONF}{253}{Prefix} = $match; + # JeeNode Milliseconds ------------------------------------------------------- + $data{JEECONF}{254}{ReadingName} = "Millis"; + $data{JEECONF}{254}{DataBytes} = 4; + $data{JEECONF}{254}{Prefix} = $match; + +} +################################################################################ +sub JSN_Define($){ + # define J001 JSN [] + # hash = New Device + # defs = $a[0] $a[1] DEVICE-TYPE $a[2] $a[3] + my ($hash, $def) = @_; + my @a = split(/\s+/, $def); + return "JSN: Unknown argument count " . int(@a) . " , usage define + NodeID []" if(int(@a) != 3); + my $NodeID = $a[2]; + if(defined($modules{JSN}{defptr}{$NodeID})) { + return "Node $NodeID allready define"; + } + $hash->{CODE} = $NodeID; + $hash->{STATE} = "NEW: " . TimeNow(); + $hash->{OrderID} = $NodeID; + $modules{JSN}{defptr}{$NodeID} = $hash; + return undef; +} +################################################################################ +sub JSN_Undef($$){ + my ($hash, $name) = @_; + Log 4, "JeeNode Undef: " . Dumper(@_); + my $NodeID = $hash->{NodeID}; + if(defined($modules{JSN}{defptr}{$NodeID})) { + delete $modules{JSN}{defptr}{$NodeID} + } + return undef; +} +################################################################################ +sub JSN_Parse($$) { + my ($iodev, $rawmsg) = @_; + # $rawmsg = JeeNodeID + SensorType + SensorData + # rawmsg = JSN 03 252 03 65 + Log 5, "JSN PARSE RAW-MSG: " . $rawmsg . " IODEV:" . $iodev->{NAME}; + # + my @data = split(/\s+/,$rawmsg); + my $NodeID = $data[1]; + # my $NodeID = sprintf("%02x" ,($data[1])); + # $NodeID = hex($NodeID); + # my $NodeID = chr(ord($data[1])); + my $SType = $data[2]; + my $data_bytes = $data{JEECONF}{$SType}{DataBytes}; + my $data_end = int(@data) - 1; + # $array[$#array]; + Log 5, "JSN PARSE N:$NodeID S:$SType B:$data_bytes CNT:" . @data . " END:" . $data_end; + my @SData = @data[3..$data_end]; + + my ($hash,$name); + if(defined($modules{JSN}{defptr}{$NodeID})) { + $hash = $modules{JSN}{defptr}{$NodeID}; + $name = $hash->{NAME}; + } + else { + return "UNDEFINED JSN_$NodeID JSN $NodeID";}; + my %readings; + # Function-Data -------------------------------------------------------------- + # If defined $data{JEECONF}{}{Function} then the function handels + # data parsing...return a hash key:reading_name Value:reading_value + # Param to Function: $iodev,$name,$NodeID, $SType,@SData + # Function-Data -------------------------------------------------------------- + if(defined($data{JEECONF}{$SType}{Function})) { + my $func = $data{JEECONF}{$SType}{Function}; + if(!defined(&$func)) { + Log 0, "JSN PARSE Function not defined: $SType -> $func"; + return undef; + } + no strict "refs"; + %readings = &$func($iodev,$name,$NodeID, $SType,@SData); + use strict "refs"; + } + else { + # Sensor-Data Bytes to Values + # lowBit HighBit reverse .... + @SData = reverse(@SData); + my $raw_value = join("",@SData); + my $value = ""; + map {$value .= sprintf "%02x",$_} @SData; + $value = hex($value); + Log 5, "JSN PARSE DATA $NodeID - $SType - " . join(" " , @SData) . " -> " . $value; + + my $reading_name = $data{JEECONF}{$SType}{ReadingName}; + $readings{$reading_name} = $value; + if(defined($data{JEECONF}{$SType}{CorrFactor})) { + my $corr = $data{JEECONF}{$SType}{CorrFactor}; + $readings{$reading_name} = $value * $corr; + } + } + #Reading + my $i = 0; + foreach my $r (sort keys %readings) { + Log 5, "JSN $name $r:" . $readings{$r}; + $defs{$name}{READINGS}{$r}{VAL} = $readings{$r}; + $defs{$name}{READINGS}{$r}{TIME} = TimeNow(); + $defs{$name}{STATE} = TimeNow() . " " . $r; + # Changed for Notify and Logs + $defs{$name}{CHANGED}[$i] = $r . ": " . $readings{$r}; + $i++; + } + return $name; +} +################################################################################ +sub JSN_parse_12() { + my ($iodev,$name,$NodeID, $SType,@SData) = @_; + Log 5, "JSN PARSE-12 DATA $NodeID - $SType - " . join(" " , @SData); + my %reading; + $reading{X} = "XXX"; + $reading{Y} = "YYY"; + $reading{Z} = "ZZZ"; + return \%reading; + +} +################################################################################ +1; diff --git a/contrib/JeeStuff/FHEM_JSN_BMP85.pde b/contrib/JeeStuff/FHEM_JSN_BMP85.pde new file mode 100644 index 000000000..733be3afe --- /dev/null +++ b/contrib/JeeStuff/FHEM_JSN_BMP85.pde @@ -0,0 +1,179 @@ +// ----------------------------------------------------------------------------- +// JeeNode for Use with BMP085 and LuxPlug +// reads out a BMP085 sensor connected via I2C +// see http://news.jeelabs.org/2010/06/20/battery-savings-for-the-pressure-plug/ +// see http://news.jeelabs.org/2010/06/30/going-for-gold-with-the-bmp085/ +// +// Baesd on RoomNode form JeeLabs roomNode.pde +// +// 2010-10-19 http://opensource.org/licenses/mit-license.php +// $Id: FHEM_JSN_BMP85.pde,v 1.1 2011-07-19 09:31:20 rudolfkoenig Exp $ +// +// see http://jeelabs.org/2010/10/20/new-roomnode-code/ +// and http://jeelabs.org/2010/10/21/reporting-motion/ +// ----------------------------------------------------------------------------- +// Includes +#include +#include +#include +#include +#include +#include "PortsBMP085.h" +// ----------------------------------------------------------------------------- +// JeeNode RF12-Config +static byte myNodeID = 5; // node ID used for this unit +static byte myNetGroup = 212; // netGroup used for this unit + +// Port BMP085 +#define BMP_PORT 1 + + +// Payload aka Data to Send +struct { + // RF12LowBat + byte rf12lowbat_type; + byte rf12lowbat_data; + // Temperature + byte temp_type; + int16_t temp_data; + // Pressure + byte pres_type; + int32_t pres_data; + +} payload; +// ----------------------------------------------------------------------------- +// BMP085 +PortI2C one (BMP_PORT); +BMP085 psensor (one, 3); // ultra high resolution +MilliTimer timer; +// ----------------------------------------------------------------------------- +// Config & Vars +#define SERIAL 1 // set to 1 to also report readings on the serial port +#define DEBUG 0 // set to 1 to display each loop() +#define MEASURE_PERIOD 3000 // how often to measure, in tenths of seconds +#define RETRY_PERIOD 10 // how soon to retry if ACK didn't come in +#define RETRY_LIMIT 5 // maximum number of times to retry +#define ACK_TIME 10 // number of milliseconds to wait for an ack +#define REPORT_EVERY 1 // report every N measurement cycles +#define SMOOTH 3 // smoothing factor used for running averages + +// set the sync mode to 2 if the fuses are still the Arduino default +// mode 3 (full powerdown) can only be used with 258 CK startup fuses +#define RADIO_SYNC_MODE 2 +// ----------------------------------------------------------------------------- +// The scheduler makes it easy to perform various tasks at various times: +enum { MEASURE, REPORT, TASK_END }; + +static word schedbuf[TASK_END]; +Scheduler scheduler (schedbuf, TASK_END); + +static byte reportCount; // count up until next report, i.e. packet send + +// has to be defined because we're using the watchdog for low-power waiting +ISR(WDT_vect) { Sleepy::watchdogEvent(); } + +// utility code to perform simple smoothing as a running average +static int smoothedAverage(int prev, int next, byte firstTime =0) { + if (firstTime) + return next; + return ((SMOOTH - 1) * prev + next + SMOOTH / 2) / SMOOTH; +} +// wait a few milliseconds for proper ACK to me, return true if indeed received +static byte waitForAck() { + MilliTimer ackTimer; + while (!ackTimer.poll(ACK_TIME)) { + if (rf12_recvDone() && rf12_crc == 0 && + rf12_hdr == (RF12_HDR_DST | RF12_HDR_ACK | myNodeID)) + return 1; + set_sleep_mode(SLEEP_MODE_IDLE); + sleep_mode(); + } + return 0; +} + +// readout all the sensors and other values +static void doMeasure() { + // RF12lowBat + payload.rf12lowbat_type = 253; + payload.rf12lowbat_data = rf12_lowbat(); + + // sensor readout takes some time, so go into power down while waiting + // payload.temp_data = psensor.measure(BMP085::TEMP); + // payload.pres_data = psensor.measure(BMP085::PRES); + + psensor.startMeas(BMP085::TEMP); + Sleepy::loseSomeTime(16); // must wait at least 16 ms + int32_t traw = psensor.getResult(BMP085::TEMP); + + psensor.startMeas(BMP085::PRES); + Sleepy::loseSomeTime(32); + int32_t praw = psensor.getResult(BMP085::PRES); + + payload.temp_type = 11; + payload.pres_type = 15; + psensor.calculate(payload.temp_data, payload.pres_data); + } +// periodic report, i.e. send out a packet and optionally report on serial port +static void doReport() { + rf12_sleep(-1); + while (!rf12_canSend()) + rf12_recvDone(); + rf12_sendStart(0, &payload, sizeof payload, RADIO_SYNC_MODE); + rf12_sleep(0); + + #if SERIAL + Serial.print("ROOM PAYLOAD: "); + Serial.print("RF12LowBat: "); + Serial.print((int) payload.rf12lowbat_data); + Serial.print(" T: "); + Serial.print(payload.temp_data); + Serial.print(" P: "); + Serial.print(payload.pres_data); + Serial.println(); + delay(2); // make sure tx buf is empty before going back to sleep + #endif +} +// ----------------------------------------------------------------------------- +void setup () { + rf12_initialize(myNodeID, RF12_868MHZ, myNetGroup); + #if SERIAL || DEBUG + Serial.begin(57600); + Serial.print("\n[FHEM-JeeNode.3]"); + // myNodeID = rf12_config(); + #else + + #endif + + rf12_sleep(0); // power down + // Start BMP085 + psensor.getCalibData(); + reportCount = REPORT_EVERY; // report right away for easy debugging + scheduler.timer(MEASURE, 0); // start the measurement loop going +} +// ----------------------------------------------------------------------------- +void loop () { + #if DEBUG + Serial.print('.'); + delay(2); + #endif + + switch (scheduler.pollWaiting()) { + + case MEASURE: + // reschedule these measurements periodically + scheduler.timer(MEASURE, MEASURE_PERIOD); + + doMeasure(); + + // every so often, a report needs to be sent out + if (++reportCount >= REPORT_EVERY) { + reportCount = 0; + scheduler.timer(REPORT, 0); + } + break; + + case REPORT: + doReport(); + break; + } +} diff --git a/contrib/JeeStuff/FHEM_JSN_LUX.pde b/contrib/JeeStuff/FHEM_JSN_LUX.pde new file mode 100644 index 000000000..14b38d9f7 --- /dev/null +++ b/contrib/JeeStuff/FHEM_JSN_LUX.pde @@ -0,0 +1,167 @@ +// ----------------------------------------------------------------------------- +// JeeNode for Use with BMP085 and LuxPlug +// reads out a BMP085 sensor connected via I2C +// see http://news.jeelabs.org/2010/06/20/battery-savings-for-the-pressure-plug/ +// see http://news.jeelabs.org/2010/06/30/going-for-gold-with-the-bmp085/ +// +// Baesd on RoomNode form JeeLabs roomNode.pde +// +// 2010-10-19 http://opensource.org/licenses/mit-license.php +// $Id: FHEM_JSN_LUX.pde,v 1.1 2011-07-19 09:31:20 rudolfkoenig Exp $ +// +// see http://jeelabs.org/2010/10/20/new-roomnode-code/ +// and http://jeelabs.org/2010/10/21/reporting-motion/ +// ----------------------------------------------------------------------------- +// Includes +#include +#include +#include +#include +#include +// ----------------------------------------------------------------------------- +// JeeNode RF12-Config +static byte myNodeID = 6; // node ID used for this unit +static byte myNetGroup = 212; // netGroup used for this unit +unsigned long lux; +// Port Lux-Plug +#define LUX_PORT 4 + +// Payload aka Data to Send +struct { + // RF12LowBat + byte rf12lowbat_type; + byte rf12lowbat_data; + // Lux + byte lux_type; + unsigned long lux_data; + +} payload; +// ----------------------------------------------------------------------------- +// Lux Plug +PortI2C two (LUX_PORT); +LuxPlug lsensor (two, 0x39); +byte highGain; +MilliTimer timer; +// ----------------------------------------------------------------------------- +// Config & Vars +#define SERIAL 1 // set to 1 to also report readings on the serial port +#define DEBUG 0 // set to 1 to display each loop() +#define MEASURE_PERIOD 3000 // how often to measure, in tenths of seconds +#define RETRY_PERIOD 10 // how soon to retry if ACK didn't come in +#define RETRY_LIMIT 5 // maximum number of times to retry +#define ACK_TIME 10 // number of milliseconds to wait for an ack +#define REPORT_EVERY 1 // report every N measurement cycles +#define SMOOTH 3 // smoothing factor used for running averages + +// set the sync mode to 2 if the fuses are still the Arduino default +// mode 3 (full powerdown) can only be used with 258 CK startup fuses +#define RADIO_SYNC_MODE 2 +// ----------------------------------------------------------------------------- +// The scheduler makes it easy to perform various tasks at various times: +enum { MEASURE, REPORT, TASK_END }; + +static word schedbuf[TASK_END]; +Scheduler scheduler (schedbuf, TASK_END); + +static byte reportCount; // count up until next report, i.e. packet send + +// has to be defined because we're using the watchdog for low-power waiting +ISR(WDT_vect) { Sleepy::watchdogEvent(); } + +// wait a few milliseconds for proper ACK to me, return true if indeed received +static byte waitForAck() { + MilliTimer ackTimer; + while (!ackTimer.poll(ACK_TIME)) { + if (rf12_recvDone() && rf12_crc == 0 && + rf12_hdr == (RF12_HDR_DST | RF12_HDR_ACK | myNodeID)) + return 1; + set_sleep_mode(SLEEP_MODE_IDLE); + sleep_mode(); + } + return 0; +} + +// readout all the sensors and other values +static void doMeasure() { + // RF12lowBat + payload.rf12lowbat_type = 253; + payload.rf12lowbat_data = rf12_lowbat(); + // lux_demo.pde + // need to wait after changing the gain + // see http://talk.jeelabs.net/topic/608 + // highGain = ! highGain; + // lsensor.setGain(highGain); + // Sleepy::loseSomeTime(1000); + // Lux Plug + const word* p = lsensor.getData(); + lux = lsensor.calcLux(); + payload.lux_type = 12; + payload.lux_data = lux; + // payload.lux_data = lsensor.calcLux(); + + } +// periodic report, i.e. send out a packet and optionally report on serial port +static void doReport() { + rf12_sleep(-1); + while (!rf12_canSend()) + rf12_recvDone(); + rf12_sendStart(0, &payload, sizeof payload, RADIO_SYNC_MODE); + rf12_sleep(0); + + #if SERIAL + Serial.print("ROOM PAYLOAD: "); + Serial.print("RF12LowBat: "); + Serial.print((int) payload.rf12lowbat_data); + Serial.print(" L: "); + Serial.print(payload.lux_data); + Serial.println(); + delay(2); // make sure tx buf is empty before going back to sleep + #endif +} +// ----------------------------------------------------------------------------- +void setup () { + rf12_initialize(myNodeID, RF12_868MHZ, myNetGroup); + #if SERIAL + Serial.begin(57600); + Serial.print("\n[FHEM-JeeNode.3]"); + // myNodeID = rf12_config(); + #endif + + rf12_sleep(0); // power down + // Start Lux-Plug + lsensor.begin(); + Sleepy::loseSomeTime(1000); + highGain = 1; + // highGain = ! highGain; + lsensor.setGain(highGain); + Sleepy::loseSomeTime(1000); + reportCount = REPORT_EVERY; // report right away for easy debugging + scheduler.timer(MEASURE, 0); // start the measurement loop going +} +// ----------------------------------------------------------------------------- +void loop () { + #if DEBUG + Serial.print('.'); + delay(2); + #endif + + switch (scheduler.pollWaiting()) { + + case MEASURE: + // reschedule these measurements periodically + scheduler.timer(MEASURE, MEASURE_PERIOD); + + doMeasure(); + + // every so often, a report needs to be sent out + if (++reportCount >= REPORT_EVERY) { + reportCount = 0; + scheduler.timer(REPORT, 0); + } + break; + + case REPORT: + doReport(); + break; + } +} diff --git a/contrib/JeeStuff/FHEM_JSN_RoomNode.pde b/contrib/JeeStuff/FHEM_JSN_RoomNode.pde new file mode 100644 index 000000000..71ddbc6d2 --- /dev/null +++ b/contrib/JeeStuff/FHEM_JSN_RoomNode.pde @@ -0,0 +1,334 @@ +// New version of the Room Node, derived from rooms.pde +// 2010-10-19 http://opensource.org/licenses/mit-license.php +// $Id: FHEM_JSN_RoomNode.pde,v 1.1 2011-07-19 09:31:20 rudolfkoenig Exp $ + +// see http://jeelabs.org/2010/10/20/new-roomnode-code/ +// and http://jeelabs.org/2010/10/21/reporting-motion/ + +// The complexity in the code below comes from the fact that newly detected PIR +// motion needs to be reported as soon as possible, but only once, while all the +// other sensor values are being collected and averaged in a more regular cycle. + +#include +#include +#include +#include +#include + +#define SERIAL 0 // set to 1 to also report readings on the serial port +#define DEBUG 0 // set to 1 to display each loop() run and PIR trigger + +#define SHT11_PORT 4 // defined if SHT11 is connected to a port +#define LDR_PORT 1 // defined if LDR is connected to a port's AIO pin +#define PIR_PORT 1 // defined if PIR is connected to a port's DIO pin + +#define MEASURE_PERIOD 600 // how often to measure, in tenths of seconds +#define RETRY_PERIOD 1 // how soon to retry if ACK didn't come in +#define RETRY_LIMIT 1 // maximum number of times to retry +#define ACK_TIME 5 // number of milliseconds to wait for an ack +#define REPORT_EVERY 5 // report every N measurement cycles +#define SMOOTH 3 // smoothing factor used for running averages + +// set the sync mode to 2 if the fuses are still the Arduino default +// mode 3 (full powerdown) can only be used with 258 CK startup fuses +#define RADIO_SYNC_MODE 2 + +// The scheduler makes it easy to perform various tasks at various times: + +enum { MEASURE, REPORT, TASK_END }; + +static word schedbuf[TASK_END]; +Scheduler scheduler (schedbuf, TASK_END); + +// Other variables used in various places in the code: + +static byte reportCount; // count up until next report, i.e. packet send +static byte myNodeID = 8; // node ID used for this unit +static byte myNetGroup = 212; + +// This defines the structure of the packets which get sent out by wireless: +/* +struct { + byte light; // light sensor: 0..255 + byte moved :1; // motion detector: 0..1 + byte humi :7; // humidity: 0..100 + int temp :10; // temperature: -500..+500 (tenths) + byte lobat :1; // supply voltage dropped under 3.1V: 0..1 +} payload; +*/ +struct { + byte light_type; + byte light_data; + byte moved_type; + byte moved_data; + byte humi_type; + byte humi_data; + byte temp_type; + int temp_data; + byte rf12lowbat_type; + byte rf12lowbat_data; +} payload; +// Conditional code, depending on which sensors are connected and how: + +#if SHT11_PORT + SHT11 sht11 (SHT11_PORT); +#endif + +#if LDR_PORT + Port ldr (LDR_PORT); +#endif + +#if PIR_PORT + #define PIR_HOLD_TIME 30 // hold PIR value this many seconds after change + #define PIR_PULLUP 1 // set to one to pull-up the PIR input pin + + class PIR : public Port { + volatile byte value, changed; + volatile uint32_t lastOn; + public: + PIR (byte portnum) + : Port (portnum), value (0), changed (0), lastOn (0) {} + + // this code is called from the pin-change interrupt handler + void poll() { + byte pin = digiRead(); + #if SERIAL + Serial.print("PIR.POLL: "); + Serial.print(pin,DEC); + Serial.print(" LastOn: "); + Serial.println(lastOn); + #endif + // if the pin just went on, then set the changed flag to report it + if (pin) { + if (!state()) + changed = 1; + lastOn = millis(); + } + value = pin; + } + + // state is true if curr value is still on or if it was on recently + byte state() const { + #if SERIAL + Serial.print("ATOMIC_RESTORESTATE"); + Serial.print(" LastOn: "); + Serial.println(lastOn); + #endif + byte f = value; + if (lastOn > 0) + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + if (millis() - lastOn < 1000 * PIR_HOLD_TIME) + f = 1; + } + return f; + } + + // return true if there is new motion to report + byte triggered() { + #if SERIAL + Serial.print("TRIGGERD"); + Serial.print(" LastOn: "); + Serial.println(lastOn); + #endif + byte f = changed; + changed = 0; + return f; + } + }; + + PIR pir (PIR_PORT); + + // the PIR signal comes in via a pin-change interrupt + ISR(PCINT2_vect) { pir.poll(); } +#endif + +// has to be defined because we're using the watchdog for low-power waiting +ISR(WDT_vect) { Sleepy::watchdogEvent(); } + +// utility code to perform simple smoothing as a running average +static int smoothedAverage(int prev, int next, byte firstTime =0) { + if (firstTime) + return next; + return ((SMOOTH - 1) * prev + next + SMOOTH / 2) / SMOOTH; +} + +// spend a little time in power down mode while the SHT11 does a measurement +static void shtDelay () { + Sleepy::loseSomeTime(32); // must wait at least 20 ms +} + +// wait a few milliseconds for proper ACK to me, return true if indeed received +static byte waitForAck() { + MilliTimer ackTimer; + while (!ackTimer.poll(ACK_TIME)) { + if (rf12_recvDone() && rf12_crc == 0 && + rf12_hdr == (RF12_HDR_DST | RF12_HDR_ACK | myNodeID)) + return 1; + set_sleep_mode(SLEEP_MODE_IDLE); + sleep_mode(); + } + return 1; +} + +// readout all the sensors and other values +static void doMeasure() { + #if SERIAL + Serial.println("doMeasure"); + #endif + byte firstTime = payload.humi_data == 0; // special case to init running avg + + // RF12lowBat + payload.rf12lowbat_type = 253; + payload.rf12lowbat_data = rf12_lowbat(); + + #if SHT11_PORT +#ifndef __AVR_ATtiny84__ + sht11.measure(SHT11::HUMI, shtDelay); + sht11.measure(SHT11::TEMP, shtDelay); + float h, t; + sht11.calculate(h, t); + int humi = h + 0.5, temp = 10 * t + 0.5; +#else + //XXX TINY! + int humi = 50, temp = 25; +#endif + + payload.humi_type = 16; + payload.humi_data = smoothedAverage(payload.humi_data, humi, firstTime); + payload.temp_type = 11; + payload.temp_data = smoothedAverage(payload.temp_data, temp, firstTime); + #endif + #if LDR_PORT + ldr.digiWrite2(1); // enable AIO pull-up + byte light = ~ ldr.anaRead() >> 2; + ldr.digiWrite2(0); // disable pull-up to reduce current draw + + payload.light_type = 17; + payload.light_data = smoothedAverage(payload.light_data, light, firstTime); + #endif + #if PIR_PORT + payload.moved_type = 18; + payload.moved_data = pir.state(); + #endif +} + +// periodic report, i.e. send out a packet and optionally report on serial port +static void doReport() { + Serial.println("REPORT"); + rf12_sleep(-1); + while (!rf12_canSend()) + rf12_recvDone(); + rf12_sendStart(0, &payload, sizeof payload, RADIO_SYNC_MODE); + rf12_sleep(0); + + #if SERIAL + Serial.print("ROOM L:"); + Serial.print((int) payload.light_data); + Serial.print(" M:"); + Serial.print((int) payload.moved_data); + Serial.print(" H:"); + Serial.print((int) payload.humi_data); + Serial.print(" T:"); + Serial.print((int) payload.temp_data); + Serial.print(" LB:"); + Serial.print((int) payload.rf12lowbat_data); + Serial.println(); + delay(2); // make sure tx buf is empty before going back to sleep + #endif +} + +// send packet and wait for ack when there is a motion trigger +static void doTrigger() { + #if DEBUG + Serial.print("doTrigger PIR "); + Serial.print((int) payload.moved_data); + delay(2); + #endif + + for (byte i = 0; i < RETRY_LIMIT; ++i) { + rf12_sleep(-1); + while (!rf12_canSend()) + rf12_recvDone(); + rf12_sendStart(RF12_HDR_ACK, &payload, sizeof payload, RADIO_SYNC_MODE); + byte acked = waitForAck(); + rf12_sleep(0); + + if (acked) { + #if DEBUG + Serial.print(" ack "); + Serial.println((int) i); + delay(2); + #endif + // reset scheduling to start a fresh measurement cycle + scheduler.timer(MEASURE, MEASURE_PERIOD); + return; + } + + Sleepy::loseSomeTime(RETRY_PERIOD * 100); + } + scheduler.timer(MEASURE, MEASURE_PERIOD); + #if DEBUG + Serial.println(" no ack!"); + delay(2); + #endif +} + +void setup () { + #if SERIAL || DEBUG + Serial.begin(57600); + Serial.print("\n[roomNode.3]"); + // myNodeID = rf12_config(); + rf12_initialize(myNodeID, RF12_868MHZ, myNetGroup); + #else + rf12_initialize(myNodeID, RF12_868MHZ, myNetGroup); + #endif + + rf12_sleep(0); // power down + + #if PIR_PORT + pir.digiWrite(PIR_PULLUP); +#ifdef PCMSK2 + bitSet(PCMSK2, PIR_PORT + 3); + bitSet(PCICR, PCIE2); +#else + //XXX TINY! +#endif + #endif + + reportCount = REPORT_EVERY; // report right away for easy debugging + scheduler.timer(MEASURE, 0); // start the measurement loop going +} + +void loop () { + #if DEBUG + Serial.println('Loop..................................................'); + delay(2); + #endif + + #if PIR_PORT + if (pir.triggered()) { + payload.moved_data = pir.state(); + doTrigger(); + } + #endif + + switch (scheduler.pollWaiting()) { + + case MEASURE: + // reschedule these measurements periodically + scheduler.timer(MEASURE, MEASURE_PERIOD); + + doMeasure(); + + // every so often, a report needs to be sent out + if (++reportCount >= REPORT_EVERY) { + reportCount = 0; + scheduler.timer(REPORT, 0); + } + break; + + case REPORT: + doReport(); + break; + } +}