-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy path12264_Buderus.py
1820 lines (1536 loc) · 79.7 KB
/
12264_Buderus.py
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
# -*# -*- coding: iso8859-1 -*-
## -----------------------------------------------------
## Logik-Generator V1.5
## -----------------------------------------------------
## Copyright © 2012, knx-user-forum e.V, All rights reserved.
##
## This program 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 3 of the License, or (at your option) any later version.
##
## This program 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 this program;
## if not, see <http://www.gnu.de/documents/gpl-3.0.de.html>.
### USAGE: python.exe LogikGenerator.py [--debug --en1=34 --en2="TEST"]
import sys
import codecs
import os
import base64
import marshal
import re
try:
from hashlib import md5
except ImportError:
import md5 as md5old
md5 = lambda x='': md5old.md5(x)
import inspect
import time
import socket
import tempfile
import zlib
import zipfile
##############
### Config ###
##############
## Name der Logik
LOGIKNAME="Buderus"
## Logik ID
LOGIKID="12264"
## Ordner im GLE
LOGIKCAT="Buderus"
## Beschreibung
LOGIKDESC="""
<P>Der Baustein stellt die zentrale Kommunikation zwischen einem "Logamatic Gateway RS232"
von Buderus her, wenn an dieses ein RS232 nach IP Umsetzter wie zum Beispiel ein Moxa angeschlossen ist. Dies stellt die Anwendung 1 dar
Zitat von der Buderus Produkt Seite:
Anwendung 1: Kommunikationsschnittstelle Logamatic 4000 / EMS zu übergeordneten DDC-/GLT- Anlagen z. B. Betriebsartenumschaltung,
Sollwerte ändern, Istwerte anzeigen, Weiterleitung von Betriebs- und Störmeldungen.
(Offenlegung Kommunikationsprotokoll zu Logamatic auf Anfrage)
Auf der einen Seite des "Logamatic Gateway RS232" ist der ECO-CAN Bus der Logamatic 4000 Heizungsanlage angeschlossen.
NICHT EMS !!! Dies ist ein anderes Protokoll. Die LED ECO-BUS muß blinken !!!
An der RS232 Schnittstelle des "Logamatic Gateway RS232" ist der Moxa (also RS232 <-> IP Umsetzter)
angeschlossen. Die IP Verbindung ist eine TCP Verbindung, wobei der Moxa der TCP Server ist und der Baustein im HS der TCP Client.
Im Moxa sind folgende Einstellungen zu tätigen (ggf. können einzelne Werte auch anders sein, diese wurden beim Test verwendet):
Für die RS232 Seite (Serial Settings):
Baudrate: 9600
Data Bits: 8
Stop Bits: 1
Parity: Keine
Flow Control: None !!!!
FIFO: Ja
Interface: RS232
Auf der IP Seite (Operational Setting):
- Operation Mode: TCP Server
- TCP alive check time: 7 min
- Inactivity time: 0 ms
- Max Connection: 1 (das kann nur eine sein !!! Der ECO-CAN Bus ist so aufgebaut)
- Local TCP Port: 5508 (Hier muß ein freier Port verwendet werden. Dieser muß dann im Baustein eingetragen werden.)
- Command Port: ist hier nicht relevant, kann irgend ein freier port sein.
Data packaging:
- Packing length: 0
- Delimiter 1: keiner
- Delimiter 2: keiner
- Delimiter process: keiner
- Force transmit: Nein
Der Moxa hat auch eine IP Adresse: z.B. 192.168.2.17 zusammen mit dem Local Port: hier z.B. 5208 stellt man das im Baustein
dann so dar: 192.168.2.17:5208
Der Baustein öffnet also eine Verbindung zu diesem Port und erhält sie kontinuierlich aufrecht. Diese Kommunikation ist noch mit dem
R3964R Protokoll geschützt. Dies macht der Baustein aber alles intern und nur die ausgepackten Informationen werden weitergegeben.
</P>
<P>Diese Schnittstelle bietet viele Möglichkeiten Monitordaten der Heizung mitzulesen und aber auch
Konfigurationen der Heizungsanlage zu verändern. Das Verändern sollte man aber dem Heizungfachmann
überlassen. Weiter ist jeder Schreiben Zugriff, ein Schreiben auf den Flash Speicher. Laut Buderus geht das pro Wert nur 1.000.000 Mal.
</P>
<P>Also, aus diesem Grund gibt es hier die sehr ernst gemeinte Empfehlung nur lesend diesem Baustein zu verwenden.<BR>
Die Config Option "readonly=1" sollte deshalb immer gesetzt bleiben. </P>
<P>Für die eigentlich Kommunikation sind zwingend folgende Beschreibungen von Buderus zu beachten:<BR>
7747004149 – 01/2009 DE - Technische Information - Monitordaten - System 4000<BR>
7747004150 – 05/2009 DE - Technische Information - Einstellbare Parameter - Logamatic 4000<BR>
Weiter ist folgende Beschreibung sehr Wertvoll:<BR>
Funktionsbeschreibung - KM471- RS232-Schnittstelle (3964R) - Für Programmversion 1.07<BR>
Die Kommunikationskarte KM471 ist das Modul, was auf der Buderus Seite den Datenaustauch abwickelt.<BR>
Zumindest ist in dem heutigen Easycom RS232 Gateway etwas vergleichbares drin.</P>
<P>Der Baustein kapselt für den Benutzer komplett die 3964R Procedure. Auch schaltet er automatisch in den Direkt Mode um, bei Kommandos
die den "Direktmode" benötigen. Er erkennt automatisch das Ende und schalten automatisch wieder in den Normal Mode zurück.
Ein Warten (und damit blockierend), dass in den Normal-Mode erst nach einem Timeout zurück geschaltet wird, kann somit vermieden werden.
Man kann sagen der Benutzer braucht sich um Normal-Mode und Direkt-Mode gar nicht kümmern. Dies geschieht alles automatisch. </P>
Für die Auswertung der Daten, die auf dem Ausgang 1 ausgegeben werden, gibt es für jeden DatenType dann einen eigenen Baustein. Dieser ist dann immer
über ein iKO vom Type 14Byte dann dort an zu schliessen. NIE DIREKT !!! Als erster wird der Solar DatenTyp veröffentlicht, weitere werden folgen.
Welche DatenTypen an welchen Regelgeräten angeschlossen sind kann man im SystemLog sehen. Die Regelgeräte haben meist die Nummer 0 wenn nur eins angeschlossen ist.
Sonst dann 1, 2 , wenn es mehrere sind.
Wenn man nun nach dem SystemStart=1 des HS zum Beispiel mit A100 alle Monitordaten des Regelgerätes 0 abfragt, werden alle Monitordaten einmal ausgelesen.
Ändern sich dann später ein Wert, werden diese im Normal-Mode automatisch gesendet und im HS dann auch aktualisiert.
<P>Folgende Kommandos sind getestet:<BR>
# Zeit abfragen mit "B1": ok<BR>
EN[3]:"B1" <BR>
# Zeit setzen mit "B2<Zeit beschrieben in Dokument Technische Information - Einstellbare Parameter> : ok <BR>
EN[3]:"B2121D941FD572"<BR>
# Anfordern des ECO-BUS Geräte Status, Endekennung: 0xA8 0x80+adr < Seriennummer > <version vorkomma> <version nachkomma><BR>
EN[3]:"A000"<BR>
# 'A8010948849F40E4' A8: Antwort , 01: Gerätestatus von Gerät #1 , 0948849F40E4: <6Byte Seriennummer> Codierung unklar<BR>
# 'A8020930032FC00C' A8: Antwort , 02: Gerätestatus von Gerät #2 , 0930032FC00C: <6Byte Seriennummer> Codierung unklar<BR>
# 'A8940102030405060F17' 0xA8 0x80+0x14 < Seriennummer '010203040506'> <version vorkomma 0x0F -> dez. 15> <version nachkomma 0x17 -> dez. 23><BR>
# Endekennung muß also 0x08 0x[89abcdef]? sein. ; Das ist die Version des EasyCom: 15.23 Sieht man nur über die ECO-SOFT.<BR>
Mit diesem Kommando "A000" kann man zum Beispiel nach dem System Start erstmal den Bus abfragen welche Geräte existieren. Hier sind auf dem ECOCAN Bus<BR>
die Adressen 1 und 2 vergeben. </P>
<P>Nun kann man mit A1 gefolgt von der Adresse des Regelgeräts alle einstellbaren Parameter abfragen: <BR>
##Anfordern aller einstellbaren Parameter eines Gerätes <BR>
#EN[3]:"A101"<BR>
#EN[3]:"A102"<BR>
## Endekennung "AA<busnr> hier also AA01 bzw. AA02 ## beides Ok</P>
<P>##Anfordern aller einstellbaren Parameter eines Gerätes im Block mode<BR>
EN[3]:"B301"<BR>
EN[3]:"B302"<BR>
## Endekennung "AA<busnr> hier also AA01 bzw. AA02 ## beides Ok</P>
<P>## Einzelabfrage A3 eines einstellbaren Parameter eines Gerätes, nur Beispiel<BR>
EN[3]:"A3010704"</P>
<P>## Einzelabfrage von Monitordaten eines Gerätes, nur Beispiel<BR>
EN[3]:"A3018004"<BR>
## ok<BR>
Das, was die meisten machen sollte ist nach dem Systemstart alle Monitor Daten einmal abzufragen.<BR>
##Anfordern aller Monitordaten eines Gerätes <BR>
#EN[3]:"A201"<BR>
#EN[3]:"A202"<BR>
## Endekennung "AC<busnr> hier also AC01 bzw AC02 ## beides Ok</P>
<P>##Anfordern aller Monitordaten eines Gerätes im Blockmode<BR>
EN[3]:"B401"<BR>
EN[3]:"B402"<BR>
## Endekennung "AC<busnr> hier also AC01 oder AC02 ## beides Ok</P>
Kommandos können auch durch ein "*" Zeichen getrennt gebündelt gesendet werden (z.B. "A101*A102"). Der Baustein
sendet sie dann nacheinander weiter.
Rechtliche Hinweise:
Die Produkte "Logamatic 4000", "Logamatic Gateway RS232" sind Produkte der Buderus Heiztechnik GmbH. - www.Buderus.de
Die Benutzung dieses Bausteins geschieht auf eigene Gefahr.
"""
VERSION="V0.20"
## Bedingung wann die kompilierte Zeile ausgeführt werden soll
BEDINGUNG="EI"
## Formel die in den Zeitspeicher geschrieben werden soll
ZEITFORMEL=""
## Nummer des zu verwenden Zeitspeichers
ZEITSPEICHER="0"
## AUF True setzen um Binären Code zu erstellen
doByteCode=False
#doByteCode=True
## Base64Code über SN[x] cachen
doCache=False
## Doku erstellen Ja/Nein
doDoku=True
debug=False
livedebug=False
showList=False
#############################
########## Logik ############
#############################
LOGIK = '''# -*- coding: iso8859-1 -*-
## -----------------------------------------------------
## '''+ LOGIKNAME +''' ### '''+VERSION+'''
##
## erstellt am: '''+time.strftime("%Y-%m-%d %H:%M")+'''
## -----------------------------------------------------
## Copyright © '''+ time.strftime("%Y") + ''', knx-user-forum e.V, All rights reserved.
##
## This program 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 3 of the License, or (at your option) any later version.
##
## This program 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 this program;
## if not, see <http://www.gnu.de/documents/gpl-3.0.de.html>.
## -- ''' +re.sub("\n","\n## -- ",LOGIKDESC)+ '''
#5000|"Text"|Remanent(1/0)|Anz.Eingänge|.n.|Anzahl Ausgänge|.n.|.n.
#5001|Anzahl Eingänge|Ausgänge|Offset|Speicher|Berechnung bei Start
#5002|Index Eingang|Default Wert|0=numerisch 1=alphanummerisch
#5003|Speicher|Initwert|Remanent
#5004|ausgang|Initwert|runden binär (0/1)|typ (1-send/2-sbc)|0=numerisch 1=alphanummerisch
#5012|abbruch bei bed. (0/1)|bedingung|formel|zeit|pin-ausgang|pin-offset|pin-speicher|pin-neg.ausgang
5000|"'''+LOGIKCAT+'''\\'''+LOGIKNAME+'''"|0|3|"E1 IP-Adresse:Port"|"E2 config"|"E3 senden"|2|"A1 Daten"|"A2 SystemLog"|"'''+VERSION+'''"
5001|3|2|0|1|1
# EN[x]
5002|1|"192.168.2.17:5208"|1 #* IP-Adresse:Port <BR>des Moxa
5002|2|""|1 #* config <BR>(mit debug=5 wird diese Konfiguration auf SystemLog ausgegeben, readonly=1 weist schreibende Kommandos ab, und writetime=1 erlaubt die BUS Zeit zu setzen) Der * ist das Trennzeichen.
5002|3|""|1 #* Senden <BR>(Hier werden die Kommandos an den ECOCAN Bus gesendet)
# Speicher
5003|1||0 #* logic
# Ausgänge
5004|1|""|0|1|1 #* Daten <BR>(Hier werden alle Antworten vom ECOCAN ausgegeben, hier müssen die Auswerte Bausteine immer über ein iKO angeschlossen werden)
5004|2|""|0|1|1 #* SystemLog <BR>(Ausgang an den SystemLog)
#################################################
'''
#####################
#### Python Code ####
#####################
code=[]
code.append([3,"EI",r"""
if EI == 1:
global socket
import socket
class buderus_connect(object):
def __init__(self,localvars):
from hs_queue import Queue
from hs_queue import hs_threading as threading
import re
self.id = "buderus_connect"
self.logik = localvars["pItem"]
self.MC = self.logik.MC
EN = localvars['EN']
self.device_connector = EN[1]
## Config
self.config = {
'debug': 0, # debug level (> 5) macht debug prints
'readonly' : 1, # wenn readyonly=1 , werden schreibende Befehle nicht zugelassen
'writetime' : 0, # wenn writetime=1 , wird das schreiben der Zeit zugelassen
'delaydirectendtime' : 1.0, # wartezeit beim beenden des directmodes
}
# 3964R Constants
self._constants = {
'STX': chr(0x02),
'DLE': chr(0x10),
'ETX': chr(0x03),
'NAK': chr(0x15),
'QVZ': 2, # Quittungsverzugzeit (QVZ) 2 sec
'ZVZ': 0.220, # Der Abstand zwischen zwei Zeichen darf nicht mehr als die Zeichenverzugszeit (ZVZ) von 220 ms
'BWZ': 4, # Blockwartezeit von 4 sec
}
## Gerätetypen
self.data_types = {
"07" : ("Heizkreis 1", 18),
"08" : ("Heizkreis 2", 18),
"09" : ("Heizkreis 3", 18),
"0A" : ("Heizkreis 4", 18),
"0B" : ("Außenparameter", 12),
"0C" : ("Warmwasser", 12),
"0D" : ("Konfiguration (Modulauswahl)", 18),
"0E" : ("Strategie wandhängend(UBA)", 18),
"10" : ("Kessel bodenstehend", 18),
"11" : ("Schaltuhr pro Woche Kanal 1", 18),
"12" : ("Schaltuhr pro Woche Kanal 2", 18),
"13" : ("Schaltuhr pro Woche Kanal 3", 18),
"14" : ("Schaltuhr pro Woche Kanal 4", 18),
"15" : ("Schaltuhr pro Woche Kanal 5", 18),
"16" : ("Heizkreis 5", 18),
"17" : ("Schaltuhr pro Woche Kanal 6", 18),
"18" : ("Heizkreis 6", 18),
"19" : ("Schaltuhr pro Woche Kanal 7", 18),
"1A" : ("Heizkreis 7", 18),
"1B" : ("Schaltuhr pro Woche Kanal 8", 18),
"1C" : ("Heizkreis 8", 18),
"1D" : ("Schaltuhr pro Woche Kanal 9", 18),
"1F" : ("Schaltuhr pro Woche Kanal 10", 18),
"20" : ("Strategie bodenstehend", 12),
"24" : ("Solar", 12),
"26" : ("Strategie (FM458)", 12),
"80" : ("Heizkreis 1", 18),
"81" : ("Heizkreis 2", 18),
"82" : ("Heizkreis 3", 18),
"83" : ("Heizkreis 4", 18),
"84" : ("Warmwasser", 12),
"85" : ("Strategie wandhängend", 12),
"87" : ("Fehlerprotokoll", 42),
"88" : ("Kessel bodenstehend", 42),
"89" : ("Konfiguration", 24),
"8A" : ("Heizkreis 5", 18),
"8B" : ("Heizkreis 6", 18),
"8C" : ("Heizkreis 7", 18),
"8D" : ("Heizkreis 8", 18),
"8E" : ("Heizkreis 9", 18),
"8F" : ("Strategie bodenstehend", 30),
"90" : ("LAP", 18),
"92" : ("Kessel 1 wandhängend", 60),
"93" : ("Kessel 2 wandhängend", 60),
"94" : ("Kessel 3 wandhängend", 60),
"95" : ("Kessel 4 wandhängend", 60),
"96" : ("Kessel 5 wandhängend", 60),
"97" : ("Kessel 6 wandhängend", 60),
"98" : ("Kessel 7 wandhängend", 60),
"99" : ("Kessel 8 wandhängend", 60),
"9A" : ("KNX FM446",60),
"9B" : ("Wärmemenge", 36),
"9C" : ("Störmeldemodul", 6),
"9D" : ("Unterstation", 6),
"9E" : ("Solarfunktion", 54),
"9F" : ("alternativer Wärmeerzeuger", 42),
}
## List für gefundene Geräte
self.found_devices = []
## List die Geräte IDs enthällt bei denen Antworten ausstehen
self.waiting_direct_bus = []
## threading Lock um _is_directmode und waiting_direct_bus zu beschreiben
self.directmode_lock = threading.RLock()
## Derzeitiger Direct-mode status
self._is_directmode = False
self._moxa_thread = None
## Socket zum MOXA
self.sock = None
## threading Lock für exklusives schreiben von entweder der Empfangs- oder Sende- Funktion
self._buderus_data_lock = threading.RLock()
## Queue für Nachrichten zum Logamtik
self._buderus_message_queue = Queue()
## Konfig an Eingang 2 parsen
self.readconfig(EN[2])
## Mit dem Kommando "0xA2 <ECOCAN-BUS-Adresse>" können die Monitordaten des ausgewählten
## ECOCAN-BUS-Gerätes von der Kommunikationskarte ausgelesen werden.
## Mit Hilfe des Kommandos "0xB0 <ECOCAN-BUS-Adresse>" gefolgt von einem entsprechenden
## Datenblock können im Direkt-Modus einstellbare Parameter die für ein Regelgerät bestimmt sind an die
## Schnittstelle geschickt werden. Die Schnittstelle schickt diese Daten dann weiter an das entsprechende
## Regelgerät.
self.directmode_regex = re.compile("(?P<id>A0|A1|A2|B3|B4)(?P<busnr>[0-9a-fA-F]{2})")
self.directmode_regexes = {
"A3" : re.compile("(?P<id>A3)(?P<busnr>[0-9a-fA-F]{2})(?P<Data_type>[0-9a-fA-F]{2})(?P<offset>[0-9a-fA-F]{2})"),
"B0" : re.compile("(?P<id>B0)(?P<busnr>[0-9a-fA-F]{2})(?P<Data_type>[0-9a-fA-F]{2})(?P<offset>[0-9a-fA-F]{2})[0-9a-fA-F]{12}"),
"B1" : re.compile("(?P<id>B1)"), ## Datum/Uhrzeit vom ECOBUS anfordern
"B2" : re.compile("(?P<id>B2)[0-9a-fA-F]{12}"), ## Datum/Uhrzeit auf ECOBUS schreiben
}
## Im "Normal-Modus" werden die Monitordaten nach folgendem Muster übertragen:
## 0xA7 <ECOCAN-BUS-Adresse> <TYP> <OFFSET> <DATUM>
## 0xA7 = Kennung für einzelne Monitordaten
## ECOCAN-BUS-Adresse = Herkunft´s Adresse des Monitordatum´s (hier Regelgeräteadresse)
## TYP = Typ der empfangenen Monitordaten
## OFFSET = Offset zur Einsortierung der Daten eines Typ´s
## DATUM = eigentlicher Messwert
## Im "Direct-Modus" werden alle Monitordaten nach folgendem Muster übertragen:
## 0xAB <ECOCAN-BUS-Adresse> <TYP> <OFFSET> <6 Daten-Byte>
## 0xAB = Kennung für Monitordaten
## ECOCAN-BUS-Adresse = die abgefragte Adresse zur Bestätigung
## TYP = Typ der gesendeten Monitordaten
## Daten unter dem entsprechenden Typ werden nur gesendet wenn auch die entsprechende Funktionalität
## im Regelgerät eingebaut ist.
## OFFSET = Offset zur Einsortierung der Daten eines Typ´s
## Im "Direct-Modus" werden alle Einstellbaren Parameter nach folgendem Muster übertragen:
## 0xA9 <ECOCAN-BUS-Adresse> <TYP> <OFFSET> <6 Daten-Byte>
## 0xA9 = Kennung für Monitordaten
## ECOCAN-BUS-Adresse = die abgefragte Adresse zur Bestätigung
## TYP = Typ der gesendeten Monitordaten
## Daten unter dem entsprechenden Typ werden nur gesendet wenn auch die entsprechende Funktionalität
## im Regelgerät eingebaut ist.
## OFFSET = Offset zur Einsortierung der Daten eines Typ´s
self.payload_regex = re.compile("(?P<id>B8|B9|A9|AB|B7)(?P<busnr>[0-9a-fA-F]{2})(?P<data_type>[0-9a-fA-F]{2})(?P<offset>[0-9a-fA-F]{2})(?P<data>(?:[0-9A-F]{12})+)")
self.payload_regexes = {
"A7" : re.compile("(?P<id>A7)(?P<busnr>[0-9a-fA-F]{2})(?P<data_type>[0-9a-fA-F]{2})(?P<offset>[0-9a-fA-F]{2})(?P<data>(?:[0-9A-F]{2}))"),
"A8" : re.compile("(?P<id>A8)(?:(?P<busnr>[0-9a-fA-F]{2})(?P<data>(?:[0-9A-F]{12}))$|[8-9a-fA-F][0-9a-fA-F]{13}(?P<version_vk>[0-9a-fA-F]{2})(?P<version_nk>[0-9a-fA-F]{2}))"),
"AE" : re.compile("(?P<id>AE)(?P<busnr>[0-9a-fA-F]{2})(?P<data>[0-9A-F]{8})"), ## Fehlerstatus
"AF" : re.compile("AF(?P<bustime>[0-9a-fA-F]{12}|FF)") ## Uhrzeit Datum
}
## Als Endekennung für das abgefragte Regelgerät oder falls keine Daten vorhanden sind, wird der
## nachfolgende String
## 0xAC <ECOCAN-BUS-Adresse> gesendet Endekennung bei A2<busnr> und bei B4<busnr>
## 0xAA <ECOCAN-BUS-Adresse> gesendet Endekennung bei A1<busnr>
## 0xA8 0x80+adr < Seriennummer > <version vorkomma> <version nachkomma> Endekennung bei A100
## Da A8 auch als normale Antwort kommt, muß auf A8[89a-fA-F]? abgefragt werden
self.directmode_finish_regex = re.compile("(AC|AA|AD)(?P<busnr>[0-9a-fA-F]{2})")
#self.directmode_finish_AD_regex = re.compile("AD(?P<busnr>[0-9a-fA-F]{2})(?P<data_type>[0-9a-fA-F]{2})(?P<offset>[0-9a-fA-F]{2})(?P<data>(?:[0-9A-F]{12}))")
##
## 1.Byte Sekunden (0-59)
## 2.Byte Minuten (0-59)
## 3.Byte Stunden / Sommerzeit
## Bit 1-5 Stunden
## Bit 6 intern
## Bit 7 Sommerzeit (1=Sommerzeit)
## Bit 8 Funkuhr (1=ist Funkuhrzeit)
## 4.Byte Tag (1-31)
## 5.Byte Monat / Wochentag
## Bit 1-4 Monat
## Bit 5-7 Wochentag
## Bit 8 Funkuhrempfang zugelassen
## 6.Byte Jahr (89-188) > 100 20xx sonst 19xx
##
## Consumer Thread der Nachrichten an das Buderus Gerät schickt
self.buderus_queue_thread = threading.Thread(target=self._send_to_buderus_consumer,name='hs_buderus_consumer')
self.buderus_queue_thread.start()
## jetzt zum MOXA verbinden
self.connect()
def readconfig(self,configstring):
## config einlesen
import re
for (option,value) in re.findall("(\w+)=(.*?)(?:\*|$)", configstring ):
option = option.lower()
_configoption = self.config.get(option)
_configtype = type(_configoption)
## wenn es den Wert nicht gibt
if _configtype == type(None):
self.log("unbekannte Konfig Option %s=%s" % (option,value), severity="error" )
continue
## versuchen Wert im config string zum richtigen Type zu machen
try:
_val = _configtype(value)
self.config[option] = _val
#self.debug("Konfig Option %s=%s empfangen" % (option,value ), 5 )
except ValueError:
self.log("falscher Wert bei Konfig Option %s=%s (erwartet %r)" % (option,value, _configtype ), severity="error" )
pass
def time_to_bustime(self,set_time,funkuhr=0):
return ("{0:02x}{1:02x}{2:02x}{3:02x}{4:02x}{5:02x}".format(
set_time[5], ## Sekunden
set_time[4], ## Minuten
set_time[3] + (set_time[8] << 6) + (funkuhr << 7) , ## Stunden + Bit 7 Sommerzeit + Bit 8 Funkuhr
set_time[2], ## Tag
set_time[1] + ((set_time[6] + 1) << 4), ## Bit 1-4 Monat + Bit 5-7 (Wochentag +1)
set_time[0] - 1900 ## Jahr -1900
)).upper()
def bustime_to_time(self,bustime):
import binascii
bustime = bustime.lstrip("AF")
_bustime = [ ord(x) for x in binascii.unhexlify(bustime) ]
return [
(_bustime[5] + 1900), ## Jahr
(_bustime[4] & 0xf), ## Monat
_bustime[3], ## Tag
_bustime[2] & 0x1f, # Stunden
_bustime[1], ## Minuten
_bustime[0], ## Sekunden
(_bustime[4] >> 4 & 0x7) -1 , ## Wochentag
0,
-1 ##_bustime[2] >> 6 & 0x1 ## Sommerzeit ##FIXME## Time unknown
]
def device_addresses(self,msg):
_ret = []
_addresses = map(lambda x: x=="1",bin(int(msg,16))[2:][::-1])
for _addr in xrange(len(_addresses)):
if _addresses[_addr]:
_ret.append(_addr)
return _ret
def debug(self,msg,lvl=8):
## wenn debuglevel zu klein gleich zurück
if self.config.get("debug") == 0:
return
import time
## FIXME sollte später gesetzt werden
if lvl < 10 and lvl <= (self.config.get("debug")):
self.log(msg,severity='debug')
if (self.config.get("debug") == 10):
print "%s DEBUG-12264: %r" % (time.strftime("%H:%M:%S"),msg,)
def connect(self):
## _connect als Thread starten
from hs_queue import hs_threading as threading
self._moxa_thread = threading.Thread(target=self._connect,name='Buderus-Moxa-Connect')
self._moxa_thread.start()
def _send_to_buderus_consumer(self):
import select,time
while True:
## wenn noch keine Verbindung
if not self.sock:
self.debug("Socket nicht bereit ... warten")
## 1 Sekunden auf Socket warten
time.sleep(1)
continue
## solange noch ein direkt mode Befehlt austeht, darf kein neuer geschickt werden.
if self.get_direct_waiting():
continue
## nächste payload aus der Queue holen
msg = self._buderus_message_queue.get()
## nach gültigen zu sendener payload suchen
_cmdid = msg[:2]
_direct_mode_regex = self.directmode_regexes.get(_cmdid,self.directmode_regex)
_direct_mode = _direct_mode_regex.search(msg)
## wenn keine gültige SENDE payload
if not _direct_mode:
self.log("ungültige sende Nachricht %r" % (msg,) )
continue
if _cmdid in [ "B1","B2"]:
_busnr = _cmdid
else:
_busnr = _direct_mode.group("busnr")
if self.config.get("readonly"):
if _cmdid == "B0":
self.log("schreiben auf den Bus deaktiviert, Payload verworfen",severity="warn")
continue
if _cmdid == "B2" and not self.config.get("writetime"):
self.log("schreiben der Uhrzeit deaktiviert, Payload verworfen",severity="warn")
continue
## Wenn eine direct-mode anfrage
if _cmdid in ["A0","A1","A2","A3","B3","B4"]:
if _busnr not in self.waiting_direct_bus:
## busnr zur liste auf Antwort wartender hinzu
self.add_direct_waiting(_busnr)
else:
## Bus wird schon abgefragt
continue
self._buderus_data_lock.acquire()
self.debug("sende Queue exklusiv lock erhalten")
try:
## wenn wir nicht schon im directmode sind oder nicht auf den directmode schalten konnten
if not self.is_directmode():
if not self.set_directmode(True):
## payload verworfen und loop
continue
## payload per 3964R versenden
self._send3964r(msg)
## gucken ob wir den directmode noch brauchen
self.check_directmode_needed()
finally:
self._buderus_data_lock.release()
self.debug("sende Queue exklusiv lock released")
def add_direct_waiting(self,busnr):
## busnr zur liste zu erwartender Antworten hinzufügen
try:
self.directmode_lock.acquire()
self.waiting_direct_bus.append(busnr)
finally:
self.directmode_lock.release()
def remove_direct_waiting(self,busnr=None):
## busnr von der Liste der zu erwartetenden Antworten entfernen
try:
self.directmode_lock.acquire()
## wenn keine busnr übergeben wurde, dann alle entfernen
if not busnr:
## Flush
self.waiting_direct_bus =[]
self._is_directmode = False
## wenn die zu entfernenden busnr in der liste ist, entfernen
elif busnr in self.waiting_direct_bus:
self.waiting_direct_bus.remove(busnr)
finally:
self.directmode_lock.release()
def get_direct_waiting(self):
## threadsicheres abfragen der zu erwartenden Antworten
try:
self.directmode_lock.acquire()
return self.waiting_direct_bus
finally:
self.directmode_lock.release()
def is_directmode(self):
## threadsicheres abfragen von directmode
try:
self.directmode_lock.acquire()
return self._is_directmode
finally:
self.directmode_lock.release()
def check_directmode_needed(self):
import time
## Die Abfrage der gesamten Monitordaten braucht nur zu Beginn oder nach einem Reset zu erfolgen.
## Nach erfolgter Abfrage der Monitordaten sollte wieder mit dem Kommando 0xDC in den "Normal-Modus"
## zurückgeschaltet werden.
self.debug("check Directmode",lvl=7)
## wenn direct mode nicht an ist, dann gleich zurück
if not self.is_directmode():
self.debug("kein Directmode",lvl=7)
return
## wenn die Sendewarteschlange leer ist und keine Antworten(AC<busnr>) mehr von einem A2<busnr> erwartet werden,
## dann directmode ausschalten
if (self._buderus_message_queue.empty() and not self.get_direct_waiting()):
self.debug("deaktiviere Directmode",lvl=7)
self.set_directmode(False)
else:
self.debug("check nicht Directmode",lvl=7)
def set_directmode(self,mode):
## Bei dem Kommunikationsmodul wird zwischen einem "Normal-Modus" und einem "Direkt-Modus"
## unterschieden.
## "Normal-Modus" Bei diesem Modus werden laufend alle sich ändernden Monitorwerte
## sowie Fehlermeldungen übertragen.
## "Direkt-Modus" Bei diesem Modus kann der aktuelle Stand aller bisher vom Regelgerät
## generierten Monitordaten en Block abgefragt und ausgelesen werden.
## Mittels des Kommandos 0xDD kann von "Normal-Modus" in den "Direkt-Modus" umgeschaltet werden.
## In diesem Modus kann auf alle am ECOCAN-BUS angeschlossenen Geräte zugegriffen und es können
## geräteweise die Monitorwerte ausgelesen werden.
import time
_setmode = "DC"
if mode:
_setmode = "DD"
try:
self.directmode_lock.acquire()
_loop = 0
## FIXME: keine Ahnung ob wir das öfter als einmal versuchen oder nicht
while not self._is_directmode == mode:
if _loop == 2:
break
# wenn der rückgabewert vom Senden dem erwarteten mode erfolgreich ist, dann mode
if self._send3964r(_setmode):
self._is_directmode = mode
else:
self._is_directmode = not mode
## wenn kein erfolg dann 1 sekunde warten
time.sleep(1)
_loop += 1
return (self._is_directmode == mode)
finally:
self.directmode_lock.release()
def _send3964r(self,payload):
## returnwert erstmal auf False
_ret = False
try:
## auf Freigabe STX/DLE vom Socket warten
if self.wait_for_ready_to_receive():
## Payload jetzt senden
self.debug("jetzt payload %r senden" % (payload,) ,lvl=5)
self.send_payload(payload)
## returnwert auf True
_ret = True
else:
## wenn kein DLE auf unser STX kam dann payload verwerfen
self.debug("payload %r verworfen" % (payload,) ,lvl=4)
except:
## Fehler auf die HS Debugseite
self.MC.Debug.setErr(sys.exc_info(),"%r" % (payload,))
return _ret
## send_out: Wert auf einen Ausgang senden
## Parameter out: Ausgang auf den gesendet werden soll analog zu AN[x]
## Parameter value: Wert der an den Ausgang gesendet werden soll
def send_to_output(self,out,value):
import time
out -= 1 ## Intern starten die Ausgänge bei 0 und nicht bei 1
## Sendeintervall wird beachtet
if self.logik.SendIntervall == 0 or time.time() >= self.logik.Ausgang[out][0]:
## Wert an allen iKOs an den Ausgängen setzen
for iko in self.logik.Ausgang[out][1]:
self.logik.MC.TagList.setWert(FLAG_EIB_LOGIK,iko, value)
## Wenn value nicht 0 / "" / None etc ist dann die Befehle ausführen
if value:
for cmd in self.logik.Ausgang[out][2]:
cmd.execBefehl()
## Direkte Verbindungen und Connectoren
for con in self.logik.Ausgang[out][3]:
self.logik.MC.LogikList.ConnectList.append(con + [ value ])
## Wenn Sendeintervall gesetzt, nächste mögliche Ausführungszeit setzen
if self.logik.SendIntervall > 0:
self.logik.Ausgang[out][0] = time.time() + self.logik.SendIntervall
## Ausgangswert auch in der Logik setzen
self.logik.OutWert[out] = value
def log(self,msg,severity='info'):
import time
try:
from hashlib import md5
except ImportError:
import md5 as md5old
md5 = lambda x,md5old=md5old: md5old.md5(x)
_msg_uid = md5( "%s%s" % ( self.id, time.time() ) ).hexdigest()
_msg = '<log><id>%s</id><facility>buderus</facility><severity>%s</severity><message>%s</message></log>' % (_msg_uid,severity,msg)
self.send_to_output( 2, _msg )
def incomming(self,msg):
self.debug("incomming message %r" % (msg), lvl=5)
## mit * getrennte messages hinzufügen
for _msg in msg.split("*"):
## leerzeichen entfernen
_msg = _msg.replace(' ','')
## _msg in die sende Warteschlange
self._buderus_message_queue.put( _msg )
def busnr_4byte_to_list(self,bytes):
return (lambda addr: [x for x in xrange(len(addr)) if addr[x] ])(map(lambda x: x=="1",bin(int(bytes,16))[2:])[::-1])
def parse_payload(self,payload):
import time,binascii
_cmdid = payload[:2]
if _cmdid in ["A5","A6"]:
self.debug("%s in Busgeräte: %r" % (_cmdid,self.busnr_4byte_to_list(payload[2:10])),lvl=6)
return True
_payload_regex = self.payload_regexes.get(_cmdid,self.payload_regex)
## nach gültiger payload suchen
_payload = _payload_regex.search(payload)
## wenn normal-mode oder direct mode antwort
if _payload:
if _cmdid == "A5":
pass
if _cmdid == "A6":
pass
## wenn einen normal mode antwort mit Typ A7 kommt und der direktmode gerade an ist,
## dann ist der 60 Sekunden Timeout abgelaufen ohne die Daten rechtzeitig erhalten zu haben
if _cmdid in ["A7", "B7"] and self.is_directmode():
self.remove_direct_waiting()
## Der "Direkt-Modus" kann durch das Kommando 0xDC wieder verlassen werden.
## Außerdem wird vom "Direkt-Modus" automatisch in den "Normal-Modus" zurückgeschaltet, wenn für die
## Zeit von 60 sec kein Protokoll des "Direkt-Modus" mehr gesendet wird.
self.log("Directmode timeout")
if _cmdid == "A8" and self.is_directmode():
if _payload.groupdict().get("busnr"):
self.log("Regelgerät %s an ECOCAN BUS gefunden" % ( _payload.group("busnr") ) , severity="info")
else:
self.log("ECOCAN BUS Version %r.%r gefunden" % ( ord(binascii.unhexlify(_payload.group("version_vk"))), ord(binascii.unhexlify(_payload.group("version_nk"))) ) ,severity="info")
self.remove_direct_waiting("00") # da mit "A000" eingeleitet ist, dann ist die Busnr "00", diese muß nun wieder gelöscht werden
time.sleep( self.config.get('delaydirectendtime') )
self.check_directmode_needed()
if _cmdid == "AF":
_bustime = _payload.group("bustime")
if _bustime == "FF":
self.log("Keine ECOBUS-Uhrzeit vorhanden")
else:
_time = self.bustime_to_time(_bustime)
_diff = time.mktime(_time) - time.time()
self.log("ECOBUS-Uhrzeit empfangen: {0} (Differenz {1:.1f}sec)".format(time.strftime("%a %d.%m.%Y %H:%M:%S",_time),_diff))
if _cmdid in ["A7","A9","AB","B7","B8","B9"]:
## Datentype
_type = _payload.group("data_type")
## wenn wir das Gerät noch nicht gefunden hatten kurze Info über den Fund loggen
if _type not in self.found_devices:
self.found_devices.append( _type )
(_devicename, _datalen) = self.data_types.get( _type, ("unbekannter Datentyp (%s)" % _type, 0) )
self.log("Datentyp '%s' an Regelgerät %d gefunden" % ( _devicename, int(_payload.group("busnr")) ) , severity="info")
return True
else:
## wenn eine Endekennung AC|AA|AD<busnr> empfangen wurde, dann die busnr aus der liste für direct Daten entfernen und evtl direct_mode beenden
_direct = self.directmode_finish_regex.search(payload)
if _direct:
_busnr = _direct.group("busnr")
## bus von der liste auf antwort wartender im direct mode entfernen
self.remove_direct_waiting(_busnr)
#self.log("BusNr %s gelöscht" % ( repr(_busnr) ) ,severity="info")
## Wenn ein AC<busnr> gekommen ist, wird ggf. die Sende Richtung geändert, was zu Initialisierungskonflikten führen kann
time.sleep( self.config.get('delaydirectendtime') )
self.check_directmode_needed()
if _cmdid == "AD":
return True
else:
self.debug("NO Payload found",lvl=5)
return False
def wait_for_ready_to_receive(self):
import select,time
## 3 versuche warten bis wir auf ein STX ein DLE erhalten
for _loop in xrange(3):
## STX senden
self.debug("STX senden")
self.sock.send( self._constants['STX'] )
self.debug("STX gesendet / warten auf DLE")
## auf daten warten, timeout ist QVZ
_r,_w,_e = select.select( [ self.sock ],[],[], self._constants['QVZ'] )
## wenn kein timeout QVZ
if self.sock in _r:
# 1 Zeichen lesen
data = self.sock.recv(1)
## wenn wir ein DLE empfangen
if data == self._constants['DLE']:
self.debug("DLE empfangen")
## erfolg zurück
return True
## Wenn wir beim warten auf ein DLE ein STX der Gegenseite erhalten stellen wir unsere Sendung zurück und lassen das andere Gerät seine Daten erstmal senden
elif data == self._constants['STX']:
self.debug("STX empfangen Initialisierungskonflikt",lvl=5)
time.sleep(self._constants['ZVZ'])
## DLE senden, dass wir Daten vom anderen Gerät akzeptieren senden
self.sock.send( self._constants['DLE'] )
self.debug("DLE gesendet")
## eigentlich Funktion aus dem connect zum lesen der payload hier ausführen
self.read_payload()
### danach loop und erneuter sende Versuch
else:
self.debug("%r empfangen" % (data,),lvl=9 )
self.debug("Nach 3x STX senden innerhalb QVZ kein DLE",lvl=5)
return False
## Verbindung zum Moxa (gethreadet)
def _connect(self):
import time,socket,sys,select
try:
## TCP Socket erstellen
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
## IP:Port auslesen und dorthin verbinden
try:
_ip,_port = self.device_connector.split(":")
self.sock.connect( ( _ip, int(_port) ) )
self.log("Verbindung zu Netzwerkschnittstelle %s:%s hergestellt" % (_ip,_port))
except (TypeError,ValueError):
self.log("ungültige IP:Port Kombination %r an Eingang 1" % self.device_connector, severity="error")
return
except socket.error as error:
self.log("Verbindung zu Netzwerkschnittstelle %s:%s fehlgeschlagen" % (_ip,_port),severity="error")
raise
while True:
# wenn keine Verbindung dann abbruch
if not self.sock:
break
_r,_w,_e = select.select( [ self.sock ],[],[], 10 )
## exklusiv lock
if not self._buderus_data_lock.acquire(blocking=False):
## wenn nicht erhalten lesen wir nicht vom socket, da die Daten für die Sende Queue sind
continue
self.debug("empfang exklusiv lock erhalten")
try:
## wenn kein Timeout
if self.sock in _r:
## wenn Daten da sind, ein zeichen lesen
data = self.sock.recv(1)
## wenn keine Daten trotz select dann Verbindung abgebrochen
if not data:
self.debug("Verbindung abgebrochen",lvl=3)
break
## wenn wir ein STX empfangen
if data == self._constants['STX']:
self.debug("STX empfangen sende DLE")
## senden wir das DLE
self.sock.send( self._constants['DLE'] )
self.debug("DLE gesendet")
## jetzt die eigentliche payload vom socket lesen
self.read_payload()
else:
self.debug("ungültiges Zeichen %r empfangen" % (data,) ,lvl=4)
finally:
## den lock auf jedenfall relasen
self._buderus_data_lock.release()
self.debug("empfang exklusiv lock releasen")
except:
## fehler auf die HS Debugseite
self.MC.Debug.setErr(sys.exc_info(),"")
## 10 sekunden pause
time.sleep(10)
## dann reconnect
self.connect()
## STX = 0x02
## DLE = 0x10
## ETX = 0x03
## NAK = 0x15
## Die Steuerzeichen für die Prozedur 3964R sind der Norm DIN 66003 für den 7-Bit-Code entnommen. Sie
## werden allerdings mit der Zeichenlänge 8 Bit übertragen (Bit I7 = 0). Am Ende jedes Datenblocks wird zur
## Datensicherung ein Prüfzeichen(BCC) gesendet.
## Das Blockprüfzeichen wird durch eine exklusiv-oder-Verknüpfung über alle Datenbytes der
## Nutzinformation, inclusive der Endekennung DLE, ETX gebildet.
def send_payload(self,payload):
## Senden mit der Prozedur 3964R
## -----------------------------
## Zum Aufbau der Verbindung sendet die Prozedur 3964R das Steuerzeichen STX aus. Antwortet das
## Peripheriegerät vor Ablauf der Quittungsverzugzeit (QVZ) von 2 sec mit dem Zeichen DLE, so geht die
## Prozedur in den Sendebetrieb über. Antwortet das Peripheriegerät mit NAK, einem beliebigen anderen
## Zeichen (außer DLE) oder die Quittungsverzugszeit verstreicht ohne Reaktion, so ist der
## Verbindungsaufbau gescheitert. Nach insgesamt drei vergeblichen Versuchen bricht die Prozedur das
## Verfahren ab und meldet dem Interpreter den Fehler im Verbindungsaufbau.
## Gelingt der Verbindungsaufbau, so werden nun die im aktuellen Ausgabepuffer enthaltenen
## Nutzinformationszeichen mit der gewählten Übertragungsgeschwindigkeit an das Peripheriegerät
## gesendet. Das Peripheriegerät soll die ankommenden Zeichen in Ihrem zeitlichen Abstand überwachen.
## Der Abstand zwischen zwei Zeichen darf nicht mehr als die Zeichenverzugszeit (ZVZ) von 220 ms
## betragen.
## Jedes im Puffer vorgefundene Zeichen DLE wird als zwei Zeichen DLE gesendet. Dabei wird das Zeichen
## DLE zweimal in die Prüfsumme übernommen.
## Nach erfolgtem senden des Pufferinhalts fügt die Prozedur die Zeichen DLE, ETX und BCC als
## Endekennung an und wartet auf ein Quittungszeichen. Sendet das Peripheriegerät innerhalb der
## Quittungsverzugszeit QVZ das Zeichen DLE, so wurde der Datenblock fehlerfrei übernommen. Antwortet
## das Peripheriegerät mit NAK, einem beliebigen anderen Zeichen (außer DLE), einem gestörten Zeichen
## oder die Quittungsverzugszeit verstreicht ohne Reaktion, so wiederholt die Prozedur das Senden des
## Datenblocks. Nach insgesamt sechs vergeblichen Versuchen, den Datenblock zu senden, bricht die
## Prozedur das Verfahren ab und meldet dem Interpreter den Fehler im Verbindungsaufbau.
## Sendet das Peripheriegerät während einer laufenden Sendung das Zeichen NAK, so beendet die
## Prozedur den Block und wiederholt in der oben beschriebenen Weise.
import select,binascii
## 6 versuche
## Ungültige Payload abfangen um exception zu verhindern
if len(payload) % 2:
self.debug("ungültige Payloadlänge",lvl=1)
return
for _loop in xrange(6):
self.debug("exklusiv senden / versuch %d" % (_loop),lvl=6)
## checksumme