From 005ca11a9e66bf383e8cf74786f922efa3822b64 Mon Sep 17 00:00:00 2001 From: rudolfkoenig <> Date: Tue, 30 Jan 2007 12:47:36 +0000 Subject: [PATCH] Initial version git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@3 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- CHANGED | 301 +++ FHEM/00_FHZ.pm | 574 ++++++ FHEM/10_FS20.pm | 331 ++++ FHEM/20_FHT.pm | 404 ++++ FHEM/30_HMS.pm | 262 +++ FHEM/40_KS300.pm | 308 +++ FHEM/50_WS300.pm | 698 +++++++ FHEM/90_FileLog.pm | 97 + Makefile | 27 + contrib/91_DbLog.pm | 99 + contrib/99_ALARM.pm | 153 ++ contrib/99_PRIV.pm | 33 + contrib/99_SUNRISE.pm | 90 + contrib/README | 21 + contrib/checkmsg.pl | 26 + contrib/crc.pl | 33 + contrib/fht.gnuplot | 33 + contrib/four2hex/Makefile | 6 + contrib/four2hex/README | 23 + contrib/four2hex/four2hex | Bin 0 -> 5777 bytes contrib/four2hex/four2hex.c | 94 + contrib/fs20_holidays.sh | 94 + contrib/garden.pl | 212 +++ contrib/init-scripts/fhem.1 | 46 + contrib/init-scripts/fhem.2 | 25 + contrib/ks300avg.pl | 77 + contrib/rolwzo_not_off.sh | 15 + contrib/rrd/98_RRD.pm | 76 + contrib/rrd/fhz1000-rrd-howto.txt | 52 + contrib/serial.pl | 89 + docs/README | 6 + docs/commandref.html | 1176 ++++++++++++ docs/faq.html | 163 ++ docs/fhem.html | 218 +++ docs/ftdi_sio.fhz1000.patch | 44 + docs/pgm1-1.gif | Bin 0 -> 32763 bytes docs/pgm1-2.gif | Bin 0 -> 9097 bytes docs/pgm2-1.gif | Bin 0 -> 35528 bytes docs/pgm2-2.png | Bin 0 -> 6663 bytes docs/pgm3-0.5.1.png | Bin 0 -> 95277 bytes docs/pgm4.gif | Bin 0 -> 29120 bytes docs/raw-codes | 16 + docs/rm100 | 28 + docs/snippet_1.txt | 74 + docs/snippet_2.txt | 102 + docs/snippet_3.txt | 60 + docs/snippet_4.txt | 39 + docs/snippet_5.txt | 60 + docs/snippet_6.txt | 127 ++ docs/snippet_7.txt | 14 + docs/web-tipps | 17 + examples/01_fs20 | 18 + examples/02_fs20 | 72 + examples/03_fht | 30 + examples/04_log | 49 + examples/05_rm100 | 23 + examples/06_at | 39 + fhem.pl | 1659 +++++++++++++++++ test/fhem.cfg | 26 + test/fhem.log | 12 + test/fhem.save | 75 + test/fhem_small.cfg | 21 + test/fhem_small.save | 37 + test/fhz.pid | 1 + test/fl-2006-13.log | 423 +++++ test/fl-2006-14.log | 160 ++ test/out-2006-19.log | 916 +++++++++ test/wz-2006-13.log | 1003 ++++++++++ test/wz-2006-14.log | 116 ++ webfrontend/pgm1/htdocs/index.html | 379 ++++ webfrontend/pgm1/scripts/hfm_get.pl | 37 + webfrontend/pgm1/scripts/hfm_set.pl | 72 + webfrontend/pgm1/scripts/hfm_tc.pl | 48 + webfrontend/pgm1/scripts/xsl/hfm_tc.xsl | 77 + webfrontend/pgm2/FS20.off.gif | Bin 0 -> 609 bytes webfrontend/pgm2/FS20.on.gif | Bin 0 -> 377 bytes webfrontend/pgm2/README | 20 + webfrontend/pgm3/config.php | 142 ++ webfrontend/pgm3/docs/README | 4 + webfrontend/pgm3/docs/copyright_fonts.txt | 124 ++ webfrontend/pgm3/docs/gnuplot/README | 5 + webfrontend/pgm3/docs/logrotate/README | 10 + .../pgm3/docs/logrotate/fhz1000.logrotate | 11 + .../pgm3/docs/logrotate/logrotate.fht.sh | 24 + .../docs/logrotate/logrotate.hms100.ks300.sh | 30 + webfrontend/pgm3/docs/misc/99_ALARM.pm | 150 ++ webfrontend/pgm3/docs/misc/99_FHTswitch.pm | 80 + webfrontend/pgm3/docs/misc/README | 4 + webfrontend/pgm3/include/FS20.off.gif | Bin 0 -> 609 bytes webfrontend/pgm3/include/FS20.on.gif | Bin 0 -> 377 bytes webfrontend/pgm3/include/Vera.ttf | Bin 0 -> 65932 bytes webfrontend/pgm3/include/VeraBd.ttf | Bin 0 -> 58716 bytes webfrontend/pgm3/include/fht.php | 173 ++ webfrontend/pgm3/include/fhtpulldown.php | 95 + webfrontend/pgm3/include/fs20.ico | Bin 0 -> 1406 bytes webfrontend/pgm3/include/fs20.php | 91 + webfrontend/pgm3/include/fs20pulldown.php | 80 + webfrontend/pgm3/include/functions.php | 41 + webfrontend/pgm3/include/gnuplot.php | 109 ++ webfrontend/pgm3/include/hms100.php | 366 ++++ webfrontend/pgm3/include/ks300.php | 361 ++++ webfrontend/pgm3/include/room.php | 48 + webfrontend/pgm3/include/style.css | 51 + webfrontend/pgm3/index.php | 770 ++++++++ webfrontend/pgm4/README | 1 + webfrontend/pgm4/fs20.php | 240 +++ webfrontend/pgm4/images/EG.gif | Bin 0 -> 6510 bytes 107 files changed, 14766 insertions(+) create mode 100644 CHANGED create mode 100755 FHEM/00_FHZ.pm create mode 100755 FHEM/10_FS20.pm create mode 100755 FHEM/20_FHT.pm create mode 100755 FHEM/30_HMS.pm create mode 100755 FHEM/40_KS300.pm create mode 100644 FHEM/50_WS300.pm create mode 100755 FHEM/90_FileLog.pm create mode 100644 Makefile create mode 100755 contrib/91_DbLog.pm create mode 100755 contrib/99_ALARM.pm create mode 100755 contrib/99_PRIV.pm create mode 100755 contrib/99_SUNRISE.pm create mode 100755 contrib/README create mode 100755 contrib/checkmsg.pl create mode 100755 contrib/crc.pl create mode 100644 contrib/fht.gnuplot create mode 100644 contrib/four2hex/Makefile create mode 100644 contrib/four2hex/README create mode 100755 contrib/four2hex/four2hex create mode 100644 contrib/four2hex/four2hex.c create mode 100755 contrib/fs20_holidays.sh create mode 100755 contrib/garden.pl create mode 100755 contrib/init-scripts/fhem.1 create mode 100755 contrib/init-scripts/fhem.2 create mode 100755 contrib/ks300avg.pl create mode 100755 contrib/rolwzo_not_off.sh create mode 100644 contrib/rrd/98_RRD.pm create mode 100644 contrib/rrd/fhz1000-rrd-howto.txt create mode 100755 contrib/serial.pl create mode 100644 docs/README create mode 100644 docs/commandref.html create mode 100644 docs/faq.html create mode 100644 docs/fhem.html create mode 100644 docs/ftdi_sio.fhz1000.patch create mode 100644 docs/pgm1-1.gif create mode 100644 docs/pgm1-2.gif create mode 100644 docs/pgm2-1.gif create mode 100644 docs/pgm2-2.png create mode 100644 docs/pgm3-0.5.1.png create mode 100644 docs/pgm4.gif create mode 100644 docs/raw-codes create mode 100644 docs/rm100 create mode 100644 docs/snippet_1.txt create mode 100644 docs/snippet_2.txt create mode 100644 docs/snippet_3.txt create mode 100644 docs/snippet_4.txt create mode 100644 docs/snippet_5.txt create mode 100644 docs/snippet_6.txt create mode 100644 docs/snippet_7.txt create mode 100644 docs/web-tipps create mode 100644 examples/01_fs20 create mode 100644 examples/02_fs20 create mode 100644 examples/03_fht create mode 100644 examples/04_log create mode 100644 examples/05_rm100 create mode 100644 examples/06_at create mode 100755 fhem.pl create mode 100644 test/fhem.cfg create mode 100644 test/fhem.log create mode 100644 test/fhem.save create mode 100644 test/fhem_small.cfg create mode 100644 test/fhem_small.save create mode 100644 test/fhz.pid create mode 100644 test/fl-2006-13.log create mode 100644 test/fl-2006-14.log create mode 100644 test/out-2006-19.log create mode 100644 test/wz-2006-13.log create mode 100644 test/wz-2006-14.log create mode 100755 webfrontend/pgm1/htdocs/index.html create mode 100755 webfrontend/pgm1/scripts/hfm_get.pl create mode 100755 webfrontend/pgm1/scripts/hfm_set.pl create mode 100755 webfrontend/pgm1/scripts/hfm_tc.pl create mode 100755 webfrontend/pgm1/scripts/xsl/hfm_tc.xsl create mode 100644 webfrontend/pgm2/FS20.off.gif create mode 100644 webfrontend/pgm2/FS20.on.gif create mode 100644 webfrontend/pgm2/README create mode 100644 webfrontend/pgm3/config.php create mode 100644 webfrontend/pgm3/docs/README create mode 100644 webfrontend/pgm3/docs/copyright_fonts.txt create mode 100644 webfrontend/pgm3/docs/gnuplot/README create mode 100644 webfrontend/pgm3/docs/logrotate/README create mode 100644 webfrontend/pgm3/docs/logrotate/fhz1000.logrotate create mode 100755 webfrontend/pgm3/docs/logrotate/logrotate.fht.sh create mode 100755 webfrontend/pgm3/docs/logrotate/logrotate.hms100.ks300.sh create mode 100755 webfrontend/pgm3/docs/misc/99_ALARM.pm create mode 100644 webfrontend/pgm3/docs/misc/99_FHTswitch.pm create mode 100644 webfrontend/pgm3/docs/misc/README create mode 100644 webfrontend/pgm3/include/FS20.off.gif create mode 100644 webfrontend/pgm3/include/FS20.on.gif create mode 100644 webfrontend/pgm3/include/Vera.ttf create mode 100644 webfrontend/pgm3/include/VeraBd.ttf create mode 100755 webfrontend/pgm3/include/fht.php create mode 100644 webfrontend/pgm3/include/fhtpulldown.php create mode 100644 webfrontend/pgm3/include/fs20.ico create mode 100755 webfrontend/pgm3/include/fs20.php create mode 100644 webfrontend/pgm3/include/fs20pulldown.php create mode 100644 webfrontend/pgm3/include/functions.php create mode 100644 webfrontend/pgm3/include/gnuplot.php create mode 100755 webfrontend/pgm3/include/hms100.php create mode 100755 webfrontend/pgm3/include/ks300.php create mode 100755 webfrontend/pgm3/include/room.php create mode 100644 webfrontend/pgm3/include/style.css create mode 100644 webfrontend/pgm3/index.php create mode 100644 webfrontend/pgm4/README create mode 100644 webfrontend/pgm4/fs20.php create mode 100644 webfrontend/pgm4/images/EG.gif diff --git a/CHANGED b/CHANGED new file mode 100644 index 000000000..9cebe924d --- /dev/null +++ b/CHANGED @@ -0,0 +1,301 @@ +- 2005-10-27 (1.3) + - Bugfix: multiple at commands at the same time. + +- 2005-11-10 (1.4) + - Reformatting the package and the documentation + - New links + +- 2005-12-26 (1.5) + - "modularized" in preparation for the FHZ80B -> each device has a type + - added relative "at" commands (with +HH:MM:SS) + - multiple commands on one line separated with ; + - sleeping 0.22 seconds after an ST command + - some commands/syntax changed: + - switch => set + - device => fhzdevice + - define ... => define ... + - the state of the devices and the at commands are saved + - at start always sending a "set 0001 00 01" to enable the FHZ receiever. + This is a workaround. + - doc rewrite, examples directory + +- 2006-01-03 (1.6) + - signal handling (to save the state on shutdown) + - module FHZ addded (for the FHZ1000PC device itself) + - added the get function (to make the initialization prettier) + - the module ST was renamed to FS20 + - FS20 timer commands added + - modules command removed (we are loading everything from the modpath + directory) + - FHT80b module added (yes, it is already useful, you can set + and view a lot of values) + - documentation adapted + - Added a TODO file + +- 2006-01-04 (1.7) + - the at command can be used to execute something repeatedly with * + - ntfy can filter on device or on device+event with a regexp + - checking the delete and notify regexps if they make sense + - the FHT init string is now a set command (refreshvalues) + - shutdown saves the detailed device information too + +- 2006-01-05 (1.8) + - Bugfix: detailed FS20 status was not set from external event + - Bugfix: setstate for FS20 returned the last state set + - Bugfix: undefined FS20 devices (can) crash the server + - HMS module added by Martin Mueller + (currently supporting the HMS100T & HMS100TF) + - Log modules added, the first one being a simple FileLog + (inspired by Martin Mueller) + - A little gnuplot script to display temperature and actuator changes + +- 2006-02-10 (1.9) + (aka as the Juergen release) + - The FHZ1300 is reported to work + - Bugfix: spaces before comment in the config file should be ignored + - added FS20STR codes to 10_FS20.pm + - names restricted to A-Za-z0-9.:- (especially _ is not allowed) + - delete calles now an UndefFn in the module + - implementation of FS20 function group/local master/global master + - the list command tells you the definition of the device too + +- 2006-02-12 (1.9a) + - Bugfix: wrong rights for HMS and wrong place for readonly + (thanks to Juergen) + +- 2006-02-12 (1.9b) + - Bugfix: Fixing the same bug again (thanks to Martin) + +- 2006-04-02 (2.0) + - XmlList and webfrontend/pgm1 programs from Raoul Matthiessen + - list tries to display the state and not the last command + - Both log facilities (FileLog and Log) take wildcards + (week, year, month, etc) to make logfile rotating easier + - webfrontend/pgm2 + +- 2006-04-15 (2.1) + - webfrontend/pgm2 changes: + - make it work on Asus dsl-routers (no "use warnings") + - css/readonly configurable + - Formatting for HMS data + - comments can be added to each device (setstate comment:xxx) + - testbed to dry-test functionality (test directory) + - added an empty hull for the KS300 weather module + - added undocumented "exec" function to call arbitrary program parts + for debugging. Example: exec FhzDecode("81xx04xx0101a0011234030011"); + - webfrontend/pgm3, contributed by Martin Haas + - fixed pgm1: changing values should work now + +- 2006-05-20 (2.2) + - FHZ1300 support verified (+ doc changes) + - KS300 support added (with Temperature, Humidity, Wind speed, Rain). + Not verified/undecoded: subzero temp, weak battery, Is-raining flag, + wind speed > 100km/h + - webpgm2 log fix for "offed" FHT devices (with no actuator data) + - webpgm3 upgrade (by Martin Haas, see webpgm/pgm3/docs/CHANGES for details) + - HMS logging/state format changed to make it similar to KS300 + - added HMS100WD (thanks to Sascha Pollok) + - ntfy/logging changed to be able to notify for multiple attributes + arriving in one message + - central FHTcode settable (see commandref.html) + - optionally listen for non-local requests (port global) + - unknown logging + - FAQ + +- 2006-6-22 (2.3) + - CRC checking (i.e. ignoring messages with bad CRC, message on verbose 4) + - contrib/checkmesg.pl added to check message consistency (debugging) + - FHT: unknown_aa, unknown_ba codes added. What they are for? + - Empty modpath / no modpath error messages added (some user think modpath is + superfluous) + - Unparsed messages (verbose 5) now printed as hex + - Try to reattach to the usb device if it disappears: no need to + restart the server if the device is pulled out from the USB socket and + plugged in again (old versions go into a busy loop here). + - Supressing the seldom (ca 1 out of 700) short KS300 messages. + (not sure how to interpret them) + - Added KS300 "israining" status flag. Note: this not always triggers when it + is raining, but there seems to be a correlation. To be evaluated in more + detail. + - notifyon can now execute "private" perl code as well (updated + commandref.html, added the file example/99_PRIV.pm) + - another "perl code" example is logging the data into the database + (with DBI), see the file contrib/91_DbLog.pm. Tested with an Oracle DB. + - logs added to the xmllist + - FHT80b: Fix measured-temp over 25.5 (handling the tempadd messages better) + +- 2006-07-23 (2.4) + - contrib/four2hex (to convert between ELV and our codes) by Peter Stark + - make dist added to set version (it won't work in a released version) + - reload function to reload (private) perl modules + - 20_FHT.pm fix: undef occures once without old data + - "setstate comment" is replaced with the attr command (i.e. attribute). + The corresponding xmllist COMMENT tag is replaced with the ATTR tag. + Devices or logs can have attr definitions. + - webfrontend/pgm2 (fhzweb.pl) updated to handle "room" attributes(showing + only devices in this room). + - version 0.4.2 of webfrontend/pgm3 integrated. + - contrib/ks300avg.pl to compute daily and monthly avarage values. + - the 40_KS300.pm module is computing daily and monthly avarages for the + temp/hum and wind values and sum of the rain. The cum_day and cum_month + state variables are used as helper values. To log the avarage use the + .*avg.* regexp. The regexp for the intraday log will trigger it also. + - Added the contrib file garden.pl as a more complex example: garden + irrigation. The program computes the time for irrigation from the avarage + temperature reported by the ks300-2. + - Enable uppercase hex codes (Bug reported by STefan Mayer) + - Renamed the unknown_XX FHT80b codes to code_XXXXXX, this will produce + "Undefined type" messages when reading the old save file + - RM100-2 added (thanks for the codes from andikt). + +- 2006-08-13 (2.5) + Special thanks to STefan Mayer for a lot of suggestions and bug reports + - If a command is enclosed in {}, then it will be evaluated as a perl + expression, if it is enclosed in "" then it is a shell command, else it is + a "normal" fhz1000 command. + "at" and "notifyon" follow this convention too. + Note: a shell command will always be issued in the background. + - won't die anymore if the at spec contains an unknown command + - rereadcfg added. Sending a HUP should work better now + - escaping % and @ in the notify argument is now possible with %% or @@ + - new command trigger to test notify commands + - where you could specify an fhz command, now you can specify a list of + them, separated by ";". Escape is ;; + - KS300 sometimes reports "negative" rain when it begins to rain. Filter + such values. israining is set when the raincounter changed or the ks300 + israining bit is set. + - sleep command, with millisecond accuracy + - HMS 100MG support by Peter Stark. + - Making FHT and FS20 messages more uniform + - contrib/fs20_holidays.sh by STefan Mayer + (simulate presence while on holiday) + - webfrontends/pgm4 by STefan Mayer: fs20.php + - KS300 avg. monthly values fixed (hopefully) + - deleted undocumented "exec" function (you can write it now as {...}) + +- 2006-09-08 (2.6) + - bugfix: updated the examples (hint from Juergen) + - bugfix: leading and trailing whitespaces in commands are ignored now + - feature: making life easier for perl oneliners: see commandref.html + (motivated by STefans suggestions) + - feature: include command and multiline commands in the configfiles (\) + - bugfix: web/pgm2 KS300 rain plot knows about the avg data + - bugfix: the FHT > 25.5 problem. Needs to be tested. + - feature: log unknown devices (peters idea, see notifyon description) + - feature: HMS wildcard device id for all HMS devices. See the define/HMS + section in the commandref.html for details. + NOTE: the wildcard for RM100-2 changed from 1001 to 1003. + (peters idea) + - feature: rolwzo_no_off.sh contrib file (for those who were already closed + out by automatically closing rollades, by Martin) + - feature: the current version (0.4.5) of the pgm3 from Martin. + +- 2006-09-13 (2.6a) + - bugfix: the FHT > 25.5 problem again. A never ending story. + +- 2006-10-03 (2.7) + - bugfix: Another try on the > 25.5 problem. (Peters suggestion) + - feature: 99_ALARM.pm from Martin (in the contrib directory) + - feature: HMS100TFK von Peter P. + - feature: attribute loglevel + - feature: attribute dummy + - feature: attr command documented + - feature: the current version (0.5a) of the pgm3 from Martin. + +- 2006-11-08 (2.8) + - feature: store oldvalue for triggers. perl only. requested by peter. + - feature: inform cmd. Patch by Martin. There are many Martins around here :-) + - bugfix: XML: fix & and < and co + - bugfix: Accept KS300 negative temperature values + - change: the FS20 msg "rain-msg" is called now "activate" + - feature: start/stop rc script from Stefan (in the contrib directory) + - feature: attribute extra_notify: setting the device will trigger a notify + - feature: optional repeat count for the at command + - feature: skip_next attribute for the at command + - feature: WS300 support by Martin. Check the contrib/ws300 directory. + - bugfix: 91_DbLog.pm: retry if the connection is broken by Peter + - feature: Martin's pgm3-0.5.2 (see the CHANGELOG on his webpage) + - feature: RRD logging example by Peter (in the contrib/rrd directory) + +- 2006-11-19 (2.9) + - bugfix: fhz1000.pl dies at startup if the savefile does not exist + - bugfix: oldvalue hash is not initialized at startup (peter, Nov 09) + - feature: Notify reorganization (requested by juergen and matthias) : + - inform will be notified on both real events and set or trigger commands + - filelogs will additionally be notified on set or trigger commands + - the extra_notify flag is gone: it is default now, there is a + do_not_notify flag for the opposite behaviour. + - feature: at timespec as a function. Example: at +*{sunset()} + commandref.html and examples revisited. + - feature: 99_SUNRISE.pm added to use with the new at functionality + (replaces the old 99_SUNSET.pm) + - feature: webpgm2 "everything" room, at/notify section, arbitrary command + - bugfix: resetting the KS300 + - feature: updated ws300pc (from martin klerx, Nov 08) + - bugfix: parsing timed commands implemented => thermo-off,thermo-on and + activate replaced with timed off-for-timer,on-for-timer and + on-old-for-timer (reported by martin klerx, Nov 08) + - feature: pidfile (requested by peter, Nov 10) + - bugfix: function 81 is not allowed + +- 2006-11-27 (2.9a) + - bugfix: FileLog+Unknown device generates undefined messages + - bugfix: trigger with unknown device generates undefined messages + +- 2006-12-28 (3.0) + - bugfix: KS300: Make the temperature negative, not the humidity + - bugfix: generate correct xmllist even with fhzdev none (Martin, 12.12) + - feature: one set command can handle multiple devices (range and enumeration) + - feature: new FS20 command on-till + - feature: perl: the current state is stored in the %value hash + - feature: perl: sunset renamed to sunset_rel, sunset_abs added (for on-till) + - feature: perl: isday function added + - feature: follow-on-for-timer attribute added to set the state to off + - bugfix: the ws300pc negative-temp bugfix included (from Martin Klerx) + - feature: version 0.6.2 of the webpgm3 included (from Martin Haas) + +- 2007-01-08 (3.1) + - bugfix: delete checks the arg first "exactly", then as a regexp + - bugfix: sun*_rel does not work correctly with offset (Martin) + - feature: FAQ entry on how to install the sunrise stuff. + - feature: the inner core is modified to be able to handle + more than one "IO" device, i.e multiple FHZ at the same time, + or FHZ + FS10 + WS300. Consequences: + - "fhzdev " replaced with "define FHZ " + - "sendraw " replaced with "set raw " + - module function parameters changed (for module developers) + - set FHZ activefor dev + - select instead sleep after sending FHZ commands + - the at timer is more exact (around 1msec instead of 1 sec) + - ignoring FS20 device 0001/00/00 + - feature: contrib/serial.pm to debug serial devices. + - feature: WS300 integrated: no external program needed (Martin) + - feature: updated to pgm3-0.7.0, see the CHANGELOG at Martins site + +- 2007-01-14 (3.2) + - bugfix: example $state changed to $value (remco) + - bugfix: sun*_rel does not work correctly with offset (Sebastian) + - feature: new HMS100TF codes (Sebastian) + - feature: logging unknown HMS with both unique and class ID (Sebastian) + - feature: WS300: "Wetter-Willi-Status", rain_raw/rain_cum added, historic + data (changes by Martin & Markus) + - bugfix: broken rereadcfg / CommandChain after init + (reported by Sebastian and Peter) + - bugfix: sunrise_coord returned "3", which is irritating + +- 2007-01-25 (3.3) + - bugfix: 50_WS300.pm fix from Martin + - bugfix: pidfile does not work as expected (reported by Martin) + - bugfix: %U in the log-filename is wrong (bugreport by Juergen) + - feature: %V added to the log-filename + - feature: KS300 wind calibration possibility added + - feature: (software) filtering repeater messages (suggested by Martin) + - feature: the "client" fhz1000.pl can address another host + - bugfix: empty FHT battery is not reported (by Holger) + - feature: new FHT codes, e.g. month/day/hour/minute setting (by Holger) + +- ==DATE== (3.4) + - bugfix: deny at +{3}... (only +*{3} allowed), reported by Bernd, 25.02 + - bugfix: allow numbers greater then 9 in at +{} + - feature: new 50_WS300.pm from Martin (bugfix + rain statistics, 26.02) + - feature: renamed fhz1000 to fhem diff --git a/FHEM/00_FHZ.pm b/FHEM/00_FHZ.pm new file mode 100755 index 000000000..d6c8e6d8f --- /dev/null +++ b/FHEM/00_FHZ.pm @@ -0,0 +1,574 @@ +############################################## +package main; + +use strict; +use warnings; +use Time::HiRes qw(gettimeofday); +use Device::SerialPort; + +sub FHZ_Write($$$); +sub FHZ_Read($); +sub FHZ_ReadAnswer($$); +sub FhzCrc(@); +sub CheckFhzCrc($); + +my $fhzdata = ""; +my $msgstart = pack('H*', "81");# Every msg starts wit this + +my %gets = ( + "init1" => "c9 02011f64", + "init2" => "c9 02011f60", + "init3" => "c9 02011f0a", + "serial" => "04 c90184570208", + "fhtbuf" => "04 c90185", +); +my %sets = ( + "time" => "c9 020161", + "initHMS" => "04 c90186", + "initFS20" => "04 c90196", + "FHTcode" => "04 c901839e0101", + + "activefor"=> "xx xx", + "raw" => "xx xx", +); +my %setnrparam = ( + "time" => 0, + "initHMS" => 0, + "initFS20" => 0, + "FHTcode" => 0, + "activefor"=> 1, + "raw" => 2, +); + +my %codes = ( + "^8501..\$" => "fhtbuf", +); + +my %readings; +my $def; +my %msghist; # Used when more than one FHZ is attached +my $msgcount = 0; + +##################################### +# Note: we are a data provider _and_ a consumer at the same time +sub +FHZ_Initialize($) +{ + my ($hash) = @_; + + + $hash->{Category}= "DEV"; + +# Provider + $hash->{ReadFn} = "FHZ_Read"; + $hash->{WriteFn} = "FHZ_Write"; + $hash->{Clients} = ":FHZ:FS20:FHT:HMS:KS300:"; + +# Consumer + $hash->{Match} = "^81..C9..0102"; + $hash->{DefFn} = "FHZ_Define"; + $hash->{UndefFn} = "FHZ_Undef"; + $hash->{GetFn} = "FHZ_Get"; + $hash->{SetFn} = "FHZ_Set"; + $hash->{StateFn} = "FHZ_SetState"; + $hash->{ListFn} = "FHZ_List"; + $hash->{ParseFn} = "FHZ_Parse"; +} + +##################################### +sub +FHZ_Set($@) +{ + my ($hash, @a) = @_; + + return "Need one to three parameter" if(@a > 4); + return "invalid parameter, use one of:\n " . join("\n ", sort keys %sets) + if(!defined($sets{$a[1]})); + return "Wrong number of parameters, need " . ($setnrparam{$a[1]} + 2) + if(@a != ($setnrparam{$a[1]} + 2)); + + my ($fn, $arg) = split(" ", $sets{$a[1]}); + + my $v = join(" ", @a); + Log GetLogLevel("FHZ"), "FHZ set $v"; + + if($a[1] eq "activefor") { + + my $dhash = $defs{$a[2]}; + return "device $a[2] unknown" if(!defined($dhash)); + + return "Cannot handle $dhash->{TYPE} devices" + if($devmods{FHZ}->{Clients} !~ m/:$dhash->{TYPE}:/); + + $dhash->{IODev} = $hash; + return undef; + + } elsif($a[1] eq "raw") { + + $fn = $a[2]; + $arg = $a[3]; + + } elsif($a[1] eq "time") { + + my @t = localtime; + $arg .= sprintf("%02x%02x%02x%02x%02x", + $t[5]%100, $t[4]+1, $t[3], $t[2], $t[1]); + + } elsif($a[1] eq "FHTcode") { + + return "invalid argument, must be hex" if(!$a[2] || + $a[2] !~ m/^[A-F0-9]{2}$/); + $arg .= $a[2]; + + } + + FHZ_Write($hash, $fn, $arg) if(!IsDummy("FHZ")); + return undef; +} + +##################################### +sub +FHZ_Get($@) +{ + my ($hash, @a) = @_; + + return "\"get FHZ\" needs only one parameter" if(@a != 2); + if(!defined($gets{$a[1]})) { + return "Unknown set value $a[1], please specify one of: " . + join(" ", sort(keys %gets)); + } + + my ($fn, $arg) = split(" ", $gets{$a[1]}); + + my $v = join(" ", @a); + Log GetLogLevel("FHZ"), "FHZ get $v"; + + FHZ_Write($hash, $fn, $arg) if(!IsDummy("FHZ")); + + my $msg = FHZ_ReadAnswer($hash, $a[1]); + return $msg if(!$msg || $msg !~ /^81..c9..0102/); + + if($a[1] eq "serial") { + $v = substr($msg, 22, 8) + + } elsif($a[1] eq "fhtbuf") { + $v = substr($msg, 16, 2); + + } else { + $v = substr($msg, 12); + } + $readings{$a[1]}{VAL} = $v; + $readings{$a[1]}{TIM} = TimeNow(); + + return "$a[0] $a[1] => $v"; +} + +##################################### +sub +FHZ_List($) +{ + my ($hash) = @_; + + my $str = ""; + foreach my $m (sort keys %readings) { + $str .= sprintf("%-19s %-15s %s\n", + $readings{$m}{TIM},$m,$readings{$m}{VAL}); + } + return $str; +} + +##################################### +sub +FHZ_SetState($$$$) +{ + my ($hash, $tim, $vt, $val) = @_; + + return "Undefined value $vt" if(!defined($gets{$vt})); + + if(!$readings{$vt} || $readings{$vt}{TIM} lt $tim) { + $readings{$vt}{TIM} = $tim; + $readings{$vt}{VAL} = $val; + } + return undef; +} + + +##################################### +sub +DoInit($) +{ + my $name = shift; + my @init; + push(@init, "get $name init2"); + push(@init, "get $name serial"); + push(@init, "set $name initHMS"); + push(@init, "set $name initFS20"); + push(@init, "set $name time"); + + # Workaround: Sending "set 0001 00 off" after initialization to enable + # the fhz1000 receiver, else we won't get anything reported. + push(@init, "set $name raw 04 01010100010000"); + + CommandChain(3, \@init); +} + +##################################### +sub +FHZ_Define($$) +{ + my ($hash, @a) = @_; + + $hash->{STATE} = "Initialized"; + + delete $hash->{PortObj}; + delete $hash->{FD}; + + my $dev = $a[2]; + if($dev eq "none") { + Log 1, "FHZ device is none, commands will be echoed only"; + return undef; + } + + my $po = new Device::SerialPort ($dev); + return "Can't open $dev: $!\n" if(!$po); + + $po->reset_error(); + $po->baudrate(9600); + $po->databits(8); + $po->parity('none'); + $po->stopbits(1); + $po->handshake('none'); + + $hash->{PortObj} = $po; + $hash->{FD} = $po->FILENO; + $hash->{DeviceName} = $dev; + + DoInit($a[0]); + return undef; +} + +##################################### +sub +FHZ_Undef($$) +{ + my ($hash, $arg) = @_; + foreach my $d (keys %defs) { + if(defined($defs{$d}) && + defined($defs{$d}{IODev}) && + $defs{$d}{IODev} == $hash) + { + Log 4, "deleting port for $d"; + delete $defs{$d}{IODev}; + } + } + $hash->{PortObj}->close(); + return undef; +} + +##################################### +sub +FHZ_Parse($$) +{ + my ($hash,$msg) = @_; + + my $omsg = $msg; + $msg = substr($msg, 12); # The first 12 bytes are not really interesting + + my $type = ""; + foreach my $c (keys %codes) { + if($msg =~ m/$c/) { + $type = $codes{$c}; + last; + } + } + + if(!$type) { + Log 4, "FHZ unknown: $omsg"; + $def->{CHANGED}[0] = "$msg"; + return $hash->{NAME}; + } + + + if($type eq "fhtbuf") { + $msg = substr($msg, 4, 2); + } + + Log 4, "FHZ $type: $msg)"; + $def->{CHANGED}[0] = "$type: $msg"; + return $hash->{NAME}; +} + +##################################### +sub +FhzCrc(@) +{ + my $sum = 0; + map { $sum += $_; } @_; + return $sum & 0xFF; +} + +##################################### +sub +CheckFhzCrc($) +{ + my $msg = shift; + return 0 if(length($msg) < 8); + + my @data; + for(my $i = 8; $i < length($msg); $i += 2) { + push(@data, ord(pack('H*', substr($msg, $i, 2)))); + } + my $crc = hex(substr($msg, 6, 2)); + + # FS20 Repeater generate a CRC which is one or two greater then the computed + # one. The FHZ1000 filters such pakets, so we do not see them + return (($crc eq FhzCrc(@data)) ? 1 : 0); +} + + +##################################### +# This is a direct read for commands like get +sub +FHZ_ReadAnswer($$) +{ + my ($hash,$arg) = @_; + + return undef if(!$hash || !defined($hash->{FD})); + + my ($mfhzdata, $rin) = ("", ''); + + for(;;) { + + vec($rin, $hash->{FD}, 1) = 1; + my $nfound = select($rin, undef, undef, 3); + if($nfound < 0) { + next if ($! == EAGAIN() || $! == EINTR() || $! == 0); + die("Select error $nfound / $!\n"); + } + return "Timeout reading answer for get $arg" if($nfound == 0); + + my $buf = $hash->{PortObj}->input(); + + Log 5, "FHZ/RAW: " . unpack('H*',$buf); + $mfhzdata .= $buf; + next if(length($mfhzdata) < 2); + + my $len = ord(substr($mfhzdata,1,1)) + 2; + if($len>20) { + Log 1, "Oversized message (" . unpack('H*',$mfhzdata) . + "), dropping it ..."; + return undef; + } + return unpack('H*', $mfhzdata) if(length($mfhzdata) == $len); + } +} + +############## +sub +FHZ_CompleteMsg($$) +{ + my ($fn,$msg) = @_; + my $len = length($msg); + my @data; + for(my $i = 0; $i < $len; $i += 2) { + push(@data, ord(pack('H*', substr($msg, $i, 2)))); + } + return pack('C*', 0x81, $len/2+2, ord(pack('H*',$fn)), FhzCrc(@data), @data); +} + +##################################### +sub +FHZ_Write($$$) +{ + my ($hash,$fn,$msg) = @_; + + if(!$hash || !defined($hash->{PortObj})) { + Log 5, "FHZ device $hash->{NAME} is not active, cannot send"; + return; + } + + ############### + # insert value into the msghist. At the moment this only makes sense for FS20 + # devices. As the transmitted value differs from the received one, we have to + # recompute. + if($fn eq "04" && substr($msg,0,6) eq "010101") { + my $nmsg = "0101a001" . substr($msg, 6, 6) . "00" . substr($msg, 12); + $msghist{$msgcount}{TIME} = gettimeofday(); + $msghist{$msgcount}{NAME} = $hash->{NAME}; + $msghist{$msgcount}{MSG} = unpack('H*', FHZ_CompleteMsg($fn, $nmsg)); + $msgcount++; + } + + my $bstring = FHZ_CompleteMsg($fn, $msg); + Log 5, "Sending " . unpack('H*', $bstring); + + if(!$hash->{QUEUECNT}) { + $hash->{PortObj}->write($bstring); + ############## + # Write the next buffer not earlier than 0.22 seconds (= 65.6ms + 10ms + + # 65.6ms + 10ms + 65.6ms), else it will be discarded by the FHZ1X00 PC + InternalTimer(gettimeofday()+0.25, "FHZ_HandleWriteQueue", $hash); + } elsif($hash->{QUEUECNT} == 1) { + $hash->{QUEUE} = [ $bstring ]; + } else { + push(@{$hash->{QUEUE}}, $bstring); + } + $hash->{QUEUECNT}++; + + +} + +##################################### +sub +FHZ_HandleWriteQueue($) +{ + my $hash = shift; + my $cnt = --$hash->{QUEUECNT}; + if($cnt > 0) { + my $bstring = shift(@{$hash->{QUEUE}}); + $hash->{PortObj}->write($bstring); + InternalTimer(gettimeofday()+0.25, "FHZ_HandleWriteQueue", $hash); + } +} + +##################################### +sub +FHZ_Read($) +{ + my ($hash) = @_; + + my $buf = $hash->{PortObj}->input(); + my $iohash = $devmods{$hash->{TYPE}}; + my $name = $hash->{NAME}; + + ########### + # Lets' try again: Some drivers return len(0) on the first read... + if(defined($buf) && length($buf) == 0) { + $buf = $hash->{PortObj}->input(); + } + + if(!defined($buf) || length($buf) == 0) { + + my $devname = $hash->{DeviceName}; + Log 1, "USB device $devname disconnected, waiting to reappear"; + $hash->{PortObj}->close(); + for(;;) { + sleep(5); + $hash->{PortObj} = new Device::SerialPort($devname); + if($hash->{PortObj}) { + Log 1, "USB device $devname reappeared"; + $hash->{FD} = $hash->{PortObj}->FILENO; + DoInit($name); + return; + } + } + } + + Log 5, "FHZ/RAW: " . unpack('H*',$buf) . + " (Unparsed: " . unpack('H*', $fhzdata) . ")"; + $fhzdata .= $buf; + + while(length($fhzdata) > 2) { + + ################################### + # Skip trash. + my $si = index($fhzdata, $msgstart); + if($si) { + if($si == -1) { + Log(5, "Bogus message received, no start character found"); + $fhzdata = ""; + last; + } else { + Log(5, "Bogus message received, skipping to start character"); + $fhzdata = substr($fhzdata, $si); + } + } + + my $len = ord(substr($fhzdata,1,1)) + 2; + if($len>20) { + Log 4, + "Oversized message (" . unpack('H*',$fhzdata) . "), dropping it ..."; + $fhzdata = ""; + next; + } + + last if(length($fhzdata) < $len); + + my $dmsg = unpack('H*', substr($fhzdata, 0, $len)); + if(CheckFhzCrc($dmsg)) { + + if(substr($fhzdata,2,1) eq $msgstart) { # Skip function 0x81 + $fhzdata = substr($fhzdata, 2); + next; + } + + ############### + # check for duplicate msg from different FHZ's + my $now = gettimeofday(); + my $skip; + my $meetoo = ($attr{$name}{repeater} ? 1 : 0); + + my $to = 3; + if(defined($attr{$name}) && defined($attr{$name}{filtertimeout})) { + $to = $attr{$name}{filtertimeout}; + } + foreach my $oidx (keys %msghist) { + if($now-$msghist{$oidx}{TIME} > $to) { + delete($msghist{$oidx}); + next; + } + if($msghist{$oidx}{MSG} eq $dmsg && + ($meetoo || $msghist{$oidx}{NAME} ne $name)) { + Log 5, "Skipping $msghist{$oidx}{MSG}"; + $skip = 1; + } + } + goto NEXTMSG if($skip); + $msghist{$msgcount}{TIME} = $now; + $msghist{$msgcount}{NAME} = $name; + $msghist{$msgcount}{MSG} = $dmsg; + $msgcount++; + + + my @found; + foreach my $m (sort { $devmods{$a}{ORDER} cmp $devmods{$b}{ORDER} } + keys %devmods) { + next if($iohash->{Clients} !~ m/:$m:/); + next if($dmsg !~ m/$devmods{$m}{Match}/i); + no strict "refs"; + @found = &{$devmods{$m}{ParseFn}}($hash,$dmsg); + use strict "refs"; + last if(int(@found)); + } + if(!int(@found)) { + Log 1, "Unknown code $dmsg, help me!"; + goto NEXTMSG; + } + + goto NEXTMSG if($found[0] eq ""); # Special return: Do not notify + + if($found[0] =~ m/^(UNDEFINED) ([^ ]*) (.*)$/) { + my $d = $1; + $defs{$d}{NAME} = $1; + $defs{$d}{TYPE} = $2; + DoTrigger($d, "$2 $3"); + delete $defs{$d}; + goto NEXTMSG; + } + + foreach my $found (@found) { + DoTrigger($found, undef); + } +NEXTMSG: + $fhzdata = substr($fhzdata, $len); + + } else { + + Log 4, "Bad CRC message, skipping it (Bogus message follows)"; + $fhzdata = substr($fhzdata, 2); + + } + } +} + +1; diff --git a/FHEM/10_FS20.pm b/FHEM/10_FS20.pm new file mode 100755 index 000000000..c84e40caa --- /dev/null +++ b/FHEM/10_FS20.pm @@ -0,0 +1,331 @@ +############################################## +package main; + +use strict; +use warnings; + + +my %codes = ( + "00" => "off", + "01" => "dim06%", + "02" => "dim12%", + "03" => "dim18%", + "04" => "dim25%", + "05" => "dim31%", + "06" => "dim37%", + "07" => "dim43%", + "08" => "dim50%", + "09" => "dim56%", + "0a" => "dim62%", + "0b" => "dim68%", + "0c" => "dim75%", + "0d" => "dim81%", + "0e" => "dim87%", + "0f" => "dim93%", + "10" => "dim100%", + "11" => "on", # Set to previous dim value (before switching it off) + "12" => "toggle", # between off and previous dim val + "13" => "dimup", + "14" => "dimdown", + "15" => "dimupdown", + "16" => "timer", + "17" => "sendstate", + "18" => "off-for-timer", + "19" => "on-for-timer", + "1a" => "on-old-for-timer", + "1b" => "reset", + "1c" => "ramp-on-time", #time to reach the desired dim value on dimmers + "1d" => "ramp-off-time", #time to reach the off state on dimmers +); + +my %readonly = ( + "thermo-on" => 1, + "thermo-off" => 1, +); + +use vars qw(%fs20_c2b); # Peter would like to access it from outside +my %defptr; +my %readings; +my %follow; + +sub +FS20_Initialize($) +{ + my ($hash) = @_; + + foreach my $k (keys %codes) { + $fs20_c2b{$codes{$k}} = $k; + } + $fs20_c2b{"on-till"} = 99; + + $hash->{Category} = "DEV"; + + $hash->{Match} = "^81..(04|0c)..0101a001"; + $hash->{SetFn} = "FS20_Set"; + $hash->{GetFn} = "FS20_Get"; + $hash->{ListFn} = "FS20_List"; + $hash->{StateFn} = "FS20_SetState"; + $hash->{DefFn} = "FS20_Define"; + $hash->{UndefFn} = "FS20_Undef"; + $hash->{ParseFn} = "FS20_Parse"; +} + +################################### +sub +FS20_Get($@) +{ + my ($hash, @a) = @_; + return "No get function implemented"; +} + +################################### +sub +FS20_List($) +{ + my ($hash) = @_; + + my $n = $hash->{NAME}; + if(!defined($readings{$n})) { + return "No information about $n\n"; + } else { + return sprintf("%-19s %s\n", $readings{$n}{TIM}, $readings{$n}{VAL}); + } +} + +##################################### +sub +FS20_SetState($$$$) +{ + my ($hash, $tim, $vt, $val) = @_; + + return "Undefined value $vt" if(!defined($fs20_c2b{$vt})); + + my $name = $hash->{NAME}; + if(!$readings{$name} || $readings{$name}{TIM} lt $tim) { + $readings{$name}{TIM} = $tim; + $readings{$name}{VAL} = $vt; + } + return undef; +} + +############################# +sub +Do_On_Till($@) +{ + my ($hash, @a) = @_; + return "Timespec (HH:MM[:SS]) needed for the on-till command" if(@a != 3); + + my ($err, $hr, $min, $sec, $fn) = GetTimeSpec($a[2]); + return $err if($err); + + my @lt = localtime; + my $hms_till = sprintf("%02d:%02d:%02d", $hr, $min, $sec); + my $hms_now = sprintf("%02d:%02d:%02d", $lt[2], $lt[1], $lt[0]); + if($hms_now ge $hms_till) { + Log 4, "on-till: won't switch as now ($hms_now) is later than $hms_till"; + return ""; + } + + my @b = ($a[0], "on"); + FS20_Set($hash, @b); + CommandAt(undef, "$hms_till set $a[0] off"); +} + + +################################### +sub +FS20_Set($@) +{ + my ($hash, @a) = @_; + my $ret = undef; + my $na = int(@a); + + return "no set value specified" if($na < 2 || $na > 3); + return "Readonly value $a[1]" if(defined($readonly{$a[1]})); + + my $c = $fs20_c2b{$a[1]}; + if(!defined($c)) { + return "Unknown set value $a[1], please specify one of:\n " . + join("\n ", sort(keys %fs20_c2b)); + } + + return Do_On_Till($hash, @a) if($a[1] eq "on-till"); + + return "Bad time spec" if($na == 3 && $a[2] !~ m/^\d*\.?\d+$/); + + my $v = join(" ", @a); + Log GetLogLevel($a[0]), "FS20 set $v"; + (undef, $v) = split(" ", $v, 2); # Not interested in the name... + + my $val; + if($na == 2) { + + IOWrite($hash, "04", "010101" . $hash->{XMIT} . $hash->{BTN} . $c) + if(!IsDummy($a[0])); + + } else { + + $c =~ s/1/3/; # Set the extension bit + + ######################## + # Calculating the time. + LOOP: for(my $i = 0; $i <= 12; $i++) { + for(my $j = 0; $j <= 15; $j++) { + $val = (2**$i)*$j*0.25; + if($val >= $a[2]) { + if($val != $a[2]) { + $ret = "FS20 Setting timeout to $val from $a[2]"; + Log GetLogLevel($a[0]), $ret; + } + $c .= sprintf("%x%x", $i, $j); + last LOOP; + } + } + } + return "Specified timeout too large, max is 15360" if(length($c) == 2); + + IOWrite($hash, "04", "010101" . $hash->{XMIT} . $hash->{BTN} . $c) + if(!IsDummy($a[0])); + + } + + ########################################### + # Set the state of a device to off if on-for-timer is called + if($follow{$a[0]}) { + CommandDelete(undef, "at .*setstate.*$a[0]"); + delete $follow{$a[0]}; + } + if($a[1] eq "on-for-timer" && $na == 3 && + defined($attr{$a[0]}) && defined($attr{$a[0]}{"follow-on-for-timer"})) { + my $to = sprintf("%02d:%02d:%02d", $val/3600, ($val%3600)/60, $val%60); + $follow{$a[0]} = $to; + Log 4, "Follow: +$to setstate $a[0] off"; + CommandAt(undef, "+$to setstate $a[0] off"); + } + + ########################## + # Look for all devices with the same code, and set state, timestamp + my $code = "$hash->{XMIT} $hash->{BTN}"; + my $tn = TimeNow(); + foreach my $n (keys %{ $defptr{$code} }) { + $defptr{$code}{$n}->{CHANGED}[0] = $v; + $defptr{$code}{$n}->{STATE} = $v; + $readings{$n}{TIM} = $tn; + $readings{$n}{VAL} = $v; + } + + + return $ret; +} + +############################# +sub +FS20_Define($@) +{ + my ($hash, @a) = @_; + my $u = + "wrong syntax: define FS20 housecode addr [fg addr] [lm addr] [gm FF]"; + + return $u if(int(@a) < 4); + return "Define $a[0]: wrong housecode format: specify a 4 digit hex value" + if($a[2] !~ m/^[a-f0-9]{4}$/i); + return "Define $a[0]: wrong btn format: specify a 2 digit hex value" + if($a[3] !~ m/^[a-f0-9]{2}$/i); + + $hash->{XMIT} = lc($a[2]); + $hash->{BTN} = lc($a[3]); + + my $code = "$a[2] $a[3]"; + my $ncode = 1; + my $name = $a[0]; + + $hash->{CODE}{$ncode++} = $code; + $defptr{$code}{$name} = $hash; + + for(my $i = 4; $i < int(@a); $i += 2) { + + return "No address specified for $a[$i]" if($i == int(@a)-1); + + $a[$i] = lc($a[$i]); + if($a[$i] eq "fg") { + return "Bad fg address, see the doc" if($a[$i+1] !~ m/^f[a-f0-9]$/); + } elsif($a[$i] eq "lm") { + return "Bad lm address, see the doc" if($a[$i+1] !~ m/^[a-f0-9]f$/); + } elsif($a[$i] eq "gm") { + return "Bad gm address, mus be ff" if($a[$i+1] ne "ff"); + } else { + return $u; + } + + $code = "$a[2] $a[$i+1]"; + $hash->{CODE}{$ncode++} = $code; + $defptr{$code}{$name} = $hash; + } + AssignIoPort($hash); +} + +############################# +sub +FS20_Undef($$) +{ + my ($hash, $name) = @_; + foreach my $c (keys %{ $hash->{CODE} } ) { + delete($defptr{$c}{$name}); + } + return undef; +} + +sub +FS20_Parse($) +{ + my ($hash, $msg) = @_; + + # Msg format: + # 81 0b 04 f7 0101 a001 HHHH 01 00 11 + + my $dev = substr($msg, 16, 4); + my $btn = substr($msg, 20, 2); + my $cde = substr($msg, 24, 2); + + my $def = $defptr{"$dev $btn"}; + + my $dur = 0; + my $cx = hex($cde); + if($cx & 0x20) { + $dur = hex(substr($msg, 26, 2)); + my $i = ($dur & 0xf0) / 16; + my $j = ($dur & 0xf); + $dur = (2**$i)*$j*0.25; + $cde = sprintf("%02x", $cx & ~0x20); + } + + my $v = $codes{$cde}; + $v = "unknown:$cde" if(!defined($v)); + $v .= " $dur" if($dur); + if($def) { + + my @list; + foreach my $n (keys %{ $def }) { + $readings{$n}{TIM} = TimeNow(); + $readings{$n}{VAL} = $v; + $def->{$n}->{CHANGED}[0] = $v; + $def->{$n}->{STATE} = $v; + Log GetLogLevel($n), "FS20 $n $v"; + push(@list, $n); + } + return @list; + + } else { + # Special FHZ initialization parameter. In Multi-FHZ-Mode we receive + # it by the second FHZ + return "" if($dev eq "0001" && $btn eq "00" && $cde eq "00"); + + Log 3, "FS20 Unknown device $dev, Button $btn Code $cde ($v), " . + "please define it"; + return "UNDEFINED FS20 $dev/$btn/$cde"; + } + +} + + +1; diff --git a/FHEM/20_FHT.pm b/FHEM/20_FHT.pm new file mode 100755 index 000000000..5e0146b56 --- /dev/null +++ b/FHEM/20_FHT.pm @@ -0,0 +1,404 @@ +############################################## +package main; + +use strict; +use warnings; + +my %codes = ( + "0000.6" => "actuator", + "00002c" => "synctime", # Not verified + "0100.6" => "actuator1", # Not verified (1-8) + "0200.6" => "actuator2", + "0300.6" => "actuator3", + "0400.6" => "actuator4", + "0500.6" => "actuator5", + "0600.6" => "actuator6", + "0700.6" => "actuator7", + "0800.6" => "actuator8", + "140069" => "mon-from1", + "150069" => "mon-to1", + "160069" => "mon-from2", + "170069" => "mon-to2", + "180069" => "tue-from1", + "190069" => "tue-to1", + "1a0069" => "tue-from2", + "1b0069" => "tue-to2", + "1c0069" => "wed-from1", + "1d0069" => "wed-to1", + "1e0069" => "wed-from2", + "1f0069" => "wed-to2", + "200069" => "thu-from1", + "210069" => "thu-to1", + "220069" => "thu-from2", + "230069" => "thu-to2", + "240069" => "fri-from1", + "250069" => "fri-to1", + "260069" => "fri-from2", + "270069" => "fri-to2", + "280069" => "sat-from1", + "290069" => "sat-to1", + "2a0069" => "sat-from2", + "2b0069" => "sat-to2", + "2c0069" => "sun-from1", + "2d0069" => "sun-to1", + "2e0069" => "sun-from2", + "2f0069" => "sun-to2", + "3e0069" => "mode", + "3f0069" => "holiday1", # Not verified + "400069" => "holiday2", # Not verified + "410069" => "desired-temp", + "XX0069" => "measured-temp", # sum of next. two, never "really" sent + "420069" => "measured-low", + "430069" => "measured-high", + "440069" => "state", + "600069" => "year", + "610069" => "month", + "620069" => "day", + "630069" => "hour", + "640069" => "minute", + "650069" => "init", + "820069" => "day-temp", + "840069" => "night-temp", + "850069" => "unknown_85", + "8a0069" => "windowopen-temp", + + "0000aa" => "code_0000aa", + "0000ba" => "code_0000ba", + "430079" => "code_430079", + "440079" => "code_440079", + "4b0067" => "code_4b004b", + "4b0077" => "code_4b0077", + "7e0067" => "code_7e0067", +); + +my %cantset = ( + "actuator" => 1, + "actuator1" => 1, + "actuator2" => 1, + "actuator3" => 1, + "actuator4" => 1, + "actuator5" => 1, + "actuator6" => 1, + "actuator7" => 1, + "actuator8" => 1, + "synctime" => 1, + "measured-temp" => 1, + "measured-high" => 1, + "measured-low" => 1, + "state" => 1, + "init" => 1, + + "code_0000aa" => 1, + "code_0000ba" => 1, + "code_430079" => 1, + "code_440079" => 1, + "code_4b004b" => 1, + "code_4b0077" => 1, + "code_7e0067" => 1, +); + +my %nosetarg = ( + "help" => 1, + "refreshvalues" => 1, +); + +my %c2m = (0 => "auto", 1 => "manual", 2 => "holiday"); +my %m2c = ("auto" => 0, "manual" => 1, "holiday" => 2); + +my %readings; +my %defptr; +my %c2b; # command->button hash (reverse of codes) +my %c2bset; # Setteable values + + +##################################### +sub +FHT_Initialize($) +{ + my ($hash) = @_; + + foreach my $k (keys %codes) { + my $v = $codes{$k}; + $c2b{$v} = $k; + $c2bset{$v} = substr($k, 0, 2) if(!defined($cantset{$v})); + } + $c2bset{refreshvalues} = "65ff66ff"; + + $hash->{Category} = "DEV"; + +# 810c0426 0909a001 1111 1600 +# 810c04b3 0909a001 1111 44006900 +# 810b0402 83098301 1111 41301d +# 81090421 c409c401 1111 00 + +# 810c0d20 0909a001 3232 7e006724 (NYI) + + $hash->{Match} = "^81..(04|09|0d)..(0909a001|83098301|c409c401).."; + $hash->{SetFn} = "FHT_Set"; + $hash->{GetFn} = "FHT_Get"; + $hash->{StateFn} = "FHT_SetState"; + $hash->{ListFn} = "FHT_List"; + $hash->{DefFn} = "FHT_Define"; + $hash->{UndefFn} = "FHT_Undef"; + $hash->{ParseFn} = "FHT_Parse"; +} + + +##################################### +sub +FHT_Set($@) +{ + my ($hash, @a) = @_; + my $ret = undef; + + return "\"set $a[0]\" needs two parameters" + if(@a != 3 && !(@a == 2 && $nosetarg{$a[1]})); + return "invalid parameter, use one of:\n " . + join("\n ", sort {$c2bset{$a} cmp $c2bset{$b} } keys %c2bset) + if(!defined($c2bset{$a[1]})); + + + Log GetLogLevel($a[0]), "FHT set " . join(" ", @a); + + my $arg = "020183" . $hash->{CODE} . $c2bset{$a[1]}; + + if($a[1] eq "refreshvalues") { + + # This is special. Without the sleep the next FHT won't send its data + if(!IsDummy($a[0])) { + my $havefhz; + $havefhz = 1 if($hash->{IODev} && defined($hash->{IODev}->{FD})); + + IOWrite($hash, "04", $arg); + sleep(1) if($havefhz); + IOWrite($hash, "04", "c90185"); # Check the fht buffer + sleep(1) if($havefhz); + } + return $ret; + + } elsif($a[1] =~ m/-temp/) { + return "Invalid temperature, use NN.N" if($a[2] !~ m/^\d*\.?\d+$/); + my $a = int($a[2]*2); + $arg .= sprintf("%02x", $a); + $ret = "Rounded temperature to " . $a/2 if($a/2 != $a[2]); + + } elsif($a[1] =~ m/-from/ || $a[1] =~ m/-to/) { + return "Invalid timeformat, use HH:MM" if($a[2] !~ m/^([0-2]\d):([0-5]\d)/); + my $a = ($1*6) + ($2/10); + $arg .= sprintf("%02x", $a); + + my $nt = sprintf("%02d:%02d", $1, ($2/10)*10); + $ret = "Rounded time to $nt" if($nt ne $a[2]); + + } elsif($a[1] eq "mode") { + return "Invalid mode, use one of " . join(" ", sort keys %m2c) + if(!defined($m2c{$a[2]})); + $arg .= sprintf("%02x", $m2c{$a[2]}); + + } else { # Holiday1, Holiday2 + $arg .= sprintf("%02x", $a[2]); + + } + + IOWrite($hash, "04", $arg) if(!IsDummy($a[0])); + return $ret; +} + +##################################### +sub +FHT_Get($@) +{ + my ($hash,@a) = @_; + + return "NYI"; +} + +##################################### +sub +FHT_List($) +{ + my ($hash) = @_; + + my $n = $hash->{CODE}; + if(!defined($readings{$n})) { + return "No information about " . $hash->{NAME} . "\n"; + } else { + my $str = ""; + foreach my $m (sort { $c2b{$a} cmp $c2b{$b} } keys %{ $readings{$n} }) { + $str .= sprintf("%-19s %-15s %s\n", + $readings{$n}{$m}{TIM}, $m, $readings{$n}{$m}{VAL}); + } + return $str; + } +} + +##################################### +sub +FHT_SetState($$$$) +{ + my ($hash, $tim, $vt, $val) = @_; + + return "Undefined type $vt" if(!defined($c2b{$vt})); + + my $n = $hash->{CODE}; + + if(!$readings{$n}{$vt} || $readings{$n}{$vt}{TIM} lt $tim) { + $readings{$n}{$vt}{TIM} = $tim; + $readings{$n}{$vt}{VAL} = $val; + } + return undef; +} + + +##################################### +sub +FHT_Define($@) +{ + my ($hash, @a) = @_; + + return "wrong syntax: define FHT CODE" if(int(@a) != 3); + $a[2] = lc($a[2]); + return "Define $a[0]: wrong CODE format: specify a 4 digit hex value" + if($a[2] !~ m/^[a-f0-9][a-f0-9][a-f0-9][a-f0-9]$/i); + + + $hash->{CODE} = $a[2]; + $defptr{$a[2]} = $hash; + + AssignIoPort($hash); + + Log 1, "Asking the FHT device $a[0]/$a[2] to send its data"; + FHT_Set($hash, ($a[0], "refreshvalues")); + + return undef; +} + +##################################### +sub +FHT_Undef($$) +{ + my ($hash, $name) = @_; + delete($defptr{$hash->{CODE}}); + return undef; +} + +##################################### +sub +FHT_Parse($$) +{ + my ($hash,$msg) = @_; + + my $dev = substr($msg, 16, 4); + my $cde = substr($msg, 20, 6); + my $val = substr($msg, 26, 2) if(length($msg) > 26); + + if(!defined($defptr{$dev})) { + Log 3, "FHT Unknown device $dev, please define it"; + return "UNDEFINED FHT $dev"; + } + + + my $def = $defptr{$dev}; + + # Unknown, but don't want report it. Should come with c409c401 + if($cde eq "00") { + return ""; + } + + if(length($cde) < 6) { + Log 4, "FHT Unknown code from $def->{NAME} : $cde"; + $def->{CHANGED}[0] = "unknown code $cde"; + return $def->{NAME}; + } + + + if(!$val) { + # This is a confirmation message. We reformat it so that + # it looks like a real message, and let the rest parse it + Log 4, "FHT $def->{NAME} confirmation: $cde)"; + $val = substr($cde, 2, 2); + $cde = substr($cde, 0, 2) . "0069"; + } + + my $type; + foreach my $c (keys %codes) { + if($cde =~ m/$c/) { + $type = $codes{$c}; + last; + } + } + + $val = hex($val); + + if(!$type) { + Log 4, "FHT $def->{NAME} (Unknown: $cde => $val)"; + $def->{CHANGED}[0] = "unknown $cde: $val"; + return $def->{NAME}; + } + + + my $tn = TimeNow(); + + ########################### + # Reformat the values so they are readable + if($type eq "actuator") { + $val = sprintf("%02d%%", int(100*$val/255 + 0.5)); + + } elsif($cde ge "140069" && $cde le "2f0069") { # Time specs + Log 5, "FHT $def->{NAME} ($type: $val)"; + return "" if($val == 144); # Empty, forget it + my $hour = $val / 6; + my $min = ($val % 6) * 10; + $val = sprintf("%02d:%02d", $hour, $min); + + } elsif($type eq "mode") { + $val = $c2m{$val} if(defined($c2m{$val})); + + } elsif($type eq "measured-low") { + + $readings{$dev}{$type}{TIM} = $tn; + $readings{$dev}{$type}{VAL} = $val; + return ""; + + } elsif($type eq "measured-high") { + + $readings{$dev}{$type}{TIM} = $tn; + $readings{$dev}{$type}{VAL} = $val; + + if(defined($readings{$dev}{"measured-low"}{VAL})) { + + $val = $val*256 + $readings{$dev}{"measured-low"}{VAL}; + $val /= 10; + $val = sprintf("%.1f (Celsius)", $val); + $type = "measured-temp" + + } else { + return ""; + } + + } elsif($type =~ m/.*-temp/) { + $val = sprintf("%.1f (Celsius)", $val / 2) + + } elsif($type eq "state") { + + my $nval; + $nval = "Bat: " . (($val & 1) ? "empty" : "ok"); + $nval .= ", Window: " . (($val & 32) ? "open" : "closed"); + $nval .= ", Fault: " . (($val & 16) ? "yes" : "no"); + $val = $nval; + + } elsif($type =~ m/echo_/) { # Ignore these messages + return ""; + + } + + $readings{$dev}{$type}{TIM} = $tn; + $readings{$dev}{$type}{VAL} = $val; + + Log 4, "FHT $def->{NAME} ($type: $val)"; + $def->{CHANGED}[0] = "$type: $val"; + $def->{STATE} = "$type: $val" if($type eq "measured-temp"); + return $def->{NAME}; +} + +1; diff --git a/FHEM/30_HMS.pm b/FHEM/30_HMS.pm new file mode 100755 index 000000000..63c5b4a1d --- /dev/null +++ b/FHEM/30_HMS.pm @@ -0,0 +1,262 @@ +############################################## +package main; + +use strict; +use warnings; + +my %codes = ( + "0" => "HMS100TF", + "1" => "HMS100T", + "2" => "HMS100WD", + "3" => "RM100-2", + "4" => "HMS100TFK", # Depending on the onboard jumper it is 4 or 5 + "5" => "HMS100TFK", + "6" => "HMS100MG", +); + +my %readings; +my %defptr; + + +##################################### +sub +HMS_Initialize($) +{ + my ($hash) = @_; + + $hash->{Category} = "DEV"; + +# 810e047e0510a001473a000000120233 HMS100TF +# 810e04b90511a0018e63000001100000 HMS100T +# 810e04e80212a001ec46000001000000 HMS100WD +# 810e04d70213a001b16d000003000000 RM100-2 +# 810e047f0214a001a81f000001000000 HMS100TFK +# 810e048f0295a0010155000001000000 HMS100TFK (jumper) +# 810e04330216a001b4c5000001000000 HMS100MG + + $hash->{Match} = "^810e04....(1|5|9)[0-6]a001"; + $hash->{SetFn} = "HMS_Set"; + $hash->{GetFn} = "HMS_Get"; + $hash->{StateFn} = "HMS_SetState"; + $hash->{ListFn} = "HMS_List"; + $hash->{DefFn} = "HMS_Define"; + $hash->{UndefFn} = "HMS_Undef"; + $hash->{ParseFn} = "HMS_Parse"; +} + +################################### +sub +HMS_Set($@) +{ + my ($hash, @a) = @_; + return "No set function implemented"; +} + +################################### +sub +HMS_Get($@) +{ + my ($hash,@a) = @_; + return "No get function implemented"; +} + +##################################### +sub +HMS_SetState($$$$) +{ + my ($hash, $tim, $vt, $val) = @_; + + my $n = $hash->{CODE}; + if(!$readings{$n}{$vt} || $readings{$n}{$vt}{TIM} lt $tim) { + $readings{$n}{$vt}{TIM} = $tim; + $readings{$n}{$vt}{VAL} = $val; + } + return undef; +} + +##################################### +sub +HMS_List($) +{ + my ($hash) = @_; + + my $n = $hash->{CODE}; + if(!defined($readings{$n})) { + return "No information about " . $hash->{NAME} . "\n"; + } else { + my $str = ""; + foreach my $m (keys %{ $readings{$n} }) { + $str .= sprintf("%-19s %-15s %s\n", + $readings{$n}{$m}{TIM}, $m, $readings{$n}{$m}{VAL}); + } + return $str; + } +} + +##################################### +sub +HMS_Define($@) +{ + my ($hash, @a) = @_; + + return "wrong syntax: define HMS CODE" if(int(@a) != 3); + $a[2] = lc($a[2]); + return "Define $a[0]: wrong CODE format: specify a 4 digit hex value" + if($a[2] !~ m/^[a-f0-9][a-f0-9][a-f0-9][a-f0-9]$/); + + + $hash->{CODE} = $a[2]; + $defptr{$a[2]} = $hash; + + return undef; +} + +##################################### +sub +HMS_Undef($$) +{ + my ($hash, $name) = @_; + delete($defptr{$hash->{CODE}}); + return undef; +} + + +##################################### +sub +HMS_Parse($$) +{ + my ($hash, $msg) = @_; + + my $dev = substr($msg, 16, 4); + my $cde = substr($msg, 11, 1); + my $val = substr($msg, 24, 8) if(length($msg) == 32); + + my $type = ""; + foreach my $c (keys %codes) { + if($cde =~ m/$c/) { + $type = $codes{$c}; + last; + } + } + + # As the HMS devices change their id on each battery change, we offer + # a wildcard too for each type: 100, + my $odev = $dev; + if(!defined($defptr{$dev})) { + Log 4, "HMS device $dev not defined, using the wildcard device 100$cde"; + $dev = "100$cde"; + } + + if(!defined($defptr{$dev})) { + Log 3, "Unknown HMS device $dev/$odev, please define it"; + $type = "HMS" if(!$type); + return "UNDEFINED $type $odev"; + } + + my $def = $defptr{$dev}; + + + my (@v, @txt, @sfx); + + if($type eq "HMS100TF") { + + @txt = ( "temperature", "humidity", "battery"); + @sfx = ( "(Celsius)", "(%)", ""); + + # Codierung + my $status = hex(substr($val, 0, 1)); + $v[0] = int(substr($val, 5, 1) . substr($val, 2, 2))/10; + $v[1] = int(substr($val, 6, 2) . substr($val, 4, 1))/10; + $v[2] = "ok"; + if ( $status & 2 ) { $v[2] = "empty"; } + if ( $status & 4 ) { $v[2] = "replaced"; } + if ( $status & 8 ) { $v[0] = -$v[0]; } + + $val = "T: $v[0] H: $v[1] Bat: $v[2]"; + + } elsif ($type eq "HMS100T") { + + @txt = ( "temperature", "battery"); + @sfx = ( "(Celsius)", ""); + + my $status = hex(substr($val, 0, 1)); + $v[0] = int(substr($val, 5, 1) . substr($val, 2, 2))/10; + $v[1] = "ok"; + if ( $status & 2 ) { $v[1] = "empty"; } + if ( $status & 4 ) { $v[1] = "replaced"; } + if ( $status & 8 ) { $v[0] = -$v[0]; } + + $val = "T: $v[0] Bat: $v[1]"; + + } elsif ($type eq "HMS100WD") { + + @txt = ( "water_detect", "battery"); + @sfx = ( "", ""); + + # Battery-low condition detect is not yet properly + # implemented. As soon as my WD's batteries get low + # I am willing to supply a patch ;-) SEP7-RIPE, 2006/05/13 + my $status = hex(substr($val, 1, 1)); + $v[1] = "ok"; + $v[0] = "off"; + if ( $status & 1 ) { $v[0] = "on"; } + $val = "Water Detect: $v[0]"; + + } elsif ($type eq "HMS100TFK") { # By Peter P. + + @txt = ( "switch_detect", "battery"); + @sfx = ( "", ""); + # Battery-low condition detect is not yet properly implemented. + my $status = hex(substr($val, 1, 1)); + $v[0] = ($status ? "on" : "off"); + $v[1] = "off"; + $val = "Switch Detect: $v[0]"; + + } elsif($type eq "RM100-2") { + + @txt = ( "smoke_detect", "battery"); + @sfx = ( "", ""); + + $v[0] = ( hex(substr($val, 1, 1)) != "0" ) ? "on" : "off"; + $v[1] = "unknown"; # Battery-low detect is _NOT_ implemented. + $val = "smoke_detect: $v[0]"; + + } elsif ($type eq "HMS100MG") { # By Peter Stark + + @txt = ( "gas_detect", "battery"); + @sfx = ( "", ""); + + # Battery-low condition detect is not yet properly + # implemented. + my $status = hex(substr($val, 1, 1)); + $v[0] = ($status != "0") ? "on" : "off"; + $v[1] = "off"; + if ($status & 1) { $v[0] = "on"; } + $val = "Gas Detect: $v[0]"; + + } else { + + Log 4, "HMS Device $dev (Unknown type: $type)"; + return ""; + + } + + my $now = TimeNow(); + Log 4, "HMS Device $dev ($type: $val)"; + + my $max = int(@txt); + for( my $i = 0; $i < $max; $i++) { + $readings{$dev}{$txt[$i]}{TIM} = $now; + my $v = "$v[$i] $sfx[$i]"; + $readings{$dev}{$txt[$i]}{VAL} = $v; + $def->{CHANGED}[$i] = "$txt[$i]: $v"; + } + $readings{$dev}{type}{TIM} = $now; + $readings{$dev}{type}{VAL} = $type; + + $def->{STATE} = $val; + $def->{CHANGED}[$max] = $val; + return $def->{NAME}; +} + +1; diff --git a/FHEM/40_KS300.pm b/FHEM/40_KS300.pm new file mode 100755 index 000000000..aaeb12bf3 --- /dev/null +++ b/FHEM/40_KS300.pm @@ -0,0 +1,308 @@ +############################################## +package main; + +use strict; +use warnings; + +my %readings; +my %defptr; +my $negcount = 0; + +###################### +# Note: this is just an empty hull. + +##################################### +sub +KS300_Initialize($) +{ + my ($hash) = @_; + + # Message is like + # 810d04f94027a00171212730000008 + # 81 0d 04 f9 4027a00171 212730000008 + + $hash->{Category} = "DEV"; + + $hash->{Match} = "^810.04..402.a001"; + $hash->{SetFn} = "KS300_Set"; + $hash->{GetFn} = "KS300_Get"; + $hash->{StateFn} = "KS300_SetState"; + $hash->{ListFn} = "KS300_List"; + $hash->{DefFn} = "KS300_Define"; + $hash->{UndefFn} = "KS300_Undef"; + $hash->{ParseFn} = "KS300_Parse"; +} + +################################### +sub +KS300_Set($@) +{ + my ($hash, @a) = @_; + return "No set function implemented"; +} + +################################### +sub +KS300_Get($@) +{ + my ($hash,@a) = @_; + return "No get function implemented"; +} + +##################################### +sub +KS300_SetState($$$$) +{ + my ($hash, $tim, $vt, $val) = @_; + + my $n = $hash->{CODE}; + if(!$readings{$n}{$vt} || $readings{$n}{$vt}{TIM} lt $tim) { + $readings{$n}{$vt}{TIM} = $tim; + $readings{$n}{$vt}{VAL} = $val; + } + return undef; +} + +##################################### +sub +KS300_List($) +{ + my ($hash) = @_; + my $str = ""; + + my $n = $hash->{CODE}; + if(!defined($readings{$n})) { + $str .= "No information about " . $hash->{NAME} . "\n"; + } else { + foreach my $m (keys %{ $readings{$n} }) { + $str .= sprintf("%-19s %-15s %s\n", + $readings{$n}{$m}{TIM}, $m, $readings{$n}{$m}{VAL}); + } + } + return $str; + +} + +##################################### +sub +KS300_Define($@) +{ + my ($hash, @a) = @_; + + return "wrong syntax: define KS300 " . + "[ml/raincounter] [wind-factor]" if(int(@a) < 3 || int(@a) > 5); + $a[2] = lc($a[2]); + return "Define $a[0]: wrong CODE format: specify a 4 digit hex value" + if($a[2] !~ m/^[a-f0-9][a-f0-9][a-f0-9][a-f0-9]$/); + + $hash->{CODE} = $a[2]; + my $rainunit = ((int(@a) > 3) ? $a[3] : 255); + my $windunit = ((int(@a) > 4) ? $a[4] : 1.0); + $hash->{CODE} = $a[2]; + $hash->{RAINUNIT} = $rainunit; + $hash->{WINDUNIT} = $windunit; + $defptr{$a[2]} = $hash; + + return undef; +} + +##################################### +sub +KS300_Undef($$) +{ + my ($hash, $name) = @_; + delete($defptr{$hash->{CODE}}); + return undef; +} + + +##################################### +sub +KS300_Parse($) +{ + my ($hash,$msg) = @_; + + if($msg !~ m/^810d04..4027a001/) { + Log 4, "KS300 unknown message $msg"; + return ""; + } + + ############################### + # 1 2 + #0123456789012345 67890123456789 + # + #810d04f94027a001 71212730000008 + ############################### + my @a = split("", $msg); + + ########################## + # I've seldom (1 out of 700) seen messages of length 10 and 11 with correct + # CRC, they seem to contain partial data (e.g. temp/wind/hum but not rain) + # They are suppressed as of now. + if(hex($a[3]) != 13) { + Log 4, "Strange KS400 message received, wont decode ($msg)"; + return ""; + } + + if(int(keys %defptr)) { + + my @arr = keys(%defptr); # No code is known yet + my $dev = shift(@arr); + my $def = $defptr{$dev}; + my $haverain = 0; + + my @v; + my @txt = ( "rain_raw", "rain", "wind", "humidity", "temperature", + "israining", "unknown1", "unknown2", "unknown3"); + my @sfx = ( "(counter)", "(l/m2)", "(km/h)", "(%)", "(Celsius)", + "(yes/no)", "","",""); + + # The next instr wont work for empty hashes, so we init it now + $readings{$dev}{$txt[0]}{VAL} = 0 if(!$readings{$dev}); + my $r = $readings{$dev}; + + $v[0] = hex("$a[28]$a[27]$a[26]"); + + ############################# + # My KS300 sends a (quite huge) "negative" rain, when the rain begins, + # then the value is "normal" again. So we have to filter neg. rain out. + # But if the KS300 is sending this value more than once, then accept it, + # as the KS300 was probably reset + + if($r->{rain_raw}{VAL}) { + my ($rrv, undef) = split(" ", $r->{rain_raw}{VAL}); + $haverain = 1 if($v[0] != $rrv); + if($v[0] < $rrv) { + if($negcount++ < 3) { + Log 3, "KS300 negative rain, ignoring it"; + $v[0] = $rrv; + } else { + Log 1, "KS300 was probably reset, accepting new rain value"; + } + } else { + $negcount = 0; + } + } + + $v[1] = sprintf("%0.1f", $v[0] * $def->{RAINUNIT} / 1000); + $v[2] = sprintf("%0.1f", ("$a[25]$a[24].$a[23]"+0) * $def->{WINDUNIT}); + $v[3] = "$a[22]$a[21]" + 0; + $v[4] = "$a[20]$a[19].$a[18]" + 0; $v[4] = "-$v[4]" if($a[17] eq "7"); + $v[4] = sprintf("%0.1f", $v[4]); + + $v[5] = ((hex($a[17]) & 0x2) || $haverain) ? "yes" : "no"; + $v[6] = $a[29]; + $v[7] = $a[16]; + $v[8] = $a[17]; + + # Negative temp + $v[4] = -$v[4] if($v[8] & 8); + + my $tm = TimeNow(); + + Log 4, "KS300 $dev: $msg"; + + my $max = int(@v); + + + for(my $i = 0; $i < $max; $i++) { + $r->{$txt[$i]}{TIM} = $tm; + my $val = "$v[$i] $sfx[$i]"; + $r->{$txt[$i]}{VAL} = $val; + $def->{CHANGED}[$i] = "$txt[$i]: $val"; + } + + # For logging/summary + my $val = "T: $v[4] H: $v[3] W: $v[2] R: $v[1] IR: $v[5]"; + $def->{STATE} = $val; + $def->{CHANGED}[$max++] = $val; + + ################################### + # AVG computing + if(!$r->{cum_day}) { + + $r->{cum_day}{VAL} = "$tm T: 0 H: 0 W: 0 R: $v[1]"; + $r->{avg_day}{VAL} = "T: $v[4] H: $v[3] W: $v[2] R: $v[1]"; + + } else { + + my @cv = split(" ", $r->{cum_day}{VAL}); + + my @cd = split("[ :-]", $r->{cum_day}{TIM}); + my $csec = 3600*$cd[3] + 60*$cd[4] + $cd[5]; # Sec of last reading + + my @d = split("[ :-]", $tm); + my $sec = 3600*$d[3] + 60*$d[4] + $d[5]; # Sec now + + my @sd = split("[ :-]", "$cv[0] $cv[1]"); + my $ssec = 3600*$sd[3] + 60*$sd[4] + $sd[5]; # Sec at start of day + + my $difft = $sec - $csec; + $difft += 86400 if($d[2] != $cd[2]); # Sec since last reading + + my $t = $cv[3] + $difft * $v[4]; + my $h = $cv[5] + $difft * $v[3]; + my $w = $cv[7] + $difft * $v[2]; + my $e = $cv[9]; + + $r->{cum_day}{VAL} = "$cv[0] $cv[1] T: $t H: $h W: $w R: $e"; + + $difft = $sec - $ssec; + $difft += 86400 if($d[2] != $sd[2]); # Sec since last reading + + $t /= $difft; $h /= $difft; $w /= $difft; $e = $v[1] - $cv[9]; + $r->{avg_day}{VAL} = + sprintf("T: %.1f H: %d W: %.1f R: %.1f", $t, $h, $w, $e); + + if($d[2] != $sd[2]) { # Day changed, report it + + $def->{CHANGED}[$max++] = "avg_day $r->{avg_day}{VAL}"; + $r->{cum_day}{VAL} = "$tm T: 0 H: 0 W: 0 R: $v[1]"; + + if(!$r->{cum_month}) { # Check the month + + $r->{cum_month}{VAL} = "1 $r->{avg_day}{VAL}"; + $r->{avg_month}{VAL} = $r->{avg_day}{VAL}; + + } else { + + my @cmv = split(" ", $r->{cum_month}{VAL}); + $t += $cmv[2]; $w += $cmv[4]; $h += $cmv[6]; + + $cmv[0]++; + $r->{cum_month}{VAL} = + sprintf("%d T: %.1f H: %d W: %.1f R: %.1f", + $cmv[0], $t, $h, $w, $cmv[8]+$e); + $r->{avg_month}{VAL} = + sprintf("T: %.1f H: %d W: %.1f R: %.1f", + $t/$cmv[0], $h/$cmv[0], $w/$cmv[0], $cmv[8]+$e); + + if($d[1] != $sd[1]) { # Month changed, report it + + $def->{CHANGED}[$max++] = "avg_month $r->{avg_month}{VAL}"; + $r->{cum_month}{VAL} = "0 T: 0 H: 0 W: 0 R: 0"; + + } + + } + $r->{cum_month}{TIM} = $r->{avg_month}{TIM} = $tm; + + } + + } + $r->{cum_day}{TIM} = $r->{avg_day}{TIM} = $tm; + # AVG computing + ################################### + + return $def->{NAME}; + + } else { + + Log 4, "KS300 detected: $msg"; + + } + + return ""; +} + +1; diff --git a/FHEM/50_WS300.pm b/FHEM/50_WS300.pm new file mode 100644 index 000000000..d16628766 --- /dev/null +++ b/FHEM/50_WS300.pm @@ -0,0 +1,698 @@ +################################################################ +# +# Copyright notice +# +# (c) 2007 Copyright: Martin Klerx (Martin at klerx dot de) +# All rights reserved +# +# This script 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 2 of the License, or +# (at your option) any later version. +# +# The GNU General Public License can be found at +# http://www.gnu.org/copyleft/gpl.html. +# A copy is found in the textfile GPL.txt and important notices to the license +# from the author is found in LICENSE.txt distributed with these scripts. +# +# This script 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. +# +# This copyright notice MUST APPEAR in all copies of the script! +# +################################################################ +# examples: +# define WS300Device WS300 /dev/ttyUSB1 (fixed name, must be first) +# define ash2200-1 WS300 0 +# define ash2200-2 WS300 1 +# ... +# define ash2200-8 WS300 7 +# define ks300 WS300 8 (always 8) +# define ws300 WS300 9 (always 9) +# set WS300Device +################################################################ + +package main; + +use strict; +use warnings; + +my %readings; +my %defptr; +my $DeviceName=""; +my $inbuf=""; + +my $config; +my $cmd=0x32; +my $errcount=0; +my $ir="no"; +my $willi=0; +my $oldwind=0.0; +my $polling=0; +my $acthour=99; +my $actday=99; +my $actmonth=99; +my $oldrain=0; +my $rain_hour=0; +my $rain_day=0; +my $rain_month=0; +##################################### +sub +WS300_Initialize($) +{ + my ($hash) = @_; + + $hash->{Category} = "DEV"; + + # Provider + $hash->{Clients} = ":WS300:"; + $hash->{ReadFn} = "WS300_Read"; + $hash->{WriteFn} = "WS300_Write"; + $hash->{Type} = "FHZ1000"; + $hash->{Match} = "^WS300.*"; + $hash->{SetFn} = "WS300_Set"; + $hash->{GetFn} = "WS300_Get"; + $hash->{StateFn} = "WS300_SetState"; + $hash->{ListFn} = "WS300_List"; + $hash->{DefFn} = "WS300_Define"; + $hash->{UndefFn} = "WS300_Undef"; + $hash->{ParseFn} = "WS300_Parse"; + $hash->{ReadFn} = "WS300_Read"; +} + +################################### +sub +WS300_Set($@) +{ + my ($hash, @a) = @_; + if($hash->{NAME} eq "WS300Device") + { + return "wrong syntax: set WS300Device " if(int(@a) < 4 || int($a[1]) < 5 || int($a[1]) > 60 || int($a[2]) > 2000); + my $bstring = sprintf("%c%c%c%c%c%c%c%c",0xfe,0x30,(int($a[1])&0xff),((int($a[2])>>8)&0xff),(int($a[2])&0xff),((int($a[3])>>8)&0xff),(int($a[3])&0xff),0xfc); + $hash->{PortObj}->write($bstring); + Log 1,"WS300 synchronization started (".unpack('H*',$bstring).")"; + return "the ws300pc will now synchronize for 10 minutes"; + } + return "No set function implemented"; +} + +################################### +sub +WS300_Get(@) +{ + my ($hash, @a) = @_; + if($hash->{NAME} eq "WS300Device") + { + Log 5,"WS300_Get $a[0] $a[1]"; + WS300_Poll($hash); + return undef; + } + return "No get function implemented"; +} + +##################################### +sub +WS300_SetState($$$$) +{ + my ($hash, $tim, $vt, $val) = @_; + + return undef if(!defined($hash->{SENSOR})); + + my $n = $hash->{SENSOR}; + if(!$readings{$n}{$vt} || $readings{$n}{$vt}{TIM} lt $tim) { + $readings{$n}{$vt}{TIM} = $tim; + $readings{$n}{$vt}{VAL} = $val; + } + return undef; +} + +##################################### +sub +WS300_List($) +{ + my ($hash) = @_; + my $str = ""; + + return "No information about $hash->{NAME}" if(!defined($hash->{SENSOR})); + my $n = $hash->{SENSOR}; + if(!defined($readings{$n})) + { + $str .= "No information about " . $hash->{NAME} . "\n"; + } + else + { + foreach my $m (keys %{ $readings{$n} }) + { + $str .= sprintf("%-19s %-15s %s\n",$readings{$n}{$m}{TIM}, $m, $readings{$n}{$m}{VAL}); + } + } + return $str; + +} + +##################################### +sub +WS300_Define($@) +{ + my ($hash, @a) = @_; + + if($a[0] eq "WS300Device") + { + return "wrong syntax: define WS300Device WS300 " if(int(@a) < 3); + $DeviceName = $a[2]; + $hash->{STATE} = "Initializing"; + $hash->{SENSOR} = 10; + $readings{10}{WS300Device}{VAL} = "Initializing"; + $readings{10}{WS300Device}{TIM} = TimeNow; + my $po = new Device::SerialPort ($a[2]); + if(!$po) + { + $hash->{STATE} = "error opening device"; + $readings{10}{WS300Device}{VAL} = "error opening device"; + $readings{10}{WS300Device}{TIM} = TimeNow; + Log 1,"Error opening WS300 Device $a[2]"; + return "Can't open $a[2]: $!\n"; + } + $po->reset_error(); + $po->baudrate(19200); + $po->databits(8); + $po->parity('even'); + $po->stopbits(1); + $po->handshake('none'); + $po->rts_active(1); + $po->dtr_active(1); + sleep(1); + $po->rts_active(0); + $hash->{PortObj} = $po; + $hash->{DeviceName} = $a[2]; + $hash->{STATE} = "opened"; + $readings{10}{WS300Device}{VAL} = "opened"; + $readings{10}{WS300Device}{TIM} = TimeNow; + CommandAt($hash,"+*00:00:05 get WS300Device data"); + Log 1,"WS300 Device $a[2] opened"; + return undef; + } + return "wrong syntax: define WS300 \n0-7=ASH2200\n8=KS300\n9=WS300" if(int(@a) < 3); + return "no device: define WS300Device WS300 first" if($DeviceName eq ""); + $a[2] = lc($a[2]); + return "Define $a[0]: wrong sensor number." if($a[2] !~ m/^[0-9]$/); + $hash->{SENSOR} = $a[2]; + $defptr{$a[2]} = $hash; + + return undef; +} + +##################################### +sub +WS300_Undef($$) +{ + my ($hash, $name) = @_; + return undef if(!defined($hash->{SENSOR})); + delete($defptr{$hash->{SENSOR}}); + return undef; +} + + +##################################### +sub +WS300_Parse($) +{ + my $msg = shift; + my $ll = GetLogLevel("WS300Device"); + $ll = 5 if($ll == 2); + + my @c = split("", $config); + my @cmsg = split("",unpack('H*',$config)); + my $dmsg = unpack('H*',$msg); + my @a = split("", $dmsg); + my $val = ""; + my $tm; + my $h; + my $t; + my $b; + my $l; + my $value; + my $offs=0; + my $ref; + my $def; + my $zeit; + my @txt = ( "temperature", "humidity", "wind", "rain_raw", "israining", "battery", "lost_receives", "pressure", "rain_cum", "rain_hour", "rain_day", "rain_month"); + my @sfx = ( "(Celsius)", "(%)", "(km/h)", "(counter)", "(yes/no)", " ", "(counter)", "(hPa)", "(mm)", "(mm)", "(mm)", "(mm)"); + # 1 2 3 4 5 6 7 8 + # 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 + # 3180800001005d4e00000000000000000000000000000000000000000000594a0634001e00f62403f1fc stored + # aaaatttthhtttthhtttthhtttthhtttthhtttthhtttthhtttthhtttthhrrrrwwwwtttthhpppp + # 3300544a0000000000000000000000000000000000000000000057470634002c00f32303ee32fc current + # tttthhtttthhtttthhtttthhtttthhtttthhtttthhtttthhtttthhrrrrwwwwtttthhppppss + # 3210000000000000001005003a0127fc config + # 001122334455667788iihhhhmmmm + $offs = 2 if(hex($a[0].$a[1]) == 0x33); + $offs = 10 if(hex($a[0].$a[1]) == 0x31); + if($offs == 0) + { + Log 1,"WS300 illegal data in WS300_Parse"; + return undef; + } + $zeit = time; + my $wind = hex($a[58+$offs].$a[59+$offs].$a[60+$offs].$a[61+$offs]); + $wind /= 10.0; + if(hex($a[0].$a[1]) == 0x33) + { + return undef if(hex($a[74].$a[75]) == $willi && $wind == $oldwind ); + $willi = hex($a[74].$a[75]); + $ir="no"; + $ir="yes" if(($willi&0x80)); + } + else + { + $zeit -= (hex($a[6].$a[7].$a[8].$a[9])*60); + } + my @lt = localtime($zeit); + $tm = sprintf("%04d-%02d-%02d %02d:%02d:%02d",$lt[5]+1900, $lt[4]+1, $lt[3], $lt[2], $lt[1], $lt[0]); + $oldwind = $wind; + my $press = hex($a[68+$offs].$a[69+$offs].$a[70+$offs].$a[71+$offs]); + my $hpress = hex($cmsg[22].$cmsg[23].$cmsg[24].$cmsg[25]); + $hpress /= 8.5; + $press += $hpress; + $press = sprintf("%.1f",$press); + my $rainc = hex($a[54+$offs].$a[55+$offs].$a[56+$offs].$a[57+$offs]); + my $rain = hex($cmsg[26].$cmsg[27].$cmsg[28].$cmsg[29]); + $rain *= $rainc; + $rain /= 1000; + $rain = sprintf("%.1f",$rain); + for(my $s=0;$s<9;$s++) + { + if((ord($c[$s+1])&0x10)) + { + my $p=($s*6)+$offs; + Log $ll,"Sensor $s vorhanden"; + if(!defined($defptr{$s})) + { + Log(3,"WS300 $s: undefined"); + } + else + { + $readings{$s}{$txt[0]}{VAL} = 0 if(!$readings{$s}); + $ref = $readings{$s}; + $def = $defptr{$s}; + $t = hex($a[$p].$a[$p+1].$a[$p+2].$a[$p+3]); + $t -= 65535 if( $t > 32767 ); + $t /= 10.0; + $h = hex($a[$p+4].$a[$p+5]); + if((ord($c[$s+1])&0xe0)) + { + $b = "Empty" + } + else + { + $b = "Ok" + } + $l = (ord($c[$s+1])&0x0f); + if($s < 8) + { + # state + $val = "T: $t H: $h Bat: $b LR: $l"; + $def->{STATE} = $val; + $def->{CHANGED}[0] = $val; + $def->{CHANGETIME}[0] = $tm; + # temperatur + $ref->{$txt[0]}{TIM} = $tm; + $value = "$t $sfx[0]"; + $ref->{$txt[0]}{VAL} = $value; + $def->{CHANGED}[1] = "$txt[0]: $value"; + $def->{CHANGETIME}[1] = $tm; + # humidity + $ref->{$txt[1]}{TIM} = $tm; + $value = "$h $sfx[1]"; + $ref->{$txt[1]}{VAL} = $value; + $def->{CHANGED}[2] = "$txt[1]: $value"; + $def->{CHANGETIME}[2] = $tm; + # battery + $ref->{$txt[5]}{TIM} = $tm; + $value = "$b $sfx[5]"; + $ref->{$txt[5]}{VAL} = $value; + $def->{CHANGED}[3] = "$txt[5]: $value"; + $def->{CHANGETIME}[3] = $tm; + # lost receives + $ref->{$txt[6]}{TIM} = $tm; + $value = "$l $sfx[6]"; + $ref->{$txt[6]}{VAL} = $value; + $def->{CHANGED}[4] = "$txt[6]: $value"; + $def->{CHANGETIME}[4] = $tm; + + Log 2,"WS300 $def->{NAME}: $val"; + DoTrigger($def->{NAME},undef); + } + else + { + # state + $val = "T: $t H: $h W: $wind R: $rain IR: $ir Bat: $b LR: $l"; + $def->{STATE} = $val; + $def->{CHANGED}[0] = $val; + $def->{CHANGETIME}[0] = $tm; + # temperature + $ref->{$txt[0]}{TIM} = $tm; + $value = "$t $sfx[0]"; + $ref->{$txt[0]}{VAL} = $value; + $def->{CHANGED}[1] = "$txt[0]: $value"; + $def->{CHANGETIME}[1] = $tm; + # humidity + $ref->{$txt[1]}{TIM} = $tm; + $value = "$h $sfx[1]"; + $ref->{$txt[1]}{VAL} = $value; + $def->{CHANGED}[2] = "$txt[1]: $value"; + $def->{CHANGETIME}[2] = $tm; + # wind + $ref->{$txt[2]}{TIM} = $tm; + $value = "$wind $sfx[2]"; + $ref->{$txt[2]}{VAL} = $value; + $def->{CHANGED}[3] = "$txt[2]: $value"; + $def->{CHANGETIME}[3] = $tm; + #rain counter + $ref->{$txt[3]}{TIM} = $tm; + $value = "$rainc $sfx[3]"; + $ref->{$txt[3]}{VAL} = $value; + $def->{CHANGED}[4] = "$txt[3]: $value"; + $def->{CHANGETIME}[4] = $tm; + # is raining + $ref->{$txt[4]}{TIM} = $tm; + $value = "$ir $sfx[4]"; + $ref->{$txt[4]}{VAL} = $value; + $def->{CHANGED}[5] = "$txt[4]: $value"; + $def->{CHANGETIME}[5] = $tm; + # battery + $ref->{$txt[5]}{TIM} = $tm; + $value = "$b $sfx[5]"; + $ref->{$txt[5]}{VAL} = $value; + $def->{CHANGED}[6] = "$txt[5]: $value"; + $def->{CHANGETIME}[6] = $tm; + # lost receives + $ref->{$txt[6]}{TIM} = $tm; + $value = "$l $sfx[6]"; + $ref->{$txt[6]}{VAL} = $value; + $def->{CHANGED}[7] = "$txt[6]: $value"; + $def->{CHANGETIME}[7] = $tm; + # rain cumulative + $ref->{$txt[8]}{TIM} = $tm; + $value = "$rain $sfx[8]"; + $ref->{$txt[8]}{VAL} = $value; + $def->{CHANGED}[8] = "$txt[8]: $value"; + $def->{CHANGETIME}[8] = $tm; + # statistics + if($actday == 99) + { + $oldrain = $rain; + $acthour = $ref->{acthour}{VAL} if(defined($ref->{acthour}{VAL})); + $actday = $ref->{actday}{VAL} if(defined($ref->{actday}{VAL})); + $actmonth = $ref->{actmonth}{VAL} if(defined($ref->{actmonth}{VAL})); + $rain_day = $ref->{rain_day}{VAL} if(defined($ref->{rain_day}{VAL})); + $rain_month = $ref->{rain_month}{VAL} if(defined($ref->{rain_month}{VAL})); + $rain_hour = $ref->{rain_hour}{VAL} if(defined($ref->{rain_hour}{VAL})); + } + if($acthour != $lt[2]) + { + $acthour = $lt[2]; + $rain_hour = sprintf("%.1f",$rain_hour); + $rain_day = sprintf("%.1f",$rain_day); + $rain_month = sprintf("%.1f",$rain_month); + $ref->{acthour}{TIM} = $tm; + $ref->{acthour}{VAL} = "$acthour"; + $ref->{$txt[9]}{TIM} = $tm; + $ref->{$txt[9]}{VAL} = $rain_hour; + $def->{CHANGED}[9] = "$txt[9]: $rain_hour $sfx[9]"; + $def->{CHANGETIME}[9] = $tm; + $ref->{$txt[10]}{TIM} = $tm; + $ref->{$txt[10]}{VAL} = $rain_day; + $def->{CHANGED}[10] = "$txt[10]: $rain_day $sfx[10]"; + $def->{CHANGETIME}[10] = $tm; + $ref->{$txt[11]}{TIM} = $tm; + $ref->{$txt[11]}{VAL} = $rain_month; + $def->{CHANGED}[11] = "$txt[11]: $rain_month $sfx[11]"; + $def->{CHANGETIME}[11] = $tm; + $rain_hour=0; + } + if($actday != $lt[3]) + { + $actday = $lt[3]; + $ref->{actday}{TIM} = $tm; + $ref->{actday}{VAL} = "$actday"; + $rain_day=0; + } + if($actmonth != $lt[4]+1) + { + $actmonth = $lt[4]+1; + $ref->{actmonth}{TIM} = $tm; + $ref->{actmonth}{VAL} = "$actmonth"; + $rain_month=0; + } + if($rain != $oldrain) + { + $rain_hour += ($rain-$oldrain); + $rain_hour = sprintf("%.1f",$rain_hour); + $rain_day += ($rain-$oldrain); + $rain_day = sprintf("%.1f",$rain_day); + $rain_month += ($rain-$oldrain); + $rain_month = sprintf("%.1f",$rain_month); + $oldrain = $rain; + + $ref->{acthour}{TIM} = $tm; + $ref->{acthour}{VAL} = "$acthour"; + $ref->{$txt[9]}{TIM} = $tm; + $ref->{$txt[9]}{VAL} = $rain_hour; + $def->{CHANGED}[9] = "$txt[9]: $rain_hour $sfx[9]"; + $def->{CHANGETIME}[9] = $tm; + $ref->{$txt[10]}{TIM} = $tm; + $ref->{$txt[10]}{VAL} = $rain_day; + $def->{CHANGED}[10] = "$txt[10]: $rain_day $sfx[10]"; + $def->{CHANGETIME}[10] = $tm; + $ref->{$txt[11]}{TIM} = $tm; + $ref->{$txt[11]}{VAL} = $rain_month; + $def->{CHANGED}[11] = "$txt[11]: $rain_month $sfx[11]"; + $def->{CHANGETIME}[11] = $tm; + } + Log 2,"WS300 $def->{NAME}: $val"; + DoTrigger($def->{NAME},undef); + } + } + } + } + if(!defined($defptr{9})) + { + Log(3,"WS300 9: undefined"); + } + else + { + $readings{9}{$txt[0]}{VAL} = 0 if(!$readings{9}); + $ref = $readings{9}; + $def = $defptr{9}; + $t = hex($a[62+$offs].$a[63+$offs].$a[64+$offs].$a[65+$offs]); + $t -= 65535 if( $t > 32767 ); + $t /= 10.0; + $h = hex($a[66+$offs].$a[67+$offs]); + # state + $val = "T: $t H: $h P: $press Willi: $willi"; + $def->{STATE} = $val; + $def->{CHANGED}[0] = $val; + $def->{CHANGETIME}[0] = $tm; + # temperature + $ref->{$txt[0]}{TIM} = $tm; + $value = "$t $sfx[0]"; + $ref->{$txt[0]}{VAL} = $value; + $def->{CHANGED}[1] = "$txt[0]: $value"; + $def->{CHANGETIME}[1] = $tm; + # humidity + $ref->{$txt[1]}{TIM} = $tm; + $value = "$h $sfx[1]"; + $ref->{$txt[1]}{VAL} = $value; + $def->{CHANGED}[2] = "$txt[1]: $value"; + $def->{CHANGETIME}[2] = $tm; + # pressure + $ref->{$txt[7]}{TIM} = $tm; + $value = "$press $sfx[7]"; + $ref->{$txt[7]}{VAL} = $value; + $def->{CHANGED}[3] = "$txt[7]: $value"; + $def->{CHANGETIME}[3] = $tm; + # willi + $ref->{willi}{TIM} = $tm; + $value = "$willi"; + $ref->{willi}{VAL} = $value; + $def->{CHANGED}[4] = "willi: $value"; + $def->{CHANGETIME}[4] = $tm; + + Log 2,"WS300 $def->{NAME}: $val"; + DoTrigger($def->{NAME},undef); + } + return undef; +} +##################################### +sub +WS300_Read($) +{ + my ($hash) = @_; +} +##################################### +sub +WS300_Write($$$) +{ + my ($hash,$fn,$msg) = @_; +} + +##################################### +sub +WS300_Poll($) +{ + my $hash = shift; + my $bstring=" "; + my $count; + my $inchar=''; + my $escape=0; + my $ll = GetLogLevel("WS300Device"); + $ll = 5 if($ll == 2); + + if(!$hash || !defined($hash->{PortObj})) + { + return; + } + return if($polling); + $polling=1; +NEXTPOLL: + $inbuf = $hash->{PortObj}->input(); + $bstring = sprintf("%c%c%c",0xfe,$cmd,0xfc); + + my $ret = $hash->{PortObj}->write($bstring); + if($ret <= 0) + { + + my $devname = $hash->{DeviceName}; + Log 1, "USB device $devname disconnected, waiting to reappear"; + $hash->{PortObj}->close(); + $hash->{STATE} = "disconnected"; + $readings{10}{WS300Device}{VAL} = "disconnected"; + $readings{10}{WS300Device}{TIM} = TimeNow; + sleep(1); + my $po = new Device::SerialPort($devname); + if($po) + { + $po->reset_error(); + $po->baudrate(19200); + $po->databits(8); + $po->parity('even'); + $po->stopbits(1); + $po->handshake('none'); + $po->rts_active(1); + $po->dtr_active(1); + sleep(1); + $po->rts_active(0); + Log 1, "USB device $devname reappeared"; + $hash->{PortObj} = $po; + $hash->{STATE} = "opened"; + $readings{10}{WS300Device}{VAL} = "opened"; + $readings{10}{WS300Device}{TIM} = TimeNow; + $polling=0; + return; + } + } + $inbuf = ""; + my $start=0; + my $tout=time(); + my $rcount=0; + my $ic=0; + + for(;;) + { + ($count,$inchar) = $hash->{PortObj}->read(1); + if($count == 0) + { + last if($tout < time()); + } + else + { + $ic = hex(unpack('H*',$inchar)); + if(!$start) + { + if($ic == 0xfe) + { + $start = 1; + } + } + else + { + if($ic == 0xf8) + { + $escape = 1; + $count = 0; + } + else + { + if($escape) + { + $ic--; + $inbuf .= chr($ic); + $escape = 0; + } + else + { + $inbuf .= $inchar; + last if($ic == 0xfc); + } + } + } + $rcount += $count; + $tout=time(); + } + } + + Log($ll,"WS300/RAW: ".$rcount." ".unpack('H*',$inbuf)); + if($ic != 0xfc) + { + $errcount++ if($errcount < 10); + if($errcount == 10) + { + $hash->{STATE} = "timeout"; + $readings{10}{WS300Device}{VAL} = "timeout"; + $readings{10}{WS300Device}{TIM} = TimeNow; + $errcount++; + } + Log 1,"WS300: no data" if($rcount == 0); + Log 1,"WS300: wrong data ".unpack('H*',$inbuf) if($rcount > 0); + $polling=0; + return; + } + if($hash->{STATE} ne "connected" && $errcount > 10) + { + $hash->{STATE} = "connected"; + $readings{10}{WS300Device}{VAL} = "connected"; + $readings{10}{WS300Device}{TIM} = TimeNow; + } + $errcount = 0; + $ic = ord(substr($inbuf,0,1)); + if($ic == 0x32) + { + $config = $inbuf if($rcount == 16); + $cmd=0x31; + goto NEXTPOLL; + } + if($ic == 0x31) + { + if($rcount == 42) + { + WS300_Parse($inbuf); + goto NEXTPOLL; + } + else + { + $cmd=0x33; + goto NEXTPOLL; + } + } + if($ic == 0x33) + { + WS300_Parse($inbuf) if($rcount == 39); + $cmd=0x32; + } + $polling=0; +} + +1; diff --git a/FHEM/90_FileLog.pm b/FHEM/90_FileLog.pm new file mode 100755 index 000000000..6ddfec9a9 --- /dev/null +++ b/FHEM/90_FileLog.pm @@ -0,0 +1,97 @@ +############################################## +package main; + +use strict; +use warnings; +use IO::File; + +##################################### +sub +FileLog_Initialize($) +{ + my ($hash) = @_; + + $hash->{Category}= "LOG"; + + $hash->{DefFn} = "FileLog_Define"; + $hash->{UndefFn} = "FileLog_Undef"; + $hash->{LogFn} = "FileLog_Log"; +} + + +##################################### +sub +FileLog_Define($@) +{ + my ($hash, @a) = @_; + my $fh; + + return "wrong syntax: define FileLog filename regexp" if(int(@a) != 4); + + eval { "Hallo" =~ m/^$a[3]$/ }; + return "Bad regexp: $@" if($@); + + my @t = localtime; + my $f = ResolveDateWildcards($a[2], @t); + $fh = new IO::File ">>$f"; + return "Can't open $f" if(!defined($fh)); + + $hash->{FH} = $fh; + $hash->{REGEXP} = $a[3]; + $hash->{FILENAME} = $a[2]; + $hash->{CURRENT} = $f; + + return undef; +} + +##################################### +sub +FileLog_Undef($$) +{ + my ($hash, $name) = @_; + close($hash->{FH}); + return undef; +} + + +##################################### +sub +FileLog_Log($$) +{ + my ($log, $dev) = @_; + + my $n = $dev->{NAME}; + my $re = $log->{REGEXP}; + my $max = int(@{$dev->{CHANGED}}); + + for (my $i = 0; $i < $max; $i++) { + my $s = $dev->{CHANGED}[$i]; + $s = "" if(!defined($s)); + if($n =~ m/^$re$/ || "$n:$s" =~ m/^$re$/) { + my $t = TimeNow(); + $t = $dev->{CHANGETIME}[$i] if(defined($dev->{CHANGETIME}[$i])); + $t =~ s/ /_/; + + my $fh = $log->{FH}; + my @t = localtime; + my $cn = ResolveDateWildcards($log->{FILENAME}, @t); + + if($cn ne $log->{CURRENT}) { # New logfile + $fh->close(); + $fh = new IO::File ">>$cn"; + if(!defined($fh)) { + Log(0, "Can't open $cn"); + return; + } + $log->{CURRENT} = $cn; + $log->{FH} = $fh; + } + + print $fh "$t $n $s\n"; + $fh->flush; + $fh->sync; + } + } +} + +1; diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..c8ca87c2b --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ +BINDIR=/usr/local/bin +MODDIR=/usr/local/lib + +VERS=3.3 +DATE=2006-01-25 +DIR=fhem-$(VERS) + +all: + @echo Nothing to do for all. + @echo To install, check the Makefile, and then \'make install\' + +install: + cp fhem.pl $(BINDIR) + cp -rp FHEM $(MODDIR) + perl -pi -e 's,modpath .,modpath $(MODDIR),' examples/* + +dist: + @echo Version is $(VERS), Date is $(DATE) + mkdir .f + cp -rp * .f + find .f -name \*.orig -print | xargs rm -f + find .f -type f -print |\ + xargs perl -pi -e 's/=VERS=/$(VERS)/g;s/=DATE=/$(DATE)/g' + mv .f fhem-$(VERS) + tar cf - fhem-$(VERS) | gzip > fhem-$(VERS).tar.gz + mv fhem-$(VERS)/docs/*.html . + rm -rf fhem-$(VERS) diff --git a/contrib/91_DbLog.pm b/contrib/91_DbLog.pm new file mode 100755 index 000000000..2aaa4794a --- /dev/null +++ b/contrib/91_DbLog.pm @@ -0,0 +1,99 @@ +############################################## +# Example for logging KS300 data into a DB. +# +# Prerequisites: +# - The DBI and the DBD:: modules must be installed. +# - a Database is created/configured +# - a db table: create table FHZLOG (TIMESTAMP varchar(20), TEMP varchar(5), +# HUM varchar(3), WIND varchar(4), RAIN varchar(8)); +# - Change the content of the dbconn variable below +# - extend your FHEM config file with +# notify .*H:.* {DbLog("@","%")} +# - copy this file into the /FHEM and restart fhem.pl +# +# If you want to change this setup, your starting point is the DbLog function + +my $dbconn = "Oracle:DBNAME:user:password"; + +package main; +use strict; +use warnings; +use DBI; + +my $dbh; + +sub DbDo($); +sub DbConnect(); + + +################################################################ +sub +DbLog_Initialize($) +{ + my ($hash) = @_; + + $hash->{Category} = "none"; + + # Lets connect here, so we see the error at startup + DbConnect(); +} + +################################################################ +sub +DbLog($$) +{ + my ($a1, $a2) = @_; + + # a2 is like "T: 21.2 H: 37 W: 0.0 R: 0.0 IR: no" + my @a = split(" ", $a2); + my $tm = TimeNow(); + + DbDo("insert into FHZLOG (TIMESTAMP, TEMP, HUM, WIND, RAIN) values " . + "('$tm', '$a[1]', '$a[3]', '$a[5]', '$a[7]')"); +} + + +################################################################ +sub +DbConnect() +{ + return 1 if($dbh); + Log 5, "Connecting to database $dbconn"; + my @a = split(":", $dbconn); + $dbh = DBI->connect("dbi:$a[0]:$a[1]", $a[2], $a[3]); + if(!$dbh) { + Log 1, "Can't connect to $a[1]: $DBI::errstr"; + return 0; + } + Log 5, "Connection to db $a[1] established"; + return 1; +} + +################################################################ +sub +DbDo($) +{ + my $str = shift; + + return 0 if(!DbConnect()); + Log 5, "Executing $str"; + my $sth = $dbh->do($str); + if(!$sth) { + Log 2, "DB: " . $DBI::errstr; + $dbh->disconnect; + $dbh = 0; + return 0 if(!DbConnect()); +#retry + $sth = $dbh->do($str); + if($sth) + { + Log 2, "Retry ok: $str"; + return 1; + } +# + return 0; + } + return 1; +} + +1; diff --git a/contrib/99_ALARM.pm b/contrib/99_ALARM.pm new file mode 100755 index 000000000..3fbb3470f --- /dev/null +++ b/contrib/99_ALARM.pm @@ -0,0 +1,153 @@ +############################################## +# Low Budget ALARM System +############################################## +# ATTENTION! This is more a toy than a real alarm system! You must know what you do! +############################################## +# +# Concept: +# 1x Signal Light (FS20 allight) to show the status (activated/deactivated) +# 1x Sirene (FS20 alsir1) +# 2x PIRI-2 (FS20 piriu pirio) +# 1x Sender (FS20 alsw) to activate/deactivate the system. +# Tip: use the KeyMatic CAC with pin code +# optional a normal sender (not a Keymatic CAC) FS20 alsw2 +# +# Add something like the following lines to the configuration file : +# notifyon alsw {MyAlsw()} +# notifyon alsw2 {MyAlswNoPin()} +# notifyon piriu {MyAlarm()} +# notifyon pirio {MyAlarm()} +# and put this file in the /FHEM directory. +# +# Martin Haas +############################################## + + +package main; +use strict; +use warnings; + +sub +ALARM_Initialize($) +{ + my ($hash) = @_; + + $hash->{Category} = "none"; +} + + +############################################## +# Switching Alarm System on or off +sub +MyAlsw() +{ + my $ON="set allight on; setstate alsw on"; + my $OFF1="set allight off"; + my $OFF2="set alsir1 off"; + my $OFF3="setstate alsw off"; + + if ( -e "/var/tmp/alertsystem") + { + unlink "/var/tmp/alertsystem"; + for (my $i = 0; $i < 2; $i++ ) + { + fhz "$OFF1"; + fhz "$OFF2"; + fhz "$OFF3"; + }; + Log 2, "alarm system is OFF"; + } else { + system "touch /var/tmp/alertsystem"; + for (my $i = 0; $i < 2; $i++ ) + { + fhz "$ON" + } + Log 2, "alarm system is ON"; + }; +} + + +############################################## +# If you have no Keymatic then use this workaround: +# After 4x pushing a fs20-button within some seconds it will activate/deactivate the alarm system. +sub +MyAlswNoPin() +{ + +my $timedout=5; + +## first time + if ( ! -e "/var/tmp/alontest1") + { + for (my $i = 1; $i < 4; $i++ ) + { + system "touch /var/tmp/alontest$i"; + system "touch -t 200601010101 /var/tmp/alontest$i"; + } + } + + +## test 4 times + my $now= `date +%s`; + for (my $i = 1; $i < 4; $i++ ) + { + my $tagx=`date -r /var/tmp/alontest$i +%s`; + my $testx=$now-$tagx; + + if ( $testx > $timedout ) + { + system "touch /var/tmp/alontest$i"; + Log 2, "test$i: more than $timedout sec\n"; + die; + } + } + system "touch -t 200601010101 /var/tmp/alontest*"; + Log 2, "ok, let's switch the alarm system..."; + +#if you only allow to activate (and not deactivate) with this script: +# if ( -e "/var/tmp/alertsystem") { die; }; + + MyAlsw(); +} + + + + +############################################## +# ALARM! Do what you want! +sub +MyAlarm() +{ + + #alarm-system activated?? + if ( -e "/var/tmp/alertsystem") + { + + + my $timer=180; # time until the sirene will be quit + my $ON1="set alsir1 on-for-timer $timer"; + + + #Paranoia + for (my $i = 1; $i < 3; $i++ ) + { + fhz "$ON1"; + } + Log 2, "ALARM! $ON1" ; + + + # have fun + my @lights=("stuwz1", "stuwz2", "stunacht", "stonacht", "stoliba"); + my @rollos=("rolu4", "rolu5", "roloadi", "rololeo", "roloco", "rolowz", "rolunik1", "rolunik2"); + + foreach my $light (@lights) { + fhz "set $light on" + } + + foreach my $rollo (@rollos) { + fhz "set $rollo on" + } + } +} + +1; diff --git a/contrib/99_PRIV.pm b/contrib/99_PRIV.pm new file mode 100755 index 000000000..9a9973052 --- /dev/null +++ b/contrib/99_PRIV.pm @@ -0,0 +1,33 @@ +############################################## +# Example for notifying with perl-code, in a proper file, not inline. +# Add the following line to the configuration file (02_fs20): +# notifyon btn3 {MyFunc("@", "%")} +# and put this file in the /FHEM directory. + +package main; +use strict; +use warnings; + +sub +PRIV_Initialize($$) +{ + my ($hash, $init) = @_; + $hash->{Category} = "none"; +} + +sub +MyFunc($$) +{ + my ($a1, $a2) = @_; + + Log 2, "Device $a1 was set to $a2 (type: $defs{$a1}{TYPE})"; + if($a2 eq "on") { + fhz "roll1 on-for-timer 10"; + fhz "roll2 on-for-timer 16"); + } else { + fhz "roll1 off"; + fhz "roll2 off"; + } +} + +1; diff --git a/contrib/99_SUNRISE.pm b/contrib/99_SUNRISE.pm new file mode 100755 index 000000000..f26c22b06 --- /dev/null +++ b/contrib/99_SUNRISE.pm @@ -0,0 +1,90 @@ +############################################## +# - Be aware: Installing the DateTime modules might be tedious, one way is: +# perl -MCPAN -e shell +# cpan> install DateTime::Event::Sunrise +# - Please call sunrise_coord before using this module, else you'll get times +# for frankfurt am main (germany). See the "at" entry in commandref.html + +package main; +use strict; +use warnings; + +use DateTime; +use DateTime::Event::Sunrise; + +sub sr($$$$); +sub sunrise_rel(@); +sub sunset_rel(@); +sub sunrise_abs(@); +sub sunset_abs(@); +sub isday(); +sub sunrise_coord($$$); +sub SUNRISE_Initialize($); + +# See perldoc DateTime::Event::Sunrise for details +my $long = "8.686"; +my $lat = "50.112"; +my $tz = "Europe/Berlin"; + +sub +SUNRISE_Initialize($) +{ + my ($hash) = @_; + + $hash->{Category} = "none"; +} + + +########################## +# Compute: +# rise: 1: event is sunrise (else sunset) +# isrel: 1: _relative_ times until the next event (else absolute for today) +# seconds: second offset to event +# daycheck: if set, then return 1 if the sun is visible, 0 else +sub +sr($$$$) +{ + my ($rise, $seconds, $isrel, $daycheck) = @_; + + my $sunrise = DateTime::Event::Sunrise ->new( + longitude => $long, + latitude => $lat, + altitude => '-6', # Civil twilight + iteration => '3'); + my $now = DateTime->now(time_zone => $tz); + my $stm = ($rise ? $sunrise->sunrise_datetime( $now ) : + $sunrise->sunset_datetime( $now )); + + if($daycheck) { + return 0 if(DateTime->compare($now, $stm) < 0); + $stm = $sunrise->sunset_datetime( $now ); + return 0 if(DateTime->compare($now, $stm) > 0); + return 1; + } + + if(!$isrel) { + $stm = $stm->add(seconds => $seconds) if($seconds); + return $stm->hms(); + } + + $stm = $stm->add(seconds => $seconds) if($seconds); + + if(DateTime->compare($now, $stm) >= 0) { + my $tom = DateTime->now(time_zone => $tz)->add(days => 1); + $stm = ($rise ? $sunrise->sunrise_datetime( $tom ) : + $sunrise->sunset_datetime( $tom )); + $stm = $stm->add(seconds => $seconds) if($seconds); + } + + my $diff = $stm->epoch - $now->epoch; + return sprintf("%02d:%02d:%02d", $diff/3600, ($diff/60)%60, $diff%60); +} + +sub sunrise_rel(@) { return sr(1, shift, 1, 0) } +sub sunset_rel(@) { return sr(0, shift, 1, 0) } +sub sunrise_abs(@) { return sr(1, shift, 0, 0) } +sub sunset_abs(@) { return sr(0, shift, 0, 0) } +sub isday() { return sr(1, 0, 0, 1) } +sub sunrise_coord($$$) { ($long, $lat, $tz) = @_; return undef; } + +1; diff --git a/contrib/README b/contrib/README new file mode 100755 index 000000000..cec8ef0fd --- /dev/null +++ b/contrib/README @@ -0,0 +1,21 @@ +- 91_DbLog.pm + Example to log data in a (DBI supported) database (MySQL, Oracle, etc) +- 99_ALARM.pm + Example for a Low Budget ALARM System by Martin +- checkmsg.pl + Check cwthe CRC of an FS20 hex message +- fhem + RC script by Stefan to be put into /etc/init.d and then symlinked + to /etc/rc3.d or similar. +- four2hex + Convert housecode from ELV notation (4) to fhem.pl notation (hex) +- fs20_holidays.sh + STefan's "presence simulator" for holidays +- garden.pl + Garden irrigation regulator with weather dependency (KS300 temp + rain) +- ks300avg.pl + Computing daily/monthly avarage values from a KS300 log +- rolwzo_not_off.sh + Martin's "don't lock me out" program: look at the comment +- rrd + Peter's RRD support. See the HOWTO diff --git a/contrib/checkmsg.pl b/contrib/checkmsg.pl new file mode 100755 index 000000000..40574c4e6 --- /dev/null +++ b/contrib/checkmsg.pl @@ -0,0 +1,26 @@ +die("Usage: checkmsg HEX-FHZ-MESSAGE\n") if(int(@ARGV) != 1); +my $msg = $ARGV[0]; + +die("Bad prefix (not 0x81)\n") if($msg !~ m/^81/); +print("Prefix is ok (0x81)\n"); + +my $l = hex(substr($msg, 2, 2)); +my $rl = length($msg)/2-2; +die("Bad length $l (should be $rl)\n") if($rl != $l); +print("Length is ok ($l)\n"); + +my @data; +for(my $i = 8; $i < length($msg); $i += 2) { + push(@data, ord(pack('H*', substr($msg, $i, 2)))); +} + +my $rcrc = 0; +map { $rcrc += $_; } @data; +$rcrc &= 0xFF; + +my $crc = hex(substr($msg, 6, 2)); +my $str = sprintf("Bad CRC 0x%02x (should be 0x%02x)\n", $crc, $rcrc); +die($str) if($crc ne $rcrc); +printf("CRC is ok (0x%02x)\n", $crc); + +exit(0); diff --git a/contrib/crc.pl b/contrib/crc.pl new file mode 100755 index 000000000..7066c2811 --- /dev/null +++ b/contrib/crc.pl @@ -0,0 +1,33 @@ +die("Usage: crc HEX-MESSAGE\n") if(int(@ARGV) != 2); +my $msg = $ARGV[0]; +$msg =~ s/ //g; + +my $des = $ARGV[1]; +$des =~ s/ //g; + +# FFFF: 77 72 statt 2c 7f +# FFFF: 5C AC statt DC D9 + + +#for(my $ic = 0; $ic < 65536; $ic++) { +for(my $ic = 0; $ic < 2; $ic++) { + my $crc = ($ic == 0?0:0xffffffff); + for(my $i = 0; $i < length($msg); $i += 2) { + my $n = ord(pack('H*', substr($msg, $i, 2))); + + my $od = $n; + for my $b (0..7) { + my $crcbit = ($crc & 0x80000000) ? 1 : 0; + my $databit = ($n & 0x80) ? 1 : 0; + $crc <<= 1; + $n <<= 1; + $crc ^= 0x04C11DB7 if($crcbit != $databit); +# printf("%3d.%d %02x CRC %x ($crcbit $databit)\n", $i/2, $b, $n, $crc); + } +# printf("%3d %02x CRC %02x %02x\n", $i/2, $od, ($crc&0xff00)>>8, $crc&0xff); + } +# print "$ic\n" if($ic % 10000 == 0); + printf("%02x %02x\n",($crc&0xff00)>>8,$crc&0xff); + print "got $ic\n" + if(sprintf("%02x%02x",($crc&0xff00)>>8,$crc&0xff) eq $des); +} diff --git a/contrib/fht.gnuplot b/contrib/fht.gnuplot new file mode 100644 index 000000000..c97033dd7 --- /dev/null +++ b/contrib/fht.gnuplot @@ -0,0 +1,33 @@ +############################ +# Display the measured temperature and actuator data logged +# as described in the 04_log config file. +# Copy your logfile to fht.log and then call +# gnuplot fht.gnuplot +# (i.e. this file) +# Note: The webfrontend pgm2 and pgm3 does this for you. + + +########################### +# Uncomment the following if you want to create a postscript file +# and comment out the pause at the end +#set terminal postscript color "Helvetica" 11 +#set output 'saplogins-lrn.ps' + +set xdata time +set timefmt "%Y-%m-%d_%H:%M:%S" +set xlabel " " + +set ylabel "Temperature (Celsius)" +set y2label "Actuator (%)" +set ytics nomirror +set y2tics +set y2label "Actuator (%)" + +set title 'FHT log' +plot \ + "< awk '/measured/{print $1, $4}' fht.log"\ + using 1:2 axes x1y1 title 'Measured temperature' with lines,\ + "< awk '/actuator/{print $1, $4+0}' fht.log"\ + using 1:2 axes x1y2 title 'Actuator (%)' with lines\ + +pause 100000 diff --git a/contrib/four2hex/Makefile b/contrib/four2hex/Makefile new file mode 100644 index 000000000..9bf17bbb7 --- /dev/null +++ b/contrib/four2hex/Makefile @@ -0,0 +1,6 @@ +CC=gcc + +four2hex : four2hex.c + +install : four2hex + install -m 0755 four2hex /usr/local/bin/four2hex diff --git a/contrib/four2hex/README b/contrib/four2hex/README new file mode 100644 index 000000000..a25edab64 --- /dev/null +++ b/contrib/four2hex/README @@ -0,0 +1,23 @@ +Four2hex was written to convert the housecode based on digits ranging from 1 +to 4 into hex code and vica versa. +Four2hex is freeware based on the GNU Public license. + +To built it: +$ make four2hex + +Install it to /usr/local/bin: +$ su +# make install + + +Here an example from "four"-based to hex: +$ four2hex 12341234 +1b1b + +Here an example in the other (reverse) direction: +$ four2hex -r 1b1b +12341234 + +Enjoy. +Peter Stark, (Peter dot stark at t-online dot de) + diff --git a/contrib/four2hex/four2hex b/contrib/four2hex/four2hex new file mode 100755 index 0000000000000000000000000000000000000000..57236bafe53a8462ac241b27efb0f9478807d8de GIT binary patch literal 5777 zcmd^DZERE589x4?7#f;{^3k!Zz1xxzI>jVupcH8xA>4$Z00jbt0=<~{CN_2K%)W-u zEf8i<6Pc`0K^j!kR5aB6VHMICVp~*&1vNpdri@nmQ27z0R5EfV!Bz>RW%Hiro_i7- zR89N6W4(UQbIx(8S+`eEOE+KPA>U`3Z;5cB!B-Vi+Brv8a)-0&&YQ z1iGS)U9ehoC9SqlATGM%;i%OvtXL#=#EgrC6*S{<(S9TzwoG9(Z$;5yIBFylW)PLJ zyi-|cARHArQ8dr za^IUq{jcuYmYUkvzj-XJkEPbQj``9%$5LxO$97%dK=yAaFB;uY?khvB&>R;5<9|Un zWPbVYT&{rhY$53(Nk0NYD7}X~c6=~S3_rm+M=+|$I>cdO>yuML=hzoBz zoxG9m8BT8=`p5glKmJ|uJHvf{ti0YgR{Y`cso~;NZ?kxEEZvCWJ{G^74W{+m#aH3> zaKE=all@mN*Qb95r#~aSjTZFlnNfX;hO;ZvdL}hdbp4@wUB7%BS^`Civh>>qT@0iv}|!w4O)b{0Ma*c271HWpTw7xk!9|bSd!o`T z&*jVwwg@uyL$419fw+R@jvZTU#F@@Bv;^rqXM2HJ*C7b6D=ddS(hls8AhsEv)zy$i z5cC&LP9cU+p7Bf2JS$m__Y`jQSkkf6NJpDuY|B09H-QoTp9AkGs?ajrtDc9fg=~Vn z1~~*#`Ge#(H8iZ%DsjzNq1E|o{Iyzbb@l3+>e?5z%C5K>HzQ^sVd7@+|Hkttj&@ps zgTjxAEOki5a3?Y2UBVxYS*E{!eRGu+=n(!;AQ2M&;L#`wN^Ql3zayITA2#EOa4cE? z4CJA6$(NC?h$Z~tXc+&N*^U2p0A$620V^Q|}FM(}t)RQc4D7a?3%yn?De z&>joiKbR=L_T@eyv8;gmfyA=KT=x=7uerV@mZPQ4MvpzBa=prYOil!@ONnh|pS=>( z3%SlD=1C48oiov6?>xeOkC6Ftq_{pL=FLE^1BsuqG3UF)=sX0@Yl-D*^F`wPorCr9GjTR}@Sc(%JVLzy$W#wT zo#S}X=?^z%DqF1l;TQ9Bz{(#Na;t!qKQ8R7Yk}?WJ!ltpvSw(03qk=e5wAdx=hffN z419l5(Ow+7&*>l9I}U8$Q=n<@G_>k(mnatR0_WR*pZU&svA!W-6};kpZWKV>kzDSl z#W=9?Pq^>r?f`Q=vy98VSUh+ne?B^h`ygj#sd!410-tvHrS2u-S#}`uIa3$HzMmLD zy|L6gcpcI>`mMm+_Yno2FJ1%Y`j_=D6%kDsn0<(RrgY{1UA94D7 zp}R;t0RBl{{xtBXdHwaIvuCI`p|1fe`z5gdypzxRHOR}4V9^$cMDm`E@a)sjx^=tJ z(!8S;Pe_Kbp#W`an90Hws4HSxX3)QCb=@k_MfCy-1Y<@=Bz7JIG zlbxMMl>vRrhS~ZRXxv`ESvM4-F%_S!)xNLETAdFhf15Bi?AcPkxw!$g^Uh}k;_<*y z!;In?tEpw{`uY}QYh&XMz13)~U*DqBuRNOlUM7t8E+ce=4kThmC=d-s@FXVRz}gZ? z!@fQ`?`Z4R%|3-WZ)y2ScHPe7T0y!r6tH5U{F?1cs$t+>DIeQ}{n+-{^ILvRyooww z`J|VhDVu7T_H`AGu3od+-(gxtSDRsllF>K(2fKyrn!*P^`3zW4laXi0*mT&~Zgzwd SxU4rs0*Qo~fENW1j{gC+Puqe3 literal 0 HcmV?d00001 diff --git a/contrib/four2hex/four2hex.c b/contrib/four2hex/four2hex.c new file mode 100644 index 000000000..a60349014 --- /dev/null +++ b/contrib/four2hex/four2hex.c @@ -0,0 +1,94 @@ +/* +Four2hex was written to convert the housecode based on digits ranging from 1 +to 4 into hex code and vica versa. +Four2hex is freeware based on the GNU Public license. + +To built it: +$ make four2hex + +Install it to /usr/local/bin: +$ su +# make install + + +Here an example from "four"-based to hex: +$ four2hex 12341234 +1b1b + +Here an example in the other (reverse) direction: +$ four2hex -r 1b1b +12341234 + +Enjoy. +Peter Stark, (Peter dot stark at t-online dot de) + +*/ + +#include +#include + +int atoh (const char c) +{ + int ret=0; + + ret = (int) (c - '0'); + if (ret > 9) { + ret = (int) (c - 'a' + 10); + } + return ret; +} + +int strlen(const char *); + +main (int argc, char **argv) +{ + char c, *s, *four; + long int result; + int b, i, h; + + if (argc < 2 || argc >3) { + fprintf (stderr, "usage: four2hex four-string\n"); + fprintf (stderr, " or: four2hex -r hex-string\n"); + return (1); + } + result = 0L; + if (strcmp(argv[1], "-r") == 0) { + /* reverse (hex->4) */ + for (s = argv[2]; *s != '\0'; s++) { + c = tolower(*s); + b = atoh(c); + for (i = 0; i < 2; i++) { + h = ((b & 0xc) >> 2) + 1; + b = (b & 0x3) << 2; + printf ("%d", h); + } + } + printf ("\n"); + } else { + /* normal (4->hex) */ + four = argv[1]; + if (strlen(four) == 4 || strlen(four) == 8) { + for (s = four; *s != '\0'; s++) { + result = result << 2; + switch (*s) { + case '1' : result = result + 0; break; + case '2' : result = result + 1; break; + case '3' : result = result + 2; break; + case '4' : result = result + 3; break; + default : + fprintf (stderr, "four-string may contain '1' to '4' only\n"); + break; + } + } + if (strlen(four) == 8) { + printf ("%04x\n", result); + } else { + printf ("%02x\n", result); + } + } else { + fprintf (stderr, "four-string must be of length 4 or 8\n"); + return (1); + } + } + return (0); +} diff --git a/contrib/fs20_holidays.sh b/contrib/fs20_holidays.sh new file mode 100755 index 000000000..e8ab01ba5 --- /dev/null +++ b/contrib/fs20_holidays.sh @@ -0,0 +1,94 @@ +#!/bin/sh +# +# script to generate a random number of on/off events to simulate presence eg. +# while on holidays. normally this script would be executed by an event like a +# dawn-sensor (you wouldn't want light during the day...:-) +# +# Copyright STefan Mayer + +################## configuration ########################### +#number of events (min - max) +event_min=5 +event_max=20 + +#maximum delay in minutes +delay_max=240 + +#minimum and maximum ontime in minutes +ontime_min=5 +ontime_max=60 + +#devices to consider +declare -a devices='("dg.gang" "dg.wand" "dg.dusche" "dg.bad" "dg.reduit")' + +#output variant [oft|onoff] +#oft: use one at with on-for-timer of system +#onoff: use two at, one for on one for off +variant="onoff" + +#command to execute +#command_start="/opt/fhem/fhem.pl 7072 \"" +command_start="echo /opt/fhem/fhem.pl 7072 \"" +command_end="\"" + + +##################### Shouldnt need any changes below here ##################### + +# count number of devices +count=0 +for i in ${devices[*]} +do + ((count++)) +done +# echo $count + +# maximum random in bash: 32768 +random_max=32768 + +#number of events +event=$(($RANDOM * (($event_max - $event_min)) / $random_max +$event_min)) + +#initialize command +command=$command_start + +for ((i=0; i<$event; i++)) +do + #calculate starttime + starttime=$(($RANDOM * $delay_max / $random_max)) + hour=$(($starttime / 60)) + minute=$(($starttime % 60)) + second=$(($RANDOM * 60 / $random_max)) + + #calculate ontime + ontime=$(($RANDOM * (($ontime_max - $ontime_min)) / $random_max +$ontime_min)) + + #choose device + dev=$(($RANDOM * $count / $random_max)) + + case $variant in + oft) + printf "event %02d: at +%02d:%02d:%02d set %s on-for-timer %d\n" $i $hour $minute $second ${devices[$dev]} $ontime + command=`printf "$command at +%02d:%02d:%02d set %s on-for-timer %d;;" $hour $minute $second ${devices[$dev]} $ontime` + ;; + onoff) + offtime=$(($starttime + $ontime)) + hour_off=$(($offtime / 60)) + minute_off=$(($offtime % 60)) + second_off=$(($RANDOM * 60 / $random_max)) + printf "event %02d/on : at +%02d:%02d:%02d set %s on\n" $i $hour $minute $second ${devices[$dev]} + printf "event %02d/off: at +%02d:%02d:%02d set %s off\n" $i $hour_off $minute_off $second_off ${devices[$dev]} + command=`printf "$command at +%02d:%02d:%02d set %s on;;" $hour $minute $second ${devices[$dev]}` + command=`printf "$command at +%02d:%02d:%02d set %s off;;" $hour_off $minute_off $second_off ${devices[$dev]}` + ;; + *) + echo "no variant specifieno variant specified!!" + ;; + esac + +done +command="$command $command_end" + +#execute command +eval "$command" + + diff --git a/contrib/garden.pl b/contrib/garden.pl new file mode 100755 index 000000000..f9473c1d1 --- /dev/null +++ b/contrib/garden.pl @@ -0,0 +1,212 @@ +#!/usr/bin/perl + +use warnings; +use strict; +use IO::Socket::INET; +use IO::Handle; + +STDOUT->autoflush(1); + +################# +# Formula: +# Compute for the last days + today the avarage temperature and the +# sum of rain, then compute the multiplier: (temp/20)^2 - rain/5 +# Now multiply the duration of each vent with this multiplier +# If the value is less than a minimum, then store the value and add it +# the next day +################# + +my $test = 0; # Test only, do not switch anything +my $fhzport = 7072; # Where to contact it +my $avg = "/home/rudi/log/avg.log"; # KS300 avarage log file +my $navg = 2; # Number of last avg_days to consider +my $min = 300; # If the duration is < min (sec) then collect +my $col = "/home/rudi/log/gardencoll.log"; # File where it will be collected +my $pmp = "GPumpe"; # Name of the water pump, will be switched in first +my $maxmult = 4; # Maximum factor (corresponds to 40 degree avg. + # temp over $navg days, no rain) + +if(@ARGV) { + if($ARGV[0] eq "test") { + $test = 1; + } else { + print "Usage: garden.pl [test]\n"; + exit(1); + } +} + +my %list = ( + GVent1 => { Nr => 1, Dur => 720 }, + GVent2 => { Nr => 2, Dur => 480 }, + GVent3 => { Nr => 3, Dur => 720 }, + GVent4 => { Nr => 4, Dur => 720 }, + GVent6 => { Nr => 5, Dur => 720 }, + GVent7 => { Nr => 6, Dur => 480 }, + GVent8 => { Nr => 7, Dur => 480 }, +); + +############################## +# End of config + +sub fhzcommand($); +sub doswitch($$); +sub donext($$); + +my ($nlines, $temp, $rain) = (0, 0, 0); +my ($KS300name, $server, $last); + +my @t = localtime; +printf("%04d-%02d-%02d %02d:%02d:%02d\n", + $t[5]+1900, $t[4]+1, $t[3], $t[2], $t[1], $t[0]); + +########################### +# First read in the last avg_days +open(FH, $avg) || die("$avg: $!\n"); +my @avg = ; +close(FH); + +my @tarr; # Want the printout in the right order +while(my $l = pop(@avg)) { + next if($l !~ m/avg_day/); + my @v = split(" ", $l); + push(@tarr, "$v[0]: T: $v[4], R: $v[10]") if($test); + $temp += $v[4]; $rain += $v[10]; + $KS300name = $v[1]; + $nlines++; + last if($nlines >= $navg); +} + +########################### +# Now get the current day +foreach my $l (split("\n", fhzcommand("list $KS300name"))) { + next if($l !~ m/avg_day/); + my @v = split(" ", $l); + print("$v[0] $v[1]: T: $v[4], R: $v[10]\n") if($test); + $temp += $v[4]; $rain += $v[10]; + $nlines++; + last; +} + +if($test) { + foreach my $l (@tarr) { + print "$l\n"; + } +} + + +########################### +# the collected data +my %coll; +if(open(FH, $col)) { + while(my $l = ) { + my ($k, $v) = split("[ \n]", $l); + $coll{$k} = $v; + } + close(FH); +} + +########################### +# The formula +$temp /= $nlines; +$rain /= $nlines; + +# safety measures +$rain = 0 if($rain < 0); +$temp = 0 if($temp < 0); +$temp = 40 if($temp > 40); +my $mult = exp( 2.0 * log( $temp / 20 )) - $rain/5; +$mult = $maxmult if($mult > $maxmult); + +if($mult <= 0) { + print("Multiplier is not positive ($mult), exiting\n"); + exit(0); +} + +printf("Multiplier is %.2f (T: $temp, R: $rain)\n", $mult, $temp, $rain); + +my $have = 0; +if(!$test) { + open(FH, ">$col") || die("Can't open $col: $!\n"); +} +foreach my $a (sort { $list{$a}{Nr} <=> $list{$b}{Nr} } keys %list) { + my $dur = int($list{$a}{Dur} * $mult); + + if(defined($coll{$a})) { + $dur += $coll{$a}; + printf(" $a: $dur ($coll{$a})\n"); + } else { + printf(" $a: $dur\n"); + } + + if($dur > $min) { + $list{$a}{Act} = $dur; + $have += $dur; + } else { + print FH "$a $dur\n" if(!$test); + } +} + +print("Total time is $have\n"); +exit(0) if($test); +close(FH); + +if($have) { + doswitch($pmp, "on") if($pmp); + sleep(3) if(!$test); + foreach my $a (sort { $list{$a}{Nr} <=> $list{$b}{Nr} } keys %list) { + next if(!$list{$a}{Act}); + donext($a, $list{$a}{Act}); + } + donext("", 0); + doswitch($pmp, "off") if($pmp); +} + +########################### +# Switch the next dev on and the last one off +sub +donext($$) +{ + my ($dev, $sl) = @_; + doswitch($dev, "on"); + doswitch($last, "off"); + $last = $dev; + if($test) { + print "sleeping $sl\n"; + } else { + sleep($sl); + } +} + +########################### +# Paranoid setting. +sub +doswitch($$) +{ + my ($dev, $how) = @_; + return if(!$dev || !$how); + + if($test) { + print "set $dev $how\n"; + return; + } + fhzcommand("set $dev $how"); + sleep(1); + fhzcommand("set $dev $how"); +} + +########################### +sub +fhzcommand($) +{ + my $cmd = shift; + + my ($ret, $buf) = ("", ""); + $server = IO::Socket::INET->new(PeerAddr => "localhost:$fhzport"); + die "Can't connect to the server at port $fhzport\n" if(!$server); + syswrite($server, "$cmd;quit\n"); + while(sysread($server, $buf, 256) > 0) { + $ret .= $buf; + } + close($server); + return $ret; +} diff --git a/contrib/init-scripts/fhem.1 b/contrib/init-scripts/fhem.1 new file mode 100755 index 000000000..c691d9a0f --- /dev/null +++ b/contrib/init-scripts/fhem.1 @@ -0,0 +1,46 @@ +#! /bin/sh -e +# +# +# +# Written by Stefan Manteuffel + +PATH=/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin +DAEMON=/usr/local/bin/fhem.pl +PIDFILE=/var/run/fhem.pid + +# Arguments to atd +# +ARGS="/etc/FHZ/fhem.cfg" + +test -x $DAEMON || exit 0 + +. /lib/lsb/init-functions + +case "$1" in + start) + echo "Starting deferred execution scheduler..." + start-stop-daemon -b --start --quiet --pidfile $PIDFILE --startas $DAEMON -- $ARGS + log_end_msg $? + ;; + stop) + log_begin_msg "Stopping deferred execution scheduler..." + start-stop-daemon --oknodo --stop --quiet --retry 30 --pidfile $PIDFILE --name fhem.pl + + log_end_msg $? + ;; + force-reload|restart) + log_begin_msg "Restarting deferred execution scheduler..." + if start-stop-daemon --stop --quiet --retry 30 --pidfile $PIDFILE --name fhem.pl; then + start-stop-daemon -b --start --quiet --pidfile $PIDFILE --startas $DAEMON -- $ARGS + log_end_msg $? + else + log_end_msg 1 + fi + ;; + *) + echo "Usage: /etc/init.d/fhem.pl {start|stop|restart|force-reload|reload}" + exit 1 + ;; +esac + +exit 0 diff --git a/contrib/init-scripts/fhem.2 b/contrib/init-scripts/fhem.2 new file mode 100755 index 000000000..2de44f8e3 --- /dev/null +++ b/contrib/init-scripts/fhem.2 @@ -0,0 +1,25 @@ +#!/bin/sh +# by Matthias Bauer + +case "$1" in + start) + echo "Starting $0" + fhem.pl /etc/fhem/fhem.conf + ;; + stop) + echo "Stopping $0" + killall fhem.pl + ;; + status) + cnt=`ps -ef | grep "fhem.pl" | grep -v grep | wc -l` + if [ "$cnt" -eq "0" ] ; then + echo "$0 is not running" + else + echo "$0 is running" + fi + ;; + *) + echo "Usage: $0 {start|stop|status}" + exit 1 +esac +exit 0 diff --git a/contrib/ks300avg.pl b/contrib/ks300avg.pl new file mode 100755 index 000000000..943012b44 --- /dev/null +++ b/contrib/ks300avg.pl @@ -0,0 +1,77 @@ +#!/usr/bin/perl + +# Compute Daily and monthly avarage temp/hum/wind and cumulative rain values +# from the "standard" KS300 logs. +# Best to concatenate all KS300-logs into one big file (cat out*.log > big.log) +# and then start the program with ks300avg.pl big.log +# Note: the program assumes that there are no "holes" in the logs. + +use strict; +use warnings; + +if(@ARGV != 1) { + print "Usage: ks300avg.pl KS300-logfile\n"; + exit(1); +} + +open(FH, $ARGV[0]) || die("$ARGV[0]: $!\n"); + +my ($mt, $mh, $mw, $md) = (0,0,0,0); +my ($t, $h, $w) = (0,0,0); +my (@ld, $lsec, $lr, $mr, $ldsec); +my ($dt, $dev, $sec, @a); + +while(my $l = ) { + next if($l =~ m/avg/); + + chomp $l; + @a = split(" ", $l); + $dev = $a[1]; + $dt = $a[0]; + my @d = split("[_:-]", $a[0]); + $sec = $d[3]*3600+$d[4]*60+$d[5]; + + if(!$lsec) { + @ld = @d; + $lr = $a[9]; + $mr = $a[9]; + $lsec = $ldsec = $sec; + next; + } + + my $difft = $sec - $lsec; + $difft += 86400 if($d[2] != $ld[2]); + + $lsec = $sec; + $t += $difft * $a[3]; + $h += $difft * $a[5]; + $w += $difft * $a[7]; + + $l = ; + + if($d[2] != $ld[2]) { # Day changed + my $diff = ($sec - $ldsec) + 86400; + $t /= $diff; $h /= $diff; $w /= $diff; + printf("$dt $dev avg_day T: %.1f H: %d W: %0.1f R: %.1f\n", + $t, $h, $w, $a[9]-$lr); + $lr = $a[9]; + $md++; + $mt += $t; $mh += $h; $mw += $w; + $t = $h = $w = 0; + $ldsec = $sec; + } + + if($d[1] != $ld[1]) { # Month changed + printf("$dt $dev avg_month T: %.1f H: %d W: %0.1f R: %.1f\n", + $mt/$md, $mh/$md, $mw/$md, $a[9]-$mr); + $mr = $a[9]; + $mt = $mh = $mw = $md = 0; + } + + @ld = @d; +} + +printf("$dt $dev avg_day T: %.1f H: %d W: %0.1f R: %.1f\n", + $t/$sec, $h/$sec, $w/$sec, $a[9]-$lr); +printf("$dt $dev avg_month T: %.1f H: %d W: %0.1f R: %.1f\n", + $mt/$md, $mh/$md, $mw/$md, $a[9]-$mr); diff --git a/contrib/rolwzo_not_off.sh b/contrib/rolwzo_not_off.sh new file mode 100755 index 000000000..bc5b43354 --- /dev/null +++ b/contrib/rolwzo_not_off.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# What do I want: If I'am sitting e.g. outside I don't want that the rollo goes +# down. solution: if the button for e.g. rollo will be pressed after 16:59 +# o'clock then the at-job for going down by sunset will be deleted + +# put something like the following into your fhz100.cfg: +# notifyon rolwzo /usr/local/bin/rolwzo_not_off.sh + + +FHZ="/usr/local/bin/fhem.pl 7072" +order="delete at set rolwzo off" + +DATESTRING=`date +"%H"` +[[ $DATESTRING > 16 ]] && $FHZ "$order" diff --git a/contrib/rrd/98_RRD.pm b/contrib/rrd/98_RRD.pm new file mode 100644 index 000000000..138787d68 --- /dev/null +++ b/contrib/rrd/98_RRD.pm @@ -0,0 +1,76 @@ +############################################## +# Example for writing to RRD. +# notify .*T:.* {RRDUpdateTemp("@","%")} +# and put this file in the /FHEM directory. + +package main; +use strict; +use warnings; +use RRDs; + +my $DB = "/var/lib/collectd/temperature.rrd"; + +sub +RRD_Initialize($$) +{ +my ($hash, $init) = @_; +$hash->{Type} = "none"; + +if(! -f $DB) + { + Log 3, "***RRD Init"; + RRDs::create($DB, "--step=300", + "DS:innen:GAUGE:1800:-30.0:70.0", + "DS:bad:GAUGE:1800:-30.0:70.0", + "DS:wasser:GAUGE:1800:-30.0:70.0", + "RRA:AVERAGE:0.5:1:288", + "RRA:MAX:0.5:12:168", + "RRA:MIN:0.5:12:168", + "RRA:AVERAGE:0.5:288:365") or die "Create error: ($RRDs::error)"; + } +} + +### FHT80 ### +sub +RRDUpdateInnen($$) +{ +my ($a1, $a2) = @_; +my @a = split(" ", $a2); +my $tm = TimeNow(); + +my $value = $a[1]; +Log 5, "Device $a1 was set to $a2 (type: $defs{$a1}{TYPE})"; + +Log 2, "***InnenTemp:$value um $tm RRD"; +RRDs::update($DB, "--template", "innen", "N:$value") or + die "Update error: ($RRDs::error)"; +} + +### HMS ### +sub +RRDUpdateTemp($$) +{ +my ($a1, $a2) = @_; +# a2 is like "T: 21.2 H: 37 " +my @a = split(" ", $a2); +my $tm = TimeNow(); +my $value = $a[1]; + +Log 5, "Device $a1 was set to $a2 (type: $defs{$a1}{TYPE})"; + +if($a1 eq "viebadtemp") + { + Log 2, "***BadTemp:$value um $tm RRD"; + RRDs::update($DB, "--template", "bad", "N:$value") or + die "Update error: ($RRDs::error)"; + } + +if($a1 eq "viewassertemp") + { + Log 2, "***WasserTemp:$value um $tm RRD"; + RRDs::update($DB, "--template", "wasser", "N:$value") or + die "Update error: ($RRDs::error)"; + } +} + +1; diff --git a/contrib/rrd/fhz1000-rrd-howto.txt b/contrib/rrd/fhz1000-rrd-howto.txt new file mode 100644 index 000000000..76bac9597 --- /dev/null +++ b/contrib/rrd/fhz1000-rrd-howto.txt @@ -0,0 +1,52 @@ +apt-get install rrdtool + +if RRDs.pm is missing apt-get install librrds-perl +apt-get collectd + +Zum weiterlesen +Messdaten mit RRDtool und Perl verwalten http://www.linux-magazin.de/Artikel/ausgabe/2004/06/perl/perl.html + +my example: + + RRDs::create($DB, "--step=300", #300 = every 5 min + "DS:innen:GAUGE:1800:-30.0:70.0", #FHT80 room temperature + "DS:bad:GAUGE:1800:-30.0:70.0", #HMS100TF bath temperature + "DS:wasser:GAUGE:1800:-30.0:70.0", #HMS100T water temperature + "RRA:AVERAGE:0.5:1:288", + "RRA:MAX:0.5:12:168", + "RRA:MIN:0.5:12:168", + "RRA:AVERAGE:0.5:288:365") or die "Create error: ($RRDs::error)"; + +Modification on collection.cgi + + temperature => [ + 'DEF:temp_avg={file}:bad:AVERAGE', + 'DEF:temp_min={file}:bad:MIN', + 'DEF:temp_max={file}:bad:MAX', + 'DEF:i_avg={file}:innen:AVERAGE', + 'DEF:i_min={file}:innen:MIN', + 'DEF:i_max={file}:innen:MAX', + 'DEF:w_avg={file}:wasser:AVERAGE', + 'DEF:w_min={file}:wasser:MIN', + 'DEF:w_max={file}:wasser:MAX', + "AREA:temp_max#$HalfBlue", + "AREA:temp_min#$Canvas", + "LINE1:temp_avg#$FullRed:Bad", + 'GPRINT:temp_min:MIN:%4.1lf Min,', + 'GPRINT:temp_avg:AVERAGE:%4.1lf Avg,', + 'GPRINT:temp_max:MAX:%4.1lf Max,', + 'GPRINT:temp_avg:LAST:%4.1lf zuletzt\l', + "LINE2:i_avg#$FullGreen:Innen", + 'GPRINT:i_min:MIN:%4.1lf Min,', + 'GPRINT:i_avg:AVERAGE:%4.1lf Avg,', + 'GPRINT:i_max:MAX:%4.1lf Max,', + 'GPRINT:i_avg:LAST:%4.1lf zuletzt\l', + "LINE3:w_avg#$FullBlue:Wasser", + 'GPRINT:w_min:MIN:%4.1lf Min,', + 'GPRINT:w_avg:AVERAGE:%4.1lf Avg,', + 'GPRINT:w_max:MAX:%4.1lf Max,', + 'GPRINT:w_avg:LAST:%4.1lf zuletzt\l' + + ], + + diff --git a/contrib/serial.pl b/contrib/serial.pl new file mode 100755 index 000000000..47e01f82f --- /dev/null +++ b/contrib/serial.pl @@ -0,0 +1,89 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use Device::SerialPort; +use Time::HiRes qw(gettimeofday); +sub pp($$); + +if(@ARGV != 3) { + printf("Usage: perl serial.pl serial-device outfile initial-hex-msg\n"); + exit(1); +} +my $ser = $ARGV[0]; +my $fil = $ARGV[1]; +my $hm = $ARGV[2]; + +my $fd; +open($fd, ">$fil") || die("Can't open $fil for writing\n"); +select $fd; +$| = 1; + +my $serport = new Device::SerialPort ($ser); +die "Can't open $ser: $!\n" if(!$serport); +$serport->reset_error(); +$serport->baudrate(38400); +$serport->databits(8); +$serport->parity('none'); +$serport->stopbits(1); +$serport->handshake('none'); + +my $interval = 2.0; # Seconds + +my $nto = gettimeofday(); +my $nfound; + +$hm=~ s/ //g; +$hm = pack('H*', $hm); + +while (1) { + my ($rout, $rin) = ('', ''); + vec($rin, 0, 1) = 1; # stdin + vec($rin, $serport->FILENO, 1) = 1; + + my $to = $nto - gettimeofday(); + if($to > 0) { + $nfound = select($rout=$rin, undef, undef, $to); + die("Select error $nfound / $!\n") if($nfound < 0); + } + + if($to <= 0 || $nfound == 0) { # Timeout + $serport->write($hm); + pp("S>", $hm); + $nto = gettimeofday() + $interval; + } + + if(vec($rout, 0, 1)) { + my $buf = ; + die "EOF on STDIN\n" if(!defined($buf) || length($buf) == 0); + $buf=~ s/[ \r\n]//g; + $buf = pack('H*', $buf); + $serport->write($buf); + pp("X>", $buf); + } + if(vec($rout, $serport->FILENO, 1)) { + my $buf = $serport->input(); + die "EOF on $ser\n" if(!defined($buf) || length($buf) == 0); + pp("S<", $buf); + } +} + +sub +pp($$) { + my ($prompt, $txt) = @_; + + my ($s, $ms) = gettimeofday(); + my @t = localtime($s); + my $tim = sprintf("%02d:%02d:%02d.%03d", $t[2],$t[1],$t[0], $ms/1000); + + for(my $i = 0; $i < length($txt); $i += 16) { + my $a = substr($txt, $i, 16); + my $h = unpack("H*", $a); + $a =~ s/[\r\n]/./g; + $a =~ s/\P{IsPrint}/\./g; + $h =~ s/(....)/$1 /g; + printf $fd "%s %s %04d %-40s %s\n", $prompt, $tim, $i, $h, $a; + } + print $fd "\n"; + +} diff --git a/docs/README b/docs/README new file mode 100644 index 000000000..09f1a1c70 --- /dev/null +++ b/docs/README @@ -0,0 +1,6 @@ +The snippet files are here just for reference, some of them were created by me, +some of them were sent to me by fellow users of the FHZ1000, others found in +the newsgroups. + +Do not assume that they were verified, they are by no means thought as an +official documentation. diff --git a/docs/commandref.html b/docs/commandref.html new file mode 100644 index 000000000..465e420e9 --- /dev/null +++ b/docs/commandref.html @@ -0,0 +1,1176 @@ + + + + + + fhem.pl command reference + + + + +

fhem.pl command reference

+ +You can use all of the following commands in in two ways: +
    +
  • In the configuration file, which must be specified if you startup the + server. Example:
    +
      + fhem.pl ~/.fhem +
    +
    + A minimal configuration file would look like: +
    +      logfile /tmp/fhem.log
    +      savefile /tmp/fhem.save  
    +      verbose 3                   
    +      port 7072                   
    +      modpath .                   
    +      define FHZ FHZ /dev/tts/USB0        
    +
    +      define lamp FS20 8765 01
    + For other configuration files see the examples subdirectory.
    +
    +
  • + +
  • Through the TCP/IP connection, which you can either use in a "session" + (via telnet) or single client command (via fhem.pl). Example: +
      + telnet localhost 7072
      + <NL>
      (This newline switches into "prompt" mode)
      + <command>...
      + quit

      +
    + or +
      + fhem.pl 7072 "set lamp off" +
    +
  • +
+ +There are three types of commands: "fhz" commands (described in this document), +shell commands (they must be enclosed in double quotes ") and perl expressions +(enclosed in curly brackets {}). shell commands or perl expressions are needed for +complex at or notifyon arguments.

+ +Shell commands will be executed in the +background, the perl program and the fhz commands will be executed in the main +"thread". +In order to make perl expressions easier to write, some special functions and +variables are available. See the section Perl special for a +description. +To trigger "fhz" commands from a shell script, use the client form of +fhem.pl (described above).

+ +Multiple fhz commands are separated by semicolon (;). In order to use semicolon +in perl code or shell programs, they have to be escaped by the double semicolon +(;;).

+ +Commands can be either typed in plain, or read from a file (e.g. the +configuration file at startup). The commands are either executed directly, or +later if they are arguments to the at and notifyon fhz commands.

+ +If commands are read from a file, then a line ending with \ will be +concatenated with the next one, so long lines (e.g. perl oneliners) can be +split in multiple lines

+ + + +

?, help

+
    + ?
    + help
    +
    + Get a list of all commands and short description for each one +
+ + + +

at

+
    + at <timespec> <command>
    +
    + Start an arbitrary fhem.pl command at a later time. + <timespec> format: [+][*]HH:MM:SS
    + The optional + indicates that the specification is + relative(i.e. it will be added to the current time).
    + The optional * indicates that the command should be + executed repeatedly.
    + The optional {N} after the * indicates,that the command + should be repeated N-times only.
    +
    + + Examples: +
    +  # absolute ones:
    +  at 17:00:00 set lamp on                            # fhz command
    +  at 17:00:00 { Log 1, "Teetime" }                   # Perl command
    +  at 17:00:00 "/bin/echo "Teetime" > /dev/console"   # shell command
    +  at *17:00:00 set lamp on                           # repeat every day
    +
    +  # relative ones
    +  at +00:00:10 set lamp on                  # switch the lamp on in 10 seconds
    +  at +00:00:02 set lamp on-for-timer 1      # Blink once in 2 seconds
    +  at +*{3}00:00:02 set lamp on-for-timer 1   # Blink 3 times
    +
    +  # Blink 3 times if the piri sends a command
    +  notify piri:on.* at +*{3}00:00:02 set lamp on-for-timer 1
    +
    +  # Switch the lamp on from sunset to 11 PM 
    +  # Copy 99_SUNRISE.pm in the FHEM directory to have sunset_rel()
    +  { sunrise_coord("8.686", "50.112", "Europe/Berlin") }
    +  at +*{sunset_rel()} set lamp on
    +  at *23:00:00 set lamp off
    +
    +  # More elegant version, works for sunset > 23:00 too
    +  at +*{sunset_rel()} set lamp on-till 23:00
    +
    +  # Only do this on weekend
    +  at +*{sunset_rel()} { fhz("set lamp on-till 23:00") if($we) }
    +
    +  # Switch lamp1 and lamp2 on from 7:00 till 10 minutes after sunrise
    +  at *07:00 set lamp1,lamp2 on-till {sunrise_abs(+600)}
    +
    +  # Switch the lamp off 2 minutes after sunrise each day
    +  at +*{sunrise_rel(+120)} set lamp on
    +  
    + + Notes:
    +
      +
    • if no * is specified, then a command will be executed + only once, and then the at entry will be deleted.
    • +
    • if the current time is greater then the time specified, then the + command will be executed tomorrow. This is why the relative forms + of the sunset/sunrise functions should be used with the relative + (+) flag
    • +
    • the delete argument for at is the + complete line as it appears in list + (with spaces), but you can use regexps.
    • +
    • In order to use the sunrise_rel()/sunset_rel() functions, copy the + 99_SUNRISE.pm file from the contrib into the modules (FHEM) + directory, and put { sunrise_coord(long, lat, tz) } into your config + file, as in the above example. If you are not using sunrise_coord, then + the coordinates for Frankfurt am Main, Germany will be used. + You also have to install the Datetime::Event::Sunrise perl module. +
    • +
    • do not place at commands in the config file if you + specified a savefile, as all + at commands will be saved there too, and then defined + twice at startup. at is intended to be inserted by + hand or by cronjobs.
    • +
    • For more complex date handling you either have to call fhem from + cron or filter the date in a perl expression, see the last example and + the section Perl special. +
    • +
+ + +

attr

+
    + attr <devname> <attrname> [<value>]
    + or
    + attr at <at-spec-regexp> <attribute>
    + +
    Set a device,log or at attribute. There are some special attributes used + by the fhem.pl itself or the web-frontends, but you can define your own to + use them in other applications.

    + + Recognized attributes:
    +
      +
    • room
      + Filter/group devices. Recognized by web-pgm2 and web-pgm3. + Devices in the room hidden will not appear in the web output.
    • +
    • loglevel
      + Set the device loglevel to e.g. 6 if you do not wish messages from a + given device to appear in the global logfile (FHZ/FS20/FHT). E.g. to + set the FHT time, you should schedule "set FHZ time" every minute, but + this in turn makes your logfile unreadable. These messages will not be + generated if the FHZ attribute loglevel is set to 6.
    • +
    • dummy
      + Set the device attribute dummy to define devices which should not + output any radio signals. Associated notifyons will be executed if + the signal is received. Used e.g. to react to a code from a sender, but + it will not emit radio signal if triggered in the web frontend. + Implemented for FS20 and FHT devices.
    • +
    • do_not_notify
      + Disable FileLog/notify/inform notification for a device. This affects + the received signal, the set and trigger commands.
    • +
    • skip_next
      + Used for at commands: skip the execution of the command the next time. +
    • +
    • follow-on-for-timer
      + the program automatically schedules a "setstate off" for the time + specified as argument to the on-for-timer command (for the specified + device only). +
    • +
    • repeater
      + Set the attribute "repeater" for an FHZ device to 1 to ignore events + received from a FS20 repeater. In fact we are not sure that they are + repeater messages, we just ignore messages which were sent out by our + device for the next 3 seconds (see the next attribute) +
    • +
    • filtertimeout
      + Ignore duplicate messages for this amount of time. The time is in + seconds, fractions are allowed. It affects installations with more then + one FHZ device or repeater, see the entry above. +
    • +
    + + Examples: +
      + attr lamp room kitchen
      + attr lamp dummy
      + attr lamp loglevel 6
      + del attr lamp
      + at *23:00:10 set lamp off
      + attr at lamp.off skip_next
      + +
    +
    + + Notes:
    +
      +
    • There is no way to delete a single attribute.
    • +
+ + + + +

define

+
    + define <name> <type> <type-specific>
    +
    + Define a device. You need devices if you want to manipulate them (e.g. + set on/off), and the logfile is also more readable if it contains e.g. + "lamp off" instead of "Device 5673, Button 00, Code 00 (off)".
    +
    + + Type FHZ: +
      + define <name> FHZ <serial-device>
      +
      + Specifies the serial port to communicate with the FHZ 1000 PC. The name(s) + of the serial-device(s) depends on your distribution. The program can + service multiple devices, FS20 and FHT device commands will be sent out + through the last FHZ device defined before the definition of the FS20/FHT + device. To change the association, use the set command + activefor. Important: this definition must occur after the + modpath command but before any other (FHZ related) device definition, + else you'll see "no I/O device found" messages.
      + If the serial-device is called none, then no device will be opened, so you + can experiment without hardware attached.
      + + Set the attribute "repeater" for this device to 1 to ignore events received + from a FS20 repeater. In fact we are not sure that they are repeater + messages, we just ignore messages which were sent out by our device for the + next 3 seconds (or configured otherwise by filtertimeout). + +
    +
    + + Type FS20: +
      + define <name> FS20 <housecode> <button> + [fg <fgaddr>] [lm <lmaddr>] [gm FF] +

      + + <housecode> is a four digit hex number, + corresponding to the housecode address, <button> is + a two digit hex nunmber, corresponding to a button of the transmitter.
      +
    • The optional fg specifies the function group, the first digit of the 2 + digit adress must be F.
    • +
    • The optional lm specifies the local master, the last digit of the 2 + digit adress must be F.
    • +
    • The optional gm specifies the global master, the adress must be FF.
    • +
      + + Examples: +
        + define lamp FS20 7777 00 fg F1
        + define roll1 FS20 7777 01 +
      +
    +
    + + Type FHT: +
      + define <name> FHT <housecode> +

      + + <housecode> is a four digit hex number, + corresponding to the adress of the FHT80b device. +
      + + Examples: +
        + define wz FHT 3232
        +
      +
      + See the section set for more. +
    +
    + + Type HMS: +
      + define <name> HMS <housecode> +

      + + <housecode> is a four digit hex number, + corresponding to the adress of the HMS device. +
      + + Examples: +
        + define temp HMS 1234
        +
      + Notes:
      +
        +
      • There is _NO_ guarantee that the code will work as expected in all + circumstances, the authors are not liable for any damage occuring as a + result of incomplete or buggy code
      • + +
      • Currently supported devices are the HMS100T, HMS100TF, HMS100WD and + the RM100-2.
      • + +
      • The housecode of the HMS devices may change if the battery is renewed. + In order to make life easier, you can define a "wildcard" device for each + type of HMS device. First the real device-id will be checked, then the + wildcard device id. The wildcards are: +
          +
        • 1000 for the HMS100TF
        • +
        • 1001 for the HMS100T
        • +
        • 1002 for the HMS100WD
        • +
        • 1003 for the RM100-2
        • +
        • 1006 for the HMS100MG
        • +
        +
      • + +
      • Some battery low notifications are not yet implemented (RM100, HMS100WD).
      • +
      • Please test your installation before relying on the functionality.
      • +
      +
      +
    +
    + + Type KS300: +
      + define <name> KS300 <housecode> [ml/raincounter [wind-factor]] +

      + + <housecode> is a four digit hex number, + corresponding to the adress of the KS300 device, right now it is ignored. + The ml/raincounter defaults to 255 ml, but it must be specified if you wish + to set the wind factor, which defaults to 1.0. + +
      + + Examples: +
        + define ks1 KS300 1234
        +
      +
      +
    +
    + + Type WS300: +
      + define WS300Device WS300 <serial device>
      + or
      + define <devname> WS300 [0-9]
      +
      + The first line is mandatory if you have a WS300 device: it defines the + input device with its USB port. The name of this device is fixed and must + be WS300Device. It must be the first defined WS300 device.
      + + For each additional device (with number 0 to 9) you have to define another + WS300 device, with an arbitrary name. The WS300 device which reports the + readings will be defined with the port number 9, an optional KS300 with the + port number 8.

      + + Examples: +
      +      define WS300Device  WS300   /dev/ttyUSB1
      +      define ash2200-1    WS300   0
      +      define ks300        WS300   8
      +      define ws300        WS300   9
      +    
      +
    +
    + + + Type FileLog: +
      + define <name> FileLog <filename> <regexp> +

      + + Log events to <filename>. The log format is +
      +      YYYY:MM:DD_HH:MM:SS <device> <event>
      + The regexp will be checked against the (complete!) device name + or against the (complete!) devicename:event combination. +
      + <filename> may contain one or more of the following + wildcards (a subset of the Unix date command arguments): +
        +
      • %d day of month (01..31)
      • +
      • %m month (01..12)
      • +
      • %Y year (1970...) +
      • %w day of week (0..6); 0 represents Sunday +
      • %j day of year (001..366) +
      • %U week number of year with Sunday as first day of week (00..53) +
      • %V week number of year with Monday as first day of week (01..53) +
      + + Examples: +
        + define lamplog FileLog /var/tmp/lamp.log lamp
        + define wzlog FileLog /var/tmp/wz-%Y-%U.log + wz:(measured-temp|actuator).*
        +
      +
      +
    +
+ + + +

delete

+
    + delete {def|ntfy|at} <name>
    +
    + Delete a definition, a + notifyon setting or an at command.
    + The <name> argument has to be the first column of + the list output (as in case of at least at + it is not obvious). +

    + + Examples: +
      + delete def lamp
      + delete at 22:15:00 set lamp off +
    +
    + + Notes: +
      +
    • The program first tries to find the exact <name>, if + it is not found, then it will try it as a regexp, so you can also specify + delete at .*lamp.* to delete the at command above.
    • + +
    • Do not use doubleqoutes ("e;) in <name>, only if + you used them in your definition. +
    +
+ + +

get

+
    + get <name> <type-specific> +

    + Ask a value directly from the device, and wait for an answer. In general, you + can get a list of possible commands by
    get <device> + help +
    + Right now only the FHZ module supports this function. + +

    Type FHZ:

    +
      + get FHZ <value> +

      + where value is one of:
      +
      +      init1
      +      init2
      +      init3
      +      serial
      +      fhtbuf
      + Notes: +
        +
      • There is only one FHZ device (called FHZ), it is created automatically + at startup.
      • +
      • The mentioned codes are needed for initializing the FHZ1000
      • +
      • The answer for a command is also displayed by list FHZ +
      • +
      • + fhtbuf should be incorporated later in the FHT + module:
        the FHZ1000PC has a message buffer for the FHT, as it + only can send messages to it every 2 (or so) minutes. If the buffer + is full, then newly issued ones will be dropped. fhtbuf + returns the free memory in this buffer (in hex), my maximum is 2c (42 + bytes).
      • + +
      +
    +
+ + + +

include

+
    + include <filename>
    +
    + Read in the file, and process every line as a fhem command. Makes + configuration files more modular and enables to reread them. +
    +
+ + +

inform

+
    + inform [on|off]
    +
    + If set to on, and a device state changes, send a notification to the current + client. This command can be used by other programs/modules to receive a + notification. +
    +
+ + +

list

+
    + list [name] +

    + Output a list of all definitions, all notifyon settings and all at + entries. This is one of the few commands which return a string in a + normal case. +

    + Example: +
      FHZ> list
    +
    +  Type list  for detailed info.
    +
    +  FHZ devices:
    +    FHZ                  (Last msg: Initialized)
    +
    +  FS20 devices:
    +    Btn3                 (Last msg: off)
    +    Roll1                (Last msg: on-for-timer 11)
    +    Stehlampe            (Last msg: off)
    +
    +  FHT devices:
    +    fl                   (Last msg: state: Bat: ok, Window: closed)
    +    wz                   (Last msg: actuator: 07%)
    +
    +  NotifyOn:
    +    Btn3                 /usr/local/bin/setroll %
    +
    +  At:
    +    +*{sunrise_rel(+10)} set Roll1 on-for-timer 10 (07:43:56)
    +
    +
    +  Logs:
    +    wzlog FileLog /var/tmp/wz.log wz:.*(temp|actuator).*
    +  
    + If specifying name, then a detailed status for name + will be displayed, e.g.: +
      FHZ> list wz
    +
    +  2006-01-03 18:28:27   actuator        07%
    +  2006-01-03 18:26:32   mon-from1       06:00
    +  2006-01-03 18:26:33   mon-to1         23:00
    +  2006-01-03 18:26:34   tue-from1       06:00
    +  2006-01-03 18:26:34   tue-to1         23:00
    +  2006-01-03 18:26:35   wed-from1       06:00
    +  2006-01-03 18:26:35   wed-to1         23:00
    +  2006-01-03 18:26:37   thu-from1       06:00
    +  2006-01-03 18:26:37   thu-to1         23:00
    +  2006-01-03 18:26:38   fri-from1       06:00
    +  2006-01-03 18:26:38   fri-to1         23:00
    +  2006-01-03 18:26:39   sat-from1       06:00
    +  2006-01-03 18:26:40   sat-to1         23:50
    +  2006-01-03 18:26:41   sun-from1       06:00
    +  2006-01-03 18:26:41   sun-to1         23:00
    +  2006-01-03 18:26:45   mode            manual
    +  2006-01-03 18:26:44   desired-temp    21.5 (Celsius)
    +  2006-01-03 18:26:45   measured-temp   22.0 (Celsius)
    +  2006-01-03 18:26:46   state           Bat: ok, Window: closed
    +  2006-01-03 18:24:37   init            255
    +  2006-01-03 18:26:42   day-temp        21.0 (Celsius)
    +  2006-01-03 18:26:42   night-temp      17.0 (Celsius)
    +  2006-01-03 18:26:43   unknown_85      4
    +  2006-01-03 18:26:43   windowopen-temp 12.0 (Celsius)
    +  
    + +
+ + + + +

logfile

+
    + logfile +

    + Specify the logfile to write. Specify this as the first command in the + configfile as everything gets logged in the logfile. You can use "-" for + stdout, in this case the server won't background itself.
    + The logfile name can also take wildcards for easier logfile rotation, + see the FileLog section of the define command. +

    + + Examples: +
      + logfile /var/log/fhem
      + logfile /var/log/fhem-%Y-%m.log +
    +
+ + + +

modpath

+
    + modpath <path> +

    + Specify the path to the modules directory FHEM. The path + should not contain the directory FHEM. Every module there will + be loaded. +

    + + Example: +
      + modpath /usr/local/lib +
    +
+ + + +

notifyon

+
    + notifyon <name> <command> +

    + Execute a command when received an event for the definition <name>. As with normal + commands, if it is enclosed in {}, then it is a perl expression, if it is + enclosed in "", then it is a shell command, else it is a "plain" fhem.pl + command (chain). See the trigger command for testing + it. + + Examples: +
      + notifyon btn3 set lamp %
      + notifyon btn3 { fhz "set lamp %" }
      + notifyon btn3 "/usr/local/bin/setlamp "%""
      + notifyon btn3 set lamp1 %;;set lamp2 %
      + notifyon wz:measured.* "/usr/local/bin/logfht @ "%""
      + notifyon .*H:.* {DbLog("@","%")}
      + notifyon UNDEFINED "send-me-mail.sh "%""
      +
    +
    + + Notes: +
      +
    • The character % will be replaced with the received event, + e.g. with on or off or measured-temp: 21.7 + (Celsius)
      It is advisable to put the % into double + quotes, else the shell may get a syntax error.
      + To use % or @ in the text itself, use the double mode (%% or @@)
    • + +
    • The character @ will be replaced with the device + name.
    • + +
    • <name> may also be a compound of + definition:event to filter for events.
    • + +
    • <name> is in fact a regexp. It must completely (!) + match either the device name, or the compound of the device name and the + event. The event is either the string you see in the list output in paranthesis after the device name, or the + string you see when you do a detailed list of the device.
    • + +
    • To use database logging, copy the file contrib/91_DbLog.p into your + modules directory, and change the $dbconn parameter in the file.
    • + +
    • Each undefined device (FS20, HMS, FHT) will be reported with the device + name "UNDEFINED". The % parameter will contain the type (FS20, HMS100T, + etc) and device number, separated by a space.
    • + +
    + +
+ + +

pidfile

+
    + pidfile <filename> +

    + Write the process id of the perl process to the specified file. We are + running as a daemon, and some distributions would like to check by the pid if + we are still runnning. +

    + Example: +
      + pidfile /var/run/fhem.pid +
    +
+ + + + +

port

+
    + port <number> [<global>] +

    + Listen on the TCP/IP port <number> for incoming + connections. To offer at least a little bit of security, the server will only + listen for connections from the localhost per default. The optional global + parameters enables listening for non-localhost connections too. +

    + Example: +
      + port 7072 +
    +
+ + + +

quit

+
    + quit +

    + If used in a TCP/IP session, terminate the client session.
    + If used in a script, terminate the parsing of the script. +

    + Example: +
      + quit +
    +
+ + + +

reload

+
    + reload <module> +

    + Reload the given module from the module directory. Mainly intended to reload + the 99_PRIV file (or equivalent) in order to test perl-scripts. +

    + Example: +
      + reload 99_PRIV +
    +
+ + +

rereadcfg

+
    + rereadcfg +

    + Re-read the configuration file. + Note: The statefile will be saved first, then the config file will be read + (all devices will be initialized again), and at last the statefile will be reloaded again. +

    + Example: +
      + rereadcfg +
    +
+ + + +

savefile

+
    + savefile <filename> +

    + Set the filename where the state and at information will + be saved before shutdown. If not setting it, then no information will be + saved. +

    + Example: +
      + savefile /var/tmp/fhem.save +
    +
+ + + +

set

+ + + + +

setstate

+
    + setstate <name> <value> +

    + Set the "Last msg" for the definition + <name> shown in the list command + to <value> without sending any signals to the device + itself. This command is also used in the savefile. +
    + Setting/changing arbitrary comments is possible, if the value begins with + comment:. To delete the comment, set it with an empty value, + see below. +

    + Examples: +
      +
      +    setstate lamp on
      +    setstate lamp comment:location sleeping room
      +    setstate lamp comment:location # deletes the comment
      +
    +
    + Note: +
      +
    • You even may set the detailed state (i.e. what you get when you call + list <name;> by specifying "<time> + <attribute> <value>". Take a look at the + savefile + after devices reported values for an example.
    • +
    +
+ + + +

shutdown

+
    + shutdown +

    + Shut down the server (after saving the state information + ) +

    + Example: +
      + shutdown +
    +
+ + + +

trigger

+
    + trigger <dev> <state> +

    + Trigger a notifyon command. +

    + Example: +
      + trigger btn3 on +
    +
+ + +

sleep

+
    + sleep <sec> +

    + Sleep for a given amount, millisecond accuracy. +

    + Example: +
      + sleep 0.5
      + notifyon btn3 set lamp toggle;;sleep 0.5;;set lamp toggle +
    +
    + Note: As the server is not multithreaded, everything is blocked for + the given amount.
    + +
+ + +

verbose

+
    + verbose <level> +

    + Set the verbosity level. Possible values: +
      +
    • 0 - it will only tell you when the server was started, or stopped
    • +
    • 1 - it logs all error messages or unknown packets
    • +
    • 2 - it logs all signals received or sent in a "digested" format,
    • +
    • 3 - it logs the signals for undefined devices,
    • +
    • 4 - it logs the TCP/IP connections and the called programs with parameters,
    • +
    • 5 - is for debugging.
    • +
    + Recommended level is 3 for normal use. +

    + Example: +
      + verbose 3 +
    +
+ + +

xmllist

+
    + xmllist +

    + Returns an XML tree of all definitions, all notifyon settings and all at + entries. It is not intended for human consumption. +

    + Example: +
      FHZ> xmllist
    +    <FHZINFO>
    +	<FHZ_DEVICES>
    +	    <FHZ name="FHZ" definition="FHZ FHZ" state="fhtbuf: 1c">
    +                <STATE name="fhtbuf" value="23" measured="2006-02-12 14:03:39"/>
    +		<STATE name="serial" value="136e21bc" measured="2006-03-26 08:47:36"/>
    +    [...]
    +  
    +
+ + + +

Perl specials

+
    +
  • To use fhz commands from the perl expression, use the function "fhz", + which takes a string argument, this string will be evaluated as an fhz + command chain.
    + Example: +
      + notifyon piri:on { fhz "set light on" } +
    +
  • +
  • + To make date and time handling easier, before evaluating a perl expression + the variables $sec, $min, $hour, $mday, $month, $year, $wday, $yday, $isdst + are set (see perldoc -f localtime), with the exception that $month is in the + range of 1 to 12, and $year is also corrected by 1900 (as one would normally + expect). Additionally $we is 1 if it is weekend (i.e $wday == 0 || + $wday == 6), and 0 otherwise. + Example: +
      + notifyon piri:on { if($hour > 18 || $hour < 5) { fhz "set light on" } } +
    + +
  • + +
  • + Note: do not forget to escape the semicolon (;) with two semicolons + (;;), else your perl code will be interpreted as an fhz command and you + most certainly get syntax errors. +
  • + +
  • + The current value (the string you see in paranthesis in the output of the + list command) is available in the value hash, to access it, + use $value{<devicename>}
    + If you need the old value (and time) of the currentliy triggered device, + then you can access it with $oldvalue{$dev}{TIME} and + $oldvalue{$dev}{VAL}.
    +
  • + +
  • + To access the numerical value of an FS20 command (e.g. toggle), use the + hash fs20_c2b. E.g. { Log 2, $fs20_c2b{"toggle"} } +
  • + +
  • + If you add the 99_SUNRISE.pm from the contrib directory to your module + directory (NOTE: you have to install the Perl module + DateTime::Event::Sunrise first), then you have access to the follwing + functions:
    +
      + sunset_rel()
      + sunset_abs()
      + sunrise_rel()
      + sunrise_abs()
      + isday()
      +
    + The _rel functions should be used as "at" spec, and the _abs functions as + argument to the on-till argument of the set command.
    + isday returns 1 if the sun is visible, and 0 else. +
  • + + + +
+ + + diff --git a/docs/faq.html b/docs/faq.html new file mode 100644 index 000000000..089ff3425 --- /dev/null +++ b/docs/faq.html @@ -0,0 +1,163 @@ + + + + + + FHEM FAQ + + + + +

FHEM FAQ

+ +I get "undefined" messages in the log after upgrading fhem.pl +
    + Stop fhem.pl, delete the previous .save file and restart fhem.pl. + If the problem still exists, send a bugreport. +
+ +I switched on a FS20 device directly (without the remote), but + the fhem.pl did not noticed it.
Is it a bug?
+
    + The protocol used by the FS20 family is quite simple: it is not + encrypted in any way, and there is no feedback to the sender. So if you + push any buttons on a pure receiver, no radio waves will be sent out, and + the FHZ1000 won't notice anything. The FHZ1000PC does not even know if + somebody received its message, it simply sends it out 3 times and hopes + for good luck.
    + To answer the question: it is not a bug :-) +
+ +I have some FS20/FHT/HMS devices.
+ How do I know their housecode?

+
    + If you already programmed some devices, then just start fhem.pl with one + of the example configuration files, and watch the log. When activating a + deivce (e.g. with the remote) then it will be logged as an unknown device + with the housecode. Note: the verbose level must be 3 or higher. KS300 + devices do not have a proper code, so you can use anything. +
+ +I have the code for my devices in the ELV notation, which contains 1,2,3 and + 4, but you require a hex code.
+ How should I convert it?
+
    + The code used by ELV is in the "quaternal" (?) system plus one added to each + digit, so you can even use the 4 button remote for programming. To convert, + you have two choices: the program four2hex in the contrib directory, or the + good old unix program bc. +
      +
    • From quaternal (ELV Remote) to hex (fhem.pl):
      +
      +	 % bc
      +	 obase=16
      +	 ibase=4
      +	 <elv number>
      + where <elv number> is the number used on the remote, but 1 + substracted from every digit (so each digit is between 0 and 3).
    • + +
    • From hex (fhem.pl) to quaternal (ELV Remote):
      +
      +         % bc
      +	 ibase=16
      +	 obase=4
      +	 <hex number>
      + Now add 1 to each digit of the result, and prepend it with 1's if + it has less than 4 digits.
    + + +
+ +I replaced my FHZ1X00PC, now the FHT80b's does not work anymore.
+Help me!
+
    + The FHT80b's are talking to a single FHZ1XXX device, which has a unique + "FHTcode". You have two choices: if you know the old FHTcode, then you can + set it, or you can tell the FHT80b to forget the old FHZ, and start talking + with the new one. +
      +
    • Set the FHTcode: The problem with this method is that I don't know how + to read out the value, we just can change it with
      + set FHZ FHTcode <hex-code>
    • +
    • Resync the FHT80b: Press PROG until "Sond" appears, then select "CEnt" + with the wheel, press PROG again, Select "nA" with the wheel, press PROG + again.
    • + +
    +
+ +I can specify an optional ml/raincounter for a KS300.
+Why do you think that 255 should be the default?
+
    + The manual talks about 0.3l resolution, but I wanted to calibrate my device. + So I filled a plastic bottle with 0.5 liter water from the measuring cup, + drilled a small hole in the bottom of the bottle, let the water flow slowly + in the KS300 rain-cup, and looked at the counter after the bottle was empty.
    + + The raincounter was incremented by 130 ticks. The diameter of my KS300 + rain-cup is 13.9 cm, the area ca 151.75 cm2, which + is ca 1/65.9 m2. + The 0.5 liter corresponds to 32.95 liter per m2. 130 ticks + correspond to 32.95 l/m2 -> 1 tick is ca 253 ml. I estimate + the error margin to +/- 2%
    + + You are welcome to do your own experiments, I am interested in the results. +
+ +The time specification of the builtin at command is not very flexible.
+Please add day/month/weekday to it.
+
    + I think the command is complex and flexible enough. Use a perl expression + for this functionality like (described in the commandref.html): +
    at *07:00:00 { fhz "set lamp on" if($we) }
    +
+ + +I defined my FS20STR as an FHT device, but I do not get any data from it. +
    + The FS20STR is an FS20 device, even if it looks like an FHT80b. + You'll get "only" on-for-timer and off-fot-timer events sent. +
+ +How to convert the FHT8b code seen in its display to the hex code needed by fhem.pl? +
    + Convert the first 2 digits first from decimal to hex, then the next two. Example:
    +
    +         % bc
    +	 set obase=16
    +	 <first two digits>
    +	 <last two digits>
    + E.g The FHT8b code 1121 is 0b15 for the fhem.pl +
+ +I'd like to use this sunrise/sunset stuff, can you help me? +
    + First you (most probably) have to install the DateTime::Event::Sunrise perl + module, as it is not part of the standard distributions. If it is not + installed and you copy the contrib/99_SUNRISE.pm into your module (FHEM) + directory, then the program will not start up, telling you that this module + is missing. + The (IMHO) easiest way to install it is via the following command (probably + as root):
    +
    +   perl -MCPAN -e shell
    +   cpan> install DateTime::Event::Sunrise
    + This will fetch the module from a CPAN archive, compile it and install it, + and will do the same with each perl module which is needed by this one.
    + + Next look for the geographic coordinates of your home, e.g with a GPS + receiver or with googleearth. Compute the latitude/longitude as needed, and + enter them in your init file (fhem.cfg) with the command: +
    {sunrise_coord("", "", "Europe/Berlin") }
    + If you are living in a different timezone, then change the string above + according to the perldoc DateTime manpage.
    + + Now copy the contrib/99_SUNRISE.pm file into your module directory, and + restart the program. If everything is ok, typing +
    { sunrise_abs() }
    + in the telnet prompt, will return the time of the sunrise today, in a + HH:MM:SS format. +
+ + + diff --git a/docs/fhem.html b/docs/fhem.html new file mode 100644 index 000000000..ea64c8f97 --- /dev/null +++ b/docs/fhem.html @@ -0,0 +1,218 @@ + + + + +Home of FHEM + + + + + +

FHEM (GPL'd FS20/HMS/FHT/KS300/WS300 server for linux, formerly known as fhz1000.pl)

+ +

News (as of =DATE=, Version =VERS=)

+
    +
+ +

Description

+
    +This program makes the FHZ1000/FHZ1300/WS300 USB devices sold by ELV, Conrad and +others useable with Linux. In fact, there is nothing Linux special in it, you +should be able to use it on other platforms as long as you can access the +hardware as a serial device.
    +The program runs as a server, you can control it via telnet, command line +program or TCP/IP directly, like the supplied web frontends do.

    + +Currently implemented features:
    +
      +
    • reading and sending FS20 events (on/off/dimming, timer commands, etc)
      +
    • support of FS20 address features function group, local and global master +
    • reading and changing FHT80b parameters (temp, actuator, etc).
      + The FHT8b seems to work too. Note: the FHT8 wont work.
    • +
    • reading HMS data (HMS100-T,-TF,-WD,-MG,-TFK and RM100-2)
    • +
    • reading KS300 data
    • +
    • reading WS300 data
    • +
    • logging events to files (or database), with regexp filters
    • +
    • notifying external programs or internal modules when receiving certain + events
    • +
    • timed commands (e.g. switching a lamp on from sunset till midnight)
    • +
    • modular architecture
    • +
    • a lot of web frontends, choose your favorite
    • +
      +
    +See commandref.html for a detailed command +description and faq.html for the F.A.Q. +
+ +

Links:

+ +

Related projects:

+ + +

Installation

+
    + +

    Linux driver

    + +
      + For kernels newer than 2.6.14 add one of the following lines + to /etc/modprobe.conf:
      +
      +  # FHZ1000 PC
      +  options ftdi_sio vendor=0x0403 product=0xf06f
      +  # FHZ1300 PC
      +  options ftdi_sio vendor=0x0403 product=0xe0e8
      + If in doubt, look at the id of the device with lsusb. + For older kernels apply the patch from the doc directory to your kernel.
      + Recompile your kernel and reboot or load/reload the ftdi_sio module. +
    + +

    Perl modules

    +
      + You need perl with the Device::SerialPort ( + http://search.cpan.org/dist/Device-SerialPort/) + module. All other needed modules were present in my installation. + If this module reports Can't call method "opened" on an undefined + value... when starting the server, then you either may ignore + this message, or replace the mentioned line with:
      +    $self->{HANDLE}->close if (defined($self->{HANDLE}) &&
      +    	                       $self->{HANDLE}->opened);
      +
    + +

    Server installation

    +
      +
    • Copy the file fhem.pl into your path (e.g. + /usr/local/bin), and the FHEM directory e.g. to + /usr/local/lib.
    • +
    • Make sure that you can access the serial USB + device (e.g. /dev/tts/USB0).
    • +
    • Create a configuration file (see the examples directory and + docs/commandref.html), and change at least the modpath + (/usr/local/lib) and define FHZ FHZ (/dev/tts/USB0) + parameters.
    • +
    • Delete the savefile if you are upgrading from an older version.
    • +
    • Start the server with fhem.pl <configfile>
    • +
    • For using the WS300, look into the contrib/ws300 directory.
    • +
    + +

    General Notes for Webfrontends:

    +
      +
    • You don't have to install all of them, one is probably more than + enough :-)
    • +
    • The web server and the fhem server must be on the same host
    • +
    • Important: Make sure you add some protection (.htaccess, etc) + else everybody will be able to set your devices
    • +
    + +

    Web frontend 2 (webfrontend/pgm2, the simple one)

    +
      + This frontend is CGI/CSS based. It has support for rooms, and FHT/KS300 logs.
      + Screenshots: overview + and temperature logs

      + +
        +
      • Copy the file fhemweb.pl to your cgi-bin directory + (/home/httpd/cgi-bin), and all the icons (*.gif) to your httpd icons + directory (/home/httpd/icons).
        + Note: The program looks for icons in the following order: + <device-name>.<state>, <device-name>, + <device-type>.<state>, <device-type>
        +
      • +
      • Edit fhemweb.pl, and check the "Config" section.
      • +
      • If you want to have access to the FHT temperature logs, then + make sure that gnuplot is installed and you log the temperatures like + in example/04_log. Note: There is still a bug with displaying empty + logfiles.
      • +
      • Call <your-site>/cgi-bin/fhemweb.pl +
      + For special features like assigning devices to rooms see the README file. +
    + +

    Web frontend 3 (webfrontend/pgm3, the professional one)

    +
      + This frontend is PHP based and was contributed by Martin Haas. + Look at the webfrontends/pgm3/docs for more documentation or at + this screenshot. A lot more details can be + found on Martins page: + http://www.martin-haas.de/fhz +

      + +
        +
      • Install PHP and enable it by commenting in the "LoadModule + phpX_module ..." directive in httpd.conf (perhaps it is already + done by your distro). Restart/reload httpd.
      • + +
      • Create a directory (e.g.: /home/httpd/html/pgm3) and copy all the + files from the webfrontend/pgm3 to this directory.
        Make sure that this + directory is writeable by the webserver!
      • + +
      • Edit index.php (/home/httpd/html/pgm3/index.php), and check the + required settings section
      • + +
      • If you want to have access to the FHT temperature logs, then: +
          +
        • Make sure gnuplot is installed
        • +
        • check the showgnuplot section in index.php
        • +
        • For each FHT device copy the file docs/gnuplot/gnuplot.wz to + gnuplot.fhtdevicename (to the /home/httpd/html/pgm3 directory) and + replace fht.log in this file with the absolute name of the current + logfile.
        • +
        +
      • Call <your-site>/pgm3/index.php
      • +
      +
    + +

    Web frontend 4 (webfrontend/pgm4, the template)

    +
      + This frontend is PHP based and was contributed by Stefan Mayer. It won't work + for you without modification, it is meant as a template or as an example. See + the screenshot. To install: +
        +
      • Copy the directory webfrontend/pgm4 to your html directory.
      • +
      • Install/enable PHP (see the description for frontend3)
      • +
      • Call the program with http://webserver/pgm4/fs20.php
      • +
      + Now you can go on, and modify it to suit your needs :-) +
    + +
+ +

License:

+
    + Copyright:
    +
      +
    • Rudolf Koenig (r dot koenig at koeniglich dot de)
    • +
    • Raoul Matthiessen (webfrontends/pgm1)
    • +
    • Martin Haas (webfrontends/pgm3)
    • +
    + License: GPL (v2) +
+ + + +

Misc:

+
    + Thanks for Tosti for inspiration and numerous other people for help.
    +
+ + + + diff --git a/docs/ftdi_sio.fhz1000.patch b/docs/ftdi_sio.fhz1000.patch new file mode 100644 index 000000000..223aded50 --- /dev/null +++ b/docs/ftdi_sio.fhz1000.patch @@ -0,0 +1,44 @@ +*** orig/drivers/usb/serial/ftdi_sio.c 2004-12-14 02:54:04.000000000 +0100 +--- linux/drivers/usb/serial/ftdi_sio.c 2004-12-13 19:04:24.000000000 +0100 +*************** static struct usb_device_id id_table_8U2 +*** 350,355 **** +--- 350,356 ---- + { USB_DEVICE_VER(FTDI_VID, PROTEGO_SPECIAL_3, 0, 0x3ff) }, + { USB_DEVICE_VER(FTDI_VID, PROTEGO_SPECIAL_4, 0, 0x3ff) }, + { USB_DEVICE_VER(FTDI_VID, FTDI_ELV_UO100_PID, 0, 0x3ff) }, ++ { USB_DEVICE_VER(FTDI_VID, FTDI_ELV_FHZ1000_PID, 0, 0x3ff) }, + { } /* Terminating entry */ + }; + +*************** static struct usb_device_id id_table_FT2 +*** 431,436 **** +--- 432,438 ---- + { USB_DEVICE_VER(FTDI_VID, PROTEGO_SPECIAL_3, 0x400, 0xffff) }, + { USB_DEVICE_VER(FTDI_VID, PROTEGO_SPECIAL_4, 0x400, 0xffff) }, + { USB_DEVICE_VER(FTDI_VID, FTDI_ELV_UO100_PID, 0x400, 0xffff) }, ++ { USB_DEVICE_VER(FTDI_VID, FTDI_ELV_FHZ1000_PID, 0x400, 0xffff) }, + { USB_DEVICE(FTDI_VID, FTDI_CCSICDU20_0_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CCSICDU40_1_PID) }, + { } /* Terminating entry */ +*************** static __devinitdata struct usb_device_i +*** 531,536 **** +--- 533,539 ---- + { USB_DEVICE(FTDI_VID, PROTEGO_SPECIAL_3) }, + { USB_DEVICE(FTDI_VID, PROTEGO_SPECIAL_4) }, + { USB_DEVICE(FTDI_VID, FTDI_ELV_UO100_PID) }, ++ { USB_DEVICE(FTDI_VID, FTDI_ELV_FHZ1000_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CCSICDU20_0_PID) }, + { USB_DEVICE(FTDI_VID, FTDI_CCSICDU40_1_PID) }, + { } /* Terminating entry */ +*** orig/drivers/usb/serial/ftdi_sio.h 2004-12-14 02:54:04.000000000 +0100 +--- linux/drivers/usb/serial/ftdi_sio.h 2004-12-13 19:03:21.000000000 +0100 +*************** +*** 143,148 **** +--- 143,149 ---- + + /* ELV USB Module UO100 (PID sent by Stefan Frings) */ + #define FTDI_ELV_UO100_PID 0xFB58 /* Product Id */ ++ #define FTDI_ELV_FHZ1000_PID 0xF06F /* Product Id */ + + /* + * Definitions for ID TECH (www.idt-net.com) devices diff --git a/docs/pgm1-1.gif b/docs/pgm1-1.gif new file mode 100644 index 0000000000000000000000000000000000000000..7463c93738d4aae4fc940fa8ca3c38929a230c53 GIT binary patch literal 32763 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 literal 0 HcmV?d00001 diff --git a/docs/pgm2-2.png b/docs/pgm2-2.png new file mode 100644 index 0000000000000000000000000000000000000000..db632d367a6c9e3cbaa4dfc86422ddf4ddb92231 GIT binary patch literal 6663 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 J`DQHozW@N3ogDxG literal 0 HcmV?d00001 diff --git a/docs/pgm3-0.5.1.png b/docs/pgm3-0.5.1.png new file mode 100644 index 0000000000000000000000000000000000000000..23160a3437188ec027757524b8417e119ac73890 GIT binary patch literal 95277 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

bOY~8 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 Ix=p~p0CAvi=l}o! literal 0 HcmV?d00001 diff --git a/docs/pgm4.gif b/docs/pgm4.gif new file mode 100644 index 0000000000000000000000000000000000000000..f8ea66321e8651d50d0867a9544b82cf7b6f6dc5 GIT binary patch literal 29120 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 literal 0 HcmV?d00001 diff --git a/docs/raw-codes b/docs/raw-codes new file mode 100644 index 000000000..333a5d6e3 --- /dev/null +++ b/docs/raw-codes @@ -0,0 +1,16 @@ +On the telnet prompt do: + {FhzDecode("")} +where is one of: + +81xx04xx0101a0011234030011 FS20 dev: 1234 button: 03 on (11) +81xx04xx0101a0011234030000 FS20 dev: 1234 button: 03 off (00) + +810d04xx4027a001de53187654321f KS300: Raincounter hex:123 (ca 75l/m2), + Wind 45.6 km/h Humidity: 78%, Temp: 13.5 + Unknown fields: d,e,f +{FhzDecode("810d04xx4027a001de53187654321f")} + +810e04d70213a001b16d000003000000 RM100-2 smoke on +810e04d70213a001b16d000000000000 RM100-2 smoke off + +81xx04xx0101a00180c2030011 FS20 dev: 1234 button: 03 on (11) diff --git a/docs/rm100 b/docs/rm100 new file mode 100644 index 000000000..23d1e9ef8 --- /dev/null +++ b/docs/rm100 @@ -0,0 +1,28 @@ + +810e041f0213a001d396000000000000 + +// Reset: +810e04ea0293a001ae06000000000000 +// Normal: +810e046a0213a001ae06000000000000 + +========== +810e04ba0293a001b1d3000000000000 +810e04a90293a0016a09000000000000 + +By andikt: +// Batteries are inserted into the smoke detector +810e04540293a001b16d000000000000 +[the same comes 10 times] + +// No smoke applied, status messages each 30min +810e04d40213a001b16d000000000000 + +// Smoke applied, detector beeps loud +810e04d70213a001b16d000003000000 + +// no more smoke, detector stops beeping, red LED flashes for some more +// seconds +810e04d40213a001b16d000000000000 + + diff --git a/docs/snippet_1.txt b/docs/snippet_1.txt new file mode 100644 index 000000000..96afe8696 --- /dev/null +++ b/docs/snippet_1.txt @@ -0,0 +1,74 @@ +Startup +------- +1a + PUT C9 - 02 01 1F 64 + 1.st fhz1000: C9 - 01 02 1F 02 78 05 5C 24 FF + 2.nd fhz1000: C9 - 01 02 1F 02 78 05 5C 24 FF + 3.rd fhz1000: C9 - 01 02 1F 02 78 05 5C 0A FF +1b + PUT C9 - 02 01 1F 0A + 4.th fhz1000: C9 - 01 02 1F 02 78 07 B6 22 80 +1c + PUT C9 - 02 01 1F 60 + C9 - 01 02 1F 02 78 07 1C 22 80 + +2 + PUT 04 - C9 01 84 57 02 08 + 1.st fhz1000: C9 - 01 02 84 01 08 05 FF 13 6E 21 BC 4B 1B # Serial no: 136E21BC (?) + 3.rd fhz1000: C9 - 01 02 84 01 08 05 FF 13 6E 29 0E 51 18 # Serial no: 136E290E (?) + +3 + PUT: 04 - C9 01 86 + PUT: 04 - C9 01 96 + PUT: C9 - 02 01 61 04 0C 10 12 0A # Set Date to 2004-12-16 14:10 + + +Switching FS20 device on / off +------------------------------ + + PUT: 04 - 020101 - HHHH BT VL + + HHHH: Transmitter (Hauscode) + BT: Button + VL: Values: + 00 => off, + 01 => dim06%, + 02 => dim12%, + 03 => dim18%, + 04 => dim25%, + 05 => dim31%, + 06 => dim37%, + 07 => dim43%, + 08 => dim50%, + 09 => dim56%, + 0A => dim62%, + 0B => dim68%, + 0C => dim75%, + 0D => dim81%, + 0E => dim87%, + 0F => dim93%, + 10 => dim100%, + 11 => on, # Set to previous dim value (before switching it off) + 12 => toggle, # between off and previous dim val + 13 => dimup, + 14 => dimdown, + 15 => dimupdown, + 16 => timer, + 17 => sendstate, + 18 => off-for-timer, + 19 => on-for-timer, + 1a => on-old-for-timer, + 1b => reset, + + The FSST20 switches on for dim*. + + When setting bit 6 (counted from 1) in the value, you can also + send a further byte, the time as suggested in snippet_6. + + The FS20ST only respects this for the values + timer, off-for-timer, on-for-timer + If the timer is set, then it works for dim*, on, *-for-timer + + sendstate does not work for the FS20ST + +================================= diff --git a/docs/snippet_2.txt b/docs/snippet_2.txt new file mode 100644 index 000000000..98008e04b --- /dev/null +++ b/docs/snippet_2.txt @@ -0,0 +1,102 @@ +// Init FHZ1000PC (Studio) mit FHT80b und HMS +<- 81 06 c9 2c 02 01 1f 0a +-> 81 0b c9 fb 01 02 1f 02 78 07 b6 22 80 +<- 81 08 04 ba c9 01 84 5e 0b 03 +-> 81 0a c9 ea 01 02 84 01 03 01 5b 03 +<- 81 05 04 50 c9 01 86 +<- 81 0b 04 __ 02 01 83 HH HH 65 ff 66 ff // FHT80b 1 +<- 81 0b 04 __ 02 01 83 HH HH 65 ff 66 ff // FHT80b 2 +<- 81 06 04 62 c9 01 96 02 +<- 81 0a c9 af 02 01 61 05 0a 0e 16 18 // Datum Uhrzeit + +// Init FHZ1000PC (Standard) mit FHT80b und HMS +<- 81 06 c9 82 02 01 1f 60 +-> 81 0b c9 61 01 02 1f 02 78 07 1c 22 80 +<- 81 08 04 af c9 01 84 57 02 08 +-> 81 0f c9 __ 01 02 84 01 08 07 80 xx xx xx xx 51 0f // Seriennummer +<- 81 05 04 50 c9 01 86 +<- 81 0b 04 __ 02 01 83 HH HH 65 ff 66 ff // FHT80b 1 +<- 81 05 04 60 c9 01 96 +<- 81 0a c9 __ 02 01 61 jj mm tt ss mm // Datum Uhrzeit +<- 81 05 04 4f c9 01 85 // Speicherabfrage +-> 81 07 c9 ac 01 02 85 01 23 // 23 ?bytes? frei +<- 81 0b 04 __ 02 01 83 HH HH 65 ff 66 ff // FHT80b 2 +<- 81 05 04 4f c9 01 85 // Speicherabfrage +-> 81 07 c9 a5 01 02 85 01 1c // 1c ?bytes? frei + +// FHZ1000PC +<- 81 0a c9 __ 02 01 61 jj mm tt ss mm // Uhrzeit 50 mal am Anfang einer Minute + +<- 81 05 04 4f c9 01 85 // Speicherabfrage +-> 81 07 c9 __ 01 02 85 01 xx // xx = ?bytes? frei + +// FHT80b +// SB=StartByte LL=Laenge TT=TelegrammType BC=BlockCheck ST=Status +// HH=Hauscode +// SB LL TT BC CODE FUNKT ST Param +-> 81 0c 04 __ 09 09 a0 01 HH HH 00 00 _6 xx // Istwert Stellantrieb +-> 81 0c 04 __ 09 09 a0 01 HH HH 00 00 2c xx // Synczeit +-> 81 0c 04 __ 09 09 a0 01 HH HH 14 00 69 xx // Montag von1 in 1/6 Stunde (10 Min.) +-> 81 0c 04 __ 09 09 a0 01 HH HH 15 00 69 xx // Montag bis1 +-> 81 0c 04 __ 09 09 a0 01 HH HH 16 00 69 xx // Montag von2 (90 = frei) +-> 81 0c 04 __ 09 09 a0 01 HH HH 17 00 69 xx // Montag bis2 +-> 81 0c 04 __ 09 09 a0 01 HH HH 18 00 69 xx // Dienstag von1 +-> 81 0c 04 __ 09 09 a0 01 HH HH 19 00 69 xx // bis 1 +-> 81 0c 04 __ 09 09 a0 01 HH HH 1a 00 69 xx // von2 +-> 81 0c 04 __ 09 09 a0 01 HH HH 1b 00 69 xx // bis2 +-> 81 0c 04 __ 09 09 a0 01 HH HH 1c 00 69 xx //Mittwoch von1 +-> 81 0c 04 __ 09 09 a0 01 HH HH 1d 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 1e 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 1f 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 20 00 69 xx // Donnerstag +-> 81 0c 04 __ 09 09 a0 01 HH HH 21 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 22 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 23 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 24 00 69 xx // Freitag +-> 81 0c 04 __ 09 09 a0 01 HH HH 25 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 26 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 27 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 28 00 69 xx // Samstag +-> 81 0c 04 __ 09 09 a0 01 HH HH 29 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 2a 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 2b 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 2c 00 69 xx // Sonntag +-> 81 0c 04 __ 09 09 a0 01 HH HH 2d 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 2e 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 2f 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 3e 00 69 xx // 0=auto 1=manuell 2=Urlaub +-> 81 0c 04 __ 09 09 a0 01 HH HH 3f 00 69 xx // Urlaub Endeuhrzeit oder Endetag +-> 81 0c 04 __ 09 09 a0 01 HH HH 40 00 69 xx // Urlaub Endetag 10=heute 11=morgen 0x=Endemonat +-> 81 0c 04 __ 09 09 a0 01 HH HH 41 00 69 xx // aktuelle Solltemperatur (x*0.5) +-> 81 0c 04 __ 09 09 a0 01 HH HH 42 00 69 xx // ist Temperatur (x/10) +-> 81 0c 04 __ 09 09 a0 01 HH HH 43 00 69 xx // ????? +-> 81 0c 04 __ 09 09 a0 01 HH HH 44 00 69 xx // bit0=Batterie 0=OK 1=leer bit5=Fenster 0=zu 1=offen +-> 81 0c 04 __ 09 09 a0 01 HH HH 4b 00 67 xx // ????? +-> 81 0c 04 __ 09 09 a0 01 HH HH 82 00 69 xx // Tag Temperatur (x*0.5) +-> 81 0c 04 __ 09 09 a0 01 HH HH 84 00 69 xx // Nacht Temperatur (x*0.5) +-> 81 0c 04 __ 09 09 a0 01 HH HH 85 00 69 04 // ????? +-> 81 0c 04 __ 09 09 a0 01 HH HH 8a 00 69 xx // Fenster offen Temperatur (x*0.5) +-> 81 0c 04 __ 09 09 a0 01 HH HH 7e 00 67 xx // ????? + +<- 81 09 04 __ 02 01 83 HH HH 3e xx // Modus setzen 00=auto 01=manu 02=Urlaub +-> 81 0b 04 __ 84 09 83 01 HH HH 3e xx yy // Übernahmequittung yy=Speicherstelle +<- 81 09 04 __ 02 01 83 HH HH 41 xx // Solltemp setzen (x=soll/0.5) +Die anderen Register können genauso verändert werden. + +//HMS100TF +-> 81 0e 04 __ 05 10 a0 01 HH HH 00 00 ss tt gu ff // Temp = (utt*0.04) Feuchte = (ffg*100/4096) + + // Status bit7=Negative Temp. bit6=Batterie + +//ST-2 +<- 81 09 04 __ 02 01 01 a0 01 HH HH xx yy // xx=Taste yy=Funktion +//Funktionen +00 Aus +01-10 Dimmen direkt +11 Ein +12 Dimmen Eintastenmodus +13 Dimmen runter +14 Dimmen rauf +15 Umschalten +16 Timerprogrammierung start/stop +1b Auslieferungszustand diff --git a/docs/snippet_3.txt b/docs/snippet_3.txt new file mode 100644 index 000000000..d8544c88c --- /dev/null +++ b/docs/snippet_3.txt @@ -0,0 +1,60 @@ +// FHT80b +// SB=StartByte LL=Laenge TT=TelegrammType CC=CRC ST=Status HH=Hauscode + +// SB LL TT CC CODE FUNKT ST Param +-> 81 0c 04 __ 09 09 a0 01 HH HH 00 00 _6 xx // Istwert Stellantrieb +-> 81 0c 04 __ 09 09 a0 01 HH HH 00 00 2c xx // Synczeit ??? +-> 81 0c 04 __ 09 09 a0 01 HH HH 14 00 69 xx // Montag von1 in 1/6 +Stunde (10 Min.) +-> 81 0c 04 __ 09 09 a0 01 HH HH 15 00 69 xx // Montag bis1 +-> 81 0c 04 __ 09 09 a0 01 HH HH 16 00 69 xx // Montag von2 (90 = frei) +-> 81 0c 04 __ 09 09 a0 01 HH HH 17 00 69 xx // Montag bis2 +-> 81 0c 04 __ 09 09 a0 01 HH HH 18 00 69 xx // Dienstag von1 +-> 81 0c 04 __ 09 09 a0 01 HH HH 19 00 69 xx // bis 1 +-> 81 0c 04 __ 09 09 a0 01 HH HH 1a 00 69 xx // von2 +-> 81 0c 04 __ 09 09 a0 01 HH HH 1b 00 69 xx // bis2 +-> 81 0c 04 __ 09 09 a0 01 HH HH 1c 00 69 xx // Mittwoch von1 +-> 81 0c 04 __ 09 09 a0 01 HH HH 1d 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 1e 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 1f 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 20 00 69 xx // Donnerstag +-> 81 0c 04 __ 09 09 a0 01 HH HH 21 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 22 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 23 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 24 00 69 xx // Freitag +-> 81 0c 04 __ 09 09 a0 01 HH HH 25 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 26 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 27 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 28 00 69 xx // Samstag +-> 81 0c 04 __ 09 09 a0 01 HH HH 29 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 2a 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 2b 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 2c 00 69 xx // Sonntag +-> 81 0c 04 __ 09 09 a0 01 HH HH 2d 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 2e 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 2f 00 69 xx +-> 81 0c 04 __ 09 09 a0 01 HH HH 3e 00 69 xx // 0=auto 1=manuell +2=Urlaub lang, 3=Urlaub kurz +-> 81 0c 04 __ 09 09 a0 01 HH HH 3f 00 69 xx // für Mode3 Uhrzeit, für +Mode2 Endtag des Monats +-> 81 0c 04 __ 09 09 a0 01 HH HH 40 00 69 xx // für Mode3 Resttage ab +aktuellem Datum, für Mode2 Endmonat +-> 81 0c 04 __ 09 09 a0 01 HH HH 41 00 69 xx // aktuelle Solltemperatur +(x*0.5) +-> 81 0c 04 __ 09 09 a0 01 HH HH 42 00 69 xx // ist Temperatur (Teil 1) +-> 81 0c 04 __ 09 09 a0 01 HH HH 43 00 69 xx // Ist-Temperatur (Teil 2) +(Die Ermittlung der Temperatur erfolgt nach der Formel Ist-Temp = (Teil2 +* 255 +Teil1) / 10) +-> 81 0c 04 __ 09 09 a0 01 HH HH 44 00 69 xx // Status (Bit0=LowBatt, +Bit1=Untertemperatur, Bit4=Störung Fenterkontakt, Bit5=Fenster auf) +-> 81 0c 04 __ 09 09 a0 01 HH HH 45 00 69 xx // Manuelle Temperatur ?? + +-> 81 0c 04 __ 09 09 a0 01 HH HH 4b 00 67 xx // ????? +-> 81 0c 04 __ 09 09 a0 01 HH HH 7e 00 67 xx // ????? +-> 81 0c 04 __ 09 09 a0 01 HH HH 82 00 69 xx // Komfort-Temperatur +(x*0.5) +-> 81 0c 04 __ 09 09 a0 01 HH HH 84 00 69 xx // Absenk-Temperatur +(x*0.5) +-> 81 0c 04 __ 09 09 a0 01 HH HH 85 00 69 04 // Alarm-Temp.-Differenz +-> 81 0c 04 __ 09 09 a0 01 HH HH 8a 00 69 xx // Fenster offen Temperatur +(x*0.5) diff --git a/docs/snippet_4.txt b/docs/snippet_4.txt new file mode 100644 index 000000000..a59776588 --- /dev/null +++ b/docs/snippet_4.txt @@ -0,0 +1,39 @@ + fs20: + Modulation: 100% Amplitude, also pulse und spaces + + Keine festen Bitraster, ein Bit besteht immer aus pulse UND space + + Bit 0: pulse+space insgesamt ca 800us, + Bit 1: ca. 1200us + + Beginn einer Uebertragung: ca 20 Startbits 0, zum Start eine 1 + + dann Uebertragung der Datenbytes jeweils 8 Bit + Pruefbit: + msb + ... + lsb + pruefbit = xor-Verknuepfung der Datenbits. + + Die Uebertragung hat folgenden Aufbau: + 1. hausadr1 + 2. hausadr0 + 3. adr + 4. cmd + 5. arg1 + ... + n-1. argn optional + n. pruefbyte + + pruefbyte = unterste 8 Bit aus summe aller bytes 1..n-1 +6 + + Hauscode=hausadr1*256 + hausadr + + Dass ein hauscode 11111111 tatsaechlich fuer 0 steht, ist wohl klar. + + cmd 0..31: Keine Argumente + cmd 32..63: Ein Argumentbyte + Jede Uebertragung wird 3* ausgesendet. + + + Grueße, + automat diff --git a/docs/snippet_5.txt b/docs/snippet_5.txt new file mode 100644 index 000000000..10f7c2605 --- /dev/null +++ b/docs/snippet_5.txt @@ -0,0 +1,60 @@ +Fs20-Codes + 0 Aus + 1..15 setze auf Helligkeit + 16 An auf max + 17 An alter wert + 18 Toggel + 19 Dim UP + 20 Dim Down + 21 DIM up/down + 22 Prog.Modus/Zeit-Messung + 23 Adressmeldung / Nix + 24 Sofort Aus - Timer, sofort wieder an, evtl slow off + 25 Sofort Max - timer, sofort aus + 26 Sofort An alter Wert - timer, sofort aus + 27 ?? AUS - RESET? + 28 ? + 29 ? + 30 sofort Max, timer, dann sofort alter wert + 31 sofort An alter Wert, nach Timer - sofort Aus + + 32-47 S auf Helligkeit mit Speed S + 48 S An auf max, + 49 S An alter wert + 50 T Toggel fuer T, dann alter Wert + 51 T Dim up, T ? + 52 T Dim down, T ? + 53 T Dim up/down, T ? + 54 T Programmiere Einschaltzeit + 55 ? + 56 T Aus fuer EinT + 57 T Ein Max fuer T, dann alter Wert + 58 T Ein alter Wert fuer T, dann AUS + 59 ??? RESET + 60 T Progr. Einschalt-Speed mit T + 61 T Progr. Ausschalt-Speed mit T + 62 T Ein Max fuer T, dann alter Wert + 63 T Alter Wert fuer T, dann wieder aktueller Wert + + S und T Argumente 2. Byte. Zeit T=0: endlos + +Übrigens ein paar Anfänge zum HMS-Protokoll: +adr1 adr2 typ arg1 arg2/seqnr arg3 + + typ: + 0 temp/feucht hms100tf + 1 temp hms100t + 2 Wasser hms100w/wd + 3 Rauch hms100rm + 4 TuerFenster hms100tfk + 5 TuerFenster hms100tfk + 6 Gas hms100 MG/PG/CO + 7 Gas hms100 MG/PG/CO + 8 Gas hms100 MG/PG/CO + 9-13 unbekannt + 14 hms100 FI + 15 ? + +16 ?? + +32 Batteriewarnung + +64 neue Batterie + +128 Vorzeichen diff --git a/docs/snippet_6.txt b/docs/snippet_6.txt new file mode 100644 index 000000000..d98c90a91 --- /dev/null +++ b/docs/snippet_6.txt @@ -0,0 +1,127 @@ +S20-Funkschaltsystem + +1. Protokoll + +1.1. Datenrahmen: + + Hauscode 16 bit + Adresse 8 bit + Befehl 8 bit (16bit, wenn im ersten Befehlsbyte das Erweiterungsbit gesetzt + ist.) + Quersumme 8 bit + +1.1.1. Hauscode: + 0-65535 + Hauscode1: 8bit (High-Byte) + Hauscode2: 8bit (Low-Byte) + +1.1.2. Adresse: + High-Nibble (4bit): Adreß-Gruppe (Bank / Raum) + 15 = Master- / Funktionsgruppen-Bereich + 0-14 = Adreßraum für Einzeladressen + Low-Nibble (4bit): Unter-Adresse + 15 = alle der Adreß-Gruppe (lokal Master) + 0-14 = Einzel-Adresse / Adresse der Funktionsgruppe + + Das High-Nibble wählt die Adreß-Gruppe aus. Es stehen 15 Adreß-Gruppen zur + Verfügung. Die Adreß-Gruppe 15 wählt den Master- bzw. + Funktionsgruppen-Adreßbereich aus. Das Low-Nibble bestimmt innerhalb der + gewählten Adreß-Gruppe die anzusprechende Unter-Adresse. Zum Ausführen einer + globalen Master-Funktion müssen High- und Low-Nibble der Adresse 15 sein! + + + Reihenfolge der Eingabe und Speicherung von Hauscode und Adresse beim Setup + von Sendern: + 1. HC1: [A1|A0|B1|B0|C1|C0|D1|D0] + 2. HC2: [E1|E0|F1|F0|G1|G0|H1|H0] + 3. Adr: [I1|I0|J1|J0|K1|K0|L1|L0] + +1.1.3. Befehl: + Unteren 5 bit: + 0 00h aus + 1 01h an, 6,25% Einschalten auf Helligkeitsstufe 1 (min.) + 2 02h an, 12,5% + ... + 15 0fh an, 93,75% + 16 10h an, 100% Einschalten auf Helligkeitsstufe 16 (max.) + + 17 11h an, alter Wert Auf letztem Helligkeitswert einschalten + 18 12h toggle Wechsel zwischen aus und an, alter Wert + 19 13h dim up Eine Helligkeitsstufe heller + 20 14h dim down Eine Helligkeitsstufe dunkler + 21 15h dim up and down ..., + bis max, kurz warten, - bis min, kurz warten, + 22 16h timeset Timerprogrammierung (Start, Ende) + 23 17h send status Nur bei bidirektionalen Komponenten! + 24 18h aus, für Timerzeit + 25 19h an, 100%, für Timerzeit + 26 1ah an, alter Wert, für Timerzeit + 27 1bh reset (auf Auslieferzustand) + 28 1ch frei + 29 1dh frei + 30 1eh frei + 31 1fh frei + + Bit 5: Erweiterungsbit (0 = ohne, 1 = Erweiterungsbyte zwischen Befehl und + Quersumme) + Bit 6: bidirektionaler Befehl (normal = 0) + Bit 7: Antwort eines Empfängers (normal = 0) + + Erweiterungsbyte bei gesetztem Erweiterungsbit: + Ist im ersten Befehlsbyte das Erweiterungsbit gesetzt, wird ein + Erweiterungsbyte eingeschoben. + + Für die Befehle 0 bis 18 und 24 bis 26 gilt folgende Codierung des + Erweiterungsbytes: + Das Byte gibt die einmalige Zeitdauer für den Timer in Schritten von 0,25s an. + + Bei Befehl 22 wird der Timer des Empfängers fest auf den übertragenen + Timerwert gestellt. + Das Low-Nibble gibt den Zahlenwert an. Ist es Null (= 0), wird die + Timerfunktion ignoriert und der Verbraucher dauerhaft/sofort geschaltet. + + Das High-Nibble dient als Multiplikator mit dem Faktor 2^x. Es sind nur Werte + kleiner oder gleich 12 sinnvoll. Größere Werte werden auf 12 begrenzt! + + Zeit = 2^(High-Nibble) * Low-Nibble * 0,25s + Die maximale Zeitdauer beträgt damit ca. 4,25Std, die minimale 0,25s, sofern + dies jeweils von den Empfängern bis zu den angegebenen Grenzen unterstützt + wird. + +1.1.4. Quersumme + + 8bit-Summe aus 6, Hauscode, Adresse und Befehl (und Erweiterungsbyte) bilden + Werden Repeater verwendet, so treten auch um 1 oder 2 erhöhte Quersummen auf, + die von den Empfängern im Normalfall akzeptiert werden sollten. Wurde von + einem Empfänger bis 1,6s vor einem Repeater-Befehl ein normaler Befehl + empfangen, so wird der Repeater-Befehl ignoriert. + + +1.1.5. Komplette Übertragung: + Synchr, HC1, Parity, HC2, Parity, Adresse, Parity, Befehl, Parity, Quersumme, Parity, EOT + 13 bit 8 bit 1 bit 8 bit 1 bit 8 bit 1 bit 8 bit 1 bit 8 bit 1 bit 1 bit + + oder: + Synchr, HC1, Parity, HC2, Parity, Adr, Parity, Bef1, Parity, Bef2, Par, Quersumme, Par, EOT + 13 bit 8 bit 1 bit 8 bit 1 bit 8 bit 1 bit 8 bit 1 bit 8 bit 1 bit 8 bit 1 bit 1 bit + + Übertragung beginnt mit MSB. + + Die Übertragung für ein komplettes Datenpaket mit 1 Befehl dauert 47,6ms bis + 65,6ms. + + Wird an der Hand-FB eine Taste < 400ms gedrückt, so wird beim Loslassen + folgendes gesendet: DAT, 10msPAUSE, DAT, 10msPAUSE, DAT, 110msPAUSE + + Wird eine Taste an der Hand-FB länger als 400ms gedrückt, so wird alle 250ms + folgendes gesendet: DAT, 10msPAUSE, DAT + + + Im AUS-Zustand schaltet ein Dimmer bei EIN mit alter Helligkeit ein. + Im AUS-Zustand schaltet ein Dimmer bei DIMUP mit voller Helligkeit ein. + Im EIN-Zustand wird bei DIMUP eine Stufe aufgedimmt. + + Alle Befehle dürfen von Empfängern immer nur 1x ausgewertet werden. Gesendet +wird der Befehl 3x mit einer Pause von 10ms. Dim-Befehle werden nur 2x mit +einer Pause von ca. 10ms und 130ms gesendet. Nach einem erkannten Befehl +ignorieren die Empfänger für 120ms weitere Befehle. Befehle von Repeatern +werden für 1,6s ignoriert. diff --git a/docs/snippet_7.txt b/docs/snippet_7.txt new file mode 100644 index 000000000..d31b5f01d --- /dev/null +++ b/docs/snippet_7.txt @@ -0,0 +1,14 @@ +Get 81 0c c9 78 0102 1f04a0ffb6ffffff +Get 81 0c c9 da 0102 1f04a0ffb661ffff +Set 81 05 04 61 c90197 +Set 81 05 04 63 c90199 +Get 81 0d 04 ac 4027a0017100215000b30f KS300 msg +Set 81 05 04 50 c90186 initHMS +Set 81 06 04 62 c9019602 InitFS20 (+02?) +Set 81 06 c9 2c 02011f0a Init3 string +Set 81 09 04 46 020101xxxx0000 Set FS20 device off +Set 81 09 04 4e c901839e010161 Set fhtcode 97 (0x61) +Set 81 09 04 57 020101xxxx0011 Set FS20 device on +Set 81 0a c9 93 02016106050f1500 Time: 2006-05-15 21:00 +Set 81 0a c9 a4 02016106050f1511 Time: 2006-05-15 21:17 +Set 81 0b 04 8f 020183xxxx65ff66ff Request FHT refreshvalues diff --git a/docs/web-tipps b/docs/web-tipps new file mode 100644 index 000000000..9800e7584 --- /dev/null +++ b/docs/web-tipps @@ -0,0 +1,17 @@ +========================================= +#httpd.conf entry: no password for for distinguished hosts + +ScriptAlias /cgi-bin/ "/home/httpd/cgi-bin/" + + AuthType Basic + AuthName "Password Required" + AuthUserFile /home/httpd/etc/passwd + Require valid-user + Order deny,allow + Deny from all + Allow from 192.168.0.207 + Allow from 192.168.0.208 + Satisfy any + + +========================================= diff --git a/examples/01_fs20 b/examples/01_fs20 new file mode 100644 index 000000000..3f31715a1 --- /dev/null +++ b/examples/01_fs20 @@ -0,0 +1,18 @@ +# +# fhem.pl configfile +# +# Define a lamp (which is plugged in via an FS20ST). +# To program the FS20ST, start the server, plug the FS20ST in pressing its +# button, and then execute fhem.pl 7072 "set lamp on" +# + +# Common part +logfile /tmp/fhem-%Y-%m.log +savefile /tmp/fhem.save # where to save the state of the devices +verbose 3 # "normal" verbosity (min 1, max 5) +port 7072 # our TCP/IP port (working from localhost only) +modpath . # where our FHEM directory is +define FHZ FHZ /dev/tts/USB0 # the serial port of an FHZ 1000 PC + + +define lamp FS20 8765 01 # type FS20, transmitter code 8765, button 2 diff --git a/examples/02_fs20 b/examples/02_fs20 new file mode 100644 index 000000000..d0a374aef --- /dev/null +++ b/examples/02_fs20 @@ -0,0 +1,72 @@ +# +# fhem.pl configfile +# +# We have 2 rollades (which are connected via the FS20MS). Button 3 on the +# FS20S20 should activate both rollades. There are three solutions: +# 1. Builtin commands +# 2. Perl expression +# 3. Shell script (realized via external script at the end of this file) + + +# Common part +logfile /tmp/fhem-%Y-%m.log +savefile /tmp/fhem.save # where to save the state of the devices +verbose 3 # "normal" verbosity +port 7072 # our TCP/IP port (working from localhost only) +modpath . # where our FHEM directory is +define FHZ FHZ /dev/tts/USB0 # the serial port of an FHZ 1000 PC + + +define roll1 FS20 7777 02 # type FS20, transmitter code 7777, button 3 +define roll2 FS20 7777 03 # type FS20, transmitter code 7777, button 4 +define btn3 FS20 8765 03 # define a button from the FS20S20 +setstate roll1 off # initial state is closed + +# Note: Only one of the methods should be used + +# Method 1a: builtin commands. Note the double ; +notifyon btn3 set roll1 %;; set roll2 % + +# Method 1b: shorter: +notifyon btn3 set roll1,roll2 % + +# Method 2a: perl. +notifyon btn3 { fhz "set roll1 %;; set roll2 %" } + +# Method 2b: perl. open the rollades only to a certain amount if they are +# closed. Else do the required command. +notifyon btn3 {\ + if("%" eq "on" && $value{roll1} eq "off") {\ + fhz "set roll1 on-for-timer 10";;\ + fhz "set roll2 on-for-timer 16";;\ + } else { \ + fhz "set roll1,roll2 %"\ + } \ +} + +# Method 3: shell. The script follows after "quit". Dont forget to chmod u+x it. +notifyon btn3 "/usr/local/bin/roll.sh %" + +quit # Ignore the rest of this file + +#!/bin/sh +# +# roll1 needs 10 sec to open to a certain level, roll2 16 sec. The following +# shell script opens both of them when called woth "on", and closes both of +# them else. We rely on the fact, that the FS20MS switches off after ca 60s. +# +# Note that for greater time values the FS20 timer gets inaccurate, you +# can use something like +# $fhz 7072 "set roll1 on; at +00:00:21 set roll1 on" +# instead. +# + +fhz=/usr/local/bin/fhem.pl + +if test $1 = "on"; then + $fhz 7072 "set roll1 on-for-timer 10 + $fhz 7072 "set roll2 on-for-timer 16 +else + $fhz 7072 "set roll1,roll2 off" +fi + diff --git a/examples/03_fht b/examples/03_fht new file mode 100644 index 000000000..096bbe93f --- /dev/null +++ b/examples/03_fht @@ -0,0 +1,30 @@ +# +# fhem.pl configfile +# +# Define an FHT80b device. You have to know its transmitter code, +# or set verbose to 4 and wait for a while watching the log. +# +# wz stands for "wohnzimmer". +# After about 5-10 minutes, check if "list wz" returns something meaningful +# + +logfile /tmp/fhem-%Y-%m.log +savefile /tmp/fhem.save # where to save the state of the devices +verbose 3 # "normal" verbosity +port 7072 # our TCP/IP port (working from localhost only) +modpath . # where our FHEM directory is +define FHZ FHZ /dev/tts/USB0 # the serial port of an FHZ 1000 PC + +define wz FHT 3232 # type FHT, transmitter code 3232 (default value) + +######################### +# Some documentation suggests that the FHZ time should be set every minute. +# I only set it once a day. +at *03:30:00 set FHZ time + +######################### +# If you wish to have up-to date information on certain strange parameters +# then comment out the line below. My devices send a message when a value +# changes, and send measured-temp, actuator and state messages regularly. +# Be patient: the reply comes in 5-10 minutes. +at *04:00:00 set wz refreshvalues diff --git a/examples/04_log b/examples/04_log new file mode 100644 index 000000000..0bcb39257 --- /dev/null +++ b/examples/04_log @@ -0,0 +1,49 @@ +# +# fhem.pl configfile +# Logging FS20/KS300 data +# See the file fht.gnuplot for displaying the logged data (or webfrontend/pgm2) +# + +logfile /tmp/fhem-%Y-%m.log +savefile /tmp/fhem.save # where to save the state of the devices +verbose 3 # "normal" verbosity +port 7072 # our TCP/IP port (working from localhost only) +modpath . # where our FHEM directory is +define FHZ FHZ /dev/tts/USB0 # the serial port of an FHZ 1000 PC + +define wz FHT 3232 # type FHT, transmitter code 3232 (default value) +define ks1 KS300 1234 250 # type KS300, with 250ml rain / counter + + +######################### +# Log temperature and actuator changes into a file, its name changes weekly +define wzlog FileLog /var/tmp/wz-%Y-%U.log wz:.*(temp|actuator).* + +# ks300 log +define kslog FileLog /var/log/wz-%Y-%U.log ks1:.*H:.* +define avglog FileLog /var/log/avg.log ks1:.*avg.* + +############################## +# Alternative log method. It does the same, but it is somewhat slower as it +# starts the shellscript below. Don't forget the "", as some values contain +# paranthesis, and your shell will probably bark. +notifyon wz:temp.* "/usr/local/bin/log.sh @ "@ %"" + +############################## +# If using the frontends pgm2 or pgm3, then you can put the devices +# into separate rooms, see the corresponding README: +attr wz room InDoor +attr wzlog room InDoor +attr ks1 room OutDoor +attr kslog room OutDoor +attr avglog room OutDoor + +quit + +######################### +And here is /usr/local/bin/log.sh, don't forget chmod +x + +#!/bin/sh +fname=$1 +shift +echo `date +"%Y-%m-%d_%H:%M:%S"` "$*" >> /var/log/$fname.log diff --git a/examples/05_rm100 b/examples/05_rm100 new file mode 100644 index 000000000..14a06e8a2 --- /dev/null +++ b/examples/05_rm100 @@ -0,0 +1,23 @@ +# +# fhem.pl configfile +# +# Define RM100-2 devices: +# +# As the RM100-2 changes its code after the battery is changed (or the switch +# on the device itself is changed), we map _all_ RM100-2 to the device id 1001 +# Check the commandref.html define, Type HMS section for details + +logfile /tmp/fhem-%Y-%m.log +savefile /tmp/fhem.save # where to save the state of the devices +verbose 3 # "normal" verbosity (min 1, max 5) +port 7072 # our TCP/IP port (working from localhost only) +modpath . # where our FHEM directory is +define FHZ FHZ /dev/tts/USB0 # the serial port of an FHZ 1000 PC + + +define rm100 HMS 1001 # type HMS +define rm100log FileLog /var/log/wz-%Y-%U.log rm100:.* +notifyon rm100:smoke.*on "wall "FIRE: @ %"" + +# Test the log/notify +# fhem.pl 7072 'trigger rm100 smoke on' diff --git a/examples/06_at b/examples/06_at new file mode 100644 index 000000000..7cf2659f4 --- /dev/null +++ b/examples/06_at @@ -0,0 +1,39 @@ +# These are only examples for the at and notify command + + +################################## +# absolute ones: +at 17:00:00 set lamp on # fhz command +at 17:00:00 { Log 1, "Teetime" } # Perl command +at 17:00:00 "/bin/echo "Teetime" > /dev/console" # shell command +at *17:00:00 set lamp on # repeat every day + +################################## +# relative ones +at +00:00:10 set lamp on # switch the lamp on in 10 seconds +at +00:00:02 set lamp on-for-timer 1 # Blink once in 2 seconds +at +*{3}00:00:02 set lamp on-for-timer 1 # Blink 3 times + +################################## +# Switch the lamp on from sunset to 11 PM each day +# You have to install 99_SUNRISE.pm in the FHEM directory to have sunset() +# We have to use the relative versions, as the next event is computed now +{ sunrise_coord("8.686", "50.112", "Europe/Berlin") } +at +*{sunset_rel()} set lamp on +at *23:00:00 set lamp off + +################################## +# A more elegant solution, which even works if sunset is after 23:00 +at +*{sunset_rel()} set lamp on-till 23:00 + +################################## +# Only do this on weekend. For the preset perl variables see commandref.html +at +*{sunset_rel()} { fhz("set lamp on-till 23:00") if($we) } + +################################## +# Switch lamp1 and lamp2 on from 7:00 till 10 minutes after sunrise +at *07:00 set lamp1,lamp2 on-till {sunrise_abs(+600)} + +################################## +# Blink 3 times if the piri sends a command +notify piri:on.* at +*{3}00:00:02 set lamp on-for-timer 1 diff --git a/fhem.pl b/fhem.pl new file mode 100755 index 000000000..eeba12403 --- /dev/null +++ b/fhem.pl @@ -0,0 +1,1659 @@ +#!/usr/bin/perl + +my $version = "=VERS= from =DATE="; + +################################################################ +# +# Copyright notice +# +# (c) 2005 Copyright: Rudolf Koenig (r dot koenig at koeniglich dot de) +# All rights reserved +# +# This script 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 2 of the License, or +# (at your option) any later version. +# +# The GNU General Public License can be found at +# http://www.gnu.org/copyleft/gpl.html. +# A copy is found in the textfile GPL.txt and important notices to the license +# from the author is found in LICENSE.txt distributed with these scripts. +# +# This script 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. +# +# This copyright notice MUST APPEAR in all copies of the script! +# Thanks for Tosti's site () +# for inspiration. +# +# Homepage: http://www.koeniglich.de/fhem/fhem.html + + +use strict; +use warnings; +use IO::File; +use IO::Socket; +use Net::hostent; +use Time::HiRes qw(gettimeofday); + + +################################################## +# Forward declarations +# +sub AnalyzeInput($); +sub AnalyzeCommand($$); +sub AnalyzeCommandChain($$); +sub IOWrite($@); +sub AssignIoPort($); +sub InternalTimer($$$); +sub fhz($); +sub CommandChain($$); +sub DoClose($); +sub HandleTimeout(); +sub Log($$); +sub OpenLogfile($); +sub ResolveDateWildcards($@); +sub SignalHandling(); +sub TimeNow(); +sub DoSavefile(); +sub SemicolonEscape($); +sub XmlEscape($); + +sub CommandAt($$); +sub CommandAttr($$); +sub CommandDefine($$); +sub CommandDelete($$); +sub CommandFhzDev($$); +sub CommandGet($$); +sub CommandHelp($$); +sub CommandInclude($$); +sub CommandInform($$); +sub CommandList($$); +sub CommandLogfile($$); +sub CommandModpath($$); +sub CommandNotifyon($$); +sub CommandPidfile($$); +sub CommandPort($$); +sub CommandRereadCfg($$); +sub CommandQuit($$); +sub CommandSavefile($$); +sub CommandSet($$); +sub CommandSetstate($$); +sub CommandSleep($$); +sub CommandShutdown($$); +sub CommandVerbose($$); +sub CommandXmlList($$); +sub CommandTrigger($$); + +################################################## +# Variables: +# global, to be able to access them from modules +use vars qw(%defs); # FHEM device/button definitions +use vars qw(%logs); # Log channels +use vars qw(%attr); # Attributes +use vars qw(%value); # Current values, see commandref.html +use vars qw(%oldvalue); # Old values, see commandref.html +use vars qw(%devmods); # List of loaded device modules + +my %ntfy; +my %at; + +my $server; # Server socket +my $verbose = 0; +my $logfile; # logfile name, if its "-" then wont background +my $currlogfile; # logfile, without wildcards +my $logopened; +my %client; # Client array +my %logmods; # List of loaded logger modules +my $savefile = ""; # Save ste info and at cmd's here +my $nextat; +my $rcvdquit; # Used for quit handling in init files +my $configfile=$ARGV[0]; +my $sig_term = 0; # if set to 1, terminate (saving the state) +my $modpath_set; # Check if modpath was used, and report if not. +my $global_cl; # To use from perl snippets +my $devcount = 0; # To sort the devices +my %intAt; # Internal at timer hash. +my $intAtCnt=0; +my $init_done = 0; +my $pidfilename; + + +my %cmds = ( + "?" => { Fn=>"CommandHelp", + Hlp=>",get this help" }, + "at" => { Fn=>"CommandAt", + Hlp=>" ,issue a command at a given time" }, + "attr" => { Fn=>"CommandAttr", + Hlp=>" ,set attributes for " }, + "define" => { Fn=>"CommandDefine", + Hlp=>" ,define a code" }, + "delete" => { Fn=>"CommandDelete", + Hlp=>"{def|ntfy|at} name,delete the corresponding definition"}, + "get" => { Fn=>"CommandGet", + Hlp=>" ,request data from " }, + "help" => { Fn=>"CommandHelp", + Hlp=>",get this help" }, + "include" => { Fn=>"CommandInclude", + Hlp=>",read the commands from " }, + "inform" => { Fn=>"CommandInform", + Hlp=>"{on|off},echo all commands and events to this client" }, + "list" => { Fn=>"CommandList", + Hlp=>"[device],list definitions and status info" }, + "logfile" => { Fn=>"CommandLogfile", + Hlp=>"filename,use - for stdout" }, + "modpath" => { Fn=>"CommandModpath", + Hlp=>",the directory where the FHEM subdir is" }, + "notifyon"=> { Fn=>"CommandNotifyon", + Hlp=>" ,exec when recvd signal for " }, + "pidfile" => { Fn=>"CommandPidfile", + Hlp=>"filename,write the process id into the pidfile" }, + "port" => { Fn=>"CommandPort", + Hlp=>" [global],TCP/IP port for the server" }, + "quit" => { Fn=>"CommandQuit", + Hlp=>",end the client session" }, + "reload" => { Fn=>"CommandReload", + Hlp=>",reload the given module (e.g. 99_PRIV)" }, + "rereadcfg" => { Fn=>"CommandRereadCfg", + Hlp=>",reread the config file" }, + "savefile"=> { Fn=>"CommandSavefile", + Hlp=>",on shutdown save all states and at entries" }, + "set" => { Fn=>"CommandSet", + Hlp=>" ,transmit code for " }, + "setstate"=> { Fn=>"CommandSetstate", + Hlp=>" ,set the state shown in the command list" }, + "shutdown"=> { Fn=>"CommandShutdown", + Hlp=>",terminate the server" }, + "sleep" => { Fn=>"CommandSleep", + Hlp=>",sleep for usecs" }, + "trigger" => { Fn=>"CommandTrigger", + Hlp=>" ,trigger notify command" }, + "verbose" => { Fn=>"CommandVerbose", + Hlp=>",verbosity level, 0-5" }, + "xmllist" => { Fn=>"CommandXmlList", + Hlp=>",list definitions and status info as xml" }, +); + + + +################################################### +# Start the program +if(int(@ARGV) != 1 && int(@ARGV) != 2) { + print "Usage:\n"; + print "as server: fhem configfile\n"; + print "as client: fhem [host:]port cmd\n"; + CommandHelp(undef, undef); + exit(1); +} + +################################################### +# Client code +if(int(@ARGV) == 2) { + my $buf; + my $addr = $ARGV[0]; + $addr = "localhost:$addr" if($ARGV[0] !~ m/:/); + $server = IO::Socket::INET->new(PeerAddr => $addr); + die "Can't connect to $addr\n" if(!$server); + syswrite($server, "$ARGV[1] ; quit\n"); + my $err = 0; + while(sysread($server, $buf, 256) > 0) { + print($buf); + $err = 1; + } + exit($err); +} + +my $ret = CommandInclude(undef, $configfile); +die($ret) if($ret); + +if($logfile ne "-") { + defined(my $pid = fork) || die "Can't fork: $!"; + exit(0) if $pid; +} + +die("No modpath specified in the configfile.\n") if(!$modpath_set); + +if($savefile && -r $savefile) { + $ret = CommandInclude(undef, $savefile); + die($ret) if($ret); +} +SignalHandling(); + + +Log 0, "Server started (version $version, pid $$)"; + +################################################ +# Main loop + +$init_done = 1; +CommandPidfile(undef, $pidfilename) if($pidfilename); + +while (1) { + my ($rout, $rin) = ('', ''); + + vec($rin, $server->fileno(), 1) = 1; + foreach my $p (keys %defs) { + vec($rin, $defs{$p}{FD}, 1) = 1 if($defs{$p}{FD}); + } + foreach my $c (keys %client) { + vec($rin, fileno($client{$c}{fd}), 1) = 1; + } + + my $nfound = select($rout=$rin, undef, undef, HandleTimeout()); + + CommandShutdown(undef, undef) if($sig_term); + + if($nfound < 0) { + next if ($! == EAGAIN() || $! == EINTR() || $! == 0); + die("Select error $nfound / $!\n"); + } + + ############################### + # Message from the hardware (FHZ1000/WS3000/etc) + foreach my $p (keys %defs) { + next if(!$defs{$p}{FD} || !vec($rout, $defs{$p}{FD}, 1)); + no strict "refs"; + &{$devmods{$defs{$p}{TYPE}}{ReadFn}}($defs{$p}); + use strict "refs"; + } + + if(vec($rout, $server->fileno(), 1)) { + my @clientinfo = $server->accept(); + if(!@clientinfo) { + Print("ERROR", 1, "016 Accept failed for admin port"); + next; + } + my @clientsock = sockaddr_in($clientinfo[1]); + my $fd = $clientinfo[0]; + $client{$fd}{fd} = $fd; + $client{$fd}{addr} = inet_ntoa($clientsock[1]) . ":" . $clientsock[0]; + $client{$fd}{buffer} = ""; + Log 4, "Connection accepted from $client{$fd}{addr}"; + } + + foreach my $c (keys %client) { + + next unless (vec($rout, fileno($client{$c}{fd}), 1)); + + my $buf; + my $ret = sysread($client{$c}{fd}, $buf, 256); + if(!defined($ret) || $ret <= 0) { + DoClose($c); + next; + } + if(ord($buf) == 4) { # EOT / ^D + CommandQuit($c, ""); + next; + } + $buf =~ s/\r//g; + $client{$c}{buffer} .= $buf; + AnalyzeInput($c); + } +} + +################################################ +sub +IsDummy($) +{ + my $dev = shift; + + return 1 if(defined($attr{$dev}) && defined($attr{$dev}{dummy})); + return 0; +} + +################################################ +sub +GetLogLevel($) +{ + my $dev = shift; + + return $attr{$dev}{loglevel} + if(defined($attr{$dev}) && defined($attr{$dev}{loglevel})); + return 2; +} + + +################################################ +sub +Log($$) +{ + my ($loglevel, $text) = @_; + + return if($loglevel > $verbose); + + my @t = localtime; + my $nfile = ResolveDateWildcards($logfile, @t); + OpenLogfile($nfile) if($currlogfile && $currlogfile ne $nfile); + my $tim = sprintf("%04d.%02d.%02d %02d:%02d:%02d", + $t[5]+1900,$t[4]+1,$t[3], $t[2],$t[1],$t[0]); + +# my ($seconds, $microseconds) = gettimeofday(); +# $tim = sprintf("%04d.%02d.%02d %02d:%02d:%02d.%03d", +# $t[5]+1900,$t[4]+1,$t[3], $t[2],$t[1],$t[0], $microseconds/1000); + + if($logopened) { + print LOG "$tim $loglevel: $text\n"; + } else { + print "$tim $loglevel: $text\n"; + } + return undef; +} + + +##################################### +sub +DoClose($) +{ + my $c = shift; + + Log 4, "Connection closed for $client{$c}{addr}"; + close($client{$c}{fd}); + delete($client{$c}); + return undef; +} + +##################################### +sub +IOWrite($@) +{ + my ($hash, @a) = @_; + + my $iohash = $hash->{IODev}; + if(!$iohash) { + Log 5, "No IO device found for $hash->{NAME}"; + return; + } + + no strict "refs"; + &{$devmods{$iohash->{TYPE}}{WriteFn}}($iohash, @a); + use strict "refs"; +} + +##################################### +sub +AnalyzeInput($) +{ + my $c = shift; + + while($client{$c}{buffer} =~ m/\n/) { + my ($cmd, $rest) = split("\n", $client{$c}{buffer}, 2); + $client{$c}{buffer} = $rest; + if($cmd) { + AnalyzeCommandChain($c, $cmd); + return if(!defined($client{$c})); # quit + } else { + $client{$c}{prompt} = 1; + } + syswrite($client{$c}{fd}, "FHZ> ") + if($client{$c}{prompt} && $rest !~ m/\n/); + } +} + +##################################### +sub +AnalyzeCommandChain($$) +{ + my ($c, $cmd) = @_; + $cmd =~ s/#.*$//; + $cmd =~ s/;;/____/g; + foreach my $subcmd (split(";", $cmd)) { + $subcmd =~ s/____/;/g; + AnalyzeCommand($c, $subcmd); + last if($c && !defined($client{$c})); # quit + } +} + +##################################### +# Used from perl oneliners inside of scripts +sub +fhz($) +{ + my $param = shift; + return AnalyzeCommandChain($global_cl, $param); +} + +##################################### +sub +AnalyzeCommand($$) +{ + my ($cl, $cmd) = @_; + + $cmd =~ s/^[ \t]*//; # Strip space + $cmd =~ s/[ \t]*$//; + + Log 5, "Cmd: >$cmd<"; + return if(!$cmd); + + if($cmd =~ m/^{.*}$/) { # Perl code + + # Make life easier for oneliners: + %value = (); + foreach my $d (keys %defs) { $value{$d} = $defs{$d}{STATE } } + my ($sec,$min,$hour,$mday,$month,$year,$wday,$yday,$isdst) = localtime; + my $we = (($wday==0 || $wday==6) ? 1 : 0); + $month++; + $year+=1900; + + $global_cl = $cl; + my $ret = eval $cmd; + $ret = $@ if($@); + if($ret) { + if($cl) { + syswrite($client{$cl}{fd}, "$ret\n") + } else { + Log 3, $ret; + } + } + return $ret; + + } + + if($cmd =~ m/^"(.*)"$/) { # Shell code, always in bg + system("$1 &"); + return; + } + + $cmd =~ s/^[ \t]*//; + my ($fn, $param) = split("[ \t][ \t]*", $cmd, 2); + + return if(!$fn); + + ############# + # Search for abbreviation + if(!defined($cmds{$fn})) { + foreach my $f (sort keys %cmds) { + if(length($f) > length($fn) && substr($f, 0, length($fn)) eq $fn) { + Log 5, "$fn => $f"; + $fn = $f; + last; + } + } + } + + if(!defined($cmds{$fn})) { + if($cl) { + syswrite($client{$cl}{fd}, "Unknown command $fn, try help\n"); + } else { + Log 1, "Unknown command >$fn<, try help"; + } + return; + } + $param = "" if(!defined($param)); + no strict "refs"; + my $ret = &{$cmds{$fn}{Fn} }($cl, $param); + use strict "refs"; + if($ret) { + if($cl) { + syswrite($client{$cl}{fd}, $ret . "\n"); + } else { + Log 1, $ret; + return $ret; + } + } +} + +##################################### +sub +CommandHelp($$) +{ + my ($cl, $param) = @_; + + my $str = "\n" . + "Possible commands:\n\n" . + "Command Parameter Description\n" . + "-----------------------------------------------\n"; + + for my $cmd (sort keys %cmds) { + my @a = split(",", $cmds{$cmd}{Hlp}, 2); + + $str .= sprintf("%-9s %-25s %s\n", $cmd, $a[0], $a[1]); + } + return $str; +} + +sub +CommandInclude($$) +{ + my ($cl, $arg) = @_; + if(!open(CFG, $arg)) { + return "Can't open $arg: $!"; + } + + my $bigcmd = ""; + $rcvdquit = 0; + while(my $l = ) { + chomp($l); + if($l =~ m/^(.*)\\$/) { # Multiline commands + $bigcmd .= $1; + } else { + AnalyzeCommandChain($cl, $bigcmd . $l); + $bigcmd = ""; + } + last if($rcvdquit); + } + close(CFG); + return undef; +} + +##################################### +sub +CommandPort($$) +{ + my ($cl, $arg) = @_; + + my ($port, $global) = split(" ", $arg); + if($global && $global ne "global") { + return "Bad syntax, usage: port [global]"; + } + + close($server) if($server); + $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalHost => ($global ? undef : "localhost"), + LocalPort => $port, + Listen => 10, + ReuseAddr => 1); + + die "Can't open server port at $port\n" if(!$server); + return undef; +} + +##################################### +sub +OpenLogfile($) +{ + my $param = shift; + + close(LOG) if($logfile); + $logopened=0; + $currlogfile = $param; + if($currlogfile eq "-") { + + open LOG, '>&STDOUT' or die "Can't dup stdout: $!"; + + } else { + + open(LOG, ">>$currlogfile") || return("Can't open $currlogfile: $!"); + # Redirect stdin/stderr + + open STDIN, '>$currlogfile") or return "Can't append STDERR to log: $!"; + STDERR->autoflush(1); + + close(STDOUT); + open STDOUT, '>&STDERR' or return "Can't dup stdout: $!"; + STDOUT->autoflush(1); + } + LOG->autoflush(1); + $logopened = 1; + return undef; +} + +##################################### +sub +CommandLogfile($$) +{ + my ($cl, $param) = @_; + + $logfile = $param; + + my @t = localtime; + my $ret = OpenLogfile(ResolveDateWildcards($param, @t)); + die($ret) if($ret); + return undef; +} + + + +##################################### +sub +CommandVerbose($$) +{ + my ($cl, $param) = @_; + if($param =~ m/^[0-5]$/) { + $verbose = $param; + return undef; + } else { + return "Valid value for verbose are 0,1,2,3,4,5"; + } +} + +##################################### +sub +CommandRereadCfg($$) +{ + my ($cl, $param) = @_; + + return "RereadCfg: No parameters are accepted" if($param); + DoSavefile(); + + foreach my $d (keys %defs) { + no strict "refs"; + my $ret = &{$devmods{$defs{$d}{TYPE}}{UndefFn}}($defs{$d}, $d); + use strict "refs"; + return $ret if($ret); + } + + %defs = (); + %logs = (); + %attr = (); + %ntfy = (); + %at = (); + + my $ret; + $ret = CommandInclude($cl, $configfile); + return $ret if($ret); + $ret = CommandInclude($cl, $savefile) if($savefile); + return $ret; +} + +##################################### +sub +CommandQuit($$) +{ + my ($cl, $param) = @_; + + if(!$cl) { + $rcvdquit = 1; + return; + } + + syswrite($client{$cl}{fd}, "Bye...\n") if($client{$cl}{prompt}); + DoClose($cl); + return undef; +} + +##################################### +sub +DoSavefile() +{ + return if(!$savefile); + if(!open(SFH, ">$savefile")) { + Log 1, "Cannot open $savefile: $!"; + return; + } + + my $t = localtime; + print SFH "#$t\n"; + + foreach my $d (sort keys %defs) { + my $t = $defs{$d}{TYPE}; + print SFH "setstate $d $defs{$d}{STATE}\n" + if($defs{$d}{STATE} && $defs{$d}{STATE} ne "unknown"); + + ############# + # Now the detailed list + no strict "refs"; + my $str = &{$devmods{$defs{$d}{TYPE}}{ListFn}}($defs{$d}); + use strict "refs"; + next if($str =~ m/^No information about/); + + foreach my $l (split("\n", $str)) { + print SFH "setstate $d $l\n" + } + + } + + foreach my $t (sort keys %at) { + # $t =~ s/_/ /g; # Why is this here? + print SFH "at $t\n"; + } + + close(SFH); +} + +##################################### +sub +CommandShutdown($$) +{ + my ($cl, $param) = @_; + Log 0, "Server shutdown"; + DoSavefile(); + exit(0); +} + +##################################### +sub +CommandNotifyon($$) +{ + my ($cl, $param) = @_; + + my @a = split("[ \t]", $param, 2); + + # Checking for misleading regexps + eval { "Hallo" =~ m/^$a[0]$/ }; + return "Bad regexp: $@" if($@); + + $ntfy{$a[0]} = SemicolonEscape($a[1]); + return undef; +} + +##################################### +sub +DoSet(@) +{ + my @a = @_; + + my $dev = $a[0]; + my $ret; + no strict "refs"; + $ret = &{$devmods{$defs{$dev}{TYPE}}{SetFn}}($defs{$dev}, @a); + use strict "refs"; + + return $ret if($ret); + + shift @a; + return DoTrigger($dev, join(" ", @a)); +} + +##################################### +sub +CommandSet($$) +{ + my ($cl, $param) = @_; + my @a = split("[ \t][ \t]*", $param); + return "Usage: set " if(int(@a) < 1); + + my $dev = $a[0]; + my @rets; + + if(defined($defs{$dev})) { + + return DoSet(@a); + + } elsif($dev =~ m/,/) { # Enumeration (separated by ,) + + foreach my $sdev (split(",", $dev)) { + push @rets, "Please define $sdev first" if(!defined($defs{$sdev})); + $a[0] = $sdev; + my $ret = DoSet(@a); + push @rets, $ret if($ret); + } + return join("\n", @rets); + + } elsif($dev =~ m/-/) { # Range (separated by -) + + my @lim = split("-", $dev); + foreach my $sdev (keys %defs) { + next if($sdev lt $lim[0] || $sdev gt $lim[1]); + $a[0] = $sdev; + my $ret = DoSet(@a); + push @rets, $ret if($ret); + } + return join("\n", @rets); + + } else { + + return "Please define $dev first ($param)"; + + } +} + + +##################################### +sub +CommandGet($$) +{ + my ($cl, $param) = @_; + + my @a = split("[ \t][ \t]*", $param); + return "Usage: get " if(int(@a) < 1); + my $dev = $a[0]; + return "Please define $dev first ($param)" if(!defined($defs{$dev})); + + ######################## + # Type specific set + my $ret; + no strict "refs"; + $ret = &{$devmods{$defs{$a[0]}{TYPE}}{GetFn}}($defs{$dev}, @a); + use strict "refs"; + + return $ret; +} + +##################################### +sub +GetTimeSpec($) +{ + my ($tspec) = @_; + my ($hr, $min, $sec, $fn); + + if($tspec =~ m/^([0-9]+):([0-5][0-9]):([0-5][0-9])$/) { + ($hr, $min, $sec) = ($1, $2, $3); + } elsif($tspec =~ m/^([0-9]+):([0-5][0-9])$/) { + ($hr, $min, $sec) = ($1, $2, 0); + } elsif($tspec =~ m/^{(.*)}$/) { + $fn = $1; + $tspec = eval $fn; + if(!$@ && $tspec =~ m/^([0-9]+):([0-5][0-9]):([0-5][0-9])$/) { + ($hr, $min, $sec) = ($1, $2, $3); + } elsif(!$@ && $tspec =~ m/^([0-9]+):([0-5][0-9])$/) { + ($hr, $min, $sec) = ($1, $2, 0); + } else { + $tspec = "" if(!$tspec); + return ("the at function must return a timespec HH:MM:SS and not $tspec.", + undef, undef, undef, undef); + } + } else { + return ("Wrong timespec $tspec: either HH:MM:SS or {perlcode}", + undef, undef, undef, undef); + } + return (undef, $hr, $min, $sec, $fn); +} + +##################################### +sub +CommandAt($$) +{ + my ($cl, $def) = @_; + my ($tm, $command) = split("[ \t]+", $def, 2); + + return "Usage: at " if(!$command); + return "Wrong timespec, use \"[+][*[{count}]] + /cgi-bin/hfm_set.pl?action=setFS20&name=&cmd=on&thinclient=true + On + | + +/cgi-bin/hfm_set.pl?action=setFS20&name=&cmd=off&thinclient=true +Off +

+ + + + + Name:
+ +

+
+ + + + =
+
+ + =
+
+ + =
+
+
+ + + diff --git a/webfrontend/pgm2/FS20.off.gif b/webfrontend/pgm2/FS20.off.gif new file mode 100644 index 0000000000000000000000000000000000000000..c4a2fc94f093f6cae4d7b678f41fc1384404dfa2 GIT binary patch literal 609 zcmZ?wbhEHbhqm5(|<~T#H5M2rL8^nz1{UwCbZ0$&@yFC$CRGQGy8g$ z&Y3=A?u?alCNEtxW$ns2YnRShyLQU@ZF9D6ow51Aip7a*R_1NoP`!3t@9GUp)^1#X z;9%R%eG5*UT6pQ&+)LM1FWq`@`K|+N_io*~bL+-~yZ7$hxnSqHwRWcJJoB^Ox@3+k5T#xqI(#K74lb>G$W)?>&0``u`vX zia%KxxftphbQpjD6ekSq`x@$-n!TIbnyihxj8){?6)ml-t<8JnTI-D`+B@1>oAgRI zsax7Px;opMt0_qf&a`rLcXze5F;$V5?Ao}>RnA7aS8}SAz3U3y?T2StT01&BIXRdr z@2Xcdwz9orBikz_QSaPkE@y6jRZgl|rOU)*hn#G`ii)I)vYbVGz1)AnXJ0>mKgi6& z>%^vZ@&cn%TVwsCm;)CcTsF?Z>Luoa6F*R0IjxS@LOy57|rmaN^l{=mVuo%ef4}$s{qz6--~Xo`Q2ZzAT$GwvlA5AW zo>`Ki5R#Fq;O^-gz@Ye(g^`QFobazu?A8p8oxP$Cb0HJluIVdw!Le zd|ItDL^RFy_J)j~r`vp^)ld2u?cDiz)3(3Qs!p9*E1i41`DLb3cyMB)b3v=GhipiQ zqDN8h1banq@9+uJ$|u`P&6AoVASx-yCNz1`5=Nd0qWnVA+>9Io6BKwvxH&i{%kYQ^ La_(_*WUvMRn_1ST literal 0 HcmV?d00001 diff --git a/webfrontend/pgm2/README b/webfrontend/pgm2/README new file mode 100644 index 000000000..5a1921e95 --- /dev/null +++ b/webfrontend/pgm2/README @@ -0,0 +1,20 @@ +See the main doc (fhem.html) for the first documentation and installation. + +If you want to show anly a part of your devices on a single screen +(i.e divide them into separate rooms), then assign each device the +room attribute in the config file: + + attr ks300 room garden + attr ks300-log room garden + +The attribute of the FHZ device will be used as title on the first +screen, which shows the list of all rooms. Devices in the room +"hidden" will not be shown. Devices without a room attribute go +to the room "misc". + +To configure the absicondir and relicondir correctly, look into the +httpd.conf file (probably /etc/httpd/conf/httpd.conf), and check the +line which looks like: + Alias /icons/ "/home/httpd/icons/" +relicondir will then be /icons, and absicondir /home/httpd/icons. + diff --git a/webfrontend/pgm3/config.php b/webfrontend/pgm3/config.php new file mode 100644 index 000000000..3b6253c0b --- /dev/null +++ b/webfrontend/pgm3/config.php @@ -0,0 +1,142 @@ + global" + $fhz1000port="7072"; # port of fhz1000.pl + $logpath="/var/tmp"; # where are your logs? + +################################################################################## +###### nice to have + + +###### showgnuplot + # Gnuplot will automatically show the pictures. + # There is no reason any more to deactivate Gnuplot. Values: 0/1 + $showgnuplot=1; + $gnuplot='/usr/bin/gnuplot'; # location of gnuplot + $pictype='png'; + +##### logrotate of hms, ks300, fht + # this is only possible, if the webserver (e.g.wwwrun) has the rights ro write the + # files from fh1000.pl. If you want that then run fhz1000.pl as wwwrun too. + # if 'yes' then only the needed lines are in the logfiles, the rest will be deleted. + $logrotate='no'; # yes/no default='no' + + +## Kioskmode. Only show but don't switch anything. Values: on/off + $kioskmode='off'; + + + +## HMS-Devices + $imgmaxxhms=620; #Size of the pictures. Default: 620 for faster systems else 380 + $imgmaxyhms=52; + $logrotateHMSlines=1200; # automatic Logrotate; $logrotate must be 'yes'. + # Default:1200 + # read docs/logrotate if you want adjust it manually! + # otherwise the system will slow down + # pgm3 (user www-data) needs the rights to write the logs + # from fhz1000.pl (user = ???) + +## FHT-Devices + $imgmaxxfht=450; #Size of the pictures Default: 450 for faster systems else 380 + $imgmaxyfht=52; + $logrotateFHTlines=4300; # automatic Logrotate; $logrotate must be 'yes'. + # Default:4300 + # read docs/logrotate if you want adjust it manually! + # otherwise the system will slow down + # pgm3 (user www-data) needs the rights to write the logs + # from fhz1000.pl (user = ???) + + +## FS20-Device, adjust it if you have e.g. long titles + $imgmaxxfs20=85; #Size of the pictures, default=85 + $imgmaxyfs20=85; # default=85 + $fs20fontsizetitel=10; # default=10 + $fs20maxiconperline=9; # default=9 + #room. Write e.g. "attr rolowz room wzo" + #into your fhz1000.cfg and restart fhz1000.pl + # this will be marked on the FS20-Button. + $txtroom=""; # default=""; example: $txtroom="room: "; + # room hidden will not be shown + + +## ROOMS adjust it if you have e.g. long titles + $showroombuttons=1; #default 1 Values 0/1 + $imgmaxxroom=$imgmaxxfs20; #Size of the pictures, default=$imgmaxxfs20 + $imgmaxyroom=30; # default=30 + $roomfontsizetitel=10; # default=9 + $roommaxiconperline=$fs20maxiconperline; # default=$fs20maxiconperline + + + + +## KS300-Device + $imgmaxxks=620; #Size of the pictures Default: 620 for faster systems else 380 + + $imgmaxyks=52; + $showbft=1; # Display values additionaly in Beafort. Values: 0 /1 Default:1 + + $logrotateKS300lines=2000; # automatic Logrotate; $logrotate must be 'yes'. + # Default:1900 + # read docs/logrotate if you want adjust it manually + # otherwise the system will slow down + # pgm3 (user www-data) needs the rights to write the logs + # from fhz1000.pl (user = ???) + + +## FHZ-DEVICES + $show_general=1; #field to type FHZ1000-orders 0/1 Default:1 + $show_fs20pulldown=1; #Pull-Down for the FS20 Devices 0/1 Default:1 + $show_fhtpulldown=1; #Pull-Down for the FHT-Devices 0/1 Default:1 + + + + +## misc + $taillog=1; #make shure to have the correct rights. Values: 0/1 + $taillogorder="/usr/bin/tail -20 $logpath/fhz1000.log"; + + + +## show Information at startup. Activate it by deleting the '#' + $showLOGS='no'; #show the LOGS at startup. Default: no Values: yes/no + $showAT='yes'; #show the AT_JOBS at startup. Default: yes Values: yes/no + $showNOTI='no'; #show the NOTIFICATIONS at startup. Default: no Values: yes/no + $showHIST='yes'; #show the HISTORY (if taillog=1) at startup. Default: yes Values: yes/no + + + + $urlreload=60; # Automatic reloading page [sec]. Default fast: 60 slow:90 + $titel="PHP-Webmachine for fhz1000.pl :-)"; #feel free to create an own title + $timeformat="Y-m-d H:i:s"; + $bodybg="bgcolor='#F5F5F5'"; + $bg1="bgcolor='#6E94B7'"; + $bg2="bgcolor='#AFC6DB'"; + $bg3="bgcolor='#F8F8F8'"; + $bg4="bgcolor='#6394BD'"; + $fontcolor1="color='#FFFFFF'"; + + $fontcolor3="color='143554'"; + $fontcol_grap_R=20; + $fontcol_grap_G=53; + $fontcol_grap_B=84; + $fontttf="Vera"; + $fontttfb="VeraBd"; ##copyright of the fonts: docs/copyright_font + ## if there is now graphic try the following: + # $fontttf="Vera.ttf"; + # $fontttfb="VeraBd.ttf"; + # or absolut: + # $fontttf="/srv/www/htdocs/fhz/include/Vera.ttf"; + # $fontttfb="/srv/www/htdocs/fhz/include/VeraBd.ttf"; + +############################### end of settings + putenv('GDFONTPATH=' . realpath('.')); + +?> diff --git a/webfrontend/pgm3/docs/README b/webfrontend/pgm3/docs/README new file mode 100644 index 000000000..00d464a13 --- /dev/null +++ b/webfrontend/pgm3/docs/README @@ -0,0 +1,4 @@ +All the Documentation of pgm3 is now only online +http://www.martin-haas.de/fhz + +Enjoy it :-) diff --git a/webfrontend/pgm3/docs/copyright_fonts.txt b/webfrontend/pgm3/docs/copyright_fonts.txt new file mode 100644 index 000000000..e651be1c4 --- /dev/null +++ b/webfrontend/pgm3/docs/copyright_fonts.txt @@ -0,0 +1,124 @@ +Bitstream Vera Fonts Copyright + +The fonts have a generous copyright, allowing derivative works (as +long as "Bitstream" or "Vera" are not in the names), and full +redistribution (so long as they are not *sold* by themselves). They +can be be bundled, redistributed and sold with any software. + +The fonts are distributed under the following copyright: + +Copyright +========= + +Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream +Vera is a trademark of Bitstream, Inc. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the fonts accompanying this license ("Fonts") and associated +documentation files (the "Font Software"), to reproduce and distribute +the Font Software, including without limitation the rights to use, +copy, merge, publish, distribute, and/or sell copies of the Font +Software, and to permit persons to whom the Font Software is furnished +to do so, subject to the following conditions: + +The above copyright and trademark notices and this permission notice +shall be included in all copies of one or more of the Font Software +typefaces. + +The Font Software may be modified, altered, or added to, and in +particular the designs of glyphs or characters in the Fonts may be +modified and additional glyphs or characters may be added to the +Fonts, only if the fonts are renamed to names not containing either +the words "Bitstream" or the word "Vera". + +This License becomes null and void to the extent applicable to Fonts +or Font Software that has been modified and is distributed under the +"Bitstream Vera" names. + +The Font Software may be sold as part of a larger software package but +no copy of one or more of the Font Software typefaces may be sold by +itself. + +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL +BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, +OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT +SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. + +Except as contained in this notice, the names of Gnome, the Gnome +Foundation, and Bitstream Inc., shall not be used in advertising or +otherwise to promote the sale, use or other dealings in this Font +Software without prior written authorization from the Gnome Foundation +or Bitstream Inc., respectively. For further information, contact: +fonts at gnome dot org. + +Copyright FAQ +============= + + 1. I don't understand the resale restriction... What gives? + + Bitstream is giving away these fonts, but wishes to ensure its + competitors can't just drop the fonts as is into a font sale system + and sell them as is. It seems fair that if Bitstream can't make money + from the Bitstream Vera fonts, their competitors should not be able to + do so either. You can sell the fonts as part of any software package, + however. + + 2. I want to package these fonts separately for distribution and + sale as part of a larger software package or system. Can I do so? + + Yes. A RPM or Debian package is a "larger software package" to begin + with, and you aren't selling them independently by themselves. + See 1. above. + + 3. Are derivative works allowed? + Yes! + + 4. Can I change or add to the font(s)? + Yes, but you must change the name(s) of the font(s). + + 5. Under what terms are derivative works allowed? + + You must change the name(s) of the fonts. This is to ensure the + quality of the fonts, both to protect Bitstream and Gnome. We want to + ensure that if an application has opened a font specifically of these + names, it gets what it expects (though of course, using fontconfig, + substitutions could still could have occurred during font + opening). You must include the Bitstream copyright. Additional + copyrights can be added, as per copyright law. Happy Font Hacking! + + 6. If I have improvements for Bitstream Vera, is it possible they might get + adopted in future versions? + + Yes. The contract between the Gnome Foundation and Bitstream has + provisions for working with Bitstream to ensure quality additions to + the Bitstream Vera font family. Please contact us if you have such + additions. Note, that in general, we will want such additions for the + entire family, not just a single font, and that you'll have to keep + both Gnome and Jim Lyles, Vera's designer, happy! To make sense to add + glyphs to the font, they must be stylistically in keeping with Vera's + design. Vera cannot become a "ransom note" font. Jim Lyles will be + providing a document describing the design elements used in Vera, as a + guide and aid for people interested in contributing to Vera. + + 7. I want to sell a software package that uses these fonts: Can I do so? + + Sure. Bundle the fonts with your software and sell your software + with the fonts. That is the intent of the copyright. + + 8. If applications have built the names "Bitstream Vera" into them, + can I override this somehow to use fonts of my choosing? + + This depends on exact details of the software. Most open source + systems and software (e.g., Gnome, KDE, etc.) are now converting to + use fontconfig (see www.fontconfig.org) to handle font configuration, + selection and substitution; it has provisions for overriding font + names and subsituting alternatives. An example is provided by the + supplied local.conf file, which chooses the family Bitstream Vera for + "sans", "serif" and "monospace". Other software (e.g., the XFree86 + core server) has other mechanisms for font substitution. + diff --git a/webfrontend/pgm3/docs/gnuplot/README b/webfrontend/pgm3/docs/gnuplot/README new file mode 100644 index 000000000..db98e65cd --- /dev/null +++ b/webfrontend/pgm3/docs/gnuplot/README @@ -0,0 +1,5 @@ +Gnuplot is now automatically supported for HMS, KS300 and FHT. + +If you really want to change the graphics then look at the include/gnuplot.php + +###################################################################### diff --git a/webfrontend/pgm3/docs/logrotate/README b/webfrontend/pgm3/docs/logrotate/README new file mode 100644 index 000000000..8aaecffc0 --- /dev/null +++ b/webfrontend/pgm3/docs/logrotate/README @@ -0,0 +1,10 @@ +If the files are too big the system will be slower than usual. + +In the config.php it is now possible to automatic logrotate the HMS, FHT and KS300-Logs. But it is only possible if the user of the pgm3 (e.g. wwwrun) has the rights to write the logfiles from fhz1000.pl (user=???????) + +If you want do that manually then read the enclosed examples for your crontab. + +Every Linux-System normally has a logrotate-Daemon. Put the fhz1000.logrotate in your /etc/logrotate.d/ to lograte the fhz1000.cfg and edit it or use the builtin method: "logfile /tmp/fhz1000-%Y-%m.log" + + + diff --git a/webfrontend/pgm3/docs/logrotate/fhz1000.logrotate b/webfrontend/pgm3/docs/logrotate/fhz1000.logrotate new file mode 100644 index 000000000..3aa2dbdce --- /dev/null +++ b/webfrontend/pgm3/docs/logrotate/fhz1000.logrotate @@ -0,0 +1,11 @@ +/var/tmp/fhz1000.log { + rotate 6 + size 2M + compress + missingok + notifempty + postrotate + [[ `ps -ef | grep fhz1000.pl | grep -v grep` ]] && \ + /etc/init.d/fhz1000 restart + endscript +} diff --git a/webfrontend/pgm3/docs/logrotate/logrotate.fht.sh b/webfrontend/pgm3/docs/logrotate/logrotate.fht.sh new file mode 100755 index 000000000..36c07ec7c --- /dev/null +++ b/webfrontend/pgm3/docs/logrotate/logrotate.fht.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# this script should be installed in e.g. /etc/crontab: +# 0 3 * * * root /usr/local/bin/logrotate.wz.sh > /dev/null 2>&1 +# then there are only about 5 days in the logfile for the FHT-Device + +logs="adi bao bau leo wz wzo" + +for fht in $logs +do +newlogs="0" + + # first time + [[ ! -f /var/tmp/$fht.log.main ]] && cp -p /var/tmp/$fht.log /var/tmp/$fht.log.main + + cat /var/tmp/$fht.log | while read line; + do + [[ "$newlogs" = "1" ]] && echo $line | egrep -v 'measured-temp: [0-2]\.' >>/var/tmp/$fht.log.main + [[ "$line" = "NEWLOGS" ]] && newlogs="1" + done; + # 4500 for about 5 days + tail -4500 /var/tmp/$fht.log.main >/var/tmp/$fht.log + echo "NEWLOGS" >>/var/tmp/$fht.log + +done; diff --git a/webfrontend/pgm3/docs/logrotate/logrotate.hms100.ks300.sh b/webfrontend/pgm3/docs/logrotate/logrotate.hms100.ks300.sh new file mode 100755 index 000000000..dc030d212 --- /dev/null +++ b/webfrontend/pgm3/docs/logrotate/logrotate.hms100.ks300.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# this script should be installed in e.g. /etc/crontab: +# 0 3 * * * root /usr/local/bin/logrotate.hms100.ks300.sh > /dev/null 2>&1 +# then there are only about 4 days in the logfile for the HMS/KS300-Device + +logpath=/var/tmp +logs="heating laundry ks300" + + + +for dev in $logs +do +newlogs="0" + + # first time + if [ ! -f $logpath/$dev.log.main ]; then cp -p $logpath/$dev.log $logpath/$dev.log.main; fi; + + cat $logpath/$dev.log | while read line; + + do + if [ "$newlogs" = "1" ]; + then echo $line >>$logpath/$dev.log.main; fi; + if [ "$line" = "NEWLOGS" ]; then newlogs="1"; fi; + + done; + # 1900 for about 5 days + tail -1900 $logpath/$dev.log.main >$logpath/$dev.log + echo "NEWLOGS" >>$logpath/$dev.log + +done; diff --git a/webfrontend/pgm3/docs/misc/99_ALARM.pm b/webfrontend/pgm3/docs/misc/99_ALARM.pm new file mode 100755 index 000000000..9a860ee70 --- /dev/null +++ b/webfrontend/pgm3/docs/misc/99_ALARM.pm @@ -0,0 +1,150 @@ +############################################## +# Low Budget ALARM System +############################################## +# ATTENTION! This is more a toy than a professional alarm system! +# You must know what you do! +############################################## +# +# Concept: +# 1x Signal Light (FS20 allight) to show the status (activated/deactivated) +# 2x Sirene (in/out) (FS20 alsir1 alsir2 ) +# 2x PIRI-2 (FS20 piriu pirio) +# 1x Sender (FS20 alsw) to activate/deactivate the system. +# Tip: use the KeyMatic CAC with pin code or +# optional a normal sender (FS20 alsw2) +# +# Add something like the following lines to the configuration file : +# notifyon alsw {MyAlsw()} +# notifyon alsw2 {MyAlswNoPin()} +# notifyon piriu {MyAlarm()} +# notifyon pirio {MyAlarm()} +# and put this file in the /FHZ1000 directory. +# +# Martin Haas +############################################## + + +package main; +use strict; +use warnings; + +sub +ALARM_Initialize($$) +{ + my ($hash, $init) = @_; + $hash->{Type} = "none"; +} + + +############################################## +# Switching Alarm System on or off +sub +MyAlsw() +{ + my $ON="set allight on; setstate alsw on"; + my $OFF="set allight off; set alsir1 off; set alsir2 off; setstate alsw off"; + + if ( -e "/var/tmp/alertsystem") + { + unlink "/var/tmp/alertsystem"; + #Paranoia + for (my $i = 0; $i < 2; $i++ ) + { + fhz "$OFF"; + }; + Log 2, "alarm system is OFF"; + } else { + system "touch /var/tmp/alertsystem"; + #Paranoia + for (my $i = 0; $i < 2; $i++ ) + { + fhz "$ON" + } + Log 2, "alarm system is ON"; + }; +} + + +############################################## +# If you have no Keymatic then use this workaround: +# After 4x pushing a fs20-button within some seconds it will activate/deactivate the alarm system. +sub +MyAlswNoPin() +{ + +my $timedout=5; + +## first time + if ( ! -e "/var/tmp/alontest1") + { + for (my $i = 1; $i < 4; $i++ ) + { + system "touch -t 200601010101 /var/tmp/alontest$i"; + } + } + + +## test 4 times + my $now= `date +%s`; + for (my $i = 1; $i < 4; $i++ ) + { + my $tagx=`date -r /var/tmp/alontest$i +%s`; + my $testx=$now-$tagx; + + if ( $testx > $timedout ) + { + system "touch /var/tmp/alontest$i"; + die "test$i: more than $timedout sec"; + } + } + system "touch -t 200601010101 /var/tmp/alontest*"; + Log 2, "ok, let's switch the alarm system..."; + +#if you only allow to activate (and not deactivate) with this script: + # if ( -e "/var/tmp/alertsystem") { die "deactivating alarm system not allowed"}; + + MyAlsw(); +} + + + + +############################################## +# ALARM! Do what you want! +sub +MyAlarm() +{ + + #alarm-system activated?? + if ( -e "/var/tmp/alertsystem") + { + + my $timer=180; # time until the sirene will be quiet + my $ON1="set alsir1 on-for-timer $timer"; + my $ON2="set alsir2 on-for-timer $timer"; + + + #Paranoia + for (my $i = 0; $i < 2; $i++ ) + { + fhz "$ON1"; + fhz "$ON2"; + } + Log 2, "ALARM! #################" ; + + + # have fun + my @lights=("stuwz1", "stuwz2", "nachto", "nachtu", "stoliba" ,"stlileo"); + my @rollos=("rolu4", "rolu5", "roloadi", "rololeo", "roloco", "rolowz", "rolunik1", "rolunik2"); + + foreach my $light (@lights) { + fhz "set $light on" + } + + foreach my $rollo (@rollos) { + fhz "set $rollo on" + } + } +} + +1; diff --git a/webfrontend/pgm3/docs/misc/99_FHTswitch.pm b/webfrontend/pgm3/docs/misc/99_FHTswitch.pm new file mode 100644 index 000000000..0881d0a4d --- /dev/null +++ b/webfrontend/pgm3/docs/misc/99_FHTswitch.pm @@ -0,0 +1,80 @@ +############################################## +# Switching the FHTs with e.g. a Button +############################################## + +# This is only an example. + +# After pressing e.g. a button it will change the values +# of desired-temp against the value of windowopen-temp + +# With small changes it is possible to change e.g. all FHTs to +# 2 degrees less if you are leaving the house and activating +# the alarm system. + +# Don't change the values very often within a time because the FHT seems +# to hang if there are too much values within a time. +# +# Add something like the following lines to the configuration file : +# notifyon NameOfButton {MyFHTswitch("NameOfFHT")} +# and put this file in the /FHZ1000 directory. + + +# Martin Haas +############################################## + + +package main; +use strict; +use warnings; + + + +sub +FHTswitch_Initialize($) +{ +# my ($hash) = @_; ## for fhz1000 >3.0 +# $hash->{Category} = "none"; ## for fhz1000 >3.0 + + my ($hash, $init) = @_; ## for fhz1000 <=3.0 + $hash->{Type} = "none"; ## for fhz1000 <=3.0 +} + + + +###### ok, let's learn perl ;-)... +sub +MyFHTswitch($) +{ + my $str; + my $fhtorder; + my @a = @_; + my $fht=$a[0]; + my @windowopentemp; + my @desiredtemp; + + no strict "refs"; + $str = &{$devmods{$defs{$a[0]}{TYPE}}{ListFn}}($defs{$a[0]}); + my @lines = split("\n", $str); + use strict "refs"; + + foreach my $l (@lines) { + my ($date, $time, $attr, $val) = split(" ", $l, 4); + if($attr eq "desired-temp") + { + Log 3, "old $attr of $a[0]: $val"; + @desiredtemp = split(" ", $val); + } + if($attr eq "windowopen-temp") + { + Log 3, "old $attr of $a[0]: $val"; + @windowopentemp = split(" ", $val); + } + } + $fhtorder="set $fht desired-temp $windowopentemp[0] "; + fhz "$fhtorder"; + $fhtorder="set $fht windowopen-temp $desiredtemp[0] "; + fhz "$fhtorder"; + fhz "set $fht refreshvalues"; +} + +1; diff --git a/webfrontend/pgm3/docs/misc/README b/webfrontend/pgm3/docs/misc/README new file mode 100644 index 000000000..4a61dc6e5 --- /dev/null +++ b/webfrontend/pgm3/docs/misc/README @@ -0,0 +1,4 @@ +These are some Modules for the fhz1000.pl. +Read the docu from fhz1000 howto intall them. + +They are intended as an example. diff --git a/webfrontend/pgm3/include/FS20.off.gif b/webfrontend/pgm3/include/FS20.off.gif new file mode 100644 index 0000000000000000000000000000000000000000..c4a2fc94f093f6cae4d7b678f41fc1384404dfa2 GIT binary patch literal 609 zcmZ?wbhEHbhqm5(|<~T#H5M2rL8^nz1{UwCbZ0$&@yFC$CRGQGy8g$ z&Y3=A?u?alCNEtxW$ns2YnRShyLQU@ZF9D6ow51Aip7a*R_1NoP`!3t@9GUp)^1#X z;9%R%eG5*UT6pQ&+)LM1FWq`@`K|+N_io*~bL+-~yZ7$hxnSqHwRWcJJoB^Ox@3+k5T#xqI(#K74lb>G$W)?>&0``u`vX zia%KxxftphbQpjD6ekSq`x@$-n!TIbnyihxj8){?6)ml-t<8JnTI-D`+B@1>oAgRI zsax7Px;opMt0_qf&a`rLcXze5F;$V5?Ao}>RnA7aS8}SAz3U3y?T2StT01&BIXRdr z@2Xcdwz9orBikz_QSaPkE@y6jRZgl|rOU)*hn#G`ii)I)vYbVGz1)AnXJ0>mKgi6& z>%^vZ@&cn%TVwsCm;)CcTsF?Z>Luoa6F*R0IjxS@LOy57|rmaN^l{=mVuo%ef4}$s{qz6--~Xo`Q2ZzAT$GwvlA5AW zo>`Ki5R#Fq;O^-gz@Ye(g^`QFobazu?A8p8oxP$Cb0HJluIVdw!Le zd|ItDL^RFy_J)j~r`vp^)ld2u?cDiz)3(3Qs!p9*E1i41`DLb3cyMB)b3v=GhipiQ zqDN8h1banq@9+uJ$|u`P&6AoVASx-yCNz1`5=Nd0qWnVA+>9Io6BKwvxH&i{%kYQ^ La_(_*WUvMRn_1ST literal 0 HcmV?d00001 diff --git a/webfrontend/pgm3/include/Vera.ttf b/webfrontend/pgm3/include/Vera.ttf new file mode 100644 index 0000000000000000000000000000000000000000..58cd6b5e61eff273e920942e28041f8ddcf1e1b5 GIT binary patch literal 65932 zcmdSC33yaR)<0Zz>)zY@nsoN1vlF(2gndgBNFXdBLRb|{$O1t~ViMNKut@^41ca~) zQ2_xF5g81KJAw$z=m0v5IF5?TyfVl*%#1>E`Ty$P?kuP?@AEzX?|Ht@raQN5JzJe~ z>eQ*0P(p|UA0n}j9-EYM?BUx5gnU@tBt8%AS5MEcEGIg=C>@6H=IOH*6q~CC z{T}r<2zlZ+GYV(V?|v)FN(jCZ*RUBy`Guc8{>%qxpNoQ?Gf-g9(3fHUk@y}vV|LYi z!V;FBa=Wf%KNM%$>as^vz}P>v%JqH5@cBJ zeYP0Whxb`W@{IrV zKI=(XNTv7LM3Tdv_C8yj@y6=GW#tPhN~X`Ka(5_5bf+XIr@E&taHp44RaR9L<K-&}mU|3uRp}m6R9RFpx2UjdOB?t2qKbU?*!u4R!KooDG==toyl87Ct|QdcYbAM zSwTrY=5rU870j7kR9cl^#o;L~nN?Kj?!ZS>JGjS|6<5v6uPBO6R3U-jR+JUaDJW8h zDJ%g?N~X=JDpFzKGqiN*>@F!Sm^G)6Lo%lgcXGl||q^T9*J+FZ%aQ&2hxApcy9gl1`my-i)%@ zKZljGp?FS3DJBF((6O-0U0K%IT{&mk%%XxSUZT->)~vF59HD};(!vr>u*$xip}9aN ze_GkxA{7Tsc2y8s1fjI73XA}QIAEMFDrlMvXm#$&8TmkKT9KD-0HmbU&5K$wEh~j& zRJdoCRj3leVQPoCyJ|ssQE@&d>goflef{kG1$>6tWrZchC0y9@XH`M`@PJ|S3ky~3 zRXX#@%kwJ$^_*Gx6)O6LMU^GfOI4CX!Isa!Q-vy}`2`rHlK1dIRO!BNCQa%JHKOIu za{uB0-abA!T1NwTrLz{eOWKJ#Xi!naHLc1q{!r-#DLHR^OQZ;LSEKZScfz1C8SbpH?wm2B$7c=67~+l|G#1~ZJG&=jR8|K1Wx7XYj2S!(BM(Z?8kvs{unTbIMxpM}M$;}!(Zsedb z?woOBaz>BMz!*a?Y<5<5<`~S9F)9N{V4%UHb0&?+8agbuGdks>u(LaN%%C9|qXvx` z(V0UyI(Jyc7`NJ_E1<*}?u_xg^Vng7Mvio+XXTE~9g{I=6mN^B?xESEM{ydB%N{Z) zH*0jZJ3Rxa3`!r#3jrIbFnHvktWllaLk5i+G?b&`n}j#>qSHza-eG7)cE*@NBRjjt z=41@c;t!x>)|iaJfEF!5dr$(U7-{h6?6DaSj6(t1`KACvhGnRD0D(dHH&}&CML!$p z@^NxUj{!lvpiIabo6*@lXiU~v&XLS9qX91GCwg!k$AO+`nw9N^m-C31@w)cXfmXb? zmx@C&293mk5R&Ylw^ijUV}3zVIaXYyZ;@+CQdOv$7KM?*%G8trq0}0}B5u-w6p%#xO@Wh{Oj7YQ4K3Ux z9c`*eCEgXJh~$&mq%%shNGaNP#nT`%3okbr(=t}2`mG3kiqK~+J`2(E=i|7^c(p}7 z+Ktqvb5&t94rQsz|8jM-O79G17_|y@C8*`^>1sZC9~M;@lh07B_T%!yM=Vg=&4%o0qx(kStu@$Z;co$Ya#`U0JCJCS*)m47Dxth@ zp*kMNy$tP3FrJ2=8#TOS4(Q59;jmVrUZYPjp18blXgZ)=gRyl6E{B{8Rb(Feae3!6 zw$g-`l%u>1v&>Q9)ab;aDa6>?Dk%Yt=3opCzi$p74nLoPkIv~(0LbR3qi9r}hf?0V zOdZRO+7jTz%i3b(8^3iWbKEoz&QWQ|$M>L95A`hM^l&=1^)<*NV|Rl^(M(&wro6w;GCp zVFl>Rxx@L*d8N(BC52;Brs7?xQeq}r6rkSM#y1a_V~%ebB*Q1Q9CI#-oF|%uRbrd( zTcNq?Y@BY>(2i@tRz9?H%STr}A77{KH9{$R^0E1f;8bX(m~XwbQmw5XXxoot$k(^V zt!XM8ZRJg)2ruE||2j`Ot{exA|FhM<+IOzCe02JCj`KDPRK6Bt9u1?eKcm)v>d$pP zw@4Ze90E>zzNUSejl<8^9bc!KuG669bmf%w@xE1_wYA6Pjjwl&)^jil|JI5X@5{C9 zbkLwx%BQ0p$7qJPjQ8;AQjVbp32(1a_kJ4jn*WSbE5|hqS|yER>IOXjTL{|Eb3Z*= zG4;{EQe6|A=X?f^L0c~K)xdSDCX<}nZk6Vxpc~gOK03S6N-N^46lzQPd8(Whsxw9Zf^CdOPmRYu>iT-Pp}T#)Lp1z?w(C-}H6t-&TU*2Bimz#o zfd(&^1Wsq)x|@sIk~Y}+<}4!fRc>>vcJLE{S7 z_HK0rbC@`c+^%uSX)ph+P-@uyk{;)LnSpc$xqYbBtP-g)%pMyD_L44E8LW(Tn52+mFIK*9&Pb%3Eh`4;3F-n~y^_ z3g5OTH47t*Lofb~myW~V9JCvY zUK$*nejM6tw9UpCW7NMxQO_aJIHA#MFk0ncZr)-j;L25@;4^XTcuNjdF6sw?BD_DJ zb%a`~LB?sqxy)f{9fj|b_}m&Coc`mz<8c|__>aVk)0We5tU5ymN=Kng8&@0E4X8LK z9Bz#ovY4EY$mj&p_6b7V_Pjc%GOaGnlAi%}}%yg$c;Q>0ZI+G64xtvz>s zNjiMe#>e7(b`BTv`e5&*h3s{$OCxDsh_Jb9(#QYEteoQlS+KKGp=46RrHvIKUy~a=~Zx(X5sGd`=Ft4<0VfT*`cWXr&5Ye_Y1+Ok4{1 zH$DSjBV5Kfmw26TeQI;~_&84O>l>B#YcKs=%J@3+we$7+Pr5^+k#BB3b}Q~&S~)E> z2sxKEYW(+cTeW=#Y#g_io z-?r^qOF3ovZiw5j);$n!>$A^4-#c?mwMYeT*VYsEc_W%PsqK}xebnIR9uoK2HJ_0C zewvq}`5N3S*LK-_H=ylQeY+UGJLI;x{r;~KFmgYDL!r&(v;VDQ@x2$1WpK}d&&DaN zLBnU$sQI64?fpAOzEkDhYm;ziO+tQ0Sbd156^WzR_CrG0q!Vebk~a*jljM*10uc#{2< zrLt4v5Yb9LV;9*$@)c$gG5&c{NA{3vz~WEK$YP;d7=x0t(nYczuQJqMq`T-PKzEWZ zCs)W;CJMvIE_wxcohSby%UQ0l80Yn=LNVY!i?J@E|8`O-66p#x5=H2QGC+^Hrm3Id ztc!F-ecd99F>@~2BR9(ax){vDDYlQkLvP3%NdvjW9%7HOPv{CUM%*tBBXt@DSRSdv z*xPv@xtJ~h?)+8FM;GRadGsLptC**ohOyt}7-8mP!WdvwOitlFPqqW6esl#}1xR^q zIJu}BE+(NrM$jz+)`XO?9%Lq-s>xw;lyqU6NgYN~@s)c?|3c55;^)A*j;ld`H$iLSPa1_Ko_lu{cE_Ln6vuu{VgKID{$*wVRM>5W{UeV3U}b;b%x=Z8@1GbX zeXp>ao7vwsvm1BVcX!zTDD1C&*|+KJ8-;zH!oIpbR{Cl)yN-s}$FeWKNRqz1!@fvj zpDXMy3i~XD{n?*=x|v;5*e6c*r$y}QtL%>o`v}cHTEwng9x7c~#4ZnIm;MkcT~gQ| zLfMB3`#@p8SJ>|qc5ySia6Ur1ps@21?EMsWPGM(OIHWUS?A-u%T4C=f>}`d;rLZ>@ z_J+bsdldHUGgj%@6!wgjJzdBe(4=8A+pVx&Pno4%3VX`TcJ2t4b{4W7+wIbhV7A@P zwi(%0g>Bhvk+vvovxU{8Q~hSPX`@xz)PZfZvM2Ab4eMW(HYjX;-4tp4t8D!ev2Iu1l(pV+$3wKw|v66+F1`1>mI>UEi9#*NlH;zHxo-vGD*o6mSkdGyBMUdcGktfI;XHs z9pj`wX0$d3Fq6WJc4knR9?kR$)A=*Gkcp@iAptIiQl>Bg--RxW+8I$8 zZKQ=O*3wS@fB295e;UZ}zOV50lINl{%o-}lvR*SU|7oFkS6 z?#6rfawdwQ(xf9&*bx?|KO)A(eEw^dpLgjzB4?ueNOQ&z@2DAhLr^w$A|}8;UX0l? zhIE1HA;rpOu~^!Jye1t9@tDQCM7~S)(qcg*NvAL0=tk_9Z(P2S?B|Gb#6>xxibc{? z$wHgHQa0r+=iLPN7jO%0#35QdyJ>k9f!UsqY?9eo=Uf zfy$@3G;YWY8e7sZo%U9q9zzEzJ7zRYS3a5k^bF-)nwP7*PD_f}3gsxPRr2X>C4ake zbel4b?&9xlGq490k;GDHwE^;eI49Mx_;yIIW@ozf2^>2>A zJ}rO5zfFn;GYwpBl2tz90N2Y$l$*h1d+viHj@VRAqXv@2k9a+*WYHMbl_vCvpn;CA zv`6=zy?Ug&@Wq8fM+9~G%R1(;;%`8pV<76|g=2-Zz7+@CHKPB}bw?28Y5 z`O%jj6;>^L^z+3_tCdT%i_oRZG0z}M--|u8`Poy}@4giyLtpIJRaC~s9NT%|9UIaU zw_9dT9G`bZ8SN;YJQ1mr5_$CAm%2ph7BL~?F@_|-Tdw!?jJ3tZ$Hm(cViVHIljevg zyRHp-GFE=lyf)ssrbFz8?g>$$aRz2_Sq&Cjl%TYj3edG2G`^|sdT(X zb?VjKyHA`HHf(x)S$+Mo<@JlNz541WpS*hN6CuBT+2flwJ-&4F;-CH@TRwU9wLg7w z>f|-P?v~#BQc^%M14*VAJ)14mYOZlO9i|$i$?0?$YKXxV;L=f9UlS1E5-6iJ;Su4a z#y}z>!rhTVRD{FmXT-8(LH-UuqfRf#28W-YQJ?}NT9pvwLJeyDjOk93fyu-e!8*9C za)$)DKB!ZD!lu{_L2Imj#;zu-fpm4c608xdt1}_W>abx|Iz#Q<>`jp8%Qx(2G+scS zxk&Tne&+hWzJ`q3&u}S+hzEK_9GsCf32*nO-50z5XX}8Mw3JSYK59#$bc*Mw&Ll+} z62nLsjT8b+9Z5$T@9ayuJBOI2l1X&3ah!8<$mGaL$rES7^#S$K z+qy&=Oa`;wVNNi22ogdK!KPqyup`Vr%oPwGnUX*fXrdv;+0n0~e+O4mNb&xLl>AAIyRDxbc;|g?bPkm@78ZO z>@aONuTN=6Ig-+63YkLHB?lSnWuOCTuT)vk(U=4)jfp0FjjAg(H6?&A(->9k=noH$ zyWH^bzAUAhHuX!FPnu^;p@B_xGp;ZHyYjo5n&gx}H;&yqZo;l1CCmGnQB$iJx2OC zS&E&YAd28DHzqgQn-Wo7F4)ofOo_!K-XRhH{^7lLeQ* zGcYDz=+WKTOQ^0{wtPjy=K4)rWarn)z;C`$`hE2sJ@c2(=;<4PV-MgcQ{jk&mF95h zC^0!jKcrgQul2v(3Wr~6fYaqK=wf<0dvq7}V95H-4J(!}mz_71{-6Ct>HFPR^xbd1 zp>Jc<0m5+h4%VoHWP3W>EhZwG4LT9Vm~E3B=50o5-Qd)ljm#iB7-a(Sw}~c$zeRT1 zFZaKmat&{;{JD9w-@XjHefkCp@I9GIk}eJgSxShD>m|V_h{NV?8=c-)IZ~k<=}V_8 z+xpU+3YsH+_Vzo|&MUQa!TD+Lyj^gfE>LRE1G1}7x}QiQ^lgmCK@4=Kj!A+`B!NcR zr8nEJHNh5hdvqCpPbX6cOfB~TdPF(cVWCU&rTxv9;0ue*mk#oWgNS)hvg@9czC#pf z^I(se?IO!%c+SBjNCx{ZU(mSNE7b*)ee2SmrDK#s%A1sXI)(HzVX?3rHrH{S>=Z;w zMEf<~o;z2VxKIdf{z_QBhs(<+_&AI?(DoIwT;RiNqL_3e8DqzMa_N$ypdGoFE*w>* zwu{G~gixrp5Jp(Kup0s_5XzEHtAYgqRxN9bL4fWS^aq=NgpB?)o9o%ydtZumKFj3s zlN+3*!Mwq_Cdd$Gi(p}{&>*09n=gjz-0CFLXu)B3rl!Ez5fV~}!%nbn@hPm{`P5VR z_taB&sX_Vo-Mh-asX@w7E-DxBzDQH?>P}M|luD&WsZ}cJTDpKPq-#0WpW_C@WME?? zBRsBj)*uQE(o!91Fz6%YFgRY+1X`WuD>CUu%5CnH0x8uoP?v^DT^c4ZTQmE|Y|JJK zQ+h=?q#kjpoVN-c4)G~^pAK)@b5N`t);R3Wm4kfd&6s&Oun!}9Jqf`fp)4rO0kLsN zl9+CP+Of&f;J-mc1dP~WIgDX}b|#0z0AIfG=9{YRRpDtvWL1x=kh$QR1b9s@mT$Pa ztiwsTPjjS<6UR&AbqmFX(%jJ6U>%f7uowbQKdg$(mFI+1hE|0wBQ?RxLY9Rt3)@fj zhdQ7;JdeD2&LD%y$t|2wJ$$@=*Rxy4zFtvzZqnD(ypF|1o?idy4{>qtbW7P>_jvujdF7SW zvGK>;?hlVX_B^D%5PaVQi4&li*LcFIg;@w=mUO~Qx(4iCmKvzpNWx^jXoh~g+#i}r zHS5>8nrd-Z&%w(&r*hi_6g7|Pe*Nv~Xd)ePTr&xw*Lma#q6?s%NIdPtdeUq<+C17a zo)*(NbRk2n?e}7KY839HC0C{q#+~nE7f3pA@*_ zkmUAkicr}UK_c$6LHLeYQSM!6TzkQDPE8>$Y$Vz;j`QnN7Tny>d1B`~G*-E+d_VP_ z8I#|9l_zaB<>vqVUHPZmeZE`r@tr%5$HsGwR0pg!s~RbmO!UP1 z$;47)CJg~{Ls-CGdxLpZ^oFoCapq`4Sa5`27>kMwjf0AU3|?22)b*z8e0QOtAVbj9E}jBV5il_p{1&)Aut~%F>bEVqEZ5cJu7$bUWqp~jNCEuy-T)! zM<4l|O3JM-lxF27&7q+qcd&jZpLzP#SD$|7q_ChdHeUHb`F_F_<@@ixR{lp-antDD z2+phhkhmG(l}rjeL6SpY0&|GaG7|X2Bt~HtWF0n(r&W(2sf|wYdGZ0=4bZ8q!9_CP z3UW>qsLVp7KGHC0Iy*v+$U2A-I74G-)PDA6^B0$>(wr(?8GmP~gdHs-t3lt@Dt%+H z^Be4m3j%c$7f40FYX*$mMCFaoxyQ0(Ne?Klk!0K)o~y85jT zgr^NL#0sb4;NpQ{m#hB<=l=%6!6%Y+!_4>Vg*RS8VSJ}I4!@WO$rfgXH}-+P8_SiWrI#%0Sl2=8vMt=+z(rgr;y_t7OUfAGP}OOCpu&(vN0_S>siVb8{KxBh`L%^CiU07I@Uj&Jc4zs8Ng9YHT zYF{h=^vO%W>EO3R-VA*+?9K4EBTh%^4mwXc|LSCrm|m(@a{754Rg$VnNpw6_cS}GE zJEzY_?i>L*>3ek6UzEGl{ss0W4&^1~tC2hDK(8!CLQ1HGI>$dmZQp%O15|^!TX`@- z*y58Uj?*m&%{yWY_@yIZ9;>`u+y{q14Xgwq*a0=fts%sOy9Hcf+`5GS6h(|t&|CFY z)ZPXX=kbI0q1z=cC;PAwl4!7q`)}$Hs@rnCiQ9EQZ5Y*ixy1b!4Agwp=fhkjQ>9M; zfsDvYM`0%u8QqC%c>Iq*C0QanWhq?}5!{m4e)%~a6-cZY19?XL2dnb-4e$Pk@9=$l z8NRnS2rk-#N}t^QQPkg2B!S&hHYgj9(+~I24>=XC(md%C_KcSb7PwFHP7x@GB!&~= zG>G7hQb85*7Y?BKICm8G%>G*kvF=(SAMNQR?<8>An6wj+`{8rlQ12=$|;UN}-BpM^AB`ib?17}Hmh+mxj8XO&LDfuendq=** zPrCUp<@QbcMHF%8nD6DG3gT2%5J%#?s^Itn!$RXiw-!h9i@};p!~O~z`4;2J*Q5>G zFCBJZwD$b@ci-qed2*lB<+Db=oImxg>5ZQan>;ZoK`+aSLN{zLS~h-CkEz`zm1Yh; z)u;E{yGO1XKR&5Pu&aM}&Y4MB9oRP~I7YZVBu#EvcDopp~@uU)@zL7foQf5-Gg zAOG?B={x(?J-Ii{Gefy@r231zr(UX@T|)hzTKdzB$%~Y$TTdvBOP18E{LNB2=C#Z8 zk?IknmA92|h2Xkp_pDp9caJh`RMt=Ly?1BC$mPxMfX`lf$T%__@c$Nha0ASU9J42d?0iB+xe|r)q^pTlb%8R-ZIRIz&%&$ zFft=?2=Hi(I=HhkFEluqQO_&jSwr5cbnNJeD}^QIt-?08Sq#+t9c&C@7^0lQDdnaRr&NC>^!dZe=7(2ak*v+Z?C_mVbg{A& zE9o38=nY`3$9~fdyA=~m>Wzka=Tcg4d?C_d(hGjUkrJ_n1xUeRT@576DMoPx#FrCy zPx(UPZi4-0pX8&qXuytrpQgK89^zp2x#3b>(U>T@kq&wGsi&S*PSH-AHf-3Wm;~{g zJ4+s`->clZ+x)F?uKCm2)oWG=#md04ibu=$z4_9rXZ+pgx4!o$Xr4+$uo9pHf=N$L zh~;VPVPn06K1~jbSpJSRA-Z4-N%psga1gzQh{N`;o5{y)p^>2iz~g?2*B9y8%LNhk zIVMs<@i)uv5#<)OQ?l%v;+cPYTzNrRNNecWn!icYt~@+dIjj6pxvHF<`tYS;!{}}b zKG5AmAvd6+bi_-=t{xYuH-LV2yo_vbv++F2BRBDqQ~hSU3>xNLLC|=j}NVxO<266HdEVyW6rV3&E-N)^O5)Yn8OY> z_u_sV=OXu(!bu;Gn@FLwo`u%yoliRsyXvhQ^lKsn66WYGrUnI@>~OGeG+l4P6nwJ` zZYq~m6&9yP7NA{6YC$oQAp7Po-;TkH5ZNcmYQvMufM+ zq}~Qx@Yl!+^npC0E(kXr%~7d}-7%so>gmV1_k};d|9*2cuy5We6yE8?Daw#d$H5dvapi9M`O5nGZaEi_gq?M7hC50jjGA4A{iMCiTEO0hbk z3EqUCNg%p<=?GbBmh^HTAF$U|9}}(#Hvzs`%<3#={DOd2-CL3^9!riT&r)aEZBb{j z%icZXx%V%AIV!ED6jN?gez<*b^V?orq?y3QNWS-U&^zF{=o~VPKX=7d-I=b36T--g z1{qFY(o>^pv{mhYFd} zVEs5@x-eImCoLCNN_F~8!Vdj6f(zPGGRUDUSSLX@>w;JZsgvAM*Hi2%^^|+)lFfsd zN6e5svPb7JPh)x5LrmArlgiDj*=lK>T&JruZ)Z=*Pw9@c-|F6F@9I8gAL+hje-*!# z{zv{d`%(Hy?mXpDGUZWlfJR|=iL)+ndKVR&Ls^LOujW+F?^VLQ=3z}=3cqje=B1Lz zsU*R7H1j1Y(lFMSh&-^f2g+(26QGawNtu zlQ%rwnM0@72@Wdg`5z`2j0PAfqaod>6PO<4)|+6Ba5gF#gp1)c~UkfwqIUPd}l1)`En zbwZffQwJQmMp7l5>- z&!iCft9O!mE%Fy^OJ%_>JCFRSVQ^pMk8g{y*~e#srpeS#mT*mJrtI1^N|k%pXkR*C zS*e^+-sMqQX{6Gqe5HJ?G}2)-goe^#dz1&2T?+O)bPt_|*IvygiEBYIJ^!5$PY~=8 zH%m^tQIE4|Sfw-vH%tBi2dYaG2{j7nG1**^t~A%ft`}VrH|O495v({uVqz!oi*8ib zZr{FE=}q6e%i+7Lye}m+|NhC^nkV;t`N^kWH1Fq>P=54MBAkrzbVOv+M$Hzpm0B$3 zbX$a3B~1{5qLv6ts12TOaHvWkRo`&s zoYKHeU4We2PN3EvIW)lf^F(kdO<(9oB_dG?4xmnS5f}9r0$8Ak{Rxc|;#qLMYxhye!r@l#vQJ4ck8J7X&d1#xM&?BjHbv? z9f=MNwsz44`$u=c<_s(1IyPl0U0~(C=dNd3)KlB@YY@iEO_6)x zWqBmM{W9WYP&>D^YzZSb;y+82@FRvuVuu2W)Y*|TQEu36FihcT37j{w_uBE0@5Xa}j)oR?G#DgK6 z#Od44M*7wH?e=5bx@bE&Xf%Z7uxO5+Km5+yhtDgYL9u+Ldce0_=&z<5R`H2!HF+mp<0_9 zJ(u=rgmq*?#i7zlzYrpZNF5R4jTaKdL@7>o>w6QNehB@={!%X) zS14$PkR@i}*O(@e@p7?HB9=%C$y{ub7KjU^Ir0)c&gbMrtcEC>YQXMD7~Xv561__Q z^oQoN(BXmNU%3~BYXL;J57ai(YEPCFB1^EUVu;beLXgNI;7ka495Oe&SoxCI@WOYZ z4*dL7x)E-U40~kKn@vW8Udvc9>4?RC*_*F|B$Zz_xh*?E%@RY%iE4p=kOf&1kk>t5atqH`e3u&>K3CUx9rxr^)ZH6W1PutbzA!jeOV7NRZ7

B-* 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 ZpvJ#@o~VO count ($resultreverse) ) + { $_SESSION["maxdata"] = count ($resultreverse); } + else + { $_SESSION["maxdata"] = $imgmaxxfht+1; }; + + + ################### + ### min/max + $mintemp=100; + $maxtemp=-100; + for ($x = 0; $x < $_SESSION["maxdata"]; $x++) + { + if ( $resultreverse[$x][2] > $maxtemp ) $maxtemp=$resultreverse[$x][2]; + if ( ($resultreverse[$x][2] < $mintemp) and ($resultreverse[$x][2]>-100) ) $mintemp=$resultreverse[$x][2]; + } + $tempdiff=$maxtemp-$mintemp; + if ($tempdiff==0) $tempdiff=1; + $fac=$imgmaxyfht/$tempdiff; + $yold=round($imgmaxyfht-(($resultreverse[0][1]-$mintemp)*$fac)); + ################### + + + + for ($x = 0; $x < $_SESSION["maxdata"]; $x++) + + { + $parts = explode("_", $resultreverse[$x][0]); + if ( ($parts[0] != $olddate) ) + { + $olddate=$parts[0]; + ImageLine($im, $imgmaxxfht-$x, 0,$imgmaxxfht-$x , $imgmaxyfht, $bg1p); + }; + $y = round($imgmaxyfht-(($resultreverse[$x][2]-$mintemp)*$fac)); + ImageLine($im, $imgmaxxfht-$x, $y, $xold, $yold, $red); + $xold=$imgmaxxfht-$x; + $yold=$y; + }; + + #print_r($resultreverse); + #exit; + ImageLine($im, $imgmaxxfht-$x, 0,$imgmaxxfht-$x , $imgmaxyfht, $yellow); +###ttf + $text="Temperature"; + $fontsize=7; + $txtcolor=$bg3p; + ImageTTFText ($im, $fontsize, 0, 5, 12, $txtcolor, $fontttf, $text); + #setlocale (LC_ALL, 'de_DE.UTF-8'); + $text=$resultreverse[0][2]." °C"; + ImageTTFText ($im, 9, 0, 90, 35, $txtcolor, $fontttfb, $text); + + $text= $drawfht; + ImageTTFText ($im, 8, 0, 90, 18, $txtcolor, $fontttfb, $text); + $txtcolor=$bg3p; + $fontsize=7; + $text="min= $mintemp max= $maxtemp"; + ImageTTFText ($im, $fontsize, 0, 67, 47, $txtcolor, $fontttf, $text); + + $text=$txtroom.$room; + ImageTTFText ($im, $fontsize, 0, 5, $imgmaxyfht-7, $txtcolor, $fontttf, $text); + + $text="desired-temp: $desired_temp"; + ImageTTFText ($im, $fontsize, 0, $imgmaxxfht-230, 23, $txtcolor, $fontttf, $text); + + $text=$desired_date; + ImageTTFText ($im, $fontsize, 0, $imgmaxxfht-127, 23, $txtcolor, $fontttf, $text); + + $text="Actuator [%]: $actuator"; + ImageTTFText ($im, $fontsize, 0, $imgmaxxfht-230, 33, $txtcolor, $fontttf, $text); + + $text=$actuator_date; + ImageTTFText ($im, $fontsize, 0, $imgmaxxfht-127, 33, $txtcolor, $fontttf, $text); + + $text=$resultreverse[0][0]; + ImageTTFText ($im, $fontsize, 0, $imgmaxxfht-127, 13, $txtcolor, $fontttf, $text); + + header("Content-type: image/png"); + imagePng($im); + + + +function show_error($file,$drawfht,$imgmaxxfht,$imgmaxyfht) +{ + $im = ImageCreateTrueColor($imgmaxxfht,$imgmaxyfht); + $black = ImageColorAllocate($im, 0, 0, 0); + $bg2p = ImageColorAllocate($im, 175,198,219); + $white = ImageColorAllocate($im, 255, 255, 255); + $red = ImageColorAllocate($im, 255, 0, 0); + + ImageFill($im, 0, 0, $bg2p); + ImageRectangle($im, 0, 0, $imgmaxxfht-1, $imgmaxyfht-1, $white); + imagestring($im, 3, 5, 5, "Error, there is no $file", $black); + imagestring($im, 1, 3, 25, "Please add the following to your fhz1000.cfg", $black); + $logname=$drawfht."log"; + imagestring($im, 1, 3, 35, "define $logname FileLog $file $drawfht:.*(temp|actuator|desired).*", $black); + header("Content-type: image/png"); + imagePng($im); + exit; +} + +?> diff --git a/webfrontend/pgm3/include/fhtpulldown.php b/webfrontend/pgm3/include/fhtpulldown.php new file mode 100644 index 000000000..65baa2e14 --- /dev/null +++ b/webfrontend/pgm3/include/fhtpulldown.php @@ -0,0 +1,95 @@ + + FHT: +

"; + # + # + # + echo" + + + + "; + + + + + echo" + set + + "; + + #$order="$atorder $attime set $fhtdev $orderpulldown $valuetime"; + echo ""; + +?> diff --git a/webfrontend/pgm3/include/fs20.ico b/webfrontend/pgm3/include/fs20.ico new file mode 100644 index 0000000000000000000000000000000000000000..cf6d7dff8d16530b0423b33c7ea9708f0dc4658d GIT binary patch literal 1406 zcmeH^%TE(g6o-GPDEKPkh;IcSfPf;XQ0t2-wF0HW!vDd&Zdj4crZyORp5tL}7n=Isz-n&C z>wY7K`rFa+?k&8Q4*0Acun%>?&3D7kcS7QgkZhd@*k2+@1U_^_BK!^$f`otAga9!< z+>0^iD+Gi-n4LUC!HmZq7e+r0Lh=m2C62%``3c@hADkf(BVR=DpC{l5O~5iU1z~m? ze0T=7a2UdmdAJwm@P080KC%c~B!Zq5hTsB&M7X0X7>_WRe=&$Z87$Nrqv(q=c$XLk z7=ve-!A|`U)ve?_VJ8Htxd;d4Q*_=<`7}i*`B$3#M)5nbzSdvhe|!N>UYXmrr)9)4 z$ouY`)Qu@SlD=#6&JEa-bT0k))=i2vezdGCFQ!~+}Ek!HZSp z<(Esd<$`>^Ky$6~wo-R?U&4#Es@nQmmF7^~PoBT2s;R5F{@b}?O@*>jc}tz>b48b` oE3WEpoZ6F^&pLfYtJjtsBaPo@?}6OH!~11T+%J~K{a^ptDz)_JLjV8( literal 0 HcmV?d00001 diff --git a/webfrontend/pgm3/include/fs20.php b/webfrontend/pgm3/include/fs20.php new file mode 100755 index 000000000..b3f3f5203 --- /dev/null +++ b/webfrontend/pgm3/include/fs20.php @@ -0,0 +1,91 @@ + diff --git a/webfrontend/pgm3/include/fs20pulldown.php b/webfrontend/pgm3/include/fs20pulldown.php new file mode 100644 index 000000000..fa7f93a1a --- /dev/null +++ b/webfrontend/pgm3/include/fs20pulldown.php @@ -0,0 +1,80 @@ + + FS20: +
+ + + + + + + + "; + + + + + echo" + set + + +
"; + +?> diff --git a/webfrontend/pgm3/include/functions.php b/webfrontend/pgm3/include/functions.php new file mode 100644 index 000000000..3dbb4290d --- /dev/null +++ b/webfrontend/pgm3/include/functions.php @@ -0,0 +1,41 @@ += 118.5) { $bft= 12; } + elseif($windspeed>= 103.7) { $bft= 11; } + elseif($windspeed>= 88.9) { $bft= 10; } + elseif($windspeed>= 75.9) { $bft= 9; } + elseif($windspeed>= 63.0) { $bft= 8; } + elseif($windspeed>= 51.9) { $bft= 7; } + elseif($windspeed>= 40.7) { $bft= 6; } + elseif($windspeed>= 29.6) { $bft= 5; } + elseif($windspeed>= 20.4) { $bft= 4; } + elseif($windspeed>= 13.0) { $bft= 3; } + elseif($windspeed>= 7.4) { $bft= 2; } + elseif($windspeed>= 1.9) { $bft= 1; } + else $bft= 0; + return($bft); +} + + +?> diff --git a/webfrontend/pgm3/include/gnuplot.php b/webfrontend/pgm3/include/gnuplot.php new file mode 100644 index 000000000..ade8d23a8 --- /dev/null +++ b/webfrontend/pgm3/include/gnuplot.php @@ -0,0 +1,109 @@ + diff --git a/webfrontend/pgm3/include/hms100.php b/webfrontend/pgm3/include/hms100.php new file mode 100755 index 000000000..1f342954f --- /dev/null +++ b/webfrontend/pgm3/include/hms100.php @@ -0,0 +1,366 @@ + count ($resultreverse) ) + { $_SESSION["maxdata"] = count ($resultreverse); } + else + { $_SESSION["maxdata"] = $imgmaxxhms; }; + + ################### + ### min/max + $mintemp=100; + $maxtemp=-100; + for ($x = 0; $x <= $_SESSION["maxdata"]; $x++) + { + if ( $resultreverse[$x][1] > $maxtemp ) $maxtemp=$resultreverse[$x][1]; + if ( ($resultreverse[$x][1] < $mintemp) and ($resultreverse[$x][1]>-100) ) $mintemp=$resultreverse[$x][1]; + } + $tempdiff=$maxtemp-$mintemp; + if ($tempdiff==0) $tempdiff=1; + $fac=$imgmaxyhms/$tempdiff; + $yold=round($imgmaxyhms-(($resultreverse[0][1]-$mintemp)*$fac)); + ################### + + + for ($x = 0; $x <= $_SESSION["maxdata"]; $x++) + + { + $parts = explode("_", $resultreverse[$x][0]); + if ( ($parts[0] != $olddate) ) + { + $olddate=$parts[0]; + ImageLine($im, $imgmaxxhms-$x, 0,$imgmaxxhms-$x , $imgmaxyhms, $bg1p); + } + $y = round($imgmaxyhms-(($resultreverse[$x][1]-$mintemp)*$fac)); + ImageLine($im, $imgmaxxhms-$x, $y, $xold, $yold, $red); + $xold=$imgmaxxhms-$x; + $yold=$y; + }; + ImageLine($im, $imgmaxxhms-$x, 0,$imgmaxxhms-$x , $imgmaxyhms, $yellow); + $tempTEMP=$temp; +}; #HMS100T + + +if ( $type == "HMS100TF") ## hms100tf-Device. +{ +#Humidity... + + #$oldmin=0; //only the data from every 10min + $min=100; + $max=-100; + + for ($x = 0; $x <= $_SESSION["maxdata"]; $x++) + { + $temp=$resultreverse[$x][2]; + if ( $temp > $max ) $max=$temp; + if ( $temp < $min ) $min=$temp; + } + $temp=$resultreverse[0][2]; + $tempdiff=$max-$min; + $fac=$imgmaxyhms/$tempdiff; + + + $xold=$imgmaxxhms; + $yold=round($imgmaxyhms-(($resultreverse[0][2]-$min)*$fac)); + + for ($x = 0; $x < count($resultreverse); $x++) + { + $y = round($imgmaxyhms-(($resultreverse[$x][2]-$min)*$fac)); + ImageLine($im, $imgmaxxhms-$x, $y, $xold, $yold, $white); + $xold=$imgmaxxhms-$x; + $yold=$y; + }; + + $text="Humidity"; + $fontsize=7; + $txtcolor=$white; + ImageTTFText ($im, $fontsize, 0, 5, 23, $txtcolor, $fontttf, $text); + $txtcolor=$white; + + $fontsize=9; + $text=$temp." %"; + $hvalue=$temp; + ImageTTFText ($im, $fontsize, 0, 210, 35, $txtcolor, $fontttfb, $text); + + $txtcolor=$white; + $fontsize=7; + $text="min= $min max= $max"; + ImageTTFText ($im, $fontsize, 0, 182, 47, $txtcolor, $fontttf, $text); + + +# Taupunkt +# $tp = Taupunkt($tvalue,$hvalue); +# $fontsize=9; +# $text=$tp." °C"; +# ImageTTFText ($im, $fontsize, 0, 350, 35, $bg1p, $fontttfb, $text); +# $txtcolor=$orange; +# $fontsize=7; +# $text="Taupunkt"; +# ImageTTFText ($im, $fontsize, 0, 350, 47, $bg1p, $fontttf, $text); +}; + + + +############################################################################# +if ( $type == "HMS100T" or $type == "HMS100TF" ) +{ + + $text="Temperature"; + $fontsize=7; + $txtcolor=$bg3p; + ImageTTFText ($im, $fontsize, 0, 5, 12, $txtcolor, $fontttf, $text); + $txtcolor=$bg3p; + $fontsize=9; + $text=$tempTEMP." °C"; + $tvalue=$tempTEMP; + ImageTTFText ($im, $fontsize, 0, 80, 35, $txtcolor, $fontttfb, $text); + + $txtcolor=$bg3p; + $fontsize=7; + $text="min= $mintemp max= $maxtemp"; + ImageTTFText ($im, $fontsize, 0, 62, 47, $txtcolor, $fontttf, $text); + $text=$resultreverse[0][0]; + ImageTTFText ($im, $fontsize, 0, $imgmaxxhms-127, 13, $txtcolor, $fontttf, $text); +}; +############################################################################# +## general + $txtcolor=$bg3p; + $fontsize=9; + $text= $drawhms; + ImageTTFText ($im, 8, 0, 80, 18, $txtcolor, $fontttfb, $text); + $fontsize=7; + $text=$txtroom.$room; + ImageTTFText ($im, $fontsize, 0, 5, $imgmaxyhms-7, $txtcolor, $fontttf, $text); + $text=$type; + ImageTTFText ($im, $fontsize, 0, 5, $imgmaxyhms-17, $txtcolor, $fontttf, $text); + + +############################################################################# +if ( $type == "HMS100WD" or $type == "HMS100MG" or $type == "HMS100W" + or $type == "HMS100TFK" or $type=="RM100-2") +{ + for ($x = 0; $x < $counter; $x++) + { + if ( $type=="RM100-2" ) + + {list ($date,$hms,$detect,$onoff) = preg_split("/[\s,]+/", $array[$x]);} + else + {list ($date,$hms,$kind,$detect,$onoff) = preg_split("/[\s,]+/", $array[$x]);}; + if ($x!=$counter-1 and $date !="NEWLOGS") + { + array_push( $_SESSION["arraydata"],array($date,$onoff)); + } + } + + $resultreverse = array_reverse($_SESSION["arraydata"]); + $xold=$imgmaxxhms; + + if ( $imgmaxxhms > count ($resultreverse) ) + { $_SESSION["maxdata"] = count ($resultreverse); } + else + { $_SESSION["maxdata"] = $imgmaxxhms; }; + + for ($x = 0; $x < $_SESSION["maxdata"]-1; $x++) + + { + $parts = explode("_", $resultreverse[$x][0]); + if ( ($parts[0] != $olddate) ) + { + $olddate=$parts[0]; + ImageLine($im, $imgmaxxhms-$x, 0,$imgmaxxhms-$x , $imgmaxyhms, $bg1p); + } + $y = round($imgmaxyhms/2); + $isonoff=rtrim($resultreverse[$x][1]); + if ( $isonoff == "off" ) + { ImageLine($im, $imgmaxxhms-$x, $y, $imgmaxxhms-$x,$y, $white);} + else + { ImageLine($im, $imgmaxxhms-$x, $y-1, $imgmaxxhms-$x,$y+1, $red); + }; + + $xold=$imgmaxxhms-$x; + $yold=$y; + }; + ImageLine($im, $imgmaxxhms-$x, 0,$imgmaxxhms-$x , $imgmaxyhms, $yellow); + + if ($type=='HMS100WD' or $type=='HMS100W'){$text="Water detected:";} + elseif ($type=='HMS100MG'){$text="Gas detected:";} + elseif ($type=='RM100-2'){$text="Smoke detected:";} + else {$text="Switch open:";} + $fontsize=7; + $txtcolor=$bg3p; + ImageTTFText ($im, $fontsize, 0, 180, 18, $txtcolor, $fontttf, $text); + + if ($isonoff == "off" ) + {ImageTTFText ($im, 9, 0, 265, 18, $white, $fontttf,'no');} + else + {ImageTTFText ($im, 9, 0, 265, 18, $red, $fontttfb,'YES');} + + $txtcolor=$bg3p; + $fontsize=7; + $text=$resultreverse[0][0]; + ImageTTFText ($im, $fontsize, 0, $imgmaxxhms-127, 13, $txtcolor, $fontttf, $text); + +}; + +#ok. let's draw + + header("Content-type: image/png"); + imagePng($im); + + + +############################################################### +## first start: shows the required logfiles +function show_error($file,$drawhms,$imgmaxx,$imgmaxy,$type) +{ + $im = ImageCreateTrueColor($imgmaxx,$imgmaxy); + $black = ImageColorAllocate($im, 0, 0, 0); + $bg2p = ImageColorAllocate($im, 175,198,219); + $white = ImageColorAllocate($im, 255, 255, 255); + $red = ImageColorAllocate($im, 255, 0, 0); + + ImageFill($im, 0, 0, $bg2p); + ImageRectangle($im, 0, 0, $imgmaxx-1, $imgmaxy-1, $white); + + include "../config.php"; + $bg3p = ImageColorAllocate($im, $fontcol_grap_R,$fontcol_grap_G,$fontcol_grap_B); + $text="There is a new supported $type-Device but no Logfile $file"; + $fontsize=9; + $txtcolor=$bg3p; + ImageTTFText ($im, $fontsize, 0, 5, 17, $txtcolor, $fontttf, $text); + $text="Please add the following to your fhz1000.cfg and restart fhz1000.pl:"; + $fontsize=7; + ImageTTFText ($im, $fontsize, 0, 5, 30, $txtcolor, $fontttf, $text); + $logname=$drawhms."log"; + $fontsize=9; + if ($type=='HMS100WD') + { + $text="define $logname FileLog $file $drawhms:.*Water.*"; + } + elseif ($type=='RM100-2') + { + $text="define $logname FileLog $file $drawhms:.*smoke*"; + } + elseif ($type=='HMS100W') + { + $text="define $logname FileLog $file $drawhms:.*Water.*"; + } + elseif ($type=='HMS100MG') + { + $text="define $logname FileLog $file $drawhms:.*Gas.*"; + } + elseif ($type=='HMS100TFK') + { + $text="define $logname FileLog $file $drawhms:.*Switch.*"; + } + else + { + $text="define $logname FileLog $file $drawhms:.*T:.*"; + } + ImageTTFText ($im, $fontsize, 0, 5, 45, $txtcolor, $fontttf, $text); + + header("Content-type: image/png"); + imagePng($im); + exit; +} + +############################################################### +## supported HMS?? +function show_error_type($imgmaxx,$imgmaxy,$type) +{ + $im = ImageCreateTrueColor($imgmaxx,$imgmaxy); + $black = ImageColorAllocate($im, 0, 0, 0); + $bg2p = ImageColorAllocate($im, 175,198,219); + $white = ImageColorAllocate($im, 255, 255, 255); + $red = ImageColorAllocate($im, 255, 0, 0); + ImageFill($im, 0, 0, $bg2p); + ImageRectangle($im, 0, 0, $imgmaxx-1, $imgmaxy-1, $white); + + include "../config.php"; + $bg3p = ImageColorAllocate($im, $fontcol_grap_R,$fontcol_grap_G,$fontcol_grap_B); + $text="HMS-Device $type is not supported"; + $fontsize=7; + $txtcolor=$bg3p; + ImageTTFText ($im, $fontsize, 0, 5, 12, $txtcolor, $fontttf, $text); + + ImageFill($im, 0, 0, $bg2p); + ImageRectangle($im, 0, 0, $imgmaxx-1, $imgmaxy-1, $white); + + header("Content-type: image/png"); + imagePng($im); + exit; +} + +?> diff --git a/webfrontend/pgm3/include/ks300.php b/webfrontend/pgm3/include/ks300.php new file mode 100755 index 000000000..bbd2b083f --- /dev/null +++ b/webfrontend/pgm3/include/ks300.php @@ -0,0 +1,361 @@ + count ($resultreverse) ) + { $maxdata = count ($resultreverse); } + else + { $maxdata = $imgmaxxks; }; + + + ################### + ### min/max + $mintemp=100; + $maxtemp=-100; + for ($x = 0; $x <= $maxdata; $x++) + { + if ( $resultreverse[$x][1] > $maxtemp ) $maxtemp=$resultreverse[$x][1]; + if ( ($resultreverse[$x][1] < $mintemp) and ($resultreverse[$x][1]>-100) ) $mintemp=$resultreverse[$x][1]; + } + $tempdiff=$maxtemp-$mintemp; + if ($tempdiff==0) $tempdiff=1; + $fac=$imgmaxyks/$tempdiff; + $yold=round($imgmaxyks-(($resultreverse[0][1]-$mintemp)*$fac)); + ################### + + + for ($x = 0; $x <= $maxdata; $x++) + { + $y = round($imgmaxyks-(($resultreverse[$x][1]-$mintemp)*$fac)); + ImageLine($im, $imgmaxxks-$x, $y, $xold, $yold, $red); + $xold=$imgmaxxks-$x; + $yold=$y; + $parts = explode("_", $resultreverse[$x][0]); + if ( ($parts[0] != $olddate) ) + { + $olddate=$parts[0]; + ImageLine($im, $imgmaxxks-$x, 0,$imgmaxxks-$x , $imgmaxyks, $bg1p); + }; + }; + ImageLine($im, $imgmaxxks-$x, 0,$imgmaxxks-$x , $imgmaxyks, $yellow); + if ($mintemp < 0) + { + $y = round($imgmaxyks-((0-$mintemp)*$fac)); + ImageLine($im, $imgmaxxks, $y,0 , $y, $bg1p); + } + $text="Temperature"; + $fontsize=7; + $txtcolor=$bg3p; + ImageTTFText ($im, $fontsize, 0, 5, 12, $txtcolor, $fontttf, $text); + $fontsize=9; + $text=$temp." °C"; + ImageTTFText ($im, $fontsize, 0, 80, 35, $txtcolor, $fontttfb, $text); + $text= $drawks; + ImageTTFText ($im, 8, 0, 80, 18, $txtcolor, $fontttfb, $text); + $fontsize=7; + $text="min= $mintemp max= $maxtemp"; + ImageTTFText ($im, $fontsize, 0, 60, 47, $txtcolor, $fontttf, $text); + $imt=$im; + +#humidity + $im = ImageCreateTrueColor($imgmaxxks,$imgmaxyks); + + + + ImageFill($im, 0, 0, $bg2p); + ImageRectangle($im, 0, 0, $imgmaxxks-1, $imgmaxyks-1, $white); + + $oldmin=0; //only the data from every 10min + $min=100; + $max=-100; + + for ($x = 0; $x <= $maxdata-1; $x++) + { + $temp=$resultreverse[$x][2]; + if ( $temp > $max ) $max=$temp; + if ( ($temp < $min) and ($temp != '')) $min=$temp; + } + $temp=$resultreverse[0][2]; + $tempdiff=$max-$min; + if ($tempdiff==0) $tempdiff=1; + $fac=$imgmaxyks/$tempdiff; + + + $xold=$imgmaxxks; + $yold=round($imgmaxyks-(($resultreverse[0][2]-$min)*$fac)); + + $olddate = ($resultreverse[0][0][9]); + for ($x = 0; $x < count($resultreverse); $x++) + { + $y = round($imgmaxyks-(($resultreverse[$x][2]-$min)*$fac)); + ImageLine($im, $imgmaxxks-$x, $y, $xold, $yold, $red); + $xold=$imgmaxxks-$x; + $yold=$y; + $parts = explode("_", $resultreverse[$x][0]); + if ( ($parts[0] != $olddate) ) + { + $olddate=$parts[0]; + ImageLine($im, $imgmaxxks-$x, 0,$imgmaxxks-$x , $imgmaxyks, $bg1p); + }; + }; + ImageLine($im, $imgmaxxks-$x, 0,$imgmaxxks-$x , $imgmaxyks, $yellow); + $text="Humidity"; + $fontsize=7; + $txtcolor=$bg3p; + ImageTTFText ($im, $fontsize, 0, 5, 12, $txtcolor, $fontttf, $text); + $fontsize=9; + $text=$temp." %"; + ImageTTFText ($im, $fontsize, 0, 80, 35, $txtcolor, $fontttfb, $text); + $fontsize=7; + $text="min= $min max= $max"; + ImageTTFText ($im, $fontsize, 0, 60, 47, $txtcolor, $fontttf, $text); + + $imh=$im; + +#wind + $im = ImageCreateTrueColor($imgmaxxks,$imgmaxyks); + ImageFill($im, 0, 0, $bg2p); + ImageRectangle($im, 0, 0, $imgmaxxks-1, $imgmaxyks-1, $white); + + $oldmin=0; //only the data from every 10min + $min=120; + $max=-100; + + for ($x = 0; $x <= $maxdata; $x++) + { + $temp=$resultreverse[$x][3]; + if ( $temp > $max ) $max=$temp; + if ( $temp < $min and ($temp != '')) $min=$temp; + } + $temp=$resultreverse[0][3]; + $tempdiff=$max-$min; + if ($tempdiff==0) $tempdiff=1; + $fac=$imgmaxyks/$tempdiff; + + + $xold=$imgmaxxks; + $yold=round($imgmaxyks-(($resultreverse[0][3]-$min)*$fac)); + + for ($x = 0; $x < count($resultreverse); $x++) + { + $y = round($imgmaxyks-(($resultreverse[$x][3]-$min)*$fac)); + ImageLine($im, $imgmaxxks-$x, $y, $xold, $yold, $red); + $xold=$imgmaxxks-$x; + $yold=$y; + $parts = explode("_", $resultreverse[$x][0]); + if ( ($parts[0] != $olddate) ) + { + $olddate=$parts[0]; + ImageLine($im, $imgmaxxks-$x, 0,$imgmaxxks-$x , $imgmaxyks, $bg1p); + }; + }; + ImageLine($im, $imgmaxxks-$x, 0,$imgmaxxks-$x , $imgmaxyks, $yellow); + $text="Wind"; + $fontsize=7; + $txtcolor=$bg3p; + ImageTTFText ($im, $fontsize, 0, 5, 12, $txtcolor, $fontttf, $text); + $fontsize=9; + $text=$temp." km/h"; + ImageTTFText ($im, $fontsize, 0, 80, 35, $txtcolor, $fontttfb, $text); + $fontsize=7; + + if ($showbft==1) + { + $text="( ".bft($temp)." Bft)"; + ImageTTFText ($im, $fontsize, 0, 150, 35, $txtcolor, $fontttfb, $text); + $text2="min= $min max= $max (".bft($max)." Bft)"; + } + else + { + $text2="min= $min max= $max"; + } + ImageTTFText ($im, $fontsize, 0, 60, 47, $txtcolor, $fontttf, $text2); + + $imw=$im; + +#rain + $im = ImageCreateTrueColor($imgmaxxks,$imgmaxyks); + ImageFill($im, 0, 0, $bg2p); + ImageRectangle($im, 0, 0, $imgmaxxks-1, $imgmaxyks-1, $white); + + $oldmin=0; //only the data from every 10min + $min=120; + $max=-100; + + for ($x = 0; $x <= $maxdata; $x++) + { + $temp=$resultreverse[$x][4]; + if ( $temp > $max ) $max=$temp; + if ( $temp < $min and ($temp != '')) $min=$temp; + } + $temp=$resultreverse[0][4]; + $israin=rtrim($resultreverse[0][5]); + $tempdiff=$max-$min; + if ( $temdiff == 0 ) $tempdiff=1; + $fac=$imgmaxyks/$tempdiff; + + + $xold=$imgmaxxks; + $yold=round($imgmaxyks-(($resultreverse[0][4]-$min)*$fac)); + + for ($x = 0; $x <= $maxdata; $x++) + { + $parts = explode("_", $resultreverse[$x][0]); + if ( ($parts[0] != $olddate) ) + { + $olddate=$parts[0]; + ImageLine($im, $imgmaxxks-$x, 0,$imgmaxxks-$x , $imgmaxyks, $bg1p); + }; + $y = round($imgmaxyks-(($resultreverse[$x][4]-$min)*$fac)); + ImageLine($im, $imgmaxxks-$x, $y, $xold, $yold, $red); + $israin2=rtrim($resultreverse[$x][5]); + if ( $israin2 == "no" ) + { ImageLine($im, $imgmaxxks-$x, 18, $imgmaxxks-$x,18, $white);} + else + { ImageLine($im, $imgmaxxks-$x, 17, $imgmaxxks-$x,19, $red);}; + + $xold=$imgmaxxks-$x; + $yold=$y; + + }; + ImageLine($im, $imgmaxxks-$x, 0,$imgmaxxks-$x , $imgmaxyks, $yellow); + $fontsize=7; + $text="Is raining:"; + ImageTTFText ($im, $fontsize, 0, 50, 14, $txtcolor, $fontttf, $text); + + if ($israin == "no" ) + { imagestring($im, 5, 110, 2, $israin, $white);} + else + { imagestring($im, 5, 110, 2, $israin, $red);}; + $text="Rain"; + $fontsize=7; + $txtcolor=$bg3p; + ImageTTFText ($im, $fontsize, 0, 5, 12, $txtcolor, $fontttf, $text); + $fontsize=9; + $text=$temp." l/m2"; + ImageTTFText ($im, $fontsize, 0, 80, 35, $txtcolor, $fontttfb, $text); + $fontsize=7; + $text="min= $min max= $max"; + ImageTTFText ($im, $fontsize, 0, $imgmaxxks-130, 30, $txtcolor, $fontttf, $text); + $text=$resultreverse[0][0]; + ImageTTFText ($im, $fontsize, 0, $imgmaxxks-130, 15, $txtcolor, $fontttf, $text); + $text="avg_day: ".$avgday; + ImageTTFText ($im, $fontsize, 0, 70, 47, $txtcolor, $fontttf, $text); + $text="avg_mon: ".$avgmonth; + ImageTTFText ($im, $fontsize, 0, 320, 47, $txtcolor, $fontttf, $text); + $text=$room; + ImageTTFText ($im, $fontsize, 0, 7, 47, $txtcolor, $fontttf, $text); + $imr=$im; + + + + + +# big picture +$imall = ImageCreateTrueColor($imgmaxxks,$imgmaxyks*4); +ImageFill($imall, 0, 0, $bg2p); +ImageCopy ($imall,$imt,0,0,0,0,$imgmaxxks,$imgmaxyks); +ImageCopy ($imall,$imh,0,$imgmaxyks,0,0,$imgmaxxks,$imgmaxyks); +ImageCopy ($imall,$imw,0,$imgmaxyks*2,0,0,$imgmaxxks,$imgmaxyks); +ImageCopy ($imall,$imr,0,$imgmaxyks*3,0,0,$imgmaxxks,$imgmaxyks); + + +header("Content-type: image/png"); +imagePng($imall); + + + + + + + + +function show_error($file,$draw,$imgmaxx,$imgmaxy) +{ + $im = ImageCreateTrueColor($imgmaxx,$imgmaxy*4); + $black = ImageColorAllocate($im, 0, 0, 0); + $bg2p = ImageColorAllocate($im, 175,198,219); + $white = ImageColorAllocate($im, 255, 255, 255); + $red = ImageColorAllocate($im, 255, 0, 0); + + ImageFill($im, 0, 0, $bg2p); + ImageRectangle($im, 0, 0, $imgmaxx-1, $imgmaxy-1, $white); + imagestring($im, 3, 5, 5, "Error, there is no $file", $black); + imagestring($im, 1, 3, 25, "Please add the following to your fhz1000.cfg", $black); + $logname=$draw."log"; + imagestring($im, 1, 3, 35, "define $logname FileLog $file $draw:.*H:.*", $black); + header("Content-type: image/png"); + imagePng($im); + exit; +} + +?> diff --git a/webfrontend/pgm3/include/room.php b/webfrontend/pgm3/include/room.php new file mode 100755 index 000000000..5b70c8afe --- /dev/null +++ b/webfrontend/pgm3/include/room.php @@ -0,0 +1,48 @@ + diff --git a/webfrontend/pgm3/include/style.css b/webfrontend/pgm3/include/style.css new file mode 100644 index 000000000..05391af4c --- /dev/null +++ b/webfrontend/pgm3/include/style.css @@ -0,0 +1,51 @@ + + + +"; +?> diff --git a/webfrontend/pgm3/index.php b/webfrontend/pgm3/index.php new file mode 100644 index 000000000..8721f43f9 --- /dev/null +++ b/webfrontend/pgm3/index.php @@ -0,0 +1,770 @@ +\n"; + } else { + fwrite($fp, "$order\n;quit\n"); + $errormessage= fgets($fp, 1024); + fclose($fp); + } +return $errormessage; +} + + +###### make an array from the xmllist + unset($output); + $stack = array(); + $output=array(); + $fp = stream_socket_client("tcp://$fhz1000:$fhz1000port", $errno, $errstr, 30); + if (!$fp) { + echo "$errstr ($errno)
\n"; + } else { + fwrite($fp, "xmllist\r\n;quit\r\n"); + while (!feof($fp)) { + $outputvar = fgets($fp, 1024); + array_push($output,$outputvar); + } + fclose($fp); + } + + + +# start_element_handler ( resource parser, string name, array attribs ) +function startElement($parser, $name, $attribs) +{ + global $stack; + $tag=array("name"=>$name,"attrs"=>$attribs); + array_push($stack,$tag); +} + +# end_element_handler ( resource parser, string name ) +function endElement($parser, $name) +{ + global $stack; + $stack[count($stack)-2]['children'][] = $stack[count($stack)-1]; + array_pop($stack); +} + + +function new_xml_parser($live) +{ + global $parser_live; + $xml_parser = xml_parser_create(); + xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, 0); + xml_set_element_handler($xml_parser, "startElement", "endElement"); + + if (!is_array($parser_live)) { + settype($parser_live, "array"); + } + $parser_live[$xml_parser] = $live; + return array($xml_parser, $live); +} + +# go parsing +if (!(list($xml_parser, $live) = new_xml_parser($live))) { + die("could not parse XML input"); +} + +foreach($output as $data) { + if (!xml_parse($xml_parser, $data)) { + die(sprintf("XML error: %s at line %d\n", + xml_error_string(xml_get_error_code($xml_parser)), + xml_get_current_line_number($xml_parser))); + } +} + +xml_parser_free($xml_parser); + +#for testing +#print_r($stack);exit; + + +#searching for rooms/fs20 + $rooms=array(); + $fs20devs=array(); + $fhtdevs=array(); + if ($showroombuttons==1) + for($i=0; $i < count($stack[0][children]); $i++) + { + if ($stack[0][children][$i][name]=='FS20_DEVICES') + { + for($j=0; $j < count($stack[0][children][$i][children]); $j++) + { + $fs20devxml=$stack[0][children][$i][children][$j][attrs][name]; + for($k=0; $k < count($stack[0][children][$i][children][$j][children]); $k++) + { + $check=$stack[0][children][$i][children][$j][children][$k][attrs][name]; + if ($check='ATTR') + { + if (($stack[0][children][$i][children][$j][children][$k][attrs][key])=='room') + { + $room=$stack[0][children][$i][children][$j][children][$k][attrs][value]; + if (! in_array($room,$rooms)) array_push($rooms,$room); + } + } + } + if ((! in_array($fs20devxml,$fs20devs)) AND ( $room != 'hidden')) array_push($fs20devs,$fs20devxml); + } + }#FS20 + elseif ($stack[0][children][$i][name]=='FHT_DEVICES') + { + for($j=0; $j < count($stack[0][children][$i][children]); $j++) + { + $fhtdevxml=$stack[0][children][$i][children][$j][attrs][name]; + for($k=0; $k < count($stack[0][children][$i][children][$j][children]); $k++) + { + $check=$stack[0][children][$i][children][$j][children][$k][attrs][key]; + if ( $check=="room") + {$room=$stack[0][children][$i][children][$j][children][$k][attrs][value]; + if (! in_array($room,$rooms)) array_push($rooms,$room); + } + } + + if ((! in_array($fhtdevxml,$fhtdevs)) AND ( $room != 'hidden')) array_push($fhtdevs,$fhtdevxml); + } + } #FHT + elseif ($stack[0][children][$i][name]=='HMS_DEVICES') + { + for($j=0; $j < count($stack[0][children][$i][children]); $j++) + { + for($k=0; $k < count($stack[0][children][$i][children][$j][children]); $k++) + { + if ( $stack[0][children][$i][children][$j][children][$k][attrs][key]=="room") + {$room=$stack[0][children][$i][children][$j][children][$k][attrs][value]; + if (! in_array($room,$rooms)) array_push($rooms,$room); + } + } + } + } # HMS + elseif ($stack[0][children][$i][name]=='KS300_DEVICES') + { + for($j=0; $j < count($stack[0][children][$i][children]); $j++) + { + for($k=0; $k < count($stack[0][children][$i][children][$j][children]); $k++) + { + $check=$stack[0][children][$i][children][$j][children][$k][attrs][name]; + if ($check='ATTR') + { + if (($stack[0][children][$i][children][$j][children][$k][attrs][key])=='room') + { + $room=$stack[0][children][$i][children][$j][children][$k][attrs][value]; + if (! in_array($room,$rooms)) array_push($rooms,$room); + } + } + } + } + } + } # end searching rooms + array_push($rooms,'ALL'); + sort($rooms); + +#print_r($rooms); echo "Count: $countrooms"; exit; +#print_r($fs20devs); exit; + +# Print Array on Screen + + $now=date($timeformat); + + echo " + + + + + + + $titel"; + include ("include/style.css"); + echo " "; + + echo" + $errormessage +
+ + +
+ +
+ + "; + + ############################ FHZ + echo ""; + + + if (($show_general=='1') AND ($showmenu=='1')) + {echo " + + "; + }; + + if (($show_fs20pulldown=='1') AND ($showmenu=='1')) include 'include/fs20pulldown.php'; + if (($show_fhtpulldown=='1') AND ($showmenu=='1')) include 'include/fhtpulldown.php'; + + ############################ ROOMS + if (($showroombuttons==1) and (count($rooms)>1)) + { + echo ""; + echo ""; + } + +##################################################################################################################### + ##### Let's go.... :-))))) + for($i=0; $i < count($stack[0][children]); $i++) + { + ############################ + if ($stack[0][children][$i][name]=='FS20_DEVICES') + { + $type=$stack[0][children][$i][name]; + echo ""; + $counter=0; + echo ""; + } + ############################ + elseif ($stack[0][children][$i][name]=='FHT_DEVICES') + { + $type=$stack[0][children][$i][name]; + echo ""; + for($j=0; $j < count($stack[0][children][$i][children]); $j++) + { + $room=""; + for($k=0; $k < count($stack[0][children][$i][children][$j][children]); $k++) + { + $check=$stack[0][children][$i][children][$j][children][$k][attrs][key]; + if ( $check=="room") + {$room=$stack[0][children][$i][children][$j][children][$k][attrs][value]; + } + } + if (($room != 'hidden') and ($showroom=='ALL' or $showroom==$room)) + { + + $FHTdev=$stack[0][children][$i][children][$j][attrs][name]; + if ($showfht == $FHTdev) + {echo ""; + }; + echo " + "; + + echo ""; + echo ""; + + if ($showfht==$FHTdev and $showgnuplot == 1) + { + drawgnuplot($FHTdev,"FHT",$gnuplot,$pictype,$logpath); + $FHTdev1=$FHTdev.'1'; + echo " + "; + } + for($k=0; $k < count($stack[0][children][$i][children][$j][children]); $k++) + { + if ( $showfht==$FHTdev) + { + $name=$stack[0][children][$i][children][$j][children][$k][attrs][name]; + $value=$stack[0][children][$i][children][$j][children][$k][attrs][value]; + $measured=$stack[0][children][$i][children][$j][children][$k][attrs][measured]; + echo ""; + } + } + } + } + } + ############################ + elseif ($stack[0][children][$i][name]=='HMS_DEVICES') + { + $type=$stack[0][children][$i][name]; + echo ""; + for($j=0; $j < count($stack[0][children][$i][children]); $j++) + { + $room=""; + for($k=0; $k < count($stack[0][children][$i][children][$j][children]); $k++) + { + if ( $stack[0][children][$i][children][$j][children][$k][attrs][key]=="room") + {$room=$stack[0][children][$i][children][$j][children][$k][attrs][value]; + } + if ( $stack[0][children][$i][children][$j][children][$k][attrs][name]=="type") + {$type=$stack[0][children][$i][children][$j][children][$k][attrs][value];}; + } + if (($room != 'hidden') and ($showroom=='ALL' or $showroom==$room)) + { + $HMSdev=$stack[0][children][$i][children][$j][attrs][name]; + if ($type=="HMS100T" or $type=="HMS100TF") + { + if ($showhmsgnu== $HMSdev) + {$formvalue="hide";$gnuvalue="";} + else {$formvalue="show";$gnuvalue=$HMSdev;}; + echo " + "; + + if ($showhmsgnu == $HMSdev and $showgnuplot == 1) + { drawgnuplot($HMSdev,$type,$gnuplot,$pictype,$logpath); + $HMSdev1=$HMSdev.'1'; + echo ""; + } + } + + } + } + ############################ + elseif ($stack[0][children][$i][name]=='KS300_DEVICES') + { + $type=$stack[0][children][$i][name]; + echo ""; + for($j=0; $j < count($stack[0][children][$i][children]); $j++) + { + $KSdev=$stack[0][children][$i][children][$j][attrs][name]; + $room=''; + for($k=0; $k < count($stack[0][children][$i][children][$j][children]); $k++) + { + $check=$stack[0][children][$i][children][$j][children][$k][attrs][name]; + if ($check='STATE') + { + $name=$stack[0][children][$i][children][$j][children][$k][attrs][name]; + $value=$stack[0][children][$i][children][$j][children][$k][attrs][value]; + $measured=$stack[0][children][$i][children][$j][children][$k][attrs][measured]; + if ($name=='temperature') {$KSmeasured=$measured;} + elseif ($name=='avg_month') {$KSavgmonth=$value;} + elseif ($name=='avg_day') {$KSavgday=$value;}; + } + if ($check='ATTR') + { + if (($stack[0][children][$i][children][$j][children][$k][attrs][key])=='room') + { + $room=$stack[0][children][$i][children][$j][children][$k][attrs][value]; + } + } + } + if (($room != 'hidden') and ($showroom=='ALL' or $showroom==$room)) + { + $Xks=$imgmaxxks; + $Yks=$imgmaxyks*4; + ##gnuplot + if ($showks == $KSdev) + {echo "";} + else + {echo ""; + }; + echo ""; + if (($showks == $KSdev) and $showgnuplot=='1') + { + if ($kstyp=="1")drawgnuplot($KSdev,"KS300_t1",$gnuplot,$pictype,$logpath); + else drawgnuplot($KSdev,"KS300_t2",$gnuplot,$pictype,$logpath); + $KSdev1=$KSdev.'1'; + echo ""; + } + + } + } + } + ############################ + elseif ($stack[0][children][$i][name]=='LOGS') + { + echo ""; + if (isset ($showlogs)) + for($j=0; $j < count($stack[0][children][$i][children]); $j++) + { + $name=$stack[0][children][$i][children][$j][attrs][name]; + $definition=$stack[0][children][$i][children][$j][attrs][definition]; + if ($definition != "") + {echo " + "; + } + } + } + ############################ + elseif ($stack[0][children][$i][name]=='NOTIFICATIONS') + { + echo ""; + + if (isset ($shownoti)) + for($j=0; $j < count($stack[0][children][$i][children]); $j++) + { + $event=$stack[0][children][$i][children][$j][attrs][event]; + $command=$stack[0][children][$i][children][$j][attrs][command]; + $measured=$stack[0][children][$i][children][$j][children][0][attrs][measured]; + echo ""; + } + } + ############################ + elseif ($stack[0][children][$i][name]=='AT_JOBS') + { + echo ""; + + + if (isset ($showat)) + for($j=0; $j < count($stack[0][children][$i][children]); $j++) + { + $command=$stack[0][children][$i][children][$j][attrs][command]; + $next=$stack[0][children][$i][children][$j][attrs][next]; + $order=$command; + $order=str_replace("+","\FFF",$order); #workaround + $order=str_replace("*","\*",$order); + $order=str_replace("$","\\$",$order); + $order=str_replace("(","\(",$order); + $order=str_replace(")","\)",$order); + $order=str_replace("{","\{",$order); + $order=str_replace("}","\}",$order); + $order='del at ^'.$order.'$'; + if ($next != '') {$nexttxt='('.$next .')';} else {$nexttxt='';}; + echo ""; + } + } + }; + if ($taillog==1) + { + echo ""; + if (isset ($showhist)) {foreach($tailoutput as $data) echo "";}; + + + }; + echo "

+
$titel +
$now +
+
v$pgm3version
+
+
FHZ_DEVICE"; + if ($showmenu != '1') + { echo "show menu";} + else + { echo "hide menu";} + + echo "
"; + echo "
+ General: +
+ + + + + +
"; + echo "ROOMS "; + echo "
"; + $counter=0; + for($i=0; $i < count($rooms); $i++) + { + $room=$rooms[$i]; + if ($room != 'hidden') + { + echo""; + $counter++; + + if (fmod($counter,$roommaxiconperline)== 0.0) echo "
"; + } else $counter--; + } + echo "
"; + echo "$type
"; + for($j=0; $j < count($stack[0][children][$i][children]); $j++) + { + $fs20=$stack[0][children][$i][children][$j][attrs][name]; + $state=$stack[0][children][$i][children][$j][attrs][state]; + $room=''; + for($k=0; $k < count($stack[0][children][$i][children][$j][children]); $k++) + { + $check=$stack[0][children][$i][children][$j][children][$k][attrs][name]; + if ($check='STATE') + { + $measured=$stack[0][children][$i][children][$j][children][$k][attrs][measured]; + } + if ($check='ATTR') + { + if (($stack[0][children][$i][children][$j][children][$k][attrs][key])=='room') + { + $room=$stack[0][children][$i][children][$j][children][$k][attrs][value]; + } + } + } + if (($state=='on') or ($state=='dimup')) + {$order="set $fs20 off";} + else + {$order="set $fs20 on";}; + if (($room != 'hidden') and ($showroom=='ALL' or $showroom==$room)) + { + $counter++; + echo""; + if (fmod($counter,$fs20maxiconperline)== 0.0) echo "
"; + }; + } + echo "
"; + echo "$type
+
+ + + +
";} + else + {echo "
+
+ + + + +
+ $FHTdev +
desired-temp + + + + + "; + echo " +
+ +

+ +

$FHTdev (FHT): $name$value + $measured
"; + echo "$type
+
+ + + +
"; + + } + else + echo "
"; + echo " $HMSdev

+ +
"; + echo "$type
+
+ + + +
+
+
Temp./Hum.
+

Wind/Rain
+

+ + +
$KSdev "; + echo ""; + echo "

+ +
+ +
LOGS"; + if (! isset ($showlogs)) + { echo "show";} + else + { + echo "hide";} + + echo "
Log:$definition
+ +
NOTIFICATIONS"; + if (! isset ($shownoti)) + { echo "show";} + else + { echo "hide";} + echo "
Notification:$event $command
+ +
AT_JOBS"; + if (! isset ($showat)) + { echo "show";} + else + { echo "hide";} + echo "
AT-Job: del$command $nexttxt
+ +
$taillogorder"; + if (! isset ($showhist)) + { echo "show";} + else + { echo "hide";} + echo "
History$data
"; + +?> diff --git a/webfrontend/pgm4/README b/webfrontend/pgm4/README new file mode 100644 index 000000000..1383b9bc6 --- /dev/null +++ b/webfrontend/pgm4/README @@ -0,0 +1 @@ +Copyright: STefan Mayer diff --git a/webfrontend/pgm4/fs20.php b/webfrontend/pgm4/fs20.php new file mode 100644 index 000000000..23f0c767a --- /dev/null +++ b/webfrontend/pgm4/fs20.php @@ -0,0 +1,240 @@ + + + + + +FS 20 Haussteuerrung + + + += 0)); +} + +function print_options($name, $device, $dimmable, $href) +{ + echo ""; + echo "$name"; + echo "
"; + echo ""; + echo ""; + echo ""; + echo "
"; + echo ""; + echo "
"; +} + + +function generate_random() +{ + $devices=array("dg.gang", "dg.wand", "dg.dusche", "dg.bad", "dg.reduit", "dg.eltern", "dg.kino", "og.gang", "og.bad.links", "og.bad.rechts", "og.bad.sterne", "og.bad.decke", "og.stefan.decke", "og.stefan.pult", "og.sandra.decke", "og.kind.r", "og.kind.l", "eg.sitzplatz", "eg.wohnzimmer", "eg.bar", "eg.tisch", "eg.decke", "eg.kueche", "eg.bahnlicht", "eg.bad", "eg.gang", "eg.og.treppe", "ug.gast", "ug.gast.dose", "ug.aussen", "ug.gang", "ug.eg.treppe"); + + #number of events (min - max) + $event_min=isset($_GET['event_min']) ? $_GET['event_min'] : 5; + $event_max=isset($_GET['event_max']) ? $_GET['event_max'] : 20; + + #maximum delay in minutes + $delay_min=isset($_GET['delay_min']) ? $_GET['delay_min'] : 0; + $delay_max=isset($_GET['delay_max']) ? $_GET['delay_max'] : 240; + + #minimum and maximum ontime in minutes + $ontime_min=isset($_GET['ontime_min']) ? $_GET['ontime_min'] : 5; + $ontime_max=isset($_GET['ontime_max']) ? $_GET['ontime_max'] : 60; + + $variant=isset($_GET['variant']) ? $_GET['variant'] : "onoff"; + + echo "

Random event generator (\"holiday-function\")

"; + echo "
"; + echo ""; + echo ""; + echo ""; + echo ""; + echo "
Number of events:-"; + echo ""; + if ( $event_min > $event_max ) { echo " : min has to be <= max"; unset($_GET['random']); } + if ( !is_posint($event_min)) { echo " : min has to be a integer"; unset($_GET['random']); } + if ( !is_posint($event_max)) { echo " : max has to be a integer"; unset($_GET['random']); } + echo "
Delay from now:-"; + echo "Min."; + if ( $delay_min > $delay_max ) { echo " : min has to be <= max"; unset($_GET['random']); } + if ( !is_posint($delay_min)) { echo " : min has to be a integer"; unset($_GET['random']); } + if ( !is_posint($delay_max)) { echo " : max has to be a integer"; unset($_GET['random']); } + echo "
Time to keep on:-"; + echo "Min."; + if ( $ontime_min > $ontime_max ) { echo " : min has to be <= max"; unset($_GET['random']); } + if ( !is_posint($ontime_min)) { echo " : min has to be a integer"; unset($_GET['random']); } + if ( !is_posint($ontime_max)) { echo " : max has to be a integer"; unset($_GET['random']); } + echo "
Varant: "; + echo ""; + echo "
"; + echo "

"; + + if ( isset($_GET['random'])) { + $event=rand($event_min, $event_max); + echo "Just copy lines below into FHZ1000 command window"; + echo "

";
+    for($i=0; $i<$event; $i++) {
+ 
+      $starttime=rand($delay_min, $delay_max);
+      $hour=intval($starttime/60);
+      $minute=intval($starttime%60);
+      $second=rand(0,59);
+
+      $ontime=rand($ontime_min, $ontime_max);
+
+      $dev=$devices[array_rand($devices)];
+
+      if ($variant == "oft") {
+        printf("at +%02d:%02d:%02d set %s on-for-timer %d
", $hour, $minute, $second, $dev, $ontime); + } elseif ($variant == "onoff") { + $offtime=$starttime + $ontime; + $hour_off=intval($offtime / 60); + $minute_off=intval($offtime % 60); + $second_off=rand(0,59); + printf("at +%02d:%02d:%02d set %s on
", $hour, $minute, $second, $dev); + printf("at +%02d:%02d:%02d set %s off
", $hour_off, $minute_off, $second_off, $dev); + } + } + echo "
";
+  }
+}
+
+?>
+
+
+

+FS 20 Haussteuerrung +

+Quicklinks: +EG +Generic +Random + +
+ + +

EG

+
+ + + + + + + + + + + +
+ + + +
+ + + + + +
+ + + + + +
+ + + +
+ +
+ + +

Send generic command:

+
+", isset($_GET['generic']) ? $_GET['generic'] : ""); +?> + +
+ +
+ +
+

Last command

"; + echo "
send:"; + foreach($cmdline as $line) { + echo "$line
"; + } + echo "
"; + echo "

"; + $fp = fsockopen("localhost", 7072, $errno, $errstr, 10); + if (!$fp) { + echo "$errstr ($errno)
\n"; + } else { + foreach($cmdline as $line) { + fwrite($fp, $line."\n"); + } + echo "
";
+    while (!feof($fp)) {
+      echo htmlentities(fgets($fp));
+    }
+    echo "
"; + fclose($fp); + } + + echo "
"; + +} + +?> + + + diff --git a/webfrontend/pgm4/images/EG.gif b/webfrontend/pgm4/images/EG.gif new file mode 100644 index 0000000000000000000000000000000000000000..c1a132d788834873ddbc2cc48586f32d5a2b7807 GIT binary patch literal 6510 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