Install FHEM as a Windows service (by T.E.)

git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@3788 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
rudolfkoenig 2013-08-25 11:49:30 +00:00
parent 07630e471e
commit 94acaa96dc
5 changed files with 310 additions and 26 deletions

14
CHANGED
View File

@ -1,11 +1,13 @@
# Add changes at the top of the list. Keep it in ASCII # Add changes at the top of the list. Keep it in ASCII, and 80-char wide.
- SVN - SVN
- feature: new module 33_readingsGroup to display a collection of readings from - feature: install FHEM as Windows service by T.E., see docs/HOWTO_Windows.txt
on or more devices. this will replace weblink readings. (by justme1968) - feature: new module 33_readingsGroup to display a collection of readings
from one or more devices. this will replace weblink readings.
(by justme1968)
- feature: setreading command added - feature: setreading command added
- change: DbLog: by using DbLog a new Attribute DbLogExclude will be propagated - change: DbLog: by using DbLog a new Attribute DbLogExclude will be
to all Devices. DbLogExclue will work as regexp to exclude propagated to all Devices. DbLogExclue will work as regexp to
defined readings to log exclude defined readings to log
- change: loglevel attribute deprecated/replaced by the verbose attribute - change: loglevel attribute deprecated/replaced by the verbose attribute
- change: VIERA: changed several readings/commands according to - change: VIERA: changed several readings/commands according to
DevelopmentGuidelinesAV. See FHEM Wiki and commandref for more DevelopmentGuidelinesAV. See FHEM Wiki and commandref for more

245
FHEM/WinService.pm Executable file
View File

