/
44_ROLLO.pm
1132 lines (993 loc) · 50.8 KB
/
44_ROLLO.pm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
########################################################################################
# $Id$ #
# Modul zur einfacheren Rolladensteuerung #
# #
# Thomas Ramm, 2016 #
# Tim Horenkamp, 2018 #
# Markus Moises, 2016 #
# Mirko Lindner, 2018 #
# KernSani, 2017 #
# #
########################################################################################
#
# This programm is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# The GNU General Public License can be found at
# http://www.gnu.org/copyleft/gpl.html.
# A copy is found in the textfile GPL.txt and important notices to the license
# from the author is found in LICENSE.txt distributed with these scripts.
#
# This script is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# CHANGELOG:
# 1.405: Fixed an issue with external driving (when already at position)
# 1.404: Hint in Commandref regarding position->pct
# 1.403: Loglevel from 3 to 5 for few messages
# Rollo should only drive 10 steps in "force" mode for up/down
########################################################################################
package main;
use strict;
use warnings;
my $version = "1.403";
my %sets = (
"open" => "noArg",
"closed" => "noArg",
"up" => "noArg",
"down" => "noArg",
"half" => "noArg",
"stop" => "noArg",
"blocked" => "noArg",
"unblocked" => "noArg",
"pct" => "0,10,20,30,40,50,60,70,80,90,100",
"reset" => "open,closed",
"extern" => "open,closed,stop",
"drive" => "textField"
);
my %pcts = (
"open" => 0,
"closed" => 100,
"half" => 50
);
my %gets = ( "version:noArg" => "V" );
############################################################ INITIALIZE #####
sub ROLLO_Initialize($) {
my ($hash) = @_;
my $name = $hash->{NAME};
$hash->{DefFn} = "ROLLO_Define";
$hash->{UndefFn} = "ROLLO_Undef";
$hash->{SetFn} = "ROLLO_Set";
$hash->{GetFn} = "ROLLO_Get";
$hash->{AttrFn} = "ROLLO_Attr";
$hash->{AttrList} =
" rl_secondsDown"
. " rl_secondsUp"
. " rl_excessTop"
. " rl_excessBottom"
. " rl_switchTime"
. " rl_resetTime"
. " rl_reactionTime"
. " rl_blockMode:blocked,force-open,force-closed,only-up,only-down,half-up,half-down,none"
. " rl_commandUp rl_commandUp2 rl_commandUp3"
. " rl_commandDown rl_commandDown2 rl_commandDown3"
. " rl_commandStop rl_commandStopDown rl_commandStopUp"
. " automatic-enabled:on,off"
. " automatic-delay"
. " rl_autoStop:1,0"
. " rl_type:normal,HomeKit"
. " disable:0,1"
. " rl_forceDrive:0,1"
. " rl_noSetPosBlocked:0,1" . " "
. $readingFnAttributes;
$hash->{stoptime} = 0;
#map new Attribute names
$hash->{AttrRenameMap} = {
"secondsDown" => "rl_secondsDown",
"secondsUp" => "rl_secondsUp",
"excessTop" => "rl_excessTop",
"excessBottom" => "rl_excessBottom",
"switchTime" => "rl_switchTime",
"resetTime" => "rl_resetTime",
"reactionTime" => "rl_resetTime",
"blockMode" => "rl_blockMode",
"commandUp" => "rl_commandUp",
"commandUp2" => "rl_commandUp2",
"commandUp3" => "rl_commandUp3",
"commandDown" => "rl_commandDown",
"commandDown2" => "rl_commandDown2",
"commandDown3" => "rl_commandDown3",
"commandStop" => "rl_commandStop",
"commandStopUp" => "rl_commandStopUp",
"commandStopDown" => "rl_commandStopDown",
"autoStop" => "rl_autoStop",
"type" => "rl_type",
"forceDrive" => "rl_forceDrive",
"noSetPosBlocked" => "rl_noSetPosBlocked"
};
return undef;
}
################################################################ DEFINE #####
sub ROLLO_Define($$) {
my ( $hash, $def ) = @_;
my $name = $hash->{NAME};
Log3 $name, 5, "ROLLO ($name) >> Define";
my @a = split( "[ \t][ \t]*", $def );
# no direct access to %attr - KernSani 13.01.2019
CommandAttr( undef, $name . " rl_secondsDown 30" )
if ( AttrVal( $name, "rl_secondsDown", "" ) eq "" );
#$attr{$name}{"rl_secondsDown"} = 30;
CommandAttr( undef, $name . " rl_secondsUp 30" )
if ( AttrVal( $name, "rl_secondsUp", "" ) eq "" );
#$attr{$name}{"rl_secondsUp"} = 30;
CommandAttr( undef, $name . " rl_excessTop 4" )
if ( AttrVal( $name, "rl_excessTop", "" ) eq "" );
#$attr{$name}{"rl_excessTop"} = 4;
CommandAttr( undef, $name . " rl_excessBottom 2" )
if ( AttrVal( $name, "rl_excessBottom", "" ) eq "" );
#$attr{$name}{"rl_excessBottom"} = 2;
CommandAttr( undef, $name . " rl_switchTime 1" )
if ( AttrVal( $name, "rl_switchTime", "" ) eq "" );
#$attr{$name}{"rl_switchTime"} = 1;
CommandAttr( undef, $name . " rl_resetTime 0" )
if ( AttrVal( $name, "rl_switchTime", "" ) eq "" );
#$attr{$name}{"rl_resetTime"} = 0;
CommandAttr( undef, $name . " rl_autoStop 0" )
if ( AttrVal( $name, "rl_autoStop", "" ) eq "" );
#fix devstateicon - KernSani 13.01.2019
my $devStateIcon =
'open:fts_shutter_10:closed closed:fts_shutter_100:open half:fts_shutter_50:closed drive-up:fts_shutter_up@red:stop drive-down:fts_shutter_down@red:stop pct-100:fts_shutter_100:open pct-90:fts_shutter_80:closed pct-80:fts_shutter_80:closed pct-70:fts_shutter_70:closed pct-60:fts_shutter_60:closed pct-50:fts_shutter_50:closed pct-40:fts_shutter_40:open pct-30:fts_shutter_30:open pct-20:fts_shutter_20:open pct-10:fts_shutter_10:open pct-0:fts_shutter_10:closed';
CommandAttr( undef, $name . " devStateIcon $devStateIcon" )
if ( AttrVal( $name, "devStateIcon", "" ) eq "" );
CommandAttr( undef, $name . " rl_type normal" )
if ( AttrVal( $name, "rl_type", "" ) eq "" );
#$attr{$name}{"rl_type"} = "normal"; #neue Attribute sollten als default keine Änderung an der Funktionsweise bewirken.
CommandAttr( undef, $name . " webCmd open:closed:half:stop:pct" )
if ( AttrVal( $name, "webCmd", "" ) eq "" );
#cmdIcon aded - KernSani 13.01.2019
CommandAttr( undef,
$name . " cmdIcon open:fts_shutter_up closed:fts_shutter_down stop:fts_shutter_manual half:fts_shutter_50" )
if ( AttrVal( $name, "cmdIcon", "" ) eq "" );
#$attr{$name}{"webCmd"} = "open:closed:half:stop:pct";
# $attr{$name}{"blockMode"} = "none";
if ( IsDisabled($name) ) {
readingsSingleUpdate( $hash, "state", "inactive", 1 );
$hash->{helper}{DISABLED} = 1;
}
return undef;
}
################################################################# UNDEF #####
sub ROLLO_Undef($) {
my ($hash) = @_;
my $name = $hash->{NAME};
RemoveInternalTimer($hash);
return undef;
}
#################################################################### SET #####
sub ROLLO_Set($@) {
my ( $hash, @a ) = @_;
my $name = $hash->{NAME};
return undef if IsDisabled($name);
#Warum steht das hier? Verschoben in Define - KernSani 13.01.2019
#$attr{$name}{"webCmd"} = "open:closed:half:stop:pct";
if ( ReadingsVal( $name, "position", "exists" ) ne "exists" ) {
#Log3 $name,1, "ROLLO ($name) Readings position and desired_position aren't used anymore. Execute \"deletereading $name position\" and \"deletereading $name desired_position\" to remove them";
}
#allgemeine Fehler in der Parameterübergabe abfangen
if ( @a < 2 ) {
Log3 $name, 2, "ERROR: \"set ROLLO\" needs at least one argument";
return "\"ROLLO_Set\" needs at least one argument";
}
my $cmd = $a[1];
# Keep command "position" for a while
if ( $cmd eq "position" ) {
$cmd = "pct";
Log3 $name, 1,
"ROLLO ($name) Set command \"position\" is deprecated. Please change your definitions to \"pct\"";
}
my $desiredPos;
my $arg = "";
$arg = $a[2] if defined $a[2];
my $arg2 = "";
$arg2 = $a[3] if defined $a[3];
Log3 $name, 5, "ROLLO ($name) >> Set ($cmd,$arg)" if ( $cmd ne "?" );
my @pctsets = ( "0", "10", "20", "30", "40", "50", "60", "70", "80", "90", "100" );
if ( !defined( $sets{$cmd} ) && $cmd !~ @pctsets ) {
my $param = "";
foreach my $val ( keys %sets ) {
$param .= " $val:$sets{$val}";
}
Log3 $name, 2, "ERROR: Unknown command $cmd, choose one of $param" if ( $cmd ne "?" );
return "Unknown argument $cmd, choose one of $param";
}
#### Stop if not driving - do we need that?
if ( ( $cmd eq "stop" ) && ( ReadingsVal( $name, "state", '' ) !~ /drive/ ) ) {
Log3 $name, 3, "WARNING: command is stop but shutter is not driving!";
RemoveInternalTimer($hash);
ROLLO_Stop($hash);
return undef;
}
##### Commands without IO
if ( $cmd eq "extern" ) {
readingsSingleUpdate( $hash, "drive-type", "extern", 1 );
$cmd = $arg;
$arg = "";
}
elsif ( $cmd eq "reset" ) {
my $reset_pct = $pcts{$arg};
$reset_pct = 100 - $reset_pct if ( AttrVal( $name, "rl_type", "normal" ) eq "HomeKit" );
readingsBeginUpdate($hash);
readingsBulkUpdate( $hash, "state", $arg );
readingsBulkUpdate( $hash, "desired_pct", $reset_pct );
readingsBulkUpdate( $hash, "pct", $reset_pct );
readingsEndUpdate( $hash, 1 );
return undef;
}
##### Block commands
if ( $cmd eq "blocked" ) {
ROLLO_Stop($hash);
readingsSingleUpdate( $hash, "blocked", "1", 1 );
return if ( AttrVal( $name, "rl_blockMode", "none" ) eq "blocked" );
}
elsif ( $cmd eq "unblocked" ) {
# Wenn blocked=1 wird in Rollo_Stop der state auf "blocked" gesetzt
# daher erst blocked auf 0 (Stop ist m.E. an dieser Stelle eigentlich nicht notwendig)
#ROLLO_Stop($hash); #delete KernSani
readingsSingleUpdate( $hash, "blocked", "0", 1 );
ROLLO_Stop($hash); #add KernSani
ROLLO_Start($hash);
#avoid the deletereading mesage in Log - KernSani 30.12.2018
#fhem("deletereading $name blocked");
#readingsDelete( $hash, "blocked" );
CommandDeleteReading( undef, "$name blocked" );
return;
}
##### Drive for Seconds - Basic Implementation (doesn't consider block mode, Homekit, ...)
if ( $cmd eq "drive" ) {
return "Drive needs two arguments, the direction and the time in seconds" if ( !$arg2 );
my $direction = $arg;
$arg = undef;
my $time = $arg2;
$hash->{driveTime} = $time;
$hash->{driveDir} = $direction;
my $dpct = ROLLO_calculateDesiredPosition( $hash, $name );
readingsSingleUpdate( $hash, "desired_pct", $dpct, 1 );
Log3 $name, 3, "ROLLO ($name) DRIVE Command drive $direction for $time seconds. ";
ROLLO_Stop($hash);
ROLLO_Drive( $hash, $time, $direction, $cmd );
return undef;
}
##### now do the real drive stuff
$desiredPos = $cmd;
Log3 $name, 5, "ROLLO ($name) DesiredPos set to $desiredPos, ($arg) ";
my $typ = AttrVal( $name, "rl_type", "normal" );
# Allow all positions
#if ( grep /^$arg$/, @pctsets )
#Log3 $name, 5, "ROLLO ($name) Arg is $arg";
#change sequence to avoid "is not numeric" warning
#if ($arg && $arg =~ /^[0-9,.E]*$/ && $arg >= 0 && $arg <= 100 )
if ( $arg ne "" && $arg >= 0 && $arg <= 100 ) {
if ( $cmd eq "pct" ) {
if ( $typ eq "HomeKit" ) {
Log3 $name, 4, "ROLLO ($name) invert pct from $arg to (100-$arg)";
$arg = 100 - $arg;
}
$cmd = "pct-" . $arg;
$desiredPos = $arg;
Log3 $name, 5, "ROLLO ($name) DesiredPos now $desiredPos, $arg";
}
else { #I think this shouldn't happen...
if ( $typ eq "HomeKit" ) {
$cmd = 100 - $cmd;
}
$cmd = "pct-" . $cmd;
$desiredPos = $cmd;
Log3 $name, 5, "ROLLO ($name) There is an arg $arg, but command is $cmd";
}
}
else {
if ( $cmd eq "down" || $cmd eq "up" ) {
# Recalculate the desired pct
my $posin = ReadingsVal( $name, "pct", 0 );
$posin = 100 - $posin if ( $typ eq "HomeKit" );
$desiredPos = int( ( $posin - 10 ) / 10 + 0.5 ) * 10;
$desiredPos = int( ( $posin + 10 ) / 10 + 0.5 ) * 10 if $cmd eq "down";
$desiredPos = 100 if $desiredPos > 100;
$desiredPos = 0 if $desiredPos < 0;
}
else {
$desiredPos = $pcts{$cmd};
}
# Ich verstehe nicht wann nachfolgender Zustand eintreten kann, das Coding führt aber dazu, dass pct 0 (open) auf "none" gesetzt wird
#$desiredPos = "none" if !$desiredPos || $desiredPos eq "";
}
#set desiredPos to avoid "uninitialized" message later (happens with "blocked" - KernSani 14.01.2019
$desiredPos = ReadingsNum( $name, "desired_pct", 0 ) unless defined($desiredPos);
Log3 $name, 5, "ROLLO ($name) DesiredPos now $desiredPos, $cmd";
#wenn ich gerade am fahren bin und eine neue Zielposition angefahren werden soll,
# muss ich jetzt erst mal meine aktuelle Position berechnen und updaten
# bevor ich die desired-position überschreibe!
if ( ( ReadingsVal( $name, "state", "" ) =~ /drive-/ ) ) {
my $pct = ROLLO_calculatepct( $hash, $name );
readingsSingleUpdate( $hash, "pct", $pct, 1 );
# Desired-position sollte auf aktuelle position gesetzt werden, wenn explizit gestoppt wird.
readingsSingleUpdate( $hash, "desired_pct", $pct, 1 )
if ( ( $cmd eq "stop" || $cmd eq "blocked" ) && $pct > 0 && $pct < 100 );
}
readingsBeginUpdate($hash);
readingsBulkUpdate( $hash, "command", $cmd );
# desired position sollte nicht gesetzt werden, wenn ein unerlaubter Befehl (wenn Rollladen geblockt ist)
# gesendet wird. Sonst rennt er direkt nach dem "unblock" los
# readingsBulkUpdate($hash,"desired_position",$desiredPos) if($cmd ne "blocked") && ($cmd ne "stop")
readingsBulkUpdate( $hash, "desired_pct", $desiredPos )
if ( $cmd ne "blocked" ) && ( $cmd ne "stop" ) && ROLLO_isAllowed( $hash, $cmd, $desiredPos );
readingsEndUpdate( $hash, 1 );
ROLLO_Start($hash);
return undef;
}
#################################################################### isAllowed #####
sub ROLLO_isAllowed($$$) {
my ( $hash, $cmd, $desired_pct ) = @_;
my $name = $hash->{NAME};
if ( ReadingsVal( $name, "blocked", "0" ) ne "1" or AttrVal( $name, "rl_noSetPosBlocked", 0 ) == 0 ) {
return 1;
}
my $pct = ReadingsVal( $name, "pct", undef );
$pct = 100 - $pct if ( AttrVal( $name, "rl_type", "normal" ) eq "HomeKit" ); # KernSani 30.12.2018
my $blockmode = AttrVal( $name, "rl_blockMode", "none" );
Log3 $name, 5, "ROLLO ($name) >> Blockmode:$blockmode $pct-->$desired_pct";
if ( $blockmode eq "blocked"
|| ( $blockmode eq "only-up" && $pct <= $desired_pct )
|| ( $blockmode eq "only-down" && $pct >= $desired_pct ) )
{
return undef;
}
return 1;
}
#****************************************************************************
sub ROLLO_Drive {
my ( $hash, $time, $direction, $command ) = @_;
my $name = $hash->{NAME};
my ( $command1, $command2, $command3 );
if ( $direction eq "down" ) {
$command1 = AttrVal( $name, 'rl_commandDown', "" );
$command2 = AttrVal( $name, 'rl_commandDown2', "" );
$command3 = AttrVal( $name, 'rl_commandDown3', "" );
}
else {
$command1 = AttrVal( $name, 'rl_commandUp', "" );
$command2 = AttrVal( $name, 'rl_commandUp2', "" );
$command3 = AttrVal( $name, 'rl_commandUp3', "" );
}
$command = "drive-" . $direction;
readingsBeginUpdate($hash);
readingsBulkUpdate( $hash, "last_drive", $command );
readingsBulkUpdate( $hash, "state", $command );
readingsEndUpdate( $hash, 1 );
#***** ROLLO NICHT LOSFAHREN WENN SCHON EXTERN GESTARTET *****#
if ( ReadingsVal( $name, "drive-type", "undef" ) ne "extern" ) {
Log3 $name, 4, "ROLLO ($name) execute following commands: $command1; $command2; $command3";
readingsSingleUpdate( $hash, "drive-type", "modul", 1 );
#no fhem() - KernSani 13.01.2019
my $ret = AnalyzeCommandChain( undef, "$command1" ) if ( $command1 ne "" );
Log3 $name, 1, "ROLLO ($name) $ret" if ( defined($ret) );
AnalyzeCommandChain( undef, "$command2" ) if ( $command2 ne "" );
AnalyzeCommandChain( undef, "$command3" ) if ( $command3 ne "" );
}
else {
#readingsSingleUpdate($hash,"drive-type","extern",1);
readingsSingleUpdate( $hash, "drive-type", "na", 1 );
Log3 $name, 4, "ROLLO ($name) drive-type is extern, not executing driving commands";
}
$hash->{stoptime} = int( gettimeofday() + $time );
InternalTimer( $hash->{stoptime}, "ROLLO_Timer", $hash, 1 );
Log3 $name, 4, "ROLLO ($name) stop in $time seconds.";
}
#################################################################### START #####
sub ROLLO_Start($) {
my ($hash) = @_;
my $name = $hash->{NAME};
Log3 $name, 5, "ROLLO ($name) >> Start";
my $command = ReadingsVal( $name, "command", "stop" );
my $desired_pct = ReadingsVal( $name, "desired_pct", 100 );
my $pct = ReadingsVal( $name, "pct", 0 );
$pct = 100 - $pct if ( AttrVal( $name, "rl_type", "normal" ) eq "HomeKit" );
my $state = ReadingsVal( $name, "state", "open" );
Log3 $name, 4, "ROLLO ($name) drive from $pct to $desired_pct. command: $command. state: $state";
if ( ReadingsVal( $name, "blocked", "0" ) eq "1" && $command ne "stop" ) {
my $blockmode = AttrVal( $name, "rl_blockMode", "none" );
Log3 $name, 4, "ROLLO ($name) block mode: $blockmode - $pct to $desired_pct?";
if ( $blockmode eq "blocked" ) {
readingsSingleUpdate( $hash, "state", "blocked", 1 );
return;
}
elsif ( $blockmode eq "force-open" ) {
$desired_pct = 0;
}
elsif ( $blockmode eq "force-closed" ) {
$desired_pct = 100;
}
elsif ( $blockmode eq "only-up" && $pct <= $desired_pct ) {
readingsSingleUpdate( $hash, "state", "blocked", 1 );
return;
}
elsif ( $blockmode eq "only-down" && $pct >= $desired_pct ) {
readingsSingleUpdate( $hash, "state", "blocked", 1 );
return;
}
elsif ( $blockmode eq "half-up" && $desired_pct < 50 ) {
$desired_pct = 50;
}
elsif ( $blockmode eq "half-up" && $desired_pct == 50 ) {
readingsSingleUpdate( $hash, "state", "blocked", 1 );
return;
}
elsif ( $blockmode eq "half-down" && $desired_pct > 50 ) {
$desired_pct = 50;
}
elsif ( $blockmode eq "half-down" && $desired_pct == 50 ) {
readingsSingleUpdate( $hash, "state", "blocked", 1 );
return;
}
#desired_pct has to be updated - KernSani 30.12.2018
readingsSingleUpdate( $hash, "desired_pct", $desired_pct, 1 );
}
my $direction = "down";
$direction = "up" if ( $pct > $desired_pct || $desired_pct == 0 );
#if ( $hash->{driveDir} ) { $direction = $hash->{driveDir} }
Log3 $name, 4, "ROLLO ($name) pct: $pct -> $desired_pct / direction: $direction";
#Ich fahre ja gerade...wo bin ich aktuell?
if ( $state =~ /drive-/ ) {
#$pct = ROLLO_calculatepct($hash,$name); #das muss weg.. verschoben in set!
if ( $command eq "stop" ) {
ROLLO_Stop($hash);
return;
}
#$direction = "down";
#$direction = "up" if ($pct > $desired_pct || $desired_pct == 0);
if ( ( ( $state eq "drive-down" ) && ( $direction eq "up" ) )
|| ( ( $state eq "drive-up" ) && ( $direction eq "down" ) ) )
{
Log3 $name, 3, "driving into wrong direction. stop and change driving direction";
ROLLO_Stop($hash);
InternalTimer( int( gettimeofday() ) + AttrVal( $name, 'rl_switchTime', 0 ), "ROLLO_Start", $hash, 0 );
return;
}
}
my $time = 0;
RemoveInternalTimer($hash);
$time = ROLLO_calculateDriveTime( $name, $pct, $desired_pct, $direction );
if ( $time > 0 ) {
ROLLO_Drive( $hash, $time, $direction, $command );
}
# Wenn drivetype "extern" müssen wir drive_type wieder zurücksetzen - KernSani 27.01.2019
elsif ( ReadingsVal( $name, "drive-type", "undef" ) eq "extern" ) {
readingsSingleUpdate( $hash, "drive-type", "na", 1 );
}
return undef;
}
#****************************************************************************
sub ROLLO_Timer($) {
my ($hash) = @_;
my $name = $hash->{NAME};
Log3 $name, 5, "ROLLO ($name) >> Timer";
my $pct = ReadingsVal( $name, "desired_pct", 0 );
$pct = 100 - $pct if ( AttrVal( $name, "rl_type", "normal" ) eq "HomeKit" );
readingsSingleUpdate( $hash, "pct", $pct, 1 );
ROLLO_Stop($hash);
return undef;
}
#****************************************************************************
sub ROLLO_Stop($) {
my ($hash) = @_;
my $name = $hash->{NAME};
#my $command = ReadingsVal($name,"command","stop");
Log3 $name, 5, "ROLLO ($name) >> Stop";
RemoveInternalTimer($hash);
my $pct = ReadingsVal( $name, "pct", 0 );
$pct = 100 - $pct if ( AttrVal( $name, "rl_type", "normal" ) eq "HomeKit" );
my $state = ReadingsVal( $name, "state", "" );
Log3 $name, 4, "ROLLO ($name) stops from $state at pct $pct";
#wenn autostop=1 und pct <> 0+100 und rollo fährt, dann kein stopbefehl ausführen...
if ( ( $state =~ /drive-/ && $pct > 0 && $pct < 100 ) || AttrVal( $name, "rl_autoStop", 0 ) ne 1 ) {
my $command = AttrVal( $name, 'rl_commandStop', "" );
$command = AttrVal( $name, 'rl_commandStopUp', "" ) if ( AttrVal( $name, 'rl_commandStopUp', "" ) ne "" );
$command = AttrVal( $name, 'rl_commandStopDown', "" )
if ( AttrVal( $name, 'rl_commandStopDown', "" ) ne "" && $state eq "drive-down" );
# NUR WENN NICHT BEREITS EXTERN GESTOPPT
if ( ReadingsVal( $name, "drive-type", "undef" ) ne "extern" ) {
AnalyzeCommandChain( undef, "$command" ) if ( $command ne "" );
Log3 $name, 4, "ROLLO ($name) stopped by excuting the command: $command";
}
else {
readingsSingleUpdate( $hash, "drive-type", "na", 1 );
Log3 $name, 4, "ROLLO ($name) is in drive-type extern";
}
}
else {
Log3 $name, 4, "ROLLO ($name) drives to end pct and autostop is enabled. No stop command executed";
}
if ( ReadingsVal( $name, "blocked", "0" ) eq "1" && AttrVal( $name, "rl_blockMode", "none" ) ne "none" ) {
readingsSingleUpdate( $hash, "state", "blocked", 1 );
}
else {
#Runden der pct auf volle 10%-Schritte für das Icon
my $newpos = int( $pct / 10 + 0.5 ) * 10;
$newpos = 0 if ( $newpos < 0 );
$newpos = 100 if ( $newpos > 100 );
my $state;
#pct in text umwandeln
my %rhash = reverse %pcts;
if ( defined( $rhash{$newpos} ) ) {
$state = $rhash{$newpos};
}
else {
#ich kenne keinen Text für die pct, also als pct-nn anzeigen
$newpos = 100 - $newpos if ( AttrVal( $name, "rl_type", "normal" ) eq "HomeKit" );
$state = "pct-$newpos";
}
Log3 $name, 4, "ROLLO ($name) updating state to $state";
readingsSingleUpdate( $hash, "state", $state, 1 );
}
return undef;
}
#****************************************************************************
sub ROLLO_calculatepct(@) {
my ( $hash, $name ) = @_;
my ($pct);
Log3 $name, 5, "ROLLO ($name) >> calculatepct";
my $type = AttrVal( $name, "rl_type", "normal" );
my $start = ReadingsVal( $name, "pct", 100 );
$start = 100 - $start if $type eq "HomeKit";
my $end = ReadingsVal( $name, "desired_pct", 0 );
my $drivetime_rest = int( $hash->{stoptime} - gettimeofday() ); #die noch zu fahrenden Sekunden
my $drivetime_total =
( $start < $end ) ? AttrVal( $name, 'rl_secondsDown', undef ) : AttrVal( $name, 'rl_secondsUp', undef );
# bsp: die fahrzeit von 0->100 ist 26sec. ich habe noch 6sec. zu fahren...was bedeutet das?
# excessTop = 5sec
# driveTimeDown=20sec -> hier wird von 0->100 gezählt, also pro sekunde 5 Schritte
# excessBottom = 1sec
# aktuelle pct = 6sec-1sec=5sec pctsfahrzeit=25steps=pct75
#Frage1: habe ich noch "tote" Sekunden vor mir wegen endpct?
my $resetTime = AttrVal( $name, 'rl_resetTime', 0 );
$drivetime_rest -= ( AttrVal( $name, 'rl_excessTop', 0 ) + $resetTime ) if ( $end == 0 );
$drivetime_rest -= ( AttrVal( $name, 'rl_excessBottom', 0 ) + $resetTime ) if ( $end == 100 );
#wenn ich schon in der nachlaufzeit war, setze ich die pct auf 99, dann kann man nochmal für die nachlaufzeit starten
if ( $start == $end ) {
$pct = $end;
}
elsif ( $drivetime_rest < 0 ) {
$pct = ( $start < $end ) ? 99 : 1;
}
else {
$pct = $drivetime_rest / $drivetime_total * 100;
$pct = ( $start < $end ) ? $end - $pct : $end + $pct;
$pct = 0 if ( $pct < 0 );
$pct = 100 if ( $pct > 100 );
}
#aktuelle pct aktualisieren und zurückgeben
Log3 $name, 4, "ROLLO ($name) calculated pct is $pct; rest drivetime is $drivetime_rest";
my $savepos = $pct;
$savepos = 100 - $pct if $type eq "HomeKit";
readingsSingleUpdate( $hash, "pct", $savepos, 100 );
return $pct;
}
#****************************************************************************
sub ROLLO_calculateDesiredPosition(@) {
my ( $hash, $name ) = @_;
my ($pct);
Log3 $name, 5, "ROLLO ($name) >> calculateDesiredPosition";
my $start = 0;
my $dtime = $hash->{driveTime};
my $direction = $hash->{driveDir};
my $typ = AttrVal( $name, "rl_type", "normal" );
Log3 $name, 4, "ROLLO ($name) drive $direction for $dtime";
my ( $time, $steps );
if ( $direction eq "up" ) {
$time = AttrVal( $name, 'rl_secondsUp', undef );
$start = 100;
}
else {
$time = AttrVal( $name, 'rl_secondsDown', undef );
$start = 0;
}
my $startPos = ReadingsVal( $name, "pct", 100 );
$startPos = 100 - $startPos if ( $typ eq "HomeKit" );
$time += AttrVal( $name, 'rl_reactionTime', 0 );
#$time += AttrVal($name,'excessTop',0) if($startPos == 0);
#$time += AttrVal($name,'excessBottom',0) if($startPos == 100);
#$time += AttrVal($name,'resetTime', 0) if($startPos == 0 or $startPos == 100);
$steps = $dtime / $time * 100;
Log3 $name, 4, "ROLLO ($name) total time = $time, we're intending to drive $steps steps";
if ( $direction eq "up" ) {
$pct = $startPos - $steps;
}
else {
$pct = $startPos + $steps;
}
$pct = 100 if ( $pct > 100 );
$pct = 0 if ( $pct < 0 );
Log3 $name, 4, "ROLLO ($name) Target pct is $pct";
return int($pct);
}
#****************************************************************************
sub ROLLO_calculateDriveTime(@) {
my ( $name, $oldpos, $newpos, $direction ) = @_;
Log3 $name, 5, "ROLLO ($name) >> calculateDriveTime | going $direction: from $oldpos to $newpos";
my ( $time, $steps );
if ( $direction eq "up" ) {
$time = AttrVal( $name, 'rl_secondsUp', undef );
$steps = $oldpos - $newpos;
}
else {
$time = AttrVal( $name, 'rl_secondsDown', undef );
$steps = $newpos - $oldpos;
}
if ( $steps == 0 ) {
Log3 $name, 4, "ROLLO ($name) already at position!";
# Wenn force-Drive gesetzt ist fahren wir immer 100% (wenn "open" oder "closed")
if ( AttrVal( $name, "rl_forceDrive", 0 ) == 1 && ( $oldpos == 0 || $oldpos == 100 ) ) {
Log3 $name, 4, "ROLLO ($name): forceDrive set, driving $direction";
my $cmd = ReadingsVal( $name, "command", "stop" );
if ( $cmd eq "up" or $cmd eq "down" ) {
$steps = 10;
}
else {
$steps = 100;
}
}
}
if ( !defined($time) ) {
Log3 $name, 2, "ROLLO ($name) ERROR: missing attribute secondsUp or secondsDown";
$time = 60;
}
my $drivetime = $time * $steps / 100;
Log3 $name, 5, "ROLLO ($name) netto drive time = $drivetime";
# reactionTime etc... sollten nur hinzugefügt werden, wenn auch gefahren wird...
if ( $drivetime > 0 ) {
$drivetime += AttrVal( $name, 'rl_reactionTime', 0 ) if ( $time > 0 && $steps > 0 );
$drivetime += AttrVal( $name, 'rl_excessTop', 0 ) if ( $oldpos == 0 or $newpos == 0 );
$drivetime += AttrVal( $name, 'rl_excessBottom', 0 ) if ( $oldpos == 100 or $newpos == 100 );
$drivetime += AttrVal( $name, 'rl_resetTime', 0 ) if ( $newpos == 0 or $newpos == 100 );
Log3 $name, 4,
"ROLLO ($name) calculateDriveTime: oldpos=$oldpos,newpos=$newpos,direction=$direction,time=$time,steps=$steps,drivetime=$drivetime";
}
return $drivetime;
}
################################################################### GET #####
sub ROLLO_Get($@) {
my ( $hash, @a ) = @_;
my $name = $hash->{NAME};
Log3 $name, 5, "ROLLO ($name) >> Get";
#-- get version
if ( $a[1] eq "version" ) {
return "$name.version => $version";
}
if ( @a < 2 ) {
Log3 $name, 2, "ROLLO ($name) ERROR: \"get ROLLO\" needs at least one argument";
return "\"get ROLLO\" needs at least one argument";
}
my $cmd = $a[1];
if ( !$gets{$cmd} ) {
my @cList = keys %gets;
Log3 $name, 3, "ERROR: Unknown argument $cmd, choose one of " . join( " ", @cList ) if ( $cmd ne "?" );
return "Unknown argument $cmd, choose one of " . join( " ", @cList );
}
my $val = "";
$val = $a[2] if ( @a > 2 );
Log3 $name, 4, "ROLLO ($name) command: $cmd, value: $val";
}
################################################################## ATTR #####
sub ROLLO_Attr(@) {
my ( $cmd, $name, $aName, $aVal ) = @_;
Log3 $name, 5, "ROLLO ($name) >> Attr";
my $hash = $defs{$name};
if ( $cmd eq "set" ) {
if ( $aName eq "Regex" ) {
eval { qr/$aVal/ };
if ($@) {
Log3 $name, 2, "ROLLO ($name):ERROR Invalid regex in attr $name $aName $aVal: $@";
return "Invalid Regex $aVal";
}
}
#Auswertung von HomeKit und dem Logo
if ( $aName eq "rl_type" ) {
#auslesen des aktuellen Icon, wenn es nicht gesetzt ist, oder dem default entspricht, dann neue Zuweisung vornehmen
my $iconNormal =
'open:fts_shutter_10:closed closed:fts_shutter_100:open half:fts_shutter_50:closed drive-up:fts_shutter_up@red:stop drive-down:fts_shutter_down@red:stop pct-100:fts_shutter_100:open pct-90:fts_shutter_80:closed pct-80:fts_shutter_80:closed pct-70:fts_shutter_70:closed pct-60:fts_shutter_60:closed pct-50:fts_shutter_50:closed pct-40:fts_shutter_40:open pct-30:fts_shutter_30:open pct-20:fts_shutter_20:open pct-10:fts_shutter_10:open pct-0:fts_shutter_10:closed';
my $iconHomeKit =
'open:fts_shutter_10:closed closed:fts_shutter_100:open half:fts_shutter_50:closed drive-up:fts_shutter_up@red:stop drive-down:fts_shutter_down@red:stop pct-100:fts_shutter_10:open pct-90:fts_shutter_10:closed pct-80:fts_shutter_20:closed pct-70:fts_shutter_30:closed pct-60:fts_shutter_40:closed pct-50:fts_shutter_50:closed pct-40:fts_shutter_60:open pct-30:fts_shutter_70:open pct-20:fts_shutter_80:open pct-10:fts_shutter_90:open pct-0:fts_shutter_100:closed';
my $iconAktuell = AttrVal( $name, "devStateIcon", "kein" );
CommandAttr( undef, " $name devStateIcon $iconHomeKit" )
if ( ( $aVal eq "HomeKit" ) && ( ( $iconAktuell eq $iconNormal ) || ( $iconAktuell eq "kein" ) ) );
CommandAttr( undef, " $name devStateIcon $iconNormal" )
if ( ( $aVal eq "normal" ) && ( ( $iconAktuell eq $iconHomeKit ) || ( $iconAktuell eq "kein" ) ) );
}
elsif ( $aName eq "disable" ) {
if ( $aVal == 1 ) {
RemoveInternalTimer($hash);
readingsSingleUpdate( $hash, "state", "inactive", 1 );
$hash->{helper}{DISABLED} = 1;
}
elsif ( $aVal == 0 ) {
readingsSingleUpdate( $hash, "state", "Initialized", 1 );
$hash->{helper}{DISABLED} = 0;
}
}
}
elsif ( $cmd eq "del" ) {
if ( $aName eq "disable" ) {
readingsSingleUpdate( $hash, "state", "Initialized", 1 );
$hash->{helper}{DISABLED} = 0;
}
}
return undef;
}
1;
=pod
=item helper
=item summary Precisely control shutters/blinds which support only open/close/stop
=item summary_DE Rollladen die nur open/close/stop unterstützen päzise steuern
=begin html
<a name="ROLLO"></a>
<h3>ROLLO</h3>
<div>
<ul>The module ROLLO offers an easy away to control shutters with one or two relays and to stop them exactly. <br>
The current position (in %) will be displayed in fhem. It doesn't matter which hardware is used to control the shutters as long as they are working with FHEM. <br />
<h4>Note</h4>
If you had installed ROLLO before it became part of FHEM and you didn't update it for a long time, you might miss the "position" readings and the corresponding set command. "position" was replaced with "pct" to ensure compatibility with other modules (like <a href="https://fhem.de/commandref.html#AutoShuttersControl">Automatic shutter control - ASC</a>). Please adjust your notifies/DOIFs accordingly.
<h4>Example</h4>
<code>define TestRollo ROLLO</code>
</ul>
<a name="ROLLO_Define"></a>
<h4>Define</h4>
<ul>
<code>define <Rollo-Device> ROLLO</code>
<br /><br /> Define a ROLLO instance.<br />
</ul>
<a name="ROLLO_Set"></a>
<h4>Set</h4>
<ul>
<li><a name="open">open</a>
<code>set <Rollo-Device> open</code><br />
opens the shutter (pct 0) </li>
<li><a name="closed">closed</a>
<code>set <Rollo-Device> closed</code><br />
close the shutter (pct 100) </li>
<li><a name="up">up</a>
<code>set <Rollo-Device> up</code><br />
opens the shutter one step (pct +10) </li>
<li><a name="down">down</a>
<code>set <Rollo-Device> down</code><br />
close the shutter one step (pct -10) </li>
<li><a name="half">half</a>
<code>set <Rollo-Device> half</code><br />
drive the shutter to half open (pct 50) </li>
<li><a name="stop">stop</a>
<code>set <Rollo-Device> stop</code><br />
stop a driving shutter</li>
<li><a name="drive">drive</a>
<code>set <Rollo-Device> drive up 5</code><br />
Drives the shutter in the specified direction for the specified time (in seconds)</li>
<li><a name="blocked">blocked</a>
<code>set <Rollo-Device> blocked</code><br />
when activated, the shutter can move only restricted. See attribute block_mode for further details.</li>
<li><a name="unblocked">unblocked</a>
<code>set <Rollo-Device> unblocked</code><br />
unblock the shutter, so you can drive the shutter</li>
<li><a name="pct">pct</a>
<code>set <Rollo-Device> pct <value></code><br />
drive the shutter to exact pct from 0 (open) to 100 (closed) </li>
<li><a name="reset">reset</a>
<code>set <Rollo-Device> reset <value></code><br />
set the modul to real pct if the shutter pct changed outside from fhem</li>
<li><a name="extern">extern</a>
<code>set <Rollo-Device> extern <value></code><br />
if the shutter is started/stopped externaly, you can inform the module so it can calculate the current pct</li>
</ul>
<a name="ROLLO_Get"></a>
<h4>Get</h4>
<ul>
<li><a name="version">version</a>
<code>get <Rollo-Device> version</code>
<br /> Returns the version number of the FHEM ROLLO module</li>
</ul>
<a name="ROLLO_Attr"></a>
<h4>Attributes</h4>
<ul>
<li><a name="rl_type">rl_type</a> <code>attr <Rollo-Device> rl_type [normal|HomeKit]</code>
Type differentiation to support different hardware. Depending on the selected type, the direction of which the pct is expected to set:
<ul>
<li>normal = pct 0 means open, pct 100 means closed</li>
<li>HomeKit = pct 100 means open, pct 0 means closed</li>
</ul>
</li>
<li><a name="rl_secondsDown">rl_secondsDown</a> <code> attr <Rollo-Device> rl_secondsDown <number></code>
<br />time in seconds needed to drive the shutter down</li>
<li><a name="rl_secondsUp">rl_secondsUp</a> <code> attr <Rollo-Device> rl_secondsUp <number></code>
<br />time in seconds needed to drive the shutter up</li>
<li><a name="rl_excessTop">rl_excessTop</a> <code> attr <Rollo-Device> rl_excessTop <number></code>
<br />additional time the shutter need from last visible top pct to the end pct</li>
<li><a name="rl_excessBottom">rl_excessBottom</a> <code>attr <Rollo-Device> rl_excessBottom <number></code>
<br />additional time the shutter need from visible closed pct to the end pct</li>
<li><a name="rl_switchTime">rl_switchTime</a> <code>attr <Rollo-Device> rl_switchTime <number></code>
<br />time for the shutter to switch from one driving direction to other driving direction</li>
<li><a name="rl_resetTime">rl_resetTime</a> <code>attr <Rollo-Device> rl_resetTime <number></code>
<br />additional time the shutter remains in driving state if driving to final positions (open, closed), to ensure that the final position was really reached. So difference in the pct calculation can be corrected.</li>
<li><a name="rl_reactionTime">rl_reactionTime</a> <code>attr <Rollo-Device> rl_reactionTime <number></code>
<br />additional time the shutter needs to start (from start command to really starting the motor)</li>
<li><a name="rl_autoStop">rl_autoStop</a> <code>attr <Rollo-Device> rl_autoStop [0|1]</code>
<br />No stop command should be sent, the shutter stops by itself.</li>
<li><a name="rl_commandUp">rl_commandUp</a> <code>attr <Rollo-Device> rl_commandUp <string></code>
<br />Up to three commands you have to send to drive the shutter up</li>
<li><a name="rl_commandDown">rl_commandDown</a> <code>attr <Rollo-Device> rl_commandDown <string></code>
<br />Up to three commands you have to send to drive the shutter down</li>
<li><a name="rl_commandStop">rl_commandStop</a> <code>attr <Rollo-Device> rl_commandStop <string></code>
<br />command to stop a driving shutter</li>
<li><a name="rl_commandStopDown">rl_commandStopDown</a> <code>attr <Rollo-Device> rl_commandStopDown <string></code>
<br />command to stop a down driving shutter, if not set commandStop is executed</li>
<li><a name="rl_commandStopUp">rl_commandStopUp</a> <code>attr <Rollo-Device> rl_commandStopUp <string></code>
<br />command to stop a up driving shutter, if not set commandStop is executed</li>
<li><a name="rl_blockMode">rl_blockMode</a> <code>attr <Rollo-Device> rl_blockMode [blocked|force-open|force-closed|only-up|only-down|half-up|half-down|none]</code>
<br />the possibilities of the shutter in blocked mode:
<ul>
<li>blocked = shutter can't drive</li>
<li>force-open = drive the shutter up if a drive command is send</li>
<li>force-closed = drive the shutter down if a drive command is send</li>
<li>only-up = only drive up commands are executed</li>
<li>only-down =only drive down commands are executed</li>
<li>half-up = only drive to pcts above half-up</li>
<li>half-down = only drive to pcts below half-down</li>
<li>none = blockmode is disabled</li>
</ul>
</li>
<li><a name="automatic-enabled">automatic-enabled</a> <code>attr <Rollo-Device> automatic-enabled [yes|no]</code>
<br />if disabled the additional module ROLLO_AUTOMATIC doesn't drive the shutter</li>
<li><a name="automatic-delay">automatic-delay</a> <code>attr <Rollo-Device> automatic-delay <number></code>
<br />if set any ROLLO_AUTOMATIC commands are executed delayed (in minutes)<br></li>
<li><a name="rl_forceDrive">rl_forceDrive</a> <code>attr <Rollo-Device> rl_forceDrive [0|1]</code>
<br />force open/closed even if device is already in target position<br></li>
<li><a name="rl_noSetPosBlocked">rl_noSetPosBlocked</a> <code>attr <Rollo-Device> rl_noSetPosBlocked [0|1]</code>
<br />if disabled positions may be set even if device is blocked. After unblocking it will drive to the position.<br></li>
<li><a name="disable">disable</a> <code>attr <Rollo-Device> disable [0|1]</code>
<br />if disabled all set and get commands for ROLLO are disabled<br></li>
<li><a href="#readingFnAttributes">readingFnAttributes</a></li>
</ul>
</div>
=end html
=begin html_DE
<a name="ROLLO"></a>
<h3>ROLLO</h3>
<div>