| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425 |
- #############################################
- # $Id: 98_GAEBUS.pm 12899 2016-12-28 16:52:39Z jamesgo $
- # derived from 00_TUL.pm
- #
- # 17.07.2015 : A.Goebel : initiale Version mit loop, readingname via attribut, keine writes
- # 21.07.2015 : A.Goebel : start implementation for "set .. write"
- # 23.07.2015 : A.Goebel : event-on-change-reading added to attributes
- # 08.09.2015 : A.Goebel : limit number of socket-open retries in GetUpdates loop
- # 10.09.2015 : A.Goebel : fix html code of commandref
- # 11.09.2015 : A.Goebel : add attribute "ebusWritesEnable:0,1"
- # 11.09.2015 : A.Goebel : add set w~ commands to set attributes for writing
- # 11.09.2015 : A.Goebel : add set <write-reading> command to write to ebusd
- # 13.09.2015 : A.Goebel : increase timeout for reads from ebusd from 1.8 to 5.0
- # 14.09.2015 : A.Goebel : use utf-8 coding to display values from ".csv" files
- # 14.09.2015 : A.Goebel : add optional parameter [FIELD[.N]] of read from ebusd to reading name
- # 15.09.2015 : A.Goebel : get rid of perl warnings when attribute value is empty
- # 16.09.2015 : A.Goebel : allow write to parameters protected by #install
- # 21.09.2015 : A.Goebel : implement BlockingCall Interface
- # 07.10.2015 : A.Goebel : beautify and complete commandref
- # 12.10.2015 : A.Goebel : fix handling of timeouts in BlockingCall Interface (additional parameter in doEbusCmd forces restart (no shutdown restart))
- # timeout for reads increased
- # 19.10.2015 : A.Goebel : add attribute disable to disable loop to collect readings
- # 05.11.2015 : A.Goebel : add support for "h" (broadcast update) commands from csv, handle them equal to (r)ead
- # 09.11.2015 : A.Goebel : add support for multiple readings generated from one command to ebusd
- # ebusd may return a list of values like "52.0;43.0;8.000;41.0;45.0;error"
- # defining a reading "VL;RL;dummy;VLWW;RLWW" will create redings VL, RL, VLWW and RLWW
- # 04.12.2015 : A.Goebel : add event-min-interval to attributes
- # 14.12.2015 : A.Goebel : add read possible commmands for ebusd using "find -f" instead of reading the ".csv" files directly ("get ebusd_find")
- # 25.01.2016 : A.Goebel : fix duplicate log entries for readings
- # 05.02.2016 : A.Goebel : add valueFormat attribute
- # 18.08.2016 : A.Goebel : fix workarround for perl warning with keys of hash reference
- # 30.08.2016 : A.Goebel : add reading "state_ebus" containing output from "state" of ebusd
- # 16.09.2016 : A.Goebel : add reset "state_ebus" if ebus is not connected
- # 06.10.2016 : A.Goebel : add valueFormat can now be used to access all values returned from one read
- # 11.10.2016 : A.Goebel : add implement hex write from ebusctl
- # 11.10.2016 : A.Goebel : add set initial reading name after "set" to "class~variable"
- # 11.10.2016 : A.Goebel : fix "set hex" is only available if ebusWritesEnabled is '1'
- # 13.10.2016 : A.Goebel : fix "set hex" referres to "ebusctl hex"
- # 13.10.2016 : A.Goebel : fix "class" is now optional for ebusctl
- # 18.10.2016 : A.Goebel : fix removed content of <comment> from attribute names for readings
- # 31.10.2016 : A.Goebel : fix rename hex to ebusd_hex
- # 31.10.2016 : A.Goebel : fix set for writings without comments did not work
- # 26.12.2016 : A.Goebel : fix handling for non "userattr" attributes
- # 27.12.2016 : A.Goebel : fix handling if ebusctl reports "usage:"
- # 27.12.2016 : A.Goebel : fix scan removed from supported classes
- package main;
- use strict;
- use warnings;
- use Time::HiRes qw(gettimeofday);
- use IO::Socket;
- use IO::Select;
- use Encode;
- use Blocking;
- sub GAEBUS_Attr(@);
- sub GAEBUS_OpenDev($$);
- sub GAEBUS_CloseDev($);
- sub GAEBUS_Disconnected($);
- sub GAEBUS_Shutdown($);
- sub GAEBUS_doEbusCmd($$$$$$$);
- sub GAEBUS_GetUpdates($);
- sub GAEBUS_GetUpdatesDoit($);
- sub GAEBUS_GetUpdatesDone($);
- sub GAEBUS_GetUpdatesAborted($);
- sub GAEBUS_State($);
- my %gets = ( # Name, Data to send to the GAEBUS, Regexp for the answer
- );
- my %sets = (
- #"reopen" => []
- );
- my %setsForWriting = ();
- my %getsForWriting = ();
- my $allSetParams = "";
- my $allSetParamsForWriting = "";
- my $allGetParams = "";
- my $allGetParamsForWriting = "";
- my $delimiter = "~";
- my $attrsDefault = "do_not_notify:1,0 disable:1,0 dummy:1,0 showtime:1,0 loglevel:0,1,2,3,4,5,6 event-on-change-reading event-min-interval ebusWritesEnabled:0,1 valueFormat:textField-long";
- my %ebusCmd = ();
- #####################################
- sub
- GAEBUS_Initialize($)
- {
- my ($hash) = @_;
- # Normal devices
- $hash->{DefFn} = "GAEBUS_Define";
- $hash->{UndefFn} = "GAEBUS_Undef";
- $hash->{GetFn} = "GAEBUS_Get";
- $hash->{SetFn} = "GAEBUS_Set";
- #$hash->{StateFn} = "GAEBUS_SetState";
- $hash->{AttrFn} = "GAEBUS_Attr";
- $hash->{AttrList} = $attrsDefault;
- $hash->{ShutdownFn} = "GAEBUS_Shutdown";
- %sets = ( "reopen" => [] );
- %gets = ( "ebusd_find" => [], "ebusd_info" => [] );
- %setsForWriting = ();
- %getsForWriting = ( "ebusd_hex" => [] );
-
- GAEBUS_initParams($hash);
- }
- #####################################
- sub
- GAEBUS_initParams ($)
- {
- my ($hash) = @_;
- # bulid set and get Params and store them in
- # $allSetParams
- # $allSetParamsForWriting
- # $allGetParams
- # $allGetParamsForWriting
- $allSetParams = "";
- foreach my $setval (sort keys %sets)
- {
- Log3 ($hash, 4, "GAEBUS Initialize params for set: $setval");
- if ( (@{$sets{$setval}}) > 0)
- {
- $allSetParams .= $setval.":".join (",", @{$sets{$setval}})." ";
- }
- else
- {
- $allSetParams .= $setval." ";
- }
- #Log3 ($hash, 2, "GAEBUS Initialize: $setval:$allSetParams");
- }
- $allSetParamsForWriting = "";
- foreach my $setval (sort keys %setsForWriting)
- {
- Log3 ($hash, 4, "GAEBUS Initialize params for setsForWriting: $setval");
- if ( (@{$setsForWriting{$setval}}) > 0)
- {
- $allSetParamsForWriting .= $setval.":".join (",", @{$setsForWriting{$setval}})." ";
- }
- else
- {
- $allSetParamsForWriting .= $setval." ";
- }
- #Log3 ($hash, 2, "GAEBUS Initialize: $setval:$allSetParamsForWriting");
- }
- $allGetParams = "";
- foreach my $getval (sort keys %gets)
- {
- Log3 ($hash, 4, "GAEBUS Initialize params for get: $getval");
- if ( (@{$gets{$getval}}) > 0)
- {
- $allGetParams .= $getval.":".join (",", @{$gets{$getval}})." ";
- }
- else
- {
- $allGetParams .= $getval." ";
- }
- #Log3 ($hash, 2, "GAEBUS Initialize: $getval:$allGetParams");
- }
- $allGetParamsForWriting = "";
- foreach my $setval (sort keys %getsForWriting)
- {
- Log3 ($hash, 4, "GAEBUS Initialize params for getsForWriting: $setval");
- if ( (@{$getsForWriting{$setval}}) > 0)
- {
- $allGetParamsForWriting .= $setval.":".join (",", @{$getsForWriting{$setval}})." ";
- }
- else
- {
- $allGetParamsForWriting .= $setval." ";
- }
- #Log3 ($hash, 2, "GAEBUS Initialize: $setval:$allSetParamsForWriting");
- }
- }
- #####################################
- sub
- GAEBUS_Define($$)
- {
- my ($hash, $def) = @_;
- my @a = split("[ \t][ \t]*", $def);
- if(@a < 3) {
- my $msg = "wrong syntax: define <name> GAEBUS <device-addr>[:<port>] [interval]";
- Log (2, $msg);
- return $msg;
- }
- GAEBUS_CloseDev($hash);
- my $name = $a[0];
- my $devaddr = $a[2];
- my $interval = $a[3];
- $hash->{DeviceName} = $hash->{NAME};
- $hash->{DeviceAddress} = $devaddr;
- $hash->{Interval} = defined ($interval) ? int ($interval) : 150;
- $hash->{UpdateCnt} = 0;
-
- my $ret = GAEBUS_OpenDev($hash, 0);
- RemoveInternalTimer($hash);
- InternalTimer(gettimeofday()+10, "GAEBUS_GetUpdates", $hash, 0);
- $hash->{helper}{longAttributesCount} = 0;
- return undef;
- }
- #####################################
- sub
- GAEBUS_Undef($$)
- {
- my ($hash, $arg) = @_;
- GAEBUS_CloseDev($hash);
- BlockingKill($hash->{helper}{RUNNING_PID}) if(defined($hash->{helper}{RUNNING_PID}));
- return undef;
- }
- #####################################
- sub GAEBUS_Shutdown($)
- {
- my ($hash) = @_;
- GAEBUS_CloseDev($hash);
- return undef;
- }
- #####################################
- sub
- GAEBUS_Set($@)
- {
- my ($hash, @a) = @_;
- return "\"set GAEBUS\" needs at least one parameter" if(@a < 2);
- my $name = shift @a;
- my $type = shift @a;
- my $arg = join(" ", @a);
- #return "No $a[1] for dummies" if(IsDummy($name));
- #Log3 ($hash, 3, "ebus1: reopen $name");
- #Log3 ($hash, 2, "$name: set $arg $type invalid parameter");
- if ($type eq "reopen") {
- Log3 ($hash, 3, "ebus1: reopen");
- GAEBUS_CloseDev($hash);
- GAEBUS_OpenDev($hash,0);
- return undef;
- }
- # handle commands defined in %sets
- if (defined ($sets{$type}))
- {
- unless (grep {$_ eq $arg} @{$sets{$type}})
- {
- return "invalid parameter";
- }
- my $attrname = $type.$delimiter.$arg;
- $attrname =~ s/\#install/install/;
- my ($io,$class,$var,$comment) = split ($delimiter, $attrname, 4);
- my $shortAttrname = join ($delimiter, ($io, $class, $var));
- #Log3 ($hash, 3, "$name: set $attrname");
- Log3 ($hash, 3, "$name: set for reading $attrname");
- addToDevAttrList($name, $shortAttrname);
- if (! defined $attr{$name}{$attrname}) {
- if ($class eq "") {
- $attr{$name}{$shortAttrname} = $var;
- } else {
- $attr{$name}{$shortAttrname} = join ("-", ($class, $var));
- }
- }
- return undef;
- }
- #
- # extend possible parameters by the readings defined for writing in attributes
- #
- my %writings = ();
- my $actSetParams = "$allSetParams ";
- my $ebusWritesEnabled = (defined($attr{$name}{"ebusWritesEnabled"})) ? $attr{$name}{"ebusWritesEnabled"} : 0;
- if ($ebusWritesEnabled) {
- $actSetParams .= "$allSetParams$allSetParamsForWriting " if ($ebusWritesEnabled);
- foreach my $oneattr (sort keys %{$attr{$name}})
- {
- my $readingname = $attr{$name}{$oneattr};
- my $readingcmdname = $oneattr;
- $readingname =~ s/ .*//;
- $readingname =~ s/:.*//;
-
- # only for "w" commands
- if ($oneattr =~ /^w$delimiter.{1,7}$delimiter.*/ or $oneattr =~ /^w$delimiter.{1,7}install$delimiter.*/)
- {
- unless ($readingname =~ /^\s*$/ or $readingname eq "1")
- {
- $writings{$readingname} = $readingcmdname;
- #Log3 ($name, 2, "$name SetParams $readingname");
- }
- }
-
- #Log3 ($name, 4, "$name Set attr name $readingname");
- #Log3 ($name, 4, "$name Set attr cmd $readingcmdname");
- }
- $actSetParams .= join (" ", sort keys %writings);
- # handle write commands (which were defined above)
- if (defined ($setsForWriting{$type}))
- {
- unless (grep {$_ eq $arg} @{$setsForWriting{$type}})
- {
- return "invalid parameter";
- }
- my $attrname = $type.$delimiter.$arg;
- $attrname =~ s/\#install/install/;
- my ($io,$class,$var,$comment) = split ($delimiter, $attrname, 4);
- my $shortAttrname = join ($delimiter, ($io, $class, $var));
- Log3 ($hash, 3, "$name: set for writing $attrname");
- #addToDevAttrList($name, $attrname);
- addToDevAttrList($name, $shortAttrname);
- if (! defined $attr{$name}{$attrname}) {
- if ($class eq "") {
- $attr{$name}{$shortAttrname} = $var;
- } else {
- $attr{$name}{$shortAttrname} = join ("-", ($class, $var));
- }
- }
- return undef;
- }
- if (defined ($writings{$type}))
- {
- foreach my $oneattr (sort keys %{$attr{$name}})
- {
- next unless ($oneattr =~ /^w.*$delimiter.{1,7}$delimiter.*$/ or $oneattr =~ /^w.*$delimiter.{1,7}install$delimiter.*$/);
-
- my $readingname = $attr{$name}{$oneattr};
- next if ($readingname ne $type);
- my $answer = GAEBUS_doEbusCmd ($hash, "w", $readingname, $oneattr, $arg, "", 0);
- return "$answer";
- }
- }
- }
- return "Unknown argument $type, choose one of " . $actSetParams
- if(!defined($sets{$type}));
- return undef;
- }
- #####################################
- sub
- GAEBUS_Get($@)
- {
- my ($hash, @a) = @_;
- my $name = $hash->{NAME};
- my $arg = (defined($a[2]) ? $a[2] : "");
- my $rsp;
- my $varname = $a[0];
- my $type = $a[1];
- my $readingname = "";
- my $readingcmdname = "";
- return "\"get GAEBUS\" needs at least one parameter" if(@a < 2);
- if ($type eq "ebusd_hex")
- {
- Log3 ($hash, 4, "$name Set $type $arg");
- my $answer = GAEBUS_doEbusCmd ($hash, "h", "", "", "$arg", "", 0);
- return $answer;
- }
- if ($type eq "removeCommentFromAttributeNames")
- {
- Log3 ($hash, 4, "$name Get $type $arg");
- my $answer = "shortened the follwing attribute names\n";
- foreach my $oneattr (sort keys %{$attr{$name}})
- {
- if ($oneattr =~ /$delimiter/) {
- my ($io,$class,$var,$comment) = split ($delimiter, $oneattr, 4);
- if (defined ($comment)) {
- # attribute name contains comment as 4-th part
- my $newattrname = join ($delimiter, ($io, $class, $var));
- $answer .= $oneattr." to ".$newattrname."\n";
- $attr{$name}{userattr} =~ s/$oneattr//;
- addToDevAttrList($name, $newattrname);
- $attr{$name}{$newattrname} = $attr{$name}{$oneattr};
- delete ($attr{$name}{$oneattr});
- }
- }
- }
- $hash->{helper}{longAttributesCount} = 0;
- return $answer;
- }
- # extend possible parameters by the readings defined in attributes
- my %readings = ();
- my %readingsCmdaddon = ();
- my $actGetParams .= "$allGetParams reading:";
- foreach my $oneattr (sort keys %{$attr{$name}})
- {
- my ($readingnameX, $cmdaddon) = split (" ", $attr{$name}{$oneattr}, 2);
- $cmdaddon = "" unless (defined ($cmdaddon));
- next unless defined ($readingnameX);
- next if ($readingnameX =~ /^\s*$/);
- next if ($readingnameX eq "1");
- my ($readingname, $doCntNo) = split (":", $readingnameX, 2); # split name from cycle number
- $doCntNo = 1 unless (defined ($doCntNo));
- #my $readingname = $attr{$name}{$oneattr};
- my $readingcmdname = $oneattr;
- $readingname =~ s/ .*//;
- $readingname =~ s/:.*//;
- # only for "r" commands
- if ($oneattr =~ /^r$delimiter.{1,7}$delimiter.*/)
- {
- $readings{$readingname} = $readingcmdname;
- $readingsCmdaddon{$readingname} = $cmdaddon;
-
- #Log3 ($name, 2, "$name GetParams $readingname");
- }
- #Log3 ($name, 4, "$name Get attr name $readingname");
- #Log3 ($name, 4, "$name Get attr cmd $readingcmdname");
- }
- $actGetParams .= join (",", sort keys %readings);
- if ($hash->{helper}{longAttributesCount} > 0) {
- $actGetParams .= " removeCommentFromAttributeNames";
- }
- my $ebusWritesEnabled = (defined($attr{$name}{"ebusWritesEnabled"})) ? $attr{$name}{"ebusWritesEnabled"} : 0;
- if ($ebusWritesEnabled) {
- $actGetParams .= " ".join (" ", (sort keys %getsForWriting));
- }
- # handle "read" parameters and update Reading
- if ($a[1] eq "reading")
- {
- my $readingname = $a[2];
- my $readingcmdname = $readings{$readingname};
- my $cmdaddon = $readingsCmdaddon{$readingname};
- Log3 ($name, 4, "$name Get name $readingname");
- Log3 ($name, 4, "$name Get cmd r $readingcmdname");
- my $answer = GAEBUS_doEbusCmd ($hash, "r", $readingname, $readingcmdname, "", $cmdaddon, 0);
- #return "$answer";
- return undef;
- }
- if ($a[1] eq "ebusd_find")
- {
- Log3 ($name, 4, "$name Get $a[1]");
- my $answer = GAEBUS_doEbusCmd ($hash, "f", "", "", "", "", 0);
- return $answer;
- }
- if ($a[1] eq "ebusd_info")
- {
- Log3 ($name, 4, "$name Get $a[1]");
- my $answer = GAEBUS_doEbusCmd ($hash, "i", "", "", "", "", 0);
- return $answer;
- }
- # other read commands
- if ($a[1] =~ /^[r]$delimiter/)
- {
- my $readingname = "";
- my $readingcmdname = $a[1].$delimiter.$a[2];
- Log3 ($name, 3, "$name get cmd v $readingcmdname");
- my $answer = GAEBUS_doEbusCmd ($hash, "v", $readingname, $readingcmdname, "", "", 0);
- #return (defined($answer ? $answer : ""));
- return "$answer";
- }
- # handle commands from %gets and show result from ebusd
- return "Unknown argument $a[1], choose one of " . $actGetParams
- if(!defined($gets{$a[1]}));
- #return "No $a[1] for dummies" if(IsDummy($varname));
- return "nix";
- }
- #####################################
- sub
- GAEBUS_SetState($$$$)
- {
- my ($hash, $tim, $vt, $val) = @_;
- return undef;
- }
- ########################
- sub
- GAEBUS_CloseDev($)
- {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- my $dev = $hash->{DeviceName};
- return if(!$dev);
-
- if($hash->{TCPDev}) {
- $hash->{TCPDev}->close();
- delete($hash->{TCPDev});
- }
- #delete($selectlist{"$name.$dev"});
- #delete($readyfnlist{"$name.$dev"});
- delete($hash->{FD});
- $hash->{STATE} = "closed";
- readingsSingleUpdate ($hash, "state_ebus", "unknown", 1);
- }
- ########################
- sub
- GAEBUS_OpenDev($$)
- {
- my ($hash, $reopen) = @_;
- my $dev = $hash->{DeviceName};
- my $name = $hash->{NAME};
- my $host = $hash->{DeviceAddress};
- my $port = 8888;
- if($host =~ m/^(.+):(.+)$/) { # host[:port]
- $host = $1;
- $port = $2;
- }
- $hash->{PARTIAL} = "";
- Log3 $hash, 3, "GAEBUS opening $name device $host($port)"
- if($reopen == 0);
- # This part is called every time the timeout (5sec) is expired _OR_
- # somebody is communicating over another TCP connection. As the connect
- # for non-existent devices has a delay of 3 sec, we are sitting all the
- # time in this connect. NEXT_OPEN tries to avoid this problem.
- if($hash->{NEXT_OPEN} && time() < $hash->{NEXT_OPEN}) {
- Log3 $hash, 5, "GAEBUS NEXT_OPEN prevented opening $name device $host($port)";
- return;
- }
- my $conn = new IO::Socket::INET (
- PeerAddr => "$host",
- PeerPort => '8888',
- Proto => 'tcp',
- Reuse => 0,
- Timeout => 10
- );
- if(defined ($conn)) {
- delete($hash->{NEXT_OPEN});
- } else {
- Log(3, "Can't connect to $dev: $!") if(!$reopen);
- #$readyfnlist{"$name.$dev"} = $hash;
- $hash->{STATE} = "disconnected";
- $hash->{NEXT_OPEN} = time()+60;
- return "";
- }
- my $sel = new IO::Select($conn);
- $hash->{DevType} = 'EBUSD';
- $hash->{TCPDev} = $conn;
- $hash->{FD} = $conn->fileno();
- $hash->{SELECTOR} = $sel;
- #delete($readyfnlist{"$name.$dev"});
- #$selectlist{"$name.$dev"} = $hash;
- if($reopen) {
- Log3 $hash, 1, "GAEBUS $dev reappeared ($name)";
- } else {
- Log3 $hash, 3, "GAEBUS device opened ($name)";
- }
- #$hash->{STATE}="Initialized";
- $hash->{STATE}="Connected";
- DoTrigger($name, "CONNECTED") if($reopen);
- return 0;
- }
- sub
- GAEBUS_Disconnected($)
- {
- my $hash = shift;
- my $dev = $hash->{DeviceName};
- my $name = $hash->{NAME};
- return if(!defined($hash->{FD})); # Already deleted or RFR
- Log3 $hash, 1, "$dev disconnected, waiting to reappear";
- GAEBUS_CloseDev($hash);
- #$readyfnlist{"$name.$dev"} = $hash; # Start polling
- $hash->{STATE} = "disconnected";
- # Without the following sleep the open of the device causes a SIGSEGV,
- # and following opens block infinitely. Only a reboot helps.
- sleep(5);
- DoTrigger($name, "DISCONNECTED");
- }
- sub
- GAEBUS_Attr(@)
- {
- my @a = @_;
- my ($action, $name, $attrname, $attrval) = @a;
- my $hash = $defs{$name};
- $attrval = "" unless defined ($attrval);
- my $userattr = defined($attr{$name}{userattr}) ? $attr{$name}{userattr} : "";
- if ($action eq "del")
- {
- #my $userattr = $attr{$name}{userattr};
- #Log3 ($hash, 2, ">$userattr<>$attrname<");
- if ( " $userattr " =~ / $attrname / )
- {
- #Log3 ($hash, 2, "match");
- # " a" or "^a$"
- $userattr =~ s/ *$attrname//;
- if ($userattr eq "")
- {
- delete($attr{$name}{userattr});
- }
- else
- {
- $attr{$name}{userattr} = $userattr;
- }
- }
- # delete reading if attribute name contains $delimiter
- if ($attrname =~ /^.*$delimiter/) {
- my $reading = $attr{$name}{$attrname};
- $reading =~ s/ .*//;
- $reading =~ s/:.*//;
- foreach my $r (split /;/, $reading) {
- Log3 ($name, 3, "$name: delete reading: $reading");
- delete($defs{$name}{READINGS}{$reading});
- }
- }
- if ($attrname eq "valueFormat" and defined ($hash->{helper}{$attrname})) {
- delete ($hash->{helper}{$attrname});
- }
- return undef;
- }
- elsif ($action eq "set")
- {
- if ($attrname eq "valueFormat") {
- my $attrVal = $attrval;
- if( $attrVal =~ m/^{.*}$/s && $attrVal =~ m/=>/ && $attrVal !~ m/\$/ ) {
- my $av = eval $attrVal;
- if( $@ ) {
- Log3 ($hash->{NAME}, 3, $hash->{NAME} ."set $attrname: ". $@);
- } else {
- $attrVal = $av if( ref($av) eq "HASH" );
- }
- $hash->{helper}{$attrname} = $attrVal;
- foreach my $key (keys %{ $hash->{helper}{$attrname} }) {
- Log3 ($hash->{NAME}, 4, $hash->{NAME} ." $key ".$hash->{helper}{$attrname}{$key});
- }
- #return "set HERE??";
- } else {
- # if valueFormat is not verified sucessfully ... the helper is deleted (=not used)
- delete $hash->{helper}{$attrname};
- }
- return undef;
- }
- if ( " $userattr " =~ / $attrname / )
- {
- # this is an attribute form "userattr"
- if (!defined $attr{$name}{$attrname})
- {
- # attribute is not yet defined
- if ( $attrname =~ /$delimiter/ )
- {
- my ($io,$class,$var,$comment) = split ($delimiter, $attrname, 4);
- $hash->{helper}{longAttributesCount}++ if (defined($comment));
- #Log3 ($hash->{NAME}, 1, "$hash->{NAME} helper longAttributesCount set to ".$hash->{helper}{longAttributesCount});
- }
- }
- if (defined $attr{$name}{$attrname})
- {
- my $oldreading = $attr{$name}{$attrname};
- $oldreading =~ s/ .*//;
- $oldreading =~ s/:.*//;
- my $newreading = $attrval;
- $newreading =~ s/ .*//;
- $newreading =~ s/:.*//;
- my @or = split /;/, $oldreading;
- my @nr = split /;/, $newreading;
- for (my $i; $i <= $#or; $i++)
- {
- if ($or[$i] ne $nr[$i])
- {
- #Log3 ($name, 2, "$name: adjust reading: $or[$i]");
- if (defined($defs{$name}{READINGS}{$or[$i]}))
- {
- if (defined ($nr[$i] and $nr[$i] ne "dummy" ))
- {
- unless ($nr[$i] =~ /^1*$/) # matches "1" or ""
- {
- #Log3 ($name, 2, "$name: change attribute $attrname ($or[$i] -> $nr[$i])");
- $defs{$name}{READINGS}{$nr[$i]}{VAL} = $defs{$name}{READINGS}{$or[$i]}{VAL};
- $defs{$name}{READINGS}{$nr[$i]}{TIME} = $defs{$name}{READINGS}{$or[$i]}{TIME};
- }
- }
- delete($defs{$name}{READINGS}{$or[$i]});
-
- }
- }
- }
- }
- }
- }
- Log3 (undef, 2, "called GAEBUS_Attr($a[0],$a[1],$a[2],<$a[3]>)");
-
- return undef;
- }
- sub
- GAEBUS_State($)
- {
- my $hash = shift;
- my $name = $hash->{NAME};
- my $state = "";
- my $actMessage = "";
- if (($hash->{STATE} eq "Connected") and ($hash->{TCPDev}->connected()) )
- {
- my $timeout = 10;
-
- syswrite ($hash->{TCPDev}, "state\n");
- if ($hash->{SELECTOR}->can_read($timeout))
- {
- sysread ($hash->{TCPDev}, $actMessage, 4096);
- $actMessage =~ s/\n$/ /g;
- $actMessage =~ s/ {1,}$//;
- if ($actMessage =~ /^signal acquired/) {
- $state = "ok";
- }
-
- }
- else
- {
- $state = "no answer";
- Log3 ($name, 2, "$name state $state ($actMessage)");
- }
- }
- return ($state, $actMessage);
- }
- sub
- GAEBUS_doEbusCmd($$$$$$$)
- {
- my $hash = shift;
- my $action = shift; # "r" = get reading, "v" = verbose mode,
- # "w" = write to ebus, "f" = execute find to read in config, "i" = execute info
- # "h" = read with command specified in hex
- my $readingname = shift;
- my $readingcmdname = shift;
- my $writeValues = shift;
- my $cmdaddon = shift;
- my $inBlockingCall = shift;
- my $actMessage = "";
- my $name = $hash->{NAME};
- if (($hash->{STATE} ne "Connected") or (!$hash->{TCPDev}->connected()) )
- {
- Log3 ($name, 2, "$name device closed. Try to reopen");
- GAEBUS_CloseDev($hash);
- GAEBUS_OpenDev($hash,0);
- if ($hash->{STATE} ne "Connected") {
- if ($inBlockingCall) {
- return "";
- }
- else {
- return undef;
- }
- }
- }
- #my $timeout = 1.8;
- my $timeout = 15.0;
- $timeout = 10.0 if ($action eq "v");
- $timeout = 10.0 if ($action eq "w");
- my ($io,$class,$var,$comment) = split ($delimiter, $readingcmdname, 4);
- my $cmd = "";
- if ($action eq "w") {
- $class =~ s/install/#install/;
- $cmd = "$io ";
- $cmd .= "-c $class " if ($class ne "");
- $cmd .= "$var ";
- $cmd .= "$writeValues";
- } elsif ($action eq "f") {
- $cmd = "find -f -r -w";
- } elsif ($action eq "i") {
- $cmd = "info";
- } elsif ($action eq "r") {
- $cmd = "$io ";
- $cmd .= " -f " if ($io ne "h");
- $cmd .= "-c $class " if ($class ne "");
- $cmd .= "$var ";
- $cmd .= "$cmdaddon";
- } elsif ($action eq "v") {
- $cmd = "$io ";
- $cmd .= " -f " if ($io ne "h");
- $cmd .= "-v ";
- $cmd .= "-c $class " if ($class ne "");
- $cmd .= "$var ";
- #$cmd =~ s/^h /r /; # obsolete
- } elsif ($action eq "h") {
- $cmd = "hex $writeValues";
- }
-
- Log3 ($name, 3, "$name execute $cmd");
- if ($hash->{SELECTOR}->can_read(0.1))
- {
- sysread ($hash->{TCPDev}, $actMessage, 4096);
- $actMessage =~ s/\n//g;
- Log3 ($name, 2, "$name old answer $actMessage\n");
- $actMessage = "";
- }
- syswrite ($hash->{TCPDev}, $cmd."\n");
- if (0 and $hash->{SELECTOR}->can_read($timeout))
- {
- #Log3 ($name, 2, "$name try to read");
- sysread ($hash->{TCPDev}, $actMessage, 4096);
- $actMessage =~ s/\n//g;
- #$actMessage =~ s/;/ /g;
- }
- if ($hash->{SELECTOR}->can_read($timeout)) {
- my $rbuffer = "";
-
- while ($hash->{SELECTOR}->can_read(0.1) and sysread ($hash->{TCPDev}, $rbuffer, 4096)) {
- $actMessage .= $rbuffer;
- if ( $rbuffer =~ /\n\n$/ )
- {
- #Log3 ($name, 3, "$name answer terminated by empty line");
- last;
- }
-
- }
- #$actMessage =~ s/\n//g;
- # handle usage messages
- if ($actMessage =~ /^usage:/)
- {
- $actMessage = "usage: syntay error";
- }
- #Log3 ($name, 3, "$name answer $action $readingname $actMessage");
- }
- else
- {
- #return "timeout reading answer for ($readingname) $cmd";
- Log3 ($name, 2, "$name: timeout reading answer for $cmd");
- return "";
- }
- if ($action eq "f")
- {
- %sets = ( "reopen" => [] );
- %gets = ( "ebusd_find" => [], "ebusd_info" => [] );
- %setsForWriting = ();
- %getsForWriting = ( "ebusd_hex" => [] );
- my $cnt = 0;
- foreach my $line (split /\n/, $actMessage) {
- $cnt++;
- $line =~ s/ /_/g; # no blanks in names and comments
- $line =~ s/$delimiter/_/g; # clean up the delimiter within the text
- Log3 ($name, 4, "$name $line");
- #my ($io,$class,$var) = split (",", $line, 3);
- my ($io, $class, $var, $comment, @params) = split (",", $line, 5);
- $io =~ s/[0-9]//g;
- # drop "memory"
- next if ($class eq "memory");
- #next if ($class eq "scan");
- next if ($class =~ /^scan/);
- push @{$sets{$io.$delimiter.$class}}, $var.$delimiter.$comment if ($io eq "r" or $io eq "h");
- push @{$setsForWriting{$io.$delimiter.$class}}, $var.$delimiter.$comment if ($io eq "w" or $io eq "wi");
- push @{$gets{$io.$delimiter.$class}}, $var.$delimiter.$comment if ($io eq "r" or $io eq "h");
- }
- GAEBUS_initParams($hash);
- Log3 ($name, 3, "$name find done.");
- return "$cnt definitions processed";
- }
- if ($action eq "i" or $action eq "h")
- {
- #Log3 ($name, 3, "$name info done.");
- return "$actMessage";
-
- }
- $actMessage =~ s/\n//g;
- $actMessage =~ s/\|//g;
- Log3 ($name, 3, "$name answer $action $readingname $actMessage");
- my @values = split /;/, $actMessage;
- my @targetreading = defined ($readingname) ? split /;/, $readingname : ();
- if ($inBlockingCall)
- {
- $actMessage = "";
- # for (my $i=0; $i <= $#targetreading; $i++)
- # {
- # next if ($targetreading[$i] eq "dummy");
- # my $v = defined($values[$i]) ? $values[$i] : "";
- #
- # $v = GAEBUS_valueFormat ($hash, $targetreading[$i], $v);
- # $actMessage .= $targetreading[$i]."|".$v."|";
- # }
- foreach my $r (@targetreading)
- {
- next if ($r eq "dummy");
- my $v = GAEBUS_valueFormat ($hash, $r, \@values);
- shift @values;
- $actMessage .= $r."|".$v."|";
- }
- $actMessage =~ s/\|$//;
- # readings will be updated in main fhem process
- return $actMessage;
- }
- if ($action eq "r")
- {
- readingsBeginUpdate ($hash);
- # for (my $i=0; $i <= $#targetreading; $i++)
- # {
- # next if ($targetreading[$i] eq "dummy");
- # my $v = defined($values[$i]) ? $values[$i] : "";
- #
- # $v = GAEBUS_valueFormat ($hash, $targetreading[$i], $v);
- #
- # readingsBulkUpdate ($hash, $targetreading[$i], $v);
- # }
- foreach my $r (@targetreading)
- {
- next if ($r eq "dummy");
- my $v = GAEBUS_valueFormat ($hash, $r, \@values);
- shift @values;
- readingsBulkUpdate ($hash, $r, $v);
- }
- readingsEndUpdate($hash, 1);
- }
-
- #if ($inBlockingCall) {
- # $actMessage = $readingname."|".$actMessage;
- #}
- #else {
- #
- # if ($action eq "r") {
- # if (defined ($readingname)) {
- # # BlockingCall changes
- # readingsSingleUpdate ($hash, $readingname, "$actMessage", 1);
- # }
- # }
- #}
-
- return $actMessage;
- }
- sub
- GAEBUS_GetUpdates($)
- {
- my ($hash) = @_;
- my $name = $hash->{NAME};
- if (defined($attr{$name}{disable}) and ($attr{$name}{disable} == 1)) {
- Log3 $hash, 4, "$name GetUpdates2 is disabled";
-
- InternalTimer(gettimeofday()+$hash->{Interval}, "GAEBUS_GetUpdates", $hash, 0);
- return;
-
- } else {
- Log3 $hash, 4, "$name start GetUpdates2";
-
- }
- $hash->{UpdateCnt} = $hash->{UpdateCnt} + 1;
- $hash->{helper}{RUNNING_PID} = BlockingCall("GAEBUS_GetUpdatesDoit", $name, "GAEBUS_GetUpdatesDone", 120, "GAEBUS_GetUpdatesAborted", $hash)
- unless(exists($hash->{helper}{RUNNING_PID}));
- }
- sub
- GAEBUS_GetUpdatesDoit($)
- {
- my ($string) = (@_);
- #my ($name, $nochwas) = split ("|", $string);
- my ($name) = $string;
- my ($hash) = $defs{$name};
- my $readingname = "";
- my $tryOpenCnt = 2; # no of tries to open the device, before giving up
- my $readingsToUpdate = "";
- # don't use socket inherited from fhem by BlockingCall.pm
- delete($hash->{TCPDev});
- $hash->{STATE} = "pleaseReconnect";
- my $ret = GAEBUS_OpenDev($hash, 0);
- if (($hash->{STATE} ne "Connected") or (!$hash->{TCPDev}->connected()) )
- {
- return "$name";
- }
- # syncronize with ebusd
- my ($state, $actMessage) = GAEBUS_State($hash);
- if ($state ne "ok") {
- Log3 ($name, 2, "$name: ebusd no connection or signal state($state)");
- return "$name";
- }
- Log3 ($name, 5, "$name: ebusd state($actMessage)");
- $actMessage =~ s/,.*//;
- $readingsToUpdate .= "|state_ebus|".$actMessage;
- foreach my $oneattr (keys %{$attr{$name}})
- {
- # only for "r" commands
- if ($oneattr =~ /^r$delimiter.{1,7}$delimiter.*/)
- {
- my ($readingnameX, $cmdaddon) = split (" ", $attr{$name}{$oneattr}, 2);
- $cmdaddon = "" unless (defined ($cmdaddon));
- next unless defined ($readingnameX);
- next if ($readingnameX =~ /^\s*$/);
- next if ($readingnameX eq "1");
- my ($readingname, $doCntNo) = split (":", $readingnameX, 2); # split name from cycle number
- $doCntNo = 1 unless (defined ($doCntNo));
- Log3 ($name, 5, "$name GetUpdates: $readingname:$doCntNo");
- #Log3 ($name, 2, "$name check modulo ".$hash->{UpdateCnt}." mod $doCntNo -> ".($hash->{UpdateCnt} % $doCntNo));
- if (($hash->{UpdateCnt} % $doCntNo) == 0)
- {
- $readingsToUpdate .= "|".GAEBUS_doEbusCmd ($hash, "r", $readingname, $oneattr, "", $cmdaddon, 1);
- }
- # limit number of reopens if ebusd cannot be reached
- if (($hash->{STATE} ne "Connected") or (!$hash->{TCPDev}->connected()) )
- {
- if (--$tryOpenCnt <= 0)
- {
- Log3 ($name, 2, "$name: not connected, stop GetUpdates loop");
- last;
- }
- }
- }
- }
- # returnvalue for BlockingCall ... done routine
- return $name.$readingsToUpdate;
- }
- sub
- GAEBUS_GetUpdatesDone($)
- {
- my ($string) = @_;
- return unless(defined($string));
- my @a = split("\\|",$string);
- my ($hash) = $defs{$a[0]};
- my $name = $hash->{NAME};
- delete($hash->{helper}{RUNNING_PID});
- #Log3 ($name, 2, "$name: GetUpdatesDoit returned $string");
-
- readingsBeginUpdate ($hash);
- for (my $i = 1; $i < $#a; $i = $i + 2)
- {
- #my $v = GAEBUS_valueFormat ($hash, $$a[$i], $a[$i+1]);
- #readingsBulkUpdate ($hash, $a[$i], $v);
- readingsBulkUpdate ($hash, $a[$i], $a[$i+1]);
- }
- readingsEndUpdate($hash, 1);
-
- #HERE
- RemoveInternalTimer($hash);
- InternalTimer(gettimeofday()+$hash->{Interval}, "GAEBUS_GetUpdates", $hash, 0);
- }
- sub
- GAEBUS_GetUpdatesAborted($)
- {
- my ($hash) = @_;
- delete($hash->{helper}{RUNNING_PID});
- Log3 $hash->{NAME}, 3, "BlockingCall for ".$hash->{NAME}." was aborted";
- RemoveInternalTimer($hash);
- InternalTimer(gettimeofday()+$hash->{Interval}, "GAEBUS_GetUpdates", $hash, 0);
- }
- sub
- GAEBUS_valueFormat(@)
- {
- my ($hash, $reading, $values_ref) = @_;
- if (ref($hash->{helper}{valueFormat}) eq 'HASH' and defined ($reading))
- {
- if (exists($hash->{helper}{valueFormat}->{$reading})) {
- my $vf = $hash->{helper}{valueFormat}->{$reading};
- return sprintf ("$vf", @{$values_ref});
- }
- }
- return (defined(${$values_ref}[0]) ? ${$values_ref}[0] : "");
- }
- 1;
- =pod
- =item device
- =item summary device to communicate with ebusd (a communication bus for heating systems).
- =begin html
- <a name="GAEBUS"></a>
- <h3>GAEBUS</h3>
- <ul>
- <table>
- <tr><td>
- The GAEBUS module is the representation of a Ebus connector in FHEM.
- The GAEBUS module is designed to connect to ebusd (ebus daemon) via a socket connection (default is port 8888) <br>
- </td></tr>
- </table>
- <a name="GAEBUS"></a>
- <b>Define</b>
- <ul>
- <code>define <name> GAEBUS <device-addr>[:<port>] [<interval>];</code> <br>
- <br>
- <device-addr>[:<port>] specifies the host:port of the ebusd device. E.g.
- 192.168.0.244:8888 or servername:8888. When using the standard port, the port can be omitted.
- <br><br>
- Example:<br><br>
- <code>define ebus1 GAEBUS localhost 300</code>
- <br><br>
- When initializing the object no device specific commands are known. Please call "get ebusd_find" to read in supported commands from ebusd.<br>
- After fresh restart of ebusd it may take a while until all supported devices and their commands are visible.<br>
- </ul>
- <br>
- <a name="GAEBUS"></a>
- <b>Set </b>
- <ul>
- <li>reopen<br>
- Will close and open the socket connection.
- </li><br>
- <li>[r]~<class> <variable-name>~<comment><br>
- Will define a attribute with the following syntax:<br>
- [r]~<class>~<variable-name>~<br>
- Valid combinations are read from ebusd (using "get ebusd_find") and are selectable.<br>
- Values from the attributes will be used as the name for the reading which are read from ebusd in the interval specified.<br>
- The content of <comment$gt; is dropped and not added to the attribute name.<br>
- </li><br>
- <li>[w]~<class> <variable-name>~<comment><br>
- Will define a attribute with the following syntax:<br>
- [w]~<class>~<variable-name><br>
- They will only appear if the attribute "ebusWritesEnabled" is set to "1"<br>
- Valid combinations are read from ebusd (using "get ebusd_find") and are selectable.<br>
- Values from the attributes will be used for set commands to modify parameters for ebus devices<br>
- Hint: if the values for the attributes are prefixed by "set-" then all possible parameters will be listed in one block<br>
- The content of <comment$gt; is dropped and not added to the attribute name.<br>
- </li><br>
- </ul>
- <a name="GAEBUS"></a>
- <b>Get</b>
- <ul>
- <li>ebusd_info<br>
- Execude <i>info</i> command on ebusd and show result.
- </li><br>
- <li>ebusd_find<br>
- Execude <i>find</i> command on ebusd. Result will be used to display supported "set" and "get" commands.
- </li><br>
- <li>ebusd_hex<br>
- Will pass the input value to the "hex" command of ebusd. See "ebusctl help hex" for valid parameters.<br>
- This command is only available if "ebusWritesEnabled" is set to '1'.<br>
- </li><br>
- <li>reading <reading-name><br>
- Will read the actual value form ebusd and update the reading.
- </li><br>
- <li>[r]~<class> <variable-name>~<comment><br>
- Will read this variable from the ebusd and show the result as a popup.<br>
- Valid combinations are read from ebusd (using "get ebusd_find") and are selectable.<br>
- </li><br>
- <li>removeCommentFromAttributeNames<br>
- This will migrate the former used attribute names of format "[rw]~<class> <variable-name>~<comment>"
- into the format "[rw]~<class> <variable-name>".<br>
- It is only available if such attributes are defined.<br>
- </li><br>
- </ul>
- <br>
- <a name="GAEBUS"></a>
- <b>Attributes</b>
- <ul>
- <li><a href="#do_not_notify">do_not_notify</a></li><br>
- <li><a href="#attrdummy">disable</a></li><br>
- <li><a href="#attrdummy">dummy</a></li><br>
- <li><a href="#showtime">showtime</a></li><br>
- <li><a href="#loglevel">loglevel</a></li><br>
- <li>ebusWritesEnabled 0,1<br>
- disable (0) or enable (1) that commands can be send to ebus devices<br>
- See also description for Set and Get<br>
- If Attribute is missing, default value is 0 (disable writes)<br>
- </li><br>
- <li>Attributes of the format<br>
- <code>[r]~<class>~<variable-name></code><br>
- define variables that can be retrieved from the ebusd.
- They will appear when they are defined by a "set" command as described above.<br>
- The value assigned to an attribute specifies the name of the reading for this variable.<br>
- If ebusd returns a list of semicolon separated values then several semicolon separated readings can be defined.<br>
- "dummy" is a placeholder for a reading that will be ignored. (e.g.: temperature;dummy;pressure).<br>
- The name of the reading can be suffixed by "<:number>" which is a multiplicator for
- the evaluation within the specified interval. (eg. OutsideTemp:3 will evaluate this reading every 3-th cycle)<br>
- All text followed the reading seperated by a blank is given as an additional parameter to ebusd.
- This can be used to request a single value if more than one is retrieved from ebus.<br>
- </li><br>
- <li>Attributes of the format<br>
- <code>[w]~<class>~<variable-name></code><br>
- define parameters that can be changed on ebus devices (using the write command from ebusctl)
- They will appear when they are defined by a "set" command as described above.<br>
- The value assigned to an attribute specifies the name that will be used in set to change a parameter for a ebus device.<br>
- </li><br>
- <li>valueFormat<br>
- Defines a map to format values within GAEBUS.<br>
- All readings can be formated using syntax of sprinf.
- Values returned from ebusd are spearated by ";" and split before valueFormat is processed. This means more than one of the return values can be assigned to one reading.
- <br>
- Example: { "temperature" => "%0.2f"; "from-to" => "%s-%s" }<br>
- </li><br>
- </ul>
- <br>
- </ul>
- =end html
- =cut
|