@ -0,0 +1,245 @@
################################################################
#
# Copyright notice
#
# (c) 2013 Thomas Eckardt (Thomas.Eckardt@thockar.com)
#
# This script is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# The GNU General Public License can be found at
# http://www.gnu.org/copyleft/gpl.html.
# A copy is found in the textfile GPL.txt and important notices to the license
# from the author is found in LICENSE.txt distributed with these scripts.
#
# This script is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# This copyright notice MUST APPEAR in all copies of the script!
#
################################################################
# $Id: WinService.pm,v 1.01 2013-08-22 18:15:00Z TE $
package FHEM::WinService;
use strict;
sub __installService($$$);
sub __initService($$);
sub new($$$);
use vars qw($VERSION);
$VERSION = $1 if('$Id: WinService.pm,v 1.01 2013-08-22 18:15:00Z TE $' =~ /,v ([\d.]+) /);
###################################################
# Windows Service Handler
#
# install/remove or possibly start fhem as a Windows Service
sub
new ($$$) {
my ($class, $argv) = @_;
my $fhem = $0;
$fhem =~ s/\\/\//go;
my $fhembase = $fhem;
$fhembase =~ s/\/?[^\/]+$//o;
if (! $fhembase && eval('use Cwd();1;')) {
$fhembase = Cwd::cwd();
$fhembase =~ s/\\/\//go;
}
my $larg = $argv->[@$argv-1];
if ($larg eq '-i') {
if (! $fhembase) {
print "error: unable to detect fhem folder - cancel\n";
exit 0;
}
$fhem = $fhembase.'/'.$fhem if $fhem !~ /\//o;
my $cfg = $argv->[0];
$cfg =~ s/\\/\//go;
$cfg = $fhembase.'/'.$cfg if $cfg !~ /\//o;
print "try to install fhem windows service as: $^X $fhem $cfg\n";
__installService('-i' , $fhem, $cfg);
exit 0;
} elsif ($larg eq '-u') {
print "try to remove fhem windows service\n";
__installService('-u',undef,undef);
exit 0;
} else {
$class = ref $class || $class;
bless my $self = {}, $class;
$self->{ServiceLog} = [];
@{$self->{ServiceLog}} = __initService($self, $argv->[0]);
return $self;
}
}
###################################################
###################################################
# from here are internal subs only !
###################################################
###################################################
# install or remove fhem as a Windows Service
#
sub
__installService($$$)
{
eval(<<'EOT') or print "error: $@\n)";
use Win32::Daemon;
my $p;
my $p2;
if(lc $_[0] eq '-u') {
system('cmd.exe /C net stop fhem');
sleep(1);
Win32::Daemon::DeleteService('','fhem') ||
print "Failed to remove fhem service: " .
Win32::FormatMessage( Win32::Daemon::GetLastError() ) . "\n" & return;
print "Successfully removed service fhem\n";
} elsif( lc $_[0] eq '-i') {
unless($p=$_[1]) {
$p=$0;
$p=~s/\w+\.pl/fhem.pl/o;
}
if($p2=$_[2]) {
$p2=~s/[\\\/]$//o;
} else {
$p2=$p; $p2=~s/\.pl/.cfg/io;
}
my %Hash = (
name => 'fhem',
display => 'fhem server',
path => "\"$^X\"",
user => '',
pwd => '',
parameters => "\"$p\" \"$p2\"",
);
if( Win32::Daemon::CreateService( \%Hash ) ) {
print "fhem service successfully added.\n";
} else {
print "Failed to add fhem service: " .
Win32::FormatMessage( Win32::Daemon::GetLastError() ) . "\n";
print "Note: if you're getting an error: Service is marked for ".
"deletion, then close the service control manager window".
" and try again.\n";
}
}
1;
EOT
}
###################################################
# check if called from SCM and start the service if so
#
sub
__initService ($$) {
my ($self, $arg) = @_;
my @ServiceLog;
# check how we are called from the OS - Console or SCM
# Win32 Daemon and Console module installed ?
if( eval("use Win32::Daemon; use Win32::Console; 1;") ) {
eval(<<'EOT');
my $cmdlin = Win32::Console::_GetConsoleTitle () ? 1 : 0;
eval{&main::doGlobalDef($arg);}; # we need some config here
if ($cmdlin) {
$self->{AsAService} = 0;
} else {
$self->{AsAService} = 1;
$self->{ServiceStopping} = 0;
$main::attr{global}{nofork}=1; # this has to be set here
push @ServiceLog, 'registering fhem as Windows Service';
Win32::Daemon::StartService();
# Wait until the service manager is ready for us to continue...
my $i = 0;
while( SERVICE_START_PENDING != Win32::Daemon::State() && $i < 60) {
# looping indefinitely and waiting to start
sleep( 1 );
$i++;
}
if ($i > 59) {
push @ServiceLog,'unable to register fhem in SCM - cancel';
die "unable to register fhem in SCM - cancel\n";
}
Win32::Daemon::State( SERVICE_RUNNING );
push @ServiceLog,'starting fhem as a service';
# this sub is called in the main loop to check the service state
$self->{serviceCheck} = sub {
return unless $self->{AsAService};
my $state = Win32::Daemon::State();
my %idlestate = (
Win32::Daemon::SERVICE_PAUSE_PENDING => 1,
Win32::Daemon::SERVICE_CONTINUE_PENDING => -1
);
if( $state == SERVICE_STOP_PENDING ) {
if ($self->{ServiceStopping} == 0) {
$self->{ServiceStopping} = 1;
&main::Log(1,'service stopping');
#ask SCM for a grace time (30 seconds) to shutdown
Win32::Daemon::State( SERVICE_STOP_PENDING, 30000 );
&main::Log(1, 'service stopped');
&main::CommandShutdown(undef, undef);
$self->{ServiceStopping} = 2;
Win32::Daemon::State( SERVICE_STOPPED );
Win32::Daemon::StopService();
# be nice, tell we stopped
exit 0;
} elsif ($self->{ServiceStopping} == 1) {
# keep telling SCM we're stopping and didn't hang
Win32::Daemon::State( SERVICE_STOP_PENDING, 30000 );
}
} elsif ( $state == SERVICE_PAUSE_PENDING ) {
Win32::Daemon::State( SERVICE_PAUSED );
$self->{allIdle} = $idlestate{$state};
&main::Log(1,'pausing service');
} elsif ( $state == SERVICE_CONTINUE_PENDING ) {
Win32::Daemon::State( SERVICE_RUNNING );
$self->{allIdle} = $idlestate{$state};
&main::Log(1, 'continue service');
} else {
my $PrevState = SERVICE_RUNNING;
$PrevState = SERVICE_STOPPED if $self->{ServiceStopping};
$PrevState = SERVICE_PAUSED if $self->{allIdle} > 0;
Win32::Daemon::State( $PrevState );
undef $self->{allIdle}
if ($PrevState == SERVICE_RUNNING);
}
};
} # end if ($cmdlin)
EOT
if ($@) { # we got some Perl errors in eval
push @ServiceLog, "error: $@";
$self->{serviceCheck} = sub {}; # set it - could be destroyed
$self->{AsAService} = 0;
}
push @ServiceLog,'starting in console mode'
unless $self->{AsAService};
} else {
$self->{AsAService} = 0;
push @ServiceLog,'starting in console mode';
}
return @ServiceLog;
}
###################################################
1;

View File

