2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2024-11-22 02:59:49 +00:00

JeeStuff von Parix

git-svn-id: https://svn.fhem.de/fhem/trunk@950 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
rudolfkoenig 2011-07-19 09:31:20 +00:00
parent d6b5b35fff
commit c03cffa773
6 changed files with 1644 additions and 0 deletions

View File

@ -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 <Name> JeeLink </dev/...> 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 <http://www.gnu.org/licenses/>.
################################################################################
# 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] <DEVICE-NAME> $a[1] DEVICE-TYPE $a[2]<Parameter-1->;
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: <n>i set node ID (standard node ids are 1..26 or 'A'..'Z')
# netGRP: <n>g set network group (RFM12 only allows 212)
# freq -> <n>b set MHz band (4 = 433, 8 = 868, 9 = 915)
# cMode -> <n>c - set collect mode (advanced 1, normally 0)
# bCast -> t - broadcast max-size test packet, with ack
# sendA -> ...,<nn> a - send data packet to node <nn>, with ack
# sendS -> ...,<nn> s - send data packet to node <nn>, no ack
# led -> <n> l - turn activity LED on PB1 on or off
# Remote control commands:
# <hchi>,<hclo>,<addr>,<cmd> f - FS20 command (868 MHz)
# <addr>,<dev>,<on> k - KAKU command (433 MHz)
# Flash storage (JeeLink v2 only):
# d - dump all log markers
# <sh>,<sl>,<t3>,<t2>,<t1>,<t0> r - replay from specified marker
# 123,<bhi>,<blo> 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 <NAME> 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
# <hchi>,<hclo>,<addr>,<cmd> 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;

View File

@ -0,0 +1,261 @@
################################################################################
# FHEM-Modul see www.fhem.de
# 18_JME.pm
# JeeMeterNode
#
# Usage: define <Name> JME <Node-Nr>
################################################################################
# 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 <http://www.gnu.org/licenses/>.
################################################################################
# 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}{<SensorType>}{ReadingName}
# $data{JEECONF}{<SensorType>}{DataBytes}
# $data{JEECONF}{<SensorType>}{Prefix}
# $data{JEECONF}{<SensorType>}{CorrFactor}
# $data{JEECONF}{<SensorType>}{Function}
# <SensorType>: 0-9 -> Reserved/not Used
# <SensorType>: 10-99 -> Default
# <SensorType>: 100-199 -> Userdifined
# <SensorType>: 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 <Node-Nr>
# hash = New Device
# defs = $a[0] <DEVICE-NAME> $a[1] DEVICE-TYPE $a[2]<Parameter-1-> $a[3]<Parameter-2->
my ($hash, $def) = @_;
my @a = split(/\s+/, $def);
return "JME: Unknown argument count " . int(@a) . " , usage define <NAME>
NodeID [<Path_to_User_Conf_File>]" 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;

View File

@ -0,0 +1,238 @@
################################################################################
# FHEM-Modul see www.fhem.de
# 18_JSN.pm
# JeeSensorNode
#
# Usage: define <Name> JSN <Node-Nr>
################################################################################
# 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 <http://www.gnu.org/licenses/>.
################################################################################
# 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}{<SensorType>}{ReadingName}
# $data{JEECONF}{<SensorType>}{DataBytes}
# $data{JEECONF}{<SensorType>}{Prefix}
# $data{JEECONF}{<SensorType>}{CorrFactor}
# $data{JEECONF}{<SensorType>}{Function}
# <SensorType>: 0-9 -> Reserved/not Used
# <SensorType>: 10-99 -> Default
# <SensorType>: 100-199 -> Userdifined
# <SensorType>: 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 <Node-Nr> [<Path_to_User_Conf_File>]
# hash = New Device
# defs = $a[0] <DEVICE-NAME> $a[1] DEVICE-TYPE $a[2]<Parameter-1-> $a[3]<Parameter-2->
my ($hash, $def) = @_;
my @a = split(/\s+/, $def);
return "JSN: Unknown argument count " . int(@a) . " , usage define <NAME>
NodeID [<Path_to_User_Conf_File>]" 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}{<SensorType>}{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;

View File

@ -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 <jcw@equi4.com> 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 <Ports.h>
#include <PortsSHT11.h>
#include <RF12.h>
#include <avr/sleep.h>
#include <util/atomic.h>
#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;
}
}

View File

@ -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 <jcw@equi4.com> 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 <Ports.h>
#include <PortsSHT11.h>
#include <RF12.h>
#include <avr/sleep.h>
#include <util/atomic.h>
// -----------------------------------------------------------------------------
// 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;
}
}

View File

@ -0,0 +1,334 @@
// New version of the Room Node, derived from rooms.pde
// 2010-10-19 <jcw@equi4.com> 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 <Ports.h>
#include <PortsSHT11.h>
#include <RF12.h>
#include <avr/sleep.h>
#include <util/atomic.h>
#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;
}
}