mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-01-31 06:39:11 +00:00
37_SHC.pm: Updated for current smarthomatic version v0.13.0
git-svn-id: https://svn.fhem.de/fhem/trunk@27810 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
e4ca3bdc3c
commit
efb008f802
@ -2,7 +2,7 @@
|
||||
# This file is part of the smarthomatic module for FHEM.
|
||||
#
|
||||
# Copyright (c) 2014 Stefan Baumann
|
||||
# 2014, 2015, 2019, 2022 Uwe Freese
|
||||
# 2014, 2015, 2019, 2022, 2023 Uwe Freese
|
||||
#
|
||||
# You can find smarthomatic at www.smarthomatic.org.
|
||||
# You can find FHEM at www.fhem.de.
|
||||
@ -28,6 +28,7 @@ use strict;
|
||||
use feature qw(switch);
|
||||
use warnings;
|
||||
use SetExtensions;
|
||||
use Encode qw(encode_utf8 decode_utf8);
|
||||
|
||||
use SHC_parser;
|
||||
|
||||
@ -37,6 +38,7 @@ my %dev_state_icons = (
|
||||
"PowerSwitch" => ".*1\\d{7}:on:off .*0\\d{7}:off:on set.*:light_question:off",
|
||||
"Dimmer" => "on:on off:off set.*:light_question:off",
|
||||
"EnvSensor" => undef,
|
||||
"Controller" => undef,
|
||||
"RGBDimmer" => undef,
|
||||
"SoilMoistureMeter" => ".*H:\\s\\d\\..*:ampel_rot"
|
||||
);
|
||||
@ -45,6 +47,7 @@ my %web_cmds = (
|
||||
"PowerSwitch" => "on:off:toggle:statusRequest",
|
||||
"Dimmer" => "on:off:statusRequest",
|
||||
"EnvSensor" => undef,
|
||||
"Controller" => undef,
|
||||
"RGBDimmer" => undef,
|
||||
"SoilMoistureMeter" => undef
|
||||
);
|
||||
@ -64,6 +67,10 @@ my %dev_state_format = (
|
||||
"port", "Port: ",
|
||||
"ains", "Ain: "
|
||||
],
|
||||
"Controller" => [
|
||||
"color", "Color: ",
|
||||
"brightness", "Brightness: "
|
||||
],
|
||||
"RGBDimmer" => [
|
||||
"color", "Color: ",
|
||||
"brightness", "Brightness: "
|
||||
@ -87,9 +94,19 @@ my %sets = (
|
||||
# Used from SetExtensions.pm
|
||||
"blink on-for-timer on-till off-for-timer off-till intervals",
|
||||
"EnvSensor" => "",
|
||||
"Controller" => "Color " .
|
||||
"ColorAnimation " .
|
||||
"Dimmer.Brightness:slider,0,1,100 " .
|
||||
"Text " .
|
||||
"MenuSelection " .
|
||||
"Backlight " .
|
||||
"Tone " .
|
||||
"Melody",
|
||||
"RGBDimmer" => "Color " .
|
||||
"ColorAnimation " .
|
||||
"Dimmer.Brightness:slider,0,1,100",
|
||||
"Dimmer.Brightness:slider,0,1,100 " .
|
||||
"Tone " .
|
||||
"Melody",
|
||||
"SoilMoistureMeter" => "",
|
||||
"Custom" => "Dimmer.Brightness " .
|
||||
"Dimmer.Animation"
|
||||
@ -101,6 +118,7 @@ my %gets = (
|
||||
"PowerSwitch" => "",
|
||||
"Dimmer" => "",
|
||||
"EnvSensor" => "din:all,1,2,3,4,5,6,7,8 ain:all,1,2,3,4,5 ain_volt:1,2,3,4,5",
|
||||
"Controller" => "",
|
||||
"RGBDimmer" => "",
|
||||
"Custom" => ""
|
||||
);
|
||||
@ -122,7 +140,7 @@ sub SHCdev_Initialize($)
|
||||
." readonly:1"
|
||||
." forceOn:1"
|
||||
." $readingFnAttributes"
|
||||
." devtype:EnvSensor,Dimmer,PowerSwitch,RGBDimmer,SoilMoistureMeter";
|
||||
." devtype:EnvSensor,Dimmer,PowerSwitch,Controller,RGBDimmer,SoilMoistureMeter";
|
||||
}
|
||||
|
||||
#####################################
|
||||
@ -207,7 +225,7 @@ sub SHCdev_Parse($$)
|
||||
return "UNDEFINED SHCdev_$rname SHCdev $raddr";
|
||||
}
|
||||
|
||||
if (($msgtypename ne "Status") && ($msgtypename ne "AckStatus")) {
|
||||
if (($msgtypename ne "Status") && ($msgtypename ne "AckStatus") && ($msgtypename ne "Deliver")) {
|
||||
Log3 $name, 3, "$rname: Ignoring MessageType $msgtypename";
|
||||
return "";
|
||||
}
|
||||
@ -348,6 +366,42 @@ sub SHCdev_Parse($$)
|
||||
}
|
||||
}
|
||||
}
|
||||
} elsif ($msggroupname eq "Controller") {
|
||||
if ($msgname eq "MenuSelection") {
|
||||
my $index;
|
||||
|
||||
for (my $i = 0 ; $i < 16 ; $i = $i + 1) {
|
||||
$index = $parser->getField("Index" , $i);
|
||||
# index 0 indicates the value was not changed / doesn't exist
|
||||
if ($index != 0) {
|
||||
readingsBulkUpdate($rhash, sprintf("index%02d", $i), $index);
|
||||
}
|
||||
}
|
||||
|
||||
# remember delivery (= user selection)
|
||||
if ($msgtypename eq "Deliver") {
|
||||
readingsBulkUpdate($rhash, "menuSelectionDelivery", 1);
|
||||
}
|
||||
}
|
||||
} elsif ($msggroupname eq "Audio") {
|
||||
if ($msgname eq "Tone") {
|
||||
my $tone = $parser->getField("Tone");
|
||||
|
||||
readingsBulkUpdate($rhash, "tone", $tone);
|
||||
} elsif ($msgname eq "Melody") {
|
||||
my $repeat = $parser->getField("Repeat");
|
||||
my $autoreverse = $parser->getField("AutoReverse");
|
||||
readingsBulkUpdate($rhash, "repeat", $repeat);
|
||||
readingsBulkUpdate($rhash, "autoreverse", $autoreverse);
|
||||
for (my $i = 0 ; $i < 25 ; $i = $i + 1) {
|
||||
my $time = $parser->getField("Time" , $i);
|
||||
my $effect = $parser->getField("Effect", $i);
|
||||
my $tone = $parser->getField("Tone", $i);
|
||||
readingsBulkUpdate($rhash, sprintf("time%02d", $i), $time);
|
||||
readingsBulkUpdate($rhash, sprintf("effect%02d", $i), $effect);
|
||||
readingsBulkUpdate($rhash, sprintf("tone%02d", $i), $tone);
|
||||
}
|
||||
}
|
||||
} elsif ($msggroupname eq "Dimmer") {
|
||||
if ($msgname eq "Brightness") {
|
||||
my $brightness = $parser->getField("Brightness");
|
||||
@ -370,6 +424,14 @@ sub SHCdev_Parse($$)
|
||||
readingsBulkUpdate($rhash, "color$i", $color);
|
||||
}
|
||||
}
|
||||
} elsif ($msggroupname eq "Display") {
|
||||
if ($msgname eq "Backlight") {
|
||||
my $mode = $parser->getField("Mode");
|
||||
my $autotimeoutsec = $parser->getField("AutoTimeoutSec");
|
||||
|
||||
readingsBulkUpdate($rhash, "backlightMode", $mode);
|
||||
readingsBulkUpdate($rhash, "backlightAutoTimeSec", $autotimeoutsec);
|
||||
}
|
||||
}
|
||||
|
||||
# If the devtype is defined add, if not already done, the according webCmds and devStateIcons
|
||||
@ -566,7 +628,7 @@ sub SHCdev_Set($@)
|
||||
} else {
|
||||
return SetExtensions($hash, "", $name, @aa);
|
||||
}
|
||||
} elsif ($devtype eq "RGBDimmer") {
|
||||
} elsif (($devtype eq "Controller") || ($devtype eq "RGBDimmer")) {
|
||||
if ($cmd eq 'Color') {
|
||||
#TODO Verify argument values
|
||||
my $color = $arg;
|
||||
@ -578,6 +640,17 @@ sub SHCdev_Set($@)
|
||||
$parser->initPacket("Dimmer", "Color", "SetGet");
|
||||
$parser->setField("Dimmer", "Color", "Color", $color);
|
||||
SHCdev_Send($hash);
|
||||
} elsif ($cmd eq 'Tone') {
|
||||
#TODO Verify argument values
|
||||
my $tone = $arg;
|
||||
|
||||
# DEBUG
|
||||
# Log3 $name, 3, "$name: Tone args: $arg, $arg2, $arg3, $arg4";
|
||||
|
||||
readingsSingleUpdate($hash, "state", "set-tone:$tone", 1);
|
||||
$parser->initPacket("Audio", "Tone", "SetGet");
|
||||
$parser->setField("Audio", "Tone", "Tone", $tone);
|
||||
SHCdev_Send($hash);
|
||||
} elsif ($cmd eq 'ColorAnimation') {
|
||||
#TODO Verify argument values
|
||||
|
||||
@ -609,6 +682,44 @@ sub SHCdev_Set($@)
|
||||
}
|
||||
readingsSingleUpdate($hash, "state", "set-coloranimation", 1);
|
||||
SHCdev_Send($hash);
|
||||
} elsif ($cmd eq 'Melody') {
|
||||
#TODO Verify argument values
|
||||
|
||||
$parser->initPacket("Audio", "Melody", "SetGet");
|
||||
$parser->setField("Audio", "Melody", "Repeat", $arg);
|
||||
$parser->setField("Audio", "Melody", "AutoReverse", $arg2);
|
||||
|
||||
my $curtime = 0;
|
||||
my $cureffect = 0;
|
||||
my $curtone = 0;
|
||||
# Iterate over all given command line parameters and set Time, Effect and Tone
|
||||
# accordingly. Fill the remaining values with zero.
|
||||
for (my $i = 0 ; $i < 25 ; $i = $i + 1) {
|
||||
if (!defined($aa[($i * 3) + 3])) {
|
||||
$curtime = 0;
|
||||
} else {
|
||||
$curtime = $aa[($i * 3) + 3];
|
||||
}
|
||||
if (!defined($aa[($i * 3) + 4])) {
|
||||
$cureffect = 0;
|
||||
} else {
|
||||
$cureffect = $aa[($i * 3) + 4];
|
||||
}
|
||||
if (!defined($aa[($i * 3) + 5])) {
|
||||
$curtone = 0;
|
||||
} else {
|
||||
$curtone = $aa[($i * 3) + 5];
|
||||
}
|
||||
|
||||
# DEBUG
|
||||
# Log3 $name, 3, "$name: Nr: $i Time: $curtime Effect: $cureffect Tone: $curtone";
|
||||
|
||||
$parser->setField("Audio", "Melody", "Time" , $curtime, $i);
|
||||
$parser->setField("Audio", "Melody", "Effect", $cureffect, $i);
|
||||
$parser->setField("Audio", "Melody", "Tone", $curtone, $i);
|
||||
}
|
||||
readingsSingleUpdate($hash, "state", "set-melody", 1);
|
||||
SHCdev_Send($hash);
|
||||
} elsif ($cmd eq 'Dimmer.Brightness') {
|
||||
my $brightness = $arg;
|
||||
|
||||
@ -619,6 +730,37 @@ sub SHCdev_Set($@)
|
||||
$parser->initPacket("Dimmer", "Brightness", "SetGet");
|
||||
$parser->setField("Dimmer", "Brightness", "Brightness", $brightness);
|
||||
SHCdev_Send($hash);
|
||||
} elsif ($cmd eq 'Text') {
|
||||
$parser->initPacket("Display", "Text", "Set");
|
||||
$parser->setField("Display", "Text", "PosY", $arg);
|
||||
$parser->setField("Display", "Text", "PosX", $arg2);
|
||||
$parser->setField("Display", "Text", "Format", $arg3);
|
||||
$arg4 = decode_utf8($arg4);
|
||||
$arg4 =~ s/(?<!\\)_/ /g; # replace non-escaped '_' with space
|
||||
$arg4 =~ s/\\_/_/g; # replace escape character from escaped '_'
|
||||
$arg4 =~ s/(?<!\\)\\1/\x01/g; # replace \1 with character 1 (user character)
|
||||
$arg4 =~ s/(?<!\\)\\2/\x02/g; # replace \2 with character 2 (user character)
|
||||
$arg4 =~ s/(?<!\\)\\3/\x03/g; # replace \3 with character 3 (user character)
|
||||
$arg4 =~ s/(?<!\\)\\4/\x04/g; # replace \4 with character 4 (user character)
|
||||
$arg4 =~ s/(?<!\\)\\5/\x05/g; # replace \5 with character 5 (user character)
|
||||
$arg4 =~ s/(?<!\\)\\6/\x06/g; # replace \6 with character 6 (user character)
|
||||
$arg4 =~ s/(?<!\\)\\7/\x07/g; # replace \7 with character 7 (user character)
|
||||
$arg4 =~ s/(?<!\\)\\8/\x08/g; # replace \8 with character 8 (user character)
|
||||
$parser->setField("Display", "Text", "Text", $arg4);
|
||||
SHCdev_Send($hash);
|
||||
} elsif ($cmd eq 'Backlight') {
|
||||
$parser->initPacket("Display", "Backlight", "SetGet");
|
||||
$parser->setField("Display", "Backlight", "Mode", $arg);
|
||||
$parser->setField("Display", "Backlight", "AutoTimeoutSec", $arg2);
|
||||
SHCdev_Send($hash);
|
||||
} elsif ($cmd eq 'MenuSelection') {
|
||||
$parser->initPacket("Controller", "MenuSelection", "SetGet");
|
||||
for (my $i = 0 ; $i < 16 ; $i = $i + 1) {
|
||||
if (defined($aa[$i + 1])) {
|
||||
$parser->setField("Controller", "MenuSelection", "Index", $aa[$i + 1], $i);
|
||||
}
|
||||
}
|
||||
SHCdev_Send($hash);
|
||||
} else {
|
||||
return SetExtensions($hash, "", $name, @aa);
|
||||
}
|
||||
@ -740,6 +882,7 @@ sub SHCdev_Send($)
|
||||
<li>EnvSensor</li>
|
||||
<li>PowerSwitch</li>
|
||||
<li>Dimmer</li>
|
||||
<li>Controller</li>
|
||||
<li>RGBDimmer</li>
|
||||
<li>SoilMoistureMeter</li>
|
||||
</ul><br>
|
||||
@ -774,35 +917,58 @@ sub SHCdev_Send($)
|
||||
Sets the brightness in percent. Supported by Dimmer.
|
||||
</li><br>
|
||||
<li>ani <AnimationMode> <TimeoutSec> <StartBrightness> <EndBrightness><br>
|
||||
Description and details available at <a href="http://www.smarthomatic.org/basics/message_catalog.html#Dimmer_Animation">www.smarthomatic.org</a>
|
||||
Description and details available at <a href="http://www.smarthomatic.org/basics/message_catalog.html#Dimmer_Animation">www.smarthomatic.org</a>.
|
||||
Supported by Dimmer.
|
||||
</li><br>
|
||||
<li>statusRequest<br>
|
||||
Supported by Dimmer and PowerSwitch.
|
||||
</li><br>
|
||||
<li>Color <ColorNumber><br>
|
||||
A detailed description is available at <a href="http://www.smarthomatic.org/basics/message_catalog.html#Dimmer_Color">www.smarthomatic.org</a>
|
||||
A detailed description is available at <a href="http://www.smarthomatic.org/basics/message_catalog.html#Dimmer_Color">www.smarthomatic.org</a>.
|
||||
The color palette can be found <a href="http://www.smarthomatic.org/devices/rgb_dimmer.html">here</a>
|
||||
Supported by RGBDimmer.
|
||||
</li><br>
|
||||
<li>ColorAnimation <Repeat> <AutoReverse> <Time0> <ColorNumber0> <Time1> <ColorNumber1> ... up to 10 time/color pairs<br>
|
||||
A detailed description is available at <a href="http://www.smarthomatic.org/basics/message_catalog.html#Dimmer_ColorAnimation">www.smarthomatic.org</a>
|
||||
A detailed description is available at <a href="http://www.smarthomatic.org/basics/message_catalog.html#Dimmer_ColorAnimation">www.smarthomatic.org</a>.
|
||||
The color palette can be found <a href="http://www.smarthomatic.org/devices/rgb_dimmer.html">here</a>
|
||||
Supported by RGBDimmer.
|
||||
</li><br>
|
||||
<li>Tone <ToneNumber><br>
|
||||
A detailed description is available at <a href="http://www.smarthomatic.org/basics/message_catalog.html#Audio_Tone">www.smarthomatic.org</a>.
|
||||
The tone definition can be found <a href="http://www.smarthomatic.org/devices/rgb_dimmer.html">here</a>
|
||||
Supported by RGBDimmer.
|
||||
</li><br>
|
||||
<li>Melody <Repeat> <AutoReverse> <Time0> <Effect0> <ToneNumber0> <Time1> <Effect1> <ToneNumber1> ... up to 25 time/effect/tone pairs<br>
|
||||
A detailed description is available at <a href="http://www.smarthomatic.org/basics/message_catalog.html#Audio_Melody">www.smarthomatic.org</a>.
|
||||
The tone definition can be found <a href="http://www.smarthomatic.org/devices/rgb_dimmer.html">here</a>
|
||||
Supported by RGBDimmer.
|
||||
</li><br>
|
||||
<li>Text <PosY> <PosX> <Format> <Text><br>
|
||||
A detailed description is available at <a href="http://www.smarthomatic.org/basics/message_catalog.html#Display_Text">www.smarthomatic.org</a>. Supported by Controller.<br>
|
||||
<b>Note:</b> Since FHEM parameters can't include spaces, there is a special form to enter them.
|
||||
To add a space to the text, use the underline character (e.g. 'Hello_world').
|
||||
If you want to send an underline character, escape it with the backslash (e.g. '\_test\_').
|
||||
</li><br>
|
||||
<li>MenuSelection <Index00> <Index01> ... <Index15><br>
|
||||
A detailed description is available at <a href="http://www.smarthomatic.org/basics/message_catalog.html#Controller_MenuSelection">www.smarthomatic.org</a>. Supported by Controller.<br>
|
||||
When the MenuSelection was initiated by the user with the controller, the reading <b>menuSelectionDelivery</b> will be set additionally to the index00, ... readings. This can be used to distinguish if the menu selection was user initiated or done programmatically by a FHEM "set" command, especially to keep more than one controller with the same options in sync reacting on a change of the menuSelectionDelivery reading.
|
||||
</li><br>
|
||||
<li>Backlight <Mode> <AutoTimeoutSec><br>
|
||||
A detailed description is available at <a href="http://www.smarthomatic.org/basics/message_catalog.html#Display_Backlight">www.smarthomatic.org</a>. Supported by Controller.
|
||||
</li><br>
|
||||
<li>DigitalPin <Pos> <On><br>
|
||||
A detailed description is available at <a href="http://www.smarthomatic.org/basics/message_catalog.html#GPIO_DigitalPin">www.smarthomatic.org</a>
|
||||
A detailed description is available at <a href="http://www.smarthomatic.org/basics/message_catalog.html#GPIO_DigitalPin">www.smarthomatic.org</a>.
|
||||
Supported by PowerSwitch.
|
||||
</li><br>
|
||||
<li>DigitalPinTimeout <Pos> <On> <Timeout><br>
|
||||
A detailed description is available at <a href="http://www.smarthomatic.org/basics/message_catalog.html#GPIO_DigitalPinTimeout">www.smarthomatic.org</a>
|
||||
A detailed description is available at <a href="http://www.smarthomatic.org/basics/message_catalog.html#GPIO_DigitalPinTimeout">www.smarthomatic.org</a>.
|
||||
Supported by PowerSwitch.
|
||||
</li><br>
|
||||
<li>DigitalPort <On><br>
|
||||
<On><br>
|
||||
is a bit array (0 or 1) describing the port state. If less than eight bits were provided zero is assumed.
|
||||
Example: set SHC_device DigitalPort 10110000 will set pin0, pin2 and pin3 to 1.<br>
|
||||
A detailed description is available at <a href="http://www.smarthomatic.org/basics/message_catalog.html#GPIO_DigitalPort">www.smarthomatic.org</a>
|
||||
A detailed description is available at <a href="http://www.smarthomatic.org/basics/message_catalog.html#GPIO_DigitalPort">www.smarthomatic.org</a>.
|
||||
Supported by PowerSwitch.
|
||||
</li><br>
|
||||
<li>DigitalPortTimeout <On> <Timeout0> .. <Timeout7><br>
|
||||
@ -811,7 +977,7 @@ sub SHCdev_Send($)
|
||||
Example: set SHC_device DigitalPort 10110000 will set pin0, pin2 and pin3 to 1.<br>
|
||||
<Timeout0> .. <Timeout7><br>
|
||||
are the timeouts for each pin. If no timeout is provided zero is assumed.
|
||||
A detailed description is available at <a href="http://www.smarthomatic.org/basics/message_catalog.html#GPIO_DigitalPortTimeout">www.smarthomatic.org</a>
|
||||
A detailed description is available at <a href="http://www.smarthomatic.org/basics/message_catalog.html#GPIO_DigitalPortTimeout">www.smarthomatic.org</a>.
|
||||
Supported by PowerSwitch.
|
||||
</li><br>
|
||||
<li><a href="#setExtensions"> set extensions</a><br>
|
||||
|
@ -126,7 +126,7 @@ sub setUInt($$$$)
|
||||
my $len = min($length_bits, 8 - $bit);
|
||||
my $val8 = get_bits($value, $src_start, $len);
|
||||
|
||||
# DEBUG print " Write value " . $val8 . " (" . $len . " bits) to byte " . $byte . ", dst_start " . $dst_start . "\r\n";
|
||||
# DEBUG print " Write value " . $val8 . " (" . $len . " bits) to byte " . $byte . ", start bit " . $dst_start . "\r\n";
|
||||
|
||||
setUIntBits($byteArrayRef, $byte, $dst_start, $len, $val8);
|
||||
|
||||
@ -138,7 +138,7 @@ sub setUInt($$$$)
|
||||
$val8 = get_bits($value, $src_start, $len);
|
||||
$byte++;
|
||||
|
||||
# DEBUG print " Write value " . $val8 . " (" . $len . " bits) from src_start " . $src_start . " to byte " . $byte . ", dst_start " . $dst_start . "\r\n";
|
||||
# DEBUG print " Write value " . $val8 . " (" . $len . " bits) from src_start " . $src_start . " to byte " . $byte . ", start bit " . $dst_start . "\r\n";
|
||||
|
||||
setUIntBits($byteArrayRef, $byte, $dst_start, $len, $val8);
|
||||
|
||||
@ -355,4 +355,68 @@ sub setValue
|
||||
SHC_util::setUInt($byteArrayRef, $self->{_offset} + $self->{_arrayElementBits} * $index, $self->{_bits}, $value);
|
||||
}
|
||||
|
||||
# ----------- ByteArray class -----------
|
||||
|
||||
package ByteArray;
|
||||
|
||||
sub new
|
||||
{
|
||||
my $class = shift;
|
||||
my $self = {
|
||||
_id => shift,
|
||||
_offset => shift,
|
||||
_bytes => shift,
|
||||
_length => shift,
|
||||
_arrayElementBits => shift
|
||||
};
|
||||
bless $self, $class;
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub getValue
|
||||
{
|
||||
my ($self, $byteArrayRef, $index) = @_;
|
||||
|
||||
my $res = "";
|
||||
my ($i, $c);
|
||||
|
||||
for ($i = 0; $i < $self->{_bytes}; $i++)
|
||||
{
|
||||
$c = SHC_util::getUInt($byteArrayRef, $self->{_offset} + $self->{_arrayElementBits} * $index + $i * 8, 8);
|
||||
|
||||
if ($c == 0)
|
||||
{
|
||||
last;
|
||||
}
|
||||
|
||||
$res .= Chr($c);
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
sub setValue
|
||||
{
|
||||
my ($self, $byteArrayRef, $value, $index) = @_;
|
||||
|
||||
my @chars = split("", $value);
|
||||
|
||||
# Set a maximum of _bytes bytes in the data array.
|
||||
my $i;
|
||||
my $strlen = SHC_util::min(length($value), $self->{_bytes});
|
||||
|
||||
for ($i = 0; $i < $strlen; $i++)
|
||||
{
|
||||
#print "set bit " . ($self->{_offset} + $self->{_arrayElementBits} * $index + $i * 8) . " char " . $i . " = " . $chars[$i] . " = ord " . ord($chars[$i]) . "\n";
|
||||
|
||||
SHC_util::setUInt($byteArrayRef, $self->{_offset} + $self->{_arrayElementBits} * $index + $i * 8, 8, ord($chars[$i]));
|
||||
}
|
||||
|
||||
# Fill up the rest of the bytes with 0.
|
||||
for ($i = $strlen; $i < $self->{_bytes}; $i++)
|
||||
{
|
||||
SHC_util::setUInt($byteArrayRef, $self->{_offset} + $self->{_arrayElementBits} * $index + $i * 8, 8, 0);
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
@ -160,6 +160,16 @@ sub init_datafield_positions_noarray($$$$$)
|
||||
}
|
||||
|
||||
$offset += $bits;
|
||||
} elsif ($field->nodeName eq "ByteArray") {
|
||||
my $id = ($field->findnodes("ID"))[0]->textContent;
|
||||
my $bytes = ($field->findnodes("Bytes"))[0]->textContent;
|
||||
|
||||
# print "Data field " . $id . " starts at " . $offset . " with " . $bytes . " bytes.\n";
|
||||
|
||||
$dataFields{$messageGroupID . "-" . $messageID . "-" . $id} =
|
||||
new ByteArray($id, $offset, $bytes, $arrayLength, $arrayElementBits);
|
||||
|
||||
$offset += $bytes * 8;
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,7 +184,7 @@ sub init_datafield_positions_array($$$)
|
||||
calc_array_bits_ovr($field); # number of bits for one struct ("set of sub-elements") in a structured array
|
||||
# print "Next field is an array with " . $arrayLength . " elements (" . $arrayElementBits . " ovr bits per array element)!\n";
|
||||
|
||||
for my $subfield ($field->findnodes("UIntValue|IntValue|FloatValue|BoolValue|EnumValue")) {
|
||||
for my $subfield ($field->findnodes("UIntValue|IntValue|FloatValue|BoolValue|EnumValue|ByteArray")) {
|
||||
my $bits =
|
||||
init_datafield_positions_noarray($messageGroupID, $messageID, $subfield, $arrayLength, $arrayElementBits);
|
||||
}
|
||||
@ -200,6 +210,10 @@ sub calc_array_bits_ovr($)
|
||||
$bits += 32;
|
||||
}
|
||||
|
||||
for my $subfield ($field->findnodes("ByteArray")) {
|
||||
$bits += ($subfield->findnodes("Bytes"))[0]->textContent * 8;
|
||||
}
|
||||
|
||||
return $bits;
|
||||
}
|
||||
|
||||
@ -234,7 +248,7 @@ sub init_datafield_positions()
|
||||
|
||||
$offset = 0;
|
||||
|
||||
for my $field ($message->findnodes("Array|UIntValue|IntValue|FloatValue|BoolValue|EnumValue")) {
|
||||
for my $field ($message->findnodes("Array|UIntValue|IntValue|FloatValue|BoolValue|EnumValue|ByteArray")) {
|
||||
|
||||
# When an array is detected, remember the array length and change the current field node
|
||||
# to the inner node for further processing.
|
||||
@ -258,12 +272,16 @@ sub parse
|
||||
|
||||
$sendMode = 0;
|
||||
|
||||
# PKT:SID=56;PC=1816;MT=3;RID=0;MGID=45;MID=1;MD=010105;efc28d5e
|
||||
if (
|
||||
(
|
||||
$msg =~
|
||||
/^PKT:SID=(\d+);PC=(\d+);MT=(\d+);MGID=(\d+);MID=(\d+);MD=([^;]+);.*/
|
||||
)
|
||||
|| ($msg =~
|
||||
/^PKT:SID=(\d+);PC=(\d+);MT=(3);RID=0;MGID=(\d+);MID=(\d+);MD=([^;]+);.*/
|
||||
)
|
||||
|| ($msg =~
|
||||
/^PKT:SID=(\d+);PC=(\d+);MT=(\d+);ASID=\d+;APC=\d+;E=\d+;MGID=(\d+);MID=(\d+);MD=([^;]+);.*/
|
||||
)
|
||||
)
|
||||
|
@ -38,6 +38,10 @@
|
||||
<Value>2</Value>
|
||||
<Name>SetGet</Name>
|
||||
</Element>
|
||||
<Element>
|
||||
<Value>3</Value>
|
||||
<Name>Deliver</Name>
|
||||
</Element>
|
||||
<Element>
|
||||
<Value>8</Value>
|
||||
<Name>Status</Name>
|
||||
@ -80,6 +84,7 @@
|
||||
<HeaderExtension>
|
||||
<MessageType>1</MessageType>
|
||||
<MessageType>2</MessageType>
|
||||
<MessageType>3</MessageType>
|
||||
<UIntValue>
|
||||
<ID>ReceiverID</ID>
|
||||
<Description>The ID of the device to process the request. Use 4095 for broadcasts.</Description>
|
||||
@ -183,43 +188,6 @@
|
||||
<Name>Generic</Name>
|
||||
<Description>This group contains messages useful for different devices.</Description>
|
||||
<MessageGroupID>0</MessageGroupID>
|
||||
<Message>
|
||||
<Name>Version</Name>
|
||||
<Description>Reports the current firmware version. Version information is only available when set in source code, which is usually only done for official builds by the build robot.</Description>
|
||||
<MessageID>1</MessageID>
|
||||
<MessageType>0</MessageType>
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>deprecated</Validity>
|
||||
<UIntValue>
|
||||
<ID>Major</ID>
|
||||
<Description>Different major version means incompatible changes.</Description>
|
||||
<Bits>8</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>255</MaxVal>
|
||||
</UIntValue>
|
||||
<UIntValue>
|
||||
<ID>Minor</ID>
|
||||
<Description>Different minor number means new functionality without breaking compatibility.</Description>
|
||||
<Bits>8</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>255</MaxVal>
|
||||
</UIntValue>
|
||||
<UIntValue>
|
||||
<ID>Patch</ID>
|
||||
<Description>The patch version is changed when backwards-compatible bug fixes are made.</Description>
|
||||
<Bits>8</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>255</MaxVal>
|
||||
</UIntValue>
|
||||
<UIntValue>
|
||||
<ID>Hash</ID>
|
||||
<Description>The beginning of the revision ID hash (as reported by Git).</Description>
|
||||
<Bits>32</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>4294967295</MaxVal>
|
||||
</UIntValue>
|
||||
</Message>
|
||||
<Message>
|
||||
<Name>DeviceInfo</Name>
|
||||
<Description>Reports DeviceType and current firmware version. Version information is only available when set in source code, which is usually only done for official builds by the build robot.</Description>
|
||||
@ -227,7 +195,7 @@
|
||||
<MessageType>0</MessageType>
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<Validity>released</Validity>
|
||||
<EnumValue>
|
||||
<ID>DeviceType</ID>
|
||||
<Description>The DeviceType can be used to adapt the behavior or representation of the SHC device at the server software (e.g. FHEM).</Description>
|
||||
@ -244,6 +212,10 @@
|
||||
<Value>40</Value>
|
||||
<Name>PowerSwitch</Name>
|
||||
</Element>
|
||||
<Element>
|
||||
<Value>45</Value>
|
||||
<Name>Controller</Name>
|
||||
</Element>
|
||||
<Element>
|
||||
<Value>50</Value>
|
||||
<Name>RGBDimmer</Name>
|
||||
@ -329,7 +301,7 @@
|
||||
<MessageType>0</MessageType>
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<Validity>released</Validity>
|
||||
<UIntValue>
|
||||
<ID>Percentage</ID>
|
||||
<Description>The remaining capacity of the battery from 0 (empty) to 100 (full).</Description>
|
||||
@ -353,7 +325,7 @@
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>9</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<Validity>released</Validity>
|
||||
<Array>
|
||||
<Length>8</Length>
|
||||
<BoolValue>
|
||||
@ -372,7 +344,7 @@
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>9</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<Validity>released</Validity>
|
||||
<Array>
|
||||
<Length>8</Length>
|
||||
<BoolValue>
|
||||
@ -398,7 +370,7 @@
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>9</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<Validity>released</Validity>
|
||||
<UIntValue>
|
||||
<ID>Pos</ID>
|
||||
<Description>The number of the pin in the port.</Description>
|
||||
@ -421,7 +393,7 @@
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>9</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<Validity>released</Validity>
|
||||
<UIntValue>
|
||||
<ID>Pos</ID>
|
||||
<Description>The number of the pin in the port.</Description>
|
||||
@ -479,7 +451,7 @@
|
||||
<MessageType>0</MessageType>
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<Validity>released</Validity>
|
||||
<IntValue>
|
||||
<ID>Temperature</ID>
|
||||
<Description>temperature [1/100 degree celsius], -50°C = -5000, 50°C = 5000</Description>
|
||||
@ -495,7 +467,7 @@
|
||||
<MessageType>0</MessageType>
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<Validity>released</Validity>
|
||||
<UIntValue>
|
||||
<ID>Humidity</ID>
|
||||
<Description>relative humidity permill, 0..1000 (other values not defined)</Description>
|
||||
@ -518,7 +490,7 @@
|
||||
<MessageType>0</MessageType>
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<Validity>released</Validity>
|
||||
<UIntValue>
|
||||
<ID>BarometricPressure</ID>
|
||||
<Description>barometric pressure in pascal</Description>
|
||||
@ -541,7 +513,7 @@
|
||||
<MessageType>0</MessageType>
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<Validity>released</Validity>
|
||||
<UIntValue>
|
||||
<ID>Humidity</ID>
|
||||
<Description>relative humidity permill, 0..1000 (other values not defined)</Description>
|
||||
@ -562,7 +534,7 @@
|
||||
<MessageType>0</MessageType>
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<Validity>released</Validity>
|
||||
<UIntValue>
|
||||
<ID>Brightness</ID>
|
||||
<Description>brightness in percent</Description>
|
||||
@ -578,7 +550,7 @@
|
||||
<MessageType>0</MessageType>
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<Validity>released</Validity>
|
||||
<UIntValue>
|
||||
<ID>Distance</ID>
|
||||
<Description>distance in cm</Description>
|
||||
@ -630,12 +602,12 @@
|
||||
</Message>
|
||||
</MessageGroup>
|
||||
<MessageGroup>
|
||||
<Name>PowerSwitch</Name>
|
||||
<Description/>
|
||||
<MessageGroupID>20</MessageGroupID>
|
||||
<Name>Display</Name>
|
||||
<Description>This message group contains messages for displays.</Description>
|
||||
<MessageGroupID>40</MessageGroupID>
|
||||
<Message>
|
||||
<Name>SwitchState</Name>
|
||||
<Description>This is the state of the relais and its timeout value.</Description>
|
||||
<Name>Text</Name>
|
||||
<Description>This is a message to get/set text content at a specified position.</Description>
|
||||
<MessageID>1</MessageID>
|
||||
<MessageType>0</MessageType>
|
||||
<MessageType>1</MessageType>
|
||||
@ -643,22 +615,38 @@
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>9</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>deprecated</Validity>
|
||||
<BoolValue>
|
||||
<ID>On</ID>
|
||||
<Description>Tells if the switch is on (active).</Description>
|
||||
</BoolValue>
|
||||
<Validity>test</Validity>
|
||||
<UIntValue>
|
||||
<ID>TimeoutSec</ID>
|
||||
<Description>The time after which the switch is automatically toggled again. Use 0 to disable this.</Description>
|
||||
<Bits>16</Bits>
|
||||
<ID>PosY</ID>
|
||||
<Description>Y (line) position at which the text shall be displayed. Line numbers above 3 are for the corresponding virtual lines / pages.</Description>
|
||||
<Bits>5</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>65535</MaxVal>
|
||||
<MaxVal>31</MaxVal>
|
||||
</UIntValue>
|
||||
<UIntValue>
|
||||
<ID>PosX</ID>
|
||||
<Description>X (character) position at which the text shall be displayed.</Description>
|
||||
<Bits>7</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>79</MaxVal>
|
||||
</UIntValue>
|
||||
<UIntValue>
|
||||
<ID>Format</ID>
|
||||
<Description>Format, font, or other value which modifies how the text is displayed. Depends on the implementation of the device.</Description>
|
||||
<Bits>4</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>15</MaxVal>
|
||||
<DefaultVal>0</DefaultVal>
|
||||
</UIntValue>
|
||||
<ByteArray>
|
||||
<ID>Text</ID>
|
||||
<Description>40 bytes for the text that shall be displayed. The end of the text is marked with a 0 byte if it's shorter than 40 characters.</Description>
|
||||
<Bytes>40</Bytes>
|
||||
</ByteArray>
|
||||
</Message>
|
||||
<Message>
|
||||
<Name>SwitchStateExt</Name>
|
||||
<Description>This is the state of up to 8 relais and its timeout values.</Description>
|
||||
<Name>Backlight</Name>
|
||||
<Description>This is a message to get/set the backlight mode of a display.</Description>
|
||||
<MessageID>2</MessageID>
|
||||
<MessageType>0</MessageType>
|
||||
<MessageType>1</MessageType>
|
||||
@ -666,22 +654,130 @@
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>9</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>deprecated</Validity>
|
||||
<Validity>test</Validity>
|
||||
<EnumValue>
|
||||
<ID>Mode</ID>
|
||||
<Description>The backlight mode defines when the backlight is switched on. The setting 'Auto' means the backlight is switched on automatically after user interaction and off after a timeout.</Description>
|
||||
<Bits>4</Bits>
|
||||
<Element>
|
||||
<Value>0</Value>
|
||||
<Name>On</Name>
|
||||
</Element>
|
||||
<Element>
|
||||
<Value>1</Value>
|
||||
<Name>Off</Name>
|
||||
</Element>
|
||||
<Element>
|
||||
<Value>2</Value>
|
||||
<Name>Auto</Name>
|
||||
</Element>
|
||||
</EnumValue>
|
||||
<UIntValue>
|
||||
<ID>AutoTimeoutSec</ID>
|
||||
<Description>This is the timeout in seconds after which the backlight is switched off in mode 'Auto'. The value 0 shall be treated as not to change the value when sending a request to the device.</Description>
|
||||
<Bits>8</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>255</MaxVal>
|
||||
</UIntValue>
|
||||
</Message>
|
||||
</MessageGroup>
|
||||
<MessageGroup>
|
||||
<Name>Controller</Name>
|
||||
<Description>This message group contains messages for controller / HMI devices.</Description>
|
||||
<MessageGroupID>45</MessageGroupID>
|
||||
<Message>
|
||||
<Name>MenuSelection</Name>
|
||||
<Description>These are the value indexes of selected menu items. It depends on the configuration of a specific controller device which value each index represents. The indexes of selected menu entries should start with 1, and 0 should be treated as 'not updated' (in a Status/AckStatus/Deliver message) or 'not to be updated' (in a Set/SetGet request). This is to allow smaller (16 byte) packets when there are few menu entries.</Description>
|
||||
<MessageID>1</MessageID>
|
||||
<MessageType>0</MessageType>
|
||||
<MessageType>1</MessageType>
|
||||
<MessageType>2</MessageType>
|
||||
<MessageType>3</MessageType>
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>9</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<Array>
|
||||
<Length>8</Length>
|
||||
<BoolValue>
|
||||
<ID>On</ID>
|
||||
<Description/>
|
||||
</BoolValue>
|
||||
</Array>
|
||||
<Array>
|
||||
<Length>8</Length>
|
||||
<Length>16</Length>
|
||||
<UIntValue>
|
||||
<ID>TimeoutSec</ID>
|
||||
<Description>The time after which the switch is automatically toggled again. Use 0 to disable this.</Description>
|
||||
<Bits>16</Bits>
|
||||
<ID>Index</ID>
|
||||
<Description>The index of the selected value of a menu entry.</Description>
|
||||
<Bits>8</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>65535</MaxVal>
|
||||
<MaxVal>255</MaxVal>
|
||||
<DefaultVal>0</DefaultVal>
|
||||
</UIntValue>
|
||||
</Array>
|
||||
</Message>
|
||||
</MessageGroup>
|
||||
<MessageGroup>
|
||||
<Name>Audio</Name>
|
||||
<Description>This message group contains messages for audio input/output.</Description>
|
||||
<MessageGroupID>50</MessageGroupID>
|
||||
<Message>
|
||||
<Name>Tone</Name>
|
||||
<Description>This is a message to get/set playback of a continuous tone which doesn't stop until another one is requested.</Description>
|
||||
<MessageID>1</MessageID>
|
||||
<MessageType>0</MessageType>
|
||||
<MessageType>1</MessageType>
|
||||
<MessageType>2</MessageType>
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>9</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<UIntValue>
|
||||
<ID>Tone</ID>
|
||||
<Description>Tone according frequency table. 0 means OFF.</Description>
|
||||
<Bits>7</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>116</MaxVal>
|
||||
</UIntValue>
|
||||
</Message>
|
||||
<Message>
|
||||
<Name>Melody</Name>
|
||||
<Description>This is a message to play a series of tones (set) or get the currently playing one.</Description>
|
||||
<MessageID>2</MessageID>
|
||||
<MessageType>0</MessageType>
|
||||
<MessageType>1</MessageType>
|
||||
<MessageType>2</MessageType>
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>9</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<UIntValue>
|
||||
<ID>Repeat</ID>
|
||||
<Description>The number of times the melody will be repeated. 0 means infinitely.</Description>
|
||||
<Bits>4</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>15</MaxVal>
|
||||
</UIntValue>
|
||||
<BoolValue>
|
||||
<ID>AutoReverse</ID>
|
||||
<Description>If true, the melody will be played back in the normal direction and then in reverse order.</Description>
|
||||
</BoolValue>
|
||||
<Array>
|
||||
<Length>25</Length>
|
||||
<UIntValue>
|
||||
<ID>Time</ID>
|
||||
<Description>The playback time between the previous tone and the new one. The number of seconds used is 0.05 * 1.3 ^ Time and covers the range from 0.03s to 170s. Use 0 to mark the end of the melody. Further values will be ignored.</Description>
|
||||
<Bits>5</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>31</MaxVal>
|
||||
</UIntValue>
|
||||
<UIntValue>
|
||||
<ID>Effect</ID>
|
||||
<Description>Define how the tone is played. 0: Tone is played immediately (default). 1: A sliding tone from the previous to the new one. A sliding tone from or to tone index 0 (off) is not possible. The new tone / no tone will be played back immediately in this case. Other values are free for future use.</Description>
|
||||
<Bits>3</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>1</MaxVal>
|
||||
<DefaultVal>0</DefaultVal>
|
||||
</UIntValue>
|
||||
<UIntValue>
|
||||
<ID>Tone</ID>
|
||||
<Description>Index according frequency table. 0 means OFF. The last index (or the first when AutoReverse is true) of the melody will remain audible after the melody is completed.</Description>
|
||||
<Bits>7</Bits>
|
||||
<MinVal>0</MinVal>
|
||||
<MaxVal>116</MaxVal>
|
||||
</UIntValue>
|
||||
</Array>
|
||||
</Message>
|
||||
@ -700,7 +796,7 @@
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>9</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<Validity>released</Validity>
|
||||
<UIntValue>
|
||||
<ID>Brightness</ID>
|
||||
<Description>The brightness in percent. 0 = Off.</Description>
|
||||
@ -719,7 +815,7 @@
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>9</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<Validity>released</Validity>
|
||||
<EnumValue>
|
||||
<ID>AnimationMode</ID>
|
||||
<Description>If a time is set, use this animation mode to change the brightness over time (none = leave at start state for the whole time and switch to end state at the end).</Description>
|
||||
@ -765,7 +861,7 @@
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>9</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<Validity>released</Validity>
|
||||
<UIntValue>
|
||||
<ID>Color</ID>
|
||||
<Description>The color is according to the 6 bit color palette used in SHC.</Description>
|
||||
@ -784,7 +880,7 @@
|
||||
<MessageType>8</MessageType>
|
||||
<MessageType>9</MessageType>
|
||||
<MessageType>10</MessageType>
|
||||
<Validity>test</Validity>
|
||||
<Validity>released</Validity>
|
||||
<UIntValue>
|
||||
<ID>Repeat</ID>
|
||||
<Description>The number of times the animation will be repeated. 0 means infinitely.</Description>
|
||||
|
Loading…
Reference in New Issue
Block a user