@ -91,11 +91,12 @@ dist:
cp -r fhem.pl fhem.cfg CHANGED HISTORY Makefile README.SVN\ cp -r fhem.pl fhem.cfg CHANGED HISTORY Makefile README.SVN\
FHEM contrib docs www webfrontend .f FHEM contrib docs www webfrontend .f
mkdir .f/log mkdir .f/log
touch .f/log/empty_file.txt
(cd .f; perl contrib/commandref_join.pl) (cd .f; perl contrib/commandref_join.pl)
find .f -name .svn -print | xargs rm -rf find .f -name .svn -print | xargs rm -rf
find .f -name \*.orig -print | xargs rm -f find .f -name \*.orig -print | xargs rm -f
find .f -name .#\* -print | xargs rm -f find .f -name .#\* -print | xargs rm -f
find .f -type f -print | grep -v Makefile |\ find .f -type f -print | grep -v Makefile | grep -v SWAP |\
xargs perl -pi -e 's/=VERS=/$(VERS)/g;s/=DATE=/$(DATE)/g' xargs perl -pi -e 's/=VERS=/$(VERS)/g;s/=DATE=/$(DATE)/g'
mv .f $(DESTDIR) mv .f $(DESTDIR)
tar cf - $(DESTDIR) | gzip > $(DESTDIR).tar.gz tar cf - $(DESTDIR) | gzip > $(DESTDIR).tar.gz

View File

@ -1,3 +1,8 @@
The following description will show you how to install FHEM on Windows on a separate
USB-Drive, without any Windows-registry modifications.
You can use the internal HD for installation too, and you can register fhem as
a service, see below.
Install FHEM: Install FHEM:
Download the latest fhem-X.Y.tar.gz package from http://fhem.de#Download Download the latest fhem-X.Y.tar.gz package from http://fhem.de#Download
(currently it is fhem-5.4.tar.gz), and unpack it into a directory where you (currently it is fhem-5.4.tar.gz), and unpack it into a directory where you
@ -27,7 +32,7 @@ Start FHEM:
Connect to the FHEM Web frontend (FHEMWEB): Connect to the FHEM Web frontend (FHEMWEB):
Start your browser (Firefox,Chrome or Safari are preferred) and open Start your browser (Firefox, Chrome or Safari are preferred) and open
http://localhost:8083/fhem http://localhost:8083/fhem
You'll see a smiling-house icon on a light-yellow background. You'll see a smiling-house icon on a light-yellow background.
@ -49,3 +54,13 @@ not mandatory):
i.e. arrow up and RETURN or type in i.e. arrow up and RETURN or type in
perl\bin\perl fhem.pl fhem.cfg perl\bin\perl fhem.pl fhem.cfg
again. again.
Install FHEM as a service (better to install perl on the internal hard-disk for
this scenario):
Terminate fhem by typing shutdown again in the FHEMWEB command line.
Install the missing Win32::Daemon perl module by typing in the command window:
F:\tmp\fhem-5.4> PATH=F:\tmp\fhem-5.4\c\bin;F:\tmp\fhem-5.4\perl\bin;%PATH%
F:\tmp\fhem-5.4> perl\bin\cpan -i Win32::Daemon
Install FHEM as a service
F:\tmp\fhem-5.4> perl\bin\perl fhem.pl fhem.cfg -i

57
fhem.pl
View File

@ -4,7 +4,7 @@
# #
# Copyright notice # Copyright notice
# #
# (c) 2005-2012 # (c) 2005-2013
# Copyright: Rudolf Koenig (r dot koenig at koeniglich dot de) # Copyright: Rudolf Koenig (r dot koenig at koeniglich dot de)
# All rights reserved # All rights reserved
# #
@ -24,8 +24,6 @@
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# This copyright notice MUST APPEAR in all copies of the script! # This copyright notice MUST APPEAR in all copies of the script!
# Thanks for Tosti's site (<http://www.tosti.com/FHZ1000PC.html>)
# for inspiration.
# #
# Homepage: http://fhem.de # Homepage: http://fhem.de
# #
@ -59,6 +57,7 @@ sub FmtDateTime($);
sub FmtTime($); sub FmtTime($);
sub GetLogLevel(@); sub GetLogLevel(@);
sub GetTimeSpec($); sub GetTimeSpec($);
sub GlobalAttr($$$$);
sub HandleArchiving($); sub HandleArchiving($);
sub HandleTimeout(); sub HandleTimeout();
sub IOWrite($@); sub IOWrite($@);
@ -174,6 +173,7 @@ use vars qw(%addNotifyCB); # Used by event enhancers (e.g. avarage)
use vars qw(%inform); # Used by telnet_ActivateInform use vars qw(%inform); # Used by telnet_ActivateInform
use vars qw($reread_active); use vars qw($reread_active);
use vars qw($winService); # the Windows Service object
my $AttrList = "verbose:0,1,2,3,4,5 room group comment alias ". my $AttrList = "verbose:0,1,2,3,4,5 room group comment alias ".
"eventMap userReadings"; "eventMap userReadings";
@ -292,6 +292,10 @@ if(int(@ARGV) < 1) {
print "Usage:\n"; print "Usage:\n";
print "as server: fhem configfile\n"; print "as server: fhem configfile\n";
print "as client: fhem [host:]port cmd cmd cmd...\n"; print "as client: fhem [host:]port cmd cmd cmd...\n";
if($^O =~ m/Win/) {
print "install as windows service: fhem.pl configfile -i\n";
print "uninstall the windows service: fhem.pl -u\n";
}
CommandHelp(undef, undef); CommandHelp(undef, undef);
exit(1); exit(1);
} }
@ -326,7 +330,7 @@ if($^O !~ m/Win/ && $< == 0) {
################################################### ###################################################
# Client code # Client code
if(int(@ARGV) > 1) { if(int(@ARGV) > 1 && $ARGV[$#ARGV] ne "-i") {
my $buf; my $buf;
my $addr = shift @ARGV; my $addr = shift @ARGV;
$addr = "localhost:$addr" if($addr !~ m/:/); $addr = "localhost:$addr" if($addr !~ m/:/);
@ -349,27 +353,30 @@ if(int(@ARGV) > 1) {
################################################### ###################################################
# for debugging # Windows Service Support: install/remove or start the fhem service
sub if($^O =~ m/Win/) {
Debug($) { (my $dir = $0) =~ s+[/\\][^/\\]*$++; # Find the FHEM directory
my $msg= shift; chdir($dir);
Log 1, "DEBUG>" . $msg; $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;
}
} }
################################################### $winService ||= {};
################################################### ###################################################
# Server initialization # Server initialization
doGlobalDef($ARGV[0]); doGlobalDef($ARGV[0]);
# As newer Linux versions reset serial parameters after fork, we parse the # As newer Linux versions reset serial parameters after fork, we parse the
# config file after the fork. Since need some global attr parameters before, we # config file after the fork. But we need some global attr parameters before, so we
# read them here. # read them here.
setGlobalAttrBeforeFork($attr{global}{configfile}); setGlobalAttrBeforeFork($attr{global}{configfile});
Log 1, $_ for eval{@{$winService->{ServiceLog}};};
if($^O =~ m/Win/ && !$attr{global}{nofork}) { if($^O =~ m/Win/ && !$attr{global}{nofork}) {
Log 1, "Forcing 'attr global nofork' on WINDOWS";
Log 1, "set it in the config file to avoid this message";
$attr{global}{nofork}=1; $attr{global}{nofork}=1;
} }
@ -457,8 +464,10 @@ while (1) {
} }
$timeout = $readytimeout if(keys(%readyfnlist) && $timeout = $readytimeout if(keys(%readyfnlist) &&
(!defined($timeout) || $timeout > $readytimeout)); (!defined($timeout) || $timeout > $readytimeout));
$timeout = 5 if $winService->{AsAService} && $timeout > 5;
my $nfound = select($rout=$rin, undef, undef, $timeout); my $nfound = select($rout=$rin, undef, undef, $timeout);
$winService->{serviceCheck}->() if($winService->{serviceCheck});
CommandShutdown(undef, undef) if($sig_term); CommandShutdown(undef, undef) if($sig_term);
if($nfound < 0) { if($nfound < 0) {
@ -965,9 +974,10 @@ OpenLogfile($)
close(LOG); close(LOG);
$logopened=0; $logopened=0;
$currlogfile = $param; $currlogfile = $param;
if($currlogfile eq "-") {
open LOG, '>&STDOUT' or die "Can't dup stdout: $!"; # STDOUT is closed in windows services per default
if(!$winService->{AsAService} && $currlogfile eq "-") {
open LOG, '>&STDOUT' || die "Can't dup stdout: $!";
} else { } else {
@ -1213,7 +1223,12 @@ CommandShutdown($$)
WriteStatefile(); WriteStatefile();
unlink($attr{global}{pidfilename}) if($attr{global}{pidfilename}); unlink($attr{global}{pidfilename}) if($attr{global}{pidfilename});
if($param && $param eq "restart") { if($param && $param eq "restart") {
system("(sleep 2; exec $^X $0 $attr{global}{configfile})&"); if ($^O !~ m/Win/) {
system("(sleep 2; exec $^X $0 $attr{global}{configfile})&");
} elsif ($winService->{AsAService}) {
# use the OS SCM to stop and start the service
exec('cmd.exe /C net stop fhem & net start fhem');
}
} }
exit(0); exit(0);
} }
@ -1871,7 +1886,7 @@ getAllSets($)
} }
sub sub
GlobalAttr($$) GlobalAttr($$$$)
{ {
my ($type, $me, $name, $val) = @_; my ($type, $me, $name, $val) = @_;
@ -3533,4 +3548,10 @@ utf8ToLatin1($)
return $s; return $s;
} }
sub
Debug($) {
my $msg= shift;
Log 1, "DEBUG>" . $msg;
}
1; 1;