mirror of
https://github.com/fhem/fhem-mirror.git
synced 2024-11-22 16:09:49 +00:00
4bdf83bebf
git-svn-id: https://svn.fhem.de/fhem/trunk@23498 2b470e98-0d58-463d-a4d8-8e2adae1ed80
3587 lines
149 KiB
Perl
3587 lines
149 KiB
Perl
########################################################################################################################
|
|
# $Id: 60_Watches.pm 22591 2020-08-12 21:13:44Z DS_Starter $
|
|
#########################################################################################################################
|
|
# 60_Watches.pm
|
|
#
|
|
# (c) 2018-2021 by Heiko Maaz
|
|
# e-mail: Heiko dot Maaz at t-online dot de
|
|
#
|
|
# This script is part of fhem.
|
|
#
|
|
# Fhem 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.
|
|
#
|
|
# Fhem 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.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with fhem. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
# The script is based on sources from following sites:
|
|
# # modern clock: https://www.w3schools.com/graphics/canvas_clock_start.asp
|
|
# # station clock: http://www.3quarks.com/de/Bahnhofsuhr/
|
|
# # digital clock: http://www.3quarks.com/de/Segmentanzeige/index.html
|
|
#
|
|
#########################################################################################################################
|
|
package FHEM::Watches; ## no critic 'package'
|
|
|
|
use strict;
|
|
use warnings;
|
|
use Time::HiRes qw(time gettimeofday tv_interval);
|
|
use GPUtils qw(GP_Import GP_Export); # wird für den Import der FHEM Funktionen aus der fhem.pl benötigt
|
|
eval "use FHEM::Meta;1" or my $modMetaAbsent = 1; ## no critic 'eval'
|
|
|
|
# Run before module compilation
|
|
BEGIN {
|
|
# Import from main::
|
|
GP_Import(
|
|
qw(
|
|
AttrVal
|
|
defs
|
|
FW_makeImage
|
|
FW_ME
|
|
FW_subdir
|
|
IsDisabled
|
|
Log3
|
|
modules
|
|
ReadingsVal
|
|
readingsDelete
|
|
readingsBeginUpdate
|
|
readingsBulkUpdate
|
|
readingsEndUpdate
|
|
readingFnAttributes
|
|
readingsSingleUpdate
|
|
sortTopicNum
|
|
)
|
|
);
|
|
|
|
# Export to main context with different name
|
|
# my $pkg = caller(0);
|
|
# my $main = $pkg;
|
|
# $main =~ s/^(?:.+::)?([^:]+)$/main::$1\_/gx;
|
|
# foreach (@_) {
|
|
# *{ $main . $_ } = *{ $pkg . '::' . $_ };
|
|
# }
|
|
GP_Export(
|
|
qw(
|
|
Initialize
|
|
)
|
|
);
|
|
}
|
|
|
|
# Versions History intern
|
|
my %vNotesIntern = (
|
|
"0.27.1" => "09.01.2021 remove usage of sscam_tooltip.js in sub controlPanel ,Forum: https:/topic,93454.msg1119567.html#msg1119567",
|
|
"0.27.0" => "12.08.2020 control buttons, new attr hideButtons, controlButtonSize, some more changes according PBP".
|
|
"fix Random triggering of alarm or random time display at start / resume ",
|
|
"0.26.0" => "01.08.2020 add attr timeAsReading -> write into reading 'currtime' if time is displayed, ".
|
|
"add \$readingFnAttributes, set release_status to stable ",
|
|
"0.25.0" => "03.06.2020 set reading 'stoptime' in type 'stopwatch' ",
|
|
"0.24.0" => "26.05.2020 entry of countDownInit can be in format <seconds> ",
|
|
"0.23.2" => "20.05.2020 english commandref ",
|
|
"0.23.1" => "10.05.2020 some more changes for PBP severity 3 ",
|
|
"0.23.0" => "10.05.2020 attr 'digitalBorderDistance' now also valid for digital watches, some changes for PBP ",
|
|
"0.22.0" => "09.05.2020 new attr 'digitalBorderDistance' for left and rigtht border distance of digital text ",
|
|
"0.21.1" => "09.05.2020 fix calculate forerun of 'text' dynamically if digitalTextDigitNumber=0 ",
|
|
"0.21.0" => "08.05.2020 support of alarm time of model digital 'watch' ",
|
|
"0.20.1" => "08.05.2020 asynchron read digital text and much more fixes, set client as default timeSource ",
|
|
"0.20.0" => "07.05.2020 asynchron read alarmTime reading, some fixes ",
|
|
"0.19.0" => "06.05.2020 alarm event creation for watch models 'Station' and 'Station' ",
|
|
"0.18.0" => "06.05.2020 attr 'digitalTextTicker' deleted and switched to setter 'textTicker', default text switched to blank ",
|
|
"0.17.0" => "05.05.2020 new attr 'digitalTextTicker', 'digitalTextDigitNumber' ",
|
|
"0.16.0" => "04.05.2020 delete attr 'digitalDisplayText', new setter 'displayText', 'displayTextDel' ",
|
|
"0.15.1" => "04.05.2020 fix permanently events when no alarmTime is set in countdownwatch and countdown is finished ",
|
|
"0.15.0" => "04.05.2020 new attribute 'digitalSegmentType' for different segement count, also new attributes ".
|
|
"'digitalDigitAngle', 'digitalDigitDistance', 'digitalDigitHeight', 'digitalDigitWidth', 'digitalSegmentDistance' ".
|
|
"'digitalSegmentWidth', stopwatches don't stop when alarm is triggered (use notify to do it) ",
|
|
"0.14.0" => "03.05.2020 switch to packages, use setVersionInfo, support of Meta.pm ",
|
|
"0.13.0" => "03.05.2020 set resume for countdownwatch, set 'continue' removed ",
|
|
"0.12.0" => "03.05.2020 set resume for stopwatch, new 'alarmDel' command for stop watches, alarmHMS renamed to 'alarmHMSdelset' ",
|
|
"0.11.0" => "02.05.2020 alarm event stabilized, reset command for 'countdownwatch', event alarmed contains alarm time ",
|
|
"0.10.0" => "02.05.2020 renamed 'countDownDone' to 'alarmed', bug fix ",
|
|
"0.9.0" => "02.05.2020 new attribute 'timeSource' for selection of client/server time ",
|
|
"0.8.0" => "01.05.2020 new values 'countdownwatch' for attribute digitalDisplayPattern, switch all watches to server time ",
|
|
"0.7.0" => "30.04.2020 new set 'continue' for stopwatch ",
|
|
"0.6.0" => "29.04.2020 new set 'reset' for stopwatch, read 'state' and 'starttime' from readings, add csrf token support ",
|
|
"0.5.0" => "28.04.2020 new values 'stopwatch', 'staticwatch' for attribute digitalDisplayPattern ",
|
|
"0.4.0" => "20.11.2018 text display ",
|
|
"0.3.0" => "19.11.2018 digital clock added ",
|
|
"0.2.0" => "14.11.2018 station clock added ",
|
|
"0.1.0" => "13.11.2018 initial Version with modern analog clock"
|
|
);
|
|
|
|
my %hcb = ( # Hash der Steuertastendefinition
|
|
1 => {cmd => "start", img => "default/remotecontrol/black_btn_GREEN.png", },
|
|
2 => {cmd => "stop", img => "default/remotecontrol/black_btn_RED.png", },
|
|
3 => {cmd => "resume", img => "default/remotecontrol/black_btn_YELLOW.png", },
|
|
4 => {cmd => "reset", img => "default/remotecontrol/black_btn_STOP.png", },
|
|
);
|
|
|
|
my %hset = ( # Hash der Set-Werte
|
|
staticwatch => {set => "time" },
|
|
stopwatch => {set => "alarmSet alarmDel:noArg reset:noArg resume:noArg start:noArg stop:noArg" },
|
|
countdownwatch => {set => "alarmSet alarmDel:noArg reset:noArg resume:noArg start:noArg stop:noArg countDownInit" },
|
|
watch => {set => "alarmSet alarmDel:noArg" },
|
|
text => {set => "displayTextSet displayTextDel:noArg textTicker:on,off" },
|
|
time => {fn => \&_setTime },
|
|
reset => {fn => \&_setReset },
|
|
textTicker => {fn => \&_setTextTicker },
|
|
displayTextDel => {fn => \&_setDisplayTextDel },
|
|
displayTextSet => {fn => \&_setDisplayTextSet },
|
|
stop => {fn => \&_setStop },
|
|
resume => {fn => \&_setResume },
|
|
countDownInit => {fn => \&_setCountDownInit },
|
|
alarmDel => {fn => \&_setAlarmDel },
|
|
alarmSet => {fn => \&_setAlarmSet },
|
|
start => {fn => \&_setStart },
|
|
);
|
|
|
|
##############################################################################
|
|
# Initialize Funktion
|
|
##############################################################################
|
|
sub Initialize {
|
|
my ($hash) = @_;
|
|
|
|
$hash->{DefFn} = \&Define;
|
|
$hash->{SetFn} = \&Set;
|
|
$hash->{FW_summaryFn} = \&FWebFn;
|
|
$hash->{FW_detailFn} = \&FWebFn;
|
|
$hash->{AttrFn} = \&Attr;
|
|
$hash->{AttrList} = "controlButtonSize:selectnumbers,50,5,150,0,lin ".
|
|
"digitalBorderDistance:slider,0,1,40 ".
|
|
"digitalColorBackground:colorpicker ".
|
|
"digitalColorDigits:colorpicker ".
|
|
"digitalDisplayPattern:countdownwatch,staticwatch,stopwatch,text,watch ".
|
|
"digitalDigitAngle:slider,-30,0.5,30,1 ".
|
|
"digitalDigitDistance:slider,0.5,0.1,10,1 ".
|
|
"digitalDigitHeight:slider,5,0.1,50,1 ".
|
|
"digitalDigitWidth:slider,5,0.1,50,1 ".
|
|
"digitalSegmentDistance:slider,0,0.1,5,1 ".
|
|
"digitalSegmentType:7,14,16 ".
|
|
"digitalSegmentWidth:slider,0.3,0.1,3.5,1 ".
|
|
"digitalTextDigitNumber ".
|
|
"disable:1,0 ".
|
|
"hideButtons:1,0 ".
|
|
"hideDisplayName:1,0 ".
|
|
"htmlattr ".
|
|
"modernColorBackground:colorpicker ".
|
|
"modernColorHand:colorpicker ".
|
|
"modernColorFigure:colorpicker ".
|
|
"modernColorFace:colorpicker ".
|
|
"modernColorRing:colorpicker ".
|
|
"modernColorRingEdge:colorpicker ".
|
|
"stationSecondHand:Bar,HoleShaped,NewHoleShaped,No ".
|
|
"stationSecondHandBehavoir:Bouncing,Overhasty,Creeping,ElasticBouncing ".
|
|
"stationMinuteHandBehavoir:Bouncing,Creeping,ElasticBouncing ".
|
|
"stationBoss:Red,Black,Vienna,No ".
|
|
"stationMinuteHand:Bar,Pointed,Swiss,Vienna ".
|
|
"stationHourHand:Bar,Pointed,Swiss,Vienna ".
|
|
"stationStrokeDial:GermanHour,German,Austria,Swiss,Vienna,No ".
|
|
"stationBody:Round,SmallWhite,RoundGreen,Square,Vienna,No ".
|
|
"timeAsReading:1,0 ".
|
|
"timeSource:server,client ".
|
|
$readingFnAttributes;;
|
|
|
|
$hash->{FW_hideDisplayName} = 1; # Forum 88667
|
|
# $hash->{FW_addDetailToSummary} = 1;
|
|
$hash->{FW_atPageEnd} = 1; # wenn 1 -> kein Longpoll ohne informid in HTML-Tag
|
|
# $hash->{FW_deviceOverview} = 1;
|
|
|
|
eval { FHEM::Meta::InitMod( __FILE__, $hash ) }; ## no critic 'eval' # für Meta.pm (https://forum.fhem.de/index.php/topic,97589.0.html)
|
|
|
|
return;
|
|
}
|
|
|
|
##############################################################################
|
|
# Define Funktion
|
|
##############################################################################
|
|
sub Define {
|
|
my ($hash, $def) = @_;
|
|
my $name = $hash->{NAME};
|
|
my @a = split m{\s+}x, $def;
|
|
|
|
if(!$a[2]) {
|
|
return "You need to specify more parameters.\n". "Format: define <name> Watches [Modern | Station | Digital]";
|
|
}
|
|
|
|
$hash->{HELPER}{MODMETAABSENT} = 1 if($modMetaAbsent); # Modul Meta.pm nicht vorhanden
|
|
$hash->{MODEL} = uc($a[2]);
|
|
|
|
setVersionInfo($hash); # Versionsinformationen setzen
|
|
|
|
readingsSingleUpdate($hash,"state", "initialized", 1); # Init für "state"
|
|
|
|
return;
|
|
}
|
|
|
|
##############################################################################
|
|
# Set Funktion
|
|
##############################################################################
|
|
sub Set {
|
|
my ($hash, @a) = @_;
|
|
return qq{"set X" needs at least an argument} if ( @a < 2 );
|
|
my $name = $a[0];
|
|
my $opt = $a[1];
|
|
my $prop = $a[2];
|
|
my $prop1 = $a[3];
|
|
my $prop2 = $a[4];
|
|
|
|
return if(IsDisabled($name));
|
|
|
|
my $addp = AttrVal($name, "digitalDisplayPattern", "watch");
|
|
if (!$hset{$addp}) {
|
|
Log3($name, 1, "$name - ERROR - The attribute 'digitalDisplayPattern' value '$addp' is not known by module '$hash->{TYPE}'");
|
|
return;
|
|
}
|
|
|
|
my $setlist = "Unknown argument $opt, choose one of ";
|
|
$setlist .= "$hset{$addp}{set} ";
|
|
|
|
my $params = {
|
|
hash => $hash,
|
|
name => $name,
|
|
opt => $opt,
|
|
prop => $prop,
|
|
prop1 => $prop1,
|
|
prop2 => $prop2,
|
|
addp => $addp,
|
|
aref => \@a,
|
|
};
|
|
|
|
no strict "refs"; ## no critic 'NoStrict'
|
|
if($hset{$opt}) {
|
|
my $ret = "";
|
|
$ret = &{$hset{$opt}{fn}} ($params) if(defined &{$hset{$opt}{fn}});
|
|
return $ret;
|
|
}
|
|
use strict "refs";
|
|
|
|
return $setlist;
|
|
}
|
|
|
|
################################################################
|
|
# Setter start
|
|
################################################################
|
|
sub _setStart {
|
|
my $paref = shift;
|
|
my $hash = $paref->{hash};
|
|
my $name = $paref->{name};
|
|
my $addp = $paref->{addp};
|
|
|
|
return qq{Please set "countDownInit" before !} if($addp =~ /countdownwatch/x && !ReadingsVal($name, "countInitVal", ""));
|
|
|
|
my $ms = int(time*1000);
|
|
|
|
readingsBeginUpdate ($hash);
|
|
readingsBulkUpdate ($hash, "alarmed", 0) if($addp =~ /stopwatch|countdownwatch/x);
|
|
readingsBulkUpdate ($hash, "starttime", $ms);
|
|
readingsBulkUpdate ($hash, "state", "started");
|
|
readingsEndUpdate ($hash, 1);
|
|
|
|
return;
|
|
}
|
|
|
|
################################################################
|
|
# Setter alarmSet
|
|
################################################################
|
|
sub _setAlarmSet {
|
|
my $paref = shift;
|
|
my $hash = $paref->{hash};
|
|
my $name = $paref->{name};
|
|
my $opt = $paref->{opt};
|
|
my $prop = $paref->{prop} // 70;
|
|
my $prop1 = $paref->{prop1} // 70;
|
|
my $prop2 = $paref->{prop2} // 70;
|
|
|
|
my $msg = qq{The value for "$opt" is invalid. Use parameter "hh mm ss" like "19 45 13".};
|
|
|
|
return $msg if($prop>23 || $prop1>59 || $prop2>59);
|
|
|
|
my $at = sprintf("%02d",$prop).":".sprintf("%02d",$prop1).":".sprintf("%02d",$prop2);
|
|
|
|
readingsSingleUpdate($hash, "alarmed", 0, 0);
|
|
readingsSingleUpdate($hash, "alarmTime", $at, 1);
|
|
|
|
return;
|
|
}
|
|
|
|
################################################################
|
|
# Setter alarmDel
|
|
################################################################
|
|
sub _setAlarmDel {
|
|
my $paref = shift;
|
|
my $name = $paref->{name};
|
|
|
|
delReadings ($name, "alarmTime");
|
|
delReadings ($name, "alarmed");
|
|
|
|
return;
|
|
}
|
|
|
|
################################################################
|
|
# Setter countDownInit
|
|
################################################################
|
|
sub _setCountDownInit {
|
|
my $paref = shift;
|
|
my $hash = $paref->{hash};
|
|
my $name = $paref->{name};
|
|
my $opt = $paref->{opt};
|
|
my $prop = $paref->{prop};
|
|
my $prop1 = $paref->{prop1};
|
|
my $prop2 = $paref->{prop2};
|
|
|
|
my $ct;
|
|
my $msg = qq{The value for "$opt" is invalid. Use parameter "hh mm ss" like "19 45 13" \nor alternatively only one entry in seconds.};
|
|
|
|
if($prop && $prop1) { # Format: hh mm ss
|
|
$prop2 = defined $prop2 ? $prop2 : 70; # Sekunden
|
|
return $msg if($prop>23 || $prop1>59 || $prop2>59);
|
|
$ct = $prop*3600 + $prop1*60 + $prop2; # in Sekunden umgewandelt !
|
|
}
|
|
elsif ($prop && !$prop1) { # Format: Sekundenangabe
|
|
$ct = $prop;
|
|
}
|
|
else {
|
|
return $msg;
|
|
|
|
}
|
|
|
|
delReadings ($name, "countInitVal");
|
|
|
|
readingsBeginUpdate ($hash);
|
|
readingsBulkUpdate ($hash, "countInitVal", $ct );
|
|
readingsBulkUpdate ($hash, "state", "initialized");
|
|
readingsEndUpdate ($hash, 1);
|
|
|
|
return;
|
|
}
|
|
|
|
################################################################
|
|
# Setter resume
|
|
################################################################
|
|
sub _setResume {
|
|
my $paref = shift;
|
|
my $hash = $paref->{hash};
|
|
my $name = $paref->{name};
|
|
my $addp = $paref->{addp};
|
|
|
|
return qq{Please set "countDownInit" before !} if($addp =~ /countdownwatch/x && !ReadingsVal($name, "countInitVal", ""));
|
|
return if(ReadingsVal($name, "state", "") eq "started");
|
|
|
|
my $ms = int(time*1000);
|
|
readingsSingleUpdate($hash, "starttime", $ms, 0);
|
|
readingsSingleUpdate($hash, "state", "resumed", 1);
|
|
|
|
return;
|
|
}
|
|
|
|
################################################################
|
|
# Setter stop
|
|
################################################################
|
|
sub _setStop {
|
|
my $paref = shift;
|
|
my $hash = $paref->{hash};
|
|
|
|
readingsSingleUpdate($hash, "state", "stopped", 1);
|
|
|
|
return;
|
|
}
|
|
|
|
################################################################
|
|
# Setter displayTextSet
|
|
################################################################
|
|
sub _setDisplayTextSet {
|
|
my $paref = shift;
|
|
my $hash = $paref->{hash};
|
|
my $aref = $paref->{aref};
|
|
|
|
my @a = @$aref;
|
|
|
|
shift @a; shift @a;
|
|
|
|
my $txt = join (" ", @a);
|
|
$txt =~ s/[\r\n]//gx;
|
|
readingsSingleUpdate($hash, "displayText", $txt, 1);
|
|
|
|
return;
|
|
}
|
|
|
|
################################################################
|
|
# Setter displayTextDel
|
|
################################################################
|
|
sub _setDisplayTextDel {
|
|
my $paref = shift;
|
|
my $name = $paref->{name};
|
|
|
|
delReadings ($name, "displayText");
|
|
|
|
return;
|
|
}
|
|
|
|
################################################################
|
|
# Setter textTicker
|
|
################################################################
|
|
sub _setTextTicker {
|
|
my $paref = shift;
|
|
my $hash = $paref->{hash};
|
|
my $prop = $paref->{prop};
|
|
|
|
if($prop eq "on") {
|
|
readingsSingleUpdate($hash, "displayTextTicker", "on", 1);
|
|
}
|
|
else {
|
|
readingsSingleUpdate($hash, "displayTextTicker", "off", 1);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
################################################################
|
|
# Setter reset
|
|
################################################################
|
|
sub _setReset {
|
|
my $paref = shift;
|
|
my $hash = $paref->{hash};
|
|
my $name = $paref->{name};
|
|
|
|
delReadings ($name);
|
|
readingsSingleUpdate($hash, "state", "initialized", 1);
|
|
|
|
return;
|
|
}
|
|
|
|
################################################################
|
|
# Setter time
|
|
################################################################
|
|
sub _setTime {
|
|
my $paref = shift;
|
|
my $hash = $paref->{hash};
|
|
my $opt = $paref->{opt};
|
|
my $prop = sprintf("%0d", $paref->{prop} // 0);
|
|
my $prop1 = sprintf("%0d", $paref->{prop1} // 0);
|
|
my $prop2 = sprintf("%0d", $paref->{prop2} // 0);
|
|
|
|
my $msg = qq{The value for "$opt" is invalid. Use parameter "hh mm ss" like "19 45 13"};
|
|
|
|
return $msg if($prop>23 || $prop1>59 || $prop2>59);
|
|
|
|
readingsBeginUpdate ($hash);
|
|
readingsBulkUpdate ($hash, "hour", $prop);
|
|
readingsBulkUpdate ($hash, "minute", $prop1);
|
|
readingsBulkUpdate ($hash, "second", $prop2);
|
|
readingsEndUpdate ($hash, 1);
|
|
|
|
return;
|
|
}
|
|
|
|
##############################################################################
|
|
# Attributfunktion
|
|
##############################################################################
|
|
sub Attr { ## no critic 'complexity'
|
|
my ($cmd,$name,$aName,$aVal) = @_;
|
|
my $hash = $defs{$name};
|
|
my ($do,$val);
|
|
|
|
# $cmd can be "del" or "set"
|
|
# $name is device name
|
|
# aName and aVal are Attribute name and value
|
|
|
|
if ($cmd eq "set" && $hash->{MODEL} !~ /modern/ix && $aName =~ /^modern/x) {
|
|
return qq{"$aName" is only valid for Watches model "Modern"};
|
|
}
|
|
|
|
if ($cmd eq "set" && $hash->{MODEL} !~ /station/ix && $aName =~ /^station/x) {
|
|
return qq{"$aName" is only valid for Watches model "Station"};
|
|
}
|
|
|
|
if ($cmd eq "set" && $hash->{MODEL} !~ /digital/ix && $aName =~ /^digital/x) {
|
|
return qq{"$aName" is only valid for Watches model "Digital"};
|
|
}
|
|
|
|
if ($aName eq "disable") {
|
|
if($cmd eq "set") {
|
|
$do = ($aVal) ? 1 : 0;
|
|
}
|
|
$do = 0 if($cmd eq "del");
|
|
$val = ($do == 1 ? "disabled" : "initialized");
|
|
|
|
readingsSingleUpdate($hash, "state", $val, 1);
|
|
}
|
|
|
|
if ($aName eq "timeAsReading") {
|
|
if($cmd eq "set") {
|
|
$do = $aVal;
|
|
}
|
|
$do = 0 if($cmd eq "del");
|
|
|
|
if(!$do) {
|
|
delReadings ($name, "currtime");
|
|
}
|
|
}
|
|
|
|
if ($aName eq "digitalDisplayPattern") {
|
|
if($cmd eq "set") {
|
|
$do = $aVal;
|
|
}
|
|
$do = 0 if($cmd eq "del");
|
|
|
|
if($do ne "text") {
|
|
delReadings ($name);
|
|
}
|
|
else {
|
|
delReadings ($name,undef,"^display.*");
|
|
}
|
|
|
|
readingsSingleUpdate($hash, "state", "initialized", 1);
|
|
|
|
if($do =~ /\bstopwatch\b/x) {
|
|
my $ms = int(time*1000);
|
|
readingsSingleUpdate($hash, "starttime", $ms, 0);
|
|
}
|
|
}
|
|
|
|
if ($cmd eq "set") {
|
|
if ($aName =~ /digitalTextDigitNumber|digitalBorderDistance/x && $aVal !~ /^[0-9]+$/x) {
|
|
return qq{The value of "$aName" is not valid. Only integers are allowed !};
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
##############################################################################
|
|
# Webanzeige des Devices
|
|
##############################################################################
|
|
sub FWebFn {
|
|
my ($FW_wname, $d, $room, $pageHash) = @_; # pageHash is set for summaryFn.
|
|
my $hash = $defs{$d};
|
|
|
|
my $alias = AttrVal($d, "alias", $d); # Linktext als Aliasname oder Devicename setzen
|
|
my $dlink = qq{<a href="$FW_ME?detail=$d">$alias</a>};
|
|
|
|
my $ret = "";
|
|
$ret .= "<span>$dlink </span><br>" if(!AttrVal($d,"hideDisplayName",0));
|
|
if(IsDisabled($d)) {
|
|
if(AttrVal($d,"hideDisplayName",0)) {
|
|
$ret .= qq{Watch <a href="$FW_ME?detail=$d">$d</a> is disabled};
|
|
}
|
|
else {
|
|
$ret .= "<html>Watch is disabled</html>";
|
|
}
|
|
}
|
|
else {
|
|
$ret .= modernWatch ($d) if($hash->{MODEL} =~ /modern/ix);
|
|
$ret .= stationWatch($d) if($hash->{MODEL} =~ /station/ix);
|
|
$ret .= digitalWatch($d) if($hash->{MODEL} =~ /digital/ix);
|
|
}
|
|
|
|
my $ddp = AttrVal($d, "digitalDisplayPattern", ""); # Steuertastenpaneel
|
|
my $hb = AttrVal($d, "hideButtons", 0);
|
|
if($ddp =~ /countdownwatch|stopwatch/x && !$hb) {
|
|
$ret .= controlPanel ($d);
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
###############################################################################
|
|
# Paneel für Stoppuhr Steuertasten
|
|
###############################################################################
|
|
sub controlPanel {
|
|
my $name = shift;
|
|
my $pbs = AttrVal($name,"controlButtonSize", 100); # Größe der Druckbuttons in %
|
|
my $iconpath = "www/images";
|
|
my $ret = "";
|
|
|
|
$ret .= "<style>TD.controlv1 { padding: 3px 3px; } </style>";
|
|
$ret .= "<style>TD.controlv2 { padding: 8px 8px; } </style>";
|
|
$ret .= "<style>.defsize { font-size:16px; } </style>";
|
|
|
|
$ret .= '<table class="defsize">';
|
|
$ret .= "<tr>";
|
|
|
|
for my $btn (sort keys %hcb) {
|
|
my $cmd = $hcb{$btn}{cmd};
|
|
my $img = $hcb{$btn}{img};
|
|
next if(!$cmd || !$img);
|
|
|
|
$ret .= "<td class='controlv1'>" if($pbs <= 100);
|
|
$ret .= "<td class='controlv2'>" if($pbs >= 105);
|
|
|
|
if ($img =~ m/\.svg/x) { # Verwendung für SVG's
|
|
$img = FW_makeImage($img, $cmd, "rc-button");
|
|
}
|
|
else { # $FW_ME = URL-Pfad unter dem der FHEMWEB-Server via HTTP erreichbar ist, z.B. /fhem
|
|
$img = "<img src=\"$FW_ME/$iconpath/$img\" height=\"$pbs%\" width=\"$pbs%\">";
|
|
}
|
|
|
|
my $cmd1 = "FW_cmd('$FW_ME$FW_subdir?XHR=1&cmd=set $name $cmd')"; # $FW_subdir = Sub-path in URL, used by FLOORPLAN/weblink
|
|
$ret .= "<a onClick=\"$cmd1\" title=\"$cmd\">$img</a>";
|
|
|
|
$ret .= "</td>";
|
|
}
|
|
|
|
$ret .= "</tr>";
|
|
$ret .= "</table>";
|
|
|
|
return $ret;
|
|
}
|
|
|
|
##############################################################################
|
|
# löscht alle oder das spezifizierte Reading (außer state)
|
|
# $todel = nur dieses Reading löschen
|
|
# $supress = Reading (Regex) nicht löschen
|
|
##############################################################################
|
|
sub delReadings {
|
|
my ($name,$todel,$supress) = @_;
|
|
my $hash = $defs{$name};
|
|
|
|
my $addp = AttrVal($name, "digitalDisplayPattern", "watch");
|
|
|
|
if($todel) {
|
|
readingsDelete($hash,$todel);
|
|
return;
|
|
}
|
|
|
|
my @allrds = keys%{$hash->{READINGS}};
|
|
for my $key(@allrds) {
|
|
next if($key =~ /\bstate\b/x);
|
|
next if(defined $supress && $key =~ /$supress/x);
|
|
readingsDelete($hash,$key);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
##############################################################################
|
|
# Digitale Uhr / Anzeige aus:
|
|
# http://www.3quarks.com/de/Segmentanzeige/index.html
|
|
#
|
|
##############################################################################
|
|
sub digitalWatch {
|
|
my ($d) = @_;
|
|
my $hash = $defs{$d};
|
|
my $alarmdef = "00:00:00";
|
|
my $bgc = AttrVal($d, "digitalColorBackground", "C4C4C4");
|
|
my $dcd = AttrVal($d, "digitalColorDigits", "000000");
|
|
my $addp = AttrVal($d, "digitalDisplayPattern", "watch");
|
|
my $adst = AttrVal($d, "digitalSegmentType", 7);
|
|
my $adsw = AttrVal($d, "digitalSegmentWidth", 1.5);
|
|
my $addh = AttrVal($d, "digitalDigitHeight", 20);
|
|
my $addw = AttrVal($d, "digitalDigitWidth", 12);
|
|
my $addd = AttrVal($d, "digitalDigitDistance", 2);
|
|
my $adsd = AttrVal($d, "digitalSegmentDistance", 0.5);
|
|
my $adda = AttrVal($d, "digitalDigitAngle", 9);
|
|
my $adtdn = AttrVal($d, "digitalTextDigitNumber", 0);
|
|
my $abdist = AttrVal($d, "digitalBorderDistance", 8);
|
|
my $hattr = AttrVal($d, "htmlattr", "width='150' height='50'");
|
|
my $tsou = AttrVal($d, "timeSource", "client");
|
|
my $showct = AttrVal($d, "timeAsReading", 0);
|
|
|
|
my $deftxt = " ";
|
|
my $rdtt = ReadingsVal ($d, "displayTextTicker", "off");
|
|
my $ddt = ReadingsVal ($d, "displayText", $deftxt);
|
|
my $alarm = ReadingsVal ($d, "alarmTime", "aa:bb:cc");
|
|
|
|
my ($h,$m,$s,$txtc) = (0,0,0,0);
|
|
|
|
my $bdist = ""; # Abstand zum linken und rechten Rand
|
|
for (my $i=0; $i<=$abdist; $i++ ) {
|
|
$bdist .= " ";
|
|
}
|
|
|
|
if ($addp eq "stopwatch") {
|
|
$alarmdef = "aa:bb:cc"; # Stoppuhr bei Start 00:00:00 nicht Alerm auslösen
|
|
}
|
|
|
|
if ($addp eq "staticwatch") { # statische Uhrzeitanzeige
|
|
$h = ReadingsVal($d, "hour" , 0);
|
|
$m = ReadingsVal($d, "minute", 0);
|
|
$s = ReadingsVal($d, "second", 0);
|
|
}
|
|
|
|
my $back = << "END_JS";
|
|
<html>
|
|
<body>
|
|
|
|
<canvas id='display_$d' $hattr style='background-color:#$bgc'></canvas>
|
|
|
|
<script>
|
|
// Segment display types
|
|
SegmentDisplay_$d.SevenSegment = 7;
|
|
SegmentDisplay_$d.FourteenSegment = 14;
|
|
SegmentDisplay_$d.SixteenSegment = 16;
|
|
|
|
// Segment corner types
|
|
SegmentDisplay_$d.SymmetricCorner = 0;
|
|
SegmentDisplay_$d.SquaredCorner = 1;
|
|
SegmentDisplay_$d.RoundedCorner = 3;
|
|
|
|
// Definition variables
|
|
var state_$d;
|
|
var st_$d;
|
|
var ct_$d;
|
|
var ci_$d;
|
|
var csrf;
|
|
var url_$d;
|
|
var devName_$d;
|
|
var selVal_$d;
|
|
var hours_$d;
|
|
var minutes_$d;
|
|
var seconds_$d;
|
|
var startDate_$d;
|
|
var ddt_$d;
|
|
var almtime0_$d = '$alarm'; // Alarmzeit initialisieren
|
|
var digitxt_$d = '$deftxt'; // default Digitaltext initialisieren
|
|
var tticker_$d = '$rdtt'; // Tickereinstellung initialisieren
|
|
var zmodulo_$d = 0; // Hilfszähler
|
|
var showCurrTime_$d = '$showct'; // Reading currtime schreiben oder nicht
|
|
var distBorderright_$d = '$bdist'; // Abstand zum rechten Rand
|
|
var distBorderleft_$d = '$bdist'; // Abstand zum linken Rand
|
|
var asydone1_$d = 0; // Statusbit dass asynchrone Datenkommunikation stattgefunden hat
|
|
var asydone2_$d = 0; // Statusbit dass asynchrone Datenkommunikation stattgefunden hat
|
|
var allowSetStopTime; // erlaube / verbiete Setzen Reading stoptime
|
|
|
|
function SegmentDisplay_$d(displayId_$d) {
|
|
this.displayId_$d = displayId_$d;
|
|
this.pattern = '##:##:##';
|
|
this.value = '12:34:56';
|
|
this.digitHeight = 20;
|
|
this.digitWidth = 10;
|
|
this.digitDistance = 2.5;
|
|
this.displayAngle = 12;
|
|
this.segmentWidth = 2.5;
|
|
this.segmentDistance = 0.2;
|
|
|
|
if ($adst == '7') {
|
|
this.segmentCount = SegmentDisplay_$d.SevenSegment;
|
|
}
|
|
|
|
if ($adst == '14') {
|
|
this.segmentCount = SegmentDisplay_$d.FourteenSegment;
|
|
}
|
|
|
|
if ($adst == '16') {
|
|
this.segmentCount = SegmentDisplay_$d.SixteenSegment;
|
|
}
|
|
|
|
this.cornerType = SegmentDisplay_$d.RoundedCorner;
|
|
this.colorOn = 'rgb(233, 93, 15)';
|
|
this.colorOff = 'rgb(75, 30, 5)';
|
|
};
|
|
|
|
var display_$d = new SegmentDisplay_$d('display_$d');
|
|
display_$d.cornerType = 2;
|
|
display_$d.displayAngle = $adda; // Zeichenwinkel: -30 - 30 (9)
|
|
display_$d.digitHeight = $addh; // Zeichenhöhe: 5 - 50 (20)
|
|
display_$d.digitWidth = $addw; // Zeichenbreite: 5 - 50 (12)
|
|
display_$d.digitDistance = $addd; // Zeichenabstand: 0.5 - 10 (2)
|
|
display_$d.segmentWidth = $adsw; // Stärke des einzelnen Segments: 0.3 - 3.5 (1.5)
|
|
display_$d.segmentDistance = $adsd; // Abstand der Einzelsegmente: 0 - 5 (0.5)
|
|
display_$d.colorOn = '#$dcd'; // original: display_$d.colorOn = 'rgba(0, 0, 0, 0.9)';
|
|
display_$d.colorOff = 'rgba(0, 0, 0, 0.1)';
|
|
|
|
SegmentDisplay_$d.prototype.setValue = function(value) {
|
|
this.value = value;
|
|
this.draw();
|
|
};
|
|
|
|
SegmentDisplay_$d.prototype.draw = function() {
|
|
var display_$d = document.getElementById(this.displayId_$d);
|
|
if (display_$d) {
|
|
var context = display_$d.getContext('2d');
|
|
if (context) {
|
|
// clear canvas
|
|
context.clearRect(0, 0, display_$d.width, display_$d.height);
|
|
|
|
// compute and check display width
|
|
var width = 0;
|
|
var first = true;
|
|
if (this.pattern) {
|
|
for (var i = 0; i < this.pattern.length; i++) {
|
|
var c = this.pattern.charAt(i).toLowerCase();
|
|
if (c == '#') {
|
|
width += this.digitWidth;
|
|
} else if (c == '.' || c == ':') {
|
|
width += this.segmentWidth;
|
|
} else if (c != ' ') {
|
|
return;
|
|
}
|
|
width += first ? 0 : this.digitDistance;
|
|
first = false;
|
|
}
|
|
}
|
|
if (width <= 0) {
|
|
return;
|
|
}
|
|
|
|
// compute skew factor
|
|
var angle = -1.0 * Math.max(-45.0, Math.min(45.0, this.displayAngle));
|
|
var skew = Math.tan((angle * Math.PI) / 180.0);
|
|
|
|
// compute scale factor
|
|
var scale = Math.min(display_$d.width / (width + Math.abs(skew * this.digitHeight)), display_$d.height / this.digitHeight);
|
|
|
|
// compute display offset
|
|
var offsetX = (display_$d.width - (width + skew * this.digitHeight) * scale) / 2.0;
|
|
var offsetY = (display_$d.height - this.digitHeight * scale) / 2.0;
|
|
|
|
// context transformation
|
|
context.save();
|
|
context.translate(offsetX, offsetY);
|
|
context.scale(scale, scale);
|
|
context.transform(1, 0, skew, 1, 0, 0);
|
|
|
|
// draw segments
|
|
var xPos = 0;
|
|
var size = (this.value) ? this.value.length : 0;
|
|
for (var i = 0; i < this.pattern.length; i++) {
|
|
var mask = this.pattern.charAt(i);
|
|
var value = (i < size) ? this.value.charAt(i).toLowerCase() : ' ';
|
|
xPos += this.drawDigit(context, xPos, mask, value);
|
|
}
|
|
|
|
// finish drawing
|
|
context.restore();
|
|
}
|
|
}
|
|
};
|
|
|
|
SegmentDisplay_$d.prototype.drawDigit = function(context, xPos, mask, c) {
|
|
switch (mask) {
|
|
case '#':
|
|
var r = Math.sqrt(this.segmentWidth * this.segmentWidth / 2.0);
|
|
var d = Math.sqrt(this.segmentDistance * this.segmentDistance / 2.0);
|
|
var e = d / 2.0;
|
|
var f = (this.segmentWidth - d) * Math.sin((45.0 * Math.PI) / 180.0);
|
|
var g = f / 2.0;
|
|
var h = (this.digitHeight - 3.0 * this.segmentWidth) / 2.0;
|
|
var w = (this.digitWidth - 3.0 * this.segmentWidth) / 2.0;
|
|
var s = this.segmentWidth / 2.0;
|
|
var t = this.digitWidth / 2.0;
|
|
|
|
// draw segment a (a1 and a2 for 16 segments)
|
|
if (this.segmentCount == 16) {
|
|
var x = xPos;
|
|
var y = 0;
|
|
context.fillStyle = this.getSegmentColor_$d(c, null, '02356789abcdefgiopqrstz@%');
|
|
context.beginPath();
|
|
switch (this.cornerType) {
|
|
case SegmentDisplay_$d.SymmetricCorner:
|
|
context.moveTo(x + s + d, y + s);
|
|
context.lineTo(x + this.segmentWidth + d, y);
|
|
break;
|
|
case SegmentDisplay_$d.SquaredCorner:
|
|
context.moveTo(x + s + e, y + s - e);
|
|
context.lineTo(x + this.segmentWidth, y);
|
|
break;
|
|
default:
|
|
context.moveTo(x + this.segmentWidth - f, y + this.segmentWidth - f - d);
|
|
context.quadraticCurveTo(x + this.segmentWidth - g, y, x + this.segmentWidth, y);
|
|
}
|
|
context.lineTo(x + t - d - s, y);
|
|
context.lineTo(x + t - d, y + s);
|
|
context.lineTo(x + t - d - s, y + this.segmentWidth);
|
|
context.lineTo(x + this.segmentWidth + d, y + this.segmentWidth);
|
|
context.fill();
|
|
|
|
var x = xPos;
|
|
var y = 0;
|
|
context.fillStyle = this.getSegmentColor_$d(c, null, '02356789abcdefgiopqrstz\@');
|
|
context.beginPath();
|
|
context.moveTo(x + this.digitWidth - this.segmentWidth - d, y + this.segmentWidth);
|
|
context.lineTo(x + t + d + s, y + this.segmentWidth);
|
|
context.lineTo(x + t + d, y + s);
|
|
context.lineTo(x + t + d + s, y);
|
|
switch (this.cornerType) {
|
|
case SegmentDisplay_$d.SymmetricCorner:
|
|
context.lineTo(x + this.digitWidth - this.segmentWidth - d, y);
|
|
context.lineTo(x + this.digitWidth - s - d, y + s);
|
|
break;
|
|
case SegmentDisplay_$d.SquaredCorner:
|
|
context.lineTo(x + this.digitWidth - this.segmentWidth, y);
|
|
context.lineTo(x + this.digitWidth - s - e, y + s - e);
|
|
break;
|
|
default:
|
|
context.lineTo(x + this.digitWidth - this.segmentWidth, y);
|
|
context.quadraticCurveTo(x + this.digitWidth - this.segmentWidth + g, y, x + this.digitWidth - this.segmentWidth + f, y + this.segmentWidth - f - d);
|
|
}
|
|
context.fill();
|
|
|
|
} else {
|
|
var x = xPos;
|
|
var y = 0;
|
|
context.fillStyle = this.getSegmentColor_$d(c, '02356789acefp', '02356789abcdefgiopqrstz\@');
|
|
context.beginPath();
|
|
switch (this.cornerType) {
|
|
case SegmentDisplay_$d.SymmetricCorner:
|
|
context.moveTo(x + s + d, y + s);
|
|
context.lineTo(x + this.segmentWidth + d, y);
|
|
context.lineTo(x + this.digitWidth - this.segmentWidth - d, y);
|
|
context.lineTo(x + this.digitWidth - s - d, y + s);
|
|
break;
|
|
case SegmentDisplay_$d.SquaredCorner:
|
|
context.moveTo(x + s + e, y + s - e);
|
|
context.lineTo(x + this.segmentWidth, y);
|
|
context.lineTo(x + this.digitWidth - this.segmentWidth, y);
|
|
context.lineTo(x + this.digitWidth - s - e, y + s - e);
|
|
break;
|
|
default:
|
|
context.moveTo(x + this.segmentWidth - f, y + this.segmentWidth - f - d);
|
|
context.quadraticCurveTo(x + this.segmentWidth - g, y, x + this.segmentWidth, y);
|
|
context.lineTo(x + this.digitWidth - this.segmentWidth, y);
|
|
context.quadraticCurveTo(x + this.digitWidth - this.segmentWidth + g, y, x + this.digitWidth - this.segmentWidth + f, y + this.segmentWidth - f - d);
|
|
}
|
|
context.lineTo(x + this.digitWidth - this.segmentWidth - d, y + this.segmentWidth);
|
|
context.lineTo(x + this.segmentWidth + d, y + this.segmentWidth);
|
|
context.fill();
|
|
}
|
|
|
|
// draw segment b
|
|
x = xPos + this.digitWidth - this.segmentWidth;
|
|
y = 0;
|
|
context.fillStyle = this.getSegmentColor_$d(c, '01234789adhpy', '01234789abdhjmnopqruwy');
|
|
context.beginPath();
|
|
switch (this.cornerType) {
|
|
case SegmentDisplay_$d.SymmetricCorner:
|
|
context.moveTo(x + s, y + s + d);
|
|
context.lineTo(x + this.segmentWidth, y + this.segmentWidth + d);
|
|
break;
|
|
case SegmentDisplay_$d.SquaredCorner:
|
|
context.moveTo(x + s + e, y + s + e);
|
|
context.lineTo(x + this.segmentWidth, y + this.segmentWidth);
|
|
break;
|
|
default:
|
|
context.moveTo(x + f + d, y + this.segmentWidth - f);
|
|
context.quadraticCurveTo(x + this.segmentWidth, y + this.segmentWidth - g, x + this.segmentWidth, y + this.segmentWidth);
|
|
}
|
|
context.lineTo(x + this.segmentWidth, y + h + this.segmentWidth - d);
|
|
context.lineTo(x + s, y + h + this.segmentWidth + s - d);
|
|
context.lineTo(x, y + h + this.segmentWidth - d);
|
|
context.lineTo(x, y + this.segmentWidth + d);
|
|
context.fill();
|
|
|
|
// draw segment c
|
|
x = xPos + this.digitWidth - this.segmentWidth;
|
|
y = h + this.segmentWidth;
|
|
context.fillStyle = this.getSegmentColor_$d(c, '013456789abdhnouy', '01346789abdghjmnoqsuw\@', '%');
|
|
context.beginPath();
|
|
context.moveTo(x, y + this.segmentWidth + d);
|
|
context.lineTo(x + s, y + s + d);
|
|
context.lineTo(x + this.segmentWidth, y + this.segmentWidth + d);
|
|
context.lineTo(x + this.segmentWidth, y + h + this.segmentWidth - d);
|
|
switch (this.cornerType) {
|
|
case SegmentDisplay_$d.SymmetricCorner:
|
|
context.lineTo(x + s, y + h + this.segmentWidth + s - d);
|
|
context.lineTo(x, y + h + this.segmentWidth - d);
|
|
break;
|
|
case SegmentDisplay_$d.SquaredCorner:
|
|
context.lineTo(x + s + e, y + h + this.segmentWidth + s - e);
|
|
context.lineTo(x, y + h + this.segmentWidth - d);
|
|
break;
|
|
default:
|
|
context.quadraticCurveTo(x + this.segmentWidth, y + h + this.segmentWidth + g, x + f + d, y + h + this.segmentWidth + f);
|
|
context.lineTo(x, y + h + this.segmentWidth - d);
|
|
}
|
|
context.fill();
|
|
|
|
// draw segment d (d1 and d2 for 16 segments)
|
|
if (this.segmentCount == 16) {
|
|
x = xPos;
|
|
y = this.digitHeight - this.segmentWidth;
|
|
context.fillStyle = this.getSegmentColor_$d(c, null, '0235689bcdegijloqsuz_=\@');
|
|
context.beginPath();
|
|
context.moveTo(x + this.segmentWidth + d, y);
|
|
context.lineTo(x + t - d - s, y);
|
|
context.lineTo(x + t - d, y + s);
|
|
context.lineTo(x + t - d - s, y + this.segmentWidth);
|
|
switch (this.cornerType) {
|
|
case SegmentDisplay_$d.SymmetricCorner:
|
|
context.lineTo(x + this.segmentWidth + d, y + this.segmentWidth);
|
|
context.lineTo(x + s + d, y + s);
|
|
break;
|
|
case SegmentDisplay_$d.SquaredCorner:
|
|
context.lineTo(x + this.segmentWidth, y + this.segmentWidth);
|
|
context.lineTo(x + s + e, y + s + e);
|
|
break;
|
|
default:
|
|
context.lineTo(x + this.segmentWidth, y + this.segmentWidth);
|
|
context.quadraticCurveTo(x + this.segmentWidth - g, y + this.segmentWidth, x + this.segmentWidth - f, y + f + d);
|
|
context.lineTo(x + this.segmentWidth - f, y + f + d);
|
|
}
|
|
context.fill();
|
|
|
|
x = xPos;
|
|
y = this.digitHeight - this.segmentWidth;
|
|
context.fillStyle = this.getSegmentColor_$d(c, null, '0235689bcdegijloqsuz_=\@', '%');
|
|
context.beginPath();
|
|
context.moveTo(x + t + d + s, y + this.segmentWidth);
|
|
context.lineTo(x + t + d, y + s);
|
|
context.lineTo(x + t + d + s, y);
|
|
context.lineTo(x + this.digitWidth - this.segmentWidth - d, y);
|
|
switch (this.cornerType) {
|
|
case SegmentDisplay_$d.SymmetricCorner:
|
|
context.lineTo(x + this.digitWidth - s - d, y + s);
|
|
context.lineTo(x + this.digitWidth - this.segmentWidth - d, y + this.segmentWidth);
|
|
break;
|
|
case SegmentDisplay_$d.SquaredCorner:
|
|
context.lineTo(x + this.digitWidth - s - e, y + s + e);
|
|
context.lineTo(x + this.digitWidth - this.segmentWidth, y + this.segmentWidth);
|
|
break;
|
|
default:
|
|
context.lineTo(x + this.digitWidth - this.segmentWidth + f, y + f + d);
|
|
context.quadraticCurveTo(x + this.digitWidth - this.segmentWidth + g, y + this.segmentWidth, x + this.digitWidth - this.segmentWidth, y + this.segmentWidth);
|
|
}
|
|
context.fill();
|
|
|
|
} else {
|
|
x = xPos;
|
|
y = this.digitHeight - this.segmentWidth;
|
|
context.fillStyle = this.getSegmentColor_$d(c, '0235689bcdelotuy_', '0235689bcdegijloqsuz_=\@');
|
|
context.beginPath();
|
|
context.moveTo(x + this.segmentWidth + d, y);
|
|
context.lineTo(x + this.digitWidth - this.segmentWidth - d, y);
|
|
switch (this.cornerType) {
|
|
case SegmentDisplay_$d.SymmetricCorner:
|
|
context.lineTo(x + this.digitWidth - s - d, y + s);
|
|
context.lineTo(x + this.digitWidth - this.segmentWidth - d, y + this.segmentWidth);
|
|
context.lineTo(x + this.segmentWidth + d, y + this.segmentWidth);
|
|
context.lineTo(x + s + d, y + s);
|
|
break;
|
|
case SegmentDisplay_$d.SquaredCorner:
|
|
context.lineTo(x + this.digitWidth - s - e, y + s + e);
|
|
context.lineTo(x + this.digitWidth - this.segmentWidth, y + this.segmentWidth);
|
|
context.lineTo(x + this.segmentWidth, y + this.segmentWidth);
|
|
context.lineTo(x + s + e, y + s + e);
|
|
break;
|
|
default:
|
|
context.lineTo(x + this.digitWidth - this.segmentWidth + f, y + f + d);
|
|
context.quadraticCurveTo(x + this.digitWidth - this.segmentWidth + g, y + this.segmentWidth, x + this.digitWidth - this.segmentWidth, y + this.segmentWidth);
|
|
context.lineTo(x + this.segmentWidth, y + this.segmentWidth);
|
|
context.quadraticCurveTo(x + this.segmentWidth - g, y + this.segmentWidth, x + this.segmentWidth - f, y + f + d);
|
|
context.lineTo(x + this.segmentWidth - f, y + f + d);
|
|
}
|
|
context.fill();
|
|
}
|
|
|
|
// draw segment e
|
|
x = xPos;
|
|
y = h + this.segmentWidth;
|
|
context.fillStyle = this.getSegmentColor_$d(c, '0268abcdefhlnoprtu', '0268acefghjklmnopqruvw\@');
|
|
context.beginPath();
|
|
context.moveTo(x, y + this.segmentWidth + d);
|
|
context.lineTo(x + s, y + s + d);
|
|
context.lineTo(x + this.segmentWidth, y + this.segmentWidth + d);
|
|
context.lineTo(x + this.segmentWidth, y + h + this.segmentWidth - d);
|
|
switch (this.cornerType) {
|
|
case SegmentDisplay_$d.SymmetricCorner:
|
|
context.lineTo(x + s, y + h + this.segmentWidth + s - d);
|
|
context.lineTo(x, y + h + this.segmentWidth - d);
|
|
break;
|
|
case SegmentDisplay_$d.SquaredCorner:
|
|
context.lineTo(x + s - e, y + h + this.segmentWidth + s - d + e);
|
|
context.lineTo(x, y + h + this.segmentWidth);
|
|
break;
|
|
default:
|
|
context.lineTo(x + this.segmentWidth - f - d, y + h + this.segmentWidth + f);
|
|
context.quadraticCurveTo(x, y + h + this.segmentWidth + g, x, y + h + this.segmentWidth);
|
|
}
|
|
context.fill();
|
|
|
|
// draw segment f
|
|
x = xPos;
|
|
y = 0;
|
|
context.fillStyle = this.getSegmentColor_$d(c, '045689abcefhlpty', '045689acefghklmnopqrsuvwy\@', '%');
|
|
context.beginPath();
|
|
context.moveTo(x + this.segmentWidth, y + this.segmentWidth + d);
|
|
context.lineTo(x + this.segmentWidth, y + h + this.segmentWidth - d);
|
|
context.lineTo(x + s, y + h + this.segmentWidth + s - d);
|
|
context.lineTo(x, y + h + this.segmentWidth - d);
|
|
switch (this.cornerType) {
|
|
case SegmentDisplay_$d.SymmetricCorner:
|
|
context.lineTo(x, y + this.segmentWidth + d);
|
|
context.lineTo(x + s, y + s + d);
|
|
break;
|
|
case SegmentDisplay_$d.SquaredCorner:
|
|
context.lineTo(x, y + this.segmentWidth);
|
|
context.lineTo(x + s - e, y + s + e);
|
|
break;
|
|
default:
|
|
context.lineTo(x, y + this.segmentWidth);
|
|
context.quadraticCurveTo(x, y + this.segmentWidth - g, x + this.segmentWidth - f - d, y + this.segmentWidth - f);
|
|
context.lineTo(x + this.segmentWidth - f - d, y + this.segmentWidth - f);
|
|
}
|
|
context.fill();
|
|
|
|
// draw segment g for 7 segments
|
|
if (this.segmentCount == 7) {
|
|
x = xPos;
|
|
y = (this.digitHeight - this.segmentWidth) / 2.0;
|
|
context.fillStyle = this.getSegmentColor_$d(c, '2345689abdefhnoprty-=');
|
|
context.beginPath();
|
|
context.moveTo(x + s + d, y + s);
|
|
context.lineTo(x + this.segmentWidth + d, y);
|
|
context.lineTo(x + this.digitWidth - this.segmentWidth - d, y);
|
|
context.lineTo(x + this.digitWidth - s - d, y + s);
|
|
context.lineTo(x + this.digitWidth - this.segmentWidth - d, y + this.segmentWidth);
|
|
context.lineTo(x + this.segmentWidth + d, y + this.segmentWidth);
|
|
context.fill();
|
|
}
|
|
|
|
// draw inner segments for the fourteen- and sixteen-segment-display
|
|
if (this.segmentCount != 7) {
|
|
// draw segment g1
|
|
x = xPos;
|
|
y = (this.digitHeight - this.segmentWidth) / 2.0;
|
|
context.fillStyle = this.getSegmentColor_$d(c, null, '2345689aefhkprsy-+*=', '%');
|
|
context.beginPath();
|
|
context.moveTo(x + s + d, y + s);
|
|
context.lineTo(x + this.segmentWidth + d, y);
|
|
context.lineTo(x + t - d - s, y);
|
|
context.lineTo(x + t - d, y + s);
|
|
context.lineTo(x + t - d - s, y + this.segmentWidth);
|
|
context.lineTo(x + this.segmentWidth + d, y + this.segmentWidth);
|
|
context.fill();
|
|
|
|
// draw segment g2
|
|
x = xPos;
|
|
y = (this.digitHeight - this.segmentWidth) / 2.0;
|
|
context.fillStyle = this.getSegmentColor_$d(c, null, '234689abefghprsy-+*=\@', '%');
|
|
context.beginPath();
|
|
context.moveTo(x + t + d, y + s);
|
|
context.lineTo(x + t + d + s, y);
|
|
context.lineTo(x + this.digitWidth - this.segmentWidth - d, y);
|
|
context.lineTo(x + this.digitWidth - s - d, y + s);
|
|
context.lineTo(x + this.digitWidth - this.segmentWidth - d, y + this.segmentWidth);
|
|
context.lineTo(x + t + d + s, y + this.segmentWidth);
|
|
context.fill();
|
|
|
|
// draw segment j
|
|
x = xPos + t - s;
|
|
y = 0;
|
|
context.fillStyle = this.getSegmentColor_$d(c, null, 'bdit+*', '%');
|
|
context.beginPath();
|
|
if (this.segmentCount == 14) {
|
|
context.moveTo(x, y + this.segmentWidth + this.segmentDistance);
|
|
context.lineTo(x + this.segmentWidth, y + this.segmentWidth + this.segmentDistance);
|
|
} else {
|
|
context.moveTo(x, y + this.segmentWidth + d);
|
|
context.lineTo(x + s, y + s + d);
|
|
context.lineTo(x + this.segmentWidth, y + this.segmentWidth + d);
|
|
}
|
|
context.lineTo(x + this.segmentWidth, y + h + this.segmentWidth - d);
|
|
context.lineTo(x + s, y + h + this.segmentWidth + s - d);
|
|
context.lineTo(x, y + h + this.segmentWidth - d);
|
|
context.fill();
|
|
|
|
// draw segment m
|
|
x = xPos + t - s;
|
|
y = this.digitHeight;
|
|
context.fillStyle = this.getSegmentColor_$d(c, null, 'bdity+*\@', '%');
|
|
context.beginPath();
|
|
if (this.segmentCount == 14) {
|
|
context.moveTo(x, y - this.segmentWidth - this.segmentDistance);
|
|
context.lineTo(x + this.segmentWidth, y - this.segmentWidth - this.segmentDistance);
|
|
} else {
|
|
context.moveTo(x, y - this.segmentWidth - d);
|
|
context.lineTo(x + s, y - s - d);
|
|
context.lineTo(x + this.segmentWidth, y - this.segmentWidth - d);
|
|
}
|
|
context.lineTo(x + this.segmentWidth, y - h - this.segmentWidth + d);
|
|
context.lineTo(x + s, y - h - this.segmentWidth - s + d);
|
|
context.lineTo(x, y - h - this.segmentWidth + d);
|
|
context.fill();
|
|
|
|
// draw segment h
|
|
x = xPos + this.segmentWidth;
|
|
y = this.segmentWidth;
|
|
context.fillStyle = this.getSegmentColor_$d(c, null, 'mnx\\\\*');
|
|
context.beginPath();
|
|
context.moveTo(x + this.segmentDistance, y + this.segmentDistance);
|
|
context.lineTo(x + this.segmentDistance + r, y + this.segmentDistance);
|
|
context.lineTo(x + w - this.segmentDistance , y + h - this.segmentDistance - r);
|
|
context.lineTo(x + w - this.segmentDistance , y + h - this.segmentDistance);
|
|
context.lineTo(x + w - this.segmentDistance - r , y + h - this.segmentDistance);
|
|
context.lineTo(x + this.segmentDistance, y + this.segmentDistance + r);
|
|
context.fill();
|
|
|
|
// draw segment k
|
|
x = xPos + w + 2.0 * this.segmentWidth;
|
|
y = this.segmentWidth;
|
|
context.fillStyle = this.getSegmentColor_$d(c, null, '0kmvxz/*', '%');
|
|
context.beginPath();
|
|
context.moveTo(x + w - this.segmentDistance, y + this.segmentDistance);
|
|
context.lineTo(x + w - this.segmentDistance, y + this.segmentDistance + r);
|
|
context.lineTo(x + this.segmentDistance + r, y + h - this.segmentDistance);
|
|
context.lineTo(x + this.segmentDistance, y + h - this.segmentDistance);
|
|
context.lineTo(x + this.segmentDistance, y + h - this.segmentDistance - r);
|
|
context.lineTo(x + w - this.segmentDistance - r, y + this.segmentDistance);
|
|
context.fill();
|
|
|
|
// draw segment l
|
|
x = xPos + w + 2.0 * this.segmentWidth;
|
|
y = h + 2.0 * this.segmentWidth;
|
|
context.fillStyle = this.getSegmentColor_$d(c, null, '5knqrwx\\\\*');
|
|
context.beginPath();
|
|
context.moveTo(x + this.segmentDistance, y + this.segmentDistance);
|
|
context.lineTo(x + this.segmentDistance + r, y + this.segmentDistance);
|
|
context.lineTo(x + w - this.segmentDistance , y + h - this.segmentDistance - r);
|
|
context.lineTo(x + w - this.segmentDistance , y + h - this.segmentDistance);
|
|
context.lineTo(x + w - this.segmentDistance - r , y + h - this.segmentDistance);
|
|
context.lineTo(x + this.segmentDistance, y + this.segmentDistance + r);
|
|
context.fill();
|
|
|
|
// draw segment n
|
|
x = xPos + this.segmentWidth;
|
|
y = h + 2.0 * this.segmentWidth;
|
|
context.fillStyle = this.getSegmentColor_$d(c, null, '0vwxz/*', '%');
|
|
context.beginPath();
|
|
context.moveTo(x + w - this.segmentDistance, y + this.segmentDistance);
|
|
context.lineTo(x + w - this.segmentDistance, y + this.segmentDistance + r);
|
|
context.lineTo(x + this.segmentDistance + r, y + h - this.segmentDistance);
|
|
context.lineTo(x + this.segmentDistance, y + h - this.segmentDistance);
|
|
context.lineTo(x + this.segmentDistance, y + h - this.segmentDistance - r);
|
|
context.lineTo(x + w - this.segmentDistance - r, y + this.segmentDistance);
|
|
context.fill();
|
|
}
|
|
|
|
return this.digitDistance + this.digitWidth;
|
|
|
|
case '.':
|
|
context.fillStyle = (c == '#') || (c == '.') ? this.colorOn : this.colorOff;
|
|
this.drawPoint(context, xPos, this.digitHeight - this.segmentWidth, this.segmentWidth);
|
|
return this.digitDistance + this.segmentWidth;
|
|
|
|
case ':':
|
|
context.fillStyle = (c == '#') || (c == ':') ? this.colorOn : this.colorOff;
|
|
var y = (this.digitHeight - this.segmentWidth) / 2.0 - this.segmentWidth;
|
|
this.drawPoint(context, xPos, y, this.segmentWidth);
|
|
this.drawPoint(context, xPos, y + 2.0 * this.segmentWidth, this.segmentWidth);
|
|
return this.digitDistance + this.segmentWidth;
|
|
|
|
default:
|
|
return this.digitDistance;
|
|
}
|
|
};
|
|
|
|
SegmentDisplay_$d.prototype.drawPoint = function(context, x1, y1, size) {
|
|
var x2 = x1 + size;
|
|
var y2 = y1 + size;
|
|
var d = size / 4.0;
|
|
|
|
context.beginPath();
|
|
context.moveTo(x2 - d, y1);
|
|
context.quadraticCurveTo(x2, y1, x2, y1 + d);
|
|
context.lineTo(x2, y2 - d);
|
|
context.quadraticCurveTo(x2, y2, x2 - d, y2);
|
|
context.lineTo(x1 + d, y2);
|
|
context.quadraticCurveTo(x1, y2, x1, y2 - d);
|
|
context.lineTo(x1, y1 + d);
|
|
context.quadraticCurveTo(x1, y1, x1 + d, y1);
|
|
context.fill();
|
|
};
|
|
|
|
SegmentDisplay_$d.prototype.getSegmentColor_$d = function(c, charSet7, charSet14, charSet16) {
|
|
if (c == '#') {
|
|
return this.colorOn;
|
|
} else {
|
|
switch (this.segmentCount) {
|
|
case 7: return (charSet7.indexOf(c) == -1) ? this.colorOff : this.colorOn;
|
|
case 14: return (charSet14.indexOf(c) == -1) ? this.colorOff : this.colorOn;
|
|
case 16: var pattern = charSet14 + (charSet16 === undefined ? '' : charSet16);
|
|
return (pattern.indexOf(c) == -1) ? this.colorOff : this.colorOn;
|
|
default: return this.colorOff;
|
|
}
|
|
}
|
|
};
|
|
|
|
// CSRF-Token auslesen
|
|
var body = document.querySelector("body");
|
|
if( body != null ) {
|
|
csrf = body.getAttribute("fwcsrf");
|
|
}
|
|
|
|
// get the base url
|
|
function getBaseUrl () {
|
|
var url = window.location.href.split("?")[0];
|
|
url += "?";
|
|
if( csrf != null ) {
|
|
url += "fwcsrf="+csrf+"&";
|
|
}
|
|
return url;
|
|
}
|
|
|
|
function makeCommand (cmd) {
|
|
return getBaseUrl()+"cmd="+encodeURIComponent(cmd)+"&XHR=1";
|
|
}
|
|
|
|
// Template digital time display
|
|
function buildtime (hours, minutes, seconds) {
|
|
var ddt = ((hours < 10) ? '0' : '') + hours + ':' +
|
|
((minutes < 10) ? '0' : '') + minutes + ':' +
|
|
((seconds < 10) ? '0' : '') + seconds
|
|
;
|
|
return ddt;
|
|
}
|
|
|
|
// localStorage Set
|
|
function localStoreSet_$d (hours, minutes, seconds, sumsecs, aSetStopT) {
|
|
if (Number.isInteger(hours)) { localStorage.setItem('h_$d', hours); }
|
|
if (Number.isInteger(minutes)) { localStorage.setItem('m_$d', minutes); }
|
|
if (Number.isInteger(seconds)) { localStorage.setItem('s_$d', seconds); }
|
|
if (Number.isInteger(sumsecs)) { localStorage.setItem('ss_$d', sumsecs); }
|
|
if (Number.isInteger(aSetStopT)) { localStorage.setItem('ast_$d', aSetStopT); }
|
|
}
|
|
|
|
// localStorage speichern letzte Alarmzeit
|
|
function localStoreSetLastalm_$d (dev, lastalmtime) {
|
|
localStorage.setItem('lastalmtime_'+dev, lastalmtime);
|
|
}
|
|
|
|
// Reading currtime setzen
|
|
function setrcurrtime (h_$d, m_$d, s_$d) {
|
|
var time_$d = buildtime (h_$d, m_$d, s_$d);
|
|
if (modulo2_$d != zmodulo_$d && showCurrTime_$d != 0 && time_$d != 'NaN:NaN:NaN') {
|
|
command = '{ CommandSetReading(undef, "$d currtime '+time_$d+'") }';
|
|
url_$d = makeCommand(command);
|
|
\$.get(url_$d);
|
|
}
|
|
}
|
|
|
|
// Check ob Alarm ausgelöst werden soll und ggf. Alarmevent triggern
|
|
function checkAndDoAlm_$d (dev, acttime, almtime) {
|
|
lastalmtime_$d = localStorage.getItem('lastalmtime_$d'); // letzte Alarmzeit laden
|
|
if ( (acttime == almtime || acttime == '$alarmdef') && acttime != lastalmtime_$d ) {
|
|
command = '{ CommandSetReading(undef, "$d alarmed '+acttime+'") }';
|
|
url_$d = makeCommand(command);
|
|
|
|
localStoreSetLastalm_$d (dev, acttime); // aktuelle Alarmzeit sichern
|
|
|
|
if(acttime == almtime) {
|
|
\$.get(url_$d);
|
|
|
|
} else {
|
|
\$.get(url_$d, function (data) {
|
|
command = '{ CommandSetReading(undef, "$d state stopped") }';
|
|
url_$d = makeCommand(command);
|
|
\$.get(url_$d);
|
|
}
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
animate_$d();
|
|
|
|
function animate_$d() {
|
|
var watchkind_$d = '$addp';
|
|
var cycletime = new Date();
|
|
var cycleseconds = cycletime.getSeconds();
|
|
modulo2_$d = cycleseconds % 2; // Taktung für Reading lesen/schreiben (Serverauslastung reduzieren)
|
|
|
|
if (watchkind_$d == 'watch') {
|
|
if (modulo2_$d != zmodulo_$d) {
|
|
command = '{ReadingsVal("$d","alarmTime","+almtime0_$d+")}'; // alarmTime Reading lesen
|
|
url_$d = makeCommand(command);
|
|
\$.get( url_$d, function (data) {
|
|
almtime0_$d = data.replace(/\\n/g, '');
|
|
zmodulo_$d = modulo2_$d;
|
|
return (almtime0_$d, zmodulo_$d);
|
|
}
|
|
);
|
|
}
|
|
|
|
// Zeitsteuerung - aktueller Timestamp in Millisekunden
|
|
if ('$tsou' == 'server') { // Serverzeit
|
|
command = '{ int(time*1000) }';
|
|
url_$d = makeCommand(command);
|
|
\$.get( url_$d, function (data) {
|
|
data = data.replace(/\\n/g, '');
|
|
ct_$d = parseInt(data);
|
|
return ct_$d;
|
|
}
|
|
);
|
|
|
|
time_$d = new Date(ct_$d);
|
|
|
|
} else {
|
|
time_$d = new Date(); // Clientzeit
|
|
}
|
|
|
|
if (typeof ct_$d === 'undefined') { // wenn Zeit noch undef mit lokaler Zeit initialisieren -> springen Zeiger verhindern
|
|
time_$d = new Date();
|
|
} else {
|
|
time_$d = new Date(ct_$d);
|
|
}
|
|
|
|
var hours_$d = time_$d.getHours();
|
|
var minutes_$d = time_$d.getMinutes();
|
|
var seconds_$d = time_$d.getSeconds();
|
|
|
|
acttime_$d = ((hours_$d < 10) ? '0' : '') + hours_$d + ':' +
|
|
((minutes_$d < 10) ? '0' : '') + minutes_$d + ':' +
|
|
((seconds_$d < 10) ? '0' : '') + seconds_$d;
|
|
|
|
if (acttime_$d == '00:00:00') {
|
|
localStoreSetLastalm_$d ('$d', 'NaN'); // letzte Alarmzeit zurücksetzen zum Tageswechsel
|
|
}
|
|
|
|
checkAndDoAlm_$d ('$d', acttime_$d, almtime0_$d);
|
|
|
|
setrcurrtime (hours_$d, minutes_$d, seconds_$d); // Reading currtime mit angezeigter Zeit setzen
|
|
}
|
|
|
|
if (watchkind_$d == 'staticwatch') {
|
|
var hours_$d = '$h';
|
|
var minutes_$d = '$m';
|
|
var seconds_$d = '$s';
|
|
|
|
setrcurrtime (hours_$d, minutes_$d, seconds_$d); // Reading currtime mit angezeigter Zeit setzen
|
|
}
|
|
|
|
if (watchkind_$d == 'stopwatch') {
|
|
command = '{ReadingsVal("$d","state","")}'; // state Reading lesen
|
|
url_$d = makeCommand(command);
|
|
\$.get( url_$d, function (data) {
|
|
state_$d = data.replace(/\\n/g, '');
|
|
return state_$d;
|
|
}
|
|
);
|
|
|
|
if (state_$d == 'started' || state_$d == 'resumed') {
|
|
localStoreSet_$d (NaN, NaN, NaN, NaN, 1); // set Reading stoptime freischalten
|
|
|
|
if (modulo2_$d != zmodulo_$d) {
|
|
command = '{ReadingsVal("$d","alarmTime","+almtime0_$d+")}'; // alarmTime Reading lesen
|
|
url_$d = makeCommand(command);
|
|
\$.get( url_$d, function (data) {
|
|
almtime0_$d = data.replace(/\\n/g, '');
|
|
zmodulo_$d = modulo2_$d;
|
|
return (almtime0_$d, zmodulo_$d);
|
|
}
|
|
);
|
|
}
|
|
|
|
// == Startzeit ==
|
|
command = '{ReadingsNum("$d","starttime", 0)}';
|
|
url_$d = makeCommand(command);
|
|
\$.get( url_$d, function (data) {
|
|
data = data.replace(/\\n/g, '');
|
|
st_$d = parseInt(data);
|
|
asydone1_$d = 1;
|
|
return (st_$d, asydone1_$d);
|
|
}
|
|
);
|
|
|
|
startDate_$d = new Date(st_$d);
|
|
|
|
// aktueller Timestamp in Millisekunden
|
|
currDate_$d = new Date();
|
|
elapsesec_$d = ((currDate_$d.getTime() - startDate_$d.getTime()))/1000; // vergangene Millisekunden in Sekunden
|
|
|
|
if (state_$d == 'resumed') {
|
|
lastsumsec_$d = localStorage.getItem('ss_$d');
|
|
elapsesec_$d = parseInt(elapsesec_$d) + parseInt(lastsumsec_$d);
|
|
} else {
|
|
elapsesec_$d = parseInt(elapsesec_$d);
|
|
}
|
|
|
|
if (state_$d == 'started' && elapsesec_$d <= 5) {
|
|
localStoreSetLastalm_$d ('$d', 'NaN'); // letzte Alarmzeit zurücksetzen bis 2 s nach Start
|
|
}
|
|
|
|
hours_$d = parseInt(elapsesec_$d / 3600);
|
|
elapsesec_$d -= hours_$d * 3600;
|
|
minutes_$d = parseInt(elapsesec_$d / 60);
|
|
seconds_$d = parseInt(elapsesec_$d - minutes_$d * 60);
|
|
|
|
ddt_$d = buildtime (hours_$d, minutes_$d, seconds_$d);
|
|
|
|
if(asydone1_$d == 1) { // vermeidet zufällige Alarmierung bei start / resume
|
|
checkAndDoAlm_$d ('$d', ddt_$d, almtime0_$d); // Alarm auslösen wenn zutreffend
|
|
}
|
|
|
|
localStoreSet_$d (hours_$d, minutes_$d, seconds_$d, NaN);
|
|
|
|
setrcurrtime (hours_$d, minutes_$d, seconds_$d); // Reading currtime mit angezeigter Zeit setzen
|
|
|
|
if(asydone1_$d == 0) { // vermeidet zufällige Zeitanzeige bei start / resume
|
|
hours_$d = 'NaN';
|
|
minutes_$d = 'NaN';
|
|
seconds_$d = 'NaN';
|
|
}
|
|
}
|
|
|
|
if (state_$d == 'stopped') {
|
|
hours_$d = localStorage.getItem('h_$d');
|
|
minutes_$d = localStorage.getItem('m_$d');
|
|
seconds_$d = localStorage.getItem('s_$d');
|
|
|
|
sumsecs_$d = parseInt(hours_$d*3600) + parseInt(minutes_$d*60) + parseInt(seconds_$d);
|
|
localStoreSet_$d (NaN, NaN, NaN, sumsecs_$d);
|
|
|
|
allowSetStopTime = localStorage.getItem('ast_$d');
|
|
if(allowSetStopTime == 1) {
|
|
ddt_$d = buildtime (hours_$d, minutes_$d, seconds_$d); // Reading mit Stoppzeit setzen
|
|
|
|
command = '{ CommandSetReading(undef, "$d stoptime '+ddt_$d+'") }';
|
|
url_$d = makeCommand(command);
|
|
\$.get(url_$d);
|
|
}
|
|
localStoreSet_$d (NaN, NaN, NaN, NaN, 0); // set Reading stoptime verbieten
|
|
|
|
asydone1_$d = 0;
|
|
}
|
|
|
|
if (state_$d == 'initialized') {
|
|
hours_$d = 0;
|
|
minutes_$d = 0;
|
|
seconds_$d = 0;
|
|
|
|
localStoreSet_$d (hours_$d, minutes_$d, seconds_$d);
|
|
localStoreSetLastalm_$d ('$d', 'NaN'); // letzte Alarmzeit zurücksetzen
|
|
|
|
asydone1_$d = 0;
|
|
}
|
|
}
|
|
|
|
if (watchkind_$d == 'countdownwatch') {
|
|
command = '{ReadingsVal("$d","state","")}';
|
|
url_$d = makeCommand(command);
|
|
\$.get( url_$d, function (data) {
|
|
state_$d = data.replace(/\\n/g, '');
|
|
return state_$d;
|
|
}
|
|
);
|
|
|
|
if (state_$d == 'started' || state_$d == 'resumed') {
|
|
if (modulo2_$d != zmodulo_$d) {
|
|
command = '{ReadingsVal("$d","alarmTime","+almtime0_$d+")}'; // alarmTime Reading lesen
|
|
url_$d = makeCommand(command);
|
|
\$.get( url_$d, function (data) {
|
|
almtime0_$d = data.replace(/\\n/g, '');
|
|
zmodulo_$d = modulo2_$d;
|
|
return (almtime0_$d, zmodulo_$d);
|
|
}
|
|
);
|
|
}
|
|
|
|
// == Ermittlung Countdown Startwert ==
|
|
if (modulo2_$d != zmodulo_$d) {
|
|
command = '{ReadingsNum("$d","countInitVal", 0)}';
|
|
url_$d = makeCommand(command);
|
|
\$.get( url_$d, function (data) {
|
|
data = data.replace(/\\n/g, '');
|
|
ci_$d = parseInt(data);
|
|
return ci_$d;
|
|
}
|
|
);
|
|
}
|
|
|
|
if (state_$d == 'resumed') {
|
|
countInitVal_$d = localStorage.getItem('ss_$d');
|
|
} else {
|
|
countInitVal_$d = parseInt(ci_$d); // Initialwert Countdown in Sekunden
|
|
}
|
|
|
|
// == Ermittlung vergangene Sekunden ==
|
|
command = '{ReadingsNum("$d","starttime", 0)}';
|
|
url_$d = makeCommand(command);
|
|
\$.get( url_$d, function (data) {
|
|
data = data.replace(/\\n/g, '');
|
|
st_$d = parseInt(data);
|
|
asydone1_$d = 1;
|
|
return (st_$d, asydone1_$d);
|
|
}
|
|
);
|
|
startDate_$d = new Date(st_$d);
|
|
|
|
// aktueller Timestamp in Millisekunden
|
|
command = '{ int(time*1000) }';
|
|
url_$d = makeCommand(command);
|
|
\$.get( url_$d, function (data) {
|
|
data = data.replace(/\\n/g, '');
|
|
ct_$d = parseInt(data);
|
|
asydone2_$d = 1;
|
|
return (ct_$d, asydone2_$d);
|
|
}
|
|
);
|
|
currDate_$d = new Date(ct_$d);
|
|
|
|
elapsesec_$d = (currDate_$d.getTime() - startDate_$d.getTime())/1000; // vergangene Millisekunden in Sekunden umrechnen
|
|
|
|
if (state_$d == 'started' && elapsesec_$d <= 5) {
|
|
localStoreSetLastalm_$d ('$d', 'NaN'); // letzte Alarmzeit zurücksetzen bis 2 s nach Start
|
|
}
|
|
|
|
// == Countdown errechnen ==
|
|
countcurr_$d = parseInt(countInitVal_$d) - parseInt(elapsesec_$d);
|
|
//log("countInitVal_$d: "+countInitVal_$d+", elapsesec_$d"+elapsesec_$d+", countcurr_$d: "+countcurr_$d);
|
|
|
|
hours_$d = parseInt(countcurr_$d / 3600);
|
|
countcurr_$d -= hours_$d * 3600;
|
|
minutes_$d = parseInt(countcurr_$d / 60);
|
|
seconds_$d = parseInt(countcurr_$d - minutes_$d * 60);
|
|
|
|
if (countcurr_$d >= 0) {
|
|
ddt_$d = buildtime (hours_$d, minutes_$d, seconds_$d);
|
|
if(asydone1_$d == 1 && asydone2_$d == 1) { // vermeidet zufällige Alarmierung bei start / resume
|
|
checkAndDoAlm_$d ('$d', ddt_$d, almtime0_$d); // Alarm auslösen wenn zutreffend
|
|
}
|
|
localStoreSet_$d (hours_$d, minutes_$d, seconds_$d, NaN);
|
|
} else {
|
|
hours_$d = 0;
|
|
minutes_$d = 0;
|
|
seconds_$d = 0;
|
|
}
|
|
|
|
setrcurrtime (hours_$d, minutes_$d, seconds_$d); // Reading currtime mit angezeigter Zeit setzen
|
|
|
|
if(asydone1_$d == 0 && asydone2_$d == 0) { // vermeidet zufällige Zeitanzeige bei start / resume
|
|
hours_$d = 'NaN';
|
|
minutes_$d = 'NaN';
|
|
seconds_$d = 'NaN';
|
|
}
|
|
}
|
|
|
|
if (state_$d == 'stopped') {
|
|
hours_$d = localStorage.getItem('h_$d');
|
|
minutes_$d = localStorage.getItem('m_$d');
|
|
seconds_$d = localStorage.getItem('s_$d');
|
|
|
|
pastsumsec_$d = parseInt(hours_$d*3600) + parseInt(minutes_$d*60) + parseInt(seconds_$d);
|
|
localStoreSet_$d (NaN, NaN, NaN, pastsumsec_$d);
|
|
|
|
asydone1_$d = 0;
|
|
asydone2_$d = 0;
|
|
}
|
|
|
|
if (state_$d == 'initialized') {
|
|
hours_$d = 0;
|
|
minutes_$d = 0;
|
|
seconds_$d = 0;
|
|
|
|
localStoreSet_$d (hours_$d, minutes_$d, seconds_$d);
|
|
localStoreSetLastalm_$d ('$d', 'NaN'); // letzte Alarmzeit zurücksetzen
|
|
|
|
asydone1_$d = 0;
|
|
asydone2_$d = 0;
|
|
}
|
|
}
|
|
|
|
if (watchkind_$d == 'text') {
|
|
tlength_$d = digitxt_$d.length; // Länge des Textes
|
|
if($adtdn > 0) {
|
|
tlength_$d = $adtdn;
|
|
}
|
|
|
|
display_$d.pattern = ''; // Template erstellen
|
|
forerun_$d = ''; // Vorlauf Textticker initialisieren
|
|
for (var i = 0; i <= tlength_$d; i++) {
|
|
display_$d.pattern += '#';
|
|
forerun_$d += ' ';
|
|
}
|
|
display_$d.pattern += distBorderright_$d; // Abstand Text zum rechten Rand
|
|
display_$d.pattern = distBorderleft_$d + display_$d.pattern
|
|
|
|
if (tticker_$d == 'on') { // Text als Laufband ?
|
|
var rttime = new Date();
|
|
var rthours = rttime.getHours();
|
|
var rtminutes = rttime.getMinutes();
|
|
var rtseconds = rttime.getSeconds();
|
|
var rtmillis = rttime.getMilliseconds();
|
|
|
|
var text_$d = forerun_$d+digitxt_$d+' ';
|
|
var index_$d = ( 2 * (rtseconds + 60*rtminutes + 24*60*rthours) + Math.floor(rtmillis / 500) ) % (text_$d.length - 6);
|
|
value_$d = distBorderleft_$d + text_$d.substr(index_$d, tlength_$d+1);
|
|
|
|
} else {
|
|
value_$d = distBorderleft_$d + digitxt_$d;
|
|
}
|
|
|
|
if (modulo2_$d != zmodulo_$d) {
|
|
command = '{ReadingsVal("$d","displayText", "$deftxt")}'; // Text dynamisch aus Reading lesen
|
|
url_$d = makeCommand(command);
|
|
\$.get( url_$d, function (data) {
|
|
digitxt_$d = data.replace(/\\n/g, '');
|
|
return (digitxt_$d);
|
|
}
|
|
);
|
|
}
|
|
|
|
if (modulo2_$d != zmodulo_$d) {
|
|
command = '{ReadingsVal("$d","displayTextTicker", "off")}'; // Textticker Einstellung aus Reading lesen
|
|
url_$d = makeCommand(command);
|
|
\$.get( url_$d, function (data) {
|
|
tticker_$d = data.replace(/\\n/g, '');
|
|
zmodulo_$d = modulo2_$d;
|
|
return (tticker_$d, zmodulo_$d);
|
|
}
|
|
);
|
|
}
|
|
|
|
} else {
|
|
display_$d.pattern = distBorderleft_$d + '##:##:##' + distBorderright_$d; // Textschablone initialisieren
|
|
ddt_$d = buildtime (hours_$d, minutes_$d, seconds_$d);
|
|
value_$d = distBorderleft_$d + ddt_$d;
|
|
|
|
if(value_$d == distBorderleft_$d + 'undefined:undefined:undefined' || value_$d == distBorderleft_$d + 'NaN:NaN:NaN') {
|
|
value_$d = distBorderleft_$d + ' : : ';
|
|
}
|
|
}
|
|
|
|
display_$d.setValue(value_$d);
|
|
window.setTimeout('animate_$d()', 200);
|
|
}
|
|
|
|
</script>
|
|
</body>
|
|
</html>
|
|
|
|
END_JS
|
|
|
|
return qq{$back};
|
|
}
|
|
|
|
##############################################################################
|
|
# Bahnhofsuhr aus:
|
|
# http://www.3quarks.com/de/Bahnhofsuhr
|
|
#
|
|
##############################################################################
|
|
sub stationWatch {
|
|
my ($d) = @_;
|
|
my $hash = $defs{$d};
|
|
my $ssh = AttrVal ($d, "stationSecondHand", "Bar"). "SecondHand";
|
|
my $shb = AttrVal ($d, "stationSecondHandBehavoir", "Bouncing"). "SecondHand";
|
|
my $smh = AttrVal ($d, "stationMinuteHand", "Pointed"). "MinuteHand";
|
|
my $mhb = AttrVal ($d, "stationMinuteHandBehavoir", "Bouncing"). "MinuteHand";
|
|
my $shh = AttrVal ($d, "stationHourHand", "Pointed"). "HourHand";
|
|
my $sb = AttrVal ($d, "stationBoss", "Red"). "Boss";
|
|
my $ssd = AttrVal ($d, "stationStrokeDial", "Swiss"). "StrokeDial";
|
|
my $sbody = AttrVal ($d, "stationBody", "Round"). "Body";
|
|
my $hattr = AttrVal ($d, "htmlattr", "width='150' height='150'");
|
|
my $tsou = AttrVal ($d, "timeSource", "client");
|
|
my $showct = AttrVal ($d, "timeAsReading", 0);
|
|
|
|
my $alarm = ReadingsVal($d, "alarmTime", "aa:bb:cc");
|
|
|
|
my $back = << "END_JS";
|
|
<html>
|
|
<body>
|
|
<canvas id='clock_$d' $hattr>
|
|
</canvas>
|
|
|
|
<script>
|
|
|
|
var ct_$d;
|
|
var almtime0_$d = '$alarm';
|
|
var time_$d;
|
|
var zmodulo_$d = 0; // Hilfszähler
|
|
var showCurrTime_$d = '$showct'; // Reading currtime schreiben oder nicht
|
|
|
|
// clock body (Uhrgehäuse)
|
|
StationClock_$d.NoBody = 0;
|
|
StationClock_$d.SmallWhiteBody = 1;
|
|
StationClock_$d.RoundBody = 2;
|
|
StationClock_$d.RoundGreenBody = 3;
|
|
StationClock_$d.SquareBody = 4;
|
|
StationClock_$d.ViennaBody = 5;
|
|
|
|
// stroke dial (Zifferblatt)
|
|
StationClock_$d.NoDial = 0;
|
|
StationClock_$d.GermanHourStrokeDial = 1;
|
|
StationClock_$d.GermanStrokeDial = 2;
|
|
StationClock_$d.AustriaStrokeDial = 3;
|
|
StationClock_$d.SwissStrokeDial = 4;
|
|
StationClock_$d.ViennaStrokeDial = 5;
|
|
|
|
//clock hour hand (Stundenzeiger)
|
|
StationClock_$d.PointedHourHand = 1;
|
|
StationClock_$d.BarHourHand = 2;
|
|
StationClock_$d.SwissHourHand = 3;
|
|
StationClock_$d.ViennaHourHand = 4;
|
|
|
|
//clock minute hand (Minutenzeiger)
|
|
StationClock_$d.PointedMinuteHand = 1;
|
|
StationClock_$d.BarMinuteHand = 2;
|
|
StationClock_$d.SwissMinuteHand = 3;
|
|
StationClock_$d.ViennaMinuteHand = 4;
|
|
|
|
//clock second hand (Sekundenzeiger)
|
|
StationClock_$d.NoSecondHand = 0;
|
|
StationClock_$d.BarSecondHand = 1;
|
|
StationClock_$d.HoleShapedSecondHand = 2;
|
|
StationClock_$d.NewHoleShapedSecondHand = 3;
|
|
StationClock_$d.SwissSecondHand = 4;
|
|
|
|
// clock boss (Zeigerabdeckung)
|
|
StationClock_$d.NoBoss = 0;
|
|
StationClock_$d.BlackBoss = 1;
|
|
StationClock_$d.RedBoss = 2;
|
|
StationClock_$d.ViennaBoss = 3;
|
|
|
|
// minute hand behavoir
|
|
StationClock_$d.CreepingMinuteHand = 0;
|
|
StationClock_$d.BouncingMinuteHand = 1;
|
|
StationClock_$d.ElasticBouncingMinuteHand = 2;
|
|
|
|
// second hand behavoir
|
|
StationClock_$d.CreepingSecondHand = 0;
|
|
StationClock_$d.BouncingSecondHand = 1;
|
|
StationClock_$d.ElasticBouncingSecondHand = 2;
|
|
StationClock_$d.OverhastySecondHand = 3;
|
|
|
|
// CSRF-Token auslesen
|
|
var body = document.querySelector("body");
|
|
if( body != null ) {
|
|
csrf = body.getAttribute("fwcsrf");
|
|
}
|
|
|
|
// get the base url
|
|
function getBaseUrl () {
|
|
var url = window.location.href.split("?")[0];
|
|
url += "?";
|
|
if( csrf != null ) {
|
|
url += "fwcsrf="+csrf+"&";
|
|
}
|
|
return url;
|
|
}
|
|
|
|
function makeCommand (cmd) {
|
|
return getBaseUrl()+"cmd="+encodeURIComponent(cmd)+"&XHR=1";
|
|
}
|
|
|
|
// Template digital time display
|
|
function buildtime (hours, minutes, seconds) {
|
|
var ddt = ((hours < 10) ? '0' : '') + hours + ':' +
|
|
((minutes < 10) ? '0' : '') + minutes + ':' +
|
|
((seconds < 10) ? '0' : '') + seconds
|
|
;
|
|
return ddt;
|
|
}
|
|
|
|
// Reading currtime setzen
|
|
function setrcurrtime (h_$d, m_$d, s_$d) {
|
|
var time_$d = buildtime (h_$d, m_$d, s_$d);
|
|
if (modulo2_$d != zmodulo_$d && showCurrTime_$d != 0 && time_$d != 'NaN:NaN:NaN') {
|
|
command = '{ CommandSetReading(undef, "$d currtime '+time_$d+'") }';
|
|
url_$d = makeCommand(command);
|
|
\$.get(url_$d);
|
|
}
|
|
}
|
|
|
|
// localStorage speichern letzte Alarmzeit
|
|
function localStoreSetLastalm_$d (dev, lastalmtime) {
|
|
localStorage.setItem('lastalmtime_'+dev, lastalmtime);
|
|
}
|
|
|
|
// Check ob Alarm ausgelöst werden soll und ggf. Alarmevent triggern
|
|
function checkAndDoAlm_$d (dev, acttime, almtime) {
|
|
lastalmtime_$d = localStorage.getItem('lastalmtime_$d'); // letzte Alarmzeit laden
|
|
if ( acttime == almtime && acttime != lastalmtime_$d ) {
|
|
command = '{ CommandSetReading(undef, "$d alarmed '+acttime+'") }';
|
|
url_$d = makeCommand(command);
|
|
|
|
localStoreSetLastalm_$d (dev, acttime); // aktuelle Alarmzeit sichern
|
|
|
|
if(acttime == almtime) {
|
|
\$.get(url_$d);
|
|
|
|
} else {
|
|
\$.get(url_$d, function (data) {
|
|
command = '{ CommandSetReading(undef, "$d state stopped") }';
|
|
url_$d = makeCommand(command);
|
|
\$.get(url_$d);
|
|
}
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
function StationClock_$d(clockId_$d) {
|
|
this.clockId_$d = clockId_$d;
|
|
this.radius = 0;
|
|
|
|
// hour offset
|
|
this.hourOffset = 0;
|
|
|
|
// clock body
|
|
this.body = StationClock_$d.RoundBody;
|
|
this.bodyShadowColor = 'rgba(0,0,0,0.5)';
|
|
this.bodyShadowOffsetX = 0.03;
|
|
this.bodyShadowOffsetY = 0.03;
|
|
this.bodyShadowBlur = 0.06;
|
|
|
|
// body dial
|
|
this.dial = StationClock_$d.GermanStrokeDial;
|
|
this.dialColor = 'rgb(60,60,60)';
|
|
|
|
// clock hands
|
|
this.hourHand = StationClock_$d.PointedHourHand;
|
|
this.minuteHand = StationClock_$d.PointedMinuteHand;
|
|
this.secondHand = StationClock_$d.HoleShapedSecondHand;
|
|
this.handShadowColor = 'rgba(0,0,0,0.3)';
|
|
this.handShadowOffsetX = 0.03;
|
|
this.handShadowOffsetY = 0.03;
|
|
this.handShadowBlur = 0.04;
|
|
|
|
// clock colors
|
|
this.hourHandColor = 'rgb(0,0,0)';
|
|
this.minuteHandColor = 'rgb(0,0,0)';
|
|
this.secondHandColor = 'rgb(200,0,0)';
|
|
|
|
// clock boss
|
|
this.boss = StationClock_$d.NoBoss;
|
|
this.bossShadowColor = 'rgba(0,0,0,0.2)';
|
|
this.bossShadowOffsetX = 0.02;
|
|
this.bossShadowOffsetY = 0.02;
|
|
this.bossShadowBlur = 0.03;
|
|
|
|
// hand behavoir
|
|
this.minuteHandBehavoir = StationClock_$d.CreepingMinuteHand;
|
|
this.secondHandBehavoir = StationClock_$d.OverhastySecondHand;
|
|
|
|
// hand animation
|
|
this.minuteHandAnimationStep = 0;
|
|
this.secondHandAnimationStep = 0;
|
|
this.lastMinute = 0;
|
|
this.lastSecond = 0;
|
|
};
|
|
|
|
StationClock_$d.prototype.draw = function() {
|
|
var clock_$d = document.getElementById(this.clockId_$d);
|
|
|
|
if (clock_$d) {
|
|
var context = clock_$d.getContext('2d');
|
|
if (context) {
|
|
this.radius = 0.75 * (Math.min(clock_$d.width, clock_$d.height) / 2);
|
|
|
|
// clear canvas and set new origin
|
|
context.clearRect(0, 0, clock_$d.width, clock_$d.height);
|
|
context.save();
|
|
context.translate(clock_$d.width / 2, clock_$d.height / 2);
|
|
|
|
// draw body
|
|
if (this.body != StationClock_$d.NoStrokeBody) {
|
|
context.save();
|
|
switch (this.body) {
|
|
case StationClock_$d.SmallWhiteBody:
|
|
this.fillCircle(context, 'rgb(255,255,255)', 0, 0, 1);
|
|
break;
|
|
case StationClock_$d.RoundBody:
|
|
this.fillCircle(context, 'rgb(255,255,255)', 0, 0, 1.1);
|
|
context.save();
|
|
this.setShadow(context, this.bodyShadowColor, this.bodyShadowOffsetX, this.bodyShadowOffsetY, this.bodyShadowBlur);
|
|
this.strokeCircle(context, 'rgb(0,0,0)', 0, 0, 1.1, 0.07);
|
|
context.restore();
|
|
break;
|
|
case StationClock_$d.RoundGreenBody:
|
|
this.fillCircle(context, 'rgb(235,236,212)', 0, 0, 1.1);
|
|
context.save();
|
|
this.setShadow(context, this.bodyShadowColor, this.bodyShadowOffsetX, this.bodyShadowOffsetY, this.bodyShadowBlur);
|
|
this.strokeCircle(context, 'rgb(180,180,180)', 0, 0, 1.1, 0.2);
|
|
context.restore();
|
|
this.strokeCircle(context, 'rgb(29,84,31)', 0, 0, 1.15, 0.1);
|
|
context.save();
|
|
this.setShadow(context, 'rgba(235,236,212,100)', -0.02, -0.02, 0.09);
|
|
this.strokeCircle(context, 'rgb(76,128,110)', 0, 0, 1.1, 0.08);
|
|
context.restore();
|
|
break;
|
|
case StationClock_$d.SquareBody:
|
|
context.save();
|
|
this.setShadow(context, this.bodyShadowColor, this.bodyShadowOffsetX, this.bodyShadowOffsetY, this.bodyShadowBlur);
|
|
this.fillSquare(context, 'rgb(237,235,226)', 0, 0, 2.4);
|
|
this.strokeSquare(context, 'rgb(38,106,186)', 0, 0, 2.32, 0.16);
|
|
context.restore();
|
|
context.save();
|
|
this.setShadow(context, this.bodyShadowColor, this.bodyShadowOffsetX, this.bodyShadowOffsetY, this.bodyShadowBlur);
|
|
this.strokeSquare(context, 'rgb(42,119,208)', 0, 0, 2.24, 0.08);
|
|
context.restore();
|
|
break;
|
|
case StationClock_$d.ViennaBody:
|
|
context.save();
|
|
this.fillSymmetricPolygon(context, 'rgb(156,156,156)', [[-1.2,1.2],[-1.2,-1.2]],0.1);
|
|
this.fillPolygon(context, 'rgb(156,156,156)', 0,1.2 , 1.2,1.2 , 1.2,0);
|
|
this.fillCircle(context, 'rgb(255,255,255)', 0, 0, 1.05, 0.08);
|
|
this.strokeCircle(context, 'rgb(0,0,0)', 0, 0, 1.05, 0.01);
|
|
this.strokeCircle(context, 'rgb(100,100,100)', 0, 0, 1.1, 0.01);
|
|
this.fillPolygon(context, 'rgb(100,100,100)', 0.45,1.2 , 1.2,1.2 , 1.2,0.45);
|
|
this.fillPolygon(context, 'rgb(170,170,170)', 0.45,-1.2 , 1.2,-1.2 , 1.2,-0.45);
|
|
this.fillPolygon(context, 'rgb(120,120,120)', -0.45,1.2 , -1.2,1.2 , -1.2,0.45);
|
|
this.fillPolygon(context, 'rgb(200,200,200)', -0.45,-1.2 , -1.2,-1.2 , -1.2,-0.45);
|
|
this.strokeSymmetricPolygon(context, 'rgb(156,156,156)', [[-1.2,1.2],[-1.2,-1.2]],0.01);
|
|
this.fillPolygon(context, 'rgb(255,0,0)', 0.05,-0.6 , 0.15,-0.6 , 0.15,-0.45 , 0.05,-0.45);
|
|
this.fillPolygon(context, 'rgb(255,0,0)', -0.05,-0.6 , -0.15,-0.6 , -0.15,-0.45 , -0.05,-0.45);
|
|
this.fillPolygon(context, 'rgb(255,0,0)', 0.05,-0.35 , 0.15,-0.35 , 0.15,-0.30 , 0.10,-0.20 , 0.05,-0.20);
|
|
this.fillPolygon(context, 'rgb(255,0,0)', -0.05,-0.35 , -0.15,-0.35 , -0.15,-0.30 , -0.10,-0.20 , -0.05,-0.20);
|
|
context.restore();
|
|
break;
|
|
}
|
|
context.restore();
|
|
}
|
|
|
|
// draw dial
|
|
for (var i = 0; i < 60; i++) {
|
|
context.save();
|
|
context.rotate(i * Math.PI / 30);
|
|
switch (this.dial) {
|
|
case StationClock_$d.SwissStrokeDial:
|
|
if ((i % 5) == 0) {
|
|
this.strokeLine(context, this.dialColor, 0.0, -1.0, 0.0, -0.75, 0.07);
|
|
} else {
|
|
this.strokeLine(context, this.dialColor, 0.0, -1.0, 0.0, -0.92, 0.026);
|
|
}
|
|
break;
|
|
case StationClock_$d.AustriaStrokeDial:
|
|
if ((i % 5) == 0) {
|
|
this.fillPolygon(context, this.dialColor, -0.04, -1.0, 0.04, -1.0, 0.03, -0.78, -0.03, -0.78);
|
|
} else {
|
|
this.strokeLine(context, this.dialColor, 0.0, -1.0, 0.0, -0.94, 0.02);
|
|
}
|
|
break;
|
|
case StationClock_$d.GermanStrokeDial:
|
|
if ((i % 15) == 0) {
|
|
this.strokeLine(context, this.dialColor, 0.0, -1.0, 0.0, -0.70, 0.08);
|
|
} else if ((i % 5) == 0) {
|
|
this.strokeLine(context, this.dialColor, 0.0, -1.0, 0.0, -0.76, 0.08);
|
|
} else {
|
|
this.strokeLine(context, this.dialColor, 0.0, -1.0, 0.0, -0.92, 0.036);
|
|
}
|
|
break;
|
|
case StationClock_$d.GermanHourStrokeDial:
|
|
if ((i % 15) == 0) {
|
|
this.strokeLine(context, this.dialColor, 0.0, -1.0, 0.0, -0.70, 0.10);
|
|
} else if ((i % 5) == 0) {
|
|
this.strokeLine(context, this.dialColor, 0.0, -1.0, 0.0, -0.74, 0.08);
|
|
}
|
|
break;
|
|
case StationClock_$d.ViennaStrokeDial:
|
|
if ((i % 15) == 0) {
|
|
this.fillPolygon(context, this.dialColor, 0.7,-0.1, 0.6,0, 0.7,0.1, 1,0.03, 1,-0.03);
|
|
} else if ((i % 5) == 0) {
|
|
this.fillPolygon(context, this.dialColor, 0.85,-0.06, 0.78,0, 0.85,0.06, 1,0.03, 1,-0.03);
|
|
}
|
|
this.fillCircle(context, this.dialColor, 0.0, -1.0, 0.03);
|
|
break;
|
|
}
|
|
context.restore();
|
|
}
|
|
|
|
var cycletime = new Date();
|
|
var cycleseconds = cycletime.getSeconds();
|
|
modulo2_$d = cycleseconds % 2; // Taktung für Readingabruf (Serverauslastung reduzieren)
|
|
|
|
if (modulo2_$d != zmodulo_$d) {
|
|
command = '{ReadingsVal("$d","alarmTime","+almtime0_$d+")}'; // alarmTime Reading lesen
|
|
url_$d = makeCommand(command);
|
|
\$.get( url_$d, function (data) {
|
|
almtime0_$d = data.replace(/\\n/g, '');
|
|
zmodulo_$d = modulo2_$d;
|
|
return (almtime0_$d, zmodulo_$d);
|
|
}
|
|
);
|
|
}
|
|
|
|
// Zeitsteuerung
|
|
if ('$tsou' == 'server') { // Serverzeit
|
|
// aktueller Timestamp in Millisekunden
|
|
command = '{ int(time*1000) }';
|
|
url_$d = makeCommand(command);
|
|
\$.get( url_$d, function (data) {
|
|
data = data.replace(/\\n/g, '');
|
|
ct_$d = parseInt(data);
|
|
return ct_$d;
|
|
}
|
|
);
|
|
|
|
time_$d = new Date(ct_$d);
|
|
|
|
} else {
|
|
time_$d = new Date(); // Clientzeit
|
|
}
|
|
|
|
if (typeof ct_$d === 'undefined') { // wenn Zeit noch undef mit lokaler Zeit initialisieren -> springen Zeiger verhindern
|
|
time_$d = new Date();
|
|
} else {
|
|
time_$d = new Date(ct_$d);
|
|
}
|
|
|
|
var millis_$d = time_$d.getMilliseconds() / 1000.0;
|
|
var seconds_$d = time_$d.getSeconds();
|
|
var minutes_$d = time_$d.getMinutes();
|
|
var hours_$d = time_$d.getHours() + this.hourOffset;
|
|
|
|
acttime_$d = ((hours_$d < 10) ? '0' : '') + hours_$d + ':' +
|
|
((minutes_$d < 10) ? '0' : '') + minutes_$d + ':' +
|
|
((seconds_$d < 10) ? '0' : '') + seconds_$d;
|
|
|
|
if (acttime_$d == '00:00:00') {
|
|
localStoreSetLastalm_$d ('$d', 'NaN'); // letzte Alarmzeit zurücksetzen zum Tageswechsel
|
|
}
|
|
|
|
checkAndDoAlm_$d ('$d', acttime_$d, almtime0_$d);
|
|
|
|
setrcurrtime (hours_$d, minutes_$d, seconds_$d); // Reading currtime mit angezeigter Zeit setzen
|
|
|
|
// draw hour hand
|
|
context.save();
|
|
context.rotate(hours_$d * Math.PI / 6 + minutes_$d * Math.PI / 360);
|
|
this.setShadow(context, this.handShadowColor, this.handShadowOffsetX, this.handShadowOffsetY, this.handShadowBlur);
|
|
switch (this.hourHand) {
|
|
case StationClock_$d.BarHourHand:
|
|
this.fillPolygon(context, this.hourHandColor, -0.05, -0.6, 0.05, -0.6, 0.05, 0.15, -0.05, 0.15);
|
|
break;
|
|
case StationClock_$d.PointedHourHand:
|
|
this.fillPolygon(context, this.hourHandColor, 0.0, -0.6, 0.065, -0.53, 0.065, 0.19, -0.065, 0.19, -0.065, -0.53);
|
|
break;
|
|
case StationClock_$d.SwissHourHand:
|
|
this.fillPolygon(context, this.hourHandColor, -0.05, -0.6, 0.05, -0.6, 0.065, 0.26, -0.065, 0.26);
|
|
break;
|
|
case StationClock_$d.ViennaHourHand:
|
|
this.fillSymmetricPolygon(context, this.hourHandColor, [[-0.02,-0.72],[-0.08,-0.56],[-0.15,-0.45],[-0.06,-0.30],[-0.03,0],[-0.1,0.2],[-0.05,0.23],[-0.03,0.2]]);
|
|
}
|
|
context.restore();
|
|
|
|
// draw minute hand
|
|
context.save();
|
|
switch (this.minuteHandBehavoir) {
|
|
case StationClock_$d.CreepingMinuteHand:
|
|
context.rotate((minutes_$d + seconds_$d / 60) * Math.PI / 30);
|
|
break;
|
|
case StationClock_$d.BouncingMinuteHand:
|
|
context.rotate(minutes_$d * Math.PI / 30);
|
|
break;
|
|
case StationClock_$d.ElasticBouncingMinuteHand:
|
|
if (this.lastMinute != minutes_$d) {
|
|
this.minuteHandAnimationStep = 3;
|
|
this.lastMinute = minutes_$d;
|
|
}
|
|
context.rotate((minutes_$d + this.getAnimationOffset(this.minuteHandAnimationStep)) * Math.PI / 30);
|
|
this.minuteHandAnimationStep--;
|
|
break;
|
|
}
|
|
this.setShadow(context, this.handShadowColor, this.handShadowOffsetX, this.handShadowOffsetY, this.handShadowBlur);
|
|
switch (this.minuteHand) {
|
|
case StationClock_$d.BarMinuteHand:
|
|
this.fillPolygon(context, this.minuteHandColor, -0.05, -0.9, 0.035, -0.9, 0.035, 0.23, -0.05, 0.23);
|
|
break;
|
|
case StationClock_$d.PointedMinuteHand:
|
|
this.fillPolygon(context, this.minuteHandColor, 0.0, -0.93, 0.045, -0.885, 0.045, 0.23, -0.045, 0.23, -0.045, -0.885);
|
|
break;
|
|
case StationClock_$d.SwissMinuteHand:
|
|
this.fillPolygon(context, this.minuteHandColor, -0.035, -0.93, 0.035, -0.93, 0.05, 0.25, -0.05, 0.25);
|
|
break;
|
|
case StationClock_$d.ViennaMinuteHand:
|
|
this.fillSymmetricPolygon(context, this.minuteHandColor, [[-0.02,-0.98],[-0.09,-0.7],[-0.03,0],[-0.05,0.2],[-0.01,0.4]]);
|
|
}
|
|
context.restore();
|
|
|
|
// draw second hand
|
|
context.save();
|
|
switch (this.secondHandBehavoir) {
|
|
case StationClock_$d.OverhastySecondHand:
|
|
context.rotate(Math.min((seconds_$d + millis_$d) * (60.0 / 58.5), 60.0) * Math.PI / 30);
|
|
break;
|
|
case StationClock_$d.CreepingSecondHand:
|
|
context.rotate((seconds_$d + millis_$d) * Math.PI / 30);
|
|
break;
|
|
case StationClock_$d.BouncingSecondHand:
|
|
context.rotate(seconds_$d * Math.PI / 30);
|
|
break;
|
|
case StationClock_$d.ElasticBouncingSecondHand:
|
|
if (this.lastSecond != seconds_$d) {
|
|
this.secondHandAnimationStep = 3;
|
|
this.lastSecond = seconds_$d;
|
|
}
|
|
context.rotate((seconds_$d + this.getAnimationOffset(this.secondHandAnimationStep)) * Math.PI / 30);
|
|
this.secondHandAnimationStep--;
|
|
break;
|
|
}
|
|
this.setShadow(context, this.handShadowColor, this.handShadowOffsetX, this.handShadowOffsetY, this.handShadowBlur);
|
|
switch (this.secondHand) {
|
|
case StationClock_$d.BarSecondHand:
|
|
this.fillPolygon(context, this.secondHandColor, -0.006, -0.92, 0.006, -0.92, 0.028, 0.23, -0.028, 0.23);
|
|
break;
|
|
case StationClock_$d.HoleShapedSecondHand:
|
|
this.fillPolygon(context, this.secondHandColor, 0.0, -0.9, 0.011, -0.889, 0.01875, -0.6, -0.01875, -0.6, -0.011, -0.889);
|
|
this.fillPolygon(context, this.secondHandColor, 0.02, -0.4, 0.025, 0.22, -0.025, 0.22, -0.02, -0.4);
|
|
this.strokeCircle(context, this.secondHandColor, 0, -0.5, 0.083, 0.066);
|
|
break;
|
|
case StationClock_$d.NewHoleShapedSecondHand:
|
|
this.fillPolygon(context, this.secondHandColor, 0.0, -0.95, 0.015, -0.935, 0.0187, -0.65, -0.0187, -0.65, -0.015, -0.935);
|
|
this.fillPolygon(context, this.secondHandColor, 0.022, -0.45, 0.03, 0.27, -0.03, 0.27, -0.022, -0.45);
|
|
this.strokeCircle(context, this.secondHandColor, 0, -0.55, 0.085, 0.07);
|
|
break;
|
|
case StationClock_$d.SwissSecondHand:
|
|
this.strokeLine(context, this.secondHandColor, 0.0, -0.6, 0.0, 0.35, 0.026);
|
|
this.fillCircle(context, this.secondHandColor, 0, -0.64, 0.1);
|
|
break;
|
|
case StationClock_$d.ViennaSecondHand:
|
|
this.strokeLine(context, this.secondHandColor, 0.0, -0.6, 0.0, 0.35, 0.026);
|
|
this.fillCircle(context, this.secondHandColor, 0, -0.64, 0.1);
|
|
break;
|
|
}
|
|
context.restore();
|
|
|
|
// draw clock boss
|
|
if (this.boss != StationClock_$d.NoBoss) {
|
|
context.save();
|
|
this.setShadow(context, this.bossShadowColor, this.bossShadowOffsetX, this.bossShadowOffsetY, this.bossShadowBlur);
|
|
switch (this.boss) {
|
|
case StationClock_$d.BlackBoss:
|
|
this.fillCircle(context, 'rgb(0,0,0)', 0, 0, 0.1);
|
|
break;
|
|
case StationClock_$d.RedBoss:
|
|
this.fillCircle(context, 'rgb(220,0,0)', 0, 0, 0.06);
|
|
break;
|
|
case StationClock_$d.ViennaBoss:
|
|
this.fillCircle(context, 'rgb(0,0,0)', 0, 0, 0.07);
|
|
break;
|
|
}
|
|
context.restore();
|
|
}
|
|
context.restore();
|
|
}
|
|
}
|
|
};
|
|
|
|
StationClock_$d.prototype.getAnimationOffset = function(animationStep) {
|
|
switch (animationStep) {
|
|
case 3: return 0.2;
|
|
case 2: return -0.1;
|
|
case 1: return 0.05;
|
|
}
|
|
return 0;
|
|
};
|
|
|
|
StationClock_$d.prototype.setShadow = function(context, color, offsetX, offsetY, blur) {
|
|
if (color) {
|
|
context.shadowColor = color;
|
|
context.shadowOffsetX = this.radius * offsetX;
|
|
context.shadowOffsetY = this.radius * offsetY;
|
|
context.shadowBlur = this.radius * blur;
|
|
}
|
|
};
|
|
|
|
StationClock_$d.prototype.fillCircle = function(context, color, x, y, radius) {
|
|
if (color) {
|
|
context.beginPath();
|
|
context.fillStyle = color;
|
|
context.arc(x * this.radius, y * this.radius, radius * this.radius, 0, 2 * Math.PI, true);
|
|
context.fill();
|
|
}
|
|
};
|
|
|
|
StationClock_$d.prototype.strokeCircle = function(context, color, x, y, radius, lineWidth) {
|
|
if (color) {
|
|
context.beginPath();
|
|
context.strokeStyle = color;
|
|
context.lineWidth = lineWidth * this.radius;
|
|
context.arc(x * this.radius, y * this.radius, radius * this.radius, 0, 2 * Math.PI, true);
|
|
context.stroke();
|
|
}
|
|
};
|
|
|
|
StationClock_$d.prototype.fillSquare = function(context, color, x, y, size) {
|
|
if (color) {
|
|
context.fillStyle = color;
|
|
context.fillRect((x - size / 2) * this.radius, (y -size / 2) * this.radius, size * this.radius, size * this.radius);
|
|
}
|
|
};
|
|
|
|
StationClock_$d.prototype.strokeSquare = function(context, color, x, y, size, lineWidth) {
|
|
if (color) {
|
|
context.strokeStyle = color;
|
|
context.lineWidth = lineWidth * this.radius;
|
|
context.strokeRect((x - size / 2) * this.radius, (y -size / 2) * this.radius, size * this.radius, size * this.radius);
|
|
}
|
|
};
|
|
|
|
StationClock_$d.prototype.strokeLine = function(context, color, x1, y1, x2, y2, width) {
|
|
if (color) {
|
|
context.beginPath();
|
|
context.strokeStyle = color;
|
|
context.moveTo(x1 * this.radius, y1 * this.radius);
|
|
context.lineTo(x2 * this.radius, y2 * this.radius);
|
|
context.lineWidth = width * this.radius;
|
|
context.stroke();
|
|
}
|
|
};
|
|
|
|
StationClock_$d.prototype.fillPolygon = function(context, color, x1, y1, x2, y2, x3, y3, x4, y4, x5, y5) {
|
|
if (color) {
|
|
context.beginPath();
|
|
context.fillStyle = color;
|
|
context.moveTo(x1 * this.radius, y1 * this.radius);
|
|
context.lineTo(x2 * this.radius, y2 * this.radius);
|
|
context.lineTo(x3 * this.radius, y3 * this.radius);
|
|
context.lineTo(x4 * this.radius, y4 * this.radius);
|
|
if ((x5 != undefined) && (y5 != undefined)) {
|
|
context.lineTo(x5 * this.radius, y5 * this.radius);
|
|
}
|
|
context.lineTo(x1 * this.radius, y1 * this.radius);
|
|
context.fill();
|
|
}
|
|
};
|
|
|
|
StationClock_$d.prototype.fillSymmetricPolygon = function(context, color, points) {
|
|
context.beginPath();
|
|
context.fillStyle = color;
|
|
context.moveTo(points[0][0] * this.radius, points[0][1] * this.radius);
|
|
for (var i = 1; i < points.length; i++) {
|
|
context.lineTo(points[i][0] * this.radius, points[i][1] * this.radius);
|
|
}
|
|
for (var i = points.length - 1; i >= 0; i--) {
|
|
context.lineTo(0 - points[i][0] * this.radius, points[i][1] * this.radius);
|
|
}
|
|
context.lineTo(points[0][0] * this.radius, points[0][1] * this.radius);
|
|
context.fill();
|
|
};
|
|
|
|
StationClock_$d.prototype.strokeSymmetricPolygon = function(context, color, points, width) {
|
|
context.beginPath();
|
|
context.strokeStyle = color;
|
|
context.moveTo(points[0][0] * this.radius, points[0][1] * this.radius);
|
|
for (var i = 1; i < points.length; i++) {
|
|
context.lineTo(points[i][0] * this.radius, points[i][1] * this.radius);
|
|
}
|
|
for (var i = points.length - 1; i >= 0; i--) {
|
|
context.lineTo(0 - points[i][0] * this.radius, points[i][1] * this.radius);
|
|
}
|
|
context.lineTo(points[0][0] * this.radius, points[0][1] * this.radius);
|
|
context.lineWidth = width * this.radius;
|
|
context.stroke();
|
|
};
|
|
|
|
var clock_$d = new StationClock_$d('clock_$d');
|
|
clock_$d.body = StationClock_$d.$sbody;
|
|
clock_$d.dial = StationClock_$d.$ssd;
|
|
clock_$d.hourHand = StationClock_$d.$shh;
|
|
clock_$d.minuteHand = StationClock_$d.$smh;
|
|
clock_$d.secondHand = StationClock_$d.$ssh;
|
|
clock_$d.boss = StationClock_$d.$sb;
|
|
clock_$d.minuteHandBehavoir = StationClock_$d.$mhb;
|
|
clock_$d.secondHandBehavoir = StationClock_$d.$shb;
|
|
|
|
function animate(clock_$d) {
|
|
clock_$d.draw();
|
|
// window.setTimeout(function(){animate(clock_$d)}, 50); // alte Variante
|
|
window.setTimeout(function(){animate(clock_$d)}, 100);
|
|
}
|
|
|
|
animate(clock_$d);
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|
|
|
|
END_JS
|
|
|
|
return qq{$back};
|
|
}
|
|
|
|
##############################################################################
|
|
# Moderne Uhr aus:
|
|
# https://www.w3schools.com/graphics/canvas_clock_start.asp
|
|
#
|
|
##############################################################################
|
|
sub modernWatch {
|
|
my ($d) = @_;
|
|
my $hash = $defs{$d};
|
|
my $facec = AttrVal($d, "modernColorFace", "FFFEFA");
|
|
my $bgc = AttrVal($d, "modernColorBackground", "333");
|
|
my $fc = AttrVal($d, "modernColorFigure", "333");
|
|
my $hc = AttrVal($d, "modernColorHand", "333");
|
|
my $fr = AttrVal($d, "modernColorRing", "FFFFFF");
|
|
my $fre = AttrVal($d, "modernColorRingEdge", "333");
|
|
my $hattr = AttrVal($d, "htmlattr", "width='150' height='150'");
|
|
my $tsou = AttrVal($d, "timeSource", "client");
|
|
my $showct = AttrVal($d, "timeAsReading", 0);
|
|
|
|
my $alarm = ReadingsVal($d, "alarmTime", "aa:bb:cc");
|
|
|
|
my $back = << "END_JS";
|
|
<html>
|
|
<body>
|
|
|
|
<canvas id='canvas_$d' $hattr style='background-color:#$bgc'>
|
|
</canvas>
|
|
|
|
<script>
|
|
|
|
var ct_$d;
|
|
var almtime0_$d = '$alarm';
|
|
var zmodulo_$d = 0; // Hilfszähler
|
|
var modulo2_$d = 0; // Hilfszähler
|
|
var showCurrTime_$d = '$showct'; // Reading currtime schreiben oder nicht
|
|
|
|
// CSRF-Token auslesen
|
|
var body = document.querySelector("body");
|
|
if( body != null ) {
|
|
csrf = body.getAttribute("fwcsrf");
|
|
}
|
|
|
|
// get the base url
|
|
function getBaseUrl () {
|
|
var url = window.location.href.split("?")[0];
|
|
url += "?";
|
|
if( csrf != null ) {
|
|
url += "fwcsrf="+csrf+"&";
|
|
}
|
|
return url;
|
|
}
|
|
|
|
function makeCommand (cmd) {
|
|
return getBaseUrl()+"cmd="+encodeURIComponent(cmd)+"&XHR=1";
|
|
}
|
|
|
|
// Template digital time display
|
|
function buildtime (hours, minutes, seconds) {
|
|
var ddt = ((hours < 10) ? '0' : '') + hours + ':' +
|
|
((minutes < 10) ? '0' : '') + minutes + ':' +
|
|
((seconds < 10) ? '0' : '') + seconds
|
|
;
|
|
return ddt;
|
|
}
|
|
|
|
|
|
// Reading currtime setzen
|
|
function setrcurrtime (h_$d, m_$d, s_$d) {
|
|
var time_$d = buildtime (h_$d, m_$d, s_$d);
|
|
if (modulo2_$d != zmodulo_$d && showCurrTime_$d != 0 && time_$d != 'NaN:NaN:NaN') {
|
|
command = '{ CommandSetReading(undef, "$d currtime '+time_$d+'") }';
|
|
url_$d = makeCommand(command);
|
|
\$.get(url_$d);
|
|
}
|
|
}
|
|
|
|
// localStorage speichern letzte Alarmzeit
|
|
function localStoreSetLastalm_$d (dev, lastalmtime) {
|
|
localStorage.setItem('lastalmtime_'+dev, lastalmtime);
|
|
}
|
|
|
|
// Check ob Alarm ausgelöst werden soll und ggf. Alarmevent triggern
|
|
function checkAndDoAlm_$d (dev, acttime, almtime) {
|
|
lastalmtime_$d = localStorage.getItem('lastalmtime_$d'); // letzte Alarmzeit laden
|
|
if ( acttime == almtime && acttime != lastalmtime_$d ) {
|
|
command = '{ CommandSetReading(undef, "$d alarmed '+acttime+'") }';
|
|
url_$d = makeCommand(command);
|
|
|
|
localStoreSetLastalm_$d (dev, acttime); // aktuelle Alarmzeit sichern
|
|
|
|
if(acttime == almtime) {
|
|
\$.get(url_$d);
|
|
|
|
} else {
|
|
\$.get(url_$d, function (data) {
|
|
command = '{ CommandSetReading(undef, "$d state stopped") }';
|
|
url_$d = makeCommand(command);
|
|
\$.get(url_$d);
|
|
}
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
var canvas_$d = document.getElementById('canvas_$d');
|
|
var ctx_$d = canvas_$d.getContext('2d');
|
|
var radius_$d = canvas_$d.height / 2;
|
|
|
|
ctx_$d.translate(radius_$d, radius_$d);
|
|
radius_$d = radius_$d * 0.90
|
|
// setInterval(drawClock_$d, 1000); // alte Variante
|
|
setInterval(drawClock_$d, 100);
|
|
|
|
function drawClock_$d() {
|
|
drawFace_$d (ctx_$d, radius_$d);
|
|
drawnumbers_$d (ctx_$d, radius_$d);
|
|
drawTime_$d (ctx_$d, radius_$d);
|
|
}
|
|
|
|
function drawFace_$d(ctx_$d, radius_$d) {
|
|
var grad_$d;
|
|
ctx_$d.beginPath();
|
|
ctx_$d.arc(0, 0, radius_$d, 0, 2*Math.PI);
|
|
ctx_$d.fillStyle = '#$facec'; // Füllung Uhr
|
|
ctx_$d.fill();
|
|
grad_$d = ctx_$d.createRadialGradient(0,0,radius_$d*0.95, 0,0,radius_$d*1.05);
|
|
grad_$d.addColorStop(0, '#$hc'); // Farbe Zeiger und innere Ringgrenze
|
|
grad_$d.addColorStop(0.5, '#$fr'); // Farbe Ziffernblattring
|
|
grad_$d.addColorStop(1, '#$fre'); // Farbe äußere Ringgrenze
|
|
ctx_$d.strokeStyle = grad_$d;
|
|
ctx_$d.lineWidth = radius_$d*0.1;
|
|
ctx_$d.stroke();
|
|
ctx_$d.beginPath();
|
|
ctx_$d.arc(0, 0, radius_$d*0.1, 0, 2*Math.PI);
|
|
ctx_$d.fillStyle = '#$fc'; // Farbe Ziffern und Zeigerwelle
|
|
ctx_$d.fill();
|
|
}
|
|
|
|
function drawnumbers_$d(ctx_$d, radius_$d) {
|
|
var ang_$d;
|
|
var num_$d;
|
|
ctx_$d.font = radius_$d*0.15 + 'px arial';
|
|
ctx_$d.textBaseline ='middle';
|
|
ctx_$d.textAlign ='center';
|
|
|
|
for(num_$d = 1; num_$d < 13; num_$d++){
|
|
ang_$d = num_$d * Math.PI / 6;
|
|
ctx_$d.rotate(ang_$d);
|
|
ctx_$d.translate(0, -radius_$d*0.85);
|
|
ctx_$d.rotate(-ang_$d);
|
|
ctx_$d.fillText(num_$d.toString(), 0, 0);
|
|
ctx_$d.rotate(ang_$d);
|
|
ctx_$d.translate(0, radius_$d*0.85);
|
|
ctx_$d.rotate(-ang_$d);
|
|
}
|
|
}
|
|
|
|
function drawTime_$d(ctx_$d, radius_$d){
|
|
var cycletime = new Date();
|
|
var cycleseconds = cycletime.getSeconds();
|
|
modulo2_$d = cycleseconds % 2; // Taktung für Readingabruf (Serverauslastung reduzieren)
|
|
|
|
if (modulo2_$d != zmodulo_$d) {
|
|
command = '{ReadingsVal("$d","alarmTime","+almtime0_$d+")}'; // alarmTime Reading lesen
|
|
url_$d = makeCommand(command);
|
|
\$.get( url_$d, function (data) {
|
|
almtime0_$d = data.replace(/\\n/g, '');
|
|
zmodulo_$d = modulo2_$d;
|
|
return (almtime0_$d, zmodulo_$d);
|
|
}
|
|
);
|
|
}
|
|
// Zeitsteuerung
|
|
if ('$tsou' == 'server') { // Serverzeit
|
|
// aktueller Timestamp in Millisekunden
|
|
command = '{ int(time*1000) }';
|
|
url_$d = makeCommand(command);
|
|
\$.get( url_$d, function (data) {
|
|
data = data.replace(/\\n/g, '');
|
|
ct_$d = parseInt(data);
|
|
return ct_$d;
|
|
}
|
|
);
|
|
|
|
time_$d = new Date(ct_$d);
|
|
|
|
} else {
|
|
time_$d = new Date(); // Clientzeit
|
|
}
|
|
|
|
if (typeof ct_$d === 'undefined') { // wenn Zeit noch undef mit lokaler Zeit initialisieren -> springen Zeiger verhindern
|
|
time_$d = new Date();
|
|
} else {
|
|
time_$d = new Date(ct_$d);
|
|
}
|
|
|
|
var hour_$d = time_$d.getHours();
|
|
var minute_$d = time_$d.getMinutes();
|
|
var second_$d = time_$d.getSeconds();
|
|
|
|
acttime_$d = ((hour_$d < 10) ? '0' : '') + hour_$d + ':' +
|
|
((minute_$d < 10) ? '0' : '') + minute_$d + ':' +
|
|
((second_$d < 10) ? '0' : '') + second_$d;
|
|
|
|
if (acttime_$d == '00:00:00') {
|
|
localStoreSetLastalm_$d ('$d', 'NaN'); // letzte Alarmzeit zurücksetzen zum Tageswechsel
|
|
}
|
|
|
|
checkAndDoAlm_$d ('$d', acttime_$d, almtime0_$d);
|
|
|
|
setrcurrtime (hour_$d, minute_$d, second_$d); // Reading currtime mit angezeigter Zeit setzen
|
|
|
|
//hour_$d
|
|
hour_$d = hour_$d%12;
|
|
hour_$d = (hour_$d*Math.PI/6)+
|
|
(minute_$d*Math.PI/(6*60))+
|
|
(second_$d*Math.PI/(360*60));
|
|
drawHand_$d(ctx_$d, hour_$d, radius_$d*0.5, radius_$d*0.07);
|
|
|
|
//minute_$d
|
|
minute_$d = (minute_$d*Math.PI/30)+(second_$d*Math.PI/(30*60));
|
|
drawHand_$d(ctx_$d, minute_$d, radius_$d*0.8, radius_$d*0.07);
|
|
|
|
// second_$d
|
|
second_$d = (second_$d*Math.PI/30);
|
|
drawHand_$d(ctx_$d, second_$d, radius_$d*0.9, radius_$d*0.02);
|
|
}
|
|
|
|
function drawHand_$d(ctx_$d, pos, length, width) {
|
|
ctx_$d.beginPath();
|
|
ctx_$d.lineWidth = width;
|
|
ctx_$d.lineCap = 'round';
|
|
ctx_$d.moveTo(0,0);
|
|
ctx_$d.rotate(pos);
|
|
ctx_$d.lineTo(0, -length);
|
|
ctx_$d.stroke();
|
|
ctx_$d.rotate(-pos);
|
|
}
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|
|
|
|
END_JS
|
|
|
|
return qq{$back};
|
|
}
|
|
|
|
##############################################################################
|
|
# Versionierungen des Moduls setzen
|
|
# Die Verwendung von Meta.pm und Packages wird berücksichtigt
|
|
#
|
|
##############################################################################
|
|
sub setVersionInfo {
|
|
my ($hash) = @_;
|
|
my $name = $hash->{NAME};
|
|
|
|
my $v = (sortTopicNum("desc",keys %vNotesIntern))[0];
|
|
my $type = $hash->{TYPE};
|
|
$hash->{HELPER}{PACKAGE} = __PACKAGE__;
|
|
$hash->{HELPER}{VERSION} = $v;
|
|
|
|
if($modules{$type}{META}{x_prereqs_src} && !$hash->{HELPER}{MODMETAABSENT}) {
|
|
# META-Daten sind vorhanden
|
|
$modules{$type}{META}{version} = "v".$v; # Version aus META.json überschreiben, Anzeige mit {Dumper $modules{SMAPortal}{META}}
|
|
if($modules{$type}{META}{x_version}) {
|
|
$modules{$type}{META}{x_version} =~ s/1\.1\.1/$v/gx;
|
|
} else {
|
|
$modules{$type}{META}{x_version} = $v;
|
|
}
|
|
return $@ unless (FHEM::Meta::SetInternals($hash));
|
|
if(__PACKAGE__ eq "FHEM::$type" || __PACKAGE__ eq $type) {
|
|
# es wird mit Packages gearbeitet -> Perl übliche Modulversion setzen
|
|
# mit {<Modul>->VERSION()} im FHEMWEB kann Modulversion abgefragt werden
|
|
use version 0.77; our $VERSION = FHEM::Meta::Get( $hash, 'version' ); ## no critic 'VERSION'
|
|
}
|
|
} else {
|
|
# herkömmliche Modulstruktur
|
|
$hash->{VERSION} = $v;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
1;
|
|
|
|
=pod
|
|
=item helper
|
|
=item summary Clock display in different variants
|
|
=item summary_DE Uhrenanzeige in verschiedenen Varianten
|
|
|
|
=begin html
|
|
|
|
<a name="Watches"></a>
|
|
<h3>Watches</h3>
|
|
|
|
<br>
|
|
The module Watches provides watches in different styles as Device. The module is a JavaScript application that runs on a
|
|
client (browser) and not on the FHEM server. Attributes and readings are read asynchronously from the server and possibly
|
|
also written, but only if the application is currently running in the browser. <br>
|
|
The user can influence the design of the watches via attributes. <br>
|
|
The clocks are based on scripts of these pages: <br>
|
|
|
|
<a href='https://www.w3schools.com/graphics/canvas_clock_start.asp'>modern watch</a>,
|
|
<a href='http://www.3quarks.com/de/Bahnhofsuhr/'>Station clock</a>,
|
|
<a href='http://www.3quarks.com/de/Segmentanzeige/'>Digital display</a>
|
|
<br><br>
|
|
|
|
A device of the model <b>Digital</b> can also be used as stopwatch, countdown timer or
|
|
universal text display (for sixteen segment mode see attribute <a href="#digitalSegmentType">digitalSegmentType</a>). <br>
|
|
As time source the client (browser time) as well as the FHEM server can be set
|
|
(attribute <a href="#timeSource">timeSource</a>). <br>
|
|
|
|
<ul>
|
|
<a name="WatchesDefine"></a>
|
|
<b>Define</b>
|
|
|
|
<ul>
|
|
define <name> Watches [Modern | Station | Digital]
|
|
<br><br>
|
|
|
|
<table>
|
|
<colgroup> <col width=5%> <col width=95%> </colgroup>
|
|
<tr><td> <b>Modern</b> </td><td>: creates an analog clock with a modern design </td></tr>
|
|
<tr><td> <b>Station</b> </td><td>: creates a station clock </td></tr>
|
|
<tr><td> <b>Digital</b> </td><td>: creates a digital display (clock, (CountDown)stop watch, static time display or text) </td></tr>
|
|
</table>
|
|
<br>
|
|
<br>
|
|
</ul>
|
|
|
|
<a name="WatchesSet"></a>
|
|
<b>Set</b>
|
|
|
|
<ul>
|
|
<ul>
|
|
|
|
<a name="alarmSet"></a>
|
|
<li><b>alarmSet <hh> <mm> <ss> </b><br>
|
|
Sets the alarm time in the format hh hours, mm minutes and ss seconds. <br>
|
|
If the time reaches the defined value, an event of the reading "alarmed" is triggered. <br>
|
|
This set command is only available for digital stopwatches. <br><br>
|
|
|
|
<ul>
|
|
<b>Example</b> <br>
|
|
set <name> alarmSet 0 30 10
|
|
</ul>
|
|
<br>
|
|
|
|
</li>
|
|
<br>
|
|
|
|
<a name="alarmDel"></a>
|
|
<li><b>alarmDel</b><br>
|
|
Clears the set alarm time and its status. <br>
|
|
This set command is only available for digital stopwatches. <br>
|
|
</li>
|
|
<br>
|
|
|
|
<a name="countDownInit"></a>
|
|
<li><b>countDownInit <hh> <mm> <ss> | <seconds> </b><br>
|
|
Sets the start time of a countdown stopwatch.
|
|
The format can be <hh> hours, <mm> minutes and <ss> seconds or alternatively only one entry in seconds. <br>
|
|
This set command is only available with a digital countdown stopwatch. <br><br>
|
|
|
|
<ul>
|
|
<b>Examples</b> <br>
|
|
set <name> countDownInit 0 30 10 <br>
|
|
set <name> countDownInit 3600
|
|
</ul>
|
|
<br>
|
|
|
|
</li>
|
|
<br>
|
|
|
|
<a name="displayTextSet"></a>
|
|
<li><b>displayTextSet</b><br>
|
|
Sets the text to be displayed. <br>
|
|
This set command is only available for a digital segment display with "digitalDisplayPattern = text". <br>
|
|
(default: blank display) <br><br>
|
|
|
|
<b>Note:</b> <br>
|
|
The displayable characters depend on the attribute "digitalSegmentType". <br>
|
|
With the (default) seven-segment display, only numbers, hyphen, underscore and the letters
|
|
A, b, C, d, E, F, H, L, n, o, P, r, t, U and Y are displayed.
|
|
In addition to numbers, short texts such as "Error", "HELP", "run" or "PLAY" can also be displayed. <br>
|
|
For text display it is recommended to set the sixteen segment display with the attribute "digitalSegmentType" !
|
|
</li>
|
|
<br>
|
|
|
|
<a name="displayTextDel"></a>
|
|
<li><b>displayTextDel</b><br>
|
|
Deletes the display text. <br>
|
|
This set command is only available for a digital segment display with "digitalDisplayPattern = text". <br>
|
|
</li>
|
|
<br>
|
|
|
|
<a name="reset"></a>
|
|
<li><b>reset</b><br>
|
|
Stops the stopwatch (if running) and clears all specific readings or resets it to initialized. <br>
|
|
This set command is only available for digital stopwatches. <br>
|
|
</li>
|
|
<br>
|
|
|
|
<a name="resume"></a>
|
|
<li><b>resume</b><br>
|
|
Resumes counting a stopped stopwatch. <br>
|
|
This set command is only available for digital stopwatches. <br>
|
|
</li>
|
|
<br>
|
|
|
|
<a name="start"></a>
|
|
<li><b>start</b><br>
|
|
Starts the stopwatch. <br>
|
|
This set command is only available for digital stopwatches. <br>
|
|
</li>
|
|
<br>
|
|
|
|
<a name="stop"></a>
|
|
<li><b>stop</b><br>
|
|
Stop the stopwatch. The achieved time is retained. <br>
|
|
This set command is only available for digital stopwatches. <br>
|
|
</li>
|
|
<br>
|
|
|
|
<a name="textTicker"></a>
|
|
<li><b>textTicker on | off </b><br>
|
|
Switches the ticker mode of a text display (see attribute digitalDisplayPattern) on or off. <br>
|
|
(default: off)
|
|
</li>
|
|
<br>
|
|
|
|
<a name="time"></a>
|
|
<li><b>time <hh> <mm> <ss> </b><br>
|
|
Sets a static time display with hh hours, mm minutes and ss seconds. <br>
|
|
This set command is only available for a digital clock with static time display. <br><br>
|
|
|
|
<ul>
|
|
<b>Example</b> <br>
|
|
set <name> time 8 15 3
|
|
</ul>
|
|
|
|
</li>
|
|
<br>
|
|
|
|
</ul>
|
|
</ul>
|
|
<br>
|
|
|
|
<a name="WatchesGet"></a>
|
|
<b>Get</b>
|
|
|
|
<ul>
|
|
|
|
N/A
|
|
|
|
</ul>
|
|
<br>
|
|
|
|
<a name="WatchesAttr"></a>
|
|
<b>Attributes</b>
|
|
<br><br>
|
|
|
|
<ul>
|
|
<ul>
|
|
|
|
<a name="controlButtonSize"></a>
|
|
<li><b>controlButtonSize</b><br>
|
|
Changes the size of the control buttons if the clock type has control buttons.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="disable"></a>
|
|
<li><b>disable</b><br>
|
|
Activates/deactivates the Device.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="hideButtons"></a>
|
|
<li><b>hideButtons</b><br>
|
|
Hides the control buttons if the watch type has control buttons.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="hideDisplayName"></a>
|
|
<li><b>hideDisplayName</b><br>
|
|
Hides the Device/Alias name (link to detail view).
|
|
</li>
|
|
<br>
|
|
|
|
<a name="htmlattr"></a>
|
|
<li><b>htmlattr</b><br>
|
|
Additional HTML tags to resize the clock / display. <br><br>
|
|
<ul>
|
|
<b>Example: </b><br>
|
|
attr <name> htmlattr width="125" height="125" <br>
|
|
</ul>
|
|
</li>
|
|
<br>
|
|
|
|
<a name="timeAsReading"></a>
|
|
<li><b>timeAsReading</b><br>
|
|
If set, a displayed time is written to the reading currtime.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="timeSource"></a>
|
|
<li><b>timeSource</b><br>
|
|
Selects the time source. The local client time (browser) or the FHEM server time can be displayed. <br>
|
|
This setting is not relevant for (countdown) stopwatches. <br>
|
|
[default: client]
|
|
</li>
|
|
<br>
|
|
|
|
</ul>
|
|
|
|
The following attributes must be set specifically for a clock type. <br>
|
|
<br>
|
|
|
|
<b>Model: Modern</b> <br><br>
|
|
|
|
<ul>
|
|
<a name="modernColorBackground"></a>
|
|
<li><b>modernColorBackground</b><br>
|
|
Background color of the clock.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="modernColorFace"></a>
|
|
<li><b>modernColorFace</b><br>
|
|
Colouring of the dial.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="modernColorFigure"></a>
|
|
<li><b>modernColorFigure</b><br>
|
|
Colour of the numbers on the dial and the pointer axle cover.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="modernColorHand"></a>
|
|
<li><b>modernColorHand</b><br>
|
|
Colour of the watch hands.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="modernColorRing"></a>
|
|
<li><b>modernColorRing</b><br>
|
|
Colour of the dial frame.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="modernColorRingEdge"></a>
|
|
<li><b>modernColorRingEdge</b><br>
|
|
Colour of the outer ring of the dial frame.
|
|
</li>
|
|
<br>
|
|
</ul>
|
|
<br>
|
|
|
|
<b>Model: Station</b> <br><br>
|
|
|
|
<ul>
|
|
<a name="stationBody"></a>
|
|
<li><b>stationBody</b><br>
|
|
Type of watch case.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="stationBoss"></a>
|
|
<li><b>stationBoss</b><br>
|
|
Type and colour of the pointer axle cover.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="stationHourHand"></a>
|
|
<li><b>stationHourHand</b><br>
|
|
Type of hour hand.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="stationMinuteHand"></a>
|
|
<li><b>stationMinuteHand</b><br>
|
|
Type of minute hand.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="stationMinuteHandBehavoir"></a>
|
|
<li><b>stationMinuteHandBehavoir</b><br>
|
|
Behavior of the minute hand.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="stationSecondHand"></a>
|
|
<li><b>stationSecondHand</b><br>
|
|
Type of second hand.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="stationSecondHandBehavoir"></a>
|
|
<li><b>stationSecondHandBehavoir</b><br>
|
|
Behavior of the second hand.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="stationStrokeDial"></a>
|
|
<li><b>stationStrokeDial</b><br>
|
|
Selection of the dial.
|
|
</li>
|
|
<br>
|
|
|
|
</ul>
|
|
<br>
|
|
|
|
<b>Model: Digital</b> <br><br>
|
|
|
|
<ul>
|
|
<a name="digitalBorderDistance"></a>
|
|
<li><b>digitalBorderDistance</b><br>
|
|
Left and right distance of the digital text display from the background edge. <br>
|
|
(default: 8)
|
|
</li>
|
|
<br>
|
|
|
|
<a name="digitalColorBackground"></a>
|
|
<li><b>digitalColorBackground</b><br>
|
|
Digital clock background color.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="digitalColorDigits"></a>
|
|
<li><b>digitalColorDigits</b><br>
|
|
Color of the bar display in a digital watch.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="digitalDigitAngle"></a>
|
|
<li><b>digitalDigitAngle </b><br>
|
|
Adjusts the tilt angle of the displayed characters. <br>
|
|
(default: 9)
|
|
</li>
|
|
<br>
|
|
|
|
<a name="digitalDigitDistance"></a>
|
|
<li><b>digitalDigitDistance </b><br>
|
|
Adjusts the character spacing. <br>
|
|
(default: 2)
|
|
</li>
|
|
<br>
|
|
|
|
<a name="digitalDigitHeight"></a>
|
|
<li><b>digitalDigitHeight </b><br>
|
|
Adjusts the character height. <br>
|
|
(default: 20)
|
|
</li>
|
|
<br>
|
|
|
|
<a name="digitalDigitWidth"></a>
|
|
<li><b>digitalDigitWidth </b><br>
|
|
Adjusts the character width. <br>
|
|
(default: 12)
|
|
</li>
|
|
<br>
|
|
|
|
<a name="digitalDisplayPattern"></a>
|
|
<li><b>digitalDisplayPattern [countdownwatch | staticwatch | stopwatch | text | watch]</b><br>
|
|
Switching the digital display between a clock (default), a stopwatch, static time display or text display.
|
|
The text to be displayed in text display mode can be defined with <br>
|
|
<b>set <name> displayText</b>. <br><br>
|
|
|
|
<b>Note:</b> For text display it is recommended to set the attribute "digitalSegmentType" to "16". <br><br>
|
|
|
|
<ul>
|
|
<table>
|
|
<colgroup> <col width=5%> <col width=95%> </colgroup>
|
|
<tr><td> <b>countdownwatch </b> </td><td>: CountDown Stopwatch </td></tr>
|
|
<tr><td> <b>staticwatch</b> </td><td>: static time display </td></tr>
|
|
<tr><td> <b>stopwatch</b> </td><td>: Stopwatch </td></tr>
|
|
<tr><td> <b>text</b> </td><td>: Display of a definable text </td></tr>
|
|
<tr><td> <b>watch</b> </td><td>: Watch </td></tr>
|
|
</table>
|
|
</ul>
|
|
<br>
|
|
<br>
|
|
</li>
|
|
|
|
<a name="digitalSegmentDistance"></a>
|
|
<li><b>digitalSegmentDistance </b><br>
|
|
Defines the distance between the segments. <br>
|
|
(default: 0.5)
|
|
</li>
|
|
<br>
|
|
|
|
<a name="digitalSegmentType"></a>
|
|
<li><b>digitalSegmentType </b><br>
|
|
Switches the segment number of the digital display. <br>
|
|
(default: 7)
|
|
</li>
|
|
<br>
|
|
|
|
<a name="digitalSegmentWidth"></a>
|
|
<li><b>digitalSegmentWidth </b><br>
|
|
Changes the width of the individual segments. <br>
|
|
(default: 1.5)
|
|
</li>
|
|
<br>
|
|
|
|
<a name="digitalTextDigitNumber"></a>
|
|
<li><b>digitalTextDigitNumber <Quantity> </b><br>
|
|
If <Quantity> > 0, the number of digits of a text display (digitalDisplayPattern = text) is fixed.
|
|
If <Quantity> = 0 or not set, the setting is made automatically. In this case an adaptation is made
|
|
of the character size to the number depending on the set display size (see htmlattr). <br>
|
|
(default: 0)
|
|
</li>
|
|
<br>
|
|
|
|
</ul>
|
|
</ul>
|
|
|
|
</ul>
|
|
|
|
=end html
|
|
=begin html_DE
|
|
|
|
<a name="Watches"></a>
|
|
<h3>Watches</h3>
|
|
|
|
<br>
|
|
Das Modul Watches stellt Uhren in unterschiedlichen Stilen als Device zur Verfügung. Das Modul ist eine JavaScript Anwendung
|
|
die auf einem Client (Browser) ausgeführt wird und nicht auf dem FHEM Server. Attribute und Readings werden asynchron vom
|
|
Server gelesen und evtl. auch geschrieben, allerdings nur dann wenn die Anwendung aktuell im Browser ausgeführt wird. <br>
|
|
Der Nutzer kann das Design der Uhren über Attribute beeinflussen. <br>
|
|
Die Uhren basieren auf Skripten dieser Seiten: <br>
|
|
|
|
<a href='https://www.w3schools.com/graphics/canvas_clock_start.asp'>moderne Uhr</a>,
|
|
<a href='http://www.3quarks.com/de/Bahnhofsuhr/'>Bahnhofsuhr</a>,
|
|
<a href='http://www.3quarks.com/de/Segmentanzeige/'>Digitalanzeige</a>
|
|
<br><br>
|
|
|
|
Ein Device vom Model <b>Digital</b> kann ebenfalls als Stoppuhr, CountDown-Timer oder
|
|
universelle Textanzeige (für Sechzehnsegment-Modus siehe Attribut <a href="#digitalSegmentType">digitalSegmentType</a>)
|
|
verwendet werden. <br>
|
|
Als Zeitquelle können sowohl der Client (Browserzeit) als auch der FHEM-Server eingestellt werden
|
|
(Attribut <a href="#timeSource">timeSource</a>). <br>
|
|
|
|
<ul>
|
|
<a name="WatchesDefine"></a>
|
|
<b>Define</b>
|
|
|
|
<ul>
|
|
define <name> Watches [Modern | Station | Digital]
|
|
<br><br>
|
|
|
|
<table>
|
|
<colgroup> <col width=5%> <col width=95%> </colgroup>
|
|
<tr><td> <b>Modern</b> </td><td>: erstellt eine analoge Uhr im modernen Design </td></tr>
|
|
<tr><td> <b>Station</b> </td><td>: erstellt eine Bahnhofsuhr </td></tr>
|
|
<tr><td> <b>Digital</b> </td><td>: erstellt eine Digitalanzeige (Uhr, (CountDown)Stoppuhr, statische Zeitanzeige oder Text) </td></tr>
|
|
</table>
|
|
<br>
|
|
<br>
|
|
</ul>
|
|
|
|
<a name="WatchesSet"></a>
|
|
<b>Set</b>
|
|
|
|
<ul>
|
|
<ul>
|
|
|
|
<a name="alarmSet"></a>
|
|
<li><b>alarmSet <hh> <mm> <ss> </b><br>
|
|
Setzt die Alarmzeit im Format hh-Stunden, mm-Minuten und ss-Sekunden. <br>
|
|
Erreicht die Zeit den definierten Wert, wird ein Event des Readings "alarmed" ausgelöst. <br>
|
|
Dieses Set-Kommando ist nur bei digitalen Stoppuhren vorhanden. <br><br>
|
|
|
|
<ul>
|
|
<b>Beispiel</b> <br>
|
|
set <name> alarmSet 0 30 10
|
|
</ul>
|
|
<br>
|
|
|
|
</li>
|
|
<br>
|
|
|
|
<a name="alarmDel"></a>
|
|
<li><b>alarmDel</b><br>
|
|
Löscht die gesetzte Alarmzeit und deren Status. <br>
|
|
Dieses Set-Kommando ist nur bei digitalen Stoppuhren vorhanden. <br>
|
|
</li>
|
|
<br>
|
|
|
|
<a name="countDownInit"></a>
|
|
<li><b>countDownInit <hh> <mm> <ss> | <Sekunden> </b><br>
|
|
Setzt die Startzeit einer CountDown-Stoppuhr.
|
|
Das Format kann sein <hh> Stunden, <mm> Minuten und <ss> Sekunden oder alternativ nur eine Angabe in Sekunden. <br>
|
|
Dieses Set-Kommando ist nur bei einer digitalen CountDown-Stoppuhr vorhanden. <br><br>
|
|
|
|
<ul>
|
|
<b>Beispiel</b> <br>
|
|
set <name> countDownInit 0 30 10 <br>
|
|
set <name> countDownInit 3600
|
|
</ul>
|
|
<br>
|
|
|
|
</li>
|
|
<br>
|
|
|
|
<a name="displayTextSet"></a>
|
|
<li><b>displayTextSet</b><br>
|
|
Stellt den anzuzeigenden Text ein. <br>
|
|
Dieses Set-Kommando ist nur bei einer digitalen Segmentanzeige mit "digitalDisplayPattern = text" vorhanden. <br>
|
|
(default: leere Anzeige) <br><br>
|
|
|
|
<b>Hinweis:</b> <br>
|
|
Die darstellbaren Zeichen sind vom Attribut "digitalSegmentType" abhängig. <br>
|
|
Mit der (default) Siebensegmentanzeige können lediglich Ziffern, Bindestrich, Unterstrich und die Buchstaben
|
|
A, b, C, d, E, F, H, L, n, o, P, r, t, U und Y angezeigt werden.
|
|
Damit lassen sich außer Zahlen auch kurze Texte wie „Error“, „HELP“, „run“ oder „PLAY“ anzeigen. <br>
|
|
Für Textdarstellung wird empfohlen die Sechzehnsegmentanzeige mit dem Attribut "digitalSegmentType" einzustellen !
|
|
</li>
|
|
<br>
|
|
|
|
<a name="displayTextDel"></a>
|
|
<li><b>displayTextDel</b><br>
|
|
Löscht den Anzeigetext. <br>
|
|
Dieses Set-Kommando ist nur bei einer digitalen Segmentanzeige mit "digitalDisplayPattern = text" vorhanden. <br>
|
|
</li>
|
|
<br>
|
|
|
|
<a name="reset"></a>
|
|
<li><b>reset</b><br>
|
|
Stoppt die Stoppuhr (falls sie läuft) und löscht alle spezifischen Readings bzw. setzt sie auf initialized zurück. <br>
|
|
Dieses Set-Kommando ist nur bei digitalen Stoppuhren vorhanden. <br>
|
|
</li>
|
|
<br>
|
|
|
|
<a name="resume"></a>
|
|
<li><b>resume</b><br>
|
|
Setzt die Zählung einer angehaltenen Stoppuhr fort. <br>
|
|
Dieses Set-Kommando ist nur bei digitalen Stoppuhren vorhanden. <br>
|
|
</li>
|
|
<br>
|
|
|
|
<a name="start"></a>
|
|
<li><b>start</b><br>
|
|
Startet die Stoppuhr. <br>
|
|
Dieses Set-Kommando ist nur bei digitalen Stoppuhren vorhanden. <br>
|
|
</li>
|
|
<br>
|
|
|
|
<a name="stop"></a>
|
|
<li><b>stop</b><br>
|
|
Stoppt die Stoppuhr. Die erreichte Zeit bleibt erhalten. <br>
|
|
Dieses Set-Kommando ist nur bei digitalen Stoppuhren vorhanden. <br>
|
|
</li>
|
|
<br>
|
|
|
|
<a name="textTicker"></a>
|
|
<li><b>textTicker on | off </b><br>
|
|
Schaltet den Laufschriftmodus einer Textanzeige (siehe Attribut digitalDisplayPattern) ein bzw. aus. <br>
|
|
(default: off)
|
|
</li>
|
|
<br>
|
|
|
|
<a name="time"></a>
|
|
<li><b>time <hh> <mm> <ss> </b><br>
|
|
Setzt eine statische Zeitanzeige mit hh-Stunden, mm-Minuten und ss-Sekunden. <br>
|
|
Dieses Set-Kommando ist nur bei einer Digitaluhr mit statischer Zeitanzeige vorhanden. <br><br>
|
|
|
|
<ul>
|
|
<b>Beispiel</b> <br>
|
|
set <name> time 8 15 3
|
|
</ul>
|
|
|
|
</li>
|
|
<br>
|
|
|
|
</ul>
|
|
</ul>
|
|
<br>
|
|
|
|
<a name="WatchesGet"></a>
|
|
<b>Get</b>
|
|
|
|
<ul>
|
|
|
|
N/A
|
|
|
|
</ul>
|
|
<br>
|
|
|
|
<a name="WatchesAttr"></a>
|
|
<b>Attribute</b>
|
|
<br><br>
|
|
|
|
<ul>
|
|
<ul>
|
|
|
|
<a name="controlButtonSize"></a>
|
|
<li><b>controlButtonSize</b><br>
|
|
Ändert die Größe der Steuerdrucktasten sofern der Uhrentyp über Steuerdrucktasten verfügt.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="disable"></a>
|
|
<li><b>disable</b><br>
|
|
Aktiviert/deaktiviert das Device.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="hideButtons"></a>
|
|
<li><b>hideButtons</b><br>
|
|
Verbirgt die Steuerdrucktasten sofern der Uhrentyp über Steuerdrucktasten verfügt.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="hideDisplayName"></a>
|
|
<li><b>hideDisplayName</b><br>
|
|
Verbirgt den Device/Alias-Namen (Link zur Detailansicht).
|
|
</li>
|
|
<br>
|
|
|
|
<a name="htmlattr"></a>
|
|
<li><b>htmlattr</b><br>
|
|
Zusätzliche HTML Tags zur Größenänderung der Uhr / Anzeige. <br><br>
|
|
<ul>
|
|
<b>Beispiel: </b><br>
|
|
attr <name> htmlattr width="125" height="125" <br>
|
|
</ul>
|
|
</li>
|
|
<br>
|
|
|
|
<a name="timeAsReading"></a>
|
|
<li><b>timeAsReading</b><br>
|
|
Wenn gesetzt, wird eine angezeigte Uhrzeit in das Reading currtime geschrieben.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="timeSource"></a>
|
|
<li><b>timeSource</b><br>
|
|
Wählt die Zeitquelle aus. Es kann die lokale Clientzeit (Browser) oder die Zeit des FHEM-Servers angezeigt werden. <br>
|
|
Diese Einstellung ist bei (CountDown-)Stoppuhren nicht relevant. <br>
|
|
(default: client)
|
|
</li>
|
|
<br>
|
|
|
|
</ul>
|
|
|
|
Die nachfolgenden Attribute sind spezifisch für einen Uhrentyp zu setzen. <br>
|
|
<br>
|
|
|
|
<b>Model: Modern</b> <br><br>
|
|
|
|
<ul>
|
|
<a name="modernColorBackground"></a>
|
|
<li><b>modernColorBackground</b><br>
|
|
Hintergrundfarbe der Uhr.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="modernColorFace"></a>
|
|
<li><b>modernColorFace</b><br>
|
|
Einfärbung des Ziffernblattes.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="modernColorFigure"></a>
|
|
<li><b>modernColorFigure</b><br>
|
|
Farbe der Ziffern im Ziffernblatt und der Zeigerachsabdeckung.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="modernColorHand"></a>
|
|
<li><b>modernColorHand</b><br>
|
|
Farbe der UhrenZeiger.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="modernColorRing"></a>
|
|
<li><b>modernColorRing</b><br>
|
|
Farbe des Ziffernblattrahmens.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="modernColorRingEdge"></a>
|
|
<li><b>modernColorRingEdge</b><br>
|
|
Farbe des Außenringes vom Ziffernblattrahmen.
|
|
</li>
|
|
<br>
|
|
</ul>
|
|
<br>
|
|
|
|
<b>Model: Station</b> <br><br>
|
|
|
|
<ul>
|
|
<a name="stationBody"></a>
|
|
<li><b>stationBody</b><br>
|
|
Art des Uhrengehäuses.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="stationBoss"></a>
|
|
<li><b>stationBoss</b><br>
|
|
Art und Farbe der Zeigerachsabdeckung.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="stationHourHand"></a>
|
|
<li><b>stationHourHand</b><br>
|
|
Art des Stundenzeigers.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="stationMinuteHand"></a>
|
|
<li><b>stationMinuteHand</b><br>
|
|
Art des Minutenzeigers.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="stationMinuteHandBehavoir"></a>
|
|
<li><b>stationMinuteHandBehavoir</b><br>
|
|
Verhalten des Minutenzeigers.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="stationSecondHand"></a>
|
|
<li><b>stationSecondHand</b><br>
|
|
Art des Sekundenzeigers.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="stationSecondHandBehavoir"></a>
|
|
<li><b>stationSecondHandBehavoir</b><br>
|
|
Verhalten des Sekundenzeigers.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="stationStrokeDial"></a>
|
|
<li><b>stationStrokeDial</b><br>
|
|
Auswahl des Ziffernblattes.
|
|
</li>
|
|
<br>
|
|
|
|
</ul>
|
|
<br>
|
|
|
|
<b>Model: Digital</b> <br><br>
|
|
|
|
<ul>
|
|
<a name="digitalBorderDistance"></a>
|
|
<li><b>digitalBorderDistance</b><br>
|
|
Linker und rechter Abstand der digitalen Textanzeige vom Hintergrundrand. <br>
|
|
(default: 8)
|
|
</li>
|
|
<br>
|
|
|
|
<a name="digitalColorBackground"></a>
|
|
<li><b>digitalColorBackground</b><br>
|
|
Digitaluhr Hintergrundfarbe.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="digitalColorDigits"></a>
|
|
<li><b>digitalColorDigits</b><br>
|
|
Farbe der Balkenanzeige in einer Digitaluhr.
|
|
</li>
|
|
<br>
|
|
|
|
<a name="digitalDigitAngle"></a>
|
|
<li><b>digitalDigitAngle </b><br>
|
|
Stellt den Neigungswinkel der dargestellten Zeichen ein. <br>
|
|
(default: 9)
|
|
</li>
|
|
<br>
|
|
|
|
<a name="digitalDigitDistance"></a>
|
|
<li><b>digitalDigitDistance </b><br>
|
|
Stellt den Zeichenabstand ein. <br>
|
|
(default: 2)
|
|
</li>
|
|
<br>
|
|
|
|
<a name="digitalDigitHeight"></a>
|
|
<li><b>digitalDigitHeight </b><br>
|
|
Stellt die Zeichenhöhe ein. <br>
|
|
(default: 20)
|
|
</li>
|
|
<br>
|
|
|
|
<a name="digitalDigitWidth"></a>
|
|
<li><b>digitalDigitWidth </b><br>
|
|
Stellt die Zeichenbreite ein. <br>
|
|
(default: 12)
|
|
</li>
|
|
<br>
|
|
|
|
<a name="digitalDisplayPattern"></a>
|
|
<li><b>digitalDisplayPattern [countdownwatch | staticwatch | stopwatch | text | watch]</b><br>
|
|
Umschaltung der Digitalanzeige zwischen einer Uhr (default), einer Stoppuhr, statischen Zeitanzeige oder Textanzeige.
|
|
Der anzuzeigende Text im Modus Textanzeige kann mit <br>
|
|
<b>set <name> displayText</b>. <br><br>
|
|
|
|
<b>Hinweis:</b> Bei Textanzeige wird empfohlen das Attribut "digitalSegmentType" auf "16" zu stellen. <br><br>
|
|
|
|
<ul>
|
|
<table>
|
|
<colgroup> <col width=5%> <col width=95%> </colgroup>
|
|
<tr><td> <b>countdownwatch </b> </td><td>: CountDown Stoppuhr </td></tr>
|
|
<tr><td> <b>staticwatch</b> </td><td>: statische Zeitanzeige </td></tr>
|
|
<tr><td> <b>stopwatch</b> </td><td>: Stoppuhr </td></tr>
|
|
<tr><td> <b>text</b> </td><td>: Anzeige eines definierbaren Textes </td></tr>
|
|
<tr><td> <b>watch</b> </td><td>: Uhr </td></tr>
|
|
</table>
|
|
</ul>
|
|
<br>
|
|
<br>
|
|
</li>
|
|
|
|
<a name="digitalSegmentDistance"></a>
|
|
<li><b>digitalSegmentDistance </b><br>
|
|
Legt den Abstand zwischen den Segmenten fest. <br>
|
|
(default: 0.5)
|
|
</li>
|
|
<br>
|
|
|
|
<a name="digitalSegmentType"></a>
|
|
<li><b>digitalSegmentType </b><br>
|
|
Schaltet die Segmentanzahl der Digitalanzeige um. <br>
|
|
(default: 7)
|
|
</li>
|
|
<br>
|
|
|
|
<a name="digitalSegmentWidth"></a>
|
|
<li><b>digitalSegmentWidth </b><br>
|
|
Verändert die Breite der einzelnen Segmente. <br>
|
|
(default: 1.5)
|
|
</li>
|
|
<br>
|
|
|
|
<a name="digitalTextDigitNumber"></a>
|
|
<li><b>digitalTextDigitNumber <Anzahl> </b><br>
|
|
Wenn <Anzahl> > 0 wird die Anzahl der Stellen einer Textanzeige (digitalDisplayPattern = text) fest eingestellt.
|
|
Wenn <Anzahl> = 0 oder nicht gesetzt erfolgt die Festlegung automatisch. In diesem Fall erfolgt eine Adaption
|
|
der Zeichengröße an die Anzahl abhängig von der eingestellten Displaygröße (siehe htmlattr). <br>
|
|
(default: 0)
|
|
</li>
|
|
<br>
|
|
|
|
</ul>
|
|
</ul>
|
|
|
|
</ul>
|
|
|
|
=end html_DE
|
|
|
|
=for :application/json;q=META.json 60_Watches.pm
|
|
{
|
|
"abstract": "Clock display in different variants",
|
|
"x_lang": {
|
|
"de": {
|
|
"abstract": "Uhrenanzeige in verschiedenen Varianten"
|
|
}
|
|
},
|
|
"keywords": [
|
|
"Watch",
|
|
"Modern clock",
|
|
"clock",
|
|
"Station clock",
|
|
"Digital display"
|
|
],
|
|
"version": "v1.1.1",
|
|
"release_status": "stable",
|
|
"author": [
|
|
"Heiko Maaz <heiko.maaz@t-online.de>",
|
|
null
|
|
],
|
|
"x_fhem_maintainer": [
|
|
"DS_Starter"
|
|
],
|
|
"x_fhem_maintainer_github": [
|
|
"nasseeder1"
|
|
],
|
|
"prereqs": {
|
|
"runtime": {
|
|
"requires": {
|
|
"FHEM": 5.00918799,
|
|
"perl": 5.014,
|
|
"Time::HiRes": 0,
|
|
"GPUtils": 0
|
|
},
|
|
"recommends": {
|
|
"FHEM::Meta": 0
|
|
},
|
|
"suggests": {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
=end :application/json;q=META.json
|
|
|
|
=cut
|