2007-01-30 12:47:36 +00:00
#!/usr/bin/perl
################################################################
#
# Copyright notice
#
2023-03-05 17:48:25 +00:00
# (c) 2005-2023
2020-01-18 12:14:41 +00:00
# Copyright: Rudolf Koenig (rudolf dot koenig at fhem dot de)
2007-01-30 12:47:36 +00:00
# All rights reserved
#
2020-10-26 18:46:24 +00:00
# This program free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License V2, which is also
# distributed together with this program in the file GPL_V2.txt
2007-01-30 12:47:36 +00:00
#
2020-10-26 18:46:24 +00:00
# This program is distributed in the hope that it will be useful,
2007-01-30 12:47:36 +00:00
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2020-10-26 18:46:24 +00:00
# GNU General Public License V2 for more details.
2007-01-30 12:47:36 +00:00
#
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 ;
2016-10-09 10:58:02 +00:00
use lib '.' ;
2007-01-30 12:47:36 +00:00
use IO::Socket ;
2017-08-20 14:21:58 +00:00
use IO::Socket::INET ;
2022-04-05 08:55:13 +00:00
use Time::HiRes qw( gettimeofday time ) ;
2015-03-14 13:01:30 +00:00
use Scalar::Util qw( looks_like_number ) ;
2017-01-28 17:39:39 +00:00
use POSIX ;
2017-11-01 16:59:23 +00:00
use File::Copy qw( copy ) ;
2022-02-14 20:39:19 +00:00
use Encode ;
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 ($) ;
2015-11-19 10:12:56 +00:00
sub AnalyzePerlCommand ( $ $ ; $ ) ;
2013-11-20 13:04:27 +00:00
sub AssignIoPort ( $ ; $ ) ;
2011-01-02 14:45:53 +00:00
sub AttrVal ($$$) ;
2017-04-05 11:36:44 +00:00
sub AttrNum ( $ $ $ ; $ ) ;
2019-08-27 08:36:02 +00:00
sub Authorized ( $ $ $ ; $ ) ;
sub Authenticate ($$) ;
2007-03-19 14:59:37 +00:00
sub CallFn (@) ;
2017-01-13 16:08:17 +00:00
sub CallInstanceFn (@) ;
2013-07-15 20:34:58 +00:00
sub CheckDuplicate ($$@) ;
2020-06-17 13:53:32 +00:00
sub CheckRegexp ($$) ;
2014-03-16 11:50:22 +00:00
sub Debug ($) ;
sub DoSet (@) ;
2018-08-10 11:52:49 +00:00
sub Dispatch ( $ $ ; $$ ) ;
2013-01-03 12:50:16 +00:00
sub DoTrigger ($$@) ;
2013-03-24 17:47:28 +00:00
sub EvalSpecials ($%) ;
2015-08-16 09:24:40 +00:00
sub Each ( $ $ ; $ ) ;
2017-09-17 09:54:02 +00:00
sub FileDelete ($) ;
2014-05-01 15:02:06 +00:00
sub FileRead ($) ;
sub FileWrite ($@) ;
2008-05-09 13:58:10 +00:00
sub FmtDateTime ($) ;
sub FmtTime ($) ;
2019-01-18 17:06:18 +00:00
sub GetDefAndAttr ( $ ; $ ) ;
2007-03-19 14:59:37 +00:00
sub GetLogLevel (@) ;
2008-07-25 14:14:24 +00:00
sub GetTimeSpec ($) ;
2017-04-04 15:05:24 +00:00
sub GetType ( $ ; $ ) ;
2013-08-25 11:49:30 +00:00
sub GlobalAttr ($$$$) ;
2015-08-13 18:09:42 +00:00
sub HandleArchiving ( $ ; $ ) ;
2008-05-09 13:58:10 +00:00
sub HandleTimeout () ;
2007-03-19 14:59:37 +00:00
sub IOWrite ($@) ;
2016-03-05 16:50:11 +00:00
sub InternalTimer ( $ $ $ ; $ ) ;
2014-03-16 11:50:22 +00:00
sub InternalVal ($$$) ;
2017-04-05 11:36:44 +00:00
sub InternalNum ( $ $ $ ; $ ) ;
2017-04-04 15:05:24 +00:00
sub IsDevice ( $ ; $ ) ;
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 ($) ;
2019-03-16 10:57:53 +00:00
sub IsWe ( ; $$ ) ;
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 ($) ;
2018-03-09 07:31:31 +00:00
sub OldReadingsAge ($$$) ;
2018-03-07 21:10:28 +00:00
sub OldReadingsNum ( $ $ $ ; $ ) ;
sub OldReadingsTimestamp ($$$) ;
sub OldReadingsVal ($$$) ;
2007-01-30 12:47:36 +00:00
sub OpenLogfile ($) ;
2022-10-28 12:09:44 +00:00
sub PrintHash ($$) ;
2018-03-09 07:31:31 +00:00
sub ReadingsAge ($$$) ;
2017-04-05 11:36:44 +00:00
sub ReadingsNum ( $ $ $ ; $ ) ;
2014-03-16 11:50:22 +00:00
sub ReadingsTimestamp ($$$) ;
2010-04-02 16:26:58 +00:00
sub ReadingsVal ($$$) ;
2015-12-29 19:08:19 +00:00
sub RefreshAuthList () ;
2016-03-05 15:38:39 +00:00
sub RemoveInternalTimer ( $ ; $ ) ;
2011-07-30 13:22:25 +00:00
sub ReplaceEventMap ($$$) ;
2007-01-30 12:47:36 +00:00
sub ResolveDateWildcards ($@) ;
2017-12-23 19:33:43 +00:00
sub SecurityCheck () ;
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 ($) ;
2021-10-30 19:23:18 +00:00
sub WriteStatefile () ;
2007-01-30 12:47:36 +00:00
sub XmlEscape ($) ;
2020-08-19 16:54:17 +00:00
sub addEvent ( $ $ ; $ ) ;
2021-07-09 17:18:55 +00:00
sub addToDevAttrList ( $ $ ; $ ) ;
2019-05-04 19:13:22 +00:00
sub applyGlobalAttrFromEnv () ;
2018-08-31 05:43:11 +00:00
sub delFromDevAttrList ($$) ;
2021-07-09 17:18:55 +00:00
sub addToAttrList ( $ ; $ ) ;
2018-09-03 19:11:22 +00:00
sub delFromAttrList ($) ;
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 () ;
2019-03-12 18:16:45 +00:00
sub devspec2array ( $ ; $$ ) ;
2008-05-09 13:58:10 +00:00
sub doGlobalDef ($) ;
2014-03-16 11:50:22 +00:00
sub escapeLogLine ($) ;
2013-08-18 14:13:59 +00:00
sub evalStateFormat ($) ;
2020-05-13 10:27:43 +00:00
sub execFhemTestFile () ;
2012-10-30 18:46:58 +00:00
sub fhem ($@) ;
2014-03-16 11:50:22 +00:00
sub fhemTimeGm ($$$$$$) ;
sub fhemTimeLocal ($$$$$$) ;
sub fhemTzOffset ($) ;
2021-03-06 11:05:44 +00:00
sub getAllAttr ( $ ; $$ ) ;
2018-01-06 17:20:00 +00:00
sub getAllGets ( $ ; $ ) ;
sub getAllSets ( $ ; $ ) ;
2016-12-14 13:04:42 +00:00
sub getPawList ($) ;
2015-01-12 17:15:46 +00:00
sub getUniqueId () ;
2020-03-23 11:34:26 +00:00
sub hashKeyRename ($$$) ;
2021-07-13 15:16:07 +00:00
sub json2nameValue ( $ ; $$ $$ ) ;
2020-07-16 08:55:26 +00:00
sub json2reading ( $ $ ; $$ $$ ) ;
2013-08-18 14:13:59 +00:00
sub latin1ToUtf8 ($) ;
2015-10-21 19:06:58 +00:00
sub myrename ($$$) ;
2021-06-17 18:56:32 +00:00
sub notifyRegexpChanged ( $ $ ; $ ) ;
2018-02-28 21:09:20 +00:00
sub parseParams ( $ ; $$ $ ) ;
2020-05-13 10:27:43 +00:00
sub prepareFhemTestFile () ;
2016-04-03 14:20:32 +00:00
sub perlSyntaxCheck ($%) ;
2013-01-03 12:50:16 +00:00
sub readingsBeginUpdate ($) ;
sub readingsBulkUpdate ($$$@) ;
sub readingsEndUpdate ($$) ;
2020-08-19 16:54:17 +00:00
sub readingsSingleUpdate ( $ $ $ $ ; $ ) ;
2018-01-21 11:22:35 +00:00
sub readingsDelete ($$) ;
2011-01-29 12:07:14 +00:00
sub redirectStdinStdErr () ;
2013-08-18 14:13:59 +00:00
sub rejectDuplicate ($$$) ;
2018-01-19 12:15:44 +00:00
sub resolveAttrRename ($$) ;
2018-03-17 16:23:45 +00:00
sub restoreDir_init ( ; $ ) ;
2017-11-01 16:59:23 +00:00
sub restoreDir_rmTree ($) ;
sub restoreDir_saveFile ($$) ;
sub restoreDir_mkDir ($$$) ;
2013-01-03 12:50:16 +00:00
sub setGlobalAttrBeforeFork ($) ;
2012-02-14 08:13:08 +00:00
sub setReadingsVal ($$$$) ;
2018-03-20 21:15:44 +00:00
sub setAttrList ($$) ;
sub setDevAttrList ( $ ; $ ) ;
2022-01-26 10:04:13 +00:00
sub setDisableNotifyFn ($$) ;
2022-01-22 09:21:18 +00:00
sub setNotifyDev ($$) ;
2017-05-22 20:25:06 +00:00
sub toJSON ($) ;
2013-09-03 09:22:23 +00:00
sub utf8ToLatin1 ($) ;
2007-01-30 12:47:36 +00:00
sub CommandAttr ($$) ;
2015-11-28 08:02:51 +00:00
sub CommandCancel ($$) ;
2007-12-29 16:25:02 +00:00
sub CommandDefaultAttr ($$) ;
2007-01-30 12:47:36 +00:00
sub CommandDefine ($$) ;
2015-04-15 13:21:04 +00:00
sub CommandDefMod ($$) ;
2007-01-30 12:47:36 +00:00
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 ($$) ;
2013-01-03 12:50:16 +00:00
sub CommandIOWrite ($$) ;
2007-01-30 12:47:36 +00:00
sub CommandInclude ($$) ;
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 ($$) ;
2019-01-18 09:27:06 +00:00
sub CommandSetuuid ($$) ;
2019-07-09 09:44:07 +00:00
sub CommandShutdown ( $ $ ; $$ $ ) ;
2013-01-03 12:50:16 +00:00
sub CommandSleep ($$) ;
2007-01-30 12:47:36 +00:00
sub CommandTrigger ($$) ;
2014-03-01 07:59:19 +00:00
# configDB special
sub cfgDB_Init ;
2020-04-01 11:44:19 +00:00
sub cfgDB_ReadAll ;
2014-03-01 07:59:19 +00:00
sub cfgDB_SaveState ;
sub cfgDB_SaveCfg ;
2020-04-01 11:44:19 +00:00
sub cfgDB_AttrRead ;
sub cfgDB_FileRead ;
sub cfgDB_FileUpdate ;
sub cfgDB_FileWrite ;
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
2019-01-14 10:04:43 +00:00
# RenameFn - inform the device about its renaming
2014-10-09 17:54:30 +00:00
# SetFn - set/activate this device
2019-01-14 10:04:43 +00:00
# DelayedShutdownFn - used to delay shutdown for some seconds
# ShutdownFn-called before shutdown, if DelayedShutdownFn is "over"
2014-10-09 17:54:30 +00:00
# StateFn - set local info for this device, do not activate anything
# UndefFn - clean up (delete timer, close fd), called by delete and rereadcfg
2022-12-04 10:04:50 +00:00
# prioSave - save the definition at the start, for a small SubProcess
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"
2022-06-01 08:25:03 +00:00
# NOTIFYDEV - if set, the NotifyFn will only be called for this device
2014-01-14 19:23:34 +00:00
2019-01-14 10:04:43 +00:00
use vars qw( $addTimerStacktrace ) ; # set to 1 by fhemdebug
2016-06-25 15:26:33 +00:00
use vars qw( $auth_refresh ) ;
use vars qw( $cmdFromAnalyze ) ; # used by the warnings-sub
2023-11-29 12:33:32 +00:00
use vars qw( $devcount ) ; # Maximum device number, used for storing.
2022-12-04 10:04:50 +00:00
use vars qw( $devcountPrioSave ) ; # Maximum prioSave device number
2023-11-29 12:33:32 +00:00
use vars qw( $devcountTemp ) ; # number for temp devices like client connect
2022-02-14 20:39:19 +00:00
use vars qw( $unicodeEncoding ) ; # internal encoding is unicode (wide character)
2016-06-25 15:26:33 +00:00
use vars qw( $featurelevel ) ;
2019-01-14 10:04:43 +00:00
use vars qw( $fhemForked ) ; # 1 in a fhemFork()'ed process, else undef
2020-05-13 10:27:43 +00:00
use vars qw( $fhemTestFile ) ; # file to include if -t is specified
2014-03-30 06:58:52 +00:00
use vars qw( $fhem_started ) ; # used for uptime calculation
2019-01-14 10:04:43 +00:00
use vars qw( $haveInet6 ) ; # Using INET6
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
2016-06-25 15:26:33 +00:00
use vars qw( $lastDefChange ) ; # number of last def/attr change
2019-01-14 10:04:43 +00:00
use vars qw( $lastWarningMsg ) ; # set by the warnings-sub
2014-03-30 06:58:52 +00:00
use vars qw( $nextat ) ; # Time when next timer will be triggered.
2022-02-14 20:39:19 +00:00
use vars qw( $numCPUs ) ; # Number of CPUs on Linux, else 1
2014-03-30 06:58:52 +00:00
use vars qw( $reread_active ) ;
2016-06-25 15:26:33 +00:00
use vars qw( $selectTimestamp ) ; # used to check last select exit timestamp
2023-11-29 12:33:32 +00:00
use vars qw( $tmpdevcount ) ; # Maximum device number, used for storing
2014-03-30 06:58:52 +00:00
use vars qw( $winService ) ; # the Windows Service object
2019-01-14 10:04:43 +00:00
2014-03-30 06:58:52 +00:00
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
2018-02-18 14:37:28 +00:00
use vars qw( %intAt ) ; # Internal timer hash, used by apptime
2016-06-25 15:26:33 +00:00
use vars qw( %logInform ) ; # Used by FHEMWEB/Event-Monitor
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.
2019-01-14 10:04:43 +00:00
use vars qw( %prioQueues ) ; #
2014-03-30 06:58:52 +00:00
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
2019-01-14 10:04:43 +00:00
use vars qw( @intAtA ) ; # Internal timer array
2016-06-25 15:26:33 +00:00
use vars qw( @structChangeHist ) ; # Contains the last 10 structural changes
2019-01-14 10:04:43 +00:00
2019-02-05 10:34:00 +00:00
use constant {
DAYSECONDS = > 86400 ,
HOURSECONDS = > 3600 ,
MINUTESECONDS = > 60
} ;
2016-06-25 15:26:33 +00:00
$ selectTimestamp = gettimeofday ( ) ;
2021-07-11 12:00:11 +00:00
my $ cvsid = '$Id$' ;
2008-12-03 16:45:26 +00:00
2018-02-18 11:59:09 +00:00
my $ AttrList = "alias comment:textField-long eventMap:textField-long " .
2021-03-07 09:02:55 +00:00
"group room suppressReading userattr " .
"userReadings:textField-long verbose:0,1,2,3,4,5 " ;
2016-09-21 19:02:22 +00:00
2021-07-13 15:16:07 +00:00
my @ authenticate ; # List of authentication devices
my @ authorize ; # List of authorization devices
2015-03-14 13:01:30 +00:00
my $ currcfgfile = "" ; # current config/include file
my $ currlogfile ; # logfile, without wildcards
2014-03-30 06:58:52 +00:00
my $ duplidx = 0 ; # helper for the above pool
2015-12-29 19:08:19 +00:00
my $ evalSpecials ; # Used by EvalSpecials->AnalyzeCommand
2014-03-30 06:58:52 +00:00
my $ intAtCnt = 0 ;
2007-03-19 14:59:37 +00:00
my $ logopened = 0 ; # logfile opened or using stdout
2018-02-20 09:23:11 +00:00
my $ namedef = "where <name> is a single device name, a list separated by comma (,) or a regexp. See the devspec section in the commandref.html for details.\n" ;
2015-03-14 13:01:30 +00:00
my $ rcvdquit ; # Used for quit handling in init files
2014-03-30 06:58:52 +00:00
my $ readingsUpdateDelayTrigger ; # needed internally
2017-08-22 14:34:02 +00:00
my $ gotSig ; # non-undef if got a signal
2021-07-11 14:35:40 +00:00
my % oldvalue ; # Old values, see commandref.html
2014-07-13 10:41:00 +00:00
my $ wbName = ".WRITEBUFFER" ; # Buffer-name for delayed writing via select
2015-03-14 13:01:30 +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
2015-11-28 08:02:51 +00:00
my % sleepers ; # list of sleepers
2019-01-14 10:04:43 +00:00
my % delayedShutdowns ; # definitions needing delayed shutdown
2019-01-18 09:37:05 +00:00
my % fuuidHash ; # for duplicate checking
2020-02-17 18:49:08 +00:00
my $ globalUniqueID ; # cache it
2023-03-05 17:57:23 +00:00
my $ LOG ; # Log file handle, formerly LOG
2007-03-19 14:59:37 +00:00
2021-07-11 14:35:40 +00:00
my $ readytimeout = ( $^O eq "MSWin32" ) ? 0.1 : 5.0 ;
2007-05-27 17:24:52 +00:00
$ init_done = 0 ;
2014-05-29 10:25:01 +00:00
$ lastDefChange = 0 ;
2024-01-28 09:11:36 +00:00
$ featurelevel = 6.3 ; # see also GlobalAttr
2020-12-17 18:53:33 +00:00
$ numCPUs = `grep -c ^processor /proc/cpuinfo 2>&1` if ( $^O eq "linux" ) ;
$ numCPUs = ( $ numCPUs && $ numCPUs =~ m/(\d+)/ ? $ 1 : 1 ) ;
2014-03-30 06:58:52 +00:00
2007-03-19 14:59:37 +00:00
2011-07-24 11:55:36 +00:00
$ modules { Global } { ORDER } = - 1 ;
$ modules { Global } { LOADED } = 1 ;
2015-04-17 16:14:45 +00:00
no warnings 'qw' ;
my @ globalAttrList = qw(
altitude
apiversion
archivecmd
archivedir
2017-03-01 11:27:53 +00:00
archivesort:timestamp , alphanum
2015-10-21 19:06:58 +00:00
archiveCompress
2018-11-07 19:02:28 +00:00
autoload_undefined_devices:0 , 1
2015-08-22 13:18:56 +00:00
autosave:1 , 0
2015-04-17 16:14:45 +00:00
backup_before_update
backupcmd
backupdir
backupsymlink
2016-08-07 16:41:53 +00:00
blockingCallMax
2016-08-19 12:47:50 +00:00
commandref:modular , full
2015-04-17 16:14:45 +00:00
configfile
2021-04-28 16:33:04 +00:00
disableFeatures:multiple - strict , attrTemplate , securityCheck , saveuuid
2017-10-18 19:46:13 +00:00
dnsHostsFile
2016-05-14 20:59:37 +00:00
dnsServer
2015-04-17 16:14:45 +00:00
dupTimeout
exclude_from_update
2022-02-15 08:34:42 +00:00
encoding:bytestream , unicode
2021-12-20 10:51:01 +00:00
hideExcludedUpdates:1 , 0
2021-09-16 17:15:44 +00:00
featurelevel:6 .1 , 6.0 , 5.9 , 5.8 , 5.7 , 5.6 , 5.5 , 99.99
2015-07-29 05:46:10 +00:00
genericDisplayType:switch , outlet , light , blind , speaker , thermostat
2015-04-17 16:14:45 +00:00
holiday2we
2017-12-07 10:48:50 +00:00
httpcompress:0 , 1
2019-12-25 19:17:36 +00:00
ignoreRegexp
2018-05-15 20:06:23 +00:00
keyFileName
2015-04-17 16:14:45 +00:00
language:EN , DE
lastinclude
latitude
logdir
logfile
longitude
2019-03-05 15:02:55 +00:00
maxChangeLog
2019-01-14 10:04:43 +00:00
maxShutdownDelay
2015-04-17 16:14:45 +00:00
modpath
motd
mseclog:1 , 0
nofork:1 , 0
nrarchive
2017-02-26 17:04:14 +00:00
perlSyntaxCheck:0 , 1
2015-04-17 16:14:45 +00:00
pidfilename
2017-09-05 22:07:18 +00:00
proxy
2017-09-09 10:20:16 +00:00
proxyAuth
2017-09-05 22:07:18 +00:00
proxyExclude
2015-04-17 16:14:45 +00:00
restartDelay
restoreDirs
sendStatistics:onUpdate , manually , never
showInternalValues:1 , 0
2015-07-13 12:30:26 +00:00
sslVersion
2015-04-17 16:14:45 +00:00
stacktrace:1 , 0
statefile
title
updateInBackground:1 , 0
2015-05-14 07:59:32 +00:00
updateNoFileCheck:1 , 0
2017-08-20 14:21:58 +00:00
useInet6:1 , 0
2015-04-17 16:14:45 +00:00
version
) ;
use warnings 'qw' ;
$ modules { Global } { AttrList } = join ( " " , @ globalAttrList ) ;
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 ) ;
2018-05-24 09:45:32 +00:00
no warnings 'qw' ;
my @ attrList = qw(
event - aggregator
event - min - interval
event - on - change - reading
event - on - update - reading
oldreadings
stateFormat:textField - long
timestamp - on - change - reading
) ;
$ readingFnAttributes = join ( " " , @ attrList ) ;
2021-07-09 17:18:55 +00:00
my % attrSource = map { s/:.*// ; $ _ = > "framework" } @ attrList ;
map { $ attrSource { $ _ } = "framework" } qw(
2021-03-07 09:20:31 +00:00
ignore
disable
disabledForIntervals
2021-03-07 09:02:55 +00:00
) ;
2018-05-24 09:45:32 +00:00
2018-03-07 21:10:28 +00:00
my % ra = (
"suppressReading" = > { s = > "\n" } ,
"event-aggregator" = > { s = > "," , c = > ".attraggr" } ,
"event-on-update-reading" = > { s = > "," , c = > ".attreour" } ,
"event-on-change-reading" = > { s = > "," , c = > ".attreocr" , r = > ":.*" } ,
"timestamp-on-change-reading" = > { s = > "," , c = > ".attrtocr" } ,
2022-07-17 09:47:10 +00:00
"event-min-interval" = > { s = > "," , c = > ".attrminint" , r = > ":.*" ,
isNum = > 1 } ,
2018-03-07 21:10:28 +00:00
"oldreadings" = > { s = > "," , c = > ".or" } ,
"devStateIcon" = > { s = > " " , r = > ":.*" , p = > "^{.*}\$" ,
pv = > { "%name" = > 1 , "%state" = > 1 , "%type" = > 1 } } ,
) ;
2013-01-03 12:50:16 +00:00
2008-12-09 14:12:40 +00:00
% cmds = (
2015-02-18 17:32:56 +00:00
"?" = > { ReplacedBy = > "help" } ,
"attr" = > { Fn = > "CommandAttr" ,
2021-07-06 08:56:14 +00:00
Hlp = > "[-a] [-r] [-silent] <devspec> <attrname> [<attrval>]," .
2019-10-25 09:35:24 +00:00
"set attribute for <devspec>" } ,
2015-11-28 08:02:51 +00:00
"cancel" = > { Fn = > "CommandCancel" ,
Hlp = > "[<id> [quiet]],list sleepers, cancel sleeper with <id>" } ,
2015-03-01 12:53:35 +00:00
"createlog" = > { ModuleName = > "autocreate" } ,
2007-01-30 12:47:36 +00:00
"define" = > { Fn = > "CommandDefine" ,
2019-10-25 09:35:24 +00:00
Hlp = > "[option] <name> <type> <options>,define a device" } ,
2015-04-15 13:21:04 +00:00
"defmod" = > { Fn = > "CommandDefMod" ,
2019-10-25 09:35:24 +00:00
Hlp = > "[-temporary] <name> <type> <options>," .
"define or modify a device" } ,
2008-11-01 21:27:10 +00:00
"deleteattr" = > { Fn = > "CommandDeleteAttr" ,
2021-07-06 08:56:14 +00:00
Hlp = > "[-silent] <devspec> [<attrname>],delete attribute for <devspec>" } ,
2013-01-19 13:36:29 +00:00
"deletereading" = > { Fn = > "CommandDeleteReading" ,
2020-12-07 09:02:12 +00:00
Hlp = > "<devspec> <readingname> [older-than-seconds]," .
"delete user defined readings" } ,
2007-01-30 12:47:36 +00:00
"delete" = > { Fn = > "CommandDelete" ,
2015-03-14 13:01:30 +00:00
Hlp = > "<devspec>,delete the corresponding definition(s)" } ,
2013-08-07 11:18:15 +00:00
"displayattr" = > { Fn = > "CommandDisplayAttr" ,
2015-03-14 13:01:30 +00:00
Hlp = > "<devspec> [attrname],display attributes" } ,
2008-11-01 21:27:10 +00:00
"get" = > { Fn = > "CommandGet" ,
2019-10-25 09:35:24 +00:00
Hlp = > "<devspec> <type-specific>,request data from <devspec>" } ,
2007-01-30 12:47:36 +00:00
"include" = > { Fn = > "CommandInclude" ,
2019-10-25 09:35:24 +00:00
Hlp = > "<filename>,read the commands from <filename>" } ,
2010-10-24 16:08:48 +00:00
"iowrite" = > { Fn = > "CommandIOWrite" ,
Hlp = > "<iodev> <data>,write raw data with iodev" } ,
2007-01-30 12:47:36 +00:00
"list" = > { Fn = > "CommandList" ,
2019-10-25 09:35:24 +00:00
Hlp = > "[-r] [devspec] [value],list definitions and status info" } ,
2007-04-24 07:13:21 +00:00
"modify" = > { Fn = > "CommandModify" ,
2019-10-27 17:23:55 +00:00
Hlp = > "device <type-dependent-options>,modify the definition" } ,
2007-01-30 12:47:36 +00:00
"quit" = > { Fn = > "CommandQuit" ,
2013-08-08 13:26:43 +00:00
ClientFilter = > "telnet" ,
2015-03-14 13:01:30 +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" ,
2015-03-14 13:01:30 +00:00
Hlp = > ",end the client session" } ,
2007-01-30 12:47:36 +00:00
"reload" = > { Fn = > "CommandReload" ,
2019-10-25 09:35:24 +00:00
Hlp = > "<module>,reload the given module (e.g. 99_PRIV)" } ,
2007-03-19 15:34:34 +00:00
"rename" = > { Fn = > "CommandRename" ,
2015-03-14 13:01:30 +00:00
Hlp = > "<old> <new>,rename a definition" } ,
2007-01-30 12:47:36 +00:00
"rereadcfg" = > { Fn = > "CommandRereadCfg" ,
2015-03-14 13:01:30 +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" ,
2015-03-14 13:01:30 +00:00
Hlp = > "[configfile],write the configfile and the statefile" } ,
2008-11-01 21:27:10 +00:00
"set" = > { Fn = > "CommandSet" ,
2019-10-25 09:35:24 +00:00
Hlp = > "<devspec> <type-specific>,transmit code for <devspec>" } ,
2013-08-22 15:13:44 +00:00
"setreading" = > { Fn = > "CommandSetReading" ,
2020-08-19 16:54:17 +00:00
Hlp = > "<devspec> [YYYY-MM-DD HH:MM:SS] <reading> <value>," .
"set reading for <devspec>" } ,
2008-11-01 21:27:10 +00:00
"setstate" = > { Fn = > "CommandSetstate" ,
2015-03-14 13:01:30 +00:00
Hlp = > "<devspec> <state>,set the state shown in the command list" } ,
2019-01-18 09:27:06 +00:00
"setuuid" = > { Fn = > "CommandSetuuid" , Hlp = > "" } ,
2008-11-01 21:27:10 +00:00
"setdefaultattr" = > { Fn = > "CommandDefaultAttr" ,
2019-10-25 09:35:24 +00:00
Hlp = > "[<attrname> [<attrvalue>]]," .
"set attr for following definitions" } ,
2007-01-30 12:47:36 +00:00
"shutdown" = > { Fn = > "CommandShutdown" ,
2015-12-21 12:43:26 +00:00
Hlp = > "[restart|exitValue],terminate the server" } ,
2007-01-30 12:47:36 +00:00
"sleep" = > { Fn = > "CommandSleep" ,
2019-10-25 09:35:24 +00:00
Hlp = > "<sec|timespec|regex> [<id>] [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" = > {
2019-10-25 09:35:24 +00:00
Hlp = > "[<fileName>|all|check|checktime|force] " .
2014-12-02 07:05:00 +00:00
"[http://.../controlfile],update FHEM" } ,
2013-03-30 12:41:09 +00:00
"updatefhem" = > { ReplacedBy = > "update" } ,
2019-10-27 17:23:55 +00:00
"usb" = > { ModuleName = > "autocreate" }
2007-01-30 12:47:36 +00:00
) ;
###################################################
# Start the program
2017-08-01 10:01:20 +00:00
my $ fhemdebug ;
2020-05-30 08:34:11 +00:00
$ fhemdebug = shift @ ARGV if ( $ ARGV [ 0 ] && $ ARGV [ 0 ] eq "-d" ) ;
2020-05-13 10:27:43 +00:00
prepareFhemTestFile ( ) ;
2017-08-01 10:01:20 +00:00
2013-08-18 10:27:54 +00:00
if ( int ( @ ARGV ) < 1 ) {
2007-01-30 12:47:36 +00:00
print "Usage:\n" ;
2020-05-27 09:31:29 +00:00
print "as server: perl fhem.pl [-d] {<configfile>|configDB}\n" ;
2020-05-13 10:27:43 +00:00
print "as client: perl fhem.pl [host:]port cmd cmd cmd...\n" ;
print "testing: perl fhem.pl -t <testfile>.t\n" ;
2013-08-25 11:49:30 +00:00
if ( $^O =~ m/Win/ ) {
2020-05-13 10:27:43 +00:00
print "install as windows service: perl fhem.pl configfile -i\n" ;
print "uninstall the windows service: perl fhem.pl -u\n" ;
2013-08-25 11:49:30 +00:00
}
2007-01-30 12:47:36 +00:00
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 ) ;
2020-12-31 14:39:10 +00:00
alarm ( 30 ) ; #117226
2012-06-23 16:22:28 +00:00
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
2016-05-18 06:42:04 +00:00
SignalHandling ( ) ;
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
2020-05-13 10:27:43 +00:00
# config file after the fork. But we need some global attr parameters before,
# so we read them here. FHEM_GLOBALATTR is for docker, as it needs to overwrite
# fhem.cfg
2019-05-04 19:13:22 +00:00
my ( undef , $ globalAttrFromEnv ) = parseParams ( $ ENV { FHEM_GLOBALATTR } ) ;
2012-06-07 06:22:00 +00:00
setGlobalAttrBeforeFork ( $ attr { global } { configfile } ) ;
2019-05-04 19:13:22 +00:00
applyGlobalAttrFromEnv ( ) ;
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
2018-01-20 20:12:25 +00:00
if ( gettimeofday ( ) < 2 * 3600 ) {
2014-08-03 08:22:52 +00:00
Log 1 , "date/time not set, waiting up to 2 hours to be set." ;
2018-01-20 20:12:25 +00:00
while ( gettimeofday ( ) < 2 * 3600 ) {
2014-08-03 08:22:52 +00:00
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 ( ) ;
2020-04-12 12:13:17 +00:00
$ defs { global } { init_errors } = "" ;
2014-04-20 19:20:42 +00:00
if ( configDBUsed ( ) ) {
2014-03-04 16:27:09 +00:00
my $ ret = cfgDB_ReadAll ( undef ) ;
2020-04-12 12:13:17 +00:00
$ defs { global } { init_errors } . = "configDB: $ret\n" if ( $ ret ) ;
2014-03-01 07:59:19 +00:00
} else {
my $ ret = CommandInclude ( undef , $ attr { global } { configfile } ) ;
2020-04-12 12:13:17 +00:00
$ defs { global } { init_errors } . = "configfile: $ret\n" if ( $ ret ) ;
2007-01-30 12:47:36 +00:00
2016-07-07 07:36:25 +00:00
my $ stateFile = $ attr { global } { statefile } ;
if ( $ stateFile ) {
2018-01-20 20:12:25 +00:00
my @ t = localtime ( gettimeofday ( ) ) ;
2016-07-07 07:36:25 +00:00
$ stateFile = ResolveDateWildcards ( $ stateFile , @ t ) ;
if ( - r $ stateFile ) {
$ ret = CommandInclude ( undef , $ stateFile ) ;
2020-04-12 12:13:17 +00:00
$ defs { global } { init_errors } . = "$stateFile: $ret\n" if ( $ ret ) ;
2016-07-07 07:36:25 +00:00
}
2014-03-01 07:59:19 +00:00
}
2007-01-30 12:47:36 +00:00
}
2019-05-04 19:13:22 +00:00
applyGlobalAttrFromEnv ( ) ;
2011-01-29 12:07:14 +00:00
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
2007-09-24 07:09:17 +00:00
$ init_done = 1 ;
2014-05-29 10:25:01 +00:00
$ lastDefChange = 1 ;
2021-02-15 17:22:42 +00:00
sub
finish_init ( )
{
foreach my $ d ( keys % defs ) {
2021-05-24 13:27:13 +00:00
my $ hash = $ defs { $ d } ;
if ( $ hash - > { IODevMissing } ) {
if ( $ hash - > { IODevName } && $ defs { $ hash - > { IODevName } } ) {
fhem_setIoDev ( $ hash , $ hash - > { IODevName } ) ;
2021-02-15 17:22:42 +00:00
} else {
2021-05-24 13:27:13 +00:00
AssignIoPort ( $ hash ) ; # For fhem.cfg editors?
2021-02-15 17:22:42 +00:00
}
2021-05-24 13:27:13 +00:00
delete $ hash - > { IODevMissing } ;
delete $ hash - > { IODevName } ;
2017-03-25 14:22:15 +00:00
}
2014-03-06 20:06:00 +00:00
}
2014-04-24 19:16:01 +00:00
2021-02-15 17:22:42 +00:00
my $ init_errors_first = ( $ defs { global } { init_errors } ? 1 : 0 ) ;
SecurityCheck ( ) ;
if ( $ defs { global } { init_errors } ) {
$ attr { global } { autosave } = 0 if ( $ init_errors_first ) ;
$ defs { global } { init_errors } =
"Messages collected while initializing FHEM:" .
"$defs{global}{init_errors}\n" .
( $ init_errors_first ? "Autosave deactivated" : "" ) ;
Log 1 , $ defs { global } { init_errors }
if ( AttrVal ( "global" , "motd" , "" ) ne "none" ) ;
}
2017-12-24 08:05:59 +00:00
}
2021-02-15 17:22:42 +00:00
finish_init ( ) ;
2017-12-24 08:05:59 +00:00
2017-12-23 19:33:43 +00:00
2018-01-20 20:12:25 +00:00
$ fhem_started = int ( gettimeofday ( ) ) ;
2017-09-18 10:41:04 +00:00
DoTrigger ( "global" , "INITIALIZED" , 1 ) ;
2007-09-24 07:09:17 +00:00
2015-11-29 15:06:52 +00:00
my $ osuser = "os:$^O user:" . ( getlogin || getpwuid ( $< ) || "unknown" ) ;
2015-06-22 18:24:59 +00:00
Log 0 , "Featurelevel: $featurelevel" ;
2013-02-08 08:22:04 +00:00
Log 0 , "Server started with " . int ( keys % defs ) .
2015-11-29 15:06:52 +00:00
" defined entities ($attr{global}{version} perl:$] $osuser pid:$$)" ;
2020-05-13 10:27:43 +00:00
execFhemTestFile ( ) ;
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 ;
2017-08-22 14:34:02 +00:00
$ gotSig = undef if ( $ gotSig && $ gotSig eq "HUP" ) ;
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 ) = ( '' , '' , '' , '' , '' , '' ) ;
2018-05-31 11:23:41 +00:00
my $ nfound = 0 ;
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" } ) ) ;
2018-06-03 21:42:58 +00:00
if ( $ hash - > { SSL } && $ hash - > { CD } &&
$ hash - > { CD } - > can ( 'pending' ) && $ hash - > { CD } - > pending ( ) ) {
2018-05-31 11:23:41 +00:00
vec ( $ rout , $ hash - > { FD } , 1 ) = 1 ;
$ nfound + + ;
}
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 ;
2018-05-31 11:23:41 +00:00
$ nfound = select ( $ rout = $ rin , $ wout = $ win , $ eout = $ ein , $ timeout ) if ( ! $ nfound ) ;
2022-04-25 18:39:55 +00:00
my $ err = int ( $! ) ;
2007-01-30 12:47:36 +00:00
2013-08-25 11:49:30 +00:00
$ winService - > { serviceCheck } - > ( ) if ( $ winService - > { serviceCheck } ) ;
2017-08-22 14:34:02 +00:00
if ( $ gotSig ) {
CommandShutdown ( undef , undef ) if ( $ gotSig eq "TERM" ) ;
CommandRereadCfg ( undef , "" ) if ( $ gotSig eq "HUP" ) ;
2020-06-08 08:26:05 +00:00
$ attr { global } { verbose } = 5 if ( $ gotSig eq "USR1" ) ;
2017-08-22 14:34:02 +00:00
$ gotSig = undef ;
}
2007-01-30 12:47:36 +00:00
if ( $ nfound < 0 ) {
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.
2022-04-25 08:12:20 +00:00
# 9/10038 => BADF, 11=>EAGAIN. don't want to "use errno.ph"
if ( $ err == 11 || $ err == 9 || $ err == 10038 ) {
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 ) {
2015-08-18 05:53:22 +00:00
next if ( ! $ p ) ; # Deleted in the loop
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 } ) ;
2015-10-10 16:17:04 +00:00
my $ ret ;
eval { $ ret = syswrite ( $ hash - > { CD } , $ wb ) ; } ;
if ( $@ ) {
2019-06-21 15:29:47 +00:00
Log 4 , "$hash->{NAME} syswrite: $@" ;
if ( $ hash - > { TEMPORARY } ) {
TcpServer_Close ( $ hash ) ;
CommandDelete ( undef , $ hash - > { NAME } ) ;
}
2015-10-10 16:17:04 +00:00
next ;
}
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 ) {
2015-03-14 13:01:30 +00:00
$ hash - > { wantRead } = 1
if ( TcpServer_WantRead ( $ hash ) ) ;
2014-12-14 15:55:51 +00:00
} elsif ( ! $ ret ) { # zero=EOF, undef=error
2019-06-21 15:29:47 +00:00
Log 4 , "$hash->{NAME} write error to $p" ;
if ( $ hash - > { TEMPORARY } ) {
TcpServer_Close ( $ hash ) ;
CommandDelete ( undef , $ hash - > { NAME } )
}
2014-12-14 15:55:51 +00:00
2013-11-09 13:45:34 +00:00
} else {
2017-01-18 10:48:34 +00:00
if ( $ ret >= length ( $ wb ) ) { # for the > see Forum #29963
2013-12-29 17:59:52 +00:00
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 ) {
2023-04-07 19:59:43 +00:00
my $ h = $ readyfnlist { $ p } ;
next if ( ! $ h ) ; # due to rereadcfg / delete
next if ( $ h - > { NEXT_OPEN } && gettimeofday ( ) < $ h - > { NEXT_OPEN } ) ;
2009-07-04 10:09:27 +00:00
2023-11-05 11:17:59 +00:00
$ h - > { _readyKey } = $ p ; # Endless-Loop-Debugging #111959
2023-04-07 19:59:43 +00:00
if ( CallFn ( $ h - > { NAME } , "ReadyFn" , $ h ) ) {
2011-01-22 21:53:18 +00:00
if ( $ readyfnlist { $ p } ) { # delete itself inside ReadyFn
2023-04-07 19:59:43 +00:00
CallFn ( $ h - > { NAME } , "ReadFn" , $ h ) ;
2009-07-04 10:09:27 +00:00
}
}
2023-11-05 11:17:59 +00:00
delete ( $ h - > { _readyKey } ) ;
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
################################################
2017-04-04 15:05:24 +00:00
sub
IsDevice ( $; $ )
{
my $ devname = shift ;
my $ devtype = shift ;
return 1
if ( defined ( $ devname )
&& defined ( $ defs { $ devname } )
&& ( ! $ devtype || $ devtype eq "" ) ) ;
return 1
if ( defined ( $ devname )
&& defined ( $ defs { $ devname } )
&& defined ( $ defs { $ devname } { TYPE } )
&& $ defs { $ devname } { TYPE } =~ m/^$devtype$/ ) ;
return 0 ;
}
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
2019-08-07 10:25:23 +00:00
return 1 if ( defined ( $ attr { $ devname } ) && $ 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 ;
2018-10-31 14:12:24 +00:00
return 0 if ( ! $ devname ) ; # no check for $attr{$devname}, #92623
2014-03-09 17:43:56 +00:00
2014-03-10 21:07:30 +00:00
return 1 if ( $ attr { $ devname } { disable } ) ;
2015-03-11 06:29:43 +00:00
return 3 if ( $ defs { $ devname } && $ defs { $ devname } { STATE } &&
2015-03-07 14:35:16 +00:00
$ defs { $ devname } { STATE } eq "inactive" ) ;
2015-10-25 10:11:46 +00:00
return 3 if ( ReadingsVal ( $ devname , "state" , "" ) eq "inactive" ) ;
2014-03-09 17:43:56 +00:00
my $ dfi = $ attr { $ devname } { disabledForIntervals } ;
if ( defined ( $ dfi ) ) {
2022-03-17 17:20:30 +00:00
$ dfi =~ s/{([^\x7d]*)}/AnalyzePerlCommand(undef,$1)/ge ; # Forum #69787
2018-01-20 20:12:25 +00:00
my ( $ sec , $ min , $ hour , $ mday , $ month , $ year , $ wday , $ yday , $ isdst ) =
localtime ( gettimeofday ( ) ) ;
2016-12-05 21:53:35 +00:00
my $ dhms = sprintf ( "%s\@%02d:%02d:%02d" , $ wday , $ hour , $ min , $ sec ) ;
2014-03-09 17:43:56 +00:00
foreach my $ ft ( split ( " " , $ dfi ) ) {
my ( $ from , $ to ) = split ( "-" , $ ft ) ;
2017-05-01 09:07:23 +00:00
if ( defined ( $ from ) && defined ( $ to ) ) {
2016-12-05 21:53:35 +00:00
$ from = "$wday\@$from" if ( index ( $ from , "@" ) < 0 ) ;
$ to = "$wday\@$to" if ( index ( $ to , "@" ) < 0 ) ;
return 2 if ( $ from le $ dhms && $ dhms le $ to ) ;
}
2014-03-09 17:43:56 +00:00
}
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 }
2015-03-14 13:01:30 +00:00
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
}
2016-11-28 16:30:54 +00:00
sub
GetVerbose ( $ )
{
my ( $ dev ) = @ _ ;
if ( defined ( $ dev ) &&
defined ( $ attr { $ dev } ) &&
defined ( my $ devlevel = $ attr { $ dev } { verbose } ) ) {
return $ devlevel ;
} else {
return $ attr { global } { verbose } ;
}
}
2017-04-04 15:05:24 +00:00
sub
GetType ( $; $ )
{
my $ devname = shift ;
my $ default = shift ;
return $ default unless ( IsDevice ( $ devname ) && $ defs { $ devname } { TYPE } ) ;
return $ defs { $ devname } { TYPE } ;
}
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
}
2020-01-18 12:14:41 +00:00
return if ( defined ( $ defs { global } { ignoreRegexpObj } ) &&
$ text =~ $ defs { global } { ignoreRegexpObj } ) ;
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 ) {
2022-02-14 20:39:19 +00:00
print $ LOG "$tim $loglevel: $text\n" ;
2007-01-30 12:47:36 +00:00
} else {
print "$tim $loglevel: $text\n" ;
}
2015-12-21 10:33:45 +00:00
no strict "refs" ;
foreach my $ li ( keys % logInform ) {
2020-05-13 10:27:43 +00:00
if ( $ defs { $ li } ) { # Function wont be called for WARNING, don't know why
2023-01-23 15:15:30 +00:00
& { $ logInform { $ li } } ( $ li , "$tim $loglevel: $text" ) ;
2016-01-17 11:03:44 +00:00
} else {
delete $ logInform { $ li } ;
}
2015-12-21 10:33:45 +00:00
}
use strict "refs" ;
2007-01-30 12:47:36 +00:00
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
{
2015-12-29 19:08:19 +00:00
my ( $ c , $ cmd ) = @ _ ;
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
2018-11-10 16:58:38 +00:00
if ( $ currcfgfile ne AttrVal ( "global" , "configfile" , "" ) &&
! configDBUsed ( ) ) ;
2012-02-08 12:41:00 +00:00
}
}
return undef ;
}
2015-02-22 13:33:26 +00:00
$ cmd =~ s/^\s*#.*$//s ; # Remove comments at the beginning of the line
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 ;
2017-05-22 08:37:51 +00:00
my $ localEvalSpecials = $ evalSpecials ;
2012-05-03 05:41:24 +00:00
while ( defined ( $ subcmd = shift @ cmdList ) ) {
2011-04-25 08:11:52 +00:00
$ subcmd =~ s/SeMiCoLoN/;/g ;
2017-05-22 08:37:51 +00:00
$ evalSpecials = $ localEvalSpecials ;
2017-04-20 18:48:47 +00:00
my $ lret = AnalyzeCommand ( $ c , $ subcmd , "ACC" ) ;
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
2015-11-19 10:12:56 +00:00
AnalyzePerlCommand ( $$ ; $ )
2007-01-30 12:47:36 +00:00
{
2015-12-29 19:08:19 +00:00
my ( $ cl , $ cmd , $ calledFromChain ) = @ _ ; # third parmeter is deprecated
return "Forbidden command $cmd." if ( $ cl && ! Authorized ( $ cl , "cmd" , "perl" ) ) ;
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:
2015-06-22 18:24:59 +00:00
if ( $ featurelevel <= 5.6 ) {
2015-12-29 19:08:19 +00:00
% value = ( ) ;
2015-06-22 18:24:59 +00:00
foreach my $ d ( keys % defs ) {
$ value { $ d } = $ defs { $ d } { STATE }
}
2011-02-05 09:26:55 +00:00
}
2018-01-20 20:12:25 +00:00
my ( $ sec , $ min , $ hour , $ mday , $ month , $ year , $ wday , $ yday , $ isdst ) =
localtime ( gettimeofday ( ) ) ;
2019-03-16 10:57:53 +00:00
$ month + + ; $ year += 1900 ;
2017-09-10 08:52:14 +00:00
my $ today = sprintf ( '%04d-%02d-%02d' , $ year , $ month , $ mday ) ;
2019-03-16 10:57:53 +00:00
my $ hms = sprintf ( "%02d:%02d:%02d" , $ hour , $ min , $ sec ) ;
my $ we = IsWe ( undef , $ wday ) ;
2013-03-24 17:47:28 +00:00
if ( $ evalSpecials ) {
2020-07-04 08:39:51 +00:00
$ cmd = join ( "" , map {
my $ n = substr ( $ _ , 1 ) ; # ignore the legacy %
my $ ref = ref ( $ evalSpecials - > { $ _ } ) ;
$ ref eq "ARRAY" ? "my \@$n=\@{\$evalSpecials->{'$_'}};" :
$ ref eq "HASH" ? "my \%$n=\%{\$evalSpecials->{'$_'}};" :
"my \$$n= \$evalSpecials->{'$_'};" ;
} sort keys % { $ evalSpecials } ) . $ cmd ;
2013-03-24 17:47:28 +00:00
}
2014-10-08 08:30:15 +00:00
$ cmdFromAnalyze = $ cmd ;
2011-02-05 09:26:55 +00:00
my $ ret = eval $ cmd ;
2018-08-04 16:59:23 +00:00
if ( $@ ) {
$ ret = $@ ;
Log 1 , "ERROR evaluating $cmd: $ret" ;
}
2020-07-04 08:39:51 +00:00
# Normally this is deleted in AnalyzeCommandChain, but ECMDDevice calls us
# directly, and combining perl with something else isnt allowed anyway.
$ evalSpecials = undef if ( ! $ calledFromChain ) ;
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
{
2017-04-20 18:48:47 +00:00
my ( $ cl , $ cmd , $ calledFromChain ) = @ _ ;
2011-01-22 21:53:18 +00:00
2015-03-29 10:25:12 +00:00
$ cmd = "" if ( ! defined ( $ cmd ) ) ; # Forum #29963
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<" ;
2017-04-20 18:48:47 +00:00
if ( ! $ cmd ) {
$ evalSpecials = undef if ( ! $ calledFromChain || $ calledFromChain ne "ACC" ) ;
return undef ;
}
2011-01-22 21:53:18 +00:00
2015-03-14 13:01:30 +00:00
if ( $ cmd =~ m/^{.*}$/s ) { # Perl code
2015-11-19 10:12:56 +00:00
return AnalyzePerlCommand ( $ cl , $ cmd , 1 ) ;
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
2016-01-07 08:36:49 +00:00
return "Forbidden command $cmd." if ( $ cl && ! Authorized ( $ cl , "cmd" , "shell" ) ) ;
2013-03-24 17:47:28 +00:00
if ( $ evalSpecials ) {
map { $ ENV { substr ( $ _ , 1 ) } = $ evalSpecials - > { $ _ } ; } keys % { $ evalSpecials } ;
2017-04-20 18:48:47 +00:00
$ evalSpecials = undef if ( ! $ calledFromChain || $ calledFromChain ne "ACC" ) ;
2013-03-24 17:47:28 +00:00
}
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 - > { $ _ } ;
2018-02-17 18:02:11 +00:00
$ cmd =~ s/\$$n/$v/g ; } sort { $ b cmp $ a } keys % { $ evalSpecials } ;
2017-04-20 18:48:47 +00:00
$ evalSpecials = undef if ( ! $ calledFromChain || $ calledFromChain ne "ACC" ) ;
2013-03-24 17:47:28 +00:00
}
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
2017-08-06 11:59:03 +00:00
2007-01-30 12:47:36 +00:00
#############
# Search for abbreviation
2017-08-06 11:59:03 +00:00
sub
2018-10-14 11:53:38 +00:00
getAbbr ( $$ ; $ )
2017-08-06 11:59:03 +00:00
{
2018-10-14 11:53:38 +00:00
my ( $ fn , $ h , $ isMod ) = @ _ ;
2017-08-06 11:59:03 +00:00
my $ lcfn = lc ( $ fn ) ;
my $ fnlen = length ( $ fn ) ;
2018-10-14 11:53:38 +00:00
return $ fn if ( defined ( $ h - > { $ fn } ) && ( $ isMod || $ h - > { $ fn } { Fn } ) ) ; # speedup
2017-08-06 11:59:03 +00:00
foreach my $ f ( sort keys % { $ h } ) {
2018-10-14 11:53:38 +00:00
if ( length ( $ f ) >= $ fnlen &&
lc ( substr ( $ f , 0 , $ fnlen ) ) eq $ lcfn &&
( $ isMod || $ h - > { $ f } { Fn } ) ) {
2017-08-06 11:59:03 +00:00
Log 5 , "AnalyzeCommand: trying $f for $fn" ;
return $ f ;
2007-01-30 12:47:36 +00:00
}
}
2017-08-06 11:59:03 +00:00
return undef ;
2007-01-30 12:47:36 +00:00
}
2017-08-06 11:59:03 +00:00
my $ lfn = getAbbr ( $ fn , \ % cmds ) ;
$ fn = $ lfn if ( $ lfn ) ;
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
2012-10-30 18:46:58 +00:00
#############
2017-08-06 11:59:03 +00:00
# autoload command with ModuleName
if ( ! $ cmds { $ fn } || ! defined ( $ cmds { $ fn } { Fn } ) ) {
2015-03-01 12:53:35 +00:00
my $ modName ;
2017-08-06 11:59:03 +00:00
$ modName = $ cmds { $ fn } { ModuleName } if ( $ cmds { $ fn } && $ cmds { $ fn } { ModuleName } ) ;
2018-10-14 11:53:38 +00:00
$ modName = getAbbr ( $ fn , \ % modules , 1 ) if ( ! $ modName ) ;
2017-08-06 11:59:03 +00:00
2015-03-01 12:53:35 +00:00
LoadModule ( $ modName ) if ( $ modName ) ;
2017-08-06 11:59:03 +00:00
my $ lfn = getAbbr ( $ fn , \ % cmds ) ;
$ fn = $ lfn if ( $ lfn ) ;
2012-10-30 18:46:58 +00:00
}
2017-08-06 11:59:03 +00:00
return "Unknown command $fn, try help." if ( ! $ cmds { $ fn } || ! $ cmds { $ fn } { Fn } ) ;
2018-03-02 11:26:04 +00:00
return "Forbidden command $fn."
if ( $ cl &&
$ cmd !~ m/^(set|get|attr)\s+[^ ]+\s+\?$/ &&
! Authorized ( $ cl , "cmd" , $ fn ) ) ;
2017-08-06 11:59:03 +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
2019-03-12 18:16:45 +00:00
devspec2array ( $; $$ )
2007-12-29 15:57:42 +00:00
{
2019-03-12 18:16:45 +00:00
my ( $ name , $ cl , $ initialList ) = @ _ ;
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 } ) ) {
2019-08-27 08:36:02 +00:00
return "" if ( $ cl && ! Authorized ( $ cl , "devicename" , $ name ) ) ;
2015-12-29 19:08:19 +00:00
2013-07-22 16:24:01 +00:00
# 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 ;
}
2019-03-12 18:16:45 +00:00
my @ names = $ initialList ? @ { $ initialList } : sort keys % defs ;
2013-12-07 11:31:38 +00:00
my @ res ;
2013-12-09 21:17:25 +00:00
foreach my $ dName ( split ( ":FILTER=" , $ l ) ) {
my ( $ n , $ op , $ re ) = ( "NAME" , "=" , $ dName ) ;
2018-02-13 21:19:52 +00:00
if ( $ dName =~ m/^(.*?)(=|!=|~|!~|<=|>=|<|>)(.*)$/ ) {
2013-12-08 11:23:23 +00:00
( $ 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
2015-12-19 20:36:40 +00:00
my $ fType = "" ;
if ( $ n =~ m/^(.:)(.*$)/ ) {
$ fType = $ 1 ;
$ n = $ 2 ;
}
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 } } ) ;
2015-12-01 15:57:00 +00:00
push @ res , $ d if ( AnalyzePerlCommand ( $ cl , $ exec ) ) ;
2013-12-07 14:46:07 +00:00
next ;
}
2013-12-07 11:31:38 +00:00
my $ hash = $ defs { $ d } ;
2014-09-01 19:24:40 +00:00
if ( ! $ hash - > { TYPE } ) {
2016-11-13 10:54:43 +00:00
Log 1 , "Error: >$d< has no TYPE, but following keys: >" .
join ( "," , sort keys % { $ hash } ) . "<" ;
2016-12-30 12:59:07 +00:00
delete ( $ defs { $ d } ) ;
2014-09-01 19:24:40 +00:00
next ;
}
2015-12-19 20:36:40 +00:00
my $ val ;
$ val = $ hash - > { $ n } if ( ! $ fType || $ fType eq "i:" ) ;
if ( ! defined ( $ val ) && ( ! $ fType || $ fType eq "r:" ) ) {
2013-12-07 11:31:38 +00:00
my $ r = $ hash - > { READINGS } ;
$ val = $ r - > { $ n } { VAL } if ( $ r && $ r - > { $ n } ) ;
}
2015-12-19 20:36:40 +00:00
if ( ! defined ( $ val ) && ( ! $ fType || $ fType eq "a:" ) ) {
2013-12-07 11:31:38 +00:00
$ val = $ attr { $ d } { $ n } if ( $ attr { $ d } ) ;
}
2014-01-10 12:28:58 +00:00
$ val = "" if ( ! defined ( $ val ) ) ;
2015-04-23 05:55:53 +00:00
$ val = $ val - > { NAME } if ( ref ( $ val ) eq 'HASH' && $ val - > { NAME } ) ; # IODev
2014-01-21 18:00:07 +00:00
2015-08-09 14:29:36 +00:00
my $ lre = ( $ n eq "room" || $ n eq "group" ) ?
"(^|,)($re)(,|\$)" : "^($re)\$" ;
2015-04-23 05:55:53 +00:00
my $ valReNum = ( looks_like_number ( $ val ) && looks_like_number ( $ re ) ? 1 : 0 ) ;
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 ) ||
2015-03-14 13:01:30 +00:00
( $ op eq "!=" && $ val !~ m/$lre/s ) ||
2018-02-13 21:19:52 +00:00
( $ op eq "~" && $ val =~ m/$lre/is ) ||
( $ op eq "!~" && $ val !~ m/$lre/is ) ||
2015-03-14 13:01:30 +00:00
( $ op eq "<" && $ valReNum && $ val < $ re ) ||
( $ op eq ">" && $ valReNum && $ val > $ re ) ||
( $ op eq "<=" && $ valReNum && $ val <= $ re ) ||
( $ op eq ">=" && $ valReNum && $ val >= $ re ) ) {
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 ( $@ ) {
2022-07-19 11:20:41 +00:00
warn "devspec2array $name: $@" ; #128362
2009-05-30 15:11:56 +00:00
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 ) ;
2019-08-27 08:36:02 +00:00
@ ret = grep { Authorized ( $ cl , "devicename" , $ _ , 1 ) } @ ret if ( $ cl ) ;
2007-12-29 15:57:42 +00:00
return @ ret ;
}
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
2022-02-14 20:39:19 +00:00
my $ type = ( $ unicodeEncoding ? "< :encoding(UTF-8)" : "<" ) ;
if ( ! open ( $ fh , $ type , $ arg ) ) {
2007-01-30 12:47:36 +00:00
return "Can't open $arg: $!" ;
}
2022-02-14 20:39:19 +00:00
2012-06-13 11:03:30 +00:00
Log 1 , "Including $arg" ;
2018-01-20 20:12:25 +00:00
my @ t = localtime ( gettimeofday ( ) ) ;
2016-08-21 09:28:23 +00:00
my $ gcfg = ResolveDateWildcards ( AttrVal ( "global" , "configfile" , "" ) , @ t ) ;
my $ stf = ResolveDateWildcards ( AttrVal ( "global" , "statefile" , "" ) , @ t ) ;
if ( ! $ init_done && $ arg ne $ stf && $ arg ne $ gcfg ) {
2012-02-08 12:41:00 +00:00
my $ nr = $ devcount + + ;
$ comments { $ nr } { TEXT } = "include $arg" ;
2016-08-21 09:28:23 +00:00
$ comments { $ nr } { CFGFN } = $ currcfgfile if ( $ currcfgfile ne $ gcfg ) ;
2012-02-08 12:41:00 +00:00
}
$ oldcfgfile = $ currcfgfile ;
$ currcfgfile = $ arg ;
2007-01-30 12:47:36 +00:00
my $ bigcmd = "" ;
2018-09-12 07:03:11 +00:00
my $ lineno = 0 ;
2007-01-30 12:47:36 +00:00
$ rcvdquit = 0 ;
2007-03-28 17:26:27 +00:00
while ( my $ l = <$fh> ) {
2018-09-12 07:03:11 +00:00
$ lineno + + ;
2008-07-28 12:33:29 +00:00
$ l =~ s/[\r\n]//g ;
2011-06-12 10:51:57 +00:00
2015-03-14 13:01:30 +00:00
if ( $ l =~ m/^(.*)\\ *$/ ) { # Multiline commands
2013-11-19 13:44:02 +00:00
$ bigcmd . = "$1\n" ;
2018-09-12 07:03:11 +00:00
2007-01-30 12:47:36 +00:00
} else {
2011-01-29 12:07:14 +00:00
my $ tret = AnalyzeCommandChain ( $ cl , $ bigcmd . $ l ) ;
2018-09-12 07:03:11 +00:00
if ( defined ( $ tret ) ) {
Log 5 , "$arg line $lineno returned >$tret<" ;
push @ ret , $ 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 ;
2022-02-14 20:39:19 +00:00
close ( $ LOG ) if ( $ 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
2022-02-14 20:39:19 +00:00
2013-08-25 11:49:30 +00:00
if ( ! $ winService - > { AsAService } && $ currlogfile eq "-" ) {
2022-02-14 20:39:19 +00:00
open ( $ LOG , '>&STDOUT' ) || die "Can't dup stdout: $!" ;
2007-01-30 12:47:36 +00:00
} else {
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 } ;
2023-07-11 18:30:38 +00:00
HandleArchiving ( $ defs { global } ) ;
2007-08-06 18:17:29 +00:00
2021-09-13 16:16:52 +00:00
restoreDir_mkDir ( $ currlogfile =~ m , ^ / , ? "" : "." , $ currlogfile , 1 ) ;
2022-02-14 20:39:19 +00:00
open ( $ LOG , ">>$currlogfile" ) || return ( "Can't open $currlogfile: $!" ) ;
2015-06-23 18:40:53 +00:00
redirectStdinStdErr ( ) ;
2011-01-29 12:07:14 +00:00
2007-01-30 12:47:36 +00:00
}
2022-02-14 20:39:19 +00:00
binmode ( $ LOG , ":encoding(UTF-8)" ) if ( $ unicodeEncoding ) ;
$ LOG - > autoflush ( 1 ) ;
2007-01-30 12:47:36 +00:00
$ logopened = 1 ;
2022-02-14 20:39:19 +00:00
$ defs { global } { FD } = $ LOG - > fileno ( ) ; # ??
2007-01-30 12:47:36 +00:00
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 = ( ) ;
2019-01-20 06:16:04 +00:00
% fuuidHash = ( ) ;
2019-01-20 06:34:06 +00:00
% intAt = ( ) ;
@ intAtA = ( ) ;
2019-05-11 17:54:17 +00:00
% sleepers = ( ) ;
% ntfyHash = ( ) ;
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
}
2019-05-04 19:13:22 +00:00
applyGlobalAttrFromEnv ( ) ;
2014-03-01 07:59:19 +00:00
2021-02-15 17:22:42 +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 = ( ) ;
2015-04-17 15:42:27 +00:00
$ lastDefChange + + ;
2021-02-15 17:22:42 +00:00
finish_init ( ) ;
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 ;
}
2016-10-24 06:21:37 +00:00
sub
GetAllReadings ( $ )
{
my ( $ d ) = @ _ ;
my @ ret ;
my $ val = $ defs { $ d } { STATE } ;
if ( defined ( $ val ) &&
$ val ne "unknown" &&
$ val ne "Initialized" &&
2016-11-11 06:48:16 +00:00
$ val ne "" &&
2016-10-24 06:21:37 +00:00
$ val ne "???" ) {
$ val =~ s/;/;;/g ;
2017-01-04 15:33:06 +00:00
$ val =~ s/([ \t])/sprintf("\\%03o",ord($1))/eg if ( $ val =~ m/^[ \t]*$/ ) ;
2016-10-24 06:21:37 +00:00
$ val =~ s/\n/\\\n/g ;
push @ ret , "setstate $d $val" ;
}
#############
# Now the detailed list
my $ r = $ defs { $ d } { READINGS } ;
if ( $ r ) {
foreach my $ c ( sort keys % { $ r } ) {
my $ rd = $ r - > { $ c } ;
if ( ! defined ( $ rd - > { TIME } ) ) {
Log 4 , "WriteStatefile $d $c: Missing TIME, using current time" ;
$ rd - > { TIME } = TimeNow ( ) ;
}
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 ;
$ val =~ s/\n/\\\n/g ;
push @ ret , "setstate $d $rd->{TIME} $c $val" ;
}
}
return @ ret ;
}
2007-01-30 12:47:36 +00:00
#####################################
sub
2021-10-30 19:23:18 +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
}
2016-07-07 07:36:25 +00:00
my $ stateFile = AttrVal ( 'global' , 'statefile' , undef ) ;
return "No statefile specified" if ( ! defined ( $ stateFile ) ) ;
2018-01-20 20:12:25 +00:00
my $ now = gettimeofday ( ) ;
my @ t = localtime ( $ now ) ;
2016-07-07 07:36:25 +00:00
$ stateFile = ResolveDateWildcards ( $ stateFile , @ t ) ;
2022-02-14 20:39:19 +00:00
my $ SFH ;
if ( ! open ( $ SFH , ">$stateFile" ) ) {
2016-10-24 17:30:37 +00:00
my $ msg = "WriteStatefile: Cannot open $stateFile: $!" ;
2007-03-19 14:59:37 +00:00
Log 1 , $ msg ;
return $ msg ;
2007-01-30 12:47:36 +00:00
}
2022-02-14 20:39:19 +00:00
binmode ( $ SFH , ":encoding(UTF-8)" ) if ( $ unicodeEncoding ) ;
2007-01-30 12:47:36 +00:00
2018-01-20 20:12:25 +00:00
my $ t = localtime ( $ now ) ;
2022-02-14 20:39:19 +00:00
print $ SFH "#$t\n" ;
2007-01-30 12:47:36 +00:00
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
2022-06-04 09:50:00 +00:00
$ def =~ s/\n/\\\n/g ;
2022-02-14 20:39:19 +00:00
print $ SFH "define $d $defs{$d}{TYPE} $def\n" ;
2014-05-01 08:19:44 +00:00
}
2012-03-30 06:23:41 +00:00
2016-10-24 06:21:37 +00:00
my @ arr = GetAllReadings ( $ d ) ;
2022-02-14 20:39:19 +00:00
print $ SFH join ( "\n" , @ arr ) . "\n" if ( @ arr ) ;
2016-10-24 06:21:37 +00:00
}
2007-01-30 12:47:36 +00:00
2022-02-14 20:39:19 +00:00
return "$attr{global}{statefile}: $!" if ( ! close ( $ SFH ) ) ;
2016-10-24 06:21:37 +00:00
return "" ;
}
2010-03-22 14:31:37 +00:00
2016-10-24 06:21:37 +00:00
sub
2019-01-18 09:27:06 +00:00
CommandSetuuid ( $$ )
2016-10-24 06:21:37 +00:00
{
2019-01-18 09:27:06 +00:00
my ( $ cl , $ param ) = @ _ ;
return "setuuid cannot be used after FHEM is initialized" if ( $ init_done ) ;
my @ a = split ( " " , $ param ) ;
2021-01-04 19:24:21 +00:00
return "setuuid: Please define $a[0] first" if ( ! defined ( $ defs { $ a [ 0 ] } ) ) ;
2019-01-18 09:37:05 +00:00
return "setuuid $a[0]: duplicate value, ignoring it" if ( $ fuuidHash { $ a [ 1 ] } ) ;
$ fuuidHash { $ a [ 1 ] } = $ a [ 1 ] ;
2019-01-18 09:27:06 +00:00
$ defs { $ a [ 0 ] } { FUUID } = $ a [ 1 ] ;
return undef ;
}
sub
GetDefAndAttr ( $; $ )
{
my ( $ d , $ dumpFUUID ) = @ _ ;
2016-10-24 06:21:37 +00:00
my @ ret ;
2010-03-22 14:31:37 +00:00
2016-10-24 06:21:37 +00:00
if ( $ d ne "global" ) {
my $ def = $ defs { $ d } { DEF } ;
if ( defined ( $ def ) ) {
$ def =~ s/;/;;/g ;
$ def =~ s/\n/\\\n/g ;
push @ ret , "define $d $defs{$d}{TYPE} $def" ;
} else {
push @ ret , "define $d $defs{$d}{TYPE}" ;
2007-01-30 12:47:36 +00:00
}
2007-03-19 14:59:37 +00:00
}
2019-01-18 09:27:06 +00:00
push @ ret , "setuuid $d $defs{$d}{FUUID}"
if ( $ dumpFUUID && defined ( $ defs { $ d } { FUUID } ) && $ defs { $ d } { FUUID } ) ;
2019-01-18 17:06:18 +00:00
# exclude attributes, format <deviceName>:<attrName>, space separated list
my @ dontSave = qw( configdb:rescue configdb:nostate configdb:loadversion
global:configfile global:version ) ;
2016-10-24 06:21:37 +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 } } ) {
2019-01-18 17:06:18 +00:00
next if ( grep { $ _ eq "$d:$a" } @ dontSave ) ;
2016-10-24 06:21:37 +00:00
my $ val = $ attr { $ d } { $ a } ;
$ val =~ s/;/;;/g ;
$ val =~ s/\n/\\\n/g ;
push @ ret , "attr $d $a $val" ;
}
return @ ret ;
2007-03-19 14:59:37 +00:00
}
#####################################
sub
CommandSave ( $$ )
{
my ( $ cl , $ param ) = @ _ ;
2012-05-12 11:36:54 +00:00
2015-01-30 07:10:22 +00:00
if ( $ param && $ param eq "?" ) {
2015-01-24 12:38:25 +00:00
return "No structural changes." if ( ! @ structChangeHist ) ;
2019-03-05 20:27:15 +00:00
return "Last unsaved structural changes:\n " .
join ( "\n " , @ structChangeHist ) ;
2015-01-24 12:38:25 +00:00
}
2017-11-01 16:59:23 +00:00
if ( ! $ cl && ! AttrVal ( "global" , "autosave" , 1 ) ) { # Forum #78769
Log 4 , "Skipping save, as autosave is disabled" ;
return ;
}
my $ restoreDir ;
2018-03-17 16:23:45 +00:00
$ restoreDir = restoreDir_init ( "save" ) if ( ! configDBUsed ( ) ) ;
2017-11-01 16:59:23 +00:00
2015-01-24 12:38:25 +00:00
@ structChangeHist = ( ) ;
2013-03-04 19:58:34 +00:00
DoTrigger ( "global" , "SAVE" , 1 ) ;
2022-09-03 15:40:42 +00:00
if ( ! configDBUsed ( ) ) {
my @ t = localtime ( gettimeofday ( ) ) ;
my $ stf = ResolveDateWildcards ( AttrVal ( "global" , "statefile" , "" ) , @ t ) ;
restoreDir_saveFile ( $ restoreDir , $ stf ) ;
}
2022-02-20 16:09:43 +00:00
$ data { saveID } = createUniqueId ( ) ; # for configDB, #126323
2016-10-24 06:21:37 +00:00
my $ ret = WriteStatefile ( ) ;
2017-11-01 16:59:23 +00:00
2014-09-26 17:55:17 +00:00
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 ) ;
2017-11-01 16:59:23 +00:00
restoreDir_saveFile ( $ restoreDir , $ param ) ;
2022-02-14 20:39:19 +00:00
my $ SFH ;
if ( ! open ( $ SFH , ">$param" ) ) {
2007-03-19 14:59:37 +00:00
return "Cannot open $param: $!" ;
}
2022-02-14 20:39:19 +00:00
binmode ( $ SFH , ":encoding(UTF-8)" ) if ( $ unicodeEncoding ) ;
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 ;
2021-04-28 16:33:04 +00:00
my $ dumpUuid = ( AttrVal ( "global" , "disableFeatures" , "" ) !~ m/\bsaveuuid\b/i ) ;
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 ) {
2017-11-01 16:59:23 +00:00
restoreDir_saveFile ( $ restoreDir , $ cfgfile ) ;
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 ;
}
2022-02-14 20:39:19 +00:00
binmode ( $ fh , ":encoding(UTF-8)" ) if ( $ unicodeEncoding ) ;
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
2021-04-28 16:33:04 +00:00
my @ arr = GetDefAndAttr ( $ d , $ dumpUuid ) ;
2016-10-24 06:21:37 +00:00
print $ fh join ( "\n" , @ arr ) . "\n" if ( @ arr ) ;
2014-09-29 19:52:15 +00:00
2007-01-30 12:47:36 +00:00
}
2015-06-22 18:24:59 +00:00
2022-02-14 20:39:19 +00:00
print $ SFH "include $attr{global}{lastinclude}\n"
2015-06-22 18:24:59 +00:00
if ( $ attr { global } { lastinclude } && $ featurelevel <= 5.6 ) ;
2007-03-28 17:26:27 +00:00
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
2019-01-14 10:04:43 +00:00
CancelDelayedShutdown ( $ )
{
my ( $ d ) = @ _ ;
delete ( $ delayedShutdowns { $ d } ) ;
}
2020-04-01 16:26:14 +00:00
sub
DoDelayedShutdown ( $ )
{
my ( $ hash ) = @ _ ;
return CommandShutdown ( $ hash - > { cl } , $ hash - > { param } , undef , 1 , $ hash - > { exitValue } )
if ( ! keys % delayedShutdowns ||
$ hash - > { waitingFor } + + >= $ hash - > { maxShutdownDelay } ) ;
InternalTimer ( gettimeofday ( ) + 1 , "DoDelayedShutdown" , $ hash , 0 ) ;
}
2019-01-14 10:04:43 +00:00
sub
2019-07-09 09:44:07 +00:00
DelayedShutdown ( $$ $ )
2007-01-30 12:47:36 +00:00
{
2019-07-09 09:44:07 +00:00
my ( $ cl , $ param , $ exitValue ) = @ _ ;
2019-01-14 10:04:43 +00:00
2019-01-15 18:55:21 +00:00
return 1 if ( keys % delayedShutdowns ) ;
2019-01-14 10:04:43 +00:00
foreach my $ d ( sort keys % defs ) {
$ delayedShutdowns { $ d } = 1 if ( CallFn ( $ d , "DelayedShutdownFn" , $ defs { $ d } ) ) ;
}
return 0 if ( ! keys % delayedShutdowns ) ;
2020-04-01 16:26:14 +00:00
my $ maxShutdownDelay = AttrVal ( "global" , "maxShutdownDelay" , 10 ) ;
2019-01-14 10:04:43 +00:00
Log 1 , "Server shutdown delayed due to " . join ( "," , keys % delayedShutdowns ) .
" for max $maxShutdownDelay sec" ;
DoTrigger ( "global" , "DELAYEDSHUTDOWN" , 1 ) ;
2020-04-01 16:26:14 +00:00
DoDelayedShutdown (
{ cl = > $ cl , param = > $ param , exitValue = > $ exitValue ,
waitingFor = > 0 , maxShutdownDelay = > $ maxShutdownDelay } ) ;
2019-01-14 10:04:43 +00:00
return 1 ;
}
sub
2019-07-09 09:44:07 +00:00
CommandShutdown ( $$ ; $$ $ )
2019-01-14 10:04:43 +00:00
{
2019-07-09 09:44:07 +00:00
my ( $ cl , $ param , $ cmdName , $ final , $ exitValue ) = @ _ ;
2015-12-21 12:43:26 +00:00
if ( $ param && $ param =~ m/^(\d+)$/ ) {
$ exitValue = $ 1 ;
$ param = "" ;
}
return "Usage: shutdown [restart|exitvalue]"
2013-10-22 20:55:35 +00:00
if ( $ param && $ param ne "restart" ) ;
2019-07-09 09:44:07 +00:00
return if ( ! $ final && DelayedShutdown ( $ cl , $ param , $ exitValue ) ) ;
2013-10-22 20:55:35 +00:00
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 } ) ;
2020-07-24 16:37:59 +00:00
# Avoid restarts in overoptimized browser #105729
doShutdown ( { p = > $ param , e = > $ exitValue } ) if ( ! $ cl ) ;
InternalTimer ( time ( ) + 1 , sub ( ) { doShutdown ( @ _ ) } , { p = > $ param , e = > $ exitValue } , 0 ) ;
}
sub
doShutdown ( $$ )
{
my ( $ param , $ exitValue ) = ( $ _ [ 0 ] - > { p } , $ _ [ 0 ] - > { e } ) ;
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/ ) {
2015-04-17 16:14:45 +00:00
system ( "(sleep " . AttrVal ( "global" , "restartDelay" , 2 ) .
"; exec $^X $0 $attr{global}{configfile})&" ) ;
2013-08-25 11:49:30 +00:00
} 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
}
2019-07-09 09:44:07 +00:00
exit ( $ exitValue ? $ exitValue : 0 ) ;
2007-01-30 12:47:36 +00:00
}
2015-06-22 18:24:59 +00:00
#####################################
sub
2015-12-01 14:10:03 +00:00
ReplaceSetMagic ( $$ @ ) # Forum #38276
2015-06-22 18:24:59 +00:00
{
2015-12-01 14:10:03 +00:00
my $ hash = shift ;
2015-11-30 06:32:35 +00:00
my $ nsplit = shift ;
2015-06-22 18:24:59 +00:00
my $ a = join ( " " , @ _ ) ;
2015-12-31 10:03:49 +00:00
my $ oa = $ a ;
2015-06-22 18:24:59 +00:00
2017-04-04 14:45:48 +00:00
sub
rsmVal ( $$ $$ $ )
{
my ( $ all , $ t , $ d , $ n , $ s , $ val ) = @ _ ;
my $ hash = $ defs { $ d } ;
return $ all if ( ! $ hash ) ;
if ( ! $ t || $ t eq "r:" ) {
my $ r = $ hash - > { READINGS } ;
2017-04-05 11:36:44 +00:00
if ( $ s && ( $ s eq ":t" || $ s eq ":sec" ) ) {
return $ all if ( ! $ r || ! $ r - > { $ n } ) ;
$ val = $ r - > { $ n } { TIME } ;
2018-01-20 20:12:25 +00:00
$ val = int ( gettimeofday ( ) ) - time_str2num ( $ val ) if ( $ s eq ":sec" ) ;
2017-04-05 11:36:44 +00:00
return $ val ;
}
2017-04-04 14:45:48 +00:00
$ val = $ r - > { $ n } { VAL } if ( $ r && $ r - > { $ n } ) ;
}
$ val = $ hash - > { $ n } if ( ! defined ( $ val ) && ( ! $ t || $ t eq "i:" ) ) ;
$ val = $ attr { $ d } { $ n } if ( ! defined ( $ val ) && ( ! $ t || $ t eq "a:" ) && $ attr { $ d } ) ;
return $ all if ( ! defined ( $ val ) ) ;
2017-04-05 11:36:44 +00:00
2017-04-12 06:31:37 +00:00
if ( $ s && $ s =~ /:d|:r|:i/ && $ val =~ /(-?\d+(\.\d+)?)/ ) {
2017-04-05 11:36:44 +00:00
$ val = $ 1 ;
2019-05-21 19:10:52 +00:00
$ val = int ( $ val ) if ( $ s eq ":i" ) ;
2017-04-05 11:36:44 +00:00
$ val = round ( $ val , defined ( $ 1 ) ? $ 1 : 1 ) if ( $ s =~ /^:r(\d)?/ ) ;
2019-05-21 19:10:52 +00:00
$ val = round ( $ val , $ 1 ) if ( $ s =~ /^:d(\d)/ ) ; #100753
2017-04-05 11:36:44 +00:00
}
2017-04-04 14:45:48 +00:00
return $ val ;
}
2019-05-21 19:10:52 +00:00
$ a =~ s /(\[([ari]:)?([a-zA-Z\d._]+):([a-zA-Z\d._\/-]+)(:(t|sec|i|[dr]\d?))?\])/
2017-04-08 13:19:04 +00:00
rsmVal ( $ 1 , $ 2 , $ 3 , $ 4 , $ 5 ) / eg ;
2015-06-22 18:24:59 +00:00
2017-04-20 18:48:47 +00:00
my $ esDef = ( $ evalSpecials ? 1 : 0 ) ;
2016-08-30 13:30:50 +00:00
$ evalSpecials - > { '%DEV' } = $ hash - > { NAME } ;
2016-04-24 16:49:10 +00:00
$ a =~ s/{\((.*?)\)}/AnalyzePerlCommand($hash->{CL},$1,1)/egs ;
2017-04-20 18:48:47 +00:00
$ evalSpecials = undef if ( ! $ esDef ) ; ;
2015-06-22 18:24:59 +00:00
2015-12-31 10:03:49 +00:00
return ( undef , @ _ ) if ( $ oa eq $ a ) ;
return ( undef , split ( / / , $ a , $ nsplit ) ) ;
2015-06-22 18:24:59 +00:00
}
2007-01-30 12:47:36 +00:00
#####################################
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
2016-04-17 10:01:39 +00:00
return CallFn ( $ dev , "SetFn" , $ hash ,
$ modules { $ hash - > { TYPE } } - > { parseParams } ? parseParams ( \ @ a ) : @ 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 } ) ;
2015-11-13 16:33:50 +00:00
my $ err ;
2015-12-01 14:10:03 +00:00
( $ err , @ a ) = ReplaceSetMagic ( $ hash , 0 , @ a ) if ( $ featurelevel >= 5.7 ) ;
2015-11-13 16:33:50 +00:00
return $ err if ( $ err ) ;
2015-06-22 18:24:59 +00:00
2013-06-22 13:47:34 +00:00
$ hash - > { ".triggerUsed" } = 0 ;
2016-04-17 10:01:39 +00:00
my ( $ ret , $ skipTrigger ) = CallFn ( $ dev , "SetFn" , $ hash ,
$ modules { $ hash - > { TYPE } } - > { parseParams } ? parseParams ( \ @ a ) : @ 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
2017-05-12 05:48:17 +00:00
my $ arg ;
$ 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 ;
2018-03-02 11:26:04 +00:00
foreach my $ sdev ( devspec2array ( $ a [ 0 ] , $ a [ 1 ] && $ a [ 1 ] eq "?" ? undef : $ cl ) ) {
2007-01-30 12:47:36 +00:00
2007-03-31 06:28:08 +00:00
$ a [ 0 ] = $ sdev ;
2015-12-06 19:44:51 +00:00
$ defs { $ sdev } - > { CL } = $ cl if ( $ defs { $ sdev } ) ;
2007-03-31 06:28:08 +00:00
my $ ret = DoSet ( @ a ) ;
2015-12-06 19:44:51 +00:00
delete $ defs { $ sdev } - > { CL } if ( $ defs { $ sdev } ) ;
2007-03-31 06:28:08 +00:00
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 ;
2018-03-02 11:26:04 +00:00
foreach my $ sdev ( devspec2array ( $ a [ 0 ] , $ a [ 1 ] && $ a [ 1 ] eq "?" ? undef : $ cl ) ) {
2007-12-29 15:57:42 +00:00
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 ;
2015-11-18 18:53:43 +00:00
$ defs { $ sdev } - > { CL } = $ cl ;
2016-04-17 10:01:39 +00:00
my $ ret = CallFn ( $ sdev , "GetFn" , $ defs { $ sdev } ,
$ modules { $ defs { $ sdev } - > { TYPE } } - > { parseParams } ? parseParams ( \ @ a ) : @ a ) ;
2015-11-18 18:53:43 +00:00
delete $ defs { $ sdev } - > { CL } ;
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
}
2015-11-18 18:53:43 +00:00
sub
asyncOutput ( $$ )
{
2018-01-06 17:20:00 +00:00
my ( $ cl , $ ret ) = @ _ ;
return undef if ( ! $ cl || ! $ cl - > { NAME } ) ;
2018-01-10 21:07:52 +00:00
my $ temporary ;
if ( $ defs { $ cl - > { NAME } } ) {
$ cl = $ defs { $ cl - > { NAME } } ; # Compatibility
} else {
$ defs { $ cl - > { NAME } } = $ cl ; # timeconsuming answer: get fd ist already closed
$ temporary = 1 ;
}
CallFn ( $ cl - > { NAME } , "AsyncOutputFn" , $ cl , $ ret ) ;
delete $ defs { $ cl - > { NAME } } if ( $ temporary ) ;
return undef ;
2015-11-18 18:53:43 +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 ;
}
2016-04-16 19:32:33 +00:00
2007-01-30 12:47:36 +00:00
#####################################
2021-04-16 16:25:03 +00:00
sub
cmd_parseOpts ( $$ $ )
{
my ( $ def , $ optRegexp , $ res ) = @ _ ;
while ( $ def ) {
last if ( $ def !~ m/^\s*($optRegexp)\s+/ ) ;
my $ o = $ 1 ;
$ def =~ s/^\s*$o\s+// ;
$ o =~ s/^-// ;
$ res - > { $ o } = 1 ;
}
return $ def ;
}
2007-01-30 12:47:36 +00:00
sub
CommandDefine ( $$ )
{
my ( $ cl , $ def ) = @ _ ;
2015-03-01 12:53:35 +00:00
2021-04-16 16:25:03 +00:00
# ignoreErr ist used by RSS in fhem.cfg.demo, with no GD installed
# temporary #39610 #46640
# silent #57691
my % opt ;
my $ optRegexp = '-ignoreErr|-temporary|-silent' ;
$ def = cmd_parseOpts ( $ def , $ optRegexp , \ % opt ) ;
my @ a = split ( "[ \t]+" , $ def , 3 ) ;
2015-08-23 12:43:56 +00:00
2012-01-05 13:09:07 +00:00
my $ name = $ a [ 0 ] ;
2021-04-16 16:25:03 +00:00
return "Usage: define [$optRegexp] <name> <type> <type dependent arguments>"
2015-03-14 13:01:30 +00:00
if ( int ( @ a ) < 2 ) ;
2012-01-05 13:09:07 +00:00
return "$name already defined, delete it first" if ( defined ( $ defs { $ name } ) ) ;
2016-01-03 12:34:27 +00:00
return "Invalid characters in name (not A-Za-z0-9._): $name"
2017-08-19 11:19:54 +00:00
if ( ! goodDeviceName ( $ name ) ) ;
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 ;
}
}
}
2021-04-16 16:25:03 +00:00
my $ newm = LoadModule ( $ m , $ opt { 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 ;
2019-01-18 09:27:06 +00:00
$ hash { FUUID } = genUUID ( ) ;
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 ) ;
2022-12-04 10:04:50 +00:00
#130588: start early after next save, for a small SubProcess size
$ hash { NR } = ( $ modules { $ m } { prioSave } && $ devcountPrioSave < 30 ) ?
2023-11-29 12:33:32 +00:00
$ devcountPrioSave + + :
( $ opt { temporary } ? $ devcountTemp + + : $ devcount + + ) ;
2012-02-08 12:41:00 +00:00
$ hash { CFGFN } = $ currcfgfile
2018-11-11 18:16:45 +00:00
if ( $ currcfgfile ne AttrVal ( "global" , "configfile" , "" ) &&
! configDBUsed ( ) ) ;
2017-02-24 09:12:38 +00:00
$ hash { CL } = $ cl ;
2021-04-16 16:25:03 +00:00
$ hash { TEMPORARY } = 1 if ( $ opt { temporary } ) ;
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
2016-04-17 10:01:39 +00:00
my $ ret = CallFn ( $ name , "DefFn" , \ % hash ,
$ modules { $ m } - > { parseParams } ? parseParams ( $ def ) : $ def ) ;
2007-02-11 17:58:23 +00:00
if ( $ ret ) {
2021-04-16 16:25:03 +00:00
Log 1 , "define $def: $ret" if ( ! $ opt { ignoreErr } ) ;
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 {
2017-02-24 09:12:38 +00:00
delete $ hash { CL } ;
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 = ( ) ;
2021-04-16 16:25:03 +00:00
if ( ! $ opt { temporary } && $ init_done ) {
addStructChange ( "define" , $ name , $ def ) if ( ! $ opt { silent } ) ;
2019-05-11 17:48:03 +00:00
DoTrigger ( "global" , "DEFINED $name" , 1 ) ;
}
2022-05-07 12:36:18 +00:00
if ( $ init_done && $ modules { $ m } { Match } ) { # reset multiple IOdev, #127565
foreach my $ an ( keys % defs ) {
my $ ah = $ defs { $ an } ;
my $ cl = $ ah - > { Clients } ;
$ cl = $ modules { $ ah - > { TYPE } } { Clients } if ( ! $ cl ) ;
next if ( ! $ cl || ! $ ah - > { '.clientArray' } ) ;
foreach my $ cmRe ( split ( /:/ , $ cl ) ) {
2022-05-09 09:50:54 +00:00
if ( $ m =~ m/^$cmRe$/ ) {
2022-05-07 12:36:18 +00:00
delete ( $ ah - > { '.clientArray' } ) ;
last ;
}
}
}
}
2007-02-11 17:58:23 +00:00
}
2021-04-16 16:25:03 +00:00
return ( $ ret && $ opt { ignoreErr } ?
2016-11-13 16:11:52 +00:00
"Cannot define $name, remove -ignoreErr for details" : $ ret ) ;
2007-01-30 12:47:36 +00:00
}
2007-04-24 07:13:21 +00:00
#####################################
sub
CommandModify ( $$ )
{
my ( $ cl , $ def ) = @ _ ;
2021-04-16 16:25:03 +00:00
my % opt ;
$ def = cmd_parseOpts ( $ def , '-silent' , \ % opt ) ;
2007-04-24 07:13:21 +00:00
my @ a = split ( "[ \t]+" , $ def , 2 ) ;
return "Usage: modify <name> <type dependent arguments>"
2015-03-14 13:01:30 +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 ] } ) ) ;
my $ hash = $ defs { $ a [ 0 ] } ;
2017-02-05 13:23:25 +00:00
% ntfyHash = ( ) if ( $ hash - > { NTFY_ORDER } ) ;
2007-04-24 07:13:21 +00:00
2008-05-09 13:58:10 +00:00
$ hash - > { OLDDEF } = $ hash - > { DEF } ;
2007-04-24 07:13:21 +00:00
$ hash - > { DEF } = $ a [ 1 ] ;
2017-02-24 09:12:38 +00:00
$ hash - > { CL } = $ cl ;
2013-02-06 18:19:59 +00:00
my $ ret = CallFn ( $ a [ 0 ] , "DefFn" , $ hash ,
2016-05-15 19:33:56 +00:00
$ modules { $ hash - > { TYPE } } - > { parseParams } ?
parseParams ( "$a[0] $hash->{TYPE}" . ( defined ( $ a [ 1 ] ) ? " $a[1]" : "" ) ) :
"$a[0] $hash->{TYPE}" . ( defined ( $ a [ 1 ] ) ? " $a[1]" : "" ) ) ;
2017-02-24 09:12:38 +00:00
delete $ hash - > { CL } ;
2014-10-15 17:11:07 +00:00
if ( $ ret ) {
$ hash - > { DEF } = $ hash - > { OLDDEF } ;
} else {
2021-04-16 16:25:03 +00:00
addStructChange ( "modify" , $ a [ 0 ] , $ def ) if ( ! $ opt { silent } ) ;
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 ;
}
2015-04-15 13:21:04 +00:00
#####################################
sub
CommandDefMod ( $$ )
{
my ( $ cl , $ def ) = @ _ ;
2021-04-16 16:25:03 +00:00
my % opt ;
my $ optRegexp = '-ignoreErr|-temporary|-silent' ;
$ def = cmd_parseOpts ( $ def , $ optRegexp , \ % opt ) ;
2015-04-15 13:21:04 +00:00
my @ a = split ( "[ \t]+" , $ def , 3 ) ;
2021-04-16 16:25:03 +00:00
return "Usage: defmod [$optRegexp] <name> <type> <type dependent arguments>"
2015-04-15 13:21:04 +00:00
if ( int ( @ a ) < 2 ) ;
if ( $ defs { $ a [ 0 ] } ) {
$ def = $ a [ 2 ] ? "$a[0] $a[2]" : $ a [ 0 ] ;
2017-02-03 18:16:19 +00:00
return "defmod $a[0]: Cannot change the TYPE of an existing definition"
if ( $ a [ 1 ] ne $ defs { $ a [ 0 ] } { TYPE } ) ;
2021-04-16 16:25:03 +00:00
$ def = "-" . join ( " -" , keys % opt ) . " " . $ def if ( % opt ) ;
2015-04-15 13:21:04 +00:00
return CommandModify ( $ cl , $ def ) ;
} else {
2021-04-16 16:25:03 +00:00
$ def = "-" . join ( " -" , keys % opt ) . " " . $ def if ( % opt ) ;
2015-04-15 13:21:04 +00:00
return CommandDefine ( $ cl , $ def ) ;
}
}
2007-01-30 12:47:36 +00:00
#############
# internal
sub
2021-05-24 13:27:13 +00:00
fhem_setIoDev ( $$ )
{
my ( $ hash , $ val ) = @ _ ;
if ( ! $ val || ! defined ( $ defs { $ val } ) ) {
if ( ! $ init_done ) {
$ hash - > { IODevMissing } = 1 ;
$ hash - > { IODevName } = $ val ;
}
return "unknown IODev $val specified" ;
}
2021-05-24 13:38:33 +00:00
my $ av = AttrVal ( $ hash - > { NAME } , "IODev" , "" ) ;
2021-05-24 13:27:13 +00:00
return "$hash->{NAME}: not setting IODev to $val, as different attr exists"
2021-05-24 13:38:33 +00:00
if ( $ av && $ av ne $ val ) ;
2021-05-24 13:27:13 +00:00
$ hash - > { IODev } = $ defs { $ val } ;
setReadingsVal ( $ hash , "IODev" , $ val , TimeNow ( ) ) ; # 120603
delete ( $ defs { $ val } { ".clientArray" } ) ; # Force a recompute
delete ( $ hash - > { IODevMissing } ) ;
delete ( $ hash - > { IODevName } ) ;
return undef ;
}
# Searches for a possible IODev, choosing the last defined compatible one.
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 } ;
2007-01-30 12:47:36 +00:00
2021-04-28 16:33:04 +00:00
$ proposed = AttrVal ( $ hn , "IODev" , undef ) if ( ! $ proposed ) ;
$ proposed = ReadingsVal ( $ hn , "IODev" , undef ) if ( ! $ proposed ) ;
2014-03-27 16:19:53 +00:00
2018-07-19 13:13:41 +00:00
if ( $ proposed && $ defs { $ proposed } && IsDisabled ( $ proposed ) != 1 ) {
2021-05-24 13:27:13 +00:00
fhem_setIoDev ( $ hash , $ proposed ) ;
2010-12-16 08:07:18 +00:00
2021-04-28 16:33:04 +00:00
} else {
# Set the I/O device, search for the last compatible one.
for my $ p ( sort { $ defs { $ b } { NR } <=> $ defs { $ a } { NR } } keys % defs ) {
next if ( IsDisabled ( $ p ) == 1 ) ;
next if ( $ defs { $ p } { TEMPORARY } ) ; # e.g. server clients
my $ cl = $ defs { $ p } { Clients } ;
$ cl = $ modules { $ defs { $ p } { TYPE } } { Clients } if ( ! $ cl ) ;
if ( $ cl && $ defs { $ p } { NAME } ne $ hn ) { # e.g. RFR
my @ fnd = grep { $ hash - > { TYPE } =~ m/^$_$/ ; } split ( ":" , $ cl ) ;
if ( @ fnd ) {
2021-05-24 13:27:13 +00:00
fhem_setIoDev ( $ hash , $ p ) ;
2021-04-28 16:33:04 +00:00
last ;
}
2013-05-21 13:25:51 +00:00
}
2007-01-30 12:47:36 +00:00
}
}
2021-04-28 16:33:04 +00:00
2021-05-24 13:27:13 +00:00
return if ( $ hash - > { IODev } ) ;
2014-03-09 12:31:07 +00:00
2021-05-24 13:27:13 +00:00
if ( $ init_done ) {
Log 3 , "No I/O device found for $hn" ;
2014-03-06 20:06:00 +00:00
} else {
2021-05-24 13:27:13 +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 ;
2015-12-01 15:57:00 +00:00
foreach my $ sdev ( devspec2array ( $ def , $ cl ) ) {
2007-12-29 15:57:42 +00:00
if ( ! defined ( $ defs { $ sdev } ) ) {
push @ rets , "Please define $sdev first" ;
next ;
}
2007-01-30 12:47:36 +00:00
2019-01-14 20:35:10 +00:00
$ defs { $ sdev } - > { CL } = $ cl ;
2007-12-29 15:57:42 +00:00
my $ ret = CallFn ( $ sdev , "UndefFn" , $ defs { $ sdev } , $ sdev ) ;
if ( $ ret ) {
push @ rets , $ ret ;
2019-01-14 20:35:10 +00:00
delete $ defs { $ sdev } - > { CL } ;
2007-12-29 15:57:42 +00:00
next ;
}
2013-04-28 12:40:28 +00:00
$ ret = CallFn ( $ sdev , "DeleteFn" , $ defs { $ sdev } , $ sdev ) ;
if ( $ ret ) {
push @ rets , $ ret ;
2019-01-14 20:35:10 +00:00
delete $ defs { $ sdev } - > { CL } ;
2013-04-28 12:40:28 +00:00
next ;
}
2019-01-14 20:35:10 +00:00
delete $ defs { $ sdev } - > { CL } ;
2020-10-01 19:49:32 +00:00
removeFromNtfyHash ( $ sdev ) ;
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 ) = @ _ ;
2021-07-06 08:56:14 +00:00
my $ optRegexp = '-silent' ;
my % opt ;
$ def = cmd_parseOpts ( $ def , $ optRegexp , \ % opt ) ;
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 ;
2015-12-01 15:57:00 +00:00
foreach my $ sdev ( devspec2array ( $ a [ 0 ] , $ cl ) ) {
2007-12-29 15:57:42 +00:00
if ( ! defined ( $ defs { $ sdev } ) ) {
push @ rets , "Please define $sdev first" ;
next ;
}
$ a [ 0 ] = $ sdev ;
2013-01-13 15:16:31 +00:00
2018-03-07 21:10:28 +00:00
if ( $ a [ 1 ] ) {
if ( $ a [ 1 ] eq "userReadings" ) {
delete ( $ defs { $ sdev } { '.userReadings' } ) ;
} elsif ( $ ra { $ a [ 1 ] } ) {
my $ cache = $ ra { $ a [ 1 ] } { c } ;
delete $ defs { $ sdev } { $ cache } if ( $ cache ) ;
}
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 ;
}
2020-09-23 15:28:56 +00:00
if ( @ a == 1 ) { # Delete all attributes of a device
2007-12-29 15:57:42 +00:00
delete ( $ attr { $ sdev } ) ;
2014-10-15 17:11:07 +00:00
2020-09-23 15:28:56 +00:00
} else { # delete specified attribute(s)
if ( defined ( $ attr { $ sdev } ) ) {
map { delete ( $ attr { $ sdev } { $ _ } ) if ( $ _ =~ m/^$a[1]$/ ) }
keys % { $ attr { $ sdev } } ;
}
2014-10-15 17:11:07 +00:00
2007-12-29 15:57:42 +00:00
}
2021-07-06 08:56:14 +00:00
addStructChange ( "deleteAttr" , $ sdev , join ( " " , @ a ) ) if ( ! $ opt { silent } ) ;
2021-06-05 09:28:01 +00:00
DoTrigger ( "global" , "DELETEATTR " . join ( " " , @ a ) , 1 ) if ( $ init_done ) ;
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 ;
2015-12-01 15:57:00 +00:00
my @ devspec = devspec2array ( $ a [ 0 ] , $ cl ) ;
2013-08-07 11:18:15 +00:00
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 ) = @ _ ;
2019-03-23 16:59:40 +00:00
my $ quiet = undef ;
if ( $ def =~ m/^\s*-q\s(.*)$/ ) {
$ quiet = 1 ;
$ def = $ 1 ;
}
2020-12-07 09:02:12 +00:00
my @ a = split ( " " , $ def , 3 ) ;
return "Usage: deletereading [-q] <name> <reading> [older-than-seconds]\n" .
$ namedef if ( @ a < 2 ) ;
2013-01-19 13:36:29 +00:00
2014-12-29 16:03:31 +00:00
eval { "" =~ m/$a[1]/ } ;
return "Bad regexp $a[1]: $@" if ( $@ ) ;
2020-12-07 09:02:12 +00:00
return "Bad older-than-seconds format $a[2]"
if ( defined ( $ a [ 2 ] ) && $ a [ 2 ] !~ m/^\d+$/ ) ;
2014-12-29 16:03:31 +00:00
2013-01-19 13:36:29 +00:00
my @ rets ;
2015-12-01 15:57:00 +00:00
foreach my $ sdev ( devspec2array ( $ a [ 0 ] , $ cl ) ) {
2013-01-19 13:36:29 +00:00
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 } } ) {
2020-12-07 09:02:12 +00:00
next if ( defined ( $ a [ 2 ] ) && ReadingsAge ( $ sdev , $ reading , 0 ) <= $ a [ 2 ] ) ;
2018-03-07 21:10:28 +00:00
readingsDelete ( $ defs { $ sdev } , $ reading ) ;
2013-01-19 13:36:29 +00:00
push @ rets , "Deleted reading $reading for device $sdev" ;
}
}
2019-03-23 16:59:40 +00:00
return undef if ( $ quiet ) ;
2013-01-19 13:36:29 +00:00
return join ( "\n" , @ rets ) ;
}
2013-08-22 15:13:44 +00:00
sub
CommandSetReading ( $$ )
{
my ( $ cl , $ def ) = @ _ ;
2020-08-19 16:54:17 +00:00
my $ timestamp ;
if ( $ def =~ m/^([^ ]+) +(\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d) +([^ ]+) +(.*)$/ ) {
$ def = "$1 $3 $4" ;
$ timestamp = $ 2 ;
}
2013-08-22 15:13:44 +00:00
my @ a = split ( " " , $ def , 3 ) ;
2020-08-19 16:54:17 +00:00
return "Usage: setreading <name> [YYYY-MM-DD HH:MM:SS] <reading> <value>\n" .
$ namedef if ( @ a != 3 ) ;
2013-08-22 15:13:44 +00:00
2015-11-30 06:32:35 +00:00
my $ err ;
2017-01-30 19:47:33 +00:00
my @ b = @ a ;
2013-08-22 15:13:44 +00:00
my @ rets ;
2015-12-01 15:57:00 +00:00
foreach my $ sdev ( devspec2array ( $ a [ 0 ] , $ cl ) ) {
2013-08-22 15:13:44 +00:00
if ( ! defined ( $ defs { $ sdev } ) ) {
push @ rets , "Please define $sdev first" ;
next ;
}
2015-12-01 14:10:03 +00:00
my $ hash = $ defs { $ sdev } ;
if ( $ featurelevel >= 5.7 ) {
$ hash - > { CL } = $ cl ;
2017-01-30 19:47:33 +00:00
( $ err , @ b ) = ReplaceSetMagic ( $ hash , 3 , @ a ) ;
2017-02-24 09:12:38 +00:00
delete $ hash - > { CL } ;
2015-12-01 14:10:03 +00:00
}
2019-02-17 09:24:01 +00:00
my $ b1 = $ b [ 1 ] ;
2020-02-18 14:11:59 +00:00
return "$sdev: bad reading name '$b1' (allowed chars: A-Za-z/\\d_\\.-)"
2019-02-17 09:24:01 +00:00
if ( ! goodReadingName ( $ b1 ) ) ;
2020-04-19 15:15:46 +00:00
2021-05-23 13:15:58 +00:00
if ( $ b1 eq "IODev" ) {
2021-07-11 11:11:07 +00:00
next if ( ! fhem_devSupportsAttr ( $ sdev , "IODev" ) ) ;
2021-05-23 13:15:58 +00:00
my $ ret = fhem_setIoDev ( $ hash , $ b [ 2 ] ) ;
2021-07-10 10:15:17 +00:00
push @ rets , $ ret if ( $ ret ) ;
next ;
2021-05-23 13:15:58 +00:00
}
2020-04-19 15:15:46 +00:00
if ( $ hash - > { ".updateTime" } ) { # Called from userReadings, #110375
2020-04-19 15:52:48 +00:00
Log 1 , "'setreading $def' called form userReadings is prohibited" ;
return ;
2020-04-19 15:15:46 +00:00
} else {
2020-08-19 16:54:17 +00:00
readingsSingleUpdate ( $ hash , $ b1 , $ b [ 2 ] , 1 , $ timestamp ) ;
2020-04-19 15:15:46 +00:00
}
2021-05-23 13:15:58 +00:00
2013-08-22 15:13:44 +00:00
}
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
2022-10-28 12:09:44 +00:00
PrintHash ( $$ )
2007-10-21 11:35:58 +00:00
{
2022-10-28 12:09:44 +00:00
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 } ) ) {
2022-10-28 12:09:44 +00:00
$ str . = sprintf ( "%*s %-19s %-15s %s\n" ,
2007-10-21 11:35:58 +00:00
$ lev , " " , $ h - > { $ c } { TIME } , $ c , $ h - > { $ c } { VAL } ) ;
2007-11-26 08:27:04 +00:00
} elsif ( $ c eq "IODev" || $ c eq "HASH" ) {
2022-10-28 12:09:44 +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 {
2022-10-28 12:09:44 +00:00
$ sstr . = sprintf ( "%*s %s:\n" , $ lev , " " , $ c ) ;
$ sstr . = PrintHash ( $ h - > { $ c } , $ lev + 2 ) ;
2007-10-21 11:35:58 +00:00
}
2011-11-06 18:49:25 +00:00
} elsif ( ref ( $ h - > { $ c } ) eq "ARRAY" ) {
2022-10-28 12:09:44 +00:00
$ sstr . = sprintf ( "%*s %s:\n" , $ lev , " " , $ c ) ;
2011-11-06 18:49:25 +00:00
foreach my $ v ( @ { $ h - > { $ c } } ) {
2022-10-28 12:09:44 +00:00
$ sstr . = sprintf ( "%*s %s\n" ,
2022-10-27 11:35:56 +00:00
$ lev + 2 , " " , defined ( $ v ) ? $ v: "undef" ) ;
2011-11-06 18:49:25 +00:00
}
2007-10-21 11:35:58 +00:00
}
} else {
2013-01-07 19:52:24 +00:00
my $ v = $ h - > { $ c } ;
2022-10-28 12:09:44 +00:00
$ str . = sprintf ( "%*s %-10s %s\n" ,
2022-10-27 11:35:56 +00:00
$ 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 = "" ;
2022-10-27 11:35:56 +00:00
my % opt ;
my $ optRegexp = '-r|-R|-i' ;
$ param = cmd_parseOpts ( $ param , $ optRegexp , \ % opt ) ;
2007-01-30 12:47:36 +00:00
2022-10-27 11:35:56 +00:00
if ( $ opt { r } || $ opt { R } ) {
2016-12-14 13:04:42 +00:00
my @ list ;
2022-10-27 11:35:56 +00:00
if ( $ opt { R } ) {
return "-R needs a valid device as argument" if ( ! $ param ) ;
push @ list , $ param ;
push @ list , getPawList ( $ param ) ;
2016-12-14 13:04:42 +00:00
} else {
2022-10-27 11:35:56 +00:00
@ list = devspec2array ( $ param ? $ param : ".*" , $ cl ) ;
2016-12-14 13:04:42 +00:00
}
foreach my $ d ( @ list ) {
2016-10-24 17:30:37 +00:00
return "No device named $d found" if ( ! defined ( $ defs { $ d } ) ) ;
2016-10-24 06:21:37 +00:00
$ str . = "\n" if ( $ str ) ;
my @ a = GetDefAndAttr ( $ d ) ;
$ str . = join ( "\n" , @ a ) . "\n" if ( @ a ) ;
2022-10-28 12:09:44 +00:00
if ( $ opt { i } ) {
my $ intHash = PrintHash ( $ defs { $ d } , 2 ) ;
$ intHash =~ s/\n/\n#/g ;
$ str . = "#" . $ intHash ;
}
2016-10-24 17:30:37 +00:00
}
foreach my $ d ( sort @ list ) {
$ str . = "\n" if ( $ str ) ;
my @ a = GetAllReadings ( $ d ) ;
2016-10-24 06:21:37 +00:00
$ str . = join ( "\n" , @ a ) . "\n" if ( @ a ) ;
}
return $ str ;
}
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
2015-03-14 13:01:30 +00:00
$ modules { $ defs { $ b } { TYPE } } { ORDER } . $ defs { $ b } { TYPE } ;
$ x = ( $ a cmp $ b ) if ( $ x == 0 ) ; $ x ; } keys % defs ) {
2019-08-27 08:36:02 +00:00
next if ( IsIgnored ( $ d ) || ( $ cl && ! Authorized ( $ cl , "devicename" , $ d , 1 ) ) ) ;
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 ) ;
2015-12-01 15:57:00 +00:00
my @ list = devspec2array ( $ arg [ 0 ] , $ cl ) ;
2009-11-25 11:13:44 +00:00
if ( $ arg [ 1 ] ) {
2013-01-03 12:50:16 +00:00
foreach my $ sdev ( @ list ) { # Show a Hash-Entry or Reading for each device
2019-10-06 12:16:21 +00:00
next if ( ! $ defs { $ sdev } ) ;
2009-11-25 11:13:44 +00:00
2016-01-21 08:28:16 +00:00
my $ first = 1 ;
foreach my $ n ( @ arg [ 1 .. @ arg - 1 ] ) {
2016-05-10 11:06:44 +00:00
my $ n = $ n ; # Forum #53223, for some perl versions $n is a reference
2016-01-21 08:28:16 +00:00
my $ fType = "" ;
if ( $ n =~ m/^(.:)(.*$)/ ) {
$ fType = $ 1 ;
$ n = $ 2 ;
}
2019-10-06 12:16:21 +00:00
if ( defined ( $ defs { $ sdev } { $ n } ) && ( ! $ fType || $ fType eq "i:" ) ) {
my $ val = $ defs { $ sdev } { $ n } ;
if ( ref ( $ val ) eq 'HASH' ) {
$ val = ( $ val - > { NAME } ? $ val - > { NAME } : # ???
join ( " " , map { "$_=$val->{$_}" } sort keys % { $ val } ) ) ;
2016-01-21 08:28:16 +00:00
}
2019-10-06 12:16:21 +00:00
$ str . = sprintf ( "%-20s %*s %*s %s\n" , ( $ first + += = 1 ) ? $ sdev: '' ,
$ arg [ 2 ] ? 19 : 0 , '' , $ arg [ 2 ] ? - 15 : 0 , $ arg [ 2 ] ? $ n: '' , $ val ) ;
} elsif ( $ defs { $ sdev } { READINGS } &&
defined ( $ defs { $ sdev } { READINGS } { $ n } )
&& ( ! $ fType || $ fType eq "r:" ) ) {
$ str . = sprintf ( "%-20s %s %*s %s\n" , ( $ first + += = 1 ) ? $ sdev: '' ,
$ defs { $ sdev } { READINGS } { $ n } { TIME } ,
$ arg [ 2 ] ? - 15 : 0 , $ arg [ 2 ] ? $ n: '' ,
$ defs { $ sdev } { READINGS } { $ n } { VAL } ) ;
} elsif ( $ attr { $ sdev } &&
defined ( $ attr { $ sdev } { $ n } )
&& ( ! $ fType || $ fType eq "a:" ) ) {
$ str . = sprintf ( "%-20s %*s %*s %s\n" , ( $ first + += = 1 ) ? $ sdev: '' ,
$ arg [ 2 ] ? 19 : 0 , '' , $ arg [ 2 ] ? - 15 : 0 , $ arg [ 2 ] ? $ n: '' ,
$ attr { $ sdev } { $ n } ) ;
2014-04-30 09:32:22 +00:00
}
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!
2022-11-01 09:31:28 +00:00
return "Can't read $file" ;
2014-04-22 19:11:59 +00:00
}
}
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 } ) ) ;
2017-08-20 11:15:22 +00:00
return "Invalid characters in name (not A-Za-z0-9._): $new"
if ( ! goodDeviceName ( $ new ) ) ;
2007-03-19 15:34:34 +00:00
return "Cannot rename global" if ( $ old eq "global" ) ;
2022-10-31 11:17:44 +00:00
return "Cannot rename $old from itself"
if ( $ cl && $ cl - > { SNAME } && $ cl - > { SNAME } eq $ old ) ;
2007-03-19 15:34:34 +00:00
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
2018-12-22 19:22:09 +00:00
for my $ d ( keys % defs ) {
my $ aw = ReadingsVal ( $ d , "associatedWith" , "" ) ;
next if ( $ aw !~ m/\b$old\b/ ) ;
$ aw =~ s/\b$old\b/$new/ ;
2019-01-22 18:29:59 +00:00
setReadingsVal ( $ defs { $ d } , "associatedWith" , $ aw , TimeNow ( ) ) if ( $ defs { $ d } ) ;
2018-12-22 19:22:09 +00:00
}
2012-10-28 21:28:41 +00:00
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
2021-03-06 11:05:44 +00:00
getAllAttr ( $; $$ )
2007-03-19 14:59:37 +00:00
{
2021-03-06 11:05:44 +00:00
my ( $ d , $ cl , $ typeHash ) = @ _ ;
2008-12-03 16:45:26 +00:00
return "" if ( ! $ defs { $ d } ) ;
2021-03-06 11:05:44 +00:00
my $ list = "" ;
my $ add = sub ( $$ )
{
my ( $ v , $ type ) = @ _ ;
return if ( ! defined ( $ v ) ) ;
$ list . = " " if ( $ list ) ;
$ list . = $ v ;
2021-03-07 09:02:55 +00:00
map { s/:.*// ;
2021-07-09 17:18:55 +00:00
$ typeHash - > { $ _ } = $ attrSource { $ _ } ? $ attrSource { $ _ } : $ type }
2021-03-07 09:02:55 +00:00
split ( " " , $ v ) if ( $ typeHash ) ;
2021-03-06 11:05:44 +00:00
} ;
2008-12-03 16:45:26 +00:00
2021-03-07 09:20:31 +00:00
& $ add ( $ AttrList , "framework" ) ;
2018-03-18 21:53:09 +00:00
if ( $ defs { $ d } { ".AttrList" } ) {
2021-12-06 18:54:51 +00:00
& $ add ( $ defs { $ d } { ".AttrList" } , "#" . $ defs { $ d } { TYPE } ) ; #124538
2021-03-06 11:05:44 +00:00
} else {
2021-12-06 18:54:51 +00:00
& $ add ( $ modules { $ defs { $ d } { TYPE } } { AttrList } , "#" . $ defs { $ d } { TYPE } ) ;
2018-03-18 21:53:09 +00:00
}
2019-04-25 06:38:44 +00:00
2021-03-06 11:05:44 +00:00
my $ nl2space = sub ( $$ )
2019-04-25 06:38:44 +00:00
{
2021-03-06 11:05:44 +00:00
my ( $ v , $ type ) = @ _ ;
2019-04-25 06:38:44 +00:00
return if ( ! defined ( $ v ) ) ;
$ v =~ s/\n/ /g ;
2021-03-06 11:05:44 +00:00
& $ add ( $ v , $ type ) ;
2019-04-25 06:38:44 +00:00
} ;
2021-03-07 09:02:55 +00:00
$ nl2space - > ( $ attr { global } { userattr } , "global userattr" ) ;
2021-03-07 09:20:31 +00:00
$ nl2space - > ( $ attr { $ d } { userattr } , "device userattr" ) if ( $ attr { $ d } ) ;
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
2018-01-06 17:20:00 +00:00
getAllGets ( $; $ )
2013-08-07 13:06:49 +00:00
{
2018-01-06 17:20:00 +00:00
my ( $ d , $ cl ) = @ _ ;
2013-08-07 13:06:49 +00:00
2018-01-06 17:20:00 +00:00
my $ a2 = CommandGet ( $ cl , "$d ?" ) ;
2013-08-07 13:06:49 +00:00
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
2018-01-06 17:20:00 +00:00
getAllSets ( $; $ )
2008-08-08 10:46:25 +00:00
{
2018-01-06 17:20:00 +00:00
my ( $ d , $ cl ) = @ _ ;
2017-01-02 19:59:04 +00:00
return "" if ( ! $ defs { $ d } ) ; # Just safeguarding
2012-02-20 12:38:48 +00:00
if ( AttrVal ( "global" , "apiversion" , 1 ) > 1 ) {
my @ setters = getSetters ( $ defs { $ d } ) ;
return join ( " " , @ setters ) ;
}
2018-01-06 17:20:00 +00:00
my $ a2 = CommandSet ( $ cl , "$d ?" ) ;
2008-08-08 10:46:25 +00:00
$ 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
2015-06-04 16:47:20 +00:00
$ a2 = $ defs { $ d } { ".eventMapCmd" } . " $a2" if ( defined ( $ defs { $ d } { ".eventMapCmd" } ) ) ;
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 ) = @ _ ;
2016-06-24 08:08:20 +00:00
if ( $ type eq "del" ) {
2022-02-18 19:18:57 +00:00
my % noDel = ( modpath = > 1 , verbose = > 1 , logfile = > 1 , configfile = > 1 , encoding = > 1 ) ;
2016-06-24 08:08:20 +00:00
return "The global attribute $name cannot be deleted" if ( $ noDel { $ name } ) ;
2024-01-28 09:11:36 +00:00
$ featurelevel = 6.3 if ( $ name eq "featurelevel" ) ;
2017-09-10 13:59:16 +00:00
$ haveInet6 = 0 if ( $ name eq "useInet6" ) ; # IPv6
2020-01-18 12:14:41 +00:00
delete ( $ defs { global } { ignoreRegexpObj } ) if ( $ name eq "ignoreRegexp" ) ;
2016-06-24 08:08:20 +00:00
return undef ;
}
2019-05-04 19:13:22 +00:00
my $ ev = $ globalAttrFromEnv - > { $ name } ;
return "$name is readonly, it is set in the FHEM_GLOBALATTR environment"
if ( defined ( $ ev ) && defined ( $ val ) && $ ev ne $ val ) ;
2022-02-14 20:39:19 +00:00
2007-03-19 14:59:37 +00:00
################
2007-12-29 15:57:42 +00:00
if ( $ name eq "logfile" ) {
2018-01-20 20:12:25 +00:00
my @ t = localtime ( gettimeofday ( ) ) ;
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 ) ;
}
}
2022-02-14 20:39:19 +00:00
if ( $ name eq "encoding" ) { # Should be called from fhem.cfg/configDB
2022-02-15 08:34:42 +00:00
return "bad encoding parameter $val, good values are bytestream or unicode"
if ( $ val ne "unicode" && $ val ne "bytestream" ) ;
2022-02-18 19:18:57 +00:00
if ( $ init_done ) {
InternalTimer ( 0 , sub {
CommandSave ( undef , undef ) ;
CommandShutdown ( undef , "restart" ) ;
} , undef ) ;
return ;
}
2022-02-14 20:39:19 +00:00
$ unicodeEncoding = ( $ val eq "unicode" ) ;
$ currlogfile = "" ;
}
2007-03-19 14:59:37 +00:00
################
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" ) ;
2020-05-21 10:06:19 +00:00
my $ modpath = $ val ;
my $ modpath_FHEM = "$modpath/FHEM" ;
my $ modpath_lib = "$modpath/lib" ;
opendir ( DH , $ modpath_FHEM ) || return "Can't read $modpath_FHEM: $!" ;
unshift @ INC , $ modpath_FHEM if ( ! grep ( /^\Q$modpath_FHEM\E$/ , @ INC ) ) ;
unshift @ INC , $ modpath_lib if ( ! grep ( /^\Q$modpath_lib\E$/ , @ INC ) ) ;
unshift @ INC , $ modpath if ( ! grep ( /^\Q$modpath\E$/ , @ INC ) ) ; #configDb
2007-01-30 12:47:36 +00:00
2015-11-29 15:06:52 +00:00
$ cvsid =~ m/(fhem.pl) (\d+) (\d+-\d+-\d+)/ ;
$ attr { global } { version } = "$1:$2/$3" ;
2007-03-19 14:59:37 +00:00
my $ counter = 0 ;
2019-04-26 08:01:47 +00:00
my $ oldVal = $ attr { global } { modpath } ;
2020-05-21 10:06:19 +00:00
$ attr { global } { modpath } = $ modpath ;
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 ) {
2019-04-26 08:01:47 +00:00
$ attr { global } { modpath } = $ oldVal ;
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
2015-06-22 18:24:59 +00:00
}
elsif ( $ name eq "featurelevel" ) {
2017-12-16 18:10:36 +00:00
return "$val is not in the form N.N" if ( $ val !~ m/^\d+\.\d+$/ ) ;
2015-06-22 18:24:59 +00:00
$ featurelevel = $ val ;
2007-01-30 12:47:36 +00:00
}
2016-08-19 12:47:50 +00:00
elsif ( $ name eq "commandref" && $ init_done ) {
my $ root = $ attr { global } { modpath } ;
2016-08-20 09:41:27 +00:00
my $ out = "" ;
$ out = ">> $currlogfile 2>&1" if ( $ currlogfile ne "-" && $^O ne "MSWin32" ) ;
2016-08-19 12:47:50 +00:00
if ( $ val eq "full" ) {
2016-08-20 09:41:27 +00:00
system ( "$^X $root/contrib/commandref_join.pl -noWarnings $out" )
2016-08-19 12:47:50 +00:00
} else {
2016-08-20 09:41:27 +00:00
system ( "$^X $root/contrib/commandref_modular.pl $out" ) ;
2016-08-19 12:47:50 +00:00
}
}
2017-08-20 14:21:58 +00:00
elsif ( $ name eq "useInet6" ) {
if ( $ val || ! defined ( $ val ) ) {
2017-08-22 17:28:46 +00:00
eval { require IO::Socket::INET6 ; require Socket6 ; } ;
2017-08-20 14:21:58 +00:00
return $@ if ( $@ ) ;
$ haveInet6 = 1 ;
} else {
$ haveInet6 = 0 ;
}
}
2019-12-25 19:17:36 +00:00
elsif ( $ name eq "ignoreRegexp" ) {
2020-01-18 12:14:41 +00:00
return "Incorrect regexp (starts with *)" if ( $ val =~ m/^\*/ ) ;
my $ reObj ;
eval { $ reObj = qr/^$val$/ ; "Hallo" =~ $ reObj ; } ;
2019-12-25 19:17:36 +00:00
return $@ if ( $@ ) ;
2020-01-18 12:14:41 +00:00
$ defs { global } { ignoreRegexpObj } = $ reObj ;
2019-12-25 19:17: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 ) = @ _ ;
2018-01-27 22:06:08 +00:00
my ( $ ret , $ append , $ remove , @ a ) ;
2021-04-16 16:32:54 +00:00
my % opt ;
my $ optRegexp = '-a|-r|-silent' ;
$ param = cmd_parseOpts ( $ param , $ optRegexp , \ % opt ) ;
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
2021-04-16 16:32:54 +00:00
return "Usage: attr [$optRegexp] <name> <attrname> [<attrvalue>]\n$namedef"
if ( @ a < 2 || ( $ opt { a } && $ opt { r } ) ) ;
2019-02-17 09:24:01 +00:00
my $ a1 = $ a [ 1 ] ;
2020-02-18 14:11:59 +00:00
return "$a[0]: bad attribute name '$a1' (allowed chars: A-Za-z/\\d_\\.-)"
2020-01-24 16:08:53 +00:00
if ( $ featurelevel > 5.9 && ! goodReadingName ( $ a1 ) && $ a1 ne "?" ) ;
2020-06-19 22:31:14 +00:00
return "attr $param: attribute value is missing" if ( $# a < 2 && $ a1 ne "?" ) ;
2007-12-29 15:57:42 +00:00
my @ rets ;
2019-02-17 09:24:01 +00:00
foreach my $ sdev ( devspec2array ( $ a [ 0 ] , $ a1 && $ a1 eq "?" ? undef : $ cl ) ) {
2007-12-29 15:57:42 +00:00
2014-10-15 17:11:07 +00:00
my $ hash = $ defs { $ sdev } ;
2019-02-17 09:24:01 +00:00
my $ attrName = $ a1 ;
2020-06-12 16:36:22 +00:00
my $ attrVal = $ a [ 2 ] ;
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 ;
}
2018-01-19 12:15:44 +00:00
$ attrName = resolveAttrRename ( $ sdev , $ attrName ) ;
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?
2016-01-26 07:27:14 +00:00
$ atr =~ /^([^;:]+)(:.*)?$/ ;
my $ base = $ 1 ;
if ( $ { attrName } =~ m/^$base$/ ) {
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. " .
2018-01-30 20:21:02 +00:00
"Type 'attr $sdev ?' for a detailed list." ;
2011-02-27 18:47:13 +00:00
next ;
}
2007-12-29 15:57:42 +00:00
}
2021-04-16 16:32:54 +00:00
if ( $ opt { a } && $ attr { $ sdev } && $ attr { $ sdev } { $ attrName } ) {
2018-01-27 22:06:08 +00:00
$ attrVal = $ attr { $ sdev } { $ attrName } .
( $ attrVal =~ m/^,/ ? $ attrVal : " $attrVal" ) ;
2020-03-30 20:37:31 +00:00
}
2021-04-16 16:32:54 +00:00
if ( $ opt { r } && $ attr { $ sdev } && $ attr { $ sdev } { $ attrName } ) {
2018-01-27 22:06:08 +00:00
my $ v = $ attr { $ sdev } { $ attrName } ;
2019-03-17 22:23:02 +00:00
$ v =~ s/\b$attrVal\b// ;
2018-01-27 22:06:08 +00:00
$ attrVal = $ v ;
}
2018-01-30 09:42:00 +00:00
if ( $ attrName eq 'disable' && $ attrVal eq 'toggle' ) {
2018-01-27 22:06:08 +00:00
$ attrVal = IsDisabled ( $ sdev ) ? 0 : 1 ;
2015-02-01 13:19:39 +00:00
}
2014-10-15 17:11:07 +00:00
if ( $ attrName eq "userReadings" ) {
2013-01-13 15:16:31 +00:00
2016-02-22 08:38:46 +00:00
my @ userReadings ;
2013-05-17 18:16:54 +00:00
# myReading1[:trigger1] [modifier1] { codecodecode1 }, ...
2018-01-27 22:06:08 +00:00
my $ arg = $ attrVal ;
2013-01-13 15:16:31 +00:00
2013-05-17 18:16:54 +00:00
# matches myReading1[:trigger2] { codecode1 }
2018-11-18 17:49:14 +00:00
my $ regexi = '\s*([\w.-]+)(:\S*)?\s+((\w+)\s+)?(\{.*?\})\s*' ;
2013-01-13 15:16:31 +00:00
my $ regexo = '^(' . $ regexi . ')(,\s*(.*))*$' ;
2016-02-21 17:28:13 +00:00
my $ rNo = 0 ;
2013-01-13 15:16:31 +00:00
2015-12-03 14:43:08 +00:00
while ( $ arg =~ /$regexo/s ) {
2016-02-22 08:38:46 +00:00
my $ reading = $ 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":"");
2015-02-03 19:37:05 +00:00
if ( grep { /$modifier/ }
qw( none difference differential offset monotonic integral ) ) {
2013-05-17 18:16:54 +00:00
$ trigger =~ s/^:// if ( $ trigger ) ;
2016-04-28 19:45:08 +00:00
my % userReading = ( reading = > $ reading ,
trigger = > $ trigger ,
modifier = > $ modifier ,
perlCode = > $ perlCode ) ;
2016-02-22 08:38:46 +00:00
push @ userReadings , \ % userReading ;
2013-02-10 09:57:02 +00:00
} else {
2013-05-17 18:16:54 +00:00
push @ rets , "$sdev: unknown modifier $modifier for " .
2016-02-22 08:38:46 +00:00
"userReading $reading, 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
}
2016-02-22 08:38:46 +00:00
$ hash - > { '.userReadings' } = \ @ userReadings ;
2018-02-04 19:52:51 +00:00
}
2013-01-13 15:16:31 +00:00
2017-04-06 12:38:59 +00:00
my $ oVal = ( $ attr { $ sdev } ? $ attr { $ sdev } { $ attrName } : "" ) ;
2015-06-04 16:47:20 +00:00
if ( $ attrName eq "eventMap" ) {
delete $ hash - > { ".eventMapHash" } ;
delete $ hash - > { ".eventMapCmd" } ;
2018-01-30 09:42:00 +00:00
$ attr { $ sdev } { eventMap } = $ attrVal ;
2015-06-04 16:47:20 +00:00
my $ r = ReplaceEventMap ( $ sdev , "test" , 1 ) ; # refresh eventMapCmd
if ( $ r =~ m/^ERROR in eventMap for / ) {
delete ( $ attr { $ sdev } { eventMap } ) ;
return $ r ;
}
}
2018-03-07 21:10:28 +00:00
if ( $ ra { $ attrName } ) {
my ( $ lval , $ rp , $ cache ) = ( $ attrVal , $ ra { $ attrName } { p } , $ ra { $ attrName } { c } ) ;
2017-05-17 10:28:41 +00:00
2019-01-01 14:41:21 +00:00
if ( $ rp && $ lval =~ m/$rp/s ) {
2018-01-27 22:06:08 +00:00
my $ err = perlSyntaxCheck ( $ attrVal , % { $ ra { $ attrName } { pv } } ) ;
2018-01-30 20:21:02 +00:00
return "attr $sdev $attrName: $err" if ( $ err ) ;
2017-05-17 10:28:41 +00:00
} else {
2018-03-07 21:10:28 +00:00
delete $ hash - > { $ cache } if ( $ cache ) ;
my @ a = split ( $ ra { $ attrName } { s } , $ lval ) ;
for my $ v ( @ a ) {
2018-03-13 21:16:06 +00:00
my $ v = $ v ; # resolve the reference to avoid changing @a itself
2022-07-17 09:47:10 +00:00
if ( $ ra { $ attrName } { isNum } ) {
my @ va = split ( ":" , $ v ) ;
return "attr $sdev $attrName $v: argument is not a number"
if ( ! defined ( $ va [ 1 ] ) || ! looks_like_number ( $ va [ 1 ] ) ) ;
}
2017-05-17 10:28:41 +00:00
$ v =~ s/$ra{$attrName}{r}// if ( $ ra { $ attrName } { r } ) ;
2018-01-30 20:21:02 +00:00
my $ err = "Argument $v for attr $sdev $attrName is not a valid regexp" ;
2017-05-17 10:28:41 +00:00
return "$err: use .* instead of *" if ( $ v =~ /^\*/ ) ; # no err in eval!?
eval { "Hallo" =~ m/^$v$/ } ;
return "$err: $@" if ( $@ ) ;
}
2018-03-07 21:10:28 +00:00
$ hash - > { $ cache } = \ @ a if ( $ cache ) ;
2017-04-24 07:44:26 +00:00
}
2016-09-21 19:02:22 +00:00
}
2017-08-01 10:01:20 +00:00
if ( $ fhemdebug && $ sdev eq "global" ) {
2018-01-27 22:06:08 +00:00
$ attrVal = "-" if ( $ attrName eq "logfile" ) ;
$ attrVal = 5 if ( $ attrName eq "verbose" ) ;
2017-08-01 10:01:20 +00:00
}
2018-11-17 22:57:02 +00:00
$ defs { $ sdev } - > { CL } = $ cl ;
2018-01-30 20:14:54 +00:00
$ ret = CallFn ( $ sdev , "AttrFn" , "set" , $ sdev , $ attrName , $ attrVal ) ;
2018-11-17 22:57:02 +00:00
delete ( $ defs { $ sdev } - > { CL } ) ;
2007-12-29 15:57:42 +00:00
if ( $ ret ) {
push @ rets , $ ret ;
next ;
}
2018-01-30 09:42:00 +00:00
$ attr { $ sdev } { $ attrName } = $ attrVal ;
2014-10-15 17:11:07 +00:00
if ( $ attrName eq "IODev" ) {
2021-04-28 16:33:04 +00:00
my $ ret = fhem_setIoDev ( $ hash , $ attrVal ) ;
if ( $ ret ) {
push @ rets , $ ret if ( $ init_done ) ;
2017-03-25 14:22:15 +00:00
next ;
}
2013-01-22 18:08:53 +00:00
}
2021-04-28 16:33:04 +00:00
2014-10-15 17:11:07 +00:00
if ( $ attrName eq "stateFormat" && $ init_done ) {
2018-01-27 22:06:08 +00:00
my $ err = perlSyntaxCheck ( $ attrVal , ( "%name" = > "" ) ) ;
2016-04-28 19:45:08 +00:00
return $ err if ( $ err ) ;
2013-01-22 18:08:53 +00:00
evalStateFormat ( $ hash ) ;
2009-11-25 10:48:01 +00:00
}
2018-02-07 13:00:16 +00:00
addStructChange ( "attr" , $ sdev , "$sdev $attrName $attrVal" )
2021-04-16 16:32:54 +00:00
if ( ! $ opt { silent } && ( ! defined ( $ oVal ) || $ oVal ne $ attrVal ) ) ;
2018-01-30 09:42:00 +00:00
DoTrigger ( "global" , "ATTR $sdev $attrName $attrVal" , 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 ) ;
2018-02-20 09:04:43 +00:00
my $ addMsg = ( $ init_done ? "" : "Bogus command was: setstate $param" ) ;
return "Usage: setstate <name> <state>\n${namedef}$addMsg" if ( @ a != 2 ) ;
2007-01-30 12:47:36 +00:00
2007-12-29 15:57:42 +00:00
my @ rets ;
2015-12-01 15:57:00 +00:00
foreach my $ sdev ( devspec2array ( $ a [ 0 ] , $ cl ) ) {
2020-11-18 18:02:38 +00:00
if ( ! defined ( $ defs { $ sdev } ) ) {
push @ rets , "Please define $sdev first" if ( $ init_done ) ; # 115934
2007-12-29 15:57:42 +00:00
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
2016-11-11 06:48:16 +00:00
if ( $ a [ 1 ] =~ m/^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) +([^ ].*)$/s ) {
2011-07-30 13:22:25 +00:00
my ( $ tim , $ nameval ) = ( $ 1 , $ 2 ) ;
my ( $ sname , $ sval ) = split ( " " , $ nameval , 2 ) ;
2015-11-02 19:34:14 +00:00
$ sval = "" if ( ! defined ( $ sval ) ) ;
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
2021-05-23 13:15:58 +00:00
if ( $ sname eq "IODev" ) {
2021-07-11 11:11:07 +00:00
next if ( ! fhem_devSupportsAttr ( $ sdev , "IODev" ) ) ;
2021-05-23 13:15:58 +00:00
my $ ret = fhem_setIoDev ( $ d , $ sval ) ;
if ( $ ret ) {
push @ rets , $ ret if ( $ init_done ) ;
next ;
}
}
2019-02-17 09:24:01 +00:00
Log3 $ d , 3 ,
2020-02-18 14:11:59 +00:00
"$sdev: bad reading name '$sname' (allowed chars: A-Za-z/\\d_\\.-)"
2017-08-19 11:19:54 +00:00
if ( ! goodReadingName ( $ sname ) ) ;
2015-12-19 16:07:11 +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 ) {
2021-07-10 10:15:17 +00:00
setReadingsVal ( $ d , $ sname , $ sval , $ tim ) ;
2007-12-29 15:57:42 +00:00
}
2021-04-28 16:33:04 +00:00
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 ( ) ;
2017-01-04 15:33:06 +00:00
$ a [ 1 ] =~ s/\\(...)/chr(oct($1))/ge if ( $ a [ 1 ] =~ m/^(\\011|\\040)+$/ ) ;
2012-07-11 10:42:38 +00:00
$ 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 ;
2015-12-01 15:57:00 +00:00
foreach my $ sdev ( devspec2array ( $ dev , $ cl ) ) {
2007-12-29 15:57:42 +00:00
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
}
#####################################
2013-07-25 07:35:49 +00:00
sub
2019-05-11 17:48:03 +00:00
sleep_WakeUpFn ( $ )
2013-07-25 07:35:49 +00:00
{
2019-05-11 17:48:03 +00:00
my $ id = shift ;
my $ h = $ sleepers { $ id } ;
return if ( ! $ h ) ;
delete $ sleepers { $ id } ;
CommandDelete ( $ h - > { cl } , $ h - > { name } ) if ( ! defined ( $ h - > { sec } ) ) ;
2015-11-28 08:02:51 +00:00
2013-07-25 07:35:49 +00:00
$ evalSpecials = $ h - > { evalSpecials } ;
2018-01-06 17:20:00 +00:00
my $ ret = AnalyzeCommandChain ( $ h - > { cl } , $ h - > { cmd } ) ;
2013-07-25 07:35:49 +00:00
Log 2 , "After sleep: $ret" if ( $ ret && ! $ h - > { quiet } ) ;
}
2019-05-11 17:48:03 +00:00
2015-11-28 08:02:51 +00:00
sub
CommandCancel ( $$ )
{
my ( $ cl , $ param ) = @ _ ;
my ( $ id , $ quiet ) = split ( " " , $ param , 3 ) ;
return "Last parameter must be quiet" if ( $ quiet && $ quiet ne "quiet" ) ;
2013-07-25 07:35:49 +00:00
2015-11-28 08:02:51 +00:00
if ( ! $ id ) {
my $ ret ;
2019-05-11 17:48:03 +00:00
foreach $ id ( sort keys % sleepers ) {
my $ h = $ sleepers { $ id } ;
2015-11-28 08:02:51 +00:00
$ ret . = "\n" if ( $ ret ) ;
2019-05-11 17:48:03 +00:00
$ ret . = sprintf ( "%-12s %-19s %s" , $ id , $ h - > { till } , $ h - > { cmd } ) ;
2015-11-28 08:02:51 +00:00
}
2019-05-11 17:48:03 +00:00
$ ret = "no pending sleeps" if ( ! $ ret ) ;
2015-11-28 08:02:51 +00:00
return $ ret ;
} elsif ( my $ h = $ sleepers { $ id } ) {
2019-05-11 17:48:03 +00:00
RemoveInternalTimer ( $ id , "sleep_WakeUpFn" ) if ( defined ( $ h - > { sec } ) ) ;
CommandDelete ( $ cl , $ h - > { name } ) if ( ! defined ( $ h - > { sec } ) ) ;
delete $ sleepers { $ id } ;
2015-11-28 08:02:51 +00:00
} else {
return "no such id: $id" if ( ! $ quiet ) ;
}
return undef ;
}
2013-07-25 07:35:49 +00:00
2007-01-30 12:47:36 +00:00
sub
CommandSleep ( $$ )
{
my ( $ cl , $ param ) = @ _ ;
2015-11-28 08:02:51 +00:00
my ( $ sec , $ id , $ quiet ) = split ( " " , $ param , 3 ) ;
if ( $ id && $ id eq 'quiet' ) {
$ quiet = $ id ;
$ id = undef ;
}
2013-07-25 07:35:49 +00:00
return "Argument missing" if ( ! defined ( $ sec ) ) ;
2015-11-28 08:02:51 +00:00
return "Last parameter must be quiet" if ( $ quiet && $ quiet ne "quiet" ) ;
2007-01-30 12:47:36 +00:00
2019-05-11 17:48:03 +00:00
my $ name = ".sleep_" . ( + + $ intAtCnt ) ;
$ id = $ name if ( ! $ id ) ;
my $ till ;
if ( $ sec !~ m/^[0-9\.]+$/ ) {
my ( $ err , $ hr , $ min , $ s , $ fn ) = GetTimeSpec ( $ sec ) ;
if ( $ err ) { # not a valid timespec => treat as regex
if ( @ cmdList && $ init_done ) {
2019-05-11 18:00:12 +00:00
CommandDelete ( $ cl , $ sleepers { $ id } { name } ) if ( $ sleepers { $ id } ) ;
2019-05-11 17:48:03 +00:00
$ err = CommandDefine ( $ cl ,
"-temporary $name notify $sec {sleep_WakeUpFn('$id')}" ) ;
$ attr { $ name } { ignore } = 1 ;
return $ err if ( $ err ) ;
}
$ till = $ sec ;
$ sec = undef ;
} else {
$ sec = 3600 * $ hr + 60 * $ min + $ s ;
}
}
$ till = gettimeofday ( ) + $ sec if ( defined ( $ sec ) ) ;
2012-03-30 07:11:39 +00:00
2017-03-04 07:53:44 +00:00
if ( @ cmdList && $ init_done ) {
2013-07-25 07:35:49 +00:00
my % h = ( cmd = > join ( ";" , @ cmdList ) ,
evalSpecials = > $ evalSpecials ,
2015-11-28 08:02:51 +00:00
quiet = > $ quiet ,
2019-05-11 17:48:03 +00:00
till = > defined ( $ sec ) ? FmtDateTime ( $ till ) : $ till ,
sec = > $ sec ,
name = > $ name ,
2018-01-06 17:20:00 +00:00
cl = > $ cl ,
2015-11-28 08:02:51 +00:00
id = > $ id ) ;
2019-05-11 17:48:03 +00:00
if ( defined ( $ sec ) ) {
RemoveInternalTimer ( $ id , "sleep_WakeUpFn" ) ;
InternalTimer ( $ till , "sleep_WakeUpFn" , $ id , 0 ) ;
2015-11-28 08:02:51 +00:00
}
2019-05-11 17:48:03 +00:00
$ sleepers { $ id } = \ % h ;
2012-03-30 07:11:39 +00:00
@ cmdList = ( ) ;
} else {
2015-07-29 05:46:10 +00:00
Log 1 ,
"WARNING: sleep without additional commands is deprecated and blocks FHEM" ;
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 ;
}
2017-10-19 14:34:34 +00:00
#####################################
# Add a function to be executed after select returns. Only one function is
# executed after select returns.
# fn: a function reference
# arg: function argument
# nice: a number like in unix "nice". Smaller numbers mean higher priority.
# limited to [-20,19], default 0
# returns the number of elements in the corrsponding queue
sub
PrioQueue_add ( $$ ; $ )
{
my ( $ fn , $ arg , $ nice ) = @ _ ;
2017-10-20 06:40:24 +00:00
$ nice = 0 if ( ! defined ( $ nice ) || ! looks_like_number ( $ nice ) ) ;
2017-10-19 14:34:34 +00:00
$ nice = - 20 if ( $ nice < - 20 ) ;
$ nice = 19 if ( $ nice > 19 ) ;
$ nextat = 1 ;
$ prioQueues { $ nice } = [] if ( ! defined $ prioQueues { $ nice } ) ;
push ( @ { $ prioQueues { $ nice } } , { fn = > $ fn , arg = > $ arg } ) ;
} ;
2013-07-13 11:56:22 +00:00
#####################################
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 ( ) ;
2016-06-25 15:26:33 +00:00
if ( $ now < $ nextat ) {
$ selectTimestamp = $ now ;
return ( $ nextat - $ now ) ;
}
2007-01-30 12:47:36 +00:00
$ nextat = 0 ;
2018-02-18 14:37:28 +00:00
while ( @ intAtA ) {
my $ at = $ intAtA [ 0 ] ;
my $ tim = $ at - > { TRIGGERTIME } ;
if ( $ tim && $ tim > $ now ) {
$ nextat = $ tim ;
last ;
2014-01-14 19:23:34 +00:00
}
2018-02-18 14:37:28 +00:00
delete $ intAt { $ at - > { atNr } } if ( $ at - > { atNr } ) ;
shift ( @ intAtA ) ;
2017-12-20 23:05:37 +00:00
2018-02-18 14:37:28 +00:00
if ( $ tim && $ at - > { FN } ) {
no strict "refs" ;
& { $ at - > { FN } } ( $ at - > { ARG } ) ;
use strict "refs" ;
}
2007-01-30 12:47:36 +00:00
}
2017-10-19 14:34:34 +00:00
if ( % prioQueues ) {
my $ nice = minNum ( keys % prioQueues ) ;
my $ entry = shift ( @ { $ prioQueues { $ nice } } ) ;
delete $ prioQueues { $ nice } if ( ! @ { $ prioQueues { $ nice } } ) ;
& { $ entry - > { fn } } ( $ entry - > { arg } ) ;
$ nextat = 1 if ( % prioQueues ) ;
}
2016-06-25 15:26:33 +00:00
if ( ! $ nextat ) {
$ selectTimestamp = $ now ;
return undef ;
}
2017-10-19 14:34:34 +00:00
$ now = gettimeofday ( ) ; # if some callbacks took longer
2016-06-25 15:26:33 +00:00
$ selectTimestamp = $ now ;
2017-12-20 23:05:37 +00:00
return ( $ now < $ nextat ) ? ( $ nextat - $ now ) : 0 ;
2007-01-30 12:47:36 +00:00
}
#####################################
sub
2016-03-05 17:02:48 +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
2017-03-04 07:53:44 +00:00
$ tim = 1 if ( ! $ tim ) ;
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 ;
}
2018-02-18 14:37:28 +00:00
2007-01-30 12:47:36 +00:00
$ nextat = $ tim if ( ! $ nextat || $ nextat > $ tim ) ;
2018-02-18 14:37:28 +00:00
my % h = ( TRIGGERTIME = > $ tim , FN = > $ fn , ARG = > $ arg , atNr = > + + $ intAtCnt ) ;
2018-05-24 09:45:32 +00:00
$ h { STACKTRACE } = stacktraceAsString ( 1 ) if ( $ addTimerStacktrace ) ;
2018-02-18 14:37:28 +00:00
$ intAt { $ h { atNr } } = \ % h ;
if ( ! @ intAtA ) {
push @ intAtA , \ % h ;
return ;
}
my $ idx = $# intAtA ; # binary insert
my ( $ lowerIdx , $ upperIdx ) = ( 0 , $ idx ) ;
while ( $ lowerIdx <= $ upperIdx ) {
$ idx = int ( ( $ upperIdx - $ lowerIdx ) / 2 ) + $ lowerIdx ;
if ( $ tim >= $ intAtA [ $ idx ] - > { TRIGGERTIME } ) {
$ lowerIdx = + + $ idx ;
} else {
$ upperIdx = $ idx - 1 ;
}
}
splice ( @ intAtA , $ idx , 0 , \ % h ) ;
2007-01-30 12:47:36 +00:00
}
2008-08-04 13:47:53 +00:00
sub
2016-03-05 15:38:39 +00:00
RemoveInternalTimer ( $; $ )
2008-08-04 13:47:53 +00:00
{
2016-03-05 15:38:39 +00:00
my ( $ arg , $ fn ) = @ _ ;
2017-12-22 09:33:24 +00:00
return if ( ! $ arg && ! $ fn ) ;
2018-02-18 14:37:28 +00:00
for ( my $ i = 0 ; $ i < @ intAtA ; $ i + + ) {
my ( $ ia , $ if ) = ( $ intAtA [ $ i ] - > { ARG } , $ intAtA [ $ i ] - > { FN } ) ;
if ( ( ! $ arg || ( $ ia && $ ia eq $ arg ) ) &&
( ! $ fn || ( $ if && $ if eq $ fn ) ) ) {
my $ t = $ intAtA [ $ i ] - > { atNr } ;
delete $ intAt { $ t } if ( $ intAt { $ t } ) ;
splice @ intAtA , $ i , 1 ;
$ i - - ;
}
2008-08-04 13:47:53 +00:00
}
}
2007-01-30 12:47:36 +00:00
#####################################
2014-10-05 07:42:43 +00:00
sub
2015-10-26 17:36:34 +00:00
stacktrace ( )
{
2014-10-05 07:42:43 +00:00
my $ i = 1 ;
my $ max_depth = 50 ;
2016-10-29 08:13:52 +00:00
# Forum #59831
2016-11-28 16:30:54 +00:00
Log 1 , "eval: $cmdFromAnalyze"
if ( $ cmdFromAnalyze && $ attr { global } { verbose } < 3 ) ;
2016-10-29 08:13:52 +00:00
Log 1 , "stacktrace:" ;
2014-10-05 07:42:43 +00:00
while ( ( my @ call_details = ( caller ( $ i + + ) ) ) && ( $ i < $ max_depth ) ) {
2016-10-29 08:13:52 +00:00
Log 1 , sprintf ( " %-35s called by %s (%s)" ,
2014-10-05 07:42:43 +00:00
$ call_details [ 3 ] , $ call_details [ 1 ] , $ call_details [ 2 ] ) ;
}
}
2018-05-24 09:45:32 +00:00
sub
stacktraceAsString ( $ )
{
my ( $ offset ) = @ _ ;
$ offset = 1 if ( ! $ offset ) ;
my ( $ max_depth , $ ret ) = ( 50 , "" ) ;
while ( ( my @ call_details = ( caller ( $ offset + + ) ) ) && ( $ offset < $ max_depth ) ) {
$ call_details [ 3 ] =~ s/main::// ;
$ ret . = sprintf ( " %s:%s" , $ call_details [ 3 ] , $ call_details [ 2 ] ) ;
}
return $ ret ;
}
2014-10-05 07:42:43 +00:00
my $ inWarnSub ;
2007-01-30 12:47:36 +00:00
sub
SignalHandling ( )
{
2009-11-28 11:14:13 +00:00
if ( $^O ne "MSWin32" ) {
2017-08-22 14:34:02 +00:00
$ SIG { TERM } = sub { $ gotSig = "TERM" ; } ;
2020-06-08 08:26:05 +00:00
$ SIG { USR1 } = sub { $ gotSig = "USR1" ; } ;
2014-10-05 07:42:43 +00:00
$ SIG { PIPE } = 'IGNORE' ;
$ SIG { CHLD } = 'IGNORE' ;
2017-08-22 14:34:02 +00:00
$ SIG { HUP } = sub { $ gotSig = "HUP" ; } ;
2014-10-05 07:42:43 +00:00
$ 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 ) ;
2017-03-14 16:01:36 +00:00
$ lastWarningMsg = $ msg ;
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" ;
2017-03-19 11:05:16 +00:00
Log 3 , "eval: $cmdFromAnalyze" if ( $ cmdFromAnalyze ) ;
2014-10-09 12:30:18 +00:00
stacktrace ( ) if ( $ attr { global } { stacktrace } &&
$ msg !~ m/ redefined at / ) ;
2014-10-05 07:42:43 +00:00
$ inWarnSub = 0 ;
} ;
2015-04-14 18:35:27 +00:00
# $SIG{__DIE__} = sub {...} #Removed. Forum #35796
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 ( )
{
2018-01-20 20:12:25 +00:00
return FmtDateTime ( gettimeofday ( ) ) ;
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 ] ) ;
}
2015-09-25 18:44:20 +00:00
sub
FmtDateTimeRFC1123 ( $ )
{
my $ t = gmtime ( shift ) ;
if ( $ t =~ m/^(...) (...) (..) (..:..:..) (....)$/ ) {
return sprintf ( "$1, %02d $2 $5 $4 GMT" , $ 3 ) ;
}
return $ t ;
}
2020-03-02 16:42:53 +00:00
sub
Logdir ( )
{
return AttrVal ( "global" , "logdir" , AttrVal ( "global" , "modpath" , "" ) . "/log" ) ;
}
2007-01-30 12:47:36 +00:00
#####################################
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 ) ;
2015-03-14 13:01:30 +00:00
return $ f if ( $ f !~ m/%/ ) ; # Be fast if there is no wildcard
2020-03-02 16:42:53 +00:00
my $ logdir = Logdir ( ) ;
2020-03-01 20:22:40 +00:00
$ f =~ s/%L/$logdir/g ;
2023-02-24 14:53:30 +00:00
my $ ret = strftime ( $ f , @ t ) ; # converts from UTF-8 to WideChar
$ ret = Encode:: encode ( "UTF-8" , $ ret ) if ( ! $ unicodeEncoding ) ;
return $ ret ;
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 ( $% )
{
2015-11-22 11:31:56 +00:00
# $NAME will be replaced with the device name which generated the event
# $EVENT will be replaced with the whole event string
# $EVTPART<N> will be replaced with single words of an event
2012-03-30 06:03:47 +00:00
my ( $ exec , % specials ) = @ _ ;
2017-02-23 20:23:38 +00:00
if ( $ specials { __UNIQUECMD__ } ) {
delete $ specials { __UNIQUECMD__ } ;
} else {
$ exec = SemicolonEscape ( $ exec ) ;
}
2012-03-30 06:03:47 +00:00
my $ idx = 0 ;
if ( defined ( $ specials { "%EVENT" } ) ) {
foreach my $ part ( split ( " " , $ specials { "%EVENT" } ) ) {
$ specials { "%EVTPART$idx" } = $ part ;
2022-09-02 09:53:39 +00:00
last if ( $ idx >= 20 ) ;
2012-03-30 06:03:47 +00:00
$ idx + + ;
}
}
2011-09-12 15:22:07 +00:00
2015-11-22 11:31:56 +00:00
if ( $ featurelevel > 5.6 ) {
$ evalSpecials = \ % specials ;
return $ exec ;
}
# featurelevel <= 5.6 only:
# 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 @@).
my $ re = join ( "|" , keys % specials ) ; # Found the $syntax, skip the rest
2013-03-24 17:47:28 +00:00
$ re =~ s/%//g ;
if ( $ exec =~ m/\$($re)\b/ ) {
$ evalSpecials = \ % specials ;
return $ exec ;
}
2015-11-22 11:31:56 +00:00
$ exec =~ s/%%/____/g ;
2013-03-24 17:47:28 +00:00
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 ) ;
}
2015-06-22 18:24:59 +00:00
2015-11-22 11:31:56 +00:00
if ( ! $ extsyntax ) {
$ exec =~ s/%/$specials{"%EVENT"}/g ;
2012-03-30 06:03:47 +00:00
}
2015-11-22 11:31:56 +00:00
$ exec =~ s/____/%/g ;
2011-01-22 21:53:18 +00:00
2015-11-22 11:31:56 +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
#####################################
2015-11-12 18:04:37 +00:00
# Parse a timespec: HH:MM:SS, HH:MM or { perfunc() }
2008-07-25 14:14:24 +00:00
sub
GetTimeSpec ( $ )
{
my ( $ tspec ) = @ _ ;
my ( $ hr , $ min , $ sec , $ fn ) ;
2015-11-07 18:39:08 +00:00
if ( $ tspec =~ m/^([0-9]+):([0-5][0-9]):([0-5][0-9])$/ ) { # HH:MM:SS
2008-07-25 14:14:24 +00:00
( $ hr , $ min , $ sec ) = ( $ 1 , $ 2 , $ 3 ) ;
2015-11-07 18:39:08 +00:00
} elsif ( $ tspec =~ m/^([0-9]+):([0-5][0-9])$/ ) { # HH:MM
2008-07-25 14:14:24 +00:00
( $ hr , $ min , $ sec ) = ( $ 1 , $ 2 , 0 ) ;
2015-11-07 18:39:08 +00:00
} elsif ( $ tspec =~ m/^{(.*)}$/ ) { # {function}
2008-07-25 14:14:24 +00:00
$ fn = $ 1 ;
2009-12-27 18:07:14 +00:00
$ tspec = AnalyzeCommand ( undef , "{$fn}" ) ;
2015-11-07 18:39:08 +00:00
$ tspec = "<empty string>" if ( ! $ tspec ) ;
my ( $ err , $ fn2 ) ;
( $ err , $ hr , $ min , $ sec , $ fn2 ) = GetTimeSpec ( $ tspec ) ;
return ( "the function \"$fn\" must return a timespec and not $tspec." ,
2020-12-02 13:12:19 +00:00
undef , undef , undef , $ tspec ) if ( $ err ) ;
2015-11-07 18:39:08 +00:00
2008-07-25 14:14:24 +00:00
} else {
return ( "Wrong timespec $tspec: either HH:MM:SS or {perlcode}" ,
2015-03-14 13:01:30 +00:00
undef , undef , undef , undef ) ;
2008-07-25 14:14:24 +00:00
}
return ( undef , $ hr , $ min , $ sec , $ fn ) ;
}
2014-04-06 06:24:47 +00:00
sub
deviceEvents ( $$ )
{
2016-03-21 09:29:52 +00:00
my ( $ hash , $ withState ) = @ _ ; # withState returns stateEvent as state:event
2014-04-06 06:24:47 +00:00
return undef if ( ! $ hash || ! $ hash - > { CHANGED } ) ;
if ( $ withState ) {
my $ cws = $ hash - > { CHANGEDWITHSTATE } ;
if ( defined ( $ cws ) ) {
if ( int ( @ { $ cws } ) == 0 ) {
2016-03-21 09:29:52 +00:00
if ( $ hash - > { READINGS } && $ hash - > { READINGS } { state } ) {
2016-04-15 16:54:11 +00:00
my $ ostate = $ hash - > { READINGS } { state } { VAL } ;
my $ mstate = ReplaceEventMap ( $ hash - > { NAME } , $ ostate , 1 ) ;
2016-08-08 14:39:02 +00:00
@ { $ cws } = map { $ _ eq $ mstate ? "state: $ostate" : $ _ }
@ { $ hash - > { CHANGED } } ;
2016-03-21 09:29:52 +00:00
} else {
@ { $ cws } = @ { $ hash - > { CHANGED } } ;
}
2014-04-06 06:24:47 +00:00
}
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 } } ) ;
2017-08-02 07:20:36 +00:00
if ( AttrVal ( $ dev , "do_not_notify" , 0 ) ) {
delete ( $ hash - > { CHANGED } ) ;
delete ( $ hash - > { CHANGETIME } ) ;
delete ( $ hash - > { CHANGEDWITHSTATE } ) ;
return "" ;
}
2015-02-03 19:23:22 +00:00
my $ now = TimeNow ( ) ;
2007-01-30 12:47:36 +00:00
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 ;
2022-05-29 12:08:22 +00:00
$ hash - > { eventCount } + + ;
2016-11-28 16:30:54 +00:00
if ( $ attr { global } { verbose } >= 5 ) {
Log 5 , "Starting notify loop for $dev, " . scalar ( @ { $ hash - > { CHANGED } } ) .
" event(s), first is " . escapeLogLine ( $ hash - > { CHANGED } - > [ 0 ] ) ;
}
2014-01-14 19:23:34 +00:00
createNtfyHash ( ) if ( ! % ntfyHash ) ;
2015-02-03 19:23:22 +00:00
$ hash - > { NTFY_TRIGGERTIME } = $ now ; # 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 } ) ;
2017-01-23 17:16:54 +00:00
Log 5 , "End notify loop for $dev" ;
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 (?)
2020-08-19 16:54:17 +00:00
my $ tn = $ now ;
if ( $ attr { global } { mseclog } ) {
my ( $ seconds , $ microseconds ) = gettimeofday ( ) ;
$ tn . = sprintf ( ".%03d" , $ microseconds / 1000 ) ;
}
my $ ct = $ hash - > { CHANGETIME } ;
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" ) ;
my $ re = $ inform { $ c } { regexp } ;
2017-09-05 09:37:33 +00:00
my $ events = deviceEvents ( $ hash , $ inform { $ c } { type } =~ m/WithState/ ) ;
$ max = int ( @ { $ events } ) ;
2012-02-27 16:59:42 +00:00
for ( my $ i = 0 ; $ i < $ max ; $ i + + ) {
2017-09-05 09:37:33 +00:00
my $ event = $ events - > [ $ i ] ;
2020-08-19 16:54:17 +00:00
my $ t = ( ( $ ct && $ ct - > [ $ i ] ) ? $ ct - > [ $ i ] : $ tn ) ;
2017-09-05 09:37:33 +00:00
next if ( $ re && ! ( $ dev =~ m/$re/ || "$dev:$event" =~ m/$re/ ) ) ;
2022-02-18 17:07:09 +00:00
my $ txt = ( $ inform { $ c } { type } eq "timer" ? "$t " : "" ) .
"$hash->{TYPE} $dev $event\n" ;
2022-03-05 10:24:57 +00:00
my $ enc = $ dc - > { encoding } &&
$ dc - > { encoding } eq "latin1" ? "Latin1" : "UTF-8" ;
2022-02-18 18:25:03 +00:00
$ txt = Encode:: encode ( $ enc , $ txt ) if ( $ unicodeEncoding ) ;
2022-02-18 17:07:09 +00:00
addToWritebuffer ( $ dc , $ txt ) ;
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)
2015-02-03 19:23:22 +00:00
$ oldvalue { $ dev } { TIME } = $ now ;
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 } ) ;
2016-01-15 08:59:32 +00:00
delete ( $ hash - > { CHANGETIME } ) ;
2014-04-06 06:24:47 +00:00
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" ;
2020-11-21 18:35:01 +00:00
stacktrace ( ) ;
2008-09-06 08:33:55 +00:00
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 } ;
2021-06-17 18:56:32 +00:00
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
}
2017-01-13 16:01:44 +00:00
#####################################
# Alternative to CallFn with optional functions in $defs, Forum #64741
sub
CallInstanceFn ( @ )
{
my $ d = shift ;
my $ n = shift ;
if ( ! $ d || ! $ defs { $ d } ) {
$ d = "<undefined>" if ( ! defined ( $ d ) ) ;
Log 0 , "Strange call for nonexistent $d: $n" ;
return undef ;
}
2017-01-13 16:08:17 +00:00
my $ fn = $ defs { $ d } { $ n } ? $ defs { $ d } { $ n } : $ defs { $ d } { ".$n" } ;
return CallFn ( $ d , $ n , @ _ ) if ( ! $ fn ) ;
2017-01-13 16:01:44 +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" ;
2016-05-29 09:13:16 +00:00
$ defs { global } { STATE } = "no definition" ;
$ 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 -" ) ;
2022-12-04 10:04:50 +00:00
$ devcountPrioSave = 2 ;
$ devcount = 30 ;
2023-11-29 12:33:32 +00:00
$ devcountTemp = 10000000 ;
2007-03-19 15:34:34 +00:00
}
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
2015-10-21 19:06:58 +00:00
myrename ( $$ $ )
2009-01-15 09:13:42 +00:00
{
2015-10-21 19:06:58 +00:00
my ( $ name , $ from , $ to ) = @ _ ;
2009-01-15 09:13:42 +00:00
2015-10-21 19:06:58 +00:00
my $ ca = AttrVal ( $ name , "archiveCompress" , 0 ) ;
if ( $ ca ) {
eval { require Compress::Zlib ; } ;
if ( $@ ) {
$ ca = 0 ;
Log 1 , $@ ;
}
}
$ to . = ".gz" if ( $ ca ) ;
2009-01-15 09:13:42 +00:00
if ( ! open ( F , $ from ) ) {
Log ( 1 , "Rename: Cannot open $from: $!" ) ;
return ;
}
if ( ! open ( T , ">$to" ) ) {
Log ( 1 , "Rename: Cannot open $to: $!" ) ;
return ;
}
2015-10-21 19:06:58 +00:00
if ( $ ca ) {
my $ d = Compress::Zlib:: deflateInit ( - WindowBits = > 31 ) ;
my $ buf ;
while ( sysread ( F , $ buf , 32768 ) > 0 ) {
syswrite ( T , $ d - > deflate ( $ buf ) ) ;
}
syswrite ( T , $ d - > flush ( ) ) ;
} else {
while ( my $ l = <F> ) {
print T $ l ;
}
2009-01-15 09:13:42 +00:00
}
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
2015-08-13 18:09:42 +00:00
HandleArchiving ( $; $ )
2007-08-06 18:17:29 +00:00
{
2015-10-21 19:06:58 +00:00
my ( $ log , $ flogInitial ) = @ _ ;
2007-08-06 18:17:29 +00:00
my $ ln = $ log - > { NAME } ;
return if ( ! $ attr { $ ln } ) ;
# If there is a command, call that
my $ cmd = $ attr { $ ln } { archivecmd } ;
if ( $ cmd ) {
2015-10-21 19:06:58 +00:00
return if ( $ flogInitial ) ; # Forum #41245
2007-08-06 18:17:29 +00:00
$ 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 ;
2023-07-11 18:30:38 +00:00
my $ clf = $ log - > { currentlogfile } ;
$ clf = $ 2 if ( $ clf =~ m , ^ ( . + ) /([^/ ] + ) $, ) ;
2018-01-20 20:12:25 +00:00
my @ t = localtime ( gettimeofday ( ) ) ;
2016-07-07 12:43:00 +00:00
$ dir = ResolveDateWildcards ( $ dir , @ t ) ;
2007-08-06 18:17:29 +00:00
return if ( ! opendir ( DH , $ dir ) ) ;
2023-07-11 18:30:38 +00:00
my @ files = sort grep { $ _ =~ m/^$file$/ && $ _ ne $ clf } readdir ( DH ) ;
2020-06-28 11:15:00 +00:00
@ files = sort { ( stat ( "$dir/$a" ) ) [ 9 ] <=> ( stat ( "$dir/$b" ) ) [ 9 ] } @ files
2017-03-01 11:27:53 +00:00
if ( AttrVal ( "global" , "archivesort" , "alphanum" ) eq "timestamp" ) ;
2007-08-06 18:17:29 +00:00
closedir ( DH ) ;
my $ max = int ( @ files ) - $ nra ;
for ( my $ i = 0 ; $ i < $ max ; $ i + + ) {
if ( $ ard ) {
Log 2 , "Moving $files[$i] to $ard" ;
2015-10-21 19:06:58 +00:00
myrename ( $ ln , "$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
2018-08-10 11:52:49 +00:00
Dispatch ( $$ ; $$ )
2009-01-09 17:31:44 +00:00
{
2018-08-10 11:52:49 +00:00
my ( $ hash , $ dmsg , $ addvals , $ nounknown ) = @ _ ;
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 } ;
2016-11-28 16:30:54 +00:00
if ( GetVerbose ( $ name ) == 5 ) {
Log3 $ hash , 5 , escapeLogLine ( "$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 ;
2016-12-04 09:32:57 +00:00
my $ parserMod = "" ;
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 } ) {
2021-01-25 09:34:42 +00:00
# The message is not for this module
next if ( $ dmsg !~ m/$modules{$m}{Match}/s ) ;
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 ;
2019-01-18 16:52:03 +00:00
my @ tfound = & { $ modules { $ m } { ParseFn } } ( $ hash , $ dmsg ) ;
2013-01-03 12:50:16 +00:00
use strict "refs" ; $ readingsUpdateDelayTrigger = 0 ;
2016-12-04 09:32:57 +00:00
$ parserMod = $ m ;
2019-01-23 21:42:13 +00:00
if ( int ( @ tfound ) && defined ( $ tfound [ 0 ] ) ) {
2019-01-18 16:52:03 +00:00
if ( $ tfound [ 0 ] && $ tfound [ 0 ] eq "[NEXT]" ) { # not a goodDeviceName, #95446
shift ( @ tfound ) ;
push @ found , @ tfound ; # continue feeding other modules
} else {
push @ found , @ tfound ;
last ;
}
}
2009-01-09 17:31:44 +00:00
}
2013-06-01 17:13:50 +00:00
2018-08-10 11:52:49 +00:00
if ( ( ! int ( @ found ) || ! defined ( $ found [ 0 ] ) ) && ! $ nounknown ) {
2017-05-13 11:58:16 +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 } ) {
2022-01-17 21:02:51 +00:00
my ( $ order , $ mname ) = split ( ":" , $ m ) ;
2022-12-18 10:35:06 +00:00
next if ( ! $ modules { $ mname } || # #130952 / FS20V
$ modules { $ mname } { LOADED } ) ; # checked in the loop above, #125292
2020-08-19 08:18:42 +00:00
if ( $ dmsg =~ m/$h->{$m}/s ) {
2018-11-07 19:02:28 +00:00
if ( AttrVal ( "global" , "autoload_undefined_devices" , 1 ) ) {
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 ;
2021-01-24 18:51:58 +00:00
my @ tfound = & { $ modules { $ mname } { ParseFn } } ( $ hash , $ dmsg ) ;
2013-01-03 12:50:16 +00:00
use strict "refs" ; $ readingsUpdateDelayTrigger = 0 ;
2016-12-04 09:32:57 +00:00
$ parserMod = $ mname ;
2022-01-20 18:25:19 +00:00
delete ( $ hash - > { ".clientArray" } ) ;
2021-01-24 18:51:58 +00:00
if ( int ( @ tfound ) && defined ( $ tfound [ 0 ] ) ) {
if ( $ tfound [ 0 ] && $ tfound [ 0 ] eq "[NEXT]" ) {
shift ( @ tfound ) ;
push @ found , @ tfound ;
} else {
push @ found , @ tfound ;
last ;
}
}
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
}
}
}
2018-08-10 11:52:49 +00:00
if ( ( ! int ( @ found ) || ! defined ( $ found [ 0 ] ) ) && ! $ nounknown ) {
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
}
}
2015-03-14 13:01:30 +00:00
# Special return: Do not notify
return undef if ( ! defined ( $ found [ 0 ] ) || $ found [ 0 ] eq "" ) ;
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" } ) ;
2016-12-04 09:32:57 +00:00
DoTrigger ( $ found , undef ) ;
2019-01-30 21:46:57 +00:00
} elsif ( defined ( $ found ) && ( $ found eq "" || $ found eq "[NEXT]" ) ) {
return undef ;
2016-12-04 09:32:57 +00:00
} else {
Log 1 , "ERROR: >$found< returned by the $parserMod ParseFn is invalid," .
2016-12-04 09:43:58 +00:00
" notify the module maintainer" ;
2016-12-04 09:32:57 +00:00
return undef ;
2009-11-14 09:20:37 +00:00
}
2009-01-09 17:31:44 +00:00
}
}
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
2021-07-09 17:18:55 +00:00
# module is the source, needed when searching for help
2010-10-24 16:08:48 +00:00
sub
2021-07-09 17:18:55 +00:00
addToDevAttrList ( $$ ; $ )
2010-10-24 16:08:48 +00:00
{
2021-07-09 17:18:55 +00:00
my ( $ dev , $ arg , $ module ) = @ _ ;
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 ) ;
2021-07-09 17:18:55 +00:00
map { s/:.*// ; $ attrSource { $ _ } = $ module ; } split ( " " , $ arg ) if ( $ module ) ;
2014-10-03 10:53:48 +00:00
}
2018-08-30 19:38:48 +00:00
# The counterpart: delete it.
sub
delFromDevAttrList ( $$ )
{
my ( $ dev , $ arg ) = @ _ ;
my $ ua = $ attr { $ dev } { userattr } ;
$ ua = "" if ( ! $ ua ) ;
my % hash = map { ( $ _ = > 1 ) }
2019-06-24 19:01:42 +00:00
grep { $ _ !~ m/^$arg(:.+)?$/ }
split ( " " , $ ua ) ;
2018-08-30 19:38:48 +00:00
$ attr { $ dev } { userattr } = join ( " " , sort keys % hash ) ;
delete $ attr { $ dev } { userattr }
if ( ! keys % hash && defined ( $ attr { $ dev } { userattr } ) ) ;
2018-08-31 05:45:01 +00:00
map { delete $ attr { $ dev } { $ _ } } split ( " " , ( split ( ":" , $ arg ) ) [ 0 ] ) ;
2018-08-30 19:38:48 +00:00
}
2014-10-03 10:53:48 +00:00
sub
2021-07-09 17:18:55 +00:00
addToAttrList ( $; $ )
2014-10-03 10:53:48 +00:00
{
2021-07-09 17:18:55 +00:00
my ( $ arg , $ module ) = @ _ ;
addToDevAttrList ( "global" , $ arg , $ module ) ;
2010-10-24 16:08:48 +00:00
}
2010-12-27 09:42:16 +00:00
2018-09-03 19:11:22 +00:00
sub
delFromAttrList ( $ )
{
delFromDevAttrList ( "global" , shift ) ;
}
2018-03-20 21:15:44 +00:00
# device specific attrList, overwrites module AttrList, user undef for $argList
# to delete it
sub
setDevAttrList ( $; $ )
{
my ( $ dev , $ argList ) = @ _ ;
return if ( ! $ defs { $ dev } ) ;
if ( defined ( $ argList ) ) {
$ defs { $ dev } { ".AttrList" } = $ argList ;
} else {
delete ( $ defs { $ dev } { ".AttrList" } ) ;
}
}
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
#######################
2018-07-30 09:46:17 +00:00
# $dir: 0: User to Device (i.e. set), $str is an array pointer
# $dir: 1: Device to Usr (i.e trigger), $str is a a string
2012-07-05 07:02:21 +00:00
sub
ReplaceEventMap ( $$ $ )
{
my ( $ dev , $ str , $ dir ) = @ _ ;
2017-12-27 19:35:48 +00:00
my $ em = AttrVal ( $ dev , "eventMap" , undef ) ;
2015-06-04 16:47:20 +00:00
2012-07-15 08:04:35 +00:00
return $ str if ( $ dir && ! $ em ) ;
2015-08-13 19:02:29 +00:00
return @ { $ str } if ( ! $ dir && ( ! $ em || int ( @ { $ str } ) < 2 ||
2015-08-18 06:07:48 +00:00
! defined ( $ str - > [ 1 ] ) || $ str - > [ 1 ] eq "?" ) ) ;
2010-12-27 09:42:16 +00:00
2015-10-26 18:16:46 +00:00
return ReplaceEventMap2 ( $ dev , $ str , $ dir , $ em ) if ( $ em =~ m/^{.*}$/s ) ;
2015-06-04 16:47:20 +00:00
my @ emList = attrSplit ( $ em ) ;
if ( ! defined $ defs { $ dev } { ".eventMapCmd" } ) {
# Delete the first word of the translation (.*:), else it will be
# interpreted as the single possible value for a dropdown
# Why is the .*= deleted?
$ defs { $ dev } { ".eventMapCmd" } = join ( " " , grep { ! / / }
2017-09-19 11:11:33 +00:00
map { $ _ =~ s/.*?=//s ; $ _ =~ s/.*?://s ;
$ _ =~ m/:/ ? $ _ : "$_:noArg" } @ emList ) ;
2015-06-04 16:47:20 +00:00
}
2017-05-13 11:58:16 +00:00
my ( $ dname , $ nstr ) ;
$ dname = shift @ { $ str } if ( ! $ dir ) ;
2017-05-12 05:48:17 +00:00
$ nstr = join ( " " , @ { $ str } ) if ( ! $ dir ) ;
2015-06-04 16:47:20 +00:00
2011-07-30 13:22:25 +00:00
my $ changed ;
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 ) ) ;
2015-06-04 16:47:20 +00:00
if ( $ dir ) { # dev -> usr
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
}
2015-06-04 16:47:20 +00:00
} else { # usr -> dev
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
2015-06-04 16:47:20 +00:00
# $dir: 0:usr,$str is array pointer, 1:dev, $str is string
2018-02-18 11:59:09 +00:00
# perl notation: { dev=>{"re1"=>"Evt1",...}, fw=>{"re1"=>"Set 1",...}}
2015-06-04 16:47:20 +00:00
sub
ReplaceEventMap2 ( $$ $ )
{
my ( $ dev , $ str , $ dir ) = @ _ ;
my $ hash = $ defs { $ dev } ;
my $ emh = $ hash - > { ".eventMapHash" } ;
if ( ! $ emh ) {
eval "\$emh = $attr{$dev}{eventMap}" ;
if ( $@ ) {
my $ msg = "ERROR in eventMap for $dev: $@" ;
Log 1 , $ msg ;
return $ msg ;
}
$ hash - > { ".eventMapHash" } = $ emh ;
$ defs { $ dev } { ".eventMapCmd" } = "" ;
if ( $ emh - > { usr } ) {
my @ cmd ;
my $ fw = $ emh - > { fw } ;
$ defs { $ dev } { ".eventMapCmd" } = join ( " " ,
map { ( $ fw && $ fw - > { $ _ } ) ? $ fw - > { $ _ } : $ _ } sort keys % { $ emh - > { usr } } ) ;
}
}
if ( $ dir == 1 ) {
$ emh = $ emh - > { dev } ;
if ( $ emh ) {
foreach my $ k ( keys % { $ emh } ) {
return $ emh - > { $ k } if ( $ str eq $ k ) ;
return eval '"' . $ emh - > { $ k } . '"' if ( $ str =~ m/$k/ ) ;
}
}
return $ str ;
}
$ emh = $ emh - > { usr } ;
return @ { $ str } if ( ! $ emh ) ;
my $ dname = shift @ { $ str } ;
my $ nstr = join ( " " , @ { $ str } ) ;
foreach my $ k ( keys % { $ emh } ) {
my $ nv ;
if ( $ nstr eq $ k ) {
$ nv = $ emh - > { $ k } ;
} elsif ( $ nstr =~ m/$k/ ) {
2016-02-08 12:11:51 +00:00
my $ NAME = $ dev ; # Compatibility, Forum #43023
2015-06-04 16:47:20 +00:00
$ nv = eval '"' . $ emh - > { $ k } . '"' ;
}
2016-03-12 13:19:20 +00:00
if ( defined ( $ nv ) ) {
2015-06-04 16:47:20 +00:00
my @ arr = split ( " " , $ nv ) ;
unshift @ arr , $ dname ;
return @ arr ;
}
}
unshift @ { $ str } , $ dname ;
return @ { $ str } ;
}
2019-04-26 08:01:47 +00:00
# Needed for logfile/pid/nofork
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+(.*)$/ ) ;
2019-04-26 08:01:47 +00:00
AnalyzeCommand ( undef , $ l ) ;
2011-01-29 12:07:14 +00:00
}
2020-05-13 10:27:43 +00:00
CommandAttr ( undef , "global modpath ." ) if ( ! AttrVal ( "global" , "modpath" , "" ) ) ;
2011-01-29 12:07:14 +00:00
}
2012-02-11 23:42:47 +00:00
2018-01-19 12:15:44 +00:00
sub
resolveAttrRename ( $$ )
{
my ( $ d , $ n ) = @ _ ;
return $ n if ( ! $ d || ! $ defs { $ d } ) ;
my $ m = $ modules { $ defs { $ d } { TYPE } } ;
if ( $ m - > { AttrRenameMap } && defined ( $ m - > { AttrRenameMap } { $ n } ) ) {
Log 3 , "WARNING: $d attribute $n was renamed to " . $ m - > { AttrRenameMap } { $ n } ;
return $ m - > { AttrRenameMap } { $ n } ;
}
return $ n ;
}
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
2024-03-02 10:05:48 +00:00
sub
numberFromString ( $$ ; $ )
{
my ( $ val , $ default , $ round ) = @ _ ;
return undef if ( ! defined ( $ val ) ) ;
# 137283 & perl cookbook
$ val = ( $ val =~ /(([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?)/ ? $ 1 : "" ) ;
$ val =~ s/^([+-]?)0+([1-9])/$1$2/ ; # Forum #135120, dont want octal numbers
return $ default if ( $ val eq "" ) ;
$ val = round ( $ val , $ round ) if ( defined $ round ) ;
return $ val ;
}
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 ;
}
2017-04-05 11:36:44 +00:00
sub
InternalNum ( $$ $; $ )
{
my ( $ d , $ n , $ default , $ round ) = @ _ ;
2024-03-02 10:05:48 +00:00
return numberFromString ( InternalVal ( $ d , $ n , $ default ) , $ default , $ round ) ;
2017-04-05 11:36:44 +00:00
}
2018-03-07 21:10:28 +00:00
sub
OldReadingsVal ( $$ $ )
{
my ( $ d , $ n , $ default ) = @ _ ;
if ( defined ( $ defs { $ d } ) &&
defined ( $ defs { $ d } { OLDREADINGS } ) &&
defined ( $ defs { $ d } { OLDREADINGS } { $ n } ) &&
defined ( $ defs { $ d } { OLDREADINGS } { $ n } { VAL } ) ) {
return $ defs { $ d } { OLDREADINGS } { $ n } { VAL } ;
}
return $ default ;
}
sub
OldReadingsNum ( $$ $; $ )
{
my ( $ d , $ n , $ default , $ round ) = @ _ ;
2024-03-02 10:05:48 +00:00
return numberFromString ( OldReadingsVal ( $ d , $ n , $ default ) , $ default , $ round ) ;
2018-03-07 21:10:28 +00:00
}
sub
OldReadingsTimestamp ( $$ $ )
{
my ( $ d , $ n , $ default ) = @ _ ;
if ( defined ( $ defs { $ d } ) &&
defined ( $ defs { $ d } { OLDREADINGS } ) &&
defined ( $ defs { $ d } { OLDREADINGS } { $ n } ) &&
defined ( $ defs { $ d } { OLDREADINGS } { $ n } { TIME } ) ) {
return $ defs { $ d } { OLDREADINGS } { $ n } { TIME } ;
}
return $ default ;
}
sub
OldReadingsAge ( $$ $ )
{
my ( $ device , $ reading , $ default ) = @ _ ;
my $ ts = OldReadingsTimestamp ( $ device , $ reading , undef ) ;
return int ( gettimeofday ( ) - time_str2num ( $ ts ) ) if ( defined ( $ ts ) ) ;
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
2017-04-05 11:36:44 +00:00
ReadingsNum ( $$ $; $ )
2014-10-18 05:25:57 +00:00
{
2017-04-05 11:36:44 +00:00
my ( $ d , $ n , $ default , $ round ) = @ _ ;
2024-03-02 10:05:48 +00:00
return numberFromString ( ReadingsVal ( $ d , $ n , $ default ) , $ default , $ round ) ;
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 ;
}
2016-03-13 20:05:32 +00:00
sub
ReadingsAge ( $$ $ )
{
my ( $ device , $ reading , $ default ) = @ _ ;
2016-03-13 20:20:18 +00:00
my $ ts = ReadingsTimestamp ( $ device , $ reading , undef ) ;
2018-01-20 20:12:25 +00:00
return int ( gettimeofday ( ) - time_str2num ( $ ts ) ) if ( defined ( $ ts ) ) ;
2016-03-13 20:20:18 +00:00
return $ default ;
2016-03-13 20:05:32 +00:00
}
2012-02-14 08:13:08 +00:00
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 ) = @ _ ;
2018-01-19 12:15:44 +00:00
$ n = resolveAttrRename ( $ d , $ n ) ;
2017-04-11 09:35:45 +00:00
return $ attr { $ d } { $ n } if ( defined ( $ attr { $ d } ) && defined ( $ attr { $ d } { $ n } ) ) ;
2012-02-14 08:13:08 +00:00
return $ default ;
}
2017-04-05 11:36:44 +00:00
sub
AttrNum ( $$ $; $ )
{
my ( $ d , $ n , $ default , $ round ) = @ _ ;
my $ val = AttrVal ( $ d , $ n , $ default ) ;
2017-04-14 15:01:35 +00:00
return undef if ( ! defined ( $ val ) ) ;
2017-04-05 11:36:44 +00:00
$ val = ( $ val =~ /(-?\d+(\.\d+)?)/ ? $ 1 : "" ) ;
$ val = round ( $ val , $ round ) if ( $ round ) ;
return $ val ;
}
2021-07-11 11:11:07 +00:00
sub
fhem_devSupportsAttr ( $$ )
{
my ( $ devName , $ attrName ) = @ _ ;
my $ attrList = getAllAttr ( $ devName ) ;
return ( " $attrList " =~ m/ $attrName[ :;]/ ) ;
}
2012-02-14 08:13:08 +00:00
################################################################
# Functions used by modules.
sub
setReadingsVal ( $$ $$ )
{
my ( $ hash , $ rname , $ val , $ ts ) = @ _ ;
2018-03-07 21:10:28 +00:00
2021-07-11 11:11:07 +00:00
return if ( $ rname eq "IODev" && ! fhem_devSupportsAttr ( $ hash - > { NAME } , "IODev" ) ) ;
2021-07-10 10:29:50 +00:00
2023-04-30 08:50:41 +00:00
my $ or = $ hash - > { ".or" } ;
if ( $ or && grep ( $ rname =~ m/^$_$/ , @ { $ or } ) ) {
my $ rd = $ hash - > { READINGS } ;
if ( defined ( $ rd - > { $ rname } ) &&
defined ( $ rd - > { $ rname } { VAL } ) &&
( $ or - > [ @ { $ or } - 1 ] eq "oldreadingsAlways" ||
$ rd - > { $ rname } { VAL } ne $ val ) ) {
$ hash - > { OLDREADINGS } { $ rname } { VAL } = $ rd - > { $ rname } { VAL } ;
$ hash - > { OLDREADINGS } { $ rname } { TIME } = $ rd - > { $ rname } { TIME } ;
2018-03-07 21:10:28 +00:00
}
}
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
2020-08-19 16:54:17 +00:00
addEvent ( $$ ; $ )
2012-02-14 08:13:08 +00:00
{
2020-08-19 16:54:17 +00:00
my ( $ hash , $ event , $ timestamp ) = @ _ ;
2012-02-14 08:13:08 +00:00
push ( @ { $ hash - > { CHANGED } } , $ event ) ;
2020-08-19 16:54:17 +00:00
if ( $ timestamp ) {
$ hash - > { CHANGETIME } = [] if ( ! defined ( $ hash - > { CHANGETIME } ) ) ;
$ hash - > { CHANGETIME } - > [ @ { $ hash - > { CHANGED } } - 1 ] = $ timestamp ;
}
2012-02-14 08:13:08 +00:00
}
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
2015-12-19 16:07:11 +00:00
if ( ! $ name ) {
Log 1 , "ERROR: empty name in readingsBeginUpdate" ;
stacktrace ( ) ;
return ;
}
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 ;
2020-03-31 18:38:33 +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 $ st = $ hash - > { READINGS } { state } ;
2018-12-16 22:17:56 +00:00
if ( $ hash - > { skipStateFormat } && defined ( $ st ) ) {
$ hash - > { STATE } = ReplaceEventMap ( $ name , $ st - > { VAL } , 1 ) ;
return ;
}
my $ sr = AttrVal ( $ name , "stateFormat" , undef ) ;
2013-01-22 18:08:53 +00:00
if ( ! $ sr ) {
$ st = $ st - > { VAL } if ( defined ( $ st ) ) ;
2016-04-28 19:45:08 +00:00
} elsif ( $ sr =~ m/^{(.*)}$/s ) {
2020-09-24 15:59:10 +00:00
$ cmdFromAnalyze = $ 1 ;
2013-01-22 18:08:53 +00:00
$ st = eval $ 1 ;
if ( $@ ) {
$ st = "Error evaluating $name stateFormat: $@" ;
Log 1 , $ st ;
}
2020-09-24 15:59:10 +00:00
$ cmdFromAnalyze = undef ;
2013-01-22 18:08:53 +00:00
} else {
# Substitute reading names with their values, leave the rest untouched.
$ st = $ sr ;
my $ r = $ hash - > { READINGS } ;
2017-04-07 10:51:47 +00:00
$ st =~ s/\$name/$name/g ;
( undef , $ st ) = ReplaceSetMagic ( $ hash , 1 , $ st ) ;
$ st =~ s/\b([A-Za-z\d_\.-]+)\b/($r->{$1} ? $r->{$1}{VAL} : $1)/ge
if ( $ st eq $ sr ) ;
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' } ) ) {
2016-02-22 08:38:46 +00:00
foreach my $ userReading ( @ { $ hash - > { '.userReadings' } } ) {
2013-05-17 18:16:54 +00:00
2016-02-22 08:38:46 +00:00
my $ trigger = $ userReading - > { trigger } ;
2017-02-27 12:34:57 +00:00
my $ reading = $ userReading - > { reading } ;
2018-04-13 19:53:08 +00:00
my ( $ event , $ eventName , $ eventValue , $ ownRead ) ;
2013-05-17 18:16:54 +00:00
if ( defined ( $ trigger ) ) {
2020-10-26 18:39:12 +00:00
map { $ event = $ _ if ( defined ( $ _ ) && $ _ =~ m/^$trigger$/ ) ;
$ ownRead = 1 if ( defined ( $ _ ) && $ _ =~ m/^$reading:/ ) ; }
@ { $ hash - > { CHANGED } } ;
next if ( ! defined ( $ event ) || $ ownRead ) ;
2018-04-13 19:53:08 +00:00
( $ eventName , $ eventValue ) = ( $ 1 , $ 2 ) if ( $ event =~ m/^([^:]*): (.*)$/ ) ;
2013-05-17 18:16:54 +00:00
}
2016-02-22 08:38:46 +00:00
my $ modifier = $ userReading - > { modifier } ;
my $ perlCode = $ userReading - > { perlCode } ;
my $ oldvalue = $ userReading - > { value } ;
my $ oldt = $ userReading - > { t } ;
#Debug "Evaluating " . $reading;
2015-01-17 21:37:05 +00:00
$ cmdFromAnalyze = $ perlCode ; # For the __WARN__ sub
2016-05-06 06:25:13 +00:00
my $ NAME = $ name ; # no exceptions, #53069
2024-05-07 08:54:34 +00:00
my $ stopRecursion = ".evalUserReading_$reading" ;
next if ( $ hash - > { $ stopRecursion } ) ; # No warning / #138149
$ hash - > { $ stopRecursion } = 1 ;
2013-02-10 09:57:02 +00:00
my $ value = eval $ perlCode ;
2024-05-07 08:54:34 +00:00
delete ( $ hash - > { $ stopRecursion } ) ;
2015-01-17 21:37:05 +00:00
$ cmdFromAnalyze = undef ;
2024-05-07 08:54:34 +00:00
2013-02-10 09:57:02 +00:00
my $ result ;
# store result
2013-01-13 15:16:31 +00:00
if ( $@ ) {
2016-02-22 08:38:46 +00:00
$ value = "Error evaluating $name userReading $reading: $@" ;
2013-01-13 15:16:31 +00:00
Log 1 , $ value ;
2013-02-10 09:57:02 +00:00
$ result = $ value ;
2017-04-24 11:45:05 +00:00
} elsif ( ! defined ( $ value ) ) {
2019-08-08 09:21:00 +00:00
if ( AttrVal ( "global" , "verbose" , 3 ) >= 5 ) { #102868
$ cmdFromAnalyze = $ perlCode ; # For the __WARN__ sub
warn ( "$name userReadings $reading evaluated to undef" ) ;
}
2017-04-24 11:45:05 +00:00
next ;
2013-02-10 09:57:02 +00:00
} elsif ( $ modifier eq "none" ) {
$ result = $ value ;
} elsif ( $ modifier eq "difference" ) {
$ result = $ value - $ oldvalue if ( defined ( $ oldvalue ) ) ;
} elsif ( $ modifier eq "differential" ) {
2017-05-13 11:58:16 +00:00
my ( $ deltav , $ deltat ) ;
$ deltav = $ value - $ oldvalue if ( defined ( $ oldvalue ) ) ;
$ deltat = $ hash - > { ".updateTime" } - $ oldt if ( defined ( $ oldt ) ) ;
2013-02-10 09:57:02 +00:00
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 ) ) {
2017-05-13 11:58:16 +00:00
my $ deltat ;
$ deltat = $ hash - > { ".updateTime" } - $ oldt if ( defined ( $ oldt ) ) ;
2014-08-19 20:55:00 +00:00
my $ avgval = ( $ value + $ oldvalue ) / 2 ;
2016-02-22 08:38:46 +00:00
$ result = ReadingsVal ( $ name , $ reading , $ value ) ;
2014-08-19 20:55:00 +00:00
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 ) ) ;
2016-02-22 08:38:46 +00:00
$ result = ReadingsVal ( $ name , $ reading , 0 ) ;
2013-07-03 19:09:06 +00:00
$ result += $ oldvalue if ( $ value < $ oldvalue ) ;
} elsif ( $ modifier eq "monotonic" ) {
2013-09-07 11:58:33 +00:00
$ oldvalue = $ value if ( ! defined ( $ oldvalue ) ) ;
2016-02-22 08:38:46 +00:00
$ result = ReadingsVal ( $ name , $ reading , $ value ) ;
2013-07-03 19:09:06 +00:00
$ result += $ value - $ oldvalue if ( $ value > $ oldvalue ) ;
2013-02-10 09:57:02 +00:00
}
2016-02-22 08:38:46 +00:00
readingsBulkUpdate ( $ hash , $ reading , $ result , 1 ) if ( defined ( $ result ) ) ;
2013-02-10 09:57:02 +00:00
# store value
2016-02-22 08:38:46 +00:00
$ userReading - > { TIME } = $ hash - > { ".updateTimestamp" } ;
$ userReading - > { t } = $ hash - > { ".updateTime" } ;
$ 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
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 ;
}
2016-10-10 17:48:19 +00:00
sub
readingsBulkUpdateIfChanged ( $$ $@ ) # Forum #58797
{
my ( $ hash , $ reading , $ value , $ changed ) = @ _ ;
return undef if ( $ value eq ReadingsVal ( $ hash - > { NAME } , $ reading , "" ) ) ;
return readingsBulkUpdate ( $ hash , $ reading , $ value , $ changed ) ;
}
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);
2016-10-10 17:48:19 +00:00
# Optional parameter $changed: if defined, and is 0, do not trigger events. If
# 1, trigger. If not defined, the name of the reading decides (starting with .
# is 0, else 1). The event-on-* filtering is done additionally.
2012-02-11 23:42:47 +00:00
#
sub
2013-01-03 12:50:16 +00:00
readingsBulkUpdate ( $$ $@ )
{
2020-08-19 16:54:17 +00:00
my ( $ hash , $ reading , $ value , $ changed , $ timestamp ) = @ _ ;
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." ;
2017-01-17 09:38:18 +00:00
stacktrace ( ) ;
2013-01-03 12:50:16 +00:00
return ;
}
2016-09-21 19:02:22 +00:00
my $ sp = AttrVal ( $ name , "suppressReading" , undef ) ;
return if ( $ sp && $ reading =~ m/^$sp$/ ) ;
2012-02-11 23:51:49 +00:00
# shorthand
2016-04-25 08:13:57 +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" } ) ;
2016-04-25 08:13:57 +00:00
# if reading does not exist yet: fake entry to allow filtering
$ readings = { VAL = > "" } if ( ! defined ( $ readings ) ) ;
my $ update_timestamp = 1 ;
if ( $ changed ) {
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
2015-09-22 07:24:46 +00:00
my $ eocr = $ attreocr &&
( my @ eocrv = grep { my $ l = $ _ ; $ l =~ s/:.*// ;
( $ reading =~ m/^$l$/ ) ? $ _ : undef } @ { $ attreocr } ) ;
2014-06-07 16:12:09 +00:00
my $ eour = $ attreour && grep ( $ reading =~ m/^$_$/ , @ { $ attreour } ) ;
# check if threshold is given
2015-02-15 13:10:48 +00:00
my $ eocrExists = $ eocr ;
2014-06-07 16:12:09 +00:00
if ( $ eocr
&& $ eocrv [ 0 ] =~ m/.*:(.*)/ ) {
2014-11-03 22:25:33 +00:00
my $ threshold = $ 1 ;
2016-12-17 18:37:11 +00:00
if ( $ value =~ m/([\d\.\-eE]+)/ && looks_like_number ( $ 1 ) ) { #41083, #62190
my $ mv = $ 1 ;
2015-09-22 07:24:46 +00:00
my $ last_value = $ hash - > { ".attreocr-threshold$reading" } ;
if ( ! defined ( $ last_value ) ) {
2016-12-17 18:37:11 +00:00
$ hash - > { ".attreocr-threshold$reading" } = $ mv ;
} elsif ( abs ( $ mv - $ last_value ) < $ threshold ) {
2015-09-22 07:24:46 +00:00
$ eocr = 0 ;
} else {
2016-12-17 18:37:11 +00:00
$ hash - > { ".attreocr-threshold$reading" } = $ mv ;
2015-09-22 07:24:46 +00:00
}
2014-06-07 16:12:09 +00:00
}
}
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 ;
2015-02-15 13:10:48 +00:00
$ changed = 1 if ( $ eocrExists ) ;
2013-03-01 11:09:18 +00:00
}
}
2016-04-25 08:13:57 +00:00
if ( $ attreocr ) {
if ( my $ attrtocr = $ hash - > { ".attrtocr" } ) {
2016-06-13 16:52:23 +00:00
$ update_timestamp = $ changed
if ( $ attrtocr && grep ( $ reading =~ m/^$_$/ , @ { $ attrtocr } ) ) ;
2016-04-25 08:13:57 +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 = $ _ ;
2015-03-14 13:01:30 +00:00
$ l =~ s/:.*// ;
( $ reading =~ m/^$l$/ ) ? $ _ : undef } @ { $ hash - > { ".attraggr" } } ;
2015-01-25 15:24:49 +00:00
if ( @ v ) {
# e.g. power:20:linear:avg
2016-06-13 16:52:23 +00:00
my ( undef , $ duration , $ method , $ function , $ holdTime ) = split ( ":" , $ v [ 0 ] , 5 ) ;
2015-01-25 15:24:49 +00:00
my $ ts ;
if ( defined ( $ readings - > { ".ts" } ) ) {
2015-03-14 13:01:30 +00:00
$ ts = $ readings - > { ".ts" } ;
2015-01-25 15:24:49 +00:00
} else {
2015-03-14 13:01:30 +00:00
require "TimeSeries.pm" ;
2019-12-03 08:39:54 +00:00
$ ts = TimeSeries - > new ( { method = > $ method ,
2020-01-24 17:56:00 +00:00
autoreset = > $ duration ,
holdTime = > $ holdTime } ) ;
2015-03-14 13:01:30 +00:00
$ readings - > { ".ts" } = $ ts ;
# access from command line:
2015-01-25 15:24:49 +00:00
# { $defs{"myClient"}{READINGS}{"myValue"}{".ts"}{max} }
2015-03-14 13:01:30 +00:00
#Debug "TimeSeries created.";
2015-01-25 15:24:49 +00:00
}
my $ now = $ hash - > { ".updateTime" } ;
2015-03-29 10:49:31 +00:00
my $ val = $ value ; # save value
$ changed = $ ts - > elapsed ( $ now ) ;
$ value = $ ts - > { $ function } if ( $ changed ) ;
$ ts - > add ( $ now , $ val ) ;
2015-01-25 15:24:49 +00:00
} else {
# If no event-aggregator attribute, then remove stale series if any.
delete $ readings - > { ".ts" } ;
}
2016-01-06 16:53:06 +00:00
}
2015-01-25 15:24:49 +00:00
2020-08-19 16:54:17 +00:00
setReadingsVal ( $ hash , $ reading , $ value ,
$ timestamp ? $ timestamp : $ hash - > { ".updateTimestamp" } )
2016-06-13 16:52:23 +00:00
if ( $ update_timestamp ) ;
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" ) {
2015-07-12 06:11:14 +00:00
$ rv = $ value ;
2014-04-06 06:24:47 +00:00
$ hash - > { CHANGEDWITHSTATE } = [] ;
}
2020-08-19 16:54:17 +00:00
addEvent ( $ hash , $ rv , $ timestamp ) ;
2013-01-03 12:50:16 +00:00
}
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
2020-08-19 16:54:17 +00:00
readingsSingleUpdate ( $$ $$ ; $ )
2013-01-03 12:50:16 +00:00
{
2020-08-19 16:54:17 +00:00
my ( $ hash , $ reading , $ value , $ dotrigger , $ timestamp ) = @ _ ;
2012-11-08 20:56:21 +00:00
readingsBeginUpdate ( $ hash ) ;
2020-08-19 16:54:17 +00:00
my $ rv = readingsBulkUpdate ( $ hash , $ reading , $ value , undef , $ timestamp ) ;
2012-11-08 20:56:21 +00:00
readingsEndUpdate ( $ hash , $ dotrigger ) ;
return $ rv ;
}
2018-01-21 11:22:35 +00:00
sub
readingsDelete ( $$ )
{
my ( $ hash , $ reading ) = @ _ ;
delete $ hash - > { READINGS } { $ reading } ;
2018-03-07 21:10:28 +00:00
delete $ hash - > { OLDREADINGS } { $ reading } ;
2018-01-21 11:22:35 +00:00
}
2013-01-03 12:50:16 +00:00
##############################################################################
2012-06-17 14:31:17 +00:00
#
# date and time routines
#
##############################################################################
sub
2015-03-22 13:58:15 +00:00
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 ) ;
2012-06-17 14:31:17 +00:00
2015-03-22 13:58:15 +00:00
# the offset is positive if the local timezone is ahead of GMT, e.g. we get
# 2*3600 seconds for CET DST vs GMT
2015-03-29 10:38:39 +00:00
return 60 * ( ( $ l [ 2 ] - $ g [ 2 ] +
( ( ( $ l [ 5 ] << 9 ) | $ l [ 7 ] ) <=> ( ( $ g [ 5 ] << 9 ) | $ g [ 7 ] ) ) * 24 ) * 60 +
$ l [ 1 ] - $ g [ 1 ] ) ;
2012-06-17 14:31:17 +00:00
}
sub
2015-06-28 15:03:28 +00:00
fhemTimeGm ( $$ $$ $$ )
{
# see http://de.wikipedia.org/wiki/Unixzeit
my ( $ sec , $ min , $ hour , $ mday , $ month , $ year ) = @ _ ;
2012-06-17 14:31:17 +00:00
2015-06-28 15:03:28 +00:00
# $mday= 1..
# $month= 0..11
# $year is year-1900
$ year += 1900 ;
my $ isleapyear = $ year % 4 ? 0 : $ year % 100 ? 1 : $ year % 400 ? 0 : 1 ;
# Forum #38610
my $ leapyears_date = int ( ( $ year - 1 ) /4) -int(($year-1)/ 100 ) + int ( ( $ year - 1 ) / 400 ) ;
my $ leapyears_1970 = int ( ( 1970 - 1 ) /4) -int((1970 -1)/ 100 ) + int ( ( 1970 - 1 ) / 400 ) ;
my $ leapyears = $ leapyears_date - $ leapyears_1970 ;
if ( $^O eq 'MacOS' ) {
$ year -= 1904 ;
} else {
$ year -= 1970 ; # the Unix Epoch
}
2012-06-17 14:31:17 +00:00
2015-06-28 15:03:28 +00:00
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 ) ) ) ;
2012-06-17 14:31:17 +00:00
}
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 = ( ) ;
2021-01-24 18:51:58 +00:00
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
2021-01-24 18:51:58 +00:00
if ( $ hash - > { ClientsKeepOrder } ) {
@ a = grep { $ modules { $ _ } && $ modules { $ _ } { Match } } @ mRe ;
} else {
2022-01-20 16:06:47 +00:00
my @ cmRe = map { qr/^$_$/ } @ mRe ; # 125292, precompile, speedup 5x for CUL
2021-01-24 18:51:58 +00:00
foreach my $ m ( sort { $ modules { $ a } { ORDER } . $ a cmp $ modules { $ b } { ORDER } . $ b }
grep { defined ( $ modules { $ _ } { ORDER } ) } keys % modules ) {
2022-01-20 16:06:47 +00:00
foreach my $ re ( @ cmRe ) {
if ( $ m =~ $ re ) {
2021-01-24 18:51:58 +00:00
push @ a , $ m if ( $ modules { $ m } { Match } ) ;
last ;
}
2013-06-01 17:13:50 +00:00
}
}
}
$ 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
2021-02-22 13:30:08 +00:00
sub
escapeLogLine ( $ ) {
2014-03-10 09:53:52 +00:00
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 ;
}
2017-05-22 20:25:06 +00:00
sub
toJSON ( $ )
{
my $ val = shift ;
if ( not defined $ val ) {
return "null" ;
2019-05-29 16:35:33 +00:00
} elsif ( length ( do { no warnings "numeric" ; $ val & "" } ) ) {
return $ val ;
2017-05-22 20:25:06 +00:00
} elsif ( not ref $ val ) {
$ val =~ s/([\x00-\x1f\x22\x5c\x7f])/sprintf '\u%04x', ord($1)/ge ;
return '"' . $ val . '"' ;
} elsif ( ref $ val eq 'ARRAY' ) {
return '[' . join ( ',' , map toJSON ( $ _ ) , @$ val ) . ']' ;
} elsif ( ref $ val eq 'HASH' ) {
return '{' . join ( ',' ,
map { toJSON ( $ _ ) . ":" . toJSON ( $ val - > { $ _ } ) }
sort keys %$ val ) . '}' ;
} else {
return toJSON ( "toJSON: Cannot encode $val" ) ;
}
}
2018-08-14 20:14:09 +00:00
#############################
2018-12-19 10:45:16 +00:00
# will return a hash of name:value pairs. in is a json_string, prefix will be
# prepended to each name, map is a hash for mapping the names
2018-08-14 20:14:09 +00:00
sub
2021-07-13 15:16:07 +00:00
json2nameValue ( $; $$ $$ )
2018-08-14 20:14:09 +00:00
{
2021-07-13 15:16:07 +00:00
my ( $ in , $ prefix , $ map , $ filter , $ negFilter ) = @ _ ;
2021-07-19 13:16:44 +00:00
return if ( ! $ in ) ; # 122048
2018-08-14 20:14:09 +00:00
$ prefix = "" if ( ! defined ( $ prefix ) ) ;
my % ret ;
sub
2018-08-25 14:04:36 +00:00
lStr ( $ ) # extract a string
2018-08-14 20:14:09 +00:00
{
my ( $ t ) = @ _ ;
my $ esc ;
for ( my $ off = 1 ; $ off < length ( $ t ) ; $ off + + ) {
my $ s = substr ( $ t , $ off , 1 ) ;
if ( $ s eq '\\' ) {
$ esc = ! $ esc ;
} elsif ( $ s eq '"' && ! $ esc ) {
2020-05-31 09:33:42 +00:00
my $ val = substr ( $ t , 1 , $ off - 1 ) ;
2022-09-02 09:53:39 +00:00
if ( $ val =~ m/\\u([0-9A-F]{4})/i ) {
$ val =~ s/\\u([0-9A-F]{4})/chr(hex($1))/gsie ; # toJSON reverse
$ val = Encode:: encode ( "UTF-8" , $ val ) if ( ! $ unicodeEncoding ) ; #128932
}
2020-05-31 09:33:42 +00:00
my % t = ( n = > "\n" , '"' = > '"' , '\\' = > '\\' ) ;
$ val =~ s/\\([n"\\])/$t{$1}/ge ;
return ( undef , $ val , substr ( $ t , $ off + 1 ) ) ;
2018-08-14 20:14:09 +00:00
} else {
$ esc = 0 ;
}
}
2018-12-20 11:47:38 +00:00
return ( 'json2nameValue: no closing " found' , "" , "" ) ;
2018-08-14 20:14:09 +00:00
}
sub
2018-08-25 14:04:36 +00:00
lObj ( $$ $ ) # extract one object: {} or []
2018-08-14 20:14:09 +00:00
{
2018-08-25 14:04:36 +00:00
my ( $ t , $ oc , $ cc ) = @ _ ;
2018-08-14 20:14:09 +00:00
my $ depth = 1 ;
my ( $ esc , $ inquote ) ;
2018-12-20 11:47:38 +00:00
$ inquote = 0 ;
2018-08-14 20:14:09 +00:00
for ( my $ off = 1 ; $ off < length ( $ t ) ; $ off + + ) {
my $ s = substr ( $ t , $ off , 1 ) ;
2018-08-25 14:04:36 +00:00
if ( $ s eq $ cc && ! $ inquote ) { # close character
2018-08-14 20:14:09 +00:00
$ depth - - ;
2018-12-20 11:47:38 +00:00
return ( "" , substr ( $ t , 1 , $ off - 1 ) , substr ( $ t , $ off + 1 ) ) if ( ! $ depth ) ;
2018-08-14 20:14:09 +00:00
2018-08-25 14:04:36 +00:00
} elsif ( $ s eq $ oc && ! $ inquote ) { # open character
2018-08-14 20:14:09 +00:00
$ depth + + ;
} elsif ( $ s eq '"' && ! $ esc ) {
$ inquote = ! $ inquote ;
} elsif ( $ s eq '\\' ) {
$ esc = ! $ esc ;
} else {
$ esc = 0 ;
}
}
2018-12-20 11:47:38 +00:00
return ( "json2nameValue: no closing $cc found" , "" , "" ) ;
2018-08-14 20:14:09 +00:00
}
2018-08-25 14:04:36 +00:00
sub
2020-07-24 16:00:57 +00:00
setVal ( $$ $$ )
2018-08-25 14:04:36 +00:00
{
2020-07-24 16:00:57 +00:00
my ( $ ret , $ prefix , $ name , $ val ) = @ _ ;
2018-12-19 10:45:16 +00:00
$ name = "$prefix$name" ;
$ ret - > { $ name } = $ val ;
} ;
2020-07-24 16:00:57 +00:00
sub eObj ( $ $ $ $ $ ; $ ) ;
2018-12-19 10:45:16 +00:00
sub
2020-07-24 16:00:57 +00:00
eObj ( $$ $$ $; $ )
2018-12-19 10:45:16 +00:00
{
2020-07-24 16:00:57 +00:00
my ( $ ret , $ name , $ val , $ in , $ prefix , $ firstLevel ) = @ _ ;
2018-12-20 11:47:38 +00:00
my $ err ;
2020-02-29 09:14:28 +00:00
$ prefix = "" if ( ! $ firstLevel ) ;
2018-08-14 20:14:09 +00:00
if ( $ val =~ m/^"/ ) {
2018-12-20 11:47:38 +00:00
( $ err , $ val , $ in ) = lStr ( $ val ) ;
return ( $ err , undef ) if ( $ err ) ;
2020-07-24 16:00:57 +00:00
setVal ( $ ret , $ prefix , $ name , $ val ) ;
2018-08-14 20:14:09 +00:00
} elsif ( $ val =~ m/^{/ ) { # }
2018-12-20 11:47:38 +00:00
( $ err , $ val , $ in ) = lObj ( $ val , '{' , '}' ) ;
return ( $ err , undef ) if ( $ err ) ;
2020-02-27 22:01:20 +00:00
my % r2 ;
my $ in2 = $ val ;
2022-01-22 15:42:45 +00:00
while ( $ in2 =~ m/^\s*"([^"]*)"\s*:\s*(.*)$/s ) { # 125340
2020-02-27 22:01:20 +00:00
my ( $ name , $ val ) = ( $ 1 , $ 2 ) ;
$ name =~ s/[^a-z0-9._\-\/]/_/gsi ;
2020-07-24 16:00:57 +00:00
( $ err , $ in2 ) = eObj ( \ % r2 , $ name , $ val , $ in2 , $ prefix ) ;
2020-02-27 22:01:20 +00:00
return ( $ err , undef ) if ( $ err ) ;
$ in2 =~ s/^\s*,\s*// ;
}
foreach my $ k ( keys % r2 ) {
2020-07-24 16:00:57 +00:00
setVal ( $ ret , $ prefix , $ firstLevel ? $ k : "${name}_$k" , $ r2 { $ k } ) ;
2018-08-14 20:14:09 +00:00
}
2022-02-06 20:01:09 +00:00
return ( "error parsing (#1) '$in2'" , undef ) if ( $ in2 !~ m/^\s*$/ ) ;
2018-08-14 20:14:09 +00:00
2020-05-31 09:33:42 +00:00
} elsif ( $ val =~ m/^\[/ ) {
2018-12-20 11:47:38 +00:00
( $ err , $ val , $ in ) = lObj ( $ val , '[' , ']' ) ;
return ( $ err , undef ) if ( $ err ) ;
2018-08-25 14:04:36 +00:00
my $ idx = 1 ;
$ val =~ s/^\s*// ;
2020-07-15 12:19:04 +00:00
while ( defined ( $ val ) && $ val ne "" ) {
2020-07-24 16:00:57 +00:00
( $ err , $ val ) = eObj ( $ ret , $ firstLevel ? "$prefix$idx" : $ name . "_$idx" ,
2020-02-29 09:14:28 +00:00
$ val , $ val , $ prefix ) ;
2018-12-20 11:47:38 +00:00
return ( $ err , undef ) if ( $ err ) ;
2018-08-25 14:04:36 +00:00
$ val =~ s/^\s*,\s*// ;
2018-10-12 14:56:42 +00:00
$ val =~ s/\s*$// ;
$ idx + + ;
2018-08-25 14:04:36 +00:00
}
2018-12-19 10:45:16 +00:00
2022-02-06 20:01:09 +00:00
} elsif ( $ val =~ m/^((-?[0-9.]+)([eE][+-]?[0-9]+)?)(.*)$/s && # 125340
2022-01-22 15:42:45 +00:00
looks_like_number ( $ 1 ) ) {
2020-07-24 16:00:57 +00:00
setVal ( $ ret , $ prefix , $ name , $ 1 ) ;
2022-01-22 15:42:45 +00:00
$ in = $ 4 ;
2018-08-14 20:14:09 +00:00
2018-09-04 16:45:21 +00:00
} elsif ( $ val =~ m/^(true|false)(.*)$/s ) {
2020-07-24 16:00:57 +00:00
setVal ( $ ret , $ prefix , $ name , $ 1 ) ;
2018-09-04 16:45:21 +00:00
$ in = $ 2 ;
2024-10-11 16:25:05 +00:00
} elsif ( $ val =~ m/^(null|none)(.*)$/is ) { # 139411
2020-07-24 16:00:57 +00:00
setVal ( $ ret , $ prefix , $ name , undef ) ;
2018-11-16 12:29:57 +00:00
$ in = $ 2 ;
2018-08-14 20:14:09 +00:00
} else {
2022-02-06 20:01:09 +00:00
return ( "error parsing (#2) '$val'" , undef ) ;
2022-01-10 14:49:44 +00:00
2018-08-14 20:14:09 +00:00
}
2018-12-20 11:47:38 +00:00
return ( undef , $ in ) ;
2018-08-25 14:04:36 +00:00
}
2018-08-14 20:14:09 +00:00
2020-02-27 22:01:20 +00:00
$ in =~ s/^\s+// ;
2022-01-10 14:49:44 +00:00
my ( $ err , undef ) = eObj ( \ % ret , "" , $ in , "" , $ prefix , 1 ) ;
return { json2nameValueErrorText = > $ err , json2nameValueInput = > $ in } if ( $ err ) ;
2020-02-27 22:01:20 +00:00
2020-07-24 16:00:57 +00:00
return \ % ret if ( ! defined ( $ map ) && ! defined ( $ filter ) ) ;
$ map = eval $ map if ( $ map && ! ref ( $ map ) ) ; # passing hash through AnalyzeCommand
my % ret2 ;
for my $ name ( keys % ret ) {
2021-07-13 15:16:07 +00:00
next if ( $ negFilter && $ name =~ m/$negFilter/ ) ;
2020-07-26 17:17:03 +00:00
my $ oname = $ name ;
2020-07-24 16:00:57 +00:00
if ( defined ( $ map - > { $ name } ) ) {
next if ( ! $ map - > { $ name } ) ;
$ name = $ map - > { $ name } ;
}
2021-07-13 15:16:07 +00:00
next if ( $ filter && $ name !~ m/$filter/ ) ;
2020-07-26 17:17:03 +00:00
$ ret2 { $ name } = $ ret { $ oname } ;
2020-07-24 16:00:57 +00:00
}
return \ % ret2 ;
2018-08-14 20:14:09 +00:00
}
2020-03-23 11:34:26 +00:00
# add certain values to the key. Used to postprocess json2nameValue, where
# the input is of the form [{"name":"NAME","value":"Value"}], with
# hashKeyRename(json2nameValue($in), "^([0-9]+)_name:(.*)","^([0-9]+)");
sub
hashKeyRename ( $$ $ )
{
my ( $ hash , $ r1 , $ r2 ) = @ _ ;
my ( % repl , % ret ) ;
for my $ k ( keys % { $ hash } ) {
2020-03-27 10:22:34 +00:00
$ repl { $ 1 } = $ 2 if ( defined ( $ hash - > { $ k } ) &&
"$k:$hash->{$k}" =~ m/$r1/ && defined ( $ 1 ) && defined ( $ 2 ) ) ;
2020-03-23 11:34:26 +00:00
}
for my $ k ( keys % { $ hash } ) {
my $ val = $ hash - > { $ k } ;
next if ( $ k !~ m/$r2/ || ! defined ( $ repl { $ 1 } ) ) ;
$ k =~ s/$r2/$repl{$1}/ ;
2020-03-25 23:27:16 +00:00
$ ret { $ k } = $ val ;
2020-03-23 11:34:26 +00:00
}
return \ % ret ;
}
2018-08-14 20:14:09 +00:00
# generate readings from the json string (parsed by json2reading) for $hash
sub
2020-07-16 08:55:26 +00:00
json2reading ( $$ ; $$ $$ )
2018-08-14 20:14:09 +00:00
{
2020-07-16 08:55:26 +00:00
my ( $ hash , $ json , $ prefix , $ map , $ postProcess , $ filter ) = @ _ ;
2018-08-14 20:14:09 +00:00
$ hash = $ defs { $ hash } if ( ref ( $ hash ) ne "HASH" ) ;
return "json2reading: first arg is not a FHEM device"
if ( ! $ hash || ref $ hash ne "HASH" || ! $ hash - > { TYPE } ) ;
2020-07-16 08:55:26 +00:00
my $ ret = json2nameValue ( $ json , $ prefix , $ map , $ filter ) ;
2020-03-23 11:34:26 +00:00
if ( $ postProcess ) {
$ ret = eval ( $ postProcess ) ;
Log 1 , $@ if ( $@ ) ;
}
2018-08-14 20:14:09 +00:00
if ( $ ret && ref $ ret eq "HASH" ) {
readingsBeginUpdate ( $ hash ) ;
foreach my $ k ( keys % { $ ret } ) {
2020-03-25 23:27:16 +00:00
readingsBulkUpdate ( $ hash , makeReadingName ( $ k ) , $ ret - > { $ k } ) ;
2018-08-14 20:14:09 +00:00
}
readingsEndUpdate ( $ hash , 1 ) ;
}
return undef ;
}
2017-05-22 20:25:06 +00:00
2013-08-25 11:49:30 +00:00
sub
Debug ( $ ) {
my $ msg = shift ;
2024-03-16 12:11:51 +00:00
stacktrace ( ) if ( AttrNum ( 'global' , 'stacktrace' , 0 ) == 1 ) ;
2013-08-25 11:49:30 +00:00
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
2020-09-03 21:56:29 +00:00
if ( ! defined ( $ hash - > { FD } ) ) {
my $ n = $ hash - > { NAME } ;
Log 1 , "ERROR: addToWritebuffer for $n without FD" ;
2020-09-23 14:56:24 +00:00
Log 1 , "callstack:" . stacktraceAsString ( 1 ) ;
Log 1 , "FD closed in " . $ hash - > { stacktrace } if ( $ hash - > { stacktrace } ) ;
2020-09-03 21:56:29 +00:00
delete ( $ defs { $ n } ) ;
delete ( $ attr { $ n } ) ;
return ;
}
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 ;
2020-06-27 09:52:31 +00:00
if ( ! defined ( $ hash - > { $ wbName } ) ) {
2013-11-09 13:45:34 +00:00
$ hash - > { $ wbName } = $ txt ;
2022-04-14 09:05:11 +00:00
} elsif ( $ nolimit || length ( $ hash - > { $ wbName } ) < 1024000 ) {
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
}
2020-10-01 19:49:32 +00:00
# Faster than createNtfyHash
sub
removeFromNtfyHash ( $ )
{
my ( $ toDel ) = @ _ ;
return if ( ! $ defs { $ toDel } ||
! $ defs { $ toDel } { TYPE } ||
! $ modules { $ defs { $ toDel } { TYPE } } { NotifyFn } ) ;
foreach my $ d ( keys % ntfyHash ) {
my @ a = grep { $ _ !~ m/^$toDel$/ } @ { $ ntfyHash { $ d } } ;
$ ntfyHash { $ d } = \ @ a ;
}
}
# Note: always executed after ntfyHash = (); slow for large installations!
2014-01-14 19:23:34 +00:00
sub
createNtfyHash ( )
{
2017-01-23 17:16:54 +00:00
Log 5 , "createNotifyHash" ;
2014-01-14 19:23:34 +00:00
my @ ntfyList = sort { $ defs { $ a } { NTFY_ORDER } cmp $ defs { $ b } { NTFY_ORDER } }
2017-01-05 20:26:04 +00:00
grep { $ defs { $ _ } { NTFY_ORDER } &&
$ defs { $ _ } { TYPE } &&
2021-06-17 18:56:32 +00:00
! $ defs { $ _ } { disableNotifyFn } &&
2017-01-05 20:26:04 +00:00
$ modules { $ defs { $ _ } { TYPE } } { NotifyFn } } keys % defs ;
2017-02-05 13:23:25 +00:00
my % d2a_cache ;
% ntfyHash = ( "*" = > [] ) ;
2014-01-14 19:23:34 +00:00
foreach my $ d ( @ ntfyList ) {
2023-08-24 12:46:07 +00:00
my $ ndl = $ attr { $ d } { overrideNotifydev } ;
$ ndl = $ defs { $ d } { NOTIFYDEV } if ( ! $ ndl ) ;
2017-02-05 13:23:25 +00:00
next if ( ! $ ndl ) ;
my @ ndlarr ;
if ( $ d2a_cache { $ ndl } ) {
@ ndlarr = @ { $ d2a_cache { $ ndl } } ;
} else {
@ ndlarr = devspec2array ( $ ndl ) ;
if ( @ ndlarr > 1 ) {
my % h = map { $ _ = > 1 } @ ndlarr ;
@ ndlarr = keys % h ;
2016-01-10 07:43:26 +00:00
}
2017-02-05 13:23:25 +00:00
$ d2a_cache { $ ndl } = \ @ ndlarr ;
2016-01-10 07:43:26 +00:00
}
2017-02-05 13:23:25 +00:00
map { $ ntfyHash { $ _ } = [] } @ ndlarr ;
2014-01-14 19:23:34 +00:00
}
2017-02-05 13:23:25 +00:00
my @ nhk = keys % ntfyHash ;
2014-01-14 19:23:34 +00:00
foreach my $ d ( @ ntfyList ) {
2023-08-24 12:46:07 +00:00
my $ ndl = $ attr { $ d } { overrideNotifydev } ;
$ ndl = $ defs { $ d } { NOTIFYDEV } if ( ! $ ndl ) ;
2017-02-05 13:23:25 +00:00
my $ arr = ( $ ndl ? $ d2a_cache { $ ndl } : \ @ nhk ) ;
map { push @ { $ ntfyHash { $ _ } } , $ d } @ { $ arr } ;
2014-01-14 19:23:34 +00:00
}
}
2020-07-24 15:10:40 +00:00
# Used for debugging
sub
notifyRegexpCheck ( $ )
{
join ( "\n" , map {
if ( $ _ !~ m/^\(?([A-Za-z0-9\.\_]+(?:\.[\+\*])?)(?::.*)?\)?$/ ) {
2020-07-24 15:20:41 +00:00
"$_: no match (ignored)"
2020-07-24 15:10:40 +00:00
} elsif ( $ defs { $ 1 } ) {
2020-07-24 15:20:41 +00:00
"$_: device $1 (OK)" ;
2020-07-24 15:10:40 +00:00
} else {
2020-07-24 15:20:41 +00:00
my @ ds = devspec2array ( $ 1 ) ;
2020-08-02 14:27:55 +00:00
if ( $ ds [ 0 ] ne $ 1 ) {
2020-07-24 15:20:41 +00:00
"$_: devspec " . join ( "," , @ ds ) . " (OK)" ;
} else {
"$_: unknown (ignored)" ;
}
2020-07-24 15:10:40 +00:00
}
} split ( /\|/ , $ _ [ 0 ] ) ) ;
}
2014-01-16 09:45:15 +00:00
sub
2021-06-17 18:56:32 +00:00
notifyRegexpChanged ( $$ ; $ )
2014-01-16 09:45:15 +00:00
{
2021-06-17 18:56:32 +00:00
my ( $ hash , $ re , $ disableNotifyFn ) = @ _ ;
2014-01-16 09:45:15 +00:00
2021-06-17 18:56:32 +00:00
% ntfyHash = ( ) ;
if ( $ disableNotifyFn ) {
delete ( $ hash - > { NOTIFYDEV } ) ;
$ hash - > { disableNotifyFn } = 1 ;
return ;
}
delete ( $ hash - > { disableNotifyFn } ) ;
2017-01-18 11:45:03 +00:00
my @ list2 = split ( /\|/ , $ re ) ;
my @ list = grep { m/./ } # Forum #62369
map { ( m/^\(?([A-Za-z0-9\.\_]+(?:\.[\+\*])?)(?::.*)?\)?$/ &&
( $ defs { $ 1 } || devspec2array ( $ 1 ) ne $ 1 ) ) ? $ 1 : "" } @ list2 ;
if ( @ list && int ( @ list ) == int ( @ list2 ) ) {
2017-01-18 14:23:53 +00:00
my % h = map { $ _ = > 1 } @ list ;
@ list = keys % h ; # remove duplicates
2017-01-18 11:45:03 +00:00
$ hash - > { NOTIFYDEV } = join ( "," , @ list ) ;
2014-01-16 09:45:15 +00:00
} else {
2017-01-18 11:45:03 +00:00
delete ( $ hash - > { NOTIFYDEV } ) ;
2014-01-16 09:45:15 +00:00
}
}
2022-01-26 10:04:13 +00:00
sub
setDisableNotifyFn ( $$ )
{
my ( $ hash , $ doit ) = @ _ ;
if ( $ doit ) {
delete ( $ hash - > { NOTIFYDEV } ) ;
$ hash - > { disableNotifyFn } = 1
} else {
delete ( $ hash - > { disableNotifyFn } ) ;
}
% ntfyHash = ( ) ;
}
2022-01-22 09:21:18 +00:00
sub
setNotifyDev ( $$ )
{
my ( $ hash , $ ntfydev ) = @ _ ;
if ( $ ntfydev ) {
$ hash - > { NOTIFYDEV } = $ ntfydev ;
} else {
delete ( $ hash - > { NOTIFYDEV } ) ;
}
% ntfyHash = ( ) ;
}
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
2018-06-14 07:58:06 +00:00
$ forceType = "" if ( ! defined ( $ forceType ) ) ;
2014-06-01 11:45:00 +00:00
if ( ref ( $ param ) eq "HASH" ) {
$ fileName = $ param - > { FileName } ;
2020-10-01 19:49:32 +00:00
$ forceType = lc ( $ param - > { ForceType } ) if ( $ param - > { ForceType } ) ;
2014-06-01 11:45:00 +00:00
} else {
$ fileName = $ param ;
}
if ( configDBUsed ( ) && $ forceType ne "file" ) {
( $ err , @ ret ) = cfgDB_FileRead ( $ fileName ) ;
2014-05-01 15:02:06 +00:00
} else {
2022-02-14 21:03:18 +00:00
my $ FH ;
if ( open ( $ FH , $ fileName ) ) {
binmode ( $ FH , ":encoding(UTF-8)" ) if ( $ unicodeEncoding ) ;
@ 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
}
2022-02-14 21:03:18 +00:00
2014-05-01 15:02:06 +00:00
}
return ( $ err , @ ret ) ;
}
sub
FileWrite ( $@ )
{
2014-06-01 11:45:00 +00:00
my ( $ param , @ rows ) = @ _ ;
2015-08-16 10:43:51 +00:00
my ( $ err , @ ret , $ fileName , $ forceType , $ nl ) ;
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 } ;
2015-08-16 10:43:51 +00:00
$ nl = $ param - > { NoNL } ? "" : "\n" ;
2014-06-01 11:45:00 +00:00
} else {
$ fileName = $ param ;
2015-08-16 10:43:51 +00:00
$ nl = "\n" ;
2014-06-01 11:45:00 +00:00
}
$ forceType = "" if ( ! defined ( $ forceType ) ) ;
if ( configDBUsed ( ) && $ forceType ne "file" ) {
return cfgDB_FileWrite ( $ fileName , @ rows ) ;
2014-05-01 15:02:06 +00:00
} else {
2022-02-14 21:03:18 +00:00
my $ FH ;
if ( open ( $ FH , ">$fileName" ) ) {
binmode ( $ FH ) ;
binmode ( $ FH , ":encoding(UTF-8)" ) if ( $ unicodeEncoding ) ;
2014-05-01 15:02:06 +00:00
foreach my $ l ( @ rows ) {
2022-02-14 21:03:18 +00:00
print $ FH $ l , $ nl ;
2014-05-01 15:02:06 +00:00
}
2022-02-14 21:03:18 +00:00
close ( $ FH ) ;
2014-05-01 15:02:06 +00:00
return undef ;
} else {
2014-06-01 11:45:00 +00:00
return "Can't open $fileName: $!" ;
2014-05-01 15:02:06 +00:00
}
}
}
2017-09-17 09:54:02 +00:00
sub
FileDelete ( $ )
{
my ( $ param ) = @ _ ;
my ( $ fileName , $ forceType ) ;
if ( ref ( $ param ) eq "HASH" ) {
$ fileName = $ param - > { FileName } ;
$ forceType = $ param - > { ForceType } ;
} else {
$ fileName = $ param ;
}
$ forceType // = '' ;
if ( configDBUsed ( ) && lc ( $ forceType ) ne "file" ) {
2017-09-21 07:22:33 +00:00
my $ ret = _cfgDB_Filedelete ( $ fileName ) ;
return ( $ ret ? undef : "$fileName: _cfgDB_Filedelete failed" ) ;
2017-09-17 09:54:02 +00:00
} else {
2017-09-21 07:22:33 +00:00
my $ ret = unlink ( $ fileName ) ;
return ( $ ret ? undef : "$fileName: $!" ) ;
2017-09-17 09:54:02 +00:00
}
}
2015-01-11 18:23:31 +00:00
sub
getUniqueId ( )
{
2020-02-17 18:49:08 +00:00
return $ globalUniqueID if ( $ globalUniqueID ) ;
2015-01-11 18:23:31 +00:00
my ( $ err , $ uniqueID ) = getKeyValue ( "uniqueID" ) ;
2019-01-26 16:17:38 +00:00
if ( defined ( $ uniqueID ) ) {
$ uniqueID =~ s/[^0-9a-f]//g ;
2020-02-17 18:49:08 +00:00
if ( $ uniqueID && length ( $ uniqueID ) == 32 ) {
$ globalUniqueID = $ uniqueID ;
return $ uniqueID ;
}
2019-01-26 16:17:38 +00:00
}
2015-01-13 09:43:33 +00:00
$ uniqueID = createUniqueId ( ) ;
2015-01-11 18:23:31 +00:00
setKeyValue ( "uniqueID" , $ uniqueID ) ;
2020-02-17 18:49:08 +00:00
$ globalUniqueID = $ uniqueID ;
2015-01-11 18:23:31 +00:00
return $ uniqueID ;
}
2015-01-12 17:15:46 +00:00
my $ srandUsed ;
sub
createUniqueId ( )
{
my $ uniqueID ;
2018-01-20 20:12:25 +00:00
srand ( gettimeofday ( ) ) if ( ! $ srandUsed ) ;
2015-01-12 17:15:46 +00:00
$ 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 ) = @ _ ;
2018-05-15 20:06:23 +00:00
my $ fName = AttrVal ( "global" , "keyFileName" , "uniqueID" ) ;
$ fName =~ s/\.\.//g ;
$ fName = $ attr { global } { modpath } . "/FHEM/FhemUtils/$fName" ;
2015-01-11 18:23:31 +00:00
my ( $ err , @ l ) = FileRead ( $ fName ) ;
return ( $ err , undef ) if ( $ err ) ;
for my $ l ( @ l ) {
return ( undef , $ 1 ) if ( $ l =~ m/^$key:(.*)/ ) ;
}
return ( undef , undef ) ;
}
2017-11-20 22:22:19 +00:00
# Use an undefined value to delete the key
2015-01-11 18:23:31 +00:00
sub
setKeyValue ( $$ )
{
my ( $ key , $ value ) = @ _ ;
2024-02-06 17:17:48 +00:00
return "setKeyValue: invalid key: $key"
if ( ! defined ( $ key ) || $ key =~ m/\n/s ) ;
return "setKeyValue: invalid value: $value"
if ( $ value && $ value =~ m/\n/s ) ;
2018-05-15 20:06:23 +00:00
my $ fName = AttrVal ( "global" , "keyFileName" , "uniqueID" ) ;
$ fName =~ s/\.\.//g ;
$ fName = $ attr { global } { modpath } . "/FHEM/FhemUtils/$fName" ;
2015-01-11 18:23:31 +00:00
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 ( $$ $ )
{
my ( $ cmd , $ dev , $ param ) = @ _ ;
2020-05-11 19:32:12 +00:00
return if ( ! $ init_done ) ;
return if ( defined ( $ dev ) &&
( ! $ defs { $ dev } || $ defs { $ dev } { TEMPORARY } || $ defs { $ dev } { VOLATILE } ) ) ;
2015-02-08 18:35:19 +00:00
2020-05-11 19:32:12 +00:00
$ lastDefChange + + ;
2024-03-05 15:57:09 +00:00
my ( $ mr , $ ml ) = split ( " " , AttrVal ( 'global' , 'maxChangeLog' , 10 ) ) ;
shift @ structChangeHist if ( @ structChangeHist > $ mr - 1 ) ;
$ ml = 40 if ( ! defined ( $ ml ) ) ;
$ param = substr ( $ param , 0 , $ ml ) . "..." if ( length ( $ param ) > $ ml ) ;
2015-01-24 12:38:25 +00:00
push @ structChangeHist , "$cmd $param" ;
}
2015-01-11 18:23:31 +00:00
2015-03-22 13:58:15 +00:00
sub
fhemFork ( )
{
my $ pid = fork ;
if ( ! defined ( $ pid ) ) {
Log 1 , "Cannot fork: $!" ;
2015-08-13 19:02:29 +00:00
stacktrace ( ) if ( $ attr { global } { stacktrace } ) ;
2015-03-22 13:58:15 +00:00
return undef ;
}
return $ pid if ( $ pid ) ;
# Child here
2016-03-16 07:54:42 +00:00
# Close FDs as we cannot restart FHEM if child keeps TCP Serverports open
2015-03-22 13:58:15 +00:00
foreach my $ d ( sort keys % defs ) {
my $ h = $ defs { $ d } ;
2016-06-04 13:57:59 +00:00
$ h - > { DBH } - > { InactiveDestroy } = 1
2016-12-30 12:59:07 +00:00
if ( $ h - > { DBH } && $ h - > { TYPE } eq 'DbLog' ) ; #Forum #43271
2015-03-22 13:58:15 +00:00
TcpServer_Close ( $ h ) if ( $ h - > { SERVERSOCKET } ) ;
if ( $ h - > { DeviceName } ) {
2020-04-13 10:34:40 +00:00
require "DevIo.pm" ;
2015-03-22 13:58:15 +00:00
DevIo_CloseDev ( $ h , 1 ) ;
}
}
2016-03-16 07:54:42 +00:00
$ SIG { CHLD } = 'DEFAULT' ; # Forum #50898
2017-11-29 21:50:39 +00:00
$ fhemForked = 1 ;
2015-03-22 13:58:15 +00:00
return 0 ;
}
2017-10-03 10:45:29 +00:00
# Return the next element from the string (list) for each consecutive call.
# The index for the next call is stored in the device hash
2015-08-16 09:24:40 +00:00
sub
Each ( $$ ; $ ) # can be used e.g. in at, Forum #40022
{
my ( $ dev , $ string , $ sep ) = @ _ ;
return "" if ( ! $ defs { $ dev } ) ;
my $ idx = ( $ defs { $ dev } { EACH_INDEX } ? $ defs { $ dev } { EACH_INDEX } : 0 ) ;
$ sep = "," if ( ! $ sep ) ;
my @ arr = split ( $ sep , $ string ) ;
$ idx = 0 if ( @ arr <= $ idx ) ;
$ defs { $ dev } { EACH_INDEX } = $ idx + 1 ;
return $ arr [ $ idx ] ;
}
2015-12-29 19:08:19 +00:00
##################
# Return 1 if Authorized, else 0
2015-12-30 09:11:46 +00:00
# Note: AuthorizeFn's returning 1 are not stackable.
2015-12-29 19:08:19 +00:00
sub
2019-08-27 08:36:02 +00:00
Authorized ( $$ $; $ )
2015-12-29 19:08:19 +00:00
{
2019-08-27 08:36:02 +00:00
my ( $ cl , $ type , $ arg , $ silent ) = @ _ ;
2015-12-29 19:08:19 +00:00
return 1 if ( ! $ init_done || ! $ cl || ! $ cl - > { SNAME } ) ; # Safeguarding
RefreshAuthList ( ) if ( $ auth_refresh ) ;
2015-12-30 15:32:59 +00:00
my $ sname = $ cl - > { SNAME } ;
my $ verbose = AttrVal ( $ sname , "verbose" , 1 ) ; # Speedup?
2015-12-29 19:08:19 +00:00
foreach my $ a ( @ authorize ) {
2019-08-27 08:36:02 +00:00
my $ r = CallFn ( $ a , "AuthorizeFn" , $ defs { $ a } , $ cl , $ type , $ arg , $ silent ) ;
if ( $ verbose >= 4 && ! $ silent ) {
2015-12-30 15:32:59 +00:00
Log3 $ sname , 4 , "authorize $sname/$type/$arg: $a returned " .
( $ r == 0 ? "dont care" : $ r == 1 ? "allowed" : "prohibited" ) ;
}
2015-12-29 19:08:19 +00:00
return 1 if ( $ r == 1 ) ;
return 0 if ( $ r == 2 ) ;
}
return 1 ;
}
##################
# Return 0 if not needed, 1 if authenticated, 2 if authentication failed
2015-12-30 09:11:46 +00:00
# Loop until one Authenticate is ok
2015-12-29 19:08:19 +00:00
sub
Authenticate ( $$ )
{
my ( $ cl , $ arg ) = @ _ ;
return 1 if ( ! $ init_done || ! $ cl || ! $ cl - > { SNAME } ) ; # Safeguarding
RefreshAuthList ( ) if ( $ auth_refresh ) ;
2015-12-30 09:11:46 +00:00
my $ needed = 0 ;
2015-12-29 19:08:19 +00:00
foreach my $ a ( @ authenticate ) {
my $ r = CallFn ( $ a , "AuthenticateFn" , $ defs { $ a } , $ cl , $ arg ) ;
2015-12-30 09:11:46 +00:00
$ needed = $ r if ( $ r ) ;
2018-10-24 15:37:39 +00:00
last if ( $ r == 1 ) ;
2015-12-29 19:08:19 +00:00
}
2018-10-24 15:37:39 +00:00
if ( $ needed == 2 && $ cl - > { NAME } ne "SecurityCheck" ) {
my $ adb = $ cl - > { AuthenticationDeniedBy } ;
if ( $ adb ) {
my $ au = $ cl - > { AuthenticatedUser } ;
Log3 $ adb , 3 , "Login denied " .
( $ au ? "for user >$au< " : "" ) . "via $cl->{NAME}" ;
}
} else {
delete $ cl - > { AuthenticationDeniedBy } ;
}
2015-12-30 09:11:46 +00:00
return $ needed ;
2015-12-29 19:08:19 +00:00
}
2016-04-17 11:35:23 +00:00
#####################################
2015-12-29 19:08:19 +00:00
sub
RefreshAuthList ( )
{
@ authorize = ( ) ;
@ authenticate = ( ) ;
foreach my $ d ( sort keys % defs ) {
my $ h = $ defs { $ d } ;
next if ( ! $ h - > { TYPE } || ! $ modules { $ h - > { TYPE } } ) ;
push @ authorize , $ d if ( $ modules { $ h - > { TYPE } } { AuthorizeFn } ) ;
push @ authenticate , $ d if ( $ modules { $ h - > { TYPE } } { AuthenticateFn } ) ;
}
$ auth_refresh = 0 ;
}
2016-04-17 11:35:23 +00:00
#####################################
2016-04-03 14:20:32 +00:00
sub
perlSyntaxCheck ( $% )
{
my ( $ exec , % specials ) = @ _ ;
my $ psc = AttrVal ( "global" , "perlSyntaxCheck" , ( $ featurelevel > 5.7 ) ? 1 : 0 ) ;
return undef if ( ! $ psc || ! $ init_done ) ;
2016-04-17 11:35:23 +00:00
my ( $ arr , $ hash ) = parseParams ( $ exec , ';' ) ;
2017-02-23 20:23:38 +00:00
$ arr = [ $ exec ] if ( ! @$ arr ) ; # temporary bugfix
2016-04-17 11:35:23 +00:00
for my $ cmd ( @ { $ arr } ) {
next if ( $ cmd !~ m/^\s*{/ ) ; # } for match
2017-02-23 20:23:38 +00:00
$ specials { __UNIQUECMD__ } = 1 ;
2016-04-17 11:35:23 +00:00
$ cmd = EvalSpecials ( "{return undef; $cmd}" , % specials ) ;
my $ r = AnalyzePerlCommand ( undef , $ cmd ) ;
return $ r if ( $ r ) ;
}
return undef ;
2016-04-03 14:20:32 +00:00
}
2016-04-17 11:35:23 +00:00
#####################################
sub
2018-02-28 21:09:20 +00:00
parseParams ( $; $$ $ )
2016-04-17 11:35:23 +00:00
{
2018-02-28 21:09:20 +00:00
my ( $ cmd , $ separator , $ joiner , $ keyvalueseparator ) = @ _ ;
2017-03-27 20:20:25 +00:00
$ separator = ' ' if ( ! $ separator ) ;
$ joiner = $ separator if ( ! $ joiner ) ; # needed if separator is a regexp
2018-02-28 21:09:20 +00:00
$ keyvalueseparator = '=' if ( ! $ keyvalueseparator ) ;
2016-04-17 11:35:23 +00:00
my ( @ a , % h ) ;
2019-05-04 19:13:22 +00:00
return ( \ @ a , \ % h ) if ( ! defined ( $ cmd ) ) ;
2016-04-17 11:35:23 +00:00
my @ params ;
if ( ref ( $ cmd ) eq 'ARRAY' ) {
@ params = @ { $ cmd } ;
} else {
@ params = split ( $ separator , $ cmd ) ;
}
while ( @ params ) {
my $ param = shift ( @ params ) ;
2017-04-15 16:23:08 +00:00
next if ( $ param eq "" ) ;
2018-02-28 21:09:20 +00:00
my ( $ key , $ value ) = split ( $ keyvalueseparator , $ param , 2 ) ;
2016-04-17 11:35:23 +00:00
if ( ! defined ( $ value ) ) {
$ value = $ key ;
$ key = undef ;
2017-02-24 07:54:45 +00:00
2017-03-19 11:05:16 +00:00
# the key can not start with a { -> it must be a perl expression # vim:}
} elsif ( $ key =~ m/^\s*{/ ) { # for vim: }
2017-02-24 09:39:02 +00:00
$ value = $ param ;
2017-02-24 07:54:45 +00:00
$ key = undef ;
2016-04-17 11:35:23 +00:00
}
#collect all parts until the closing ' or "
while ( $ param && $ value =~ m/^('|")/ && $ value !~ m/$1$/ ) {
my $ next = shift ( @ params ) ;
last if ( ! defined ( $ next ) ) ;
2017-03-27 20:20:25 +00:00
$ value . = $ joiner . $ next ;
2016-04-17 11:35:23 +00:00
}
#remove matching ' or " from the start and end
if ( $ value =~ m/^('|")/ && $ value =~ m/$1$/ ) {
$ value =~ s/^.(.*).$/$1/ ;
}
2016-12-14 13:04:42 +00:00
#collect all parts until opening { and closing } are matched
2017-03-05 21:37:35 +00:00
if ( $ value =~ m/^\s*{/ ) { # } for match
2016-04-17 11:35:23 +00:00
my $ count = 0 ;
for my $ i ( 0 .. length ( $ value ) - 1 ) {
my $ c = substr ( $ value , $ i , 1 ) ;
+ + $ count if ( $ c eq '{' ) ;
- - $ count if ( $ c eq '}' ) ;
}
while ( $ param && $ count != 0 ) {
my $ next = shift ( @ params ) ;
last if ( ! defined ( $ next ) ) ;
2017-03-27 20:20:25 +00:00
$ value . = $ joiner . $ next ;
2016-04-17 11:35:23 +00:00
for my $ i ( 0 .. length ( $ next ) - 1 ) {
my $ c = substr ( $ next , $ i , 1 ) ;
+ + $ count if ( $ c eq '{' ) ;
- - $ count if ( $ c eq '}' ) ;
}
}
}
if ( defined ( $ key ) ) {
$ h { $ key } = $ value ;
} else {
push @ a , $ value ;
}
}
return ( \ @ a , \ % h ) ;
}
2016-12-14 13:04:42 +00:00
# get "Porbably Associated With" list for a devicename
sub
getPawList ( $ )
{
my ( $ d ) = @ _ ;
my $ h = $ defs { $ d } ;
my @ dob ;
2019-08-27 07:47:27 +00:00
my $ daw = ReadingsVal ( $ d , ".associatedWith" , "" ) ; # 103095
2016-12-14 13:04:42 +00:00
foreach my $ dn ( sort keys % defs ) {
next if ( ! $ dn || $ dn eq $ d ) ;
my $ dh = $ defs { $ dn } ;
if ( ( $ dh - > { DEF } && $ dh - > { DEF } =~ m/\b$d\b/ ) ||
2019-08-27 07:47:27 +00:00
( ReadingsVal ( $ dn , ".associatedWith" , "" ) =~ m/\b$d\b/ ) ||
( $ h - > { DEF } && $ h - > { DEF } =~ m/\b$dn\b/ ) ||
$ daw =~ m/\b$dn\b/ ) {
2016-12-14 13:04:42 +00:00
push ( @ dob , $ dn ) ;
}
}
2018-12-22 19:22:09 +00:00
my $ aw = ReadingsVal ( $ d , "associatedWith" , "" ) ; # Explicit link
2019-01-22 18:29:59 +00:00
push ( @ dob , grep { $ defs { $ _ } } split ( "[ ,]" , $ aw ) ) if ( $ aw ) ;
2016-12-14 13:04:42 +00:00
return @ dob ;
}
2016-04-17 11:35:23 +00:00
2017-08-19 11:19:54 +00:00
sub
goodDeviceName ( $ )
{
my ( $ name ) = @ _ ;
return ( $ name && $ name =~ m/^[a-z0-9._]*$/i ) ;
}
sub
makeDeviceName ( $ ) # Convert non-valid characters to _
{
my ( $ name ) = @ _ ;
$ name = "UNDEFINED" if ( ! defined ( $ name ) ) ;
$ name =~ s/[^a-z0-9._]/_/gi ;
return $ name ;
}
sub
goodReadingName ( $ )
{
my ( $ name ) = @ _ ;
2019-02-17 09:24:01 +00:00
return undef if ( ! $ name ) ;
2020-04-19 16:07:40 +00:00
return ( $ name =~ m/^[a-z0-9._\-\/]+$/i ||
$ name =~ m/^\.[^\s]*$/ ) ;
2017-08-19 11:19:54 +00:00
}
sub
makeReadingName ( $ ) # Convert non-valid characters to _
{
my ( $ name ) = @ _ ;
$ name = "UNDEFINED" if ( ! defined ( $ name ) ) ;
2020-04-19 16:07:40 +00:00
if ( $ name =~ m/^\./ ) {
$ name =~ s/\s/_/g ;
return $ name ;
}
2020-10-19 22:18:24 +00:00
my % umlaut = ( '\xc3\xa4' = > 'ae' ,
'\xc3\xb6' = > 'oe' ,
'\xc3\xbc' = > 'ue' ,
'\xc3\x9f' = > 'ss' ) ;
map { $ name =~ s/$_/$umlaut{$_}/g } keys % umlaut ;
2017-08-19 11:19:54 +00:00
$ name =~ s/[^a-z0-9._\-\/]/_/gi ;
return $ name ;
}
2017-09-03 14:23:14 +00:00
sub
computeAlignTime ( $$ @ )
{
my ( $ timeSpec , $ alignSpec , $ triggertime ) = @ _ ; # triggertime is now if absent
my ( $ alErr , $ alHr , $ alMin , $ alSec , undef ) = GetTimeSpec ( $ alignSpec ) ;
return ( "alignTime: $alErr" , undef ) if ( $ alErr ) ;
my ( $ tmErr , $ hr , $ min , $ sec , undef ) = GetTimeSpec ( $ timeSpec ) ;
return ( "timeSpec: $tmErr" , undef ) if ( $ alErr ) ;
2018-01-20 20:12:25 +00:00
my $ now = int ( gettimeofday ( ) ) ;
2019-08-03 12:30:38 +00:00
my $ alTime = ( $ alHr * 60 + $ alMin ) * 60 + $ alSec ;
2017-09-03 14:23:14 +00:00
my $ step = ( $ hr * 60 + $ min ) * 60 + $ sec ;
my $ ttime = ( $ triggertime ? int ( $ triggertime ) : $ now ) ;
2019-08-03 12:30:38 +00:00
my $ off = ( ( $ ttime + fhemTzOffset ( $ now ) ) % 86400 ) - 86400 ;
2017-09-03 14:23:14 +00:00
while ( $ off < $ alTime ) {
$ off += $ step ;
}
$ ttime += ( $ alTime - $ off ) ;
$ ttime += $ step if ( $ ttime < $ now ) ;
return ( undef , $ ttime ) ;
}
2017-05-22 20:25:06 +00:00
2017-11-01 16:59:23 +00:00
############################
my % restoreDir_dirs ;
sub
restoreDir_mkDir ( $$ $ )
{
my ( $ root , $ dir , $ isFile ) = @ _ ;
if ( $ isFile ) { # Delete the file Component
$ dir =~ m , ^ ( . * ) /([^/ ] * ) $, ;
$ dir = $ 1 ;
2022-04-16 10:17:59 +00:00
$ dir = "" if ( ! defined ( $ dir ) ) ; # file in .
2017-11-01 16:59:23 +00:00
}
return if ( $ restoreDir_dirs { $ dir } ) ;
$ restoreDir_dirs { $ dir } = 1 ;
my @ p = split ( "/" , $ dir ) ;
for ( my $ i = 0 ; $ i < int ( @ p ) ; $ i + + ) {
my $ path = "$root/" . join ( "/" , @ p [ 0 .. $ i ] ) ;
if ( ! - d $ path ) {
mkdir $ path ;
Log 4 , "MKDIR $root/" . join ( "/" , @ p [ 0 .. $ i ] ) ;
}
}
}
sub
restoreDir_rmTree ( $ )
{
my ( $ dir ) = @ _ ;
my $ dh ;
if ( ! opendir ( $ dh , $ dir ) ) {
Log 1 , "opendir $dir: $!" ;
return ;
}
my @ files = grep { $ _ ne "." && $ _ ne ".." } readdir ( $ dh ) ;
closedir ( $ dh ) ;
foreach my $ f ( @ files ) {
if ( - d "$dir/$f" ) {
restoreDir_rmTree ( "$dir/$f" ) ;
} else {
Log 4 , "rm $dir/$f" ;
if ( ! unlink ( "$dir/$f" ) ) {
Log 1 , "rm $dir/$f failed: $!" ;
}
}
}
Log 4 , "rmdir $dir" ;
if ( ! rmdir ( $ dir ) ) {
Log 1 , "rmdir $dir failed: $!" ;
}
}
sub
2018-03-18 11:34:55 +00:00
restoreDir_init ( ; $ )
2017-11-01 16:59:23 +00:00
{
2018-03-17 16:23:45 +00:00
my ( $ subDir ) = @ _ ;
2017-11-01 16:59:23 +00:00
my $ root = $ attr { global } { modpath } ;
my $ nDirs = AttrVal ( "global" , "restoreDirs" , 3 ) ;
if ( $ nDirs !~ m/^\d+$/ || $ nDirs < 0 ) {
Log 1 , "invalid restoreDirs value $nDirs, setting it to 3" ;
$ nDirs = 3 ;
}
return "" if ( $ nDirs == 0 ) ;
my $ rdName = "restoreDir" ;
2018-03-17 16:23:45 +00:00
$ rdName . = "/$subDir" if ( $ subDir ) ;
2018-01-20 20:12:25 +00:00
my @ t = localtime ( gettimeofday ( ) ) ;
2017-11-01 16:59:23 +00:00
my $ restoreDir = sprintf ( "$rdName/%04d-%02d-%02d" ,
$ t [ 5 ] + 1900 , $ t [ 4 ] + 1 , $ t [ 3 ] ) ;
Log 1 , "MKDIR $restoreDir" if ( ! - d "$root/restoreDir" ) ;
restoreDir_mkDir ( $ root , $ restoreDir , 0 ) ;
if ( ! opendir ( DH , "$root/$rdName" ) ) {
Log 1 , "opendir $root/$rdName: $!" ;
return "" ;
}
2018-03-17 16:23:45 +00:00
my @ oldDirs = sort grep { $ _ =~ m/^20\d\d-\d\d-\d\d/ } readdir ( DH ) ;
2017-11-01 16:59:23 +00:00
closedir ( DH ) ;
while ( int ( @ oldDirs ) > $ nDirs ) {
my $ dir = "$root/$rdName/" . shift ( @ oldDirs ) ;
next if ( $ dir =~ m/$restoreDir/ ) ; # Just in case
Log 1 , "RMDIR: $dir" ;
restoreDir_rmTree ( $ dir ) ;
}
return $ restoreDir ;
}
sub
restoreDir_saveFile ( $$ )
{
my ( $ restoreDir , $ fName ) = @ _ ;
return if ( ! $ restoreDir || ! $ fName ) ;
2020-04-12 11:05:08 +00:00
if ( $^O eq "MSWin32" ) { # Forum #110071
$ fName =~ s , ^ . : , , g ;
$ fName =~ s , \ \ , / , g ;
}
2017-11-01 16:59:23 +00:00
my $ root = $ attr { global } { modpath } ;
restoreDir_mkDir ( $ root , "$restoreDir/$fName" , 1 ) ;
if ( ! copy ( $ fName , "$root/$restoreDir/$fName" ) ) {
2020-03-23 19:40:22 +00:00
Log 1 , "copy $fName $root/$restoreDir/$fName failed:$!" ;
2017-11-01 16:59:23 +00:00
}
}
2017-12-23 19:33:43 +00:00
sub
SecurityCheck ( )
{
my @ fnd ;
2020-04-23 10:08:41 +00:00
return if ( AttrVal ( "global" , "disableFeatures" , "" ) =~ m/\bsecurityCheck\b/i ) ;
2018-08-10 11:52:49 +00:00
foreach my $ sdev ( keys % defs ) {
next if ( $ defs { $ sdev } { TEMPORARY } ) ;
my $ type = $ defs { $ sdev } { TYPE } ;
next if ( ! $ modules { $ type } { CanAuthenticate } ) ;
my $ hash = { SNAME = > $ sdev , TYPE = > $ type , NAME = > "SecurityCheck" } ;
2017-12-23 19:33:43 +00:00
push ( @ fnd , " $sdev is not password protected" )
if ( ! Authenticate ( $ hash , undef ) ) ;
}
if ( @ fnd ) {
push @ fnd , "" ;
my @ l = devspec2array ( "TYPE=allowed" ) ;
if ( @ l ) {
push @ fnd , "Protect this FHEM installation by " .
"configuring the allowed device $l[0]" ;
} else {
push @ fnd , "Protect this FHEM installation by " .
"defining an allowed device with define allowed allowed" ;
}
}
if ( $^O !~ m/Win/ && $< == 0 ) {
push ( @ fnd , "Running with root privileges is discouraged." )
}
if ( @ fnd ) {
unshift ( @ fnd , "SecurityCheck:" ) ;
push ( @ fnd , "You can disable this message with attr global motd none" ) ;
2020-04-17 11:15:16 +00:00
$ defs { global } { init_errors } =~ s/SecurityCheck:.*motd none//s ;
2020-04-12 12:13:17 +00:00
$ defs { global } { init_errors } . = join ( "\n" , @ fnd ) ;
2017-12-23 19:33:43 +00:00
}
}
2019-01-18 09:27:06 +00:00
#
sub genUUID ()
{
srand ( gettimeofday ( ) ) if ( ! $ srandUsed ) ;
$ srandUsed = 1 ;
2019-01-18 09:37:05 +00:00
my $ uuid = sprintf ( "%08x-f33f-%s-%s-%s" , time ( ) , substr ( getUniqueId ( ) , - 4 ) ,
2019-01-18 09:27:06 +00:00
join ( "" , map { unpack "H*" , chr ( rand ( 256 ) ) } 1 .. 2 ) ,
join ( "" , map { unpack "H*" , chr ( rand ( 256 ) ) } 1 .. 8 ) ) ;
2019-01-18 09:37:05 +00:00
$ fuuidHash { $ uuid } = 1 ;
return $ uuid ;
2019-01-18 09:27:06 +00:00
}
2019-03-16 10:57:53 +00:00
sub
IsWe ( ; $$ )
{
my ( $ when , $ wday ) = @ _ ;
2019-07-01 07:29:27 +00:00
my $ dt = ( $ when && $ when =~ m/^((\d{4})-)?([01]\d)-([0-3]\d)$/ ) ;
2021-10-01 16:21:46 +00:00
$ when = "state" if ( ! $ when ||
( $ when !~ m/^(yesterday|today|tomorrow)$/ && ! $ dt ) ) ;
2019-07-01 07:29:27 +00:00
if ( ! defined ( $ wday ) ) {
if ( $ dt ) {
my ( $ y , $ m , $ d ) = ( $ 2 ? $ 2 - 1900 : ( localtime ( ) ) [ 5 ] , $ 3 - 1 , $ 4 ) ;
$ wday = ( localtime ( mktime ( 1 , 1 , 1 , $ d , $ m , $ y , 0 , 0 , - 1 ) ) ) [ 6 ] ;
} else {
$ wday = ( localtime ( gettimeofday ( ) ) ) [ 6 ] ;
}
}
2019-06-26 16:17:29 +00:00
my ( $ we , $ wf ) ;
foreach my $ h2we ( split ( "," , AttrVal ( "global" , "holiday2we" , "" ) ) ) {
2019-07-01 07:29:27 +00:00
my $ b = $ dt ? CommandGet ( undef , "$h2we $when" ) : ReadingsVal ( $ h2we , $ when , 0 ) ;
2019-06-27 05:33:17 +00:00
if ( $ b && $ b ne "none" ) {
return 0 if ( $ h2we eq "noWeekEnd" ) ;
$ we = 1 if ( $ b && $ b ne "none" ) ;
}
2019-06-26 16:17:29 +00:00
$ wf = 1 if ( $ h2we eq "weekEnd" ) ;
}
if ( ! $ wf && ! $ we ) {
$ we = ( $ when eq "yesterday" ? ( $ wday == 0 || $ wday == 1 ) :
2019-07-01 07:29:27 +00:00
( $ when ne "tomorrow" ? ( $ wday == 6 || $ wday == 0 ) :
( $ wday == 5 || $ wday == 6 ) ) ) ; # tomorrow
2019-03-16 10:57:53 +00:00
}
return $ we ? 1 : 0 ;
}
2019-05-04 19:13:22 +00:00
sub
applyGlobalAttrFromEnv ( )
{
while ( my ( $ k , $ v ) = each % { $ globalAttrFromEnv } ) {
Log 3 , "From the FHEM_GLOBALATTR environment: attr global $k $v" ;
CommandAttr ( undef , "global $k $v" ) ;
}
}
2020-05-13 10:27:43 +00:00
# set the test config file: either the corresponding X.cfg, or fhem.cfg
sub
prepareFhemTestFile ( )
{
2020-05-30 08:34:11 +00:00
return if ( $ ARGV [ 0 ] && $ ARGV [ 0 ] ne "-t" || @ ARGV < 2 ) ;
2020-05-13 10:27:43 +00:00
shift @ ARGV ;
2020-05-14 16:07:14 +00:00
if ( $ ARGV [ 0 ] !~ m , ^ ( . * ? ) ( [ ^ / ] + ) \ . t $, || ! - r $ ARGV [ 0 ] ) {
print STDERR "Need a .t file as argument for -t\n" ;
exit ( 1 ) ;
}
2020-05-13 10:27:43 +00:00
my ( $ dir , $ fileBase ) = ( $ 1 , $ 2 ) ;
$ fhemTestFile = $ ARGV [ 0 ] ;
$ ARGV [ 0 ] = "${dir}fhem.cfg" if ( - r "${dir}fhem.cfg" ) ;
$ ARGV [ 0 ] = "$dir$fileBase.cfg" if ( - r "$dir$fileBase.cfg" ) ;
}
sub
execFhemTestFile ( )
{
return if ( ! $ fhemTestFile ) ;
$ attr { global } { autosave } = 0 ;
AnalyzeCommand ( undef , "define .ftu FhemTestUtils" )
if ( ! grep { $ defs { $ _ } { TYPE } eq "FhemTestUtils" } keys % defs ) ;
InternalTimer ( 1 , sub { require $ fhemTestFile } , 0 ) if ( $ fhemTestFile ) ;
}
2020-06-17 13:53:32 +00:00
# return undef if ok or error. Prameter: regexp, error context
sub
CheckRegexp ( $$ )
{
my ( $ re , $ context ) = @ _ ;
return "Empty regexp in $context" if ( ! defined ( $ re ) ) ;
return "Bad regexp >$re< in $context" if ( $ re =~ m/^[*+]/ ) ;
2022-03-27 10:39:22 +00:00
my $ warn ;
my $ osig = $ SIG { __WARN__ } ;
2022-03-30 07:18:07 +00:00
$ SIG { __WARN__ } = sub { $ warn = $ _ [ 0 ] } ;
2020-06-17 13:53:32 +00:00
eval { "Hallo" =~ m/^$re$/ } ;
2022-03-27 10:39:22 +00:00
$ SIG { __WARN__ } = $ osig ;
2020-06-17 13:53:32 +00:00
return "Bad regexp >$re< in $context: $@" if ( $@ ) ;
2022-03-27 10:39:22 +00:00
return "Bad regexp >$re< in $context: $warn" if ( $ warn ) ;
2020-06-17 13:53:32 +00:00
return undef ;
}
2012-02-11 23:42:47 +00:00
1 ;