2007-01-30 12:47:36 +00:00
#!/usr/bin/perl
################################################################
#
# Copyright notice
#
2014-01-14 19:23:34 +00:00
# (c) 2005-2014
2008-12-09 14:12:40 +00:00
# Copyright: Rudolf Koenig (r dot koenig at koeniglich dot de)
2007-01-30 12:47:36 +00:00
# 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!
#
2011-02-27 18:47:13 +00:00
# Homepage: http://fhem.de
2012-06-23 19:54:36 +00:00
#
# $Id$
2007-01-30 12:47:36 +00:00
use strict ;
use warnings ;
use IO::Socket ;
use Time::HiRes qw( gettimeofday ) ;
2014-12-14 15:55:51 +00:00
use Errno qw( :POSIX ) ;
2007-01-30 12:47:36 +00:00
##################################################
# Forward declarations
#
2009-11-12 19:08:01 +00:00
sub AddDuplicate ($$) ;
2014-02-07 07:27:47 +00:00
sub AnalyzeCommand ( $ $ ; $ ) ;
sub AnalyzeCommandChain ( $ $ ; $ ) ;
2007-03-19 14:59:37 +00:00
sub AnalyzeInput ($) ;
2013-01-03 12:50:16 +00:00
sub AnalyzePerlCommand ($$) ;
2013-11-20 13:04:27 +00:00
sub AssignIoPort ( $ ; $ ) ;
2011-01-02 14:45:53 +00:00
sub AttrVal ($$$) ;
2007-03-19 14:59:37 +00:00
sub CallFn (@) ;
2013-07-15 20:34:58 +00:00
sub CheckDuplicate ($$@) ;
2013-01-03 12:50:16 +00:00
sub CommandChain ($$) ;
2014-03-16 11:50:22 +00:00
sub Debug ($) ;
sub DoSet (@) ;
2009-11-14 09:20:37 +00:00
sub Dispatch ($$$) ;
2013-01-03 12:50:16 +00:00
sub DoTrigger ($$@) ;
2013-03-24 17:47:28 +00:00
sub EvalSpecials ($%) ;
2014-05-01 15:02:06 +00:00
sub FileRead ($) ;
sub FileWrite ($@) ;
2008-05-09 13:58:10 +00:00
sub FmtDateTime ($) ;
sub FmtTime ($) ;
2007-03-19 14:59:37 +00:00
sub GetLogLevel (@) ;
2008-07-25 14:14:24 +00:00
sub GetTimeSpec ($) ;
2013-08-25 11:49:30 +00:00
sub GlobalAttr ($$$$) ;
2007-08-06 18:17:29 +00:00
sub HandleArchiving ($) ;
2008-05-09 13:58:10 +00:00
sub HandleTimeout () ;
2007-03-19 14:59:37 +00:00
sub IOWrite ($@) ;
2007-11-26 08:27:04 +00:00
sub InternalTimer ($$$$) ;
2014-03-16 11:50:22 +00:00
sub InternalVal ($$$) ;
2013-08-18 14:13:59 +00:00
sub IsDisabled ($) ;
2013-01-03 12:50:16 +00:00
sub IsDummy ($) ;
sub IsIgnored ($) ;
2014-03-16 11:50:22 +00:00
sub IsIoDummy ($) ;
2015-01-11 17:55:36 +00:00
sub LoadModule ( $ ; $ ) ;
2007-01-30 12:47:36 +00:00
sub Log ($$) ;
2013-08-18 14:13:59 +00:00
sub Log3 ($$$) ;
2014-03-16 11:50:22 +00:00
sub OldTimestamp ($) ;
sub OldValue ($) ;
2007-01-30 12:47:36 +00:00
sub OpenLogfile ($) ;
2008-05-09 13:58:10 +00:00
sub PrintHash ($$) ;
2014-03-16 11:50:22 +00:00
sub ReadingsNum ($$$) ;
sub ReadingsTimestamp ($$$) ;
2010-04-02 16:26:58 +00:00
sub ReadingsVal ($$$) ;
2013-01-03 12:50:16 +00:00
sub RemoveInternalTimer ($) ;
2011-07-30 13:22:25 +00:00
sub ReplaceEventMap ($$$) ;
2007-01-30 12:47:36 +00:00
sub ResolveDateWildcards ($@) ;
2007-03-19 14:59:37 +00:00
sub SemicolonEscape ($) ;
2007-01-30 12:47:36 +00:00
sub SignalHandling () ;
sub TimeNow () ;
2014-03-16 11:50:22 +00:00
sub Value ($) ;
sub WakeUpFn ($) ;
2007-03-19 14:59:37 +00:00
sub WriteStatefile () ;
2007-01-30 12:47:36 +00:00
sub XmlEscape ($) ;
2013-01-03 12:50:16 +00:00
sub addEvent ($$) ;
2014-10-03 10:53:48 +00:00
sub addToDevAttrList ($$) ;
2013-01-03 12:50:16 +00:00
sub addToAttrList ($) ;
2014-04-24 10:05:01 +00:00
sub addToWritebuffer ($$@) ;
2013-12-13 08:15:43 +00:00
sub attrSplit ($) ;
2014-03-16 11:50:22 +00:00
sub computeClientArray ($$) ;
sub concatc ($$$) ;
2014-04-20 19:20:42 +00:00
sub configDBUsed () ;
2014-03-16 11:50:22 +00:00
sub createNtfyHash () ;
2015-01-12 17:15:46 +00:00
sub createUniqueId () ;
2008-05-09 13:58:10 +00:00
sub devspec2array ($) ;
sub doGlobalDef ($) ;
2014-03-16 11:50:22 +00:00
sub escapeLogLine ($) ;
2013-08-18 14:13:59 +00:00
sub evalStateFormat ($) ;
2012-10-30 18:46:58 +00:00
sub fhem ($@) ;
2014-03-16 11:50:22 +00:00
sub fhemTimeGm ($$$$$$) ;
sub fhemTimeLocal ($$$$$$) ;
sub fhemTzOffset ($) ;
sub getAllAttr ($) ;
2013-08-07 13:06:49 +00:00
sub getAllGets ($) ;
2012-07-10 06:23:04 +00:00
sub getAllSets ($) ;
2015-01-12 17:15:46 +00:00
sub getUniqueId () ;
2013-08-18 14:13:59 +00:00
sub latin1ToUtf8 ($) ;
2014-03-16 11:50:22 +00:00
sub myrename ($$) ;
sub notifyRegexpChanged ($$) ;
2013-01-03 12:50:16 +00:00
sub readingsBeginUpdate ($) ;
sub readingsBulkUpdate ($$$@) ;
sub readingsEndUpdate ($$) ;
sub readingsSingleUpdate ($$$$) ;
2011-01-29 12:07:14 +00:00
sub redirectStdinStdErr () ;
2013-08-18 14:13:59 +00:00
sub rejectDuplicate ($$$) ;
2013-01-03 12:50:16 +00:00
sub setGlobalAttrBeforeFork ($) ;
2012-02-14 08:13:08 +00:00
sub setReadingsVal ($$$$) ;
2013-09-03 09:22:23 +00:00
sub utf8ToLatin1 ($) ;
2007-01-30 12:47:36 +00:00
sub CommandAttr ($$) ;
2007-12-29 16:25:02 +00:00
sub CommandDefaultAttr ($$) ;
2007-01-30 12:47:36 +00:00
sub CommandDefine ($$) ;
sub CommandDelete ($$) ;
2013-01-03 12:50:16 +00:00
sub CommandDeleteAttr ($$) ;
2014-03-16 11:50:22 +00:00
sub CommandDeleteReading ($$) ;
sub CommandDisplayAttr ($$) ;
2007-01-30 12:47:36 +00:00
sub CommandGet ($$) ;
sub CommandHelp ($$) ;
2013-01-03 12:50:16 +00:00
sub CommandIOWrite ($$) ;
2007-01-30 12:47:36 +00:00
sub CommandInclude ($$) ;
sub CommandInform ($$) ;
sub CommandList ($$) ;
2007-04-24 07:13:21 +00:00
sub CommandModify ($$) ;
2013-01-03 12:50:16 +00:00
sub CommandQuit ($$) ;
2015-01-11 17:55:36 +00:00
sub CommandReload ( $ $ ; $ ) ;
2007-03-19 15:34:34 +00:00
sub CommandRename ($$) ;
2013-01-03 12:50:16 +00:00
sub CommandRereadCfg ($$) ;
2007-03-19 14:59:37 +00:00
sub CommandSave ($$) ;
2007-01-30 12:47:36 +00:00
sub CommandSet ($$) ;
2014-03-16 11:50:22 +00:00
sub CommandSetReading ($$) ;
2007-01-30 12:47:36 +00:00
sub CommandSetstate ($$) ;
sub CommandShutdown ($$) ;
2013-01-03 12:50:16 +00:00
sub CommandSleep ($$) ;
2007-01-30 12:47:36 +00:00
sub CommandTrigger ($$) ;
2014-03-16 11:50:22 +00:00
sub CommandVersion ($$) ;
2007-01-30 12:47:36 +00:00
2014-03-01 07:59:19 +00:00
# configDB special
sub cfgDB_Init ;
sub cfgDB_ReadAll ($) ;
sub cfgDB_SaveState ;
sub cfgDB_SaveCfg ;
2014-05-03 09:41:12 +00:00
sub cfgDB_AttrRead ($) ;
sub cfgDB_ReadFile ($) ;
sub cfgDB_UpdateFile ($) ;
sub cfgDB_WriteFile ($@) ;
2014-03-03 13:20:18 +00:00
sub cfgDB_svnId ;
2014-03-01 07:59:19 +00:00
2007-01-30 12:47:36 +00:00
##################################################
# Variables:
# global, to be able to access them from modules
2007-03-19 14:59:37 +00:00
#Special values in %modules (used if set):
2014-10-09 17:54:30 +00:00
# AttrFn - called for attribute changes
2007-03-19 14:59:37 +00:00
# DefFn - define a "device" of this type
2013-04-28 12:40:28 +00:00
# DeleteFn - clean up (delete logfile), called by delete after UndefFn
2014-10-09 17:54:30 +00:00
# ExceptFn - called if the global select reports an except field
2014-10-09 19:21:23 +00:00
# FingerprintFn - convert messages for duplicate detection
2007-03-19 14:59:37 +00:00
# GetFn - get some data from this device
# NotifyFn - call this if some device changed its properties
2014-10-09 17:54:30 +00:00
# ParseFn - Interpret a raw message
2012-10-28 21:28:41 +00:00
# ReadFn - Reading from a Device (see FHZ/WS300)
2014-10-09 17:54:30 +00:00
# ReadyFn - check for available data, if no FD
# RenameFn - inform the device about its renameing
# SetFn - set/activate this device
# ShutdownFn-called before shutdown
# StateFn - set local info for this device, do not activate anything
# UndefFn - clean up (delete timer, close fd), called by delete and rereadcfg
2007-03-19 14:59:37 +00:00
#Special values in %defs:
# TYPE - The name of the module it belongs to
# STATE - Oneliner describing its state
# NR - its "serial" number
# DEF - its definition
# READINGS- The readings. Each value has a "VAL" and a "TIME" component.
2008-09-06 08:33:55 +00:00
# FD - FileDescriptor. Used by selectlist / readyfnlist
2007-03-19 14:59:37 +00:00
# IODev - attached to io device
# CHANGED - Currently changed attributes of this device. Used by NotifyFn
# VOLATILE- Set if the definition should be saved to the "statefile"
2014-01-14 19:23:34 +00:00
# NOTIFYDEV - if set, the notifyFn will only be called for this device
2014-03-30 06:58:52 +00:00
use vars qw( $devcount ) ; # Maximum device number, used for storing
use vars qw( $fhem_started ) ; # used for uptime calculation
2007-05-27 17:24:52 +00:00
use vars qw( $init_done ) ; #
2014-01-14 19:23:34 +00:00
use vars qw( $internal_data ) ; # FileLog/DbLog -> SVG data transport
2014-03-30 06:58:52 +00:00
use vars qw( $nextat ) ; # Time when next timer will be triggered.
use vars qw( $readytimeout ) ; # Polling interval. UNIX: device search only
use vars qw( $reread_active ) ;
use vars qw( $winService ) ; # the Windows Service object
use vars qw( %attr ) ; # Attributes
2014-01-14 19:23:34 +00:00
use vars qw( %cmds ) ; # Global command name hash.
use vars qw( %data ) ; # Hash for user data
use vars qw( %defaultattr ) ; # Default attributes, used by FHEM2FHEM
2014-03-30 06:58:52 +00:00
use vars qw( %defs ) ; # FHEM device/button definitions
2014-01-14 19:23:34 +00:00
use vars qw( %inform ) ; # Used by telnet_ActivateInform
use vars qw( %intAt ) ; # Internal at timer hash, global for benchmark
2014-03-30 06:58:52 +00:00
use vars qw( %modules ) ; # List of loaded modules (device/log/etc)
2014-01-14 19:23:34 +00:00
use vars qw( %ntfyHash ) ; # hash of devices needed to be notified.
2014-03-30 06:58:52 +00:00
use vars qw( %oldvalue ) ; # Old values, see commandref.html
use vars qw( %readyfnlist ) ; # devices which want a "readyfn"
use vars qw( %selectlist ) ; # devices which want a "select"
use vars qw( %value ) ; # Current values, see commandref.html
2014-05-29 10:25:01 +00:00
use vars qw( $lastDefChange ) ; # number of last def/attr change
2015-01-24 12:38:25 +00:00
use vars qw( @structChangeHist ) ; # Contains the last 10 structural changes
2014-10-08 08:30:15 +00:00
use vars qw( $cmdFromAnalyze ) ; # used by the warnings-sub
2008-12-03 16:45:26 +00:00
2013-08-18 14:13:59 +00:00
my $ AttrList = "verbose:0,1,2,3,4,5 room group comment alias " .
"eventMap userReadings" ;
2012-02-08 12:41:00 +00:00
my $ currcfgfile = "" ; # current config/include file
2014-03-30 06:58:52 +00:00
my $ currlogfile ; # logfile, without wildcards
my $ cvsid = '$Id$' ;
my $ duplidx = 0 ; # helper for the above pool
my $ evalSpecials ; # Used by EvalSpecials->AnalyzeCommand parameter passing
my $ intAtCnt = 0 ;
2007-03-19 14:59:37 +00:00
my $ logopened = 0 ; # logfile opened or using stdout
2014-03-30 06:58:52 +00:00
my $ namedef = "where <name> is a single device name, a list separated by komma (,) or a regexp. See the devspec section in the commandref.html for details.\n" ;
2007-01-30 12:47:36 +00:00
my $ rcvdquit ; # Used for quit handling in init files
2014-03-30 06:58:52 +00:00
my $ readingsUpdateDelayTrigger ; # needed internally
2007-01-30 12:47:36 +00:00
my $ sig_term = 0 ; # if set to 1, terminate (saving the state)
2014-07-13 10:41:00 +00:00
my $ wbName = ".WRITEBUFFER" ; # Buffer-name for delayed writing via select
2014-03-30 06:58:52 +00:00
my % comments ; # Comments from the include files
2009-11-12 19:08:01 +00:00
my % duplicate ; # Pool of received msg for multi-fhz/cul setups
2012-03-30 07:11:39 +00:00
my @ cmdList ; # Remaining commands in a chain. Used by sleep
2007-03-19 14:59:37 +00:00
2007-05-27 17:24:52 +00:00
$ init_done = 0 ;
2014-05-29 10:25:01 +00:00
$ lastDefChange = 0 ;
2014-03-30 06:58:52 +00:00
$ readytimeout = ( $^O eq "MSWin32" ) ? 0.1 : 5.0 ;
2007-03-19 14:59:37 +00:00
2011-07-24 11:55:36 +00:00
$ modules { Global } { ORDER } = - 1 ;
$ modules { Global } { LOADED } = 1 ;
$ modules { Global } { AttrList } =
2012-06-23 16:22:28 +00:00
"archivecmd apiversion archivedir configfile lastinclude logfile " .
2014-09-29 19:52:15 +00:00
"modpath nrarchive pidfilename port statefile title " .
2013-08-18 14:13:59 +00:00
"mseclog:1,0 version nofork:1,0 logdir holiday2we " .
2013-07-24 06:50:26 +00:00
"autoload_undefined_devices:1,0 dupTimeout latitude longitude altitude " .
2012-06-13 10:57:58 +00:00
"backupcmd backupdir backupsymlink backup_before_update " .
2014-08-19 19:15:33 +00:00
"exclude_from_update motd restoreDirs uniqueID " .
2013-07-11 19:46:39 +00:00
"sendStatistics:onUpdate,manually,never updateInBackground:1,0 " .
2014-10-09 12:30:18 +00:00
"showInternalValues:1,0 stacktrace:1,0 " ;
2011-07-24 11:55:36 +00:00
$ modules { Global } { AttrFn } = "GlobalAttr" ;
2007-03-19 14:59:37 +00:00
2013-01-03 12:50:16 +00:00
use vars qw( $readingFnAttributes ) ;
2013-03-01 11:09:18 +00:00
$ readingFnAttributes = "event-on-change-reading event-on-update-reading " .
2015-01-25 15:24:49 +00:00
"event-aggregator event-min-interval stateFormat" ;
2013-01-03 12:50:16 +00:00
2008-12-09 14:12:40 +00:00
% cmds = (
2007-01-30 12:47:36 +00:00
"?" = > { Fn = > "CommandHelp" ,
Hlp = > ",get this help" } ,
2008-11-01 21:27:10 +00:00
"attr" = > { Fn = > "CommandAttr" ,
2009-11-12 19:08:01 +00:00
Hlp = > "<devspec> <attrname> [<attrval>],set attribute for <devspec>" } ,
2007-01-30 12:47:36 +00:00
"define" = > { Fn = > "CommandDefine" ,
2007-04-15 12:55:01 +00:00
Hlp = > "<name> <type> <options>,define a device/at/notify entity" } ,
2008-11-01 21:27:10 +00:00
"deleteattr" = > { Fn = > "CommandDeleteAttr" ,
2007-12-29 15:57:42 +00:00
Hlp = > "<devspec> [<attrname>],delete attribute for <devspec>" } ,
2013-01-19 13:36:29 +00:00
"deletereading" = > { Fn = > "CommandDeleteReading" ,
Hlp = > "<devspec> [<attrname>],delete user defined reading for <devspec>" } ,
2007-01-30 12:47:36 +00:00
"delete" = > { Fn = > "CommandDelete" ,
2007-12-29 15:57:42 +00:00
Hlp = > "<devspec>,delete the corresponding definition(s)" } ,
2013-08-07 11:18:15 +00:00
"displayattr" = > { Fn = > "CommandDisplayAttr" ,
Hlp = > "<devspec> [attrname],display attributes" } ,
2008-11-01 21:27:10 +00:00
"get" = > { Fn = > "CommandGet" ,
2007-12-29 15:57:42 +00:00
Hlp = > "<devspec> <type dependent>,request data from <devspec>" } ,
2007-01-30 12:47:36 +00:00
"help" = > { Fn = > "CommandHelp" ,
Hlp = > ",get this help" } ,
"include" = > { Fn = > "CommandInclude" ,
Hlp = > "<filename>,read the commands from <filenname>" } ,
"inform" = > { Fn = > "CommandInform" ,
2013-08-08 13:26:43 +00:00
ClientFilter = > "telnet" ,
2010-10-24 16:08:48 +00:00
Hlp = > "{on|timer|raw|off},echo all events to this client" } ,
"iowrite" = > { Fn = > "CommandIOWrite" ,
Hlp = > "<iodev> <data>,write raw data with iodev" } ,
2007-01-30 12:47:36 +00:00
"list" = > { Fn = > "CommandList" ,
2007-12-29 15:57:42 +00:00
Hlp = > "[devspec],list definitions and status info" } ,
2007-04-24 07:13:21 +00:00
"modify" = > { Fn = > "CommandModify" ,
Hlp = > "device <options>,modify the definition (e.g. at, notify)" } ,
2007-01-30 12:47:36 +00:00
"quit" = > { Fn = > "CommandQuit" ,
2013-08-08 13:26:43 +00:00
ClientFilter = > "telnet" ,
2007-01-30 12:47:36 +00:00
Hlp = > ",end the client session" } ,
2012-06-19 15:12:22 +00:00
"exit" = > { Fn = > "CommandQuit" ,
2013-08-08 13:26:43 +00:00
ClientFilter = > "telnet" ,
2012-06-19 15:12:22 +00:00
Hlp = > ",end the client session" } ,
2007-01-30 12:47:36 +00:00
"reload" = > { Fn = > "CommandReload" ,
Hlp = > "<module-name>,reload the given module (e.g. 99_PRIV)" } ,
2007-03-19 15:34:34 +00:00
"rename" = > { Fn = > "CommandRename" ,
Hlp = > "<old> <new>,rename a definition" } ,
2007-01-30 12:47:36 +00:00
"rereadcfg" = > { Fn = > "CommandRereadCfg" ,
2012-10-09 19:19:15 +00:00
Hlp = > "[configfile],read in the config after deleting everything" } ,
2014-12-02 07:05:00 +00:00
"restore" = > {
Hlp = > "[list] [<filename|directory>],restore files saved by update" } ,
2008-11-01 21:27:10 +00:00
"save" = > { Fn = > "CommandSave" ,
2007-03-19 14:59:37 +00:00
Hlp = > "[configfile],write the configfile and the statefile" } ,
2008-11-01 21:27:10 +00:00
"set" = > { Fn = > "CommandSet" ,
2007-12-29 15:57:42 +00:00
Hlp = > "<devspec> <type dependent>,transmit code for <devspec>" } ,
2013-08-22 15:13:44 +00:00
"setreading" = > { Fn = > "CommandSetReading" ,
Hlp = > "<devspec> <reading> <value>,set reading for <devspec>" } ,
2008-11-01 21:27:10 +00:00
"setstate" = > { Fn = > "CommandSetstate" ,
2007-12-29 15:57:42 +00:00
Hlp = > "<devspec> <state>,set the state shown in the command list" } ,
2008-11-01 21:27:10 +00:00
"setdefaultattr" = > { Fn = > "CommandDefaultAttr" ,
2007-12-29 16:25:02 +00:00
Hlp = > "<attrname> <attrvalue>,set attr for following definitions" } ,
2007-01-30 12:47:36 +00:00
"shutdown" = > { Fn = > "CommandShutdown" ,
2011-12-26 14:32:39 +00:00
Hlp = > "[restart],terminate the server" } ,
2007-01-30 12:47:36 +00:00
"sleep" = > { Fn = > "CommandSleep" ,
2013-07-25 07:35:49 +00:00
Hlp = > "<sec> [quiet],sleep for sec, 3 decimal places" } ,
2007-01-30 12:47:36 +00:00
"trigger" = > { Fn = > "CommandTrigger" ,
2007-12-29 15:57:42 +00:00
Hlp = > "<devspec> <state>,trigger notify command" } ,
2013-03-30 12:41:09 +00:00
"update" = > {
2014-12-02 07:05:00 +00:00
Hlp = > "[<fileName>|all|check|force] " .
"[http://.../controlfile],update FHEM" } ,
2013-03-30 12:41:09 +00:00
"updatefhem" = > { ReplacedBy = > "update" } ,
2013-07-13 12:01:01 +00:00
"version" = > { Fn = > "CommandVersion" ,
Hlp = > "[filter],print SVN version of loaded modules" } ,
2007-01-30 12:47:36 +00:00
) ;
###################################################
# Start the program
2013-08-18 10:27:54 +00:00
if ( int ( @ ARGV ) < 1 ) {
2007-01-30 12:47:36 +00:00
print "Usage:\n" ;
print "as server: fhem configfile\n" ;
2013-08-18 10:27:54 +00:00
print "as client: fhem [host:]port cmd cmd cmd...\n" ;
2013-08-25 11:49:30 +00:00
if ( $^O =~ m/Win/ ) {
print "install as windows service: fhem.pl configfile -i\n" ;
print "uninstall the windows service: fhem.pl -u\n" ;
}
2007-01-30 12:47:36 +00:00
CommandHelp ( undef , undef ) ;
exit ( 1 ) ;
}
2011-01-29 12:07:14 +00:00
# If started as root, and there is a fhem user in the /etc/passwd, su to it
if ( $^O !~ m/Win/ && $< == 0 ) {
2011-09-23 11:52:00 +00:00
2011-01-29 12:07:14 +00:00
my @ pw = getpwnam ( "fhem" ) ;
if ( @ pw ) {
2012-01-22 12:21:05 +00:00
use POSIX qw( setuid setgid ) ;
# set primary group
setgid ( $ pw [ 3 ] ) ;
# read all secondary groups into an array:
my @ groups ;
while ( my ( $ name , $ pw , $ gid , $ members ) = getgrent ( ) ) {
push ( @ groups , $ gid ) if ( grep ( $ _ eq $ pw [ 0 ] , split ( /\s+/ , $ members ) ) ) ;
}
# set the secondary groups via $)
if ( @ groups ) {
$ ) = "$pw[3] " . join ( " " , @ groups ) ;
} else {
$ ) = "$pw[3] $pw[3]" ;
}
2011-01-29 12:07:14 +00:00
setuid ( $ pw [ 2 ] ) ;
}
2012-01-22 12:21:05 +00:00
2011-01-29 12:07:14 +00:00
}
2007-03-27 14:50:04 +00:00
2007-01-30 12:47:36 +00:00
###################################################
# Client code
2013-08-25 11:49:30 +00:00
if ( int ( @ ARGV ) > 1 && $ ARGV [ $# ARGV ] ne "-i" ) {
2007-01-30 12:47:36 +00:00
my $ buf ;
2013-08-18 10:27:54 +00:00
my $ addr = shift @ ARGV ;
$ addr = "localhost:$addr" if ( $ addr !~ m/:/ ) ;
2012-06-23 16:22:28 +00:00
my $ client = IO::Socket::INET - > new ( PeerAddr = > $ addr ) ;
die "Can't connect to $addr\n" if ( ! $ client ) ;
2013-08-18 10:27:54 +00:00
for ( my $ i = 0 ; $ i < int ( @ ARGV ) ; $ i + + ) {
syswrite ( $ client , $ ARGV [ $ i ] . "\n" ) ;
}
2012-06-23 16:22:28 +00:00
shutdown ( $ client , 1 ) ;
while ( sysread ( $ client , $ buf , 256 ) > 0 ) {
2013-08-18 10:27:54 +00:00
$ buf =~ s/\xff\xfb\x01Password: // ;
$ buf =~ s/\xff\xfc\x01\r\n// ;
$ buf =~ s/\xff\xfd\x00// ;
2007-01-30 12:47:36 +00:00
print ( $ buf ) ;
}
2007-03-19 14:59:37 +00:00
exit ( 0 ) ;
2007-01-30 12:47:36 +00:00
}
2007-03-19 14:59:37 +00:00
# End of client code
###################################################
2007-01-30 12:47:36 +00:00
2013-01-13 15:16:31 +00:00
###################################################
2013-08-25 11:49:30 +00:00
# Windows Service Support: install/remove or start the fhem service
if ( $^O =~ m/Win/ ) {
( my $ dir = $ 0 ) =~ s + [ /\\][^/ \ \ ] * $+ + ; # Find the FHEM directory
chdir ( $ dir ) ;
$ winService = eval { require FHEM::WinService ; FHEM::WinService - > new ( \ @ ARGV ) ; } ;
if ( ( ! $ winService || $@ ) && ( $ ARGV [ $# ARGV ] eq "-i" || $ ARGV [ $# ARGV ] eq "-u" ) ) {
print "Cannot initialize FHEM::WinService: $@, exiting.\n" ;
exit 0 ;
}
2013-01-13 15:16:31 +00:00
}
2013-08-25 11:49:30 +00:00
$ winService || = { } ;
2013-01-13 15:16:31 +00:00
2007-11-26 08:27:04 +00:00
###################################################
# Server initialization
2011-01-29 12:07:14 +00:00
doGlobalDef ( $ ARGV [ 0 ] ) ;
2014-04-20 19:20:42 +00:00
if ( configDBUsed ( ) ) {
2014-03-01 07:59:19 +00:00
eval "use configDB" ;
Log 1 , $@ if ( $@ ) ;
cfgDB_Init ( ) ;
}
2011-01-29 12:07:14 +00:00
# As newer Linux versions reset serial parameters after fork, we parse the
2013-08-25 11:49:30 +00:00
# config file after the fork. But we need some global attr parameters before, so we
2011-01-29 12:07:14 +00:00
# read them here.
2012-06-07 06:22:00 +00:00
setGlobalAttrBeforeFork ( $ attr { global } { configfile } ) ;
2007-01-30 12:47:36 +00:00
2013-08-25 11:49:30 +00:00
Log 1 , $ _ for eval { @ { $ winService - > { ServiceLog } } ; } ;
2014-04-26 06:53:09 +00:00
# Go to background if the logfile is a real file (not stdout)
2009-08-04 08:03:57 +00:00
if ( $^O =~ m/Win/ && ! $ attr { global } { nofork } ) {
$ attr { global } { nofork } = 1 ;
}
2008-09-06 08:33:55 +00:00
if ( $ attr { global } { logfile } ne "-" && ! $ attr { global } { nofork } ) {
2007-01-30 12:47:36 +00:00
defined ( my $ pid = fork ) || die "Can't fork: $!" ;
exit ( 0 ) if $ pid ;
}
2011-06-12 10:51:57 +00:00
# FritzBox special: Wait until the time is set via NTP,
2011-07-15 08:59:31 +00:00
# but not more than 2 hours
2014-08-03 08:22:52 +00:00
if ( time ( ) < 2 * 3600 ) {
Log 1 , "date/time not set, waiting up to 2 hours to be set." ;
while ( time ( ) < 2 * 3600 ) {
sleep ( 5 ) ;
}
2011-06-12 10:51:57 +00:00
}
2014-04-26 06:53:09 +00:00
###################################################
# initialize the readings semantics meta information
require RTypes ;
RTypes_Initialize ( ) ;
2014-04-05 06:23:39 +00:00
my $ cfgErrMsg = "Error messages while initializing FHEM:" ;
2014-04-02 11:05:28 +00:00
my $ cfgRet = "" ;
2014-04-20 19:20:42 +00:00
if ( configDBUsed ( ) ) {
2014-03-04 16:27:09 +00:00
my $ ret = cfgDB_ReadAll ( undef ) ;
2014-04-02 11:05:28 +00:00
$ cfgRet . = "configDB: $ret" if ( $ ret ) ;
2014-03-01 07:59:19 +00:00
} else {
my $ ret = CommandInclude ( undef , $ attr { global } { configfile } ) ;
2015-01-11 17:55:36 +00:00
$ cfgRet . = "configfile: $ret" if ( $ ret ) ;
2007-01-30 12:47:36 +00:00
2014-03-01 07:59:19 +00:00
if ( $ attr { global } { statefile } && - r $ attr { global } { statefile } ) {
$ ret = CommandInclude ( undef , $ attr { global } { statefile } ) ;
2014-04-02 11:05:28 +00:00
$ cfgRet . = "statefile: $ret" if ( $ ret ) ;
2014-03-01 07:59:19 +00:00
}
2007-01-30 12:47:36 +00:00
}
2011-01-29 12:07:14 +00:00
2014-04-02 11:05:28 +00:00
if ( $ cfgRet ) {
2014-04-05 06:23:39 +00:00
$ attr { global } { motd } = "$cfgErrMsg\n$cfgRet" ;
2014-04-02 11:05:28 +00:00
Log 1 , $ cfgRet ;
2014-04-10 07:50:19 +00:00
} elsif ( $ attr { global } { motd } && $ attr { global } { motd } =~ m/^$cfgErrMsg/ ) {
2014-04-02 11:05:28 +00:00
$ attr { global } { motd } = "" ;
}
2007-01-30 12:47:36 +00:00
SignalHandling ( ) ;
2007-03-19 14:59:37 +00:00
my $ pfn = $ attr { global } { pidfilename } ;
if ( $ pfn ) {
2007-09-24 07:09:17 +00:00
die "$pfn: $!\n" if ( ! open ( PID , ">$pfn" ) ) ;
2007-03-19 14:59:37 +00:00
print PID $$ . "\n" ;
close ( PID ) ;
}
2011-01-29 12:07:14 +00:00
2012-06-23 16:22:28 +00:00
my $ gp = $ attr { global } { port } ;
if ( $ gp ) {
Log 3 , "Converting 'attr global port $gp' to 'define telnetPort telnet $gp'" ;
2012-09-12 14:04:47 +00:00
my $ ret = CommandDefine ( undef , "telnetPort telnet $gp" ) ;
Log 1 , "$ret" if ( $ ret ) ;
2012-06-23 16:22:28 +00:00
delete ( $ attr { global } { port } ) ;
}
my $ sc_text = "SecurityCheck:" ;
$ attr { global } { motd } = "$sc_text\n\n"
if ( ! $ attr { global } { motd } || $ attr { global } { motd } =~ m/^$sc_text/ ) ;
2012-06-22 08:22:24 +00:00
2007-09-24 07:09:17 +00:00
$ init_done = 1 ;
2014-05-29 10:25:01 +00:00
$ lastDefChange = 1 ;
2014-03-06 20:06:00 +00:00
foreach my $ d ( keys % defs ) {
2014-03-09 12:31:07 +00:00
if ( $ defs { $ d } { IODevMissing } ) {
2014-03-06 20:06:00 +00:00
Log 3 , "No I/O device found for $defs{$d}{NAME}" ;
2014-03-09 12:31:07 +00:00
delete $ defs { $ d } { IODevMissing } ;
2014-03-06 20:06:00 +00:00
}
}
2014-04-24 19:16:01 +00:00
2013-01-30 10:39:30 +00:00
DoTrigger ( "global" , "INITIALIZED" , 1 ) ;
2014-03-30 06:58:52 +00:00
$ fhem_started = time ;
2007-09-24 07:09:17 +00:00
2012-06-28 16:48:15 +00:00
$ attr { global } { motd } . = "Running with root privileges."
2012-06-30 08:03:18 +00:00
if ( $^O !~ m/Win/ && $< == 0 && $ attr { global } { motd } =~ m/^$sc_text/ ) ;
2012-06-22 08:22:24 +00:00
$ attr { global } { motd } . =
2014-04-06 06:24:47 +00:00
"\nRestart FHEM for a new check if the problem is fixed,\n" .
2012-06-23 16:22:28 +00:00
"or set the global attribute motd to none to supress this message.\n"
if ( $ attr { global } { motd } =~ m/^$sc_text\n\n./ ) ;
my $ motd = $ attr { global } { motd } ;
if ( $ motd eq "$sc_text\n\n" ) {
delete ( $ attr { global } { motd } ) ;
} else {
2012-06-28 16:48:15 +00:00
if ( $ motd ne "none" ) {
$ motd =~ s/\n/ /g ;
Log 2 , $ motd ;
}
2012-06-23 16:22:28 +00:00
}
2012-06-22 08:22:24 +00:00
2013-08-18 15:23:42 +00:00
my $ osuser = "os $^O, user " . ( getlogin || getpwuid ( $< ) || "unknown" ) ;
2013-02-08 08:22:04 +00:00
Log 0 , "Server started with " . int ( keys % defs ) .
2013-08-18 15:23:42 +00:00
" defined entities (version $attr{global}{version}, $osuser, pid $$)" ;
2007-01-30 12:47:36 +00:00
2007-11-26 08:27:04 +00:00
################################################
2007-03-19 14:59:37 +00:00
# Main Loop
2009-09-11 07:34:12 +00:00
sub MAIN { MAIN: } ; #Dummy
2011-01-22 21:53:18 +00:00
2011-06-02 07:10:01 +00:00
2011-01-22 21:53:18 +00:00
my $ errcount = 0 ;
2007-01-30 12:47:36 +00:00
while ( 1 ) {
2013-11-09 13:45:34 +00:00
my ( $ rout , $ rin , $ wout , $ win , $ eout , $ ein ) = ( '' , '' , '' , '' , '' , '' ) ;
2007-01-30 12:47:36 +00:00
2011-10-02 12:27:51 +00:00
my $ timeout = HandleTimeout ( ) ;
2008-09-06 08:33:55 +00:00
foreach my $ p ( keys % selectlist ) {
2013-11-09 13:45:34 +00:00
my $ hash = $ selectlist { $ p } ;
2013-12-29 17:59:52 +00:00
if ( defined ( $ hash - > { FD } ) ) {
vec ( $ rin , $ hash - > { FD } , 1 ) = 1
2014-12-14 15:55:51 +00:00
if ( ! defined ( $ hash - > { directWriteFn } ) && ! $ hash - > { wantWrite } ) ;
2013-12-29 17:59:52 +00:00
vec ( $ win , $ hash - > { FD } , 1 ) = 1
2014-12-14 15:55:51 +00:00
if ( ( defined ( $ hash - > { directWriteFn } ) ||
defined ( $ hash - > { $ wbName } ) ||
$ hash - > { wantWrite } ) && ! $ hash - > { wantRead } ) ;
2013-12-29 17:59:52 +00:00
}
2013-11-09 13:45:34 +00:00
vec ( $ ein , $ hash - > { EXCEPT_FD } , 1 ) = 1
if ( defined ( $ hash - > { "EXCEPT_FD" } ) ) ;
2007-01-30 12:47:36 +00:00
}
2009-05-23 07:32:08 +00:00
$ timeout = $ readytimeout if ( keys ( % readyfnlist ) &&
( ! defined ( $ timeout ) || $ timeout > $ readytimeout ) ) ;
2013-08-25 11:49:30 +00:00
$ timeout = 5 if $ winService - > { AsAService } && $ timeout > 5 ;
2013-11-09 13:45:34 +00:00
my $ nfound = select ( $ rout = $ rin , $ wout = $ win , $ eout = $ ein , $ timeout ) ;
2007-01-30 12:47:36 +00:00
2013-08-25 11:49:30 +00:00
$ winService - > { serviceCheck } - > ( ) if ( $ winService - > { serviceCheck } ) ;
2007-01-30 12:47:36 +00:00
CommandShutdown ( undef , undef ) if ( $ sig_term ) ;
if ( $ nfound < 0 ) {
2010-10-27 16:51:32 +00:00
my $ err = int ( $! ) ;
2014-01-29 08:14:58 +00:00
next if ( $ err == 0 || $ err == 4 ) ; # 4==EINTR
2010-10-08 07:07:40 +00:00
2011-01-22 21:53:18 +00:00
Log 1 , "ERROR: Select error $nfound ($err), error count= $errcount" ;
$ errcount + + ;
2010-10-08 07:07:40 +00:00
# Handling "Bad file descriptor". This is a programming error.
2011-01-22 21:53:18 +00:00
if ( $ err == 9 ) { # BADF, don't want to "use errno.ph"
2010-10-08 07:07:40 +00:00
my $ nbad = 0 ;
foreach my $ p ( keys % selectlist ) {
my ( $ tin , $ tout ) = ( '' , '' ) ;
vec ( $ tin , $ selectlist { $ p } { FD } , 1 ) = 1 ;
if ( select ( $ tout = $ tin , undef , undef , 0 ) < 0 ) {
2011-01-22 21:53:18 +00:00
Log 1 , "Found and deleted bad fileno for $p" ;
2010-10-08 07:07:40 +00:00
delete ( $ selectlist { $ p } ) ;
$ nbad + + ;
}
}
next if ( $ nbad > 0 ) ;
2011-01-22 21:53:18 +00:00
next if ( $ errcount <= 3 ) ;
2010-10-08 07:07:40 +00:00
}
2011-01-22 21:53:18 +00:00
die ( "Select error $nfound ($err)\n" ) ;
} else {
$ errcount = 0 ;
2007-01-30 12:47:36 +00:00
}
2008-11-01 21:27:10 +00:00
2007-01-30 12:47:36 +00:00
###############################
2008-09-06 08:33:55 +00:00
# Message from the hardware (FHZ1000/WS3000/etc) via select or the Ready
# Function. The latter ist needed for Windows, where USB devices are not
2009-07-04 10:09:27 +00:00
# reported by select, but is used by unix too, to check if the device is
# attached again.
2008-09-06 08:33:55 +00:00
foreach my $ p ( keys % selectlist ) {
2013-11-09 13:45:34 +00:00
my $ hash = $ selectlist { $ p } ;
2013-12-29 17:59:52 +00:00
my $ isDev = ( $ hash && $ hash - > { NAME } && $ defs { $ hash - > { NAME } } ) ;
my $ isDirect = ( $ hash && ( $ hash - > { directReadFn } || $ hash - > { directWriteFn } ) ) ;
next if ( ! $ isDev && ! $ isDirect ) ;
if ( defined ( $ hash - > { FD } ) && vec ( $ rout , $ hash - > { FD } , 1 ) ) {
2014-12-14 15:55:51 +00:00
delete $ hash - > { wantRead } ;
2013-12-29 17:59:52 +00:00
if ( $ hash - > { directReadFn } ) {
$ hash - > { directReadFn } ( $ hash ) ;
} else {
CallFn ( $ hash - > { NAME } , "ReadFn" , $ hash ) ;
}
}
2013-11-09 13:45:34 +00:00
2014-12-14 15:55:51 +00:00
if ( defined ( $ hash - > { FD } ) && vec ( $ wout , $ hash - > { FD } , 1 ) ) {
delete $ hash - > { wantWrite } ;
2013-11-12 17:39:31 +00:00
2013-12-29 17:59:52 +00:00
if ( $ hash - > { directWriteFn } ) {
$ hash - > { directWriteFn } ( $ hash ) ;
2013-11-09 13:45:34 +00:00
2014-12-14 15:55:51 +00:00
} elsif ( defined ( $ hash - > { $ wbName } ) ) {
2013-12-29 17:59:52 +00:00
my $ wb = $ hash - > { $ wbName } ;
2014-08-10 11:55:40 +00:00
alarm ( $ hash - > { ALARMTIMEOUT } ) if ( $ hash - > { ALARMTIMEOUT } ) ;
2013-12-29 17:59:52 +00:00
my $ ret = syswrite ( $ hash - > { CD } , $ wb ) ;
2014-12-14 15:55:51 +00:00
my $ werr = int ( $! ) ;
2014-08-10 11:55:40 +00:00
alarm ( 0 ) if ( $ hash - > { ALARMTIMEOUT } ) ;
2014-12-14 15:55:51 +00:00
if ( ! defined ( $ ret ) && $ werr == EWOULDBLOCK ) {
$ hash - > { wantRead } = 1
if ( TcpServer_WantRead ( $ hash ) ) ;
} elsif ( ! $ ret ) { # zero=EOF, undef=error
2013-12-29 17:59:52 +00:00
Log 4 , "Write error to $p, deleting $hash->{NAME}" ;
2014-04-24 10:05:01 +00:00
TcpServer_Close ( $ hash ) ;
2013-12-29 17:59:52 +00:00
CommandDelete ( undef , $ hash - > { NAME } ) ;
2014-12-14 15:55:51 +00:00
2013-11-09 13:45:34 +00:00
} else {
2013-12-29 17:59:52 +00:00
if ( $ ret == length ( $ wb ) ) {
delete ( $ hash - > { $ wbName } ) ;
2014-04-24 10:05:01 +00:00
if ( $ hash - > { WBCallback } ) {
no strict "refs" ;
my $ ret = & { $ hash - > { WBCallback } } ( $ hash ) ;
use strict "refs" ;
delete $ hash - > { WBCallback } ;
}
2013-12-29 17:59:52 +00:00
} else {
$ hash - > { $ wbName } = substr ( $ wb , $ ret ) ;
}
2013-11-09 13:45:34 +00:00
}
}
}
2012-07-11 14:45:23 +00:00
2013-11-09 13:45:34 +00:00
if ( defined ( $ hash - > { "EXCEPT_FD" } ) && vec ( $ eout , $ hash - > { EXCEPT_FD } , 1 ) ) {
CallFn ( $ hash - > { NAME } , "ExceptFn" , $ hash ) ;
}
2008-09-06 08:33:55 +00:00
}
2013-11-09 13:45:34 +00:00
2008-09-06 08:33:55 +00:00
foreach my $ p ( keys % readyfnlist ) {
2009-08-12 08:08:14 +00:00
next if ( ! $ readyfnlist { $ p } ) ; # due to rereadcfg / delete
2009-07-04 10:09:27 +00:00
2009-08-12 08:08:14 +00:00
if ( CallFn ( $ readyfnlist { $ p } { NAME } , "ReadyFn" , $ readyfnlist { $ p } ) ) {
2011-01-22 21:53:18 +00:00
if ( $ readyfnlist { $ p } ) { # delete itself inside ReadyFn
2009-07-04 10:09:27 +00:00
CallFn ( $ readyfnlist { $ p } { NAME } , "ReadFn" , $ readyfnlist { $ p } ) ;
}
}
2007-01-30 12:47:36 +00:00
}
2008-11-01 21:27:10 +00:00
2007-01-30 12:47:36 +00:00
}
2007-03-19 14:59:37 +00:00
################################################
#Functions ahead, no more "plain" code
2007-01-30 12:47:36 +00:00
################################################
sub
IsDummy ( $ )
{
2007-11-26 08:27:04 +00:00
my $ devname = shift ;
2007-01-30 12:47:36 +00:00
2007-11-26 08:27:04 +00:00
return 1 if ( defined ( $ attr { $ devname } ) && defined ( $ attr { $ devname } { dummy } ) ) ;
2007-01-30 12:47:36 +00:00
return 0 ;
}
2010-01-01 14:53:03 +00:00
sub
IsIgnored ( $ )
{
my $ devname = shift ;
2010-01-01 15:18:09 +00:00
if ( $ devname &&
2014-05-30 06:58:37 +00:00
defined ( $ attr { $ devname } ) && $ attr { $ devname } { ignore } ) {
2010-01-01 15:18:09 +00:00
Log 4 , "Ignoring $devname" ;
return 1 ;
}
2010-01-01 14:53:03 +00:00
return 0 ;
}
2013-03-27 08:16:07 +00:00
sub
IsDisabled ( $ )
{
my $ devname = shift ;
2014-03-09 17:43:56 +00:00
return 0 if ( ! $ devname || ! defined ( $ attr { $ devname } ) ) ;
2014-03-10 21:07:30 +00:00
return 1 if ( $ attr { $ devname } { disable } ) ;
2014-03-09 17:43:56 +00:00
my $ dfi = $ attr { $ devname } { disabledForIntervals } ;
if ( defined ( $ dfi ) ) {
my ( $ sec , $ min , $ hour , $ mday , $ month , $ year , $ wday , $ yday , $ isdst ) = localtime ;
my $ hms = sprintf ( "%02d:%02d:%02d" , $ hour , $ min , $ sec ) ;
foreach my $ ft ( split ( " " , $ dfi ) ) {
my ( $ from , $ to ) = split ( "-" , $ ft ) ;
return 1 if ( $ from && $ to && $ from le $ hms && $ hms le $ to ) ;
}
2013-03-27 08:16:07 +00:00
}
2014-03-09 17:43:56 +00:00
2013-03-27 08:16:07 +00:00
return 0 ;
}
2010-01-01 14:53:03 +00:00
2007-11-26 08:27:04 +00:00
################################################
sub
IsIoDummy ( $ )
{
my $ name = shift ;
return IsDummy ( $ defs { $ name } { IODev } { NAME } )
if ( $ defs { $ name } && $ defs { $ name } { IODev } ) ;
return 1 ;
}
2007-01-30 12:47:36 +00:00
################################################
sub
2007-03-19 14:59:37 +00:00
GetLogLevel ( @ )
2007-01-30 12:47:36 +00:00
{
2007-03-19 14:59:37 +00:00
my ( $ dev , $ deflev ) = @ _ ;
2009-07-26 09:20:07 +00:00
my $ df = defined ( $ deflev ) ? $ deflev : 2 ;
2007-01-30 12:47:36 +00:00
2009-07-26 09:20:07 +00:00
return $ df if ( ! defined ( $ dev ) ) ;
2007-01-30 12:47:36 +00:00
return $ attr { $ dev } { loglevel }
if ( defined ( $ attr { $ dev } ) && defined ( $ attr { $ dev } { loglevel } ) ) ;
2009-07-26 09:20:07 +00:00
return $ df ;
2007-01-30 12:47:36 +00:00
}
################################################
2013-08-10 08:42:31 +00:00
# the new Log with integrated loglevel checking
2007-01-30 12:47:36 +00:00
sub
2013-08-10 08:42:31 +00:00
Log3 ( $$ $ )
2007-01-30 12:47:36 +00:00
{
2013-08-10 08:42:31 +00:00
my ( $ dev , $ loglevel , $ text ) = @ _ ;
2013-08-18 10:27:54 +00:00
$ dev = $ dev - > { NAME } if ( defined ( $ dev ) && ref ( $ dev ) eq "HASH" ) ;
2013-08-10 08:42:31 +00:00
if ( defined ( $ dev ) &&
defined ( $ attr { $ dev } ) &&
2013-08-18 14:13:59 +00:00
defined ( my $ devlevel = $ attr { $ dev } { verbose } ) ) {
2013-08-10 08:42:31 +00:00
return if ( $ loglevel > $ devlevel ) ;
2007-01-30 12:47:36 +00:00
2013-08-10 08:42:31 +00:00
} else {
return if ( $ loglevel > $ attr { global } { verbose } ) ;
2007-01-30 12:47:36 +00:00
2013-08-10 08:42:31 +00:00
}
my ( $ seconds , $ microseconds ) = gettimeofday ( ) ;
my @ t = localtime ( $ seconds ) ;
2007-03-19 14:59:37 +00:00
my $ nfile = ResolveDateWildcards ( $ attr { global } { logfile } , @ t ) ;
2011-02-28 07:27:10 +00:00
OpenLogfile ( $ nfile ) if ( ! $ currlogfile || $ currlogfile ne $ nfile ) ;
2007-03-19 14:59:37 +00:00
2008-08-04 14:34:53 +00:00
my $ tim = sprintf ( "%04d.%02d.%02d %02d:%02d:%02d" ,
2008-08-25 09:52:29 +00:00
$ t [ 5 ] + 1900 , $ t [ 4 ] + 1 , $ t [ 3 ] , $ t [ 2 ] , $ t [ 1 ] , $ t [ 0 ] ) ;
if ( $ attr { global } { mseclog } ) {
$ tim . = sprintf ( ".%03d" , $ microseconds / 1000 ) ;
}
2007-01-30 12:47:36 +00:00
if ( $ logopened ) {
print LOG "$tim $loglevel: $text\n" ;
} else {
print "$tim $loglevel: $text\n" ;
}
return undef ;
}
2013-08-10 08:42:31 +00:00
################################################
sub
Log ( $$ )
{
my ( $ loglevel , $ text ) = @ _ ;
Log3 ( undef , $ loglevel , $ text ) ;
}
2007-01-30 12:47:36 +00:00
#####################################
sub
IOWrite ( $@ )
{
my ( $ hash , @ a ) = @ _ ;
2010-01-01 14:53:03 +00:00
my $ dev = $ hash - > { NAME } ;
return if ( IsDummy ( $ dev ) || IsIgnored ( $ dev ) ) ;
2007-01-30 12:47:36 +00:00
my $ iohash = $ hash - > { IODev } ;
2010-05-14 12:19:31 +00:00
if ( ! $ iohash ||
! $ iohash - > { TYPE } ||
! $ modules { $ iohash - > { TYPE } } ||
! $ modules { $ iohash - > { TYPE } } { WriteFn } ) {
Log 5 , "No IO device or WriteFn found for $dev" ;
2007-01-30 12:47:36 +00:00
return ;
}
2013-12-15 17:09:05 +00:00
return if ( IsDummy ( $ iohash - > { NAME } ) ) ;
2007-01-30 12:47:36 +00:00
no strict "refs" ;
2007-05-27 17:24:52 +00:00
my $ ret = & { $ modules { $ iohash - > { TYPE } } { WriteFn } } ( $ iohash , @ a ) ;
2007-01-30 12:47:36 +00:00
use strict "refs" ;
2007-05-27 17:24:52 +00:00
return $ ret ;
2007-01-30 12:47:36 +00:00
}
2010-10-24 16:08:48 +00:00
#####################################
sub
CommandIOWrite ( $$ )
{
my ( $ cl , $ param ) = @ _ ;
my @ a = split ( " " , $ param ) ;
2012-02-25 19:45:26 +00:00
return "Usage: iowrite <iodev> <param> ..." if ( int ( @ a ) < 2 ) ;
2010-10-24 16:08:48 +00:00
my $ name = shift ( @ a ) ;
my $ hash = $ defs { $ name } ;
return "$name not found" if ( ! $ hash ) ;
return undef if ( IsDummy ( $ name ) || IsIgnored ( $ name ) ) ;
if ( ! $ hash - > { TYPE } ||
! $ modules { $ hash - > { TYPE } } ||
! $ modules { $ hash - > { TYPE } } { WriteFn } ) {
Log 1 , "No IO device or WriteFn found for $name" ;
return ;
}
unshift ( @ a , "" ) if ( int ( @ a ) == 1 ) ;
no strict "refs" ;
my $ ret = & { $ modules { $ hash - > { TYPE } } { WriteFn } } ( $ hash , @ a ) ;
use strict "refs" ;
return $ ret ;
}
2007-01-30 12:47:36 +00:00
#####################################
2007-03-19 14:59:37 +00:00
# i.e. split a line by ; (escape ;;), and execute each
2007-01-30 12:47:36 +00:00
sub
2014-02-07 07:27:47 +00:00
AnalyzeCommandChain ( $$ ; $ )
2007-01-30 12:47:36 +00:00
{
2014-02-07 07:27:47 +00:00
my ( $ c , $ cmd , $ allowed ) = @ _ ;
2011-02-05 09:26:55 +00:00
my @ ret ;
2010-05-18 08:08:53 +00:00
2012-02-08 12:41:00 +00:00
if ( $ cmd =~ m/^[ \t]*(#.*)?$/ ) { # Save comments
if ( ! $ init_done ) {
if ( $ currcfgfile ne AttrVal ( "global" , "statefile" , "" ) ) {
my $ nr = $ devcount + + ;
$ comments { $ nr } { TEXT } = $ cmd ;
$ comments { $ nr } { CFGFN } = $ currcfgfile
if ( $ currcfgfile ne AttrVal ( "global" , "configfile" , "" ) ) ;
}
}
return undef ;
}
2008-05-09 13:58:10 +00:00
$ cmd =~ s/#.*$//s ;
2012-02-08 12:41:00 +00:00
2011-04-25 08:11:52 +00:00
$ cmd =~ s/;;/SeMiCoLoN/g ;
2012-05-28 07:27:17 +00:00
my @ saveCmdList = @ cmdList ; # Needed for recursive calls
2012-03-30 07:11:39 +00:00
@ cmdList = split ( ";" , $ cmd ) ;
2012-05-03 05:41:24 +00:00
my $ subcmd ;
while ( defined ( $ subcmd = shift @ cmdList ) ) {
2011-04-25 08:11:52 +00:00
$ subcmd =~ s/SeMiCoLoN/;/g ;
2014-02-07 07:27:47 +00:00
my $ lret = AnalyzeCommand ( $ c , $ subcmd , $ allowed ) ;
2011-02-05 09:26:55 +00:00
push ( @ ret , $ lret ) if ( defined ( $ lret ) ) ;
2007-01-30 12:47:36 +00:00
}
2012-05-28 07:27:17 +00:00
@ cmdList = @ saveCmdList ;
2013-03-24 17:47:28 +00:00
$ evalSpecials = undef ;
2011-02-05 09:26:55 +00:00
return join ( "\n" , @ ret ) if ( @ ret ) ;
return undef ;
2007-01-30 12:47:36 +00:00
}
#####################################
sub
2011-01-22 21:53:18 +00:00
AnalyzePerlCommand ( $$ )
2007-01-30 12:47:36 +00:00
{
2011-02-05 09:26:55 +00:00
my ( $ cl , $ cmd ) = @ _ ;
2007-01-30 12:47:36 +00:00
2013-12-07 14:46:07 +00:00
$ cmd =~ s/\\ *\n/ /g ; # Multi-line. Probably not needed anymore
2013-03-24 17:47:28 +00:00
2011-02-05 09:26:55 +00:00
# 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 ;
2013-08-31 12:56:19 +00:00
my $ hms = sprintf ( "%02d:%02d:%02d" , $ hour , $ min , $ sec ) ;
2011-02-05 09:26:55 +00:00
my $ we = ( ( $ wday == 0 || $ wday == 6 ) ? 1 : 0 ) ;
if ( ! $ we ) {
my $ h2we = $ attr { global } { holiday2we } ;
2014-05-16 20:36:31 +00:00
if ( $ h2we && $ value { $ h2we } ) {
my ( $ a , $ b ) = ReplaceEventMap ( $ h2we , [ $ h2we , $ value { $ h2we } ] , 0 ) ;
$ we = 1 if ( $ b ne "none" ) ;
}
2011-02-05 09:26:55 +00:00
}
$ month + + ;
$ year += 1900 ;
2013-03-24 17:47:28 +00:00
if ( $ evalSpecials ) {
$ cmd = join ( "" , map { my $ n = substr ( $ _ , 1 ) ;
my $ v = $ evalSpecials - > { $ _ } ;
$ v =~ s/(['\\])/\\$1/g ;
"my \$$n='$v';" ;
} keys % { $ evalSpecials } )
. $ cmd ;
# Normally this is deleted in AnalyzeCommandChain, but ECMDDevice calls us
# directly, and combining perl with something else isnt allowed anyway.
$ evalSpecials = undef ;
}
2014-10-08 08:30:15 +00:00
$ cmdFromAnalyze = $ cmd ;
2011-02-05 09:26:55 +00:00
my $ ret = eval $ cmd ;
$ ret = $@ if ( $@ ) ;
2014-10-08 08:30:15 +00:00
$ cmdFromAnalyze = undef ;
2011-02-05 09:26:55 +00:00
return $ ret ;
2011-01-22 21:53:18 +00:00
}
sub
2014-02-07 07:27:47 +00:00
AnalyzeCommand ( $$ ; $ )
2011-01-22 21:53:18 +00:00
{
2014-02-07 07:27:47 +00:00
my ( $ cl , $ cmd , $ allowed ) = @ _ ;
2011-01-22 21:53:18 +00:00
2013-11-24 11:34:19 +00:00
$ cmd =~ s/^(\n|[ \t])*// ; # Strip space or \n at the begginning
2011-01-22 21:53:18 +00:00
$ cmd =~ s/[ \t]*$// ;
Log 5 , "Cmd: >$cmd<" ;
2011-02-05 09:26:55 +00:00
return undef if ( ! $ cmd ) ;
2011-01-22 21:53:18 +00:00
if ( $ cmd =~ m/^{.*}$/s ) { # Perl code
2014-02-13 08:04:53 +00:00
return "Forbidden command $cmd." if ( $ allowed && $ allowed !~ m/\bperl\b/ ) ;
2011-01-22 21:53:18 +00:00
return AnalyzePerlCommand ( $ cl , $ cmd ) ;
2007-01-30 12:47:36 +00:00
}
2011-02-05 09:26:55 +00:00
if ( $ cmd =~ m/^"(.*)"$/s ) { # Shell code in bg, to be able to call us from it
2014-02-13 08:04:53 +00:00
return "Forbidden command $cmd." if ( $ allowed && $ allowed !~ m/\bshell\b/ ) ;
2013-03-24 17:47:28 +00:00
if ( $ evalSpecials ) {
map { $ ENV { substr ( $ _ , 1 ) } = $ evalSpecials - > { $ _ } ; } keys % { $ evalSpecials } ;
}
2011-06-12 10:51:57 +00:00
my $ out = "" ;
2013-01-03 12:50:16 +00:00
$ out = ">> $currlogfile 2>&1" if ( $ currlogfile ne "-" && $^O ne "MSWin32" ) ;
2011-06-12 10:51:57 +00:00
system ( "$1 $out &" ) ;
2011-02-05 09:26:55 +00:00
return undef ;
2007-01-30 12:47:36 +00:00
}
$ cmd =~ s/^[ \t]*// ;
2013-03-24 17:47:28 +00:00
if ( $ evalSpecials ) {
map { my $ n = substr ( $ _ , 1 ) ; my $ v = $ evalSpecials - > { $ _ } ;
$ cmd =~ s/\$$n/$v/g ; } keys % { $ evalSpecials } ;
}
2007-01-30 12:47:36 +00:00
my ( $ fn , $ param ) = split ( "[ \t][ \t]*" , $ cmd , 2 ) ;
2011-02-05 09:26:55 +00:00
return undef if ( ! $ fn ) ;
2007-01-30 12:47:36 +00:00
#############
# Search for abbreviation
if ( ! defined ( $ cmds { $ fn } ) ) {
foreach my $ f ( sort keys % cmds ) {
2013-03-24 17:47:28 +00:00
if ( length ( $ f ) > length ( $ fn ) && lc ( substr ( $ f , 0 , length ( $ fn ) ) ) eq lc ( $ fn ) ) {
2007-01-30 12:47:36 +00:00
Log 5 , "$fn => $f" ;
$ fn = $ f ;
last ;
}
}
}
2013-03-31 08:08:11 +00:00
$ fn = $ cmds { $ fn } { ReplacedBy }
if ( defined ( $ cmds { $ fn } ) && defined ( $ cmds { $ fn } { ReplacedBy } ) ) ;
2013-03-30 12:41:09 +00:00
2014-02-13 08:04:53 +00:00
return "Forbidden command $fn." if ( $ allowed && $ allowed !~ m/\b$fn\b/ ) ;
2014-02-07 07:27:47 +00:00
2012-10-30 18:46:58 +00:00
#############
# autoload commands.
2013-03-30 12:41:09 +00:00
if ( ! defined ( $ cmds { $ fn } ) || ! defined ( $ cmds { $ fn } { Fn } ) ) {
2013-02-25 11:04:38 +00:00
map { $ fn = $ _ if ( lc ( $ fn ) eq lc ( $ _ ) ) ; } keys % modules ;
$ fn = LoadModule ( $ fn ) ;
2013-04-15 15:19:48 +00:00
$ fn = lc ( $ fn ) if ( defined ( $ cmds { lc ( $ fn ) } ) ) ;
2013-08-08 13:26:43 +00:00
return "Unknown command $fn, try help." if ( ! defined ( $ cmds { $ fn } ) ) ;
2012-10-30 18:46:58 +00:00
}
2013-08-08 13:26:43 +00:00
if ( $ cl && $ cmds { $ fn } { ClientFilter } &&
$ cl - > { TYPE } !~ m/$cmds{$fn}{ClientFilter}/ ) {
return "This command ($fn) is not valid for this input channel." ;
}
2013-04-15 15:16:19 +00:00
2007-01-30 12:47:36 +00:00
$ param = "" if ( ! defined ( $ param ) ) ;
no strict "refs" ;
2013-10-20 11:19:58 +00:00
my $ ret = & { $ cmds { $ fn } { Fn } } ( $ cl , $ param , $ fn ) ;
2007-01-30 12:47:36 +00:00
use strict "refs" ;
2011-02-05 09:26:55 +00:00
return undef if ( defined ( $ ret ) && $ ret eq "" ) ;
2007-12-31 14:43:02 +00:00
return $ ret ;
2007-01-30 12:47:36 +00:00
}
2007-12-29 15:57:42 +00:00
sub
devspec2array ( $ )
{
my ( $ name ) = @ _ ;
2009-01-17 10:01:56 +00:00
2008-09-06 08:33:55 +00:00
return "" if ( ! defined ( $ name ) ) ;
2013-07-22 16:24:01 +00:00
if ( defined ( $ defs { $ name } ) ) {
# FHEM2FHEM LOG mode fake device, avoid local set/attr/etc operations on it
return "FHEM2FHEM_FAKE_$name" if ( $ defs { $ name } { FAKEDEVICE } ) ;
return $ name ;
}
2008-12-03 16:45:26 +00:00
2013-12-08 11:23:23 +00:00
my ( @ ret , $ isAttr ) ;
2013-12-07 11:31:38 +00:00
foreach my $ l ( split ( "," , $ name ) ) { # List of elements
2014-01-21 18:00:07 +00:00
if ( defined ( $ defs { $ l } ) ) {
push @ ret , $ l ;
next ;
}
2013-12-07 11:31:38 +00:00
my @ names = sort keys % defs ;
my @ res ;
2013-12-09 21:17:25 +00:00
foreach my $ dName ( split ( ":FILTER=" , $ l ) ) {
my ( $ n , $ op , $ re ) = ( "NAME" , "=" , $ dName ) ;
2013-12-08 11:23:23 +00:00
if ( $ dName =~ m/^([^!]*)(=|!=)(.*)$/ ) {
( $ n , $ op , $ re ) = ( $ 1 , $ 2 , $ 3 ) ;
$ isAttr = 1 ; # Compatibility: return "" instead of $name
}
2013-12-07 14:46:07 +00:00
( $ n , $ op , $ re ) = ( $ 1 , "eval" , "" ) if ( $ dName =~ m/^{(.*)}$/ ) ;
2013-12-07 11:31:38 +00:00
@ res = ( ) ;
foreach my $ d ( @ names ) {
next if ( $ attr { $ d } && $ attr { $ d } { ignore } ) ;
2013-12-07 14:46:07 +00:00
if ( $ op eq "eval" ) {
2013-12-09 21:17:25 +00:00
my $ exec = EvalSpecials ( $ n , % { { "%DEVICE" = > $ d } } ) ;
2013-12-07 14:46:07 +00:00
push @ res , $ d if ( AnalyzePerlCommand ( undef , $ exec ) ) ;
next ;
}
2013-12-07 11:31:38 +00:00
my $ hash = $ defs { $ d } ;
2014-09-01 19:24:40 +00:00
if ( ! $ hash - > { TYPE } ) {
Log 1 , "Error: $d has no TYPE" ;
next ;
}
2013-12-07 11:31:38 +00:00
my $ val = $ hash - > { $ n } ;
if ( ! defined ( $ val ) ) {
my $ r = $ hash - > { READINGS } ;
$ val = $ r - > { $ n } { VAL } if ( $ r && $ r - > { $ n } ) ;
}
if ( ! defined ( $ val ) ) {
$ val = $ attr { $ d } { $ n } if ( $ attr { $ d } ) ;
}
2014-01-10 12:28:58 +00:00
$ val = "" if ( ! defined ( $ val ) ) ;
2014-01-21 18:00:07 +00:00
2014-02-27 08:04:33 +00:00
my $ lre = ( $ n eq "room" ? "(^|,)($re)(,|\$)" : "^($re)\$" ) ;
2013-12-07 11:31:38 +00:00
eval { # a bad regexp is deadly
2014-03-16 16:23:31 +00:00
if ( ( $ op eq "=" && $ val =~ m/$lre/s ) ||
( $ op eq "!=" && $ val !~ m/$lre/s ) ) {
2013-12-07 11:31:38 +00:00
push @ res , $ d
2009-05-30 15:11:56 +00:00
}
} ;
2014-01-21 18:00:07 +00:00
2009-05-30 15:11:56 +00:00
if ( $@ ) {
Log 1 , "devspec2array $name: $@" ;
return $ name ;
2009-01-17 10:01:56 +00:00
}
2009-05-30 15:11:56 +00:00
}
2013-12-07 11:31:38 +00:00
@ names = @ res ;
2007-12-29 15:57:42 +00:00
}
2013-12-07 11:31:38 +00:00
push @ ret , @ res ;
2007-12-29 15:57:42 +00:00
}
2013-12-08 11:23:23 +00:00
return $ name if ( ! @ ret && ! $ isAttr ) ;
2007-12-29 15:57:42 +00:00
return @ ret ;
}
2007-01-30 12:47:36 +00:00
#####################################
sub
CommandHelp ( $$ )
{
my ( $ cl , $ param ) = @ _ ;
my $ str = "\n" .
2008-11-01 21:27:10 +00:00
"Possible commands:\n\n" .
2007-01-30 12:47:36 +00:00
"Command Parameter Description\n" .
"-----------------------------------------------\n" ;
for my $ cmd ( sort keys % cmds ) {
2013-03-30 12:41:09 +00:00
next if ( ! $ cmds { $ cmd } { Hlp } ) ;
2013-08-08 13:26:43 +00:00
next if ( $ cl && $ cmds { $ cmd } { ClientFilter } &&
$ cl - > { TYPE } !~ m/$cmds{$cmd}{ClientFilter}/ ) ;
2007-01-30 12:47:36 +00:00
my @ a = split ( "," , $ cmds { $ cmd } { Hlp } , 2 ) ;
$ str . = sprintf ( "%-9s %-25s %s\n" , $ cmd , $ a [ 0 ] , $ a [ 1 ] ) ;
}
return $ str ;
}
2007-03-19 14:59:37 +00:00
#####################################
2007-01-30 12:47:36 +00:00
sub
CommandInclude ( $$ )
{
my ( $ cl , $ arg ) = @ _ ;
2007-03-28 17:26:27 +00:00
my $ fh ;
2011-02-05 09:26:55 +00:00
my @ ret ;
2012-02-08 12:41:00 +00:00
my $ oldcfgfile ;
2011-01-29 12:07:14 +00:00
2007-03-28 17:26:27 +00:00
if ( ! open ( $ fh , $ arg ) ) {
2007-01-30 12:47:36 +00:00
return "Can't open $arg: $!" ;
}
2012-06-13 11:03:30 +00:00
Log 1 , "Including $arg" ;
2012-02-08 12:41:00 +00:00
if ( ! $ init_done &&
$ arg ne AttrVal ( "global" , "statefile" , "" ) &&
$ arg ne AttrVal ( "global" , "configfile" , "" ) ) {
my $ nr = $ devcount + + ;
$ comments { $ nr } { TEXT } = "include $arg" ;
$ comments { $ nr } { CFGFN } = $ currcfgfile
if ( $ currcfgfile ne AttrVal ( "global" , "configfile" , "" ) ) ;
}
$ oldcfgfile = $ currcfgfile ;
$ currcfgfile = $ arg ;
2007-01-30 12:47:36 +00:00
my $ bigcmd = "" ;
$ rcvdquit = 0 ;
2007-03-28 17:26:27 +00:00
while ( my $ l = <$fh> ) {
2008-07-28 12:33:29 +00:00
$ l =~ s/[\r\n]//g ;
2011-06-12 10:51:57 +00:00
2008-12-23 15:53:13 +00:00
if ( $ l =~ m/^(.*)\\ *$/ ) { # Multiline commands
2013-11-19 13:44:02 +00:00
$ bigcmd . = "$1\n" ;
2007-01-30 12:47:36 +00:00
} else {
2011-01-29 12:07:14 +00:00
my $ tret = AnalyzeCommandChain ( $ cl , $ bigcmd . $ l ) ;
2011-02-05 09:26:55 +00:00
push @ ret , $ tret if ( defined ( $ tret ) ) ;
2007-01-30 12:47:36 +00:00
$ bigcmd = "" ;
}
last if ( $ rcvdquit ) ;
2011-06-12 10:51:57 +00:00
2007-01-30 12:47:36 +00:00
}
2012-02-08 12:41:00 +00:00
$ currcfgfile = $ oldcfgfile ;
2007-03-28 17:26:27 +00:00
close ( $ fh ) ;
2011-02-05 09:26:55 +00:00
return join ( "\n" , @ ret ) if ( @ ret ) ;
return undef ;
2007-01-30 12:47:36 +00:00
}
#####################################
sub
OpenLogfile ( $ )
{
my $ param = shift ;
2007-03-19 14:59:37 +00:00
close ( LOG ) ;
2007-01-30 12:47:36 +00:00
$ logopened = 0 ;
$ currlogfile = $ param ;
2013-08-25 11:49:30 +00:00
# STDOUT is closed in windows services per default
if ( ! $ winService - > { AsAService } && $ currlogfile eq "-" ) {
open LOG , '>&STDOUT' || die "Can't dup stdout: $!" ;
2007-01-30 12:47:36 +00:00
} else {
2008-11-01 21:27:10 +00:00
2012-07-23 13:16:02 +00:00
HandleArchiving ( $ defs { global } ) if ( $ defs { global } { currentlogfile } ) ;
2012-05-23 09:27:26 +00:00
$ defs { global } { currentlogfile } = $ param ;
2012-07-23 13:16:02 +00:00
$ defs { global } { logfile } = $ attr { global } { logfile } ;
2007-08-06 18:17:29 +00:00
2007-01-30 12:47:36 +00:00
open ( LOG , ">>$currlogfile" ) || return ( "Can't open $currlogfile: $!" ) ;
2011-01-29 12:07:14 +00:00
redirectStdinStdErr ( ) if ( $ init_done ) ;
2007-01-30 12:47:36 +00:00
}
LOG - > autoflush ( 1 ) ;
$ logopened = 1 ;
return undef ;
}
2011-01-29 12:07:14 +00:00
sub
redirectStdinStdErr ( )
{
# Redirect stdin/stderr
2011-02-28 07:27:10 +00:00
return if ( ! $ currlogfile || $ currlogfile eq "-" ) ;
2011-01-29 12:07:14 +00:00
2011-02-28 07:27:10 +00:00
open STDIN , '</dev/null' or print "Can't read /dev/null: $!\n" ;
2011-01-29 12:07:14 +00:00
close ( STDERR ) ;
2011-02-28 07:27:10 +00:00
open ( STDERR , ">>$currlogfile" ) or print "Can't append STDERR to log: $!\n" ;
2011-01-29 12:07:14 +00:00
STDERR - > autoflush ( 1 ) ;
close ( STDOUT ) ;
2011-02-28 07:27:10 +00:00
open STDOUT , '>&STDERR' or print "Can't dup stdout: $!\n" ;
2011-01-29 12:07:14 +00:00
STDOUT - > autoflush ( 1 ) ;
}
2007-01-30 12:47:36 +00:00
#####################################
sub
CommandRereadCfg ( $$ )
{
my ( $ cl , $ param ) = @ _ ;
2013-01-09 09:14:30 +00:00
my $ name = ( $ cl ? $ cl - > { NAME } : "__anonymous__" ) ;
2012-10-09 19:19:15 +00:00
my $ cfgfile = ( $ param ? $ param : $ attr { global } { configfile } ) ;
2014-03-01 07:59:19 +00:00
return "Cannot open $cfgfile: $!"
2014-04-20 19:20:42 +00:00
if ( ! - f $ cfgfile && ! configDBUsed ( ) ) ;
2007-01-30 12:47:36 +00:00
2012-10-09 19:19:15 +00:00
$ attr { global } { configfile } = $ cfgfile ;
2007-03-19 14:59:37 +00:00
WriteStatefile ( ) ;
2007-01-30 12:47:36 +00:00
2008-12-09 14:12:40 +00:00
$ reread_active = 1 ;
2011-11-10 13:47:26 +00:00
$ init_done = 0 ;
2012-09-19 14:44:18 +00:00
foreach my $ d ( sort { $ defs { $ b } { NR } <=> $ defs { $ a } { NR } } keys % defs ) {
2012-09-19 14:38:51 +00:00
my $ ret = CallFn ( $ d , "UndefFn" , $ defs { $ d } , $ d )
if ( $ name && $ name ne $ d ) ;
Log 1 , "$d is against deletion ($ret), continuing with rereadcfg anyway"
if ( $ ret ) ;
2012-09-19 14:44:18 +00:00
delete $ defs { $ d } ;
2007-01-30 12:47:36 +00:00
}
2012-03-02 20:26:30 +00:00
% comments = ( ) ;
2007-01-30 12:47:36 +00:00
% defs = ( ) ;
% attr = ( ) ;
2008-12-09 14:12:40 +00:00
% selectlist = ( ) ;
% readyfnlist = ( ) ;
2014-10-15 17:11:07 +00:00
my $ informMe = $ inform { $ name } ;
2012-06-23 16:22:28 +00:00
% inform = ( ) ;
2007-01-30 12:47:36 +00:00
2008-12-09 14:12:40 +00:00
doGlobalDef ( $ cfgfile ) ;
2014-03-01 07:59:19 +00:00
my $ ret ;
2014-04-20 19:20:42 +00:00
if ( configDBUsed ( ) ) {
2014-03-04 16:27:09 +00:00
$ ret = cfgDB_ReadAll ( $ cl ) ;
2007-03-19 15:34:34 +00:00
2014-03-01 07:59:19 +00:00
} else {
setGlobalAttrBeforeFork ( $ cfgfile ) ;
$ ret = CommandInclude ( $ cl , $ cfgfile ) ;
if ( $ attr { global } { statefile } && - r $ attr { global } { statefile } ) {
my $ ret2 = CommandInclude ( $ cl , $ attr { global } { statefile } ) ;
$ ret = ( defined ( $ ret ) ? "$ret\n$ret2" : $ ret2 ) if ( defined ( $ ret2 ) ) ;
}
2007-03-19 15:34:34 +00:00
}
2014-03-01 07:59:19 +00:00
2013-01-09 09:14:30 +00:00
$ defs { $ name } = $ selectlist { $ name } = $ cl if ( $ name && $ name ne "__anonymous__" ) ;
2014-10-15 17:11:07 +00:00
$ inform { $ name } = $ informMe if ( $ informMe ) ;
2015-01-24 12:38:25 +00:00
@ structChangeHist = ( ) ;
2014-10-15 17:11:07 +00:00
DoTrigger ( "global" , "REREADCFG" , 1 ) ;
2007-03-19 15:34:34 +00:00
2011-11-10 13:47:26 +00:00
$ init_done = 1 ;
2007-03-19 15:34:34 +00:00
$ reread_active = 0 ;
2007-01-30 12:47:36 +00:00
return $ ret ;
}
#####################################
sub
CommandQuit ( $$ )
{
my ( $ cl , $ param ) = @ _ ;
if ( ! $ cl ) {
$ rcvdquit = 1 ;
2011-02-05 09:26:55 +00:00
} else {
2012-06-23 16:22:28 +00:00
$ cl - > { rcvdQuit } = 1 ;
return "Bye..." if ( $ cl - > { prompt } ) ;
2007-01-30 12:47:36 +00:00
}
return undef ;
}
#####################################
sub
2007-03-19 14:59:37 +00:00
WriteStatefile ( )
2007-01-30 12:47:36 +00:00
{
2014-04-20 19:20:42 +00:00
if ( configDBUsed ( ) ) {
2014-09-26 17:55:17 +00:00
return cfgDB_SaveState ( ) ;
2014-03-01 07:59:19 +00:00
}
2012-05-12 11:36:54 +00:00
return "No statefile specified" if ( ! $ attr { global } { statefile } ) ;
2007-03-19 14:59:37 +00:00
if ( ! open ( SFH , ">$attr{global}{statefile}" ) ) {
2011-03-10 17:42:01 +00:00
my $ msg = "WriteStateFile: Cannot open $attr{global}{statefile}: $!" ;
2007-03-19 14:59:37 +00:00
Log 1 , $ msg ;
return $ msg ;
2007-01-30 12:47:36 +00:00
}
my $ t = localtime ;
print SFH "#$t\n" ;
foreach my $ d ( sort keys % defs ) {
2007-12-31 14:43:02 +00:00
next if ( $ defs { $ d } { TEMPORARY } ) ;
2014-05-01 08:19:44 +00:00
if ( $ defs { $ d } { VOLATILE } ) {
my $ def = $ defs { $ d } { DEF } ;
$ def =~ s/;/;;/g ; # follow-on-for-timer at
print SFH "define $d $defs{$d}{TYPE} $def\n" ;
}
2012-03-30 06:23:41 +00:00
my $ val = $ defs { $ d } { STATE } ;
if ( defined ( $ val ) &&
$ val ne "unknown" &&
$ val ne "Initialized" &&
$ val ne "???" ) {
$ val =~ s/;/;;/g ;
2013-12-03 21:06:40 +00:00
$ val =~ s/\n/\\\n/g ;
2012-03-30 06:23:41 +00:00
print SFH "setstate $d $val\n"
}
2007-01-30 12:47:36 +00:00
#############
# Now the detailed list
2007-03-19 14:59:37 +00:00
my $ r = $ defs { $ d } { READINGS } ;
if ( $ r ) {
foreach my $ c ( sort keys % { $ r } ) {
2010-03-22 14:31:37 +00:00
2012-03-30 06:23:41 +00:00
my $ rd = $ r - > { $ c } ;
if ( ! defined ( $ rd - > { TIME } ) ) {
Log 4 , "WriteStatefile $d $c: Missing TIME, using current time" ;
$ rd - > { TIME } = TimeNow ( ) ;
}
2010-03-22 14:31:37 +00:00
2012-03-30 06:23:41 +00:00
if ( ! defined ( $ rd - > { VAL } ) ) {
Log 4 , "WriteStatefile $d $c: Missing VAL, setting it to 0" ;
$ rd - > { VAL } = 0 ;
}
my $ val = $ rd - > { VAL } ;
$ val =~ s/;/;;/g ;
2013-12-03 21:06:40 +00:00
$ val =~ s/\n/\\\n/g ;
2012-03-30 06:23:41 +00:00
print SFH "setstate $d $rd->{TIME} $c $val\n" ;
2007-03-19 14:59:37 +00:00
}
2007-01-30 12:47:36 +00:00
}
2007-03-19 14:59:37 +00:00
}
2014-09-26 17:55:17 +00:00
return "$attr{global}{statefile}: $!" if ( ! close ( SFH ) ) ;
2012-05-12 11:36:54 +00:00
return "" ;
2007-03-19 14:59:37 +00:00
}
#####################################
sub
CommandSave ( $$ )
{
my ( $ cl , $ param ) = @ _ ;
2012-05-12 11:36:54 +00:00
2015-01-24 12:38:25 +00:00
if ( $ param eq "?" ) {
return "No structural changes." if ( ! @ structChangeHist ) ;
return "Last 10 structural changes:\n " . join ( "\n " , @ structChangeHist ) ;
}
@ structChangeHist = ( ) ;
2013-03-04 19:58:34 +00:00
DoTrigger ( "global" , "SAVE" , 1 ) ;
2014-09-26 17:55:17 +00:00
my $ ret = WriteStatefile ( ) ;
return $ ret if ( $ ret ) ;
$ ret = "" ; # cfgDB_SaveState may return undef
2007-03-19 14:59:37 +00:00
2014-04-20 19:20:42 +00:00
if ( configDBUsed ( ) ) {
2014-03-01 07:59:19 +00:00
$ ret = cfgDB_SaveCfg ( ) ;
return ( $ ret ? $ ret : "Saved configuration to the DB" ) ;
}
2007-03-19 14:59:37 +00:00
$ param = $ attr { global } { configfile } if ( ! $ param ) ;
return "No configfile attribute set and no argument specified" if ( ! $ param ) ;
if ( ! open ( SFH , ">$param" ) ) {
return "Cannot open $param: $!" ;
}
2012-02-08 12:41:00 +00:00
my % fh = ( "configfile" = > * SFH ) ;
2012-05-12 11:36:54 +00:00
my % skip ;
2012-02-08 12:41:00 +00:00
my % devByNr ;
map { $ devByNr { $ defs { $ _ } { NR } } = $ _ } keys % defs ;
2007-03-19 14:59:37 +00:00
2012-02-08 12:41:00 +00:00
for ( my $ i = 0 ; $ i < $ devcount ; $ i + + ) {
my ( $ h , $ d ) ;
if ( $ comments { $ i } ) {
$ h = $ comments { $ i } ;
} else {
$ d = $ devByNr { $ i } ;
next if ( ! defined ( $ d ) ||
$ defs { $ d } { TEMPORARY } || # e.g. WEBPGM connections
$ defs { $ d } { VOLATILE } ) ; # e.g at, will be saved to the statefile
$ h = $ defs { $ d } ;
}
my $ cfgfile = $ h - > { CFGFN } ? $ h - > { CFGFN } : "configfile" ;
my $ fh = $ fh { $ cfgfile } ;
if ( ! $ fh ) {
2012-05-12 11:36:54 +00:00
if ( ! open ( $ fh , ">$cfgfile" ) ) {
$ ret . = "Cannot open $cfgfile: $!, ignoring its content\n" ;
$ fh { $ cfgfile } = 1 ;
$ skip { $ cfgfile } = 1 ;
} else {
$ fh { $ cfgfile } = $ fh ;
}
2012-02-08 12:41:00 +00:00
}
2012-05-12 11:36:54 +00:00
next if ( $ skip { $ cfgfile } ) ;
2012-02-08 12:41:00 +00:00
if ( ! defined ( $ d ) ) {
print $ fh $ h - > { TEXT } , "\n" ;
next ;
}
2007-01-30 12:47:36 +00:00
2009-11-22 19:16:16 +00:00
if ( $ d ne "global" ) {
2012-04-12 07:21:37 +00:00
my $ def = $ defs { $ d } { DEF } ;
if ( defined ( $ def ) ) {
2007-11-26 14:56:45 +00:00
$ def =~ s/;/;;/g ;
2013-11-19 13:44:02 +00:00
$ def =~ s/\n/\\\n/g ;
2012-02-08 12:41:00 +00:00
print $ fh "define $d $defs{$d}{TYPE} $def\n" ;
2007-11-26 14:56:45 +00:00
} else {
2012-02-08 12:41:00 +00:00
print $ fh "define $d $defs{$d}{TYPE}\n" ;
2007-11-26 14:56:45 +00:00
}
2009-11-22 19:16:16 +00:00
}
2014-09-29 19:52:15 +00:00
foreach my $ a ( sort {
return - 1 if ( $ a eq "userattr" ) ; # userattr must be first
return 1 if ( $ b eq "userattr" ) ;
return $ a cmp $ b ;
} keys % { $ attr { $ d } } ) {
2011-01-22 21:53:18 +00:00
next if ( $ d eq "global" &&
2009-11-25 10:48:01 +00:00
( $ a eq "configfile" || $ a eq "version" ) ) ;
2012-03-24 07:30:55 +00:00
my $ val = $ attr { $ d } { $ a } ;
$ val =~ s/;/;;/g ;
2012-06-22 08:22:24 +00:00
$ val =~ s/\n/\\\n/g ;
2012-03-24 07:30:55 +00:00
print $ fh "attr $d $a $val\n" ;
2007-03-19 14:59:37 +00:00
}
2007-01-30 12:47:36 +00:00
}
2007-03-28 17:26:27 +00:00
print SFH "include $attr{global}{lastinclude}\n"
if ( $ attr { global } { lastinclude } ) ;
2014-09-26 17:25:28 +00:00
foreach my $ key ( keys % fh ) {
next if ( $ fh { $ key } eq "1" ) ; ## R/O include files
$ ret . = "$key: $!" if ( ! close ( $ fh { $ key } ) ) ;
2012-02-08 12:41:00 +00:00
}
2015-01-17 21:37:05 +00:00
2014-02-12 09:10:01 +00:00
return ( $ ret ? $ ret : "Wrote configuration to $param" ) ;
2007-01-30 12:47:36 +00:00
}
#####################################
sub
CommandShutdown ( $$ )
{
my ( $ cl , $ param ) = @ _ ;
2013-10-22 20:55:35 +00:00
return "Usage: shutdown [restart]"
if ( $ param && $ param ne "restart" ) ;
2013-01-30 10:39:30 +00:00
DoTrigger ( "global" , "SHUTDOWN" , 1 ) ;
2007-01-30 12:47:36 +00:00
Log 0 , "Server shutdown" ;
2009-07-03 06:53:50 +00:00
foreach my $ d ( sort keys % defs ) {
CallFn ( $ d , "ShutdownFn" , $ defs { $ d } ) ;
}
2007-03-19 14:59:37 +00:00
WriteStatefile ( ) ;
unlink ( $ attr { global } { pidfilename } ) if ( $ attr { global } { pidfilename } ) ;
2011-12-26 14:32:39 +00:00
if ( $ param && $ param eq "restart" ) {
2013-08-25 11:49:30 +00:00
if ( $^O !~ m/Win/ ) {
system ( "(sleep 2; exec $^X $0 $attr{global}{configfile})&" ) ;
} elsif ( $ winService - > { AsAService } ) {
# use the OS SCM to stop and start the service
exec ( 'cmd.exe /C net stop fhem & net start fhem' ) ;
}
2011-12-26 14:32:39 +00:00
}
2007-01-30 12:47:36 +00:00
exit ( 0 ) ;
}
#####################################
sub
DoSet ( @ )
{
my @ a = @ _ ;
my $ dev = $ a [ 0 ] ;
2013-01-03 12:50:16 +00:00
my $ hash = $ defs { $ dev } ;
return "Please define $dev first" if ( ! $ hash ) ;
2013-03-07 15:10:37 +00:00
return "Bogus entry $dev without TYPE" if ( ! $ hash - > { TYPE } ) ;
2013-01-03 12:50:16 +00:00
return "No set implemented for $dev" if ( ! $ modules { $ hash - > { TYPE } } { SetFn } ) ;
# No special handling needed fo the Usage check
return CallFn ( $ dev , "SetFn" , $ hash , @ a ) if ( $ a [ 1 ] && $ a [ 1 ] eq "?" ) ;
2010-12-27 09:42:16 +00:00
2011-07-30 13:22:25 +00:00
@ a = ReplaceEventMap ( $ dev , \ @ a , 0 ) if ( $ attr { $ dev } { eventMap } ) ;
2013-06-22 13:47:34 +00:00
$ hash - > { ".triggerUsed" } = 0 ;
2013-01-03 12:50:16 +00:00
my ( $ ret , $ skipTrigger ) = CallFn ( $ dev , "SetFn" , $ hash , @ a ) ;
2007-01-30 12:47:36 +00:00
return $ ret if ( $ ret ) ;
2012-11-05 07:32:55 +00:00
return undef if ( $ skipTrigger ) ;
2013-01-03 12:50:16 +00:00
2013-02-02 13:57:30 +00:00
# Backward compatibility. Use readingsUpdate in SetFn now
2013-07-10 19:19:30 +00:00
# case: DoSet is called from a notify triggered by DoSet with same dev
if ( defined ( $ hash - > { ".triggerUsed" } ) && $ hash - > { ".triggerUsed" } == 0 ) {
2013-02-17 13:55:05 +00:00
shift @ a ;
# set arg if the module did not triggered events
2013-02-02 13:57:30 +00:00
my $ arg = join ( " " , @ a ) if ( ! $ hash - > { CHANGED } || ! int ( @ { $ hash - > { CHANGED } } ) ) ;
2013-02-17 13:55:05 +00:00
DoTrigger ( $ dev , $ arg , 0 ) ;
2013-01-03 12:50:16 +00:00
}
2013-06-22 13:47:34 +00:00
delete ( $ hash - > { ".triggerUsed" } ) ;
2013-01-03 12:50:16 +00:00
return undef ;
2007-01-30 12:47:36 +00:00
}
2007-12-29 15:57:42 +00:00
2007-01-30 12:47:36 +00:00
#####################################
sub
CommandSet ( $$ )
{
my ( $ cl , $ param ) = @ _ ;
my @ a = split ( "[ \t][ \t]*" , $ param ) ;
2008-09-06 08:33:55 +00:00
return "Usage: set <name> <type-dependent-options>\n$namedef" if ( int ( @ a ) < 1 ) ;
2007-01-30 12:47:36 +00:00
my @ rets ;
2007-12-29 15:57:42 +00:00
foreach my $ sdev ( devspec2array ( $ a [ 0 ] ) ) {
2007-01-30 12:47:36 +00:00
2007-03-31 06:28:08 +00:00
$ a [ 0 ] = $ sdev ;
my $ ret = DoSet ( @ a ) ;
push @ rets , $ ret if ( $ ret ) ;
2007-01-30 12:47:36 +00:00
}
2007-03-31 06:28:08 +00:00
return join ( "\n" , @ rets ) ;
2007-01-30 12:47:36 +00:00
}
#####################################
sub
CommandGet ( $$ )
{
my ( $ cl , $ param ) = @ _ ;
my @ a = split ( "[ \t][ \t]*" , $ param ) ;
2008-09-06 08:33:55 +00:00
return "Usage: get <name> <type-dependent-options>\n$namedef" if ( int ( @ a ) < 1 ) ;
2007-12-29 15:57:42 +00:00
my @ rets ;
foreach my $ sdev ( devspec2array ( $ a [ 0 ] ) ) {
if ( ! defined ( $ defs { $ sdev } ) ) {
push @ rets , "Please define $sdev first" ;
next ;
}
if ( ! $ modules { $ defs { $ sdev } { TYPE } } { GetFn } ) {
push @ rets , "No get implemented for $sdev" ;
next ;
}
2007-01-30 12:47:36 +00:00
2007-12-29 15:57:42 +00:00
$ a [ 0 ] = $ sdev ;
my $ ret = CallFn ( $ sdev , "GetFn" , $ defs { $ sdev } , @ a ) ;
2013-08-10 08:51:31 +00:00
push @ rets , $ ret if ( defined ( $ ret ) && $ ret ne "" ) ;
2007-12-29 15:57:42 +00:00
}
return join ( "\n" , @ rets ) ;
2007-01-30 12:47:36 +00:00
}
2009-12-21 18:03:56 +00:00
#####################################
sub
2015-01-11 17:55:36 +00:00
LoadModule ( $; $ )
2009-12-21 18:03:56 +00:00
{
2015-01-11 17:55:36 +00:00
my ( $ m , $ ignoreErr ) = @ _ ;
2009-12-21 18:03:56 +00:00
if ( $ modules { $ m } && ! $ modules { $ m } { LOADED } ) { # autoload
my $ o = $ modules { $ m } { ORDER } ;
2015-01-11 17:55:36 +00:00
my $ ret = CommandReload ( undef , "${o}_$m" , $ ignoreErr ) ;
2010-08-14 10:35:12 +00:00
if ( $ ret ) {
2015-01-11 17:55:36 +00:00
Log 0 , $ ret if ( ! $ ignoreErr ) ;
2010-08-14 10:35:12 +00:00
return "UNDEFINED" ;
}
2009-12-21 18:03:56 +00:00
if ( ! $ modules { $ m } { LOADED } ) { # Case corrected by reload?
foreach my $ i ( keys % modules ) {
if ( uc ( $ m ) eq uc ( $ i ) && $ modules { $ i } { LOADED } ) {
delete ( $ modules { $ m } ) ;
$ m = $ i ;
last ;
}
}
}
}
return $ m ;
}
2007-01-30 12:47:36 +00:00
#####################################
sub
CommandDefine ( $$ )
{
my ( $ cl , $ def ) = @ _ ;
2007-03-19 14:59:37 +00:00
my @ a = split ( "[ \t][ \t]*" , $ def , 3 ) ;
2015-01-11 17:55:36 +00:00
my $ ignoreErr ;
if ( $ a [ 0 ] eq "-ignoreErr" ) { # RSS in fhem.cfg.demo, with no GD installed
$ def =~ s/\s*-ignoreErr\s*// ;
@ a = split ( "[ \t][ \t]*" , $ def , 3 ) ;
$ ignoreErr = 1 ;
}
2012-01-05 13:09:07 +00:00
my $ name = $ a [ 0 ] ;
2007-01-30 12:47:36 +00:00
return "Usage: define <name> <type> <type dependent arguments>"
if ( int ( @ a ) < 2 ) ;
2012-01-05 13:09:07 +00:00
return "$name already defined, delete it first" if ( defined ( $ defs { $ name } ) ) ;
return "Invalid characters in name (not A-Za-z0-9.:_): $name"
if ( $ name !~ m/^[a-z0-9.:_]*$/i ) ;
2007-03-19 14:59:37 +00:00
2008-07-25 14:14:24 +00:00
my $ m = $ a [ 1 ] ;
2008-09-06 08:33:55 +00:00
if ( ! $ modules { $ m } ) { # Perhaps just wrong case?
foreach my $ i ( keys % modules ) {
if ( uc ( $ m ) eq uc ( $ i ) ) {
$ m = $ i ;
last ;
}
}
}
2015-01-11 17:55:36 +00:00
my $ newm = LoadModule ( $ m , $ ignoreErr ) ;
2012-07-15 07:44:25 +00:00
return "Cannot load module $m" if ( $ newm eq "UNDEFINED" ) ;
$ m = $ newm ;
2008-07-25 14:14:24 +00:00
2014-04-05 06:23:39 +00:00
return "Unknown module $m" if ( ! $ modules { $ m } || ! $ modules { $ m } { DefFn } ) ;
2007-03-19 14:59:37 +00:00
2007-01-30 12:47:36 +00:00
my % hash ;
2012-01-05 13:09:07 +00:00
$ hash { NAME } = $ name ;
2008-07-25 14:14:24 +00:00
$ hash { TYPE } = $ m ;
2007-01-30 12:47:36 +00:00
$ hash { STATE } = "???" ;
2007-11-26 14:56:45 +00:00
$ hash { DEF } = $ a [ 2 ] if ( int ( @ a ) > 2 ) ;
2007-01-30 12:47:36 +00:00
$ hash { NR } = $ devcount + + ;
2012-02-08 12:41:00 +00:00
$ hash { CFGFN } = $ currcfgfile
if ( $ currcfgfile ne AttrVal ( "global" , "configfile" , "" ) ) ;
2007-01-30 12:47:36 +00:00
2008-11-01 21:27:10 +00:00
# If the device wants to issue initialization gets/sets, then it needs to be
2007-01-30 12:47:36 +00:00
# in the global hash.
2012-01-05 13:09:07 +00:00
$ defs { $ name } = \ % hash ;
2007-01-30 12:47:36 +00:00
2012-01-05 13:09:07 +00:00
my $ ret = CallFn ( $ name , "DefFn" , \ % hash , $ def ) ;
2007-02-11 17:58:23 +00:00
if ( $ ret ) {
2014-03-06 22:57:44 +00:00
Log 1 , "define $name $def: $ret" ;
2012-01-05 13:09:07 +00:00
delete $ defs { $ name } ; # Veto
delete $ attr { $ name } ;
2010-10-10 08:23:29 +00:00
2007-02-11 17:58:23 +00:00
} else {
2007-12-29 16:25:02 +00:00
foreach my $ da ( sort keys ( % defaultattr ) ) { # Default attributes
2012-01-05 13:09:07 +00:00
CommandAttr ( $ cl , "$name $da $defaultattr{$da}" ) ;
}
if ( $ modules { $ m } { NotifyFn } && ! $ hash { NTFY_ORDER } ) {
$ hash { NTFY_ORDER } = ( $ modules { $ m } { NotifyOrderPrefix } ?
$ modules { $ m } { NotifyOrderPrefix } : "50-" ) . $ name ;
2007-02-11 17:58:23 +00:00
}
2014-05-22 07:02:35 +00:00
% ntfyHash = ( ) ;
2015-01-24 12:38:25 +00:00
addStructChange ( "define" , $ name , $ def ) ;
2014-02-27 12:28:41 +00:00
DoTrigger ( "global" , "DEFINED $name" , 1 ) if ( $ init_done ) ;
2007-02-11 17:58:23 +00:00
}
2007-01-30 12:47:36 +00:00
return $ ret ;
}
2007-04-24 07:13:21 +00:00
#####################################
sub
CommandModify ( $$ )
{
my ( $ cl , $ def ) = @ _ ;
my @ a = split ( "[ \t]+" , $ def , 2 ) ;
return "Usage: modify <name> <type dependent arguments>"
2013-02-06 18:19:59 +00:00
if ( int ( @ a ) < 1 ) ;
2007-04-24 07:13:21 +00:00
# Return a list of modules
return "Define $a[0] first" if ( ! defined ( $ defs { $ a [ 0 ] } ) ) ;
2014-01-14 19:23:34 +00:00
% ntfyHash = ( ) ;
2007-04-24 07:13:21 +00:00
my $ hash = $ defs { $ a [ 0 ] } ;
2008-05-09 13:58:10 +00:00
$ hash - > { OLDDEF } = $ hash - > { DEF } ;
2007-04-24 07:13:21 +00:00
$ hash - > { DEF } = $ a [ 1 ] ;
2013-02-06 18:19:59 +00:00
my $ ret = CallFn ( $ a [ 0 ] , "DefFn" , $ hash ,
"$a[0] $hash->{TYPE}" . ( defined ( $ a [ 1 ] ) ? " $a[1]" : "" ) ) ;
2014-10-15 17:11:07 +00:00
if ( $ ret ) {
$ hash - > { DEF } = $ hash - > { OLDDEF } ;
} else {
2015-01-24 12:38:25 +00:00
addStructChange ( "modify" , $ a [ 0 ] , $ def ) ;
2014-10-15 17:11:07 +00:00
DoTrigger ( "global" , "MODIFIED $a[0]" , 1 ) if ( $ init_done ) ;
}
2008-05-09 13:58:10 +00:00
delete ( $ hash - > { OLDDEF } ) ;
2007-04-24 07:13:21 +00:00
return $ ret ;
}
2007-01-30 12:47:36 +00:00
#############
# internal
sub
2013-11-20 13:04:27 +00:00
AssignIoPort ( $; $ )
2007-01-30 12:47:36 +00:00
{
2013-11-20 13:04:27 +00:00
my ( $ hash , $ proposed ) = @ _ ;
2014-03-16 15:50:40 +00:00
my $ ht = $ hash - > { TYPE } ;
my $ hn = $ hash - > { NAME } ;
my $ hasIODevAttr = ( $ ht &&
$ modules { $ ht } { AttrList } &&
$ modules { $ ht } { AttrList } =~ m/IODev/ ) ;
2007-01-30 12:47:36 +00:00
2014-03-27 16:19:53 +00:00
$ proposed = $ attr { $ hn } { IODev }
if ( ! $ proposed && $ attr { $ hn } && $ attr { $ hn } { IODev } ) ;
2013-11-20 13:04:27 +00:00
if ( $ proposed && $ defs { $ proposed } ) {
$ hash - > { IODev } = $ defs { $ proposed } ;
2014-03-16 15:50:40 +00:00
$ attr { $ hn } { IODev } = $ proposed if ( $ hasIODevAttr ) ;
2013-11-20 13:04:27 +00:00
delete ( $ defs { $ proposed } { ".clientArray" } ) ;
return ;
}
2010-10-24 16:08:48 +00:00
# Set the I/O device, search for the last compatible one.
2008-07-28 12:33:29 +00:00
for my $ p ( sort { $ defs { $ b } { NR } <=> $ defs { $ a } { NR } } keys % defs ) {
2010-12-16 08:07:18 +00:00
my $ cl = $ defs { $ p } { Clients } ;
$ cl = $ modules { $ defs { $ p } { TYPE } } { Clients } if ( ! $ cl ) ;
2014-03-16 15:50:40 +00:00
if ( $ cl && $ defs { $ p } { NAME } ne $ hn ) { # e.g. RFR
2013-05-21 13:25:51 +00:00
my @ fnd = grep { $ hash - > { TYPE } =~ m/^$_$/ ; } split ( ":" , $ cl ) ;
if ( @ fnd ) {
$ hash - > { IODev } = $ defs { $ p } ;
2013-06-01 17:13:50 +00:00
delete ( $ defs { $ p } { ".clientArray" } ) ; # Force a recompute
2013-05-21 13:25:51 +00:00
last ;
}
2007-01-30 12:47:36 +00:00
}
}
2014-03-06 20:06:00 +00:00
if ( $ hash - > { IODev } ) {
2014-03-16 15:50:40 +00:00
# See CUL_WS_Attr() for details
$ attr { $ hn } { IODev } = $ hash - > { IODev } { NAME }
if ( $ hasIODevAttr && $ hash - > { TYPE } ne "CUL_WS" ) ;
2014-03-09 12:31:07 +00:00
2014-03-06 20:06:00 +00:00
} else {
if ( $ init_done ) {
2014-03-16 15:50:40 +00:00
Log 3 , "No I/O device found for $hn" ;
2014-03-06 20:06:00 +00:00
} else {
2014-03-09 12:31:07 +00:00
$ hash - > { IODevMissing } = 1 ;
2014-03-06 20:06:00 +00:00
}
}
2014-03-06 22:57:44 +00:00
return undef ;
2007-01-30 12:47:36 +00:00
}
2007-03-19 14:59:37 +00:00
2007-01-30 12:47:36 +00:00
#############
sub
2007-03-19 14:59:37 +00:00
CommandDelete ( $$ )
2007-01-30 12:47:36 +00:00
{
2007-03-19 14:59:37 +00:00
my ( $ cl , $ def ) = @ _ ;
2008-09-06 08:33:55 +00:00
return "Usage: delete <name>$namedef\n" if ( ! $ def ) ;
2007-03-19 14:59:37 +00:00
2015-01-24 12:38:25 +00:00
my @ rets ;
2007-12-29 15:57:42 +00:00
foreach my $ sdev ( devspec2array ( $ def ) ) {
if ( ! defined ( $ defs { $ sdev } ) ) {
push @ rets , "Please define $sdev first" ;
next ;
}
2007-01-30 12:47:36 +00:00
2007-12-29 15:57:42 +00:00
my $ ret = CallFn ( $ sdev , "UndefFn" , $ defs { $ sdev } , $ sdev ) ;
if ( $ ret ) {
push @ rets , $ ret ;
next ;
}
2013-04-28 12:40:28 +00:00
$ ret = CallFn ( $ sdev , "DeleteFn" , $ defs { $ sdev } , $ sdev ) ;
if ( $ ret ) {
push @ rets , $ ret ;
next ;
}
2008-09-06 08:33:55 +00:00
2014-05-29 10:25:01 +00:00
2008-09-06 08:33:55 +00:00
# Delete releated hashes
foreach my $ p ( keys % selectlist ) {
2011-10-02 12:27:51 +00:00
if ( $ selectlist { $ p } && $ selectlist { $ p } { NAME } eq $ sdev ) {
delete $ selectlist { $ p } ;
}
2008-09-06 08:33:55 +00:00
}
foreach my $ p ( keys % readyfnlist ) {
2009-08-12 08:08:14 +00:00
delete $ readyfnlist { $ p }
if ( $ readyfnlist { $ p } && $ readyfnlist { $ p } { NAME } eq $ sdev ) ;
2008-09-06 08:33:55 +00:00
}
2011-10-02 12:27:51 +00:00
my $ temporary = $ defs { $ sdev } { TEMPORARY } ;
2015-01-24 12:38:25 +00:00
addStructChange ( "delete" , $ sdev , $ sdev ) if ( ! $ temporary ) ;
delete ( $ attr { $ sdev } ) ;
delete ( $ defs { $ sdev } ) ;
2013-01-30 10:39:30 +00:00
DoTrigger ( "global" , "DELETED $sdev" , 1 ) if ( ! $ temporary ) ;
2008-09-06 08:33:55 +00:00
2007-12-29 15:57:42 +00:00
}
return join ( "\n" , @ rets ) ;
2007-01-30 12:47:36 +00:00
}
#############
sub
2007-12-29 15:57:42 +00:00
CommandDeleteAttr ( $$ )
2007-01-30 12:47:36 +00:00
{
my ( $ cl , $ def ) = @ _ ;
2007-03-19 14:59:37 +00:00
my @ a = split ( " " , $ def , 2 ) ;
2008-09-06 08:33:55 +00:00
return "Usage: deleteattr <name> [<attrname>]\n$namedef" if ( @ a < 1 ) ;
2007-01-30 12:47:36 +00:00
2015-01-24 12:38:25 +00:00
my @ rets ;
2007-12-29 15:57:42 +00:00
foreach my $ sdev ( devspec2array ( $ a [ 0 ] ) ) {
if ( ! defined ( $ defs { $ sdev } ) ) {
push @ rets , "Please define $sdev first" ;
next ;
}
$ a [ 0 ] = $ sdev ;
2013-01-13 15:16:31 +00:00
2013-11-09 13:45:34 +00:00
if ( $ a [ 1 ] && $ a [ 1 ] eq "userReadings" ) {
2013-02-17 13:55:05 +00:00
delete ( $ defs { $ sdev } { '.userReadings' } ) ;
2013-01-13 15:16:31 +00:00
}
2014-03-01 07:59:19 +00:00
my $ ret = CallFn ( $ sdev , "AttrFn" , "del" , @ a ) ;
2007-12-29 15:57:42 +00:00
if ( $ ret ) {
push @ rets , $ ret ;
next ;
}
if ( @ a == 1 ) {
delete ( $ attr { $ sdev } ) ;
2015-01-24 12:38:25 +00:00
addStructChange ( "deleteAttr" , $ sdev , $ def ) ;
2014-10-15 17:11:07 +00:00
DoTrigger ( "global" , "DELETEATTR $sdev" , 1 ) if ( $ init_done ) ;
2007-12-29 15:57:42 +00:00
} else {
delete ( $ attr { $ sdev } { $ a [ 1 ] } ) if ( defined ( $ attr { $ sdev } ) ) ;
2015-01-24 12:38:25 +00:00
addStructChange ( "deleteAttr" , $ sdev , $ def ) ;
2014-10-15 17:11:07 +00:00
DoTrigger ( "global" , "DELETEATTR $sdev $a[1]" , 1 ) if ( $ init_done ) ;
2007-12-29 15:57:42 +00:00
}
2007-01-30 12:47:36 +00:00
}
2007-12-29 15:57:42 +00:00
return join ( "\n" , @ rets ) ;
2007-01-30 12:47:36 +00:00
}
2013-08-07 11:18:15 +00:00
#############
sub
CommandDisplayAttr ( $$ )
{
my ( $ cl , $ def ) = @ _ ;
my @ a = split ( " " , $ def , 2 ) ;
return "Usage: displayattr <name> [<attrname>]\n$namedef" if ( @ a < 1 ) ;
my @ rets ;
my @ devspec = devspec2array ( $ a [ 0 ] ) ;
foreach my $ sdev ( @ devspec ) {
if ( ! defined ( $ defs { $ sdev } ) ) {
push @ rets , "Please define $sdev first" ;
next ;
}
my $ ap = $ attr { $ sdev } ;
next if ( ! $ ap ) ;
my $ d = ( @ devspec > 1 ? "$sdev " : "" ) ;
if ( defined ( $ a [ 1 ] ) ) {
push @ rets , "$d$ap->{$a[1]}" if ( defined ( $ ap - > { $ a [ 1 ] } ) ) ;
} else {
push @ rets , map { "$d$_ $ap->{$_}" } sort keys % { $ ap } ;
}
}
return join ( "\n" , @ rets ) ;
}
2013-01-19 13:36:29 +00:00
#############
sub
CommandDeleteReading ( $$ )
{
my ( $ cl , $ def ) = @ _ ;
my @ a = split ( " " , $ def , 2 ) ;
return "Usage: deletereading <name> <reading>\n$namedef" if ( @ a != 2 ) ;
2014-12-29 16:03:31 +00:00
eval { "" =~ m/$a[1]/ } ;
return "Bad regexp $a[1]: $@" if ( $@ ) ;
2014-01-14 19:23:34 +00:00
% ntfyHash = ( ) ;
2013-01-19 13:36:29 +00:00
my @ rets ;
foreach my $ sdev ( devspec2array ( $ a [ 0 ] ) ) {
if ( ! defined ( $ defs { $ sdev } ) ) {
push @ rets , "Please define $sdev first" ;
next ;
}
$ a [ 0 ] = $ sdev ;
my $ readingspec = '^' . $ a [ 1 ] . '$' ;
2013-01-20 09:21:35 +00:00
foreach my $ reading ( grep { /$readingspec/ }
keys % { $ defs { $ sdev } { READINGS } } ) {
2013-01-19 13:36:29 +00:00
delete ( $ defs { $ sdev } { READINGS } { $ reading } ) ;
push @ rets , "Deleted reading $reading for device $sdev" ;
}
}
return join ( "\n" , @ rets ) ;
}
2013-08-22 15:13:44 +00:00
sub
CommandSetReading ( $$ )
{
my ( $ cl , $ def ) = @ _ ;
my @ a = split ( " " , $ def , 3 ) ;
return "Usage: setreading <name> <reading> <value>\n$namedef" if ( @ a != 3 ) ;
my @ rets ;
foreach my $ sdev ( devspec2array ( $ a [ 0 ] ) ) {
if ( ! defined ( $ defs { $ sdev } ) ) {
push @ rets , "Please define $sdev first" ;
next ;
}
readingsSingleUpdate ( $ defs { $ sdev } , $ a [ 1 ] , $ a [ 2 ] , 1 ) ;
}
2014-11-05 06:51:46 +00:00
return join ( "\n" , @ rets ) ;
2013-08-22 15:13:44 +00:00
}
2013-01-19 13:36:29 +00:00
#############
2007-10-21 11:35:58 +00:00
sub
PrintHash ( $$ )
{
my ( $ h , $ lev ) = @ _ ;
2013-01-03 12:50:16 +00:00
my $ si = AttrVal ( "global" , "showInternalValues" , 0 ) ;
2013-04-13 10:39:20 +00:00
return "" if ( $ h - > { ".visited" } ) ;
$ h - > { ".visited" } = 1 ;
2007-10-21 11:35:58 +00:00
my ( $ str , $ sstr ) = ( "" , "" ) ;
foreach my $ c ( sort keys % { $ h } ) {
2013-04-13 10:39:20 +00:00
next if ( ! $ si && $ c =~ m/^\./ || $ c eq ".visited" ) ;
2007-10-21 11:35:58 +00:00
if ( ref ( $ h - > { $ c } ) ) {
if ( ref ( $ h - > { $ c } ) eq "HASH" ) {
if ( defined ( $ h - > { $ c } { TIME } ) && defined ( $ h - > { $ c } { VAL } ) ) {
$ str . = sprintf ( "%*s %-19s %-15s %s\n" ,
$ lev , " " , $ h - > { $ c } { TIME } , $ c , $ h - > { $ c } { VAL } ) ;
2007-11-26 08:27:04 +00:00
} elsif ( $ c eq "IODev" || $ c eq "HASH" ) {
2007-10-21 11:35:58 +00:00
$ str . = sprintf ( "%*s %-10s %s\n" , $ lev , " " , $ c , $ h - > { $ c } { NAME } ) ;
2011-11-06 18:49:25 +00:00
2007-10-21 11:35:58 +00:00
} else {
$ sstr . = sprintf ( "%*s %s:\n" ,
$ lev , " " , uc ( substr ( $ c , 0 , 1 ) ) . lc ( substr ( $ c , 1 ) ) ) ;
$ sstr . = PrintHash ( $ h - > { $ c } , $ lev + 2 ) ;
}
2011-11-06 18:49:25 +00:00
} elsif ( ref ( $ h - > { $ c } ) eq "ARRAY" ) {
$ sstr . = sprintf ( "%*s %s:\n" , $ lev , " " , $ c ) ;
foreach my $ v ( @ { $ h - > { $ c } } ) {
$ sstr . = sprintf ( "%*s %s\n" , $ lev + 2 , " " , $ v ) ;
}
2007-10-21 11:35:58 +00:00
}
} else {
2013-01-07 19:52:24 +00:00
my $ v = $ h - > { $ c } ;
$ str . = sprintf ( "%*s %-10s %s\n" , $ lev , " " , $ c , defined ( $ v ) ? $ v : "" ) ;
2007-10-21 11:35:58 +00:00
}
}
2013-04-13 10:39:20 +00:00
delete $ h - > { ".visited" } ;
2007-10-21 11:35:58 +00:00
return $ str . $ sstr ;
}
2007-03-19 14:59:37 +00:00
2007-01-30 12:47:36 +00:00
#####################################
sub
CommandList ( $$ )
{
my ( $ cl , $ param ) = @ _ ;
2007-03-19 14:59:37 +00:00
my $ str = "" ;
2007-01-30 12:47:36 +00:00
2013-01-03 12:50:16 +00:00
if ( ! $ param ) { # List of all devices
2007-01-30 12:47:36 +00:00
$ str = "\nType list <name> for detailed info.\n" ;
my $ lt = "" ;
2007-03-19 14:59:37 +00:00
# Sort first by type then by name
2010-01-21 19:29:47 +00:00
for my $ d ( sort { my $ x = $ modules { $ defs { $ a } { TYPE } } { ORDER } . $ defs { $ a } { TYPE } cmp
$ modules { $ defs { $ b } { TYPE } } { ORDER } . $ defs { $ b } { TYPE } ;
$ x = ( $ a cmp $ b ) if ( $ x == 0 ) ; $ x ; } keys % defs ) {
2010-01-01 14:53:03 +00:00
next if ( IsIgnored ( $ d ) ) ;
2007-01-30 12:47:36 +00:00
my $ t = $ defs { $ d } { TYPE } ;
2007-03-19 14:59:37 +00:00
$ str . = "\n$t:\n" if ( $ t ne $ lt ) ;
2007-01-30 12:47:36 +00:00
$ str . = sprintf ( " %-20s (%s)\n" , $ d , $ defs { $ d } { STATE } ) ;
$ lt = $ t ;
}
2013-01-03 12:50:16 +00:00
} else { # devspecArray
2007-01-30 12:47:36 +00:00
2009-11-25 11:13:44 +00:00
my @ arg = split ( " " , $ param ) ;
my @ list = devspec2array ( $ arg [ 0 ] ) ;
if ( $ arg [ 1 ] ) {
2013-01-03 12:50:16 +00:00
foreach my $ sdev ( @ list ) { # Show a Hash-Entry or Reading for each device
2009-11-25 11:13:44 +00:00
2014-04-30 09:32:22 +00:00
if ( $ defs { $ sdev } ) {
2014-11-30 07:41:36 +00:00
if ( defined ( $ defs { $ sdev } { $ arg [ 1 ] } ) ) {
2014-04-30 09:32:22 +00:00
$ str . = sprintf ( "%-20s %s\n" , $ sdev , $ defs { $ sdev } { $ arg [ 1 ] } ) ;
} elsif ( $ defs { $ sdev } { READINGS } &&
2014-11-30 07:41:36 +00:00
defined ( $ defs { $ sdev } { READINGS } { $ arg [ 1 ] } ) ) {
2014-04-30 09:32:22 +00:00
$ str . = sprintf ( "%-20s %s %s\n" , $ sdev ,
$ defs { $ sdev } { READINGS } { $ arg [ 1 ] } { TIME } ,
$ defs { $ sdev } { READINGS } { $ arg [ 1 ] } { VAL } ) ;
2014-11-30 07:41:36 +00:00
} elsif ( $ attr { $ sdev } &&
defined ( $ attr { $ sdev } { $ arg [ 1 ] } ) ) {
2014-04-30 09:32:22 +00:00
$ str . = sprintf ( "%-20s %s\n" , $ sdev , $ attr { $ sdev } { $ arg [ 1 ] } ) ;
}
2009-11-25 11:13:44 +00:00
}
}
2013-01-03 12:50:16 +00:00
} elsif ( @ list == 1 ) { # Details
2008-12-03 16:45:26 +00:00
my $ sdev = $ list [ 0 ] ;
2007-12-29 15:57:42 +00:00
if ( ! defined ( $ defs { $ sdev } ) ) {
$ str . = "No device named $param found" ;
2008-12-03 16:45:26 +00:00
} else {
$ str . = "Internals:\n" ;
$ str . = PrintHash ( $ defs { $ sdev } , 2 ) ;
$ str . = "Attributes:\n" ;
$ str . = PrintHash ( $ attr { $ sdev } , 2 ) ;
}
2009-11-25 11:13:44 +00:00
2008-12-03 16:45:26 +00:00
} else {
2013-01-03 12:50:16 +00:00
foreach my $ sdev ( @ list ) { # List of devices
2008-12-03 16:45:26 +00:00
$ str . = "$sdev\n" ;
2007-12-29 15:57:42 +00:00
}
2009-11-25 11:13:44 +00:00
2007-12-29 15:57:42 +00:00
}
2007-01-30 12:47:36 +00:00
}
return $ str ;
}
#####################################
sub
2015-01-11 17:55:36 +00:00
CommandReload ( $$ ; $ )
2007-01-30 12:47:36 +00:00
{
2015-01-11 17:55:36 +00:00
my ( $ cl , $ param , $ ignoreErr ) = @ _ ;
2007-01-30 12:47:36 +00:00
my % hash ;
2008-07-25 14:14:24 +00:00
$ param =~ s , / , , g ;
2007-01-30 12:47:36 +00:00
$ param =~ s , \ . pm $, , g ;
2011-01-29 12:07:14 +00:00
my $ file = "$attr{global}{modpath}/FHEM/$param.pm" ;
2014-04-22 19:11:59 +00:00
my $ cfgDB = '-' ;
if ( ! - r "$file" ) {
if ( configDBUsed ( ) ) {
# try to find the file in configDB
my $ r = _cfgDB_Fileexport ( $ file ) ; # create file temporarily
return "Can't read $file from configDB." if ( $ r =~ m/^0/ ) ;
$ cfgDB = 'X' ;
} else {
# configDB not used and file not found: it's a real error!
return "Can't read $file: $!" ;
}
}
2007-01-30 12:47:36 +00:00
my $ m = $ param ;
2008-04-28 16:26:10 +00:00
$ m =~ s , ^ ( [ 0 - 9 ] [ 0 - 9 ] ) _ , , ;
2008-07-25 14:14:24 +00:00
my $ order = ( defined ( $ 1 ) ? $ 1 : "00" ) ;
2009-11-08 14:18:06 +00:00
Log 5 , "Loading $file" ;
2008-04-28 17:27:14 +00:00
2008-05-09 13:58:10 +00:00
no strict "refs" ;
2012-04-17 11:10:32 +00:00
my $ ret = eval {
2008-05-11 21:03:13 +00:00
my $ ret = do "$file" ;
2014-04-22 19:11:59 +00:00
unlink ( $ file ) if ( $ cfgDB eq 'X' ) ; # delete temp file
2010-01-29 07:37:47 +00:00
if ( ! $ ret ) {
2015-01-11 17:55:36 +00:00
Log 1 , "reload: Error:Modul $param deactivated:\n $@" if ( ! $ ignoreErr ) ;
2012-04-17 11:10:32 +00:00
return $@ ;
2008-05-11 21:03:13 +00:00
}
2008-07-25 14:14:24 +00:00
# Get the name of the initialize function. This may differ from the
# filename as sometimes we live on a FAT fs with wrong case.
my $ fnname = $ m ;
2008-04-28 17:27:14 +00:00
foreach my $ i ( keys % main:: ) {
if ( $ i =~ m/^(${m})_initialize$/i ) {
2008-07-25 14:14:24 +00:00
$ fnname = $ 1 ;
2008-04-28 17:27:14 +00:00
last ;
}
}
2012-04-17 11:10:32 +00:00
& { "${fnname}_Initialize" } ( \ % hash ) ;
2008-09-06 08:33:55 +00:00
$ m = $ fnname ;
2012-04-17 11:10:32 +00:00
return undef ;
2007-01-30 12:47:36 +00:00
} ;
use strict "refs" ;
2010-01-29 07:37:47 +00:00
return "$@" if ( $@ ) ;
2012-04-17 11:10:32 +00:00
return $ ret if ( $ ret ) ;
2010-01-29 07:37:47 +00:00
2010-01-01 13:48:33 +00:00
my ( $ defptr , $ ldata ) ;
if ( $ modules { $ m } ) {
$ defptr = $ modules { $ m } { defptr } ;
$ ldata = $ modules { $ m } { ldata } ;
}
2007-03-19 14:59:37 +00:00
$ modules { $ m } = \ % hash ;
2008-04-28 16:26:10 +00:00
$ modules { $ m } { ORDER } = $ order ;
2008-07-25 14:14:24 +00:00
$ modules { $ m } { LOADED } = 1 ;
2010-01-01 13:48:33 +00:00
$ modules { $ m } { defptr } = $ defptr if ( $ defptr ) ;
2014-04-15 16:32:25 +00:00
$ modules { $ m } { ldata } = $ ldata if ( $ ldata ) ;
2007-01-30 12:47:36 +00:00
return undef ;
}
2007-03-19 15:34:34 +00:00
#####################################
sub
CommandRename ( $$ )
{
my ( $ cl , $ param ) = @ _ ;
my ( $ old , $ new ) = split ( " " , $ param ) ;
2013-05-25 07:16:54 +00:00
return "old name is empty" if ( ! defined ( $ old ) ) ;
return "new name is empty" if ( ! defined ( $ new ) ) ;
2012-10-20 07:11:56 +00:00
2007-03-19 15:34:34 +00:00
return "Please define $old first" if ( ! defined ( $ defs { $ old } ) ) ;
2012-11-27 07:47:41 +00:00
return "$new already defined" if ( defined ( $ defs { $ new } ) ) ;
2011-01-29 07:32:48 +00:00
return "Invalid characters in name (not A-Za-z0-9.:_): $new"
if ( $ new !~ m/^[a-z0-9.:_]*$/i ) ;
2007-03-19 15:34:34 +00:00
return "Cannot rename global" if ( $ old eq "global" ) ;
2014-01-14 19:23:34 +00:00
% ntfyHash = ( ) ;
2007-03-19 15:34:34 +00:00
$ defs { $ new } = $ defs { $ old } ;
2008-09-06 08:33:55 +00:00
$ defs { $ new } { NAME } = $ new ;
delete ( $ defs { $ old } ) ; # The new pointer will preserve the hash
2007-03-19 15:34:34 +00:00
2007-06-09 08:57:46 +00:00
$ attr { $ new } = $ attr { $ old } if ( defined ( $ attr { $ old } ) ) ;
2007-03-19 15:34:34 +00:00
delete ( $ attr { $ old } ) ;
2007-06-09 08:57:46 +00:00
$ oldvalue { $ new } = $ oldvalue { $ old } if ( defined ( $ oldvalue { $ old } ) ) ;
delete ( $ oldvalue { $ old } ) ;
2012-10-28 21:28:41 +00:00
CallFn ( $ new , "RenameFn" , $ new , $ old ) ; # ignore replies
2015-01-24 12:38:25 +00:00
addStructChange ( "rename" , $ new , $ param ) ;
2013-01-30 10:39:30 +00:00
DoTrigger ( "global" , "RENAMED $old $new" , 1 ) ;
2007-03-19 15:34:34 +00:00
return undef ;
}
2007-03-19 14:59:37 +00:00
#####################################
sub
getAllAttr ( $ )
{
my $ d = shift ;
2008-12-03 16:45:26 +00:00
return "" if ( ! $ defs { $ d } ) ;
2014-10-04 14:32:17 +00:00
my $ list = $ AttrList ; # Global values
2007-03-19 14:59:37 +00:00
$ list . = " " . $ modules { $ defs { $ d } { TYPE } } { AttrList }
if ( $ modules { $ defs { $ d } { TYPE } } { AttrList } ) ;
$ list . = " " . $ attr { global } { userattr }
if ( $ attr { global } { userattr } ) ;
2014-09-29 19:52:15 +00:00
$ list . = " " . $ attr { $ d } { userattr }
if ( $ attr { $ d } && $ attr { $ d } { userattr } ) ;
$ list . = " userattr" ;
2007-03-19 14:59:37 +00:00
return $ list ;
}
2008-08-08 10:46:25 +00:00
#####################################
2013-08-07 13:06:49 +00:00
sub
getAllGets ( $ )
{
my $ d = shift ;
my $ a2 = CommandGet ( undef , "$d ?" ) ;
return "" if ( $ a2 !~ m/unknown.*choose one of /i ) ;
$ a2 =~ s/.*choose one of // ;
return $ a2 ;
}
#####################################
2008-08-08 10:46:25 +00:00
sub
getAllSets ( $ )
{
my $ d = shift ;
2012-02-20 12:38:48 +00:00
if ( AttrVal ( "global" , "apiversion" , 1 ) > 1 ) {
my @ setters = getSetters ( $ defs { $ d } ) ;
return join ( " " , @ setters ) ;
}
2008-08-08 10:46:25 +00:00
my $ a2 = CommandSet ( undef , "$d ?" ) ;
$ a2 =~ s/.*choose one of // ;
$ a2 = "" if ( $ a2 =~ /^No set implemented for/ ) ;
2013-01-03 12:50:16 +00:00
return "" if ( $ a2 eq "" ) ;
2012-07-05 07:02:21 +00:00
my $ em = AttrVal ( $ d , "eventMap" , undef ) ;
if ( $ em ) {
2013-01-22 19:16:46 +00:00
# Delete the first word of the translation (.*:), else it will be
# interpreted as the single possible value for a dropdown
# Why is the .*= deleted?
2012-07-10 06:23:04 +00:00
$ em = join ( " " , grep { ! / / }
2013-08-09 18:02:45 +00:00
map { $ _ =~ s/.*?=//s ;
$ _ =~ s/.*?://s ; $ _ }
2013-12-13 08:15:43 +00:00
attrSplit ( $ em ) ) ;
2012-07-05 07:02:21 +00:00
$ a2 = "$em $a2" ;
}
2008-08-08 10:46:25 +00:00
return $ a2 ;
}
2007-01-30 12:47:36 +00:00
sub
2013-08-25 11:49:30 +00:00
GlobalAttr ( $$ $$ )
2007-01-30 12:47:36 +00:00
{
2008-12-09 14:12:40 +00:00
my ( $ type , $ me , $ name , $ val ) = @ _ ;
return if ( $ type ne "set" ) ;
2007-03-19 14:59:37 +00:00
################
2007-12-29 15:57:42 +00:00
if ( $ name eq "logfile" ) {
2007-03-19 14:59:37 +00:00
my @ t = localtime ;
2007-12-29 15:57:42 +00:00
my $ ret = OpenLogfile ( ResolveDateWildcards ( $ val , @ t ) ) ;
2007-03-19 14:59:37 +00:00
if ( $ ret ) {
return $ ret if ( $ init_done ) ;
die ( $ ret ) ;
}
}
################
2007-12-29 15:57:42 +00:00
elsif ( $ name eq "verbose" ) {
if ( $ val =~ m/^[0-5]$/ ) {
2007-03-19 14:59:37 +00:00
return undef ;
} else {
$ attr { global } { verbose } = 3 ;
return "Valid value for verbose are 0,1,2,3,4,5" ;
}
2007-01-30 12:47:36 +00:00
}
2007-12-29 15:57:42 +00:00
elsif ( $ name eq "modpath" ) {
2007-03-19 14:59:37 +00:00
return "modpath must point to a directory where the FHEM subdir is"
2007-12-29 15:57:42 +00:00
if ( ! - d "$val/FHEM" ) ;
my $ modpath = "$val/FHEM" ;
2007-01-30 12:47:36 +00:00
2007-03-19 14:59:37 +00:00
opendir ( DH , $ modpath ) || return "Can't read $modpath: $!" ;
2013-10-21 20:41:33 +00:00
push @ INC , $ modpath if ( ! grep ( /\Q$modpath\E/ , @ INC ) ) ;
2013-08-18 15:23:42 +00:00
$ attr { global } { version } = $ cvsid ;
2007-03-19 14:59:37 +00:00
my $ counter = 0 ;
2007-01-30 12:47:36 +00:00
2014-04-22 19:11:59 +00:00
if ( configDBUsed ( ) ) {
2014-05-03 09:33:39 +00:00
my $ list = cfgDB_Read99 ( ) ; # retrieve filelist from configDB
if ( $ list ) {
foreach my $ m ( split ( /,/ , $ list ) ) {
2014-05-03 09:41:12 +00:00
$ m =~ m/^([0-9][0-9])_(.*)\.pm$/ ;
CommandReload ( undef , $ m ) if ( ! $ modules { $ 2 } { LOADED } ) ;
2014-05-03 09:33:39 +00:00
$ counter + + ;
}
2014-04-22 19:11:59 +00:00
}
}
2008-07-25 14:14:24 +00:00
foreach my $ m ( sort readdir ( DH ) ) {
next if ( $ m !~ m/^([0-9][0-9])_(.*)\.pm$/ ) ;
$ modules { $ 2 } { ORDER } = $ 1 ;
2008-07-28 12:33:29 +00:00
CommandReload ( undef , $ m ) # Always load utility modules
2008-09-06 08:33:55 +00:00
if ( $ 1 eq "99" && ! $ modules { $ 2 } { LOADED } ) ;
2007-03-19 14:59:37 +00:00
$ counter + + ;
}
closedir ( DH ) ;
if ( ! $ counter ) {
2008-04-28 16:26:10 +00:00
return "No modules found, set modpath to a directory in which a " .
"subdirectory called \"FHEM\" exists wich in turn contains " .
"the fhem module files <*>.pm" ;
2007-03-19 14:59:37 +00:00
}
2012-06-19 15:12:22 +00:00
2007-01-30 12:47:36 +00:00
}
2007-03-19 14:59:37 +00:00
2007-01-30 12:47:36 +00:00
return undef ;
}
2007-12-29 15:57:42 +00:00
#####################################
sub
CommandAttr ( $$ )
{
my ( $ cl , $ param ) = @ _ ;
2015-01-24 12:38:25 +00:00
my ( $ ret , @ a ) ;
2011-01-22 21:53:18 +00:00
2008-01-19 19:02:30 +00:00
@ a = split ( " " , $ param , 3 ) if ( $ param ) ;
2008-11-01 21:27:10 +00:00
2008-09-06 08:33:55 +00:00
return "Usage: attr <name> <attrname> [<attrvalue>]\n$namedef"
if ( @ a && @ a < 2 ) ;
2007-12-29 15:57:42 +00:00
my @ rets ;
foreach my $ sdev ( devspec2array ( $ a [ 0 ] ) ) {
2014-10-15 17:11:07 +00:00
my $ hash = $ defs { $ sdev } ;
my $ attrName = $ a [ 1 ] ;
2013-01-22 18:08:53 +00:00
if ( ! defined ( $ hash ) ) {
2015-01-11 17:55:36 +00:00
push @ rets , "Please define $sdev first" if ( $ init_done ) ; #define -ignoreErr
2007-12-29 15:57:42 +00:00
next ;
}
my $ list = getAllAttr ( $ sdev ) ;
2014-10-15 17:11:07 +00:00
if ( $ attrName eq "?" ) {
push @ rets , "$sdev: unknown attribute $attrName, choose one of $list" ;
2007-12-29 15:57:42 +00:00
next ;
}
2010-12-21 07:45:28 +00:00
2014-10-15 17:11:07 +00:00
if ( " $list " !~ m/ ${attrName}[ :;]/ ) {
2010-12-21 07:45:28 +00:00
my $ found = 0 ;
foreach my $ atr ( split ( "[ \t]" , $ list ) ) { # is it a regexp?
2014-10-15 17:11:07 +00:00
if ( $ { attrName } =~ m/^$atr$/ ) {
2010-12-21 07:45:28 +00:00
$ found + + ;
last ;
}
}
2011-02-27 18:47:13 +00:00
if ( ! $ found ) {
2014-10-15 17:11:07 +00:00
push @ rets , "$sdev: unknown attribute $attrName. " .
2014-02-15 08:34:09 +00:00
"Type 'attr $a[0] ?' for a detailed list." ;
2011-02-27 18:47:13 +00:00
next ;
}
2007-12-29 15:57:42 +00:00
}
2014-10-15 17:11:07 +00:00
if ( $ attrName eq "userReadings" ) {
2013-01-13 15:16:31 +00:00
my % userReadings ;
2013-05-17 18:16:54 +00:00
# myReading1[:trigger1] [modifier1] { codecodecode1 }, ...
2013-01-22 18:08:53 +00:00
my $ arg = $ a [ 2 ] ;
2013-01-13 15:16:31 +00:00
2013-05-17 18:16:54 +00:00
# matches myReading1[:trigger2] { codecode1 }
2014-10-03 10:16:50 +00:00
my $ regexi = '\s*([\w.-]+)(:\S*)?\s+((\w+)\s+)?({.*?})\s*' ;
2013-01-13 15:16:31 +00:00
my $ regexo = '^(' . $ regexi . ')(,\s*(.*))*$' ;
2013-05-17 18:16:54 +00:00
#Log 1, "arg is $arg";
2013-01-13 15:16:31 +00:00
while ( $ arg =~ /$regexo/ ) {
my $ userReading = $ 2 ;
2013-05-17 18:16:54 +00:00
my $ trigger = $ 3 ? $ 3 : undef ;
my $ modifier = $ 5 ? $ 5 : "none" ;
my $ perlCode = $ 6 ;
#Log 1, sprintf("userReading %s has perlCode %s with modifier %s%s",
# $userReading,$perlCode,$modifier,$trigger?" and trigger $trigger":"");
2013-07-03 19:09:06 +00:00
if ( grep { /$modifier/ } qw( none difference differential offset monotonic ) ) {
2013-05-17 18:16:54 +00:00
$ trigger =~ s/^:// if ( $ trigger ) ;
$ userReadings { $ userReading } { trigger } = $ trigger ;
2013-02-10 09:57:02 +00:00
$ userReadings { $ userReading } { modifier } = $ modifier ;
$ userReadings { $ userReading } { perlCode } = $ perlCode ;
} else {
2013-05-17 18:16:54 +00:00
push @ rets , "$sdev: unknown modifier $modifier for " .
"userReading $userReading, this userReading will be ignored" ;
2013-02-10 09:57:02 +00:00
}
2013-05-17 18:16:54 +00:00
$ arg = defined ( $ 8 ) ? $ 8 : "" ;
2013-01-13 15:16:31 +00:00
}
2013-02-17 13:55:05 +00:00
$ hash - > { '.userReadings' } = \ % userReadings ;
2013-01-22 18:08:53 +00:00
}
2013-01-13 15:16:31 +00:00
2014-10-15 17:11:07 +00:00
if ( $ attrName eq "IODev" && ( ! $ a [ 2 ] || ! defined ( $ defs { $ a [ 2 ] } ) ) ) {
2012-05-12 11:36:54 +00:00
push @ rets , "$sdev: unknown IODev specified" ;
2009-01-09 17:31:44 +00:00
next ;
}
2007-12-29 15:57:42 +00:00
$ a [ 0 ] = $ sdev ;
2015-01-24 13:07:32 +00:00
my $ oVal = ( $ attr { $ sdev } ? $ attr { $ sdev } { $ attrName } : "" ) ;
2007-12-29 15:57:42 +00:00
$ ret = CallFn ( $ sdev , "AttrFn" , "set" , @ a ) ;
if ( $ ret ) {
push @ rets , $ ret ;
next ;
}
2014-10-15 17:11:07 +00:00
my $ val = $ a [ 2 ] ;
$ val = 1 if ( ! defined ( $ val ) ) ;
$ attr { $ sdev } { $ attrName } = $ val ;
if ( $ attrName eq "IODev" ) {
2009-11-25 10:48:01 +00:00
my $ ioname = $ a [ 2 ] ;
2013-01-22 18:08:53 +00:00
$ hash - > { IODev } = $ defs { $ ioname } ;
$ hash - > { NR } = $ devcount + +
if ( $ defs { $ ioname } { NR } > $ hash - > { NR } ) ;
2013-11-20 12:56:23 +00:00
delete ( $ defs { $ ioname } { ".clientArray" } ) ; # Force a recompute
2013-01-22 18:08:53 +00:00
}
2014-10-15 17:11:07 +00:00
if ( $ attrName eq "stateFormat" && $ init_done ) {
2013-01-22 18:08:53 +00:00
evalStateFormat ( $ hash ) ;
2009-11-25 10:48:01 +00:00
}
2015-01-24 13:07:32 +00:00
addStructChange ( "attr" , $ sdev , $ param ) if ( ! defined ( $ oVal ) || $ oVal ne $ val ) ;
2014-10-15 17:11:07 +00:00
DoTrigger ( "global" , "ATTR $sdev $attrName $val" , 1 ) if ( $ init_done ) ;
2013-01-13 15:16:31 +00:00
2007-12-29 15:57:42 +00:00
}
2014-05-29 10:25:01 +00:00
2010-12-21 07:45:28 +00:00
Log 3 , join ( " " , @ rets ) if ( ! $ cl && @ rets ) ;
2007-12-29 15:57:42 +00:00
return join ( "\n" , @ rets ) ;
}
2007-03-19 14:59:37 +00:00
#####################################
# Default Attr
2007-02-11 17:58:23 +00:00
sub
2007-12-29 16:25:02 +00:00
CommandDefaultAttr ( $$ )
2007-02-11 17:58:23 +00:00
{
my ( $ cl , $ param ) = @ _ ;
my @ a = split ( " " , $ param , 2 ) ;
if ( int ( @ a ) == 0 ) {
2007-12-29 16:25:02 +00:00
% defaultattr = ( ) ;
2007-02-11 17:58:23 +00:00
} elsif ( int ( @ a ) == 1 ) {
2007-12-29 16:25:02 +00:00
$ defaultattr { $ a [ 0 ] } = 1 ;
2007-02-11 17:58:23 +00:00
} else {
2007-12-29 16:25:02 +00:00
$ defaultattr { $ a [ 0 ] } = $ a [ 1 ] ;
2008-11-01 21:27:10 +00:00
}
2007-02-11 17:58:23 +00:00
return undef ;
}
2007-01-30 12:47:36 +00:00
#####################################
sub
CommandSetstate ( $$ )
{
my ( $ cl , $ param ) = @ _ ;
2008-11-01 21:27:10 +00:00
2007-01-30 12:47:36 +00:00
my @ a = split ( " " , $ param , 2 ) ;
2008-09-06 08:33:55 +00:00
return "Usage: setstate <name> <state>\n$namedef" if ( @ a != 2 ) ;
2007-01-30 12:47:36 +00:00
2007-12-29 15:57:42 +00:00
my @ rets ;
foreach my $ sdev ( devspec2array ( $ a [ 0 ] ) ) {
if ( ! defined ( $ defs { $ sdev } ) ) {
push @ rets , "Please define $sdev first" ;
next ;
2007-03-19 14:59:37 +00:00
}
2007-12-29 15:57:42 +00:00
my $ d = $ defs { $ sdev } ;
2007-03-19 14:59:37 +00:00
2007-12-29 15:57:42 +00:00
# Detailed state with timestamp
2011-07-30 13:22:25 +00:00
if ( $ a [ 1 ] =~ m/^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) +([^ ].*)$/ ) {
my ( $ tim , $ nameval ) = ( $ 1 , $ 2 ) ;
my ( $ sname , $ sval ) = split ( " " , $ nameval , 2 ) ;
2012-07-11 10:42:38 +00:00
( undef , $ sval ) = ReplaceEventMap ( $ sdev , [ $ sdev , $ sval ] , 0 )
if ( $ attr { $ sdev } { eventMap } ) ;
2011-07-30 13:22:25 +00:00
my $ ret = CallFn ( $ sdev , "StateFn" , $ d , $ tim , $ sname , $ sval ) ;
2007-12-29 15:57:42 +00:00
if ( $ ret ) {
push @ rets , $ ret ;
next ;
}
2007-06-09 08:57:46 +00:00
2013-11-12 17:43:33 +00:00
if ( ! defined ( $ d - > { READINGS } { $ sname } ) ||
! defined ( $ d - > { READINGS } { $ sname } { TIME } ) ||
$ d - > { READINGS } { $ sname } { TIME } lt $ tim ) {
2011-07-30 13:22:25 +00:00
$ d - > { READINGS } { $ sname } { VAL } = $ sval ;
$ d - > { READINGS } { $ sname } { TIME } = $ tim ;
2007-12-29 15:57:42 +00:00
}
} else {
2010-01-21 19:29:47 +00:00
2014-09-12 09:03:55 +00:00
# The timestamp is not the correct one, but we do not store a timestamp
# for this reading.
2012-07-11 10:42:38 +00:00
my $ tn = TimeNow ( ) ;
$ oldvalue { $ sdev } { TIME } = $ tn ;
2013-01-30 10:39:30 +00:00
$ oldvalue { $ sdev } { VAL } = ( $ init_done ? $ d - > { STATE } : $ a [ 1 ] ) ;
2011-06-05 11:10:34 +00:00
2013-01-30 10:39:30 +00:00
# Do not overwrite state like "opened" or "initialized"
$ d - > { STATE } = $ a [ 1 ] if ( $ init_done || $ d - > { STATE } eq "???" ) ;
2012-07-11 10:42:38 +00:00
my $ ret = CallFn ( $ sdev , "StateFn" , $ d , $ tn , "STATE" , $ a [ 1 ] ) ;
if ( $ ret ) {
push @ rets , $ ret ;
next ;
}
2007-12-29 15:57:42 +00:00
}
2007-01-30 12:47:36 +00:00
}
2007-12-29 15:57:42 +00:00
return join ( "\n" , @ rets ) ;
2007-01-30 12:47:36 +00:00
}
#####################################
sub
CommandTrigger ( $$ )
{
my ( $ cl , $ param ) = @ _ ;
my ( $ dev , $ state ) = split ( " " , $ param , 2 ) ;
2009-12-09 13:15:16 +00:00
return "Usage: trigger <name> <state>\n$namedef" if ( ! $ dev ) ;
2012-12-28 10:52:16 +00:00
$ state = "" if ( ! defined ( $ state ) ) ;
2007-12-29 15:57:42 +00:00
my @ rets ;
foreach my $ sdev ( devspec2array ( $ dev ) ) {
if ( ! defined ( $ defs { $ sdev } ) ) {
push @ rets , "Please define $sdev first" ;
next ;
}
my $ ret = DoTrigger ( $ sdev , $ state ) ;
if ( $ ret ) {
push @ rets , $ ret ;
next ;
}
}
return join ( "\n" , @ rets ) ;
2007-01-30 12:47:36 +00:00
}
#####################################
sub
CommandInform ( $$ )
{
my ( $ cl , $ param ) = @ _ ;
2012-06-23 16:22:28 +00:00
return if ( ! $ cl ) ;
my $ name = $ cl - > { NAME } ;
2007-01-30 12:47:36 +00:00
2011-11-06 18:49:25 +00:00
return "Usage: inform {on|timer|raw|off} [regexp]"
if ( $ param !~ m/^(on|off|raw|timer)/ ) ;
2008-09-14 12:53:39 +00:00
2012-06-23 16:22:28 +00:00
delete ( $ inform { $ name } ) ;
2011-11-06 18:49:25 +00:00
if ( $ param !~ m/^off/ ) {
my ( $ type , $ regexp ) = split ( " " , $ param ) ;
2012-06-23 16:22:28 +00:00
$ inform { $ name } { NR } = $ cl - > { NR } ;
$ inform { $ name } { type } = $ type ;
2011-11-06 18:49:25 +00:00
if ( $ regexp ) {
eval { "Hallo" =~ m/$regexp/ } ;
return "Bad regexp: $@" if ( $@ ) ;
2012-06-23 16:22:28 +00:00
$ inform { $ name } { regexp } = $ regexp ;
2011-11-06 18:49:25 +00:00
}
2008-09-14 12:53:39 +00:00
Log 4 , "Setting inform to $param" ;
2011-11-06 18:49:25 +00:00
2008-09-14 12:53:39 +00:00
}
2007-01-30 12:47:36 +00:00
return undef ;
}
#####################################
2013-07-25 07:35:49 +00:00
sub
WakeUpFn ( $ )
{
my $ h = shift ;
$ evalSpecials = $ h - > { evalSpecials } ;
my $ ret = AnalyzeCommandChain ( undef , $ h - > { cmd } ) ;
Log 2 , "After sleep: $ret" if ( $ ret && ! $ h - > { quiet } ) ;
}
2007-01-30 12:47:36 +00:00
sub
CommandSleep ( $$ )
{
my ( $ cl , $ param ) = @ _ ;
2013-07-25 07:35:49 +00:00
my ( $ sec , $ quiet ) = split ( " " , $ param ) ;
return "Argument missing" if ( ! defined ( $ sec ) ) ;
return "Cannot interpret $sec as seconds" if ( $ sec !~ m/^[0-9\.]+$/ ) ;
return "Second parameter must be quiet" if ( $ quiet && $ quiet ne "quiet" ) ;
2007-01-30 12:47:36 +00:00
2013-07-25 07:35:49 +00:00
Log 4 , "sleeping for $sec" ;
2012-03-30 07:11:39 +00:00
2014-01-01 15:43:32 +00:00
if ( @ cmdList && $ sec && $ init_done ) {
2013-07-25 07:35:49 +00:00
my % h = ( cmd = > join ( ";" , @ cmdList ) ,
evalSpecials = > $ evalSpecials ,
quiet = > $ quiet ) ;
InternalTimer ( gettimeofday ( ) + $ sec , "WakeUpFn" , \ % h , 0 ) ;
2012-03-30 07:11:39 +00:00
@ cmdList = ( ) ;
} else {
2013-07-25 07:35:49 +00:00
select ( undef , undef , undef , $ sec ) ;
2012-03-30 07:11:39 +00:00
}
2007-01-30 12:47:36 +00:00
return undef ;
}
2013-07-13 11:56:22 +00:00
#####################################
sub
CommandVersion ( $$ )
{
my ( $ cl , $ param ) = @ _ ;
my @ ret = ( "# $cvsid" ) ;
2014-04-20 19:20:42 +00:00
push @ ret , cfgDB_svnId if ( configDBUsed ( ) ) ;
2013-07-13 11:56:22 +00:00
foreach my $ m ( sort keys % modules ) {
next if ( ! $ modules { $ m } { LOADED } || $ modules { $ m } { ORDER } < 0 ) ;
2014-04-09 20:20:27 +00:00
Log 4 , "Looking for SVN Id in module $m" ;
2013-07-13 11:56:22 +00:00
my $ fn = "$attr{global}{modpath}/FHEM/" . $ modules { $ m } { ORDER } . "_$m.pm" ;
if ( ! open ( FH , $ fn ) ) {
2014-04-26 06:59:30 +00:00
my $ ret = "$fn: $!" ;
if ( configDBUsed ( ) ) {
Log 4 , "Looking for module $m in configDB to find SVN Id" ;
$ ret = cfgDB_Fileversion ( $ fn , $ ret ) ;
}
push @ ret , $ ret ;
2013-07-13 11:56:22 +00:00
} else {
push @ ret , map { chomp ; $ _ } grep ( /# \$Id:/ , <FH> ) ;
}
}
if ( $ param ) {
return join ( "\n" , grep /$param/ , @ ret ) ;
} else {
return join ( "\n" , @ ret ) ;
}
}
2007-01-30 12:47:36 +00:00
#####################################
# Return the time to the next event (or undef if there is none)
# and call each function which was scheduled for this time
sub
HandleTimeout ( )
{
return undef if ( ! $ nextat ) ;
my $ now = gettimeofday ( ) ;
return ( $ nextat - $ now ) if ( $ now < $ nextat ) ;
2012-12-06 20:44:21 +00:00
$ now += 0.01 ; # need to cover min delay at least
2007-01-30 12:47:36 +00:00
$ nextat = 0 ;
#############
# Check the internal list.
2011-01-22 21:53:18 +00:00
foreach my $ i ( sort { $ intAt { $ a } { TRIGGERTIME } <=>
2010-03-22 14:31:37 +00:00
$ intAt { $ b } { TRIGGERTIME } } keys % intAt ) {
2007-03-19 14:59:37 +00:00
my $ tim = $ intAt { $ i } { TRIGGERTIME } ;
2008-11-15 09:28:22 +00:00
my $ fn = $ intAt { $ i } { FN } ;
if ( ! defined ( $ tim ) || ! defined ( $ fn ) ) {
delete ( $ intAt { $ i } ) ;
next ;
} elsif ( $ tim <= $ now ) {
2007-01-30 12:47:36 +00:00
no strict "refs" ;
2008-11-15 09:28:22 +00:00
& { $ fn } ( $ intAt { $ i } { ARG } ) ;
2007-01-30 12:47:36 +00:00
use strict "refs" ;
delete ( $ intAt { $ i } ) ;
2012-12-06 20:44:21 +00:00
} else {
$ nextat = $ tim if ( ! $ nextat || $ nextat > $ tim ) ;
2014-01-14 19:23:34 +00:00
}
2007-01-30 12:47:36 +00:00
}
return undef if ( ! $ nextat ) ;
2012-12-06 20:44:21 +00:00
$ now = gettimeofday ( ) ; # possibly some tasks did timeout in the meantime
# we will cover them
return ( $ now + 0.01 < $ nextat ) ? ( $ nextat - $ now ) : 0.01 ;
2007-01-30 12:47:36 +00:00
}
#####################################
sub
2007-11-26 08:27:04 +00:00
InternalTimer ( $$ $$ )
2007-01-30 12:47:36 +00:00
{
2007-11-26 08:27:04 +00:00
my ( $ tim , $ fn , $ arg , $ waitIfInitNotDone ) = @ _ ;
2007-01-30 12:47:36 +00:00
2007-11-26 08:27:04 +00:00
if ( ! $ init_done && $ waitIfInitNotDone ) {
2007-01-30 12:47:36 +00:00
select ( undef , undef , undef , $ tim - gettimeofday ( ) ) ;
no strict "refs" ;
& { $ fn } ( $ arg ) ;
use strict "refs" ;
return ;
}
2007-03-19 14:59:37 +00:00
$ intAt { $ intAtCnt } { TRIGGERTIME } = $ tim ;
2007-01-30 12:47:36 +00:00
$ intAt { $ intAtCnt } { FN } = $ fn ;
$ intAt { $ intAtCnt } { ARG } = $ arg ;
$ intAtCnt + + ;
$ nextat = $ tim if ( ! $ nextat || $ nextat > $ tim ) ;
}
2008-08-04 13:47:53 +00:00
sub
RemoveInternalTimer ( $ )
{
my ( $ arg ) = @ _ ;
foreach my $ a ( keys % intAt ) {
delete ( $ intAt { $ a } ) if ( $ intAt { $ a } { ARG } eq $ arg ) ;
}
}
2007-01-30 12:47:36 +00:00
#####################################
2014-10-05 07:42:43 +00:00
sub
stacktrace ( ) {
my $ i = 1 ;
my $ max_depth = 50 ;
Log 3 , "stacktrace:" ;
while ( ( my @ call_details = ( caller ( $ i + + ) ) ) && ( $ i < $ max_depth ) ) {
Log 3 , sprintf ( " %-35s called by %s (%s)" ,
$ call_details [ 3 ] , $ call_details [ 1 ] , $ call_details [ 2 ] ) ;
}
}
my $ inWarnSub ;
2007-01-30 12:47:36 +00:00
sub
SignalHandling ( )
{
2009-11-28 11:14:13 +00:00
if ( $^O ne "MSWin32" ) {
2014-12-05 07:10:20 +00:00
$ SIG { INT } = sub { exit ( ) } ;
2014-10-05 07:42:43 +00:00
$ SIG { TERM } = sub { $ sig_term = 1 ; } ;
$ SIG { PIPE } = 'IGNORE' ;
$ SIG { CHLD } = 'IGNORE' ;
$ SIG { HUP } = sub { CommandRereadCfg ( undef , "" ) } ;
$ SIG { ALRM } = sub { Log 1 , "ALARM signal, blocking write?" } ;
2014-09-26 17:55:17 +00:00
#$SIG{'XFSZ'} = sub { Log 1, "XFSZ signal" }; # to test with limit filesize
2007-01-30 12:47:36 +00:00
}
2014-10-05 07:42:43 +00:00
$ SIG { __WARN__ } = sub {
my ( $ msg ) = @ _ ;
return if ( $ inWarnSub ) ;
2014-11-08 10:32:44 +00:00
if ( ! $ attr { global } { stacktrace } && $ data { WARNING } { $ msg } ) {
$ data { WARNING } { $ msg } + + ;
return ;
}
2014-10-05 07:42:43 +00:00
$ inWarnSub = 1 ;
2014-11-08 10:32:44 +00:00
$ data { WARNING } { $ msg } + + ;
2014-10-05 07:42:43 +00:00
chomp ( $ msg ) ;
Log 1 , "PERL WARNING: $msg" ;
2014-10-08 08:30:15 +00:00
Log 3 , "eval: $cmdFromAnalyze" if ( $ cmdFromAnalyze && $ msg =~ m/\(eval / ) ;
2014-10-09 12:30:18 +00:00
stacktrace ( ) if ( $ attr { global } { stacktrace } &&
$ attr { global } { verbose } >= 3 &&
$ msg !~ m/ redefined at / ) ;
2014-10-05 07:42:43 +00:00
$ inWarnSub = 0 ;
} ;
2007-01-30 12:47:36 +00:00
}
2014-10-05 07:42:43 +00:00
2007-01-30 12:47:36 +00:00
#####################################
sub
TimeNow ( )
{
2013-03-01 11:09:18 +00:00
return FmtDateTime ( time ( ) ) ;
2007-01-30 12:47:36 +00:00
}
2007-12-13 15:26:27 +00:00
#####################################
sub
FmtDateTime ( $ )
{
my @ t = localtime ( shift ) ;
return sprintf ( "%04d-%02d-%02d %02d:%02d:%02d" ,
$ t [ 5 ] + 1900 , $ t [ 4 ] + 1 , $ t [ 3 ] , $ t [ 2 ] , $ t [ 1 ] , $ t [ 0 ] ) ;
}
2007-03-19 14:59:37 +00:00
sub
FmtTime ( $ )
{
my @ t = localtime ( shift ) ;
return sprintf ( "%02d:%02d:%02d" , $ t [ 2 ] , $ t [ 1 ] , $ t [ 0 ] ) ;
}
2007-01-30 12:47:36 +00:00
#####################################
sub
CommandChain ( $$ )
{
my ( $ retry , $ list ) = @ _ ;
2007-03-19 14:59:37 +00:00
my $ ov = $ attr { global } { verbose } ;
2007-01-30 12:47:36 +00:00
my $ oid = $ init_done ;
2011-11-10 13:47:26 +00:00
$ init_done = 0 ; # Rudi: ???
2013-08-18 14:13:59 +00:00
$ attr { global } { verbose } = 1 ; # ???
2007-01-30 12:47:36 +00:00
foreach my $ cmd ( @ { $ list } ) {
for ( my $ n = 0 ; $ n < $ retry ; $ n + + ) {
Log 1 , sprintf ( "Trying again $cmd (%d out of %d)" , $ n + 1 , $ retry ) if ( $ n > 0 ) ;
my $ ret = AnalyzeCommand ( undef , $ cmd ) ;
2011-02-05 09:26:55 +00:00
last if ( ! defined ( $ ret ) || $ ret !~ m/Timeout/ ) ;
2007-01-30 12:47:36 +00:00
}
}
2007-03-19 14:59:37 +00:00
$ attr { global } { verbose } = $ ov ;
2007-01-30 12:47:36 +00:00
$ init_done = $ oid ;
}
#####################################
sub
ResolveDateWildcards ( $@ )
{
2012-02-21 20:20:23 +00:00
use POSIX qw( strftime ) ;
2007-01-30 12:47:36 +00:00
my ( $ f , @ t ) = @ _ ;
return $ f if ( ! $ f ) ;
return $ f if ( $ f !~ m/%/ ) ; # Be fast if there is no wildcard
2012-02-21 20:20:23 +00:00
$ f =~ s/%L/$attr{global}{logdir}/g if ( $ attr { global } { logdir } ) ; #log directory
return strftime ( $ f , @ t ) ;
2007-01-30 12:47:36 +00:00
}
sub
SemicolonEscape ( $ )
{
my $ cmd = shift ;
$ cmd =~ s/^[ \t]*// ;
$ cmd =~ s/[ \t]*$// ;
2008-05-09 13:58:10 +00:00
if ( $ cmd =~ m/^{.*}$/s || $ cmd =~ m/^".*"$/s ) {
2007-01-30 12:47:36 +00:00
$ cmd =~ s/;/;;/g
}
return $ cmd ;
}
2011-01-22 21:53:18 +00:00
sub
EvalSpecials ( $% )
{
2012-03-30 06:03:47 +00:00
# The character % will be replaced with the received event,
# e.g. with on or off or measured-temp: 21.7 (Celsius)
# The character @ will be replaced with the device name.
# To use % or @ in the text itself, use the double mode (%% or @@).
# Instead of % and @, the parameters %EVENT (same as %),
# %NAME (same as @) and %TYPE (contains the device type, e.g. FHT)
# can be used. A single % looses its special meaning if any of these
# parameters appears in the definition.
my ( $ exec , % specials ) = @ _ ;
$ exec = SemicolonEscape ( $ exec ) ;
# %EVTPART due to HM remote logic
my $ idx = 0 ;
if ( defined ( $ specials { "%EVENT" } ) ) {
foreach my $ part ( split ( " " , $ specials { "%EVENT" } ) ) {
$ specials { "%EVTPART$idx" } = $ part ;
$ idx + + ;
}
}
2011-09-12 15:22:07 +00:00
2013-03-24 17:47:28 +00:00
my $ re = join ( "|" , keys % specials ) ;
$ re =~ s/%//g ;
if ( $ exec =~ m/\$($re)\b/ ) {
$ evalSpecials = \ % specials ;
return $ exec ;
}
$ exec =~ s/%%/____/g ;
2012-03-30 06:03:47 +00:00
# perform macro substitution
my $ extsyntax = 0 ;
foreach my $ special ( keys % specials ) {
$ extsyntax += ( $ exec =~ s/$special/$specials{$special}/g ) ;
}
if ( ! $ extsyntax ) {
$ exec =~ s/%/$specials{"%EVENT"}/g ;
}
$ exec =~ s/____/%/g ;
2011-01-22 21:53:18 +00:00
2012-03-30 06:03:47 +00:00
$ exec =~ s/@@/____/g ;
$ exec =~ s/@/$specials{"%NAME"}/g ;
$ exec =~ s/____/@/g ;
2011-01-22 21:53:18 +00:00
2012-03-30 06:03:47 +00:00
return $ exec ;
2011-01-22 21:53:18 +00:00
}
2008-07-25 14:14:24 +00:00
#####################################
# Parse a timespec: Either HH:MM:SS or HH:MM or { perfunc() }
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 ;
2009-12-27 18:07:14 +00:00
$ tspec = AnalyzeCommand ( undef , "{$fn}" ) ;
2008-07-25 14:14:24 +00:00
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 = "<empty string>" if ( ! $ tspec ) ;
return ( "the at function \"$fn\" must return a timespec 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 ) ;
}
2014-04-06 06:24:47 +00:00
sub
deviceEvents ( $$ )
{
my ( $ hash , $ withState ) = @ _ ;
return undef if ( ! $ hash || ! $ hash - > { CHANGED } ) ;
if ( $ withState ) {
my $ cws = $ hash - > { CHANGEDWITHSTATE } ;
if ( defined ( $ cws ) ) {
if ( int ( @ { $ cws } ) == 0 ) {
@ { $ cws } = @ { $ hash - > { CHANGED } } ;
push @ { $ cws } , "state: $hash->{READINGS}{state}{VAL}"
if ( $ hash - > { READINGS } && $ hash - > { READINGS } { state } ) ;
}
return $ cws ;
}
}
return $ hash - > { CHANGED } ;
}
2008-07-25 14:14:24 +00:00
#####################################
# Do the notification
2007-01-30 12:47:36 +00:00
sub
2013-01-03 12:50:16 +00:00
DoTrigger ( $$ @ )
2007-01-30 12:47:36 +00:00
{
2013-01-30 10:39:30 +00:00
my ( $ dev , $ newState , $ noreplace ) = @ _ ;
2011-02-05 09:26:55 +00:00
my $ ret = "" ;
2013-01-30 10:39:30 +00:00
my $ hash = $ defs { $ dev } ;
return "" if ( ! defined ( $ hash ) ) ;
2007-01-30 12:47:36 +00:00
2013-06-22 13:47:34 +00:00
$ hash - > { ".triggerUsed" } = 1 if ( defined ( $ hash - > { ".triggerUsed" } ) ) ;
2013-01-30 10:39:30 +00:00
if ( defined ( $ newState ) ) {
if ( $ hash - > { CHANGED } ) {
push @ { $ hash - > { CHANGED } } , $ newState ;
2012-02-27 16:59:42 +00:00
} else {
2013-01-30 10:39:30 +00:00
$ hash - > { CHANGED } [ 0 ] = $ newState ;
2012-02-27 16:59:42 +00:00
}
2013-01-30 10:39:30 +00:00
} elsif ( ! defined ( $ hash - > { CHANGED } ) ) {
2007-01-30 12:47:36 +00:00
return "" ;
}
2008-07-28 12:33:29 +00:00
2013-01-30 10:39:30 +00:00
if ( ! $ noreplace ) { # Backward compatibility for code without readingsUpdate
2013-01-03 12:50:16 +00:00
if ( $ attr { $ dev } { eventMap } ) {
2013-01-30 10:39:30 +00:00
my $ c = $ hash - > { CHANGED } ;
2013-01-03 12:50:16 +00:00
for ( my $ i = 0 ; $ i < @ { $ c } ; $ i + + ) {
$ c - > [ $ i ] = ReplaceEventMap ( $ dev , $ c - > [ $ i ] , 1 ) ;
}
2013-01-30 10:39:30 +00:00
$ hash - > { STATE } = ReplaceEventMap ( $ dev , $ hash - > { STATE } , 1 ) ;
2010-12-27 09:42:16 +00:00
}
}
2007-11-26 08:27:04 +00:00
2013-01-30 10:39:30 +00:00
my $ max = int ( @ { $ hash - > { CHANGED } } ) ;
2007-11-26 08:27:04 +00:00
Log 5 , "Triggering $dev ($max changes)" ;
2007-01-30 12:47:36 +00:00
return "" if ( defined ( $ attr { $ dev } ) && defined ( $ attr { $ dev } { do_not_notify } ) ) ;
2012-01-05 13:09:07 +00:00
################
# Log/notify modules
# If modifying a device in its own trigger, do not call the triggers from
# the inner loop.
2013-01-30 10:39:30 +00:00
if ( $ max && ! defined ( $ hash - > { INTRIGGER } ) ) {
$ hash - > { INTRIGGER } = 1 ;
Log 5 , "Notify loop for $dev $hash->{CHANGED}->[0]" ;
2014-01-14 19:23:34 +00:00
createNtfyHash ( ) if ( ! % ntfyHash ) ;
2013-04-08 14:31:22 +00:00
$ hash - > { NTFY_TRIGGERTIME } = TimeNow ( ) ; # Optimize FileLog
2014-01-14 19:23:34 +00:00
my $ ntfyLst = ( defined ( $ ntfyHash { $ dev } ) ? $ ntfyHash { $ dev } : $ ntfyHash { "*" } ) ;
foreach my $ n ( @ { $ ntfyLst } ) {
2012-01-05 13:09:07 +00:00
next if ( ! defined ( $ defs { $ n } ) ) ; # Was deleted in a previous notify
2013-01-30 10:39:30 +00:00
my $ r = CallFn ( $ n , "NotifyFn" , $ defs { $ n } , $ hash ) ;
2014-12-21 16:14:16 +00:00
$ ret . = " $n:$r" if ( $ r ) ;
2012-01-05 13:09:07 +00:00
}
2013-07-15 19:34:35 +00:00
delete ( $ hash - > { NTFY_TRIGGERTIME } ) ;
2012-01-05 13:09:07 +00:00
2012-02-27 16:59:42 +00:00
################
# Inform
2013-01-30 10:39:30 +00:00
if ( $ hash - > { CHANGED } ) { # It gets deleted sometimes (?)
$ max = int ( @ { $ hash - > { CHANGED } } ) ; # can be enriched in the notifies
2012-06-23 16:22:28 +00:00
foreach my $ c ( keys % inform ) {
2013-11-09 13:45:34 +00:00
my $ dc = $ defs { $ c } ;
if ( ! $ dc || $ dc - > { NR } != $ inform { $ c } { NR } ) {
2012-06-23 16:22:28 +00:00
delete ( $ inform { $ c } ) ;
next ;
}
next if ( $ inform { $ c } { type } eq "raw" ) ;
2012-02-27 16:59:42 +00:00
my $ tn = TimeNow ( ) ;
if ( $ attr { global } { mseclog } ) {
my ( $ seconds , $ microseconds ) = gettimeofday ( ) ;
$ tn . = sprintf ( ".%03d" , $ microseconds / 1000 ) ;
}
2012-06-23 16:22:28 +00:00
my $ re = $ inform { $ c } { regexp } ;
2012-02-27 16:59:42 +00:00
for ( my $ i = 0 ; $ i < $ max ; $ i + + ) {
2013-01-30 10:39:30 +00:00
my $ state = $ hash - > { CHANGED } [ $ i ] ;
2012-07-10 06:23:04 +00:00
next if ( $ re && ! ( $ dev =~ m/$re/ || "$dev:$state" =~ m/$re/ ) ) ;
2013-11-09 13:45:34 +00:00
addToWritebuffer ( $ dc , ( $ inform { $ c } { type } eq "timer" ? "$tn " : "" ) .
"$hash->{TYPE} $dev $state\n" ) ;
2012-02-27 16:59:42 +00:00
}
2012-01-07 09:00:32 +00:00
}
2007-01-30 12:47:36 +00:00
}
2012-02-27 16:59:42 +00:00
2013-01-30 10:39:30 +00:00
delete ( $ hash - > { INTRIGGER } ) ;
2007-01-30 12:47:36 +00:00
}
####################
# Used by triggered perl programs to check the old value
# Not suited for multi-valued devices (KS300, etc)
$ oldvalue { $ dev } { TIME } = TimeNow ( ) ;
2013-01-30 10:39:30 +00:00
$ oldvalue { $ dev } { VAL } = $ hash - > { STATE } ;
2007-01-30 12:47:36 +00:00
2014-04-06 06:24:47 +00:00
if ( ! defined ( $ hash - > { INTRIGGER } ) ) {
delete ( $ hash - > { CHANGED } ) ;
delete ( $ hash - > { CHANGEDWITHSTATE } ) ;
}
2007-01-30 12:47:36 +00:00
Log 3 , "NTFY return: $ret" if ( $ ret ) ;
2011-10-23 09:23:55 +00:00
2007-01-30 12:47:36 +00:00
return $ ret ;
}
2007-03-19 14:59:37 +00:00
2008-07-25 14:14:24 +00:00
#####################################
# Wrapper for calling a module function
2007-03-19 14:59:37 +00:00
sub
CallFn ( @ )
{
my $ d = shift ;
my $ n = shift ;
2008-09-06 08:33:55 +00:00
2013-06-22 13:47:34 +00:00
if ( ! $ d || ! $ defs { $ d } ) {
$ d = "<undefined>" if ( ! defined ( $ d ) ) ;
2008-09-06 08:33:55 +00:00
Log 0 , "Strange call for nonexistent $d: $n" ;
return undef ;
}
2008-08-08 10:46:25 +00:00
if ( ! $ defs { $ d } { TYPE } ) {
2008-09-06 08:33:55 +00:00
Log 0 , "Strange call for typeless $d: $n" ;
2008-08-08 10:46:25 +00:00
return undef ;
}
2007-03-19 14:59:37 +00:00
my $ fn = $ modules { $ defs { $ d } { TYPE } } { $ n } ;
return "" if ( ! $ fn ) ;
2012-11-05 07:32:55 +00:00
if ( wantarray ) {
no strict "refs" ;
my @ ret = & { $ fn } ( @ _ ) ;
use strict "refs" ;
return @ ret ;
} else {
no strict "refs" ;
my $ ret = & { $ fn } ( @ _ ) ;
use strict "refs" ;
return $ ret ;
}
2007-03-19 14:59:37 +00:00
}
#####################################
# Used from perl oneliners inside of scripts
sub
2012-10-30 18:46:58 +00:00
fhem ( $@ )
2007-03-19 14:59:37 +00:00
{
2012-10-30 18:46:58 +00:00
my ( $ param , $ silent ) = @ _ ;
2010-12-16 08:07:18 +00:00
my $ ret = AnalyzeCommandChain ( undef , $ param ) ;
2012-10-30 18:46:58 +00:00
Log 3 , "$param : $ret" if ( $ ret && ! $ silent ) ;
2010-12-16 08:07:18 +00:00
return $ ret ;
2007-03-31 06:28:08 +00:00
}
2008-07-25 14:14:24 +00:00
#####################################
# initialize the global device
2007-03-19 15:34:34 +00:00
sub
doGlobalDef ( $ )
{
my ( $ arg ) = @ _ ;
2009-07-26 09:20:07 +00:00
$ devcount = 1 ;
2007-03-19 15:34:34 +00:00
$ defs { global } { NR } = $ devcount + + ;
2011-07-24 11:55:36 +00:00
$ defs { global } { TYPE } = "Global" ;
2007-03-27 14:50:04 +00:00
$ defs { global } { STATE } = "<no definition>" ;
2007-03-19 15:34:34 +00:00
$ defs { global } { DEF } = "<no definition>" ;
2007-08-06 18:17:29 +00:00
$ defs { global } { NAME } = "global" ;
2007-03-19 15:34:34 +00:00
CommandAttr ( undef , "global verbose 3" ) ;
CommandAttr ( undef , "global configfile $arg" ) ;
CommandAttr ( undef , "global logfile -" ) ;
}
2007-08-06 18:17:29 +00:00
2009-01-15 09:13:42 +00:00
#####################################
# rename does not work over Filesystems: lets copy it
sub
myrename ( $$ )
{
my ( $ from , $ to ) = @ _ ;
if ( ! open ( F , $ from ) ) {
Log ( 1 , "Rename: Cannot open $from: $!" ) ;
return ;
}
if ( ! open ( T , ">$to" ) ) {
Log ( 1 , "Rename: Cannot open $to: $!" ) ;
return ;
}
while ( my $ l = <F> ) {
print T $ l ;
}
close ( F ) ;
close ( T ) ;
unlink ( $ from ) ;
}
2008-07-25 14:14:24 +00:00
#####################################
2007-08-06 18:17:29 +00:00
# Make a directory and its parent directories if needed.
sub
HandleArchiving ( $ )
{
my ( $ log ) = @ _ ;
my $ ln = $ log - > { NAME } ;
return if ( ! $ attr { $ ln } ) ;
# If there is a command, call that
my $ cmd = $ attr { $ ln } { archivecmd } ;
if ( $ cmd ) {
$ cmd =~ s/%/$log->{currentlogfile}/g ;
2008-11-01 21:27:10 +00:00
Log 2 , "Archive: calling $cmd" ;
2007-08-06 18:17:29 +00:00
system ( $ cmd ) ;
return ;
}
my $ nra = $ attr { $ ln } { nrarchive } ;
my $ ard = $ attr { $ ln } { archivedir } ;
return if ( ! defined ( $ nra ) ) ;
# If nrarchive is set, then check the last files:
# Get a list of files:
my ( $ dir , $ file ) ;
if ( $ log - > { logfile } =~ m , ^ ( . + ) /([^/ ] + ) $, ) {
( $ dir , $ file ) = ( $ 1 , $ 2 ) ;
} else {
( $ dir , $ file ) = ( "." , $ log - > { logfile } ) ;
}
$ file =~ s/%./.+/g ;
return if ( ! opendir ( DH , $ dir ) ) ;
my @ files = sort grep { /^$file$/ } readdir ( DH ) ;
closedir ( DH ) ;
my $ max = int ( @ files ) - $ nra ;
for ( my $ i = 0 ; $ i < $ max ; $ i + + ) {
if ( $ ard ) {
Log 2 , "Moving $files[$i] to $ard" ;
2009-01-15 09:13:42 +00:00
myrename ( "$dir/$files[$i]" , "$ard/$files[$i]" ) ;
2007-08-06 18:17:29 +00:00
} else {
Log 2 , "Deleting $files[$i]" ;
unlink ( "$dir/$files[$i]" ) ;
}
}
}
2009-01-09 17:31:44 +00:00
#####################################
# Call a logical device (FS20) ParseMessage with data from a physical device
2013-12-24 09:10:26 +00:00
# (FHZ). Note: $hash may be dummy, used by FHEM2FHEM
2009-01-09 17:31:44 +00:00
sub
2009-11-14 09:20:37 +00:00
Dispatch ( $$ $ )
2009-01-09 17:31:44 +00:00
{
2009-11-14 09:20:37 +00:00
my ( $ hash , $ dmsg , $ addvals ) = @ _ ;
2013-11-20 12:56:23 +00:00
my $ module = $ modules { $ hash - > { TYPE } } ;
2009-01-09 17:31:44 +00:00
my $ name = $ hash - > { NAME } ;
2013-09-07 11:58:33 +00:00
Log3 $ hash , 5 , "$name dispatch $dmsg" ;
2009-01-09 17:31:44 +00:00
2013-11-20 12:56:23 +00:00
my ( $ isdup , $ idx ) = CheckDuplicate ( $ name , $ dmsg , $ module - > { FingerprintFn } ) ;
2013-07-15 20:34:58 +00:00
return rejectDuplicate ( $ name , $ idx , $ addvals ) if ( $ isdup ) ;
2009-11-12 19:08:01 +00:00
2009-01-09 17:31:44 +00:00
my @ found ;
2013-06-01 17:13:50 +00:00
my $ clientArray = $ hash - > { ".clientArray" } ;
2013-11-20 12:56:23 +00:00
$ clientArray = computeClientArray ( $ hash , $ module ) if ( ! $ clientArray ) ;
2009-01-09 17:31:44 +00:00
2013-06-01 17:13:50 +00:00
foreach my $ m ( @ { $ clientArray } ) {
2009-01-09 17:31:44 +00:00
# Module is not loaded or the message is not for this module
2013-06-01 17:13:50 +00:00
next if ( $ dmsg !~ m/$modules{$m}{Match}/i ) ;
2009-01-09 17:31:44 +00:00
2013-07-15 20:34:58 +00:00
if ( my $ ffn = $ modules { $ m } { FingerprintFn } ) {
2013-08-13 08:41:17 +00:00
( $ isdup , $ idx ) = CheckDuplicate ( $ name , $ dmsg , $ ffn ) ;
2013-07-15 20:34:58 +00:00
return rejectDuplicate ( $ name , $ idx , $ addvals ) if ( $ isdup ) ;
}
2013-01-03 12:50:16 +00:00
no strict "refs" ; $ readingsUpdateDelayTrigger = 1 ;
2009-01-09 17:31:44 +00:00
@ found = & { $ modules { $ m } { ParseFn } } ( $ hash , $ dmsg ) ;
2013-01-03 12:50:16 +00:00
use strict "refs" ; $ readingsUpdateDelayTrigger = 0 ;
2009-01-09 17:31:44 +00:00
last if ( int ( @ found ) ) ;
}
2013-06-01 17:13:50 +00:00
2014-10-09 19:21:23 +00:00
if ( ! int ( @ found ) || ! defined ( $ found [ 0 ] ) ) {
2013-11-20 12:56:23 +00:00
my $ h = $ hash - > { MatchList } ; $ h = $ module - > { MatchList } if ( ! $ h ) ;
2009-01-27 08:01:35 +00:00
if ( defined ( $ h ) ) {
foreach my $ m ( sort keys % { $ h } ) {
if ( $ dmsg =~ m/$h->{$m}/ ) {
2009-12-21 18:03:56 +00:00
my ( $ order , $ mname ) = split ( ":" , $ m ) ;
if ( $ attr { global } { autoload_undefined_devices } ) {
2012-07-17 14:49:20 +00:00
my $ newm = LoadModule ( $ mname ) ;
$ mname = $ newm if ( $ newm ne "UNDEFINED" ) ;
2010-02-24 08:20:37 +00:00
if ( $ modules { $ mname } && $ modules { $ mname } { ParseFn } ) {
2013-01-03 12:50:16 +00:00
no strict "refs" ; $ readingsUpdateDelayTrigger = 1 ;
2010-02-24 08:20:37 +00:00
@ found = & { $ modules { $ mname } { ParseFn } } ( $ hash , $ dmsg ) ;
2013-01-03 12:50:16 +00:00
use strict "refs" ; $ readingsUpdateDelayTrigger = 0 ;
2010-02-24 08:20:37 +00:00
} else {
Log 0 , "ERROR: Cannot autoload $mname" ;
}
2009-12-21 18:03:56 +00:00
} else {
2013-08-10 08:42:31 +00:00
Log3 $ name , 3 , "$name: Unknown $mname device detected, " .
2009-01-27 08:01:35 +00:00
"define one to get detailed information." ;
2009-12-21 18:03:56 +00:00
return undef ;
}
2009-01-27 08:01:35 +00:00
}
}
}
2014-10-09 19:21:23 +00:00
if ( ! int ( @ found ) || ! defined ( $ found [ 0 ] ) ) {
2012-09-30 07:31:59 +00:00
DoTrigger ( $ name , "UNKNOWNCODE $dmsg" ) ;
2013-08-10 08:42:31 +00:00
Log3 $ name , 3 , "$name: Unknown code $dmsg, help me!" ;
2009-12-21 18:03:56 +00:00
return undef ;
}
2009-01-09 17:31:44 +00:00
}
2010-10-24 16:08:48 +00:00
################
# Inform raw
2013-11-20 12:56:23 +00:00
if ( ! $ module - > { noRawInform } ) {
2012-06-23 16:22:28 +00:00
foreach my $ c ( keys % inform ) {
if ( ! $ defs { $ c } || $ defs { $ c } { NR } != $ inform { $ c } { NR } ) {
delete ( $ inform { $ c } ) ;
next ;
}
next if ( $ inform { $ c } { type } ne "raw" ) ;
syswrite ( $ defs { $ c } { CD } , "$hash->{TYPE} $name $dmsg\n" ) ;
2010-10-24 16:08:48 +00:00
}
}
2014-10-09 19:21:23 +00:00
return undef if ( ! defined ( $ found [ 0 ] ) || $ found [ 0 ] eq "" ) ; # Special return: Do not notify
2009-01-09 17:31:44 +00:00
foreach my $ found ( @ found ) {
2009-12-21 18:03:56 +00:00
if ( $ found =~ m/^(UNDEFINED.*)/ ) {
DoTrigger ( "global" , $ 1 ) ;
2009-01-27 08:01:35 +00:00
return undef ;
2009-12-21 18:03:56 +00:00
2009-01-09 17:31:44 +00:00
} else {
2009-11-14 09:20:37 +00:00
if ( $ defs { $ found } ) {
2013-11-19 08:41:35 +00:00
if ( ! $ defs { $ found } { ".noDispatchVars" } ) { # CUL_HM special
$ defs { $ found } { MSGCNT } + + ;
my $ avtrigger = ( $ attr { $ name } && $ attr { $ name } { addvaltrigger } ) ;
if ( $ addvals ) {
foreach my $ av ( keys % { $ addvals } ) {
$ defs { $ found } { "${name}_$av" } = $ addvals - > { $ av } ;
push ( @ { $ defs { $ found } { CHANGED } } , "$av: $addvals->{$av}" )
if ( $ avtrigger ) ;
}
2009-11-14 09:20:37 +00:00
}
2013-11-19 08:41:35 +00:00
$ defs { $ found } { "${name}_MSGCNT" } + + ;
$ defs { $ found } { "${name}_TIME" } = TimeNow ( ) ;
$ defs { $ found } { LASTInputDev } = $ name ;
2009-11-14 09:20:37 +00:00
}
2013-11-19 08:41:35 +00:00
delete ( $ defs { $ found } { ".noDispatchVars" } ) ;
2009-11-14 09:20:37 +00:00
}
2013-11-19 08:41:35 +00:00
2009-01-09 17:31:44 +00:00
DoTrigger ( $ found , undef ) ;
}
}
2009-11-14 09:20:37 +00:00
2013-08-13 08:41:17 +00:00
$ duplicate { $ idx } { FND } = \ @ found
if ( defined ( $ idx ) && defined ( $ duplicate { $ idx } ) ) ;
2009-01-09 17:31:44 +00:00
2009-01-27 08:01:35 +00:00
return \ @ found ;
2009-01-17 10:01:56 +00:00
}
2009-11-12 19:08:01 +00:00
sub
2013-07-15 20:34:58 +00:00
CheckDuplicate ( $$ @ )
2009-11-12 19:08:01 +00:00
{
2013-07-15 20:34:58 +00:00
my ( $ ioname , $ msg , $ ffn ) = @ _ ;
2009-11-12 19:08:01 +00:00
2013-07-15 20:34:58 +00:00
if ( $ ffn ) {
no strict "refs" ;
( $ ioname , $ msg ) = & { $ ffn } ( $ ioname , $ msg ) ;
use strict "refs" ;
return ( 0 , undef ) if ( ! defined ( $ msg ) ) ;
#Debug "got $ffn ". $ioname .":". $msg;
}
2009-11-12 19:08:01 +00:00
my $ now = gettimeofday ( ) ;
2011-03-04 06:48:28 +00:00
my $ lim = $ now - AttrVal ( "global" , "dupTimeout" , 0.5 ) ;
2009-11-12 19:08:01 +00:00
foreach my $ oidx ( keys % duplicate ) {
if ( $ duplicate { $ oidx } { TIM } < $ lim ) {
delete ( $ duplicate { $ oidx } ) ;
2013-07-15 20:34:58 +00:00
} elsif ( $ duplicate { $ oidx } { MSG } eq $ msg &&
$ duplicate { $ oidx } { ION } eq "" ) {
return ( 1 , $ oidx ) ;
2009-11-12 19:08:01 +00:00
} elsif ( $ duplicate { $ oidx } { MSG } eq $ msg &&
$ duplicate { $ oidx } { ION } ne $ ioname ) {
return ( 1 , $ oidx ) ;
}
}
2013-07-15 20:34:58 +00:00
#Debug "is unique";
2009-11-12 19:08:01 +00:00
$ duplicate { $ duplidx } { ION } = $ ioname ;
$ duplicate { $ duplidx } { MSG } = $ msg ;
$ duplicate { $ duplidx } { TIM } = $ now ;
$ duplidx + + ;
return ( 0 , $ duplidx - 1 ) ;
}
2013-07-15 20:34:58 +00:00
sub
rejectDuplicate ( $$ $ )
{
#Debug "is duplicate";
my ( $ name , $ idx , $ addvals ) = @ _ ;
my $ found = $ duplicate { $ idx } { FND } ;
foreach my $ found ( @ { $ found } ) {
if ( $ addvals ) {
foreach my $ av ( keys % { $ addvals } ) {
$ defs { $ found } { "${name}_$av" } = $ addvals - > { $ av } ;
}
}
$ defs { $ found } { "${name}_MSGCNT" } + + ;
$ defs { $ found } { "${name}_TIME" } = TimeNow ( ) ;
}
return $ duplicate { $ idx } { FND } ;
}
2009-11-12 19:08:01 +00:00
sub
AddDuplicate ( $$ )
{
$ duplicate { $ duplidx } { ION } = shift ;
$ duplicate { $ duplidx } { MSG } = shift ;
$ duplicate { $ duplidx } { TIM } = gettimeofday ( ) ;
$ duplidx + + ;
}
2010-04-02 14:20:53 +00:00
2010-12-27 09:42:16 +00:00
# Add an attribute to the userattr list, if not yet present
2010-10-24 16:08:48 +00:00
sub
2014-10-03 10:53:48 +00:00
addToDevAttrList ( $$ )
2010-10-24 16:08:48 +00:00
{
2014-10-03 10:53:48 +00:00
my ( $ dev , $ arg ) = @ _ ;
2010-10-24 16:08:48 +00:00
2014-10-04 14:32:17 +00:00
my $ ua = $ attr { $ dev } { userattr } ;
$ ua = "" if ( ! $ ua ) ;
my % hash = map { ( $ _ = > 1 ) }
grep { " $AttrList " !~ m/ $_ / }
split ( " " , "$ua $arg" ) ;
2014-10-03 10:53:48 +00:00
$ attr { $ dev } { userattr } = join ( " " , sort keys % hash ) ;
}
sub
addToAttrList ( $ )
{
2014-10-04 14:32:17 +00:00
addToDevAttrList ( "global" , shift ) ;
2010-10-24 16:08:48 +00:00
}
2010-12-27 09:42:16 +00:00
sub
2013-12-13 08:15:43 +00:00
attrSplit ( $ )
2010-12-27 09:42:16 +00:00
{
2012-07-05 07:02:21 +00:00
my ( $ em ) = @ _ ;
2011-07-30 13:22:25 +00:00
my $ sc = " " ; # Split character
2011-09-30 15:46:19 +00:00
my $ fc = substr ( $ em , 0 , 1 ) ; # First character of the eventMap
2011-07-30 13:22:25 +00:00
if ( $ fc eq "," || $ fc eq "/" ) {
$ sc = $ fc ;
$ em = substr ( $ em , 1 ) ;
}
2012-07-05 07:02:21 +00:00
return split ( $ sc , $ em ) ;
}
2012-07-15 07:44:25 +00:00
#######################
2012-07-05 07:02:21 +00:00
# $dir: 0 = User to Fhem (i.e. set), 1 = Fhem to User (i.e trigger)
sub
ReplaceEventMap ( $$ $ )
{
my ( $ dev , $ str , $ dir ) = @ _ ;
my $ em = $ attr { $ dev } { eventMap } ;
2012-07-15 08:04:35 +00:00
return $ str if ( $ dir && ! $ em ) ;
2013-01-07 19:45:45 +00:00
return @ { $ str } if ( ! $ dir && ( ! $ em || int ( @ { $ str } ) < 2 || $ str - > [ 1 ] eq "?" ) ) ;
2012-07-05 07:02:21 +00:00
my $ dname = shift @ { $ str } if ( ! $ dir ) ;
2010-12-27 09:42:16 +00:00
2011-07-30 13:22:25 +00:00
my $ nstr = join ( " " , @ { $ str } ) if ( ! $ dir ) ;
my $ changed ;
2013-12-13 08:15:43 +00:00
my @ emList = attrSplit ( $ em ) ;
2012-07-05 07:02:21 +00:00
foreach my $ rv ( @ emList ) {
2013-08-10 07:49:51 +00:00
# Real-Event-Regexp:GivenName[:modifier]
my ( $ re , $ val , $ modifier ) = split ( ":" , $ rv , 3 ) ;
2011-07-30 13:22:25 +00:00
next if ( ! defined ( $ val ) ) ;
2012-05-23 09:01:25 +00:00
if ( $ dir ) { # event -> GivenName
2014-10-18 06:14:57 +00:00
my $ reIsWord = ( $ re =~ m/^\w*$/ ) ; # dim100% is not \w only, cant use \b
2014-10-18 06:07:58 +00:00
if ( $ reIsWord ) {
if ( $ str =~ m/\b$re\b/ ) {
$ str =~ s/\b$re\b/$val/ ;
$ changed = 1 ;
}
} else {
if ( $ str =~ m/$re/ ) {
$ str =~ s/$re/$val/ ;
$ changed = 1 ;
}
2011-07-30 13:22:25 +00:00
}
2012-05-23 09:01:25 +00:00
} else { # GivenName -> set command
2013-01-03 12:50:16 +00:00
if ( $ nstr eq $ val ) { # for special translations like <> and <<
$ nstr = $ re ;
$ changed = 1 ;
2014-10-18 06:14:57 +00:00
} else {
my $ reIsWord = ( $ val =~ m/^\w*$/ ) ;
if ( $ reIsWord ) {
if ( $ nstr =~ m/\b$val\b/ ) {
$ nstr =~ s/\b$val\b/$re/ ;
$ changed = 1 ;
}
} elsif ( $ nstr =~ m/$val/ ) {
2014-10-18 06:07:58 +00:00
$ nstr =~ s/$val/$re/ ;
$ changed = 1 ;
}
2011-07-30 13:22:25 +00:00
}
2010-12-27 09:42:16 +00:00
}
2014-10-18 06:14:57 +00:00
last if ( $ changed ) ;
2010-12-27 09:42:16 +00:00
}
2011-07-30 13:22:25 +00:00
return $ str if ( $ dir ) ;
2012-03-18 11:36:25 +00:00
if ( $ changed ) {
my @ arr = split ( " " , $ nstr ) ;
2012-03-20 09:39:08 +00:00
unshift @ arr , $ dname ;
2012-03-18 11:36:25 +00:00
return @ arr ;
} else {
unshift @ { $ str } , $ dname ;
return @ { $ str } ;
}
2010-12-27 09:42:16 +00:00
}
2011-01-29 12:07:14 +00:00
sub
2012-06-07 06:22:00 +00:00
setGlobalAttrBeforeFork ( $ )
2011-01-29 12:07:14 +00:00
{
2012-06-07 06:22:00 +00:00
my ( $ f ) = @ _ ;
2014-03-01 07:59:19 +00:00
2014-05-02 18:50:33 +00:00
my ( $ err , @ rows ) ;
2014-03-01 07:59:19 +00:00
if ( $ f eq 'configDB' ) {
2014-05-03 09:33:39 +00:00
@ rows = cfgDB_AttrRead ( 'global' ) ;
2014-05-02 18:50:33 +00:00
} else {
( $ err , @ rows ) = FileRead ( $ f ) ;
die ( "$err\n" ) if ( $ err ) ;
2014-03-01 07:59:19 +00:00
}
2014-05-02 18:50:33 +00:00
foreach my $ l ( @ rows ) {
2011-09-30 15:46:19 +00:00
$ l =~ s/[\r\n]//g ;
2011-07-08 12:14:12 +00:00
next if ( $ l !~ m/^attr\s+global\s+([^\s]+)\s+(.*)$/ ) ;
2011-02-12 11:27:16 +00:00
my ( $ n , $ v ) = ( $ 1 , $ 2 ) ;
$ v =~ s/#.*// ;
$ v =~ s/ .*$// ;
$ attr { global } { $ n } = $ v ;
2014-04-26 06:53:09 +00:00
GlobalAttr ( "set" , "global" , $ n , $ v ) ;
2011-01-29 12:07:14 +00:00
}
}
2012-02-11 23:42:47 +00:00
2012-02-14 08:13:08 +00:00
###########################################
# Functions used to make fhem-oneliners more readable,
# but also recommended to be used by modules
2013-06-25 08:55:15 +00:00
sub
InternalVal ( $$ $ )
{
my ( $ d , $ n , $ default ) = @ _ ;
if ( defined ( $ defs { $ d } ) &&
defined ( $ defs { $ d } { $ n } ) ) {
return $ defs { $ d } { $ n } ;
}
return $ default ;
}
2012-02-14 08:13:08 +00:00
sub
ReadingsVal ( $$ $ )
{
my ( $ d , $ n , $ default ) = @ _ ;
if ( defined ( $ defs { $ d } ) &&
defined ( $ defs { $ d } { READINGS } ) &&
defined ( $ defs { $ d } { READINGS } { $ n } ) &&
defined ( $ defs { $ d } { READINGS } { $ n } { VAL } ) ) {
return $ defs { $ d } { READINGS } { $ n } { VAL } ;
}
return $ default ;
}
2014-03-10 09:53:52 +00:00
sub
ReadingsNum ( $$ $ )
2014-10-18 05:25:57 +00:00
{
2014-03-10 09:53:52 +00:00
my ( $ d , $ n , $ default ) = @ _ ;
2014-10-18 05:25:57 +00:00
my $ val = ReadingsVal ( $ d , $ n , $ default ) ;
$ val =~ s/[^-\.\d]//g ;
return $ val ;
2014-03-10 09:53:52 +00:00
}
2012-02-14 08:13:08 +00:00
sub
ReadingsTimestamp ( $$ $ )
{
my ( $ d , $ n , $ default ) = @ _ ;
if ( defined ( $ defs { $ d } ) &&
defined ( $ defs { $ d } { READINGS } ) &&
defined ( $ defs { $ d } { READINGS } { $ n } ) &&
defined ( $ defs { $ d } { READINGS } { $ n } { TIME } ) ) {
return $ defs { $ d } { READINGS } { $ n } { TIME } ;
}
return $ default ;
}
sub
Value ( $ )
{
my ( $ d ) = @ _ ;
if ( defined ( $ defs { $ d } ) &&
defined ( $ defs { $ d } { STATE } ) ) {
return $ defs { $ d } { STATE } ;
}
return "" ;
}
sub
OldValue ( $ )
{
my ( $ d ) = @ _ ;
return $ oldvalue { $ d } { VAL } if ( defined ( $ oldvalue { $ d } ) ) ;
return "" ;
}
sub
OldTimestamp ( $ )
{
my ( $ d ) = @ _ ;
return $ oldvalue { $ d } { TIME } if ( defined ( $ oldvalue { $ d } ) ) ;
return "" ;
}
sub
AttrVal ( $$ $ )
{
my ( $ d , $ n , $ default ) = @ _ ;
return $ attr { $ d } { $ n } if ( $ d && defined ( $ attr { $ d } ) && defined ( $ attr { $ d } { $ n } ) ) ;
return $ default ;
}
################################################################
# Functions used by modules.
sub
setReadingsVal ( $$ $$ )
{
my ( $ hash , $ rname , $ val , $ ts ) = @ _ ;
2012-11-26 17:02:43 +00:00
$ hash - > { READINGS } { $ rname } { VAL } = $ val ;
$ hash - > { READINGS } { $ rname } { TIME } = $ ts ;
2012-02-14 08:13:08 +00:00
}
sub
addEvent ( $$ )
{
my ( $ hash , $ event ) = @ _ ;
push ( @ { $ hash - > { CHANGED } } , $ event ) ;
}
2012-02-20 12:38:48 +00:00
sub
concatc ( $$ $ ) {
my ( $ separator , $ a , $ b ) = @ _ ; ;
return ( $ a && $ b ? $ a . $ separator . $ b : $ a . $ b ) ;
}
2012-02-11 23:42:47 +00:00
################################################################
#
2012-02-14 08:13:08 +00:00
# Wrappers for commonly used core functions in device-specific modules.
2012-02-11 23:42:47 +00:00
#
################################################################
#
# Call readingsBeginUpdate before you start updating readings.
# The updated readings will all get the same timestamp,
# which is the time when you called this subroutine.
#
sub
2013-01-03 12:50:16 +00:00
readingsBeginUpdate ( $ )
{
2012-02-11 23:42:47 +00:00
my ( $ hash ) = @ _ ;
2013-01-03 12:50:16 +00:00
my $ name = $ hash - > { NAME } ;
2012-02-11 23:42:47 +00:00
# get timestamp
2013-03-01 11:09:18 +00:00
my $ now = gettimeofday ( ) ;
my $ fmtDateTime = FmtDateTime ( $ now ) ;
$ hash - > { ".updateTime" } = $ now ; # in seconds since the epoch
$ hash - > { ".updateTimestamp" } = $ fmtDateTime ;
my $ attrminint = AttrVal ( $ name , "event-min-interval" , undef ) ;
if ( $ attrminint ) {
my @ a = split ( /,/ , $ attrminint ) ;
$ hash - > { ".attrminint" } = \ @ a ;
}
2015-01-25 15:24:49 +00:00
my $ attraggr = AttrVal ( $ name , "event-aggregator" , undef ) ;
if ( $ attraggr ) {
my @ a = split ( /,/ , $ attraggr ) ;
$ hash - > { ".attraggr" } = \ @ a ;
}
2013-01-03 12:50:16 +00:00
my $ attreocr = AttrVal ( $ name , "event-on-change-reading" , undef ) ;
if ( $ attreocr ) {
my @ a = split ( /,/ , $ attreocr ) ;
$ hash - > { ".attreocr" } = \ @ a ;
}
2012-02-11 23:42:47 +00:00
2013-01-03 12:50:16 +00:00
my $ attreour = AttrVal ( $ name , "event-on-update-reading" , undef ) ;
if ( $ attreour ) {
my @ a = split ( /,/ , $ attreour ) ;
$ hash - > { ".attreour" } = \ @ a ;
}
2012-02-11 23:42:47 +00:00
2013-07-10 19:19:30 +00:00
$ hash - > { CHANGED } = ( ) if ( ! defined ( $ hash - > { CHANGED } ) ) ;
2013-03-01 11:09:18 +00:00
return $ fmtDateTime ;
2012-02-11 23:42:47 +00:00
}
2013-01-22 18:08:53 +00:00
sub
evalStateFormat ( $ )
{
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
###########################
# Set STATE
my $ sr = AttrVal ( $ name , "stateFormat" , undef ) ;
my $ st = $ hash - > { READINGS } { state } ;
if ( ! $ sr ) {
$ st = $ st - > { VAL } if ( defined ( $ st ) ) ;
} elsif ( $ sr =~ m/^{(.*)}$/ ) {
$ st = eval $ 1 ;
if ( $@ ) {
$ st = "Error evaluating $name stateFormat: $@" ;
Log 1 , $ st ;
}
} else {
# Substitute reading names with their values, leave the rest untouched.
$ st = $ sr ;
my $ r = $ hash - > { READINGS } ;
2013-07-26 08:57:39 +00:00
$ st =~ s/\b([A-Za-z\d_\.-]+)\b/($r->{$1} ? $r->{$1}{VAL} : $1)/ge ;
2013-01-22 18:08:53 +00:00
}
$ hash - > { STATE } = ReplaceEventMap ( $ name , $ st , 1 ) if ( defined ( $ st ) ) ;
}
2013-01-13 15:16:31 +00:00
2012-02-11 23:42:47 +00:00
#
# Call readingsEndUpdate when you are done updating readings.
# This optionally calls DoTrigger to propagate the changes.
#
sub
2013-01-03 12:50:16 +00:00
readingsEndUpdate ( $$ )
{
2012-02-11 23:42:47 +00:00
my ( $ hash , $ dotrigger ) = @ _ ;
2013-01-03 12:50:16 +00:00
my $ name = $ hash - > { NAME } ;
2013-01-30 10:39:30 +00:00
2013-06-22 13:47:34 +00:00
$ hash - > { ".triggerUsed" } = 1 if ( defined ( $ hash - > { ".triggerUsed" } ) ) ;
2013-06-20 07:31:37 +00:00
2013-01-13 15:16:31 +00:00
# process user readings
2013-02-17 13:55:05 +00:00
if ( defined ( $ hash - > { '.userReadings' } ) ) {
my % userReadings = % { $ hash - > { '.userReadings' } } ;
2013-01-13 15:16:31 +00:00
foreach my $ userReading ( keys % userReadings ) {
2013-05-17 18:16:54 +00:00
my $ trigger = $ userReadings { $ userReading } { trigger } ;
if ( defined ( $ trigger ) ) {
my @ fnd = grep { $ _ && $ _ =~ m/^$trigger/ } @ { $ hash - > { CHANGED } } ;
next if ( ! @ fnd ) ;
}
2013-02-10 09:57:02 +00:00
my $ modifier = $ userReadings { $ userReading } { modifier } ;
my $ perlCode = $ userReadings { $ userReading } { perlCode } ;
my $ oldvalue = $ userReadings { $ userReading } { value } ;
my $ oldt = $ userReadings { $ userReading } { t } ;
2013-01-19 13:36:29 +00:00
#Debug "Evaluating " . $userReadings{$userReading};
2015-01-17 21:37:05 +00:00
$ cmdFromAnalyze = $ perlCode ; # For the __WARN__ sub
2013-02-10 09:57:02 +00:00
my $ value = eval $ perlCode ;
2015-01-17 21:37:05 +00:00
$ cmdFromAnalyze = undef ;
2013-02-10 09:57:02 +00:00
my $ result ;
# store result
2013-01-13 15:16:31 +00:00
if ( $@ ) {
$ value = "Error evaluating $name userReading $userReading: $@" ;
Log 1 , $ value ;
2013-02-10 09:57:02 +00:00
$ result = $ value ;
} elsif ( $ modifier eq "none" ) {
$ result = $ value ;
} elsif ( $ modifier eq "difference" ) {
$ result = $ value - $ oldvalue if ( defined ( $ oldvalue ) ) ;
} elsif ( $ modifier eq "differential" ) {
my $ deltav = $ value - $ oldvalue if ( defined ( $ oldvalue ) ) ;
my $ deltat = $ hash - > { ".updateTime" } - $ oldt if ( defined ( $ oldt ) ) ;
if ( defined ( $ deltav ) && defined ( $ deltat ) && ( $ deltat >= 1.0 ) ) {
$ result = $ deltav / $ deltat ;
}
2014-08-19 20:55:00 +00:00
} elsif ( $ modifier eq "integral" ) {
if ( defined ( $ oldt ) && defined ( $ oldvalue ) ) {
my $ deltat = $ hash - > { ".updateTime" } - $ oldt if ( defined ( $ oldt ) ) ;
my $ avgval = ( $ value + $ oldvalue ) / 2 ;
$ result = ReadingsVal ( $ name , $ userReading , $ value ) ;
if ( defined ( $ deltat ) && $ deltat >= 1.0 ) {
$ result += $ avgval * $ deltat ;
}
}
2013-07-03 19:09:06 +00:00
} elsif ( $ modifier eq "offset" ) {
2013-09-07 11:58:33 +00:00
$ oldvalue = $ value if ( ! defined ( $ oldvalue ) ) ;
2013-07-03 19:09:06 +00:00
$ result = ReadingsVal ( $ name , $ userReading , 0 ) ;
$ result += $ oldvalue if ( $ value < $ oldvalue ) ;
} elsif ( $ modifier eq "monotonic" ) {
2013-09-07 11:58:33 +00:00
$ oldvalue = $ value if ( ! defined ( $ oldvalue ) ) ;
$ result = ReadingsVal ( $ name , $ userReading , $ value ) ;
2013-07-03 19:09:06 +00:00
$ result += $ value - $ oldvalue if ( $ value > $ oldvalue ) ;
2013-02-10 09:57:02 +00:00
}
readingsBulkUpdate ( $ hash , $ userReading , $ result , 1 ) if ( defined ( $ result ) ) ;
# store value
2013-02-17 13:55:05 +00:00
$ hash - > { '.userReadings' } { $ userReading } { TIME } = $ hash - > { ".updateTimestamp" } ;
$ hash - > { '.userReadings' } { $ userReading } { t } = $ hash - > { ".updateTime" } ;
$ hash - > { '.userReadings' } { $ userReading } { value } = $ value ;
2013-01-13 15:16:31 +00:00
}
}
2013-01-30 10:39:30 +00:00
evalStateFormat ( $ hash ) ;
2013-01-13 15:16:31 +00:00
2012-02-11 23:42:47 +00:00
# turn off updating mode
2013-01-03 12:50:16 +00:00
delete $ hash - > { ".updateTimestamp" } ;
2013-02-10 09:57:02 +00:00
delete $ hash - > { ".updateTime" } ;
2013-01-03 12:50:16 +00:00
delete $ hash - > { ".attreour" } ;
delete $ hash - > { ".attreocr" } ;
2015-01-25 15:24:49 +00:00
delete $ hash - > { ".attraggr" } ;
2013-03-01 11:09:18 +00:00
delete $ hash - > { ".attrminint" } ;
2013-01-03 12:50:16 +00:00
2013-01-30 10:39:30 +00:00
2012-02-11 23:42:47 +00:00
# propagate changes
2013-01-03 12:50:16 +00:00
if ( $ dotrigger && $ init_done ) {
2013-02-17 13:55:05 +00:00
DoTrigger ( $ name , undef , 0 ) if ( ! $ readingsUpdateDelayTrigger ) ;
2013-01-03 12:50:16 +00:00
} else {
2014-04-06 06:24:47 +00:00
if ( ! defined ( $ hash - > { INTRIGGER } ) ) {
delete ( $ hash - > { CHANGED } ) ;
delete ( $ hash - > { CHANGEDWITHSTATE } )
}
2012-02-11 23:42:47 +00:00
}
return undef ;
}
#
2012-11-08 20:56:21 +00:00
# Call readingsBulkUpdate to update the reading.
2012-02-11 23:42:47 +00:00
# Example: readingsUpdate($hash,"temperature",$value);
#
sub
2013-01-03 12:50:16 +00:00
readingsBulkUpdate ( $$ $@ )
{
my ( $ hash , $ reading , $ value , $ changed ) = @ _ ;
2012-02-11 23:42:47 +00:00
my $ name = $ hash - > { NAME } ;
2013-01-06 17:23:43 +00:00
return if ( ! defined ( $ reading ) || ! defined ( $ value ) ) ;
2012-02-11 23:42:47 +00:00
# sanity check
2013-01-03 12:50:16 +00:00
if ( ! defined ( $ hash - > { ".updateTimestamp" } ) ) {
Log 1 , "readingsUpdate($name,$reading,$value) missed to call " .
"readingsBeginUpdate first." ;
return ;
}
2012-02-11 23:51:49 +00:00
# shorthand
2013-01-03 12:50:16 +00:00
my $ readings = $ hash - > { READINGS } { $ reading } ;
2012-11-24 13:48:12 +00:00
2013-01-03 12:50:16 +00:00
if ( ! defined ( $ changed ) ) {
$ changed = ( substr ( $ reading , 0 , 1 ) ne "." ) ; # Dont trigger dot-readings
}
2013-03-01 11:09:18 +00:00
$ changed = 0 if ( $ hash - > { ".ignoreEvent" } ) ;
2012-11-24 13:48:12 +00:00
# check for changes only if reading already exists
2013-01-03 12:50:16 +00:00
if ( $ changed && defined ( $ readings ) ) {
2012-02-11 23:51:49 +00:00
2013-01-03 12:50:16 +00:00
# these flags determine if any of the "event-on" attributes are set
2014-06-07 16:12:09 +00:00
my $ attreocr = $ hash - > { ".attreocr" } ;
my $ attreour = $ hash - > { ".attreour" } ;
# determine whether the reading is listed in any of the attributes
my $ eocr = $ attreocr && ( my @ eocrv = grep { my $ l = $ _ ;
$ l =~ s/:.*// ;
( $ reading =~ m/^$l$/ ) ? $ _ : undef } @ { $ attreocr } ) ;
my $ eour = $ attreour && grep ( $ reading =~ m/^$_$/ , @ { $ attreour } ) ;
# check if threshold is given
if ( $ eocr
&& $ eocrv [ 0 ] =~ m/.*:(.*)/ ) {
2014-11-03 22:25:33 +00:00
my $ threshold = $ 1 ;
2014-06-07 16:12:09 +00:00
2014-07-13 10:41:00 +00:00
$ value =~ s/[^\d\.\-]//g ; # We expect only numbers here.
2014-06-07 16:12:09 +00:00
my $ last_value = $ hash - > { ".attreocr-threshold$reading" } ;
if ( ! defined ( $ last_value ) ) {
$ hash - > { ".attreocr-threshold$reading" } = $ value ;
2014-11-03 22:25:33 +00:00
} elsif ( abs ( $ value - $ last_value ) < $ threshold ) {
2014-12-22 07:12:41 +00:00
$ eocr = 0 ;
2014-06-07 16:12:09 +00:00
} else {
$ hash - > { ".attreocr-threshold$reading" } = $ value ;
}
}
2013-01-03 12:50:16 +00:00
# determine if an event should be created:
# always create event if no attribute is set
# or if the reading is listed in event-on-update-reading
# or if the reading is listed in event-on-change-reading...
2014-06-07 16:12:09 +00:00
# ...and its value has changed...
# ...and the change greater then the threshold
2013-01-03 12:50:16 +00:00
$ changed = ! ( $ attreocr || $ attreour )
|| $ eour
2014-12-22 07:12:41 +00:00
|| ( $ eocr && ( $ value ne $ readings - > { VAL } ) ) ;
2013-01-03 12:50:16 +00:00
#Log 1, "EOCR:$eocr EOUR:$eour CHANGED:$changed";
2013-03-01 11:09:18 +00:00
my @ v = grep { my $ l = $ _ ;
$ l =~ s/:.*// ;
( $ reading =~ m/^$l$/ ) ? $ _ : undef } @ { $ hash - > { ".attrminint" } } ;
2013-04-25 08:26:36 +00:00
if ( @ v ) {
2013-03-01 11:09:18 +00:00
my ( undef , $ minInt ) = split ( ":" , $ v [ 0 ] ) ;
my $ now = $ hash - > { ".updateTime" } ;
my $ le = $ hash - > { ".lastTime$reading" } ;
if ( $ le && $ now - $ le < $ minInt ) {
2013-05-29 10:58:17 +00:00
if ( ! $ eocr || ( $ eocr && $ value eq $ readings - > { VAL } ) ) {
$ changed = 0 ;
} else {
$ hash - > { ".lastTime$reading" } = $ now ;
}
2013-03-01 11:09:18 +00:00
} else {
$ hash - > { ".lastTime$reading" } = $ now ;
2013-04-25 08:26:36 +00:00
$ changed = 1 if ( $ eocr ) ;
2013-03-01 11:09:18 +00:00
}
}
2015-01-25 15:24:49 +00:00
2012-11-24 13:48:12 +00:00
}
2015-01-25 15:24:49 +00:00
if ( $ changed ) {
#Debug "Processing $reading: $value";
my @ v = grep { my $ l = $ _ ;
$ l =~ s/:.*// ;
( $ reading =~ m/^$l$/ ) ? $ _ : undef } @ { $ hash - > { ".attraggr" } } ;
if ( @ v ) {
# e.g. power:20:linear:avg
my ( undef , $ duration , $ method , $ function ) = split ( ":" , $ v [ 0 ] , 4 ) ;
my $ ts ;
if ( defined ( $ readings - > { ".ts" } ) ) {
$ ts = $ readings - > { ".ts" } ;
} else {
require "TimeSeries.pm" ;
$ ts = TimeSeries - > new ( { method = > $ method , autoreset = > $ duration } ) ;
$ readings - > { ".ts" } = $ ts ;
# access from command line:
# { $defs{"myClient"}{READINGS}{"myValue"}{".ts"}{max} }
#Debug "TimeSeries created.";
}
my $ now = $ hash - > { ".updateTime" } ;
$ changed = $ ts - > elapsed ( $ now ) ;
$ value = $ ts - > { $ function } if ( $ changed ) ;
$ ts - > add ( $ now , $ value ) ;
} else {
# If no event-aggregator attribute, then remove stale series if any.
delete $ readings - > { ".ts" } ;
}
}
2013-01-03 12:50:16 +00:00
setReadingsVal ( $ hash , $ reading , $ value , $ hash - > { ".updateTimestamp" } ) ;
2012-02-11 23:42:47 +00:00
2013-01-03 12:50:16 +00:00
my $ rv = "$reading: $value" ;
if ( $ changed ) {
2014-04-06 06:24:47 +00:00
if ( $ reading eq "state" ) {
$ rv = "$value" ;
$ hash - > { CHANGEDWITHSTATE } = [] ;
}
2013-01-03 12:50:16 +00:00
addEvent ( $ hash , $ rv ) ;
}
2012-02-11 23:42:47 +00:00
return $ rv ;
}
2012-04-04 10:54:55 +00:00
2012-11-08 20:56:21 +00:00
#
# this is a shorthand call
#
sub
2013-01-03 12:50:16 +00:00
readingsSingleUpdate ( $$ $$ )
{
2012-11-08 20:56:21 +00:00
my ( $ hash , $ reading , $ value , $ dotrigger ) = @ _ ;
readingsBeginUpdate ( $ hash ) ;
2013-01-03 12:50:16 +00:00
my $ rv = readingsBulkUpdate ( $ hash , $ reading , $ value ) ;
2012-11-08 20:56:21 +00:00
readingsEndUpdate ( $ hash , $ dotrigger ) ;
return $ rv ;
}
2013-01-03 12:50:16 +00:00
##############################################################################
2012-06-17 14:31:17 +00:00
#
# date and time routines
#
##############################################################################
sub
fhemTzOffset ( $ ) {
# see http://stackoverflow.com/questions/2143528/whats-the-best-way-to-get-the-utc-offset-in-perl
my $ t = shift ;
my @ l = localtime ( $ t ) ;
my @ g = gmtime ( $ t ) ;
# the offset is positive if the local timezone is ahead of GMT, e.g. we get 2*3600 seconds for CET DST vs GMT
return 60 * ( ( $ l [ 2 ] - $ g [ 2 ] + ( ( ( $ l [ 5 ] << 9 ) | $ l [ 7 ] ) <=> ( ( $ g [ 5 ] << 9 ) | $ g [ 7 ] ) ) * 24 ) * 60 + $ l [ 1 ] - $ g [ 1 ] ) ;
}
sub
fhemTimeGm ( $$ $$ $$ ) {
# see http://de.wikipedia.org/wiki/Unixzeit
my ( $ sec , $ min , $ hour , $ mday , $ month , $ year ) = @ _ ;
# $mday= 1..
# $month= 0..11
# $year is year-1900
$ year += 1900 ;
my $ isleapyear = $ year % 4 ? 0 : $ year % 100 ? 1 : $ year % 400 ? 0 : 1 ;
2014-09-01 10:07:43 +00:00
my $ leapyears = int ( ( $ year - 1969 ) /4) - int(($year-1901)/ 100 ) + int ( ( $ year - 1601 ) / 400 ) ;
2012-12-01 20:00:04 +00:00
#Debug sprintf("%02d.%02d.%04d %02d:%02d:%02d %d leap years, is leap year: %d", $mday,$month+1,$year,$hour,$min,$sec,$leapyears,$isleapyear);
2012-06-17 14:31:17 +00:00
if ( $^O eq 'MacOS' ) {
$ year -= 1904 ;
} else {
$ year -= 1970 ; # the Unix Epoch
}
my @ d = ( 0 , 31 , 59 , 90 , 120 , 151 , 181 , 212 , 243 , 273 , 304 , 334 ) ; # no leap day
# add one day in leap years if month is later than February
$ mday + + if ( $ month > 1 && $ isleapyear ) ;
return $ sec + 60 * ( $ min + 60 * ( $ hour + 24 * ( $ d [ $ month ] + $ mday - 1 + 365 * $ year + $ leapyears ) ) ) ;
}
sub
fhemTimeLocal ( $$ $$ $$ ) {
my $ t = fhemTimeGm ( $ _ [ 0 ] , $ _ [ 1 ] , $ _ [ 2 ] , $ _ [ 3 ] , $ _ [ 4 ] , $ _ [ 5 ] ) ;
return $ t - fhemTzOffset ( $ t ) ;
}
2013-11-20 12:56:23 +00:00
# compute the list of defined logical modules for a physical module
2013-06-01 17:13:50 +00:00
sub
computeClientArray ( $$ )
{
2013-11-20 12:56:23 +00:00
my ( $ hash , $ module ) = @ _ ;
2013-06-01 17:13:50 +00:00
my @ a = ( ) ;
2013-11-20 12:56:23 +00:00
my @ mRe = split ( ":" , $ hash - > { Clients } ? $ hash - > { Clients } : $ module - > { Clients } ) ;
2013-06-01 17:13:50 +00:00
foreach my $ m ( sort { $ modules { $ a } { ORDER } cmp $ modules { $ b } { ORDER } }
grep { defined ( $ modules { $ _ } { ORDER } ) } keys % modules ) {
foreach my $ re ( @ mRe ) {
if ( $ m =~ m/^$re$/ ) {
push @ a , $ m if ( $ modules { $ m } { Match } ) ;
last ;
}
}
}
$ hash - > { ".clientArray" } = \ @ a ;
return \ @ a ;
}
2013-08-06 09:15:31 +00:00
# http://perldoc.perl.org/perluniintro.html, UNICODE IN OLDER PERLS
2013-08-06 07:18:41 +00:00
sub
latin1ToUtf8 ( $ )
{
my ( $ s ) = @ _ ;
$ s =~ s/([\x80-\xFF])/chr(0xC0|ord($1)>>6).chr(0x80|ord($1)&0x3F)/eg ;
return $ s ;
}
2013-08-06 09:15:31 +00:00
sub
utf8ToLatin1 ( $ )
{
my ( $ s ) = @ _ ;
$ s =~ s/([\xC2\xC3])([\x80-\xBF])/chr(ord($1)<<6&0xC0|ord($2)&0x3F)/eg ;
return $ s ;
}
2014-03-10 09:53:52 +00:00
# replaces some common control chars by escape sequences
# in order to make logs more readable
sub escapeLogLine ($) {
my ( $ s ) = @ _ ;
2014-03-16 16:16:32 +00:00
# http://perldoc.perl.org/perlrebackslash.html
2014-03-10 09:53:52 +00:00
my % escSequences = (
2014-03-16 16:16:32 +00:00
'\a' = > "\\a" ,
'\e' = > "\\e" ,
'\f' = > "\\f" ,
2014-03-10 09:53:52 +00:00
'\n' = > "\\n" ,
'\r' = > "\\r" ,
2014-03-16 16:16:32 +00:00
'\t' = > "\\t" ,
2014-03-10 09:53:52 +00:00
) ;
2014-03-16 16:16:32 +00:00
$ s =~ s/\\/\\\\/g ;
2014-03-10 09:53:52 +00:00
foreach my $ regex ( keys % escSequences ) {
$ s =~ s/$regex/$escSequences{$regex}/g ;
}
2014-03-16 16:16:32 +00:00
$ s =~ s/([\000-\037])/sprintf("\\%03o", ord($1))/eg ;
2014-03-10 09:53:52 +00:00
return $ s ;
}
2013-08-25 11:49:30 +00:00
sub
Debug ( $ ) {
my $ msg = shift ;
Log 1 , "DEBUG>" . $ msg ;
}
2013-11-09 13:45:34 +00:00
sub
2014-04-24 10:05:01 +00:00
addToWritebuffer ( $$ @ )
2013-11-09 13:45:34 +00:00
{
2014-04-24 10:27:44 +00:00
my ( $ hash , $ txt , $ callback , $ nolimit ) = @ _ ;
2014-04-24 10:05:01 +00:00
2014-12-14 15:55:51 +00:00
if ( $ hash - > { isChild } ) { # Wont go to the main select in a forked process
TcpServer_WriteBlocking ( $ hash , $ txt ) ;
2014-04-24 10:05:01 +00:00
if ( $ callback ) {
no strict "refs" ;
my $ ret = & { $ callback } ( $ hash ) ;
use strict "refs" ;
}
return ;
}
2013-11-09 13:45:34 +00:00
2014-04-24 10:05:01 +00:00
$ hash - > { WBCallback } = $ callback ;
2013-11-09 13:45:34 +00:00
if ( ! $ hash - > { $ wbName } ) {
$ hash - > { $ wbName } = $ txt ;
2014-04-24 10:27:44 +00:00
} elsif ( $ nolimit || length ( $ hash - > { $ wbName } ) < 102400 ) {
2013-11-09 13:45:34 +00:00
$ hash - > { $ wbName } . = $ txt ;
2014-12-14 15:55:51 +00:00
} else {
return 0 ;
2013-11-09 13:45:34 +00:00
}
2014-12-14 15:55:51 +00:00
return 1 ; # success
2013-11-09 13:45:34 +00:00
}
2014-01-14 19:23:34 +00:00
sub
createNtfyHash ( )
{
my @ ntfyList = sort { $ defs { $ a } { NTFY_ORDER } cmp $ defs { $ b } { NTFY_ORDER } }
grep { $ defs { $ _ } { NTFY_ORDER } } keys % defs ;
foreach my $ d ( @ ntfyList ) {
my $ nd = $ defs { $ d } { NOTIFYDEV } ;
#Log 1, "Created notify class for $nd / $d" if($nd);
$ ntfyHash { $ nd } = [] if ( $ nd && ! defined ( $ ntfyHash { $ nd } ) ) ;
}
$ ntfyHash { "*" } = [] ;
foreach my $ d ( @ ntfyList ) {
my $ nd = $ defs { $ d } { NOTIFYDEV } ;
if ( $ nd ) {
push @ { $ ntfyHash { $ nd } } , $ d ;
} else {
foreach $ nd ( keys % ntfyHash ) {
push @ { $ ntfyHash { $ nd } } , $ d ;
}
}
}
}
2014-01-16 09:45:15 +00:00
sub
notifyRegexpChanged ( $$ )
{
my ( $ hash , $ re ) = @ _ ;
my $ dev ;
$ dev = $ 1 if ( ( $ re =~ m/^([^:]*)$/ || $ re =~ m/^([^:]*):(.*)$/ ) ) ;
if ( $ dev && defined ( $ defs { $ dev } ) && $ re !~ m/\|/ ) {
$ hash - > { NOTIFYDEV } = $ dev ;
} else {
delete ( $ hash - > { NOTIFYDEV } ) ; # when called by modify
}
}
2014-04-20 19:20:42 +00:00
sub
configDBUsed ( )
{
return ( $ attr { global } { configfile } eq 'configDB' ) ;
}
2014-01-16 09:45:15 +00:00
2014-05-01 15:02:06 +00:00
sub
FileRead ( $ )
{
2014-06-01 11:45:00 +00:00
my ( $ param ) = @ _ ;
my ( $ err , @ ret , $ fileName , $ forceType ) ;
2014-05-01 15:02:06 +00:00
2014-06-01 11:45:00 +00:00
if ( ref ( $ param ) eq "HASH" ) {
$ fileName = $ param - > { FileName } ;
$ forceType = $ param - > { ForceType } ;
} else {
$ fileName = $ param ;
}
$ forceType = "" if ( ! defined ( $ forceType ) ) ;
if ( configDBUsed ( ) && $ forceType ne "file" ) {
( $ err , @ ret ) = cfgDB_FileRead ( $ fileName ) ;
2014-05-01 15:02:06 +00:00
} else {
2014-06-01 11:45:00 +00:00
if ( open ( FH , $ fileName ) ) {
2014-05-01 15:02:06 +00:00
@ ret = <FH> ;
close ( FH ) ;
2014-05-24 13:04:04 +00:00
chomp ( @ ret ) ;
2014-05-01 15:02:06 +00:00
} else {
2014-06-01 11:45:00 +00:00
$ err = "Can't open $fileName: $!" ;
2014-05-01 15:02:06 +00:00
}
}
return ( $ err , @ ret ) ;
}
sub
FileWrite ( $@ )
{
2014-06-01 11:45:00 +00:00
my ( $ param , @ rows ) = @ _ ;
my ( $ err , @ ret , $ fileName , $ forceType ) ;
2014-05-01 15:02:06 +00:00
2014-06-01 11:45:00 +00:00
if ( ref ( $ param ) eq "HASH" ) {
$ fileName = $ param - > { FileName } ;
$ forceType = $ param - > { ForceType } ;
} else {
$ fileName = $ param ;
}
$ forceType = "" if ( ! defined ( $ forceType ) ) ;
if ( configDBUsed ( ) && $ forceType ne "file" ) {
return cfgDB_FileWrite ( $ fileName , @ rows ) ;
2014-05-01 15:02:06 +00:00
} else {
2014-06-01 11:45:00 +00:00
if ( open ( FH , ">$fileName" ) ) {
2014-05-24 13:04:04 +00:00
binmode ( FH ) ;
2014-05-01 15:02:06 +00:00
foreach my $ l ( @ rows ) {
2014-05-24 13:04:04 +00:00
print FH $ l , "\n" ;
2014-05-01 15:02:06 +00:00
}
close ( FH ) ;
return undef ;
} else {
2014-06-01 11:45:00 +00:00
return "Can't open $fileName: $!" ;
2014-05-01 15:02:06 +00:00
}
}
}
2015-01-11 18:23:31 +00:00
sub
getUniqueId ( )
{
my ( $ err , $ uniqueID ) = getKeyValue ( "uniqueID" ) ;
return $ uniqueID if ( defined ( $ uniqueID ) ) ;
2015-01-13 09:43:33 +00:00
$ uniqueID = createUniqueId ( ) ;
2015-01-11 18:23:31 +00:00
setKeyValue ( "uniqueID" , $ uniqueID ) ;
return $ uniqueID ;
}
2015-01-12 17:15:46 +00:00
my $ srandUsed ;
sub
createUniqueId ( )
{
my $ uniqueID ;
srand ( time ) if ( ! $ srandUsed ) ;
$ srandUsed = 1 ;
$ uniqueID = join "" , map { unpack "H*" , chr ( rand ( 256 ) ) } 1 .. 16 ;
return $ uniqueID ;
}
2015-01-11 18:23:31 +00:00
sub
getKeyValue ( $ )
{
my ( $ key ) = @ _ ;
my $ fName = $ attr { global } { modpath } . "/FHEM/FhemUtils/uniqueID" ;
my ( $ err , @ l ) = FileRead ( $ fName ) ;
return ( $ err , undef ) if ( $ err ) ;
for my $ l ( @ l ) {
return ( undef , $ 1 ) if ( $ l =~ m/^$key:(.*)/ ) ;
}
return ( undef , undef ) ;
}
sub
setKeyValue ( $$ )
{
my ( $ key , $ value ) = @ _ ;
my $ fName = $ attr { global } { modpath } . "/FHEM/FhemUtils/uniqueID" ;
my ( $ err , @ old ) = FileRead ( $ fName ) ;
my @ new ;
if ( $ err ) {
push ( @ new , "# This file is auto generated." ,
"# Please do not modify, move or delete it." ,
"" ) ;
@ old = ( ) ;
}
my $ fnd ;
foreach my $ l ( @ old ) {
if ( $ l =~ m/^$key:/ ) {
$ fnd = 1 ;
push @ new , "$key:$value" if ( defined ( $ value ) ) ;
} else {
push @ new , $ l ;
}
}
push @ new , "$key:$value" if ( ! $ fnd && defined ( $ value ) ) ;
return FileWrite ( $ fName , @ new ) ;
}
2015-01-24 12:38:25 +00:00
sub
addStructChange ( $$ $ )
{
return if ( ! $ init_done ) ;
my ( $ cmd , $ dev , $ param ) = @ _ ;
2015-01-25 12:42:07 +00:00
return if ( ! $ defs { $ dev } || $ defs { $ dev } { TEMPORARY } || $ defs { $ dev } { VOLATILE } ) ;
2015-01-24 12:38:25 +00:00
$ lastDefChange + + ;
2015-01-24 13:07:32 +00:00
shift @ structChangeHist if ( @ structChangeHist > 9 ) ;
2015-01-24 12:38:25 +00:00
$ param = substr ( $ param , 0 , 40 ) . "..." if ( length ( $ param ) > 40 ) ;
push @ structChangeHist , "$cmd $param" ;
}
2015-01-11 18:23:31 +00:00
2012-02-11 23:42:47 +00:00
1 ;