98_ComfoAir.pm 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100
  1. ##############################################
  2. # $Id: 98_ComfoAir.pm 14232 2017-05-09 19:10:28Z StefanStrobel $
  3. #
  4. # fhem Modul für ComfoAir Lüftungsanlagen von Zehnder mit
  5. # serieller Schnittstelle (RS232) sowie dazu kompatible Anlagen wie
  6. # Storkair WHR 930, Storkair 950
  7. # Paul Santos 370 DC, Paul Santos 570 DC
  8. # Wernig G90-380, Wernig G90-550
  9. #
  10. # Dieses Modul basiert auf der Protokollanalyse von SeeSolutions:
  11. # http://www.see-solutions.de/sonstiges/Protokollbeschreibung_ComfoAir.pdf
  12. # sowie auf den bereits existierenden Modulen von Joachim und danhauck
  13. # http://forum.fhem.de/index.php/topic,14697.0.html
  14. #
  15. # This file is part of fhem.
  16. #
  17. # Fhem is free software: you can redistribute it and/or modify
  18. # it under the terms of the GNU General Public License as published by
  19. # the Free Software Foundation, either version 2 of the License, or
  20. # (at your option) any later version.
  21. #
  22. # Fhem is distributed in the hope that it will be useful,
  23. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  24. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  25. # GNU General Public License for more details.
  26. #
  27. # You should have received a copy of the GNU General Public License
  28. # along with fhem. If not, see <http://www.gnu.org/licenses/>.
  29. #
  30. ##############################################################################
  31. # Changelog:
  32. #
  33. # 2014-04-18 initial version
  34. # 2014-05-17 added more protocol commands, changed logging settings
  35. # 2014-05-25 added hide- attributes
  36. # 2014-07-07 corrected handling of 0xD2 Message (protocol is different than documented)
  37. # 2014-07-24 added max queue length checking and attribute
  38. # 2016-01-25 Reading Namen mit Umlauten korrigiert
  39. # 2016-04-06 Testmode Protokollbefehle hinzugefügt
  40. # 2016-11-13 korrektur bei set / get / readanswer. Set liefert bei Erfolg undef statt Text
  41. # 2017-02-12 Doku ergänzt
  42. # 2017-05-09 Text-Kodierung für summary korrigiert
  43. #
  44. #
  45. # Todo / Ideas:
  46. #
  47. package main;
  48. use strict;
  49. use warnings;
  50. use Time::HiRes qw( time );
  51. sub ComfoAir_Initialize($);
  52. sub ComfoAir_Define($$);
  53. sub ComfoAir_Undef($$);
  54. sub ComfoAir_Set($@);
  55. sub ComfoAir_Get($@);
  56. sub ComfoAir_Read($);
  57. sub ComfoAir_Ready($);
  58. sub ComfoAir_ReadAnswer($$$);
  59. sub ComfoAir_GetUpdate($$);
  60. sub ComfoAir_Send($$$;$$);
  61. sub ComfoAir_ParseFrames($);
  62. sub ComfoAir_InterpretFrame($$);
  63. sub ComfoAir_HandleSendQueue($);
  64. sub ComfoAir_SendAck($);
  65. sub ComfoAir_TimeoutSend($);
  66. my $ComfoAir_Version = '1.51 - 9.5.2017';
  67. # %parseInfo:
  68. # replyCode => msgHashRef
  69. # msgHash => unpack, name, request, readings (array of readingHashes)
  70. # readingHash => name, map, set, setmin, setmax, hint, expr
  71. # jeder readingHash in parseMap wird in der Initialize-Funktion ergänzt um
  72. # - rmap aus map
  73. # - setopt aus map
  74. # - msgHash - Rückverweis auf msgHash
  75. my %parseInfo = (
  76. "0002" => { unpack => "C",
  77. name => "Test-Modus-Ein",
  78. request => "0001",
  79. },
  80. "001A" => { unpack => "C",
  81. name => "Test-Modus-Aus",
  82. request => "0019",
  83. },
  84. "FF09" => { unpack => "C",
  85. name => "Klappen setzen",
  86. readings => [ { name => "Bypass",
  87. map => "1:offen, 0:geschlossen, 3:stop",
  88. set => "0009:%02x03", }]},
  89. "000c" => { unpack => "CCS>S>",
  90. name => "Ventilation-Status", # PC Befehl
  91. request => "000b",
  92. readings => [ { name => "Proz_Zuluft"},
  93. { name => "Proz_Abluft"},
  94. { name => "UPM_Zuluft", expr => 'int(1875000/$val)'},
  95. { name => "UPM_Abluft", expr => 'int(1875000/$val)'}]},
  96. "0068" => { unpack => "CCCA*",
  97. name => "Bootloader-Version", # PC Befehl
  98. request => "0067",
  99. readings => [ { name => "Bootloader_Version_Major"},
  100. { name => "Bootloader_Version_Minor"},
  101. { name => "Bootloader_Version_Beta"},
  102. { name => "Bootloader_Version_Name"}]},
  103. "006a" => { unpack => "CCCA*", # PC Befehl
  104. name => "Firmware-Version",
  105. request => "0069",
  106. readings => [ { name => "Firmware_Version_Major"},
  107. { name => "Firmware_Version_Minor"},
  108. { name => "Firmware_Version_Beta"},
  109. { name => "Firmware_Version_Name"}]},
  110. "0098" => { unpack => "CCCCCCxCCCCCCCCCC",
  111. name => "Sensordaten",
  112. request => "0097",
  113. readings => [ { name => "Temp_Enthalpie", expr => '$val / 2 - 20'},
  114. { name => "Feucht_Enthalpie"},
  115. { name => "Analog1_Proz"},
  116. { name => "Analog2_Proz"},
  117. { name => "Koeff_Enthalpie"},
  118. { name => "Timer_Enthalpie", expr => '$val * 12'},
  119. { name => "Analog1_Zu_Wunsch"},
  120. { name => "Analog1_Ab_Wunsch"},
  121. { name => "Analog2_Zu_Wunsch"},
  122. { name => "Analog2_Ab_Wunsch"},
  123. { name => "Analog3_Proz"},
  124. { name => "Analog4_Proz"},
  125. { name => "Analog3_Zu_Wunsch"},
  126. { name => "Analog3_Ab_Wunsch"},
  127. { name => "Analog4_Zu_Wunsch"},
  128. { name => "Analog4_Ab_Wunsch"}]},
  129. "009c" => { unpack => "C", # PC Befehl
  130. name => "RS232-Modus", # eigener Request existiert nicht sondern set mit 9b erzeugt Antwort 9c
  131. readings => [ { name => "RS232-Modus",
  132. map => "0:Ende, 1:nur-PC, 2:nur-CC-Ease, 3:PC-Master, 4:PC-Log",
  133. set => "009b:%02x", }]},
  134. "00a2" => { unpack => "CCA10CC", # PC Befehl
  135. name => "KonPlatine-Version",
  136. request => "00a1",
  137. readings => [ { name => "KonPlatine_Version_Major"},
  138. { name => "KonPlatine_Version_Minor"},
  139. { name => "KonPlatine_Version_Name"},
  140. { name => "CC-Ease_Version"},
  141. { name => "CC-Luxe_Version"}]},
  142. "00ca" => { unpack => "CCCCCCCC",
  143. name => "Verzoegerungen",
  144. request => "00c9",
  145. readings => [ { name => "Verz_Bad_Einschalt"},
  146. { name => "Verz_Bad_Ausschalt"},
  147. { name => "Verz_L1_Ausschalt"},
  148. { name => "Verz_Stosslueftung"},
  149. { name => "Verz_Filter_Wochen"},
  150. { name => "Verz_RF_Hoch_Kurz"},
  151. { name => "Verz_RF_Hoch_Lang"},
  152. { name => "Verz_Kuechenhaube_Ausschalt"}]},
  153. "00ce" => { unpack => "CCCCCCCCCCCC",
  154. name => "Ventilation-Levels",
  155. request => "00cd", defaultpoll => 1,
  156. readings => [ { name => "Proz_Abluft_abwesend"},
  157. { name => "Proz_Abluft_niedrig"},
  158. { name => "Proz_Abluft_mittel"},
  159. { name => "Proz_Zuluft_abwesend"},
  160. { name => "Proz_Zuluft_niedrig"},
  161. { name => "Proz_Zuluft_mittel"},
  162. { name => "Proz_Abluft_aktuell"},
  163. { name => "Proz_Zuluft_aktuell"},
  164. { name => "Stufe",
  165. showget => 1,
  166. map => "0:auto, 1:abwesend, 2:niedrig, 3:mittel, 4:hoch",
  167. set => "0099:%02x"},
  168. { name => "Zuluft_aktiv"},
  169. { name => "Proz_Abluft_hoch"},
  170. { name => "Proz_Zuluft_hoch"}]},
  171. "00d2" => { unpack => "CCCCCCC",
  172. name => "Temperaturen",
  173. request => "00d1", defaultpoll => 1,
  174. check => '($fields[5] & 15) == 15',
  175. readings => [ { name => "Temp_Komfort", expr => '$val / 2 - 20',
  176. set => "00D3:%02x", setexpr => '($val + 20) *2',
  177. setmin => 12, setmax => 28, hint => "slider,12,1,28"},
  178. { name => "Temp_Aussen" ,
  179. showget => 1, expr => '$val / 2 - 20'},
  180. { name => "Temp_Zuluft" , expr => '$val / 2 - 20'},
  181. { name => "Temp_Abluft" , expr => '$val / 2 - 20'},
  182. { name => "Temp_Fortluft", expr => '$val / 2 - 20'},
  183. { name => "Temp_Flag"},
  184. { name => "Temp_EWT", expr => '$val / 2 - 20'}]},
  185. "00de" => { unpack => "H6H6H6S>S>S>S>H6",
  186. name => "Betriebsstunden",
  187. request => "00dd",
  188. readings => [ { name => "Betriebsstunden_Abwesend", expr => 'hex($val)'},
  189. { name => "Betriebsstunden_Niedrig", expr => 'hex($val)'},
  190. { name => "Betriebsstunden_Mittel", expr => 'hex($val)'},
  191. { name => "Betriebsstunden_Frostschutz"},
  192. { name => "Betriebsstunden_Vorheizung"},
  193. { name => "Betriebsstunden_Bypass"},
  194. { name => "Betriebsstunden_Filter"},
  195. { name => "Betriebsstunden_Hoch", expr => 'hex($val)'}]},
  196. "00e0" => { unpack => "xxCCCxC",
  197. name => "Status-Bypass",
  198. request => "00df", defaultpoll => 1,
  199. readings => [ { name => "Bypass_Faktor"},
  200. { name => "Bypass_Stufe"},
  201. { name => "Bypass_Korrektur"},
  202. { name => "Bypass_Sommermodus", map => "0:nein, 1:ja"}]},
  203. "00e2" => { unpack => "CCCS>C",
  204. name => "Status-Vorheizung",
  205. request => "00e1",
  206. readings => [ { name => "Status_Klappe", map => "0:geschlossen, 1:offen, 2:unbekannt"},
  207. { name => "Status_Frostschutz", map => "0:inaktiv, 1:aktiv"},
  208. { name => "Status_Vorheizung", map => "0:inaktiv, 1:aktiv"},
  209. { name => "Frostminuten"}, # S> is 2 bytes as high low
  210. { name => "Status_Frostsicherheit", map => "1:extra, 4:sicher"}]},
  211. );
  212. my @setList; # helper to return valid set options if set is called with "?"
  213. my @getList; # helper to return valid get options if get is called with "?"
  214. my %setHash; # helper to reference the readings array in the above parseInfo for each set option
  215. my %getHash; # helper to reference the msgHash in parseInfo for each name / get option
  216. my %requestHash; # helper to reference each msgHash for each request Set
  217. my %cmdHash; # helper to map from send cmd code to msgHash of Reply
  218. my %ComfoAir_AddSets = (
  219. "SendRawData" => ""
  220. );
  221. #####################################
  222. sub
  223. ComfoAir_Initialize($)
  224. {
  225. my ($hash) = @_;
  226. require "$attr{global}{modpath}/FHEM/DevIo.pm";
  227. $hash->{ReadFn} = "ComfoAir_Read";
  228. $hash->{ReadyFn} = "ComfoAir_Ready";
  229. $hash->{DefFn} = "ComfoAir_Define";
  230. $hash->{UndefFn} = "ComfoAir_Undef";
  231. $hash->{SetFn} = "ComfoAir_Set";
  232. $hash->{GetFn} = "ComfoAir_Get";
  233. @setList = ();
  234. @getList = ();
  235. my @pollList = (); # ergänzt später $hash->{AttrList}
  236. # gehe durch alle Nachrichtentypen in parseInfo und erzeuge Hilfsdaten für set, get und die Attribute:
  237. # berechne reverse map aus der map zum Wandeln der gesetzten Werte
  238. # und berechnet setList für den "choose one of" Rückgabewert in det Set-Funktion
  239. # setHash enthält dann für jede gültige Set-Option eine Referenz auf den readingHash
  240. # requestHash: für jeden Request den Verweis auf msgHash innerhalb parseInfo
  241. while (my ($replyCode, $msgHashRef) = each (%parseInfo)) {
  242. my $msgName = $msgHashRef->{name};
  243. # baue pollList und requestHash auf, setze Requests in @setList.
  244. if (defined ($msgHashRef->{request})) { # Nachricht kann abgefragt werden
  245. my $requestName = "request-" . $msgName; # für eine Set-Option
  246. my $attrName = "poll-" . $msgName; # für das Attribut zur Steuerung welche Blöcke abgefragt werden
  247. my $attr2Name = "hide-" . $msgName; # für das Attribut zum Verstecken von Blöcken
  248. $requestHash{$requestName} = $msgHashRef; # erzeuge requestHash für Verweis von requestName auf msgHash
  249. $requestHash{$requestName}->{replyCode} = $replyCode; # ergänze Replycode im msgHash
  250. $cmdHash{$msgHashRef->{request}} = $msgHashRef; # erzeuge %cmdHash für Verweis von RequestCode auf msgHash (für Debug Log)
  251. push @setList, $requestName;
  252. push @pollList, "$attrName:0,1";
  253. push @pollList, "$attr2Name:0,1";
  254. }
  255. # gehe durch alle Readings im Nachrichtentyp und erzeuge getHash, setHash und setList, rmap, setopt
  256. foreach my $readingHashRef (@{$msgHashRef->{readings}}) {
  257. my $reading = $readingHashRef->{name}; # Name des Readings
  258. # getHash erzeugen
  259. $getHash{$reading} = $readingHashRef; # erzeuge getHash mit Verweis von Reading-Name auf msgHash
  260. push @getList, $reading
  261. if ($readingHashRef->{showget}); # sichtbares get (alle Readings können per Get aktiv abgefragt werden)
  262. # Rückwärtsverweis auf msgHash erzeugen
  263. $readingHashRef->{msgHash} = $msgHashRef; # ergänze Rückwärtsverweis
  264. # gibt es für das Reading ein SET?
  265. if (defined($readingHashRef->{set})) {
  266. # ist eine Map definiert, aus der eine Reverse-Map und auch Hints abgeleitet werden können?
  267. if (defined($readingHashRef->{map})){
  268. my $rm = $readingHashRef->{map};
  269. $rm =~ s/([^ ,\$]+):([^ ,\$]+),? ?/$2 $1 /g; # reverse map string erzeugen
  270. my %rmap = split (' ', $rm); # reverse hash aus dem reverse string
  271. $readingHashRef->{rmap} = \%rmap; # reverse map im readingHash sichern
  272. my $hl = $readingHashRef->{map}; # create hint list from map
  273. $hl =~ s/([^ ,\$]+):([^ ,\$]+,?) ?/$2/g;
  274. $readingHashRef->{setopt} = $reading . ":$hl";
  275. } else {
  276. $readingHashRef->{setopt} = $reading; # keine besonderen Optionen, nur den Namen für setopt verwenden.
  277. }
  278. if (defined($readingHashRef->{hint})){ # hints explizit definiert? (überschreibt evt. schon abgeleitete hints)
  279. $readingHashRef->{setopt} = $reading .
  280. ":" . $readingHashRef->{hint};
  281. }
  282. $setHash{$reading} = $readingHashRef; # erzeuge Hash mit Verweis auf readingHashRef für jedes Reading mit Set
  283. push @setList, $readingHashRef->{setopt}; # speichere Liste mit allen Sets inkl. der Hints nach ":" für Rückgabe bei Set ?
  284. }
  285. }
  286. }
  287. $hash->{AttrList}= "do_not_notify:1,0 " .
  288. "queueDelay " .
  289. "timeout " .
  290. "queueMax " .
  291. #"minSendDelay " .
  292. join (" ", @pollList) . " " . # Def der zyklisch abzufragenden Nachrichten
  293. $readingFnAttributes;
  294. }
  295. #####################################
  296. sub
  297. ComfoAir_Define($$)
  298. {
  299. my ($hash, $def) = @_;
  300. my @a = split("[ \t][ \t]*", $def);
  301. my ($name, $ComfoAir, $dev, $interval) = @a;
  302. return "wrong syntax: define <name> ComfoAir [devicename|none] [interval]"
  303. if(@a < 3);
  304. $hash->{BUSY} = 0;
  305. $hash->{EXPECT} = "";
  306. $hash->{ModuleVersion} = $ComfoAir_Version;
  307. if (!defined($interval)) {
  308. $hash->{INTERVAL} = 0;
  309. Log 1, "$name: interval is 0 or not defined - not sending requests - just listening!";
  310. } else {
  311. $hash->{INTERVAL} = $interval;
  312. }
  313. DevIo_CloseDev($hash);
  314. if($dev eq "none") {
  315. Log 1, "$name: device is none, commands will be echoed only";
  316. return undef;
  317. }
  318. $hash->{DeviceName} = $dev;
  319. my $ret = DevIo_OpenDev($hash, 0, 0);
  320. InternalTimer(gettimeofday()+1, "ComfoAir_GetUpdate", $hash, 0) # erste Abfrage von Werten nach 1 Sekunde (zumindest in Queue stellen)
  321. if ($hash->{INTERVAL});
  322. return $ret;
  323. }
  324. #####################################
  325. sub
  326. ComfoAir_Undef($$)
  327. {
  328. my ($hash, $arg) = @_;
  329. my $name = $hash->{NAME};
  330. DevIo_CloseDev($hash);
  331. RemoveInternalTimer ("timeout:".$name);
  332. RemoveInternalTimer ("queue:".$name);
  333. RemoveInternalTimer ($hash);
  334. return undef;
  335. }
  336. #####################################
  337. sub
  338. ComfoAir_Get($@)
  339. {
  340. my ($hash, @a) = @_;
  341. return "\"get ComfoAir\" needs at least one argument" if(@a < 2);
  342. my $name = $hash->{NAME};
  343. my $getName = $a[1];
  344. if (defined($getHash{$getName})) {
  345. # get Option für Reading aus parseInfo -> generische Verarbeitung
  346. my $msgHash = $getHash{$getName}{msgHash}; # Hash für die Nachricht aus parseInfo
  347. Log3 $name, 3, "$name: Request found in getHash created from parseInfo data";
  348. if ($msgHash->{request}) {
  349. ComfoAir_Send($hash, $msgHash->{request}, "", $msgHash->{replyCode}, 1);
  350. my ($err, $result) = ComfoAir_ReadAnswer($hash, $getName, $msgHash->{replyCode});
  351. return $err if ($err);
  352. return $result;
  353. } else {
  354. return "Protocol doesn't provide a command to get $getName";
  355. }
  356. } else {
  357. # undefiniertes Get
  358. Log3 $name, 5, "$name: Get $getName not found, return list @getList ";
  359. return "Unknown argument $a[1], choose one of @getList ";
  360. }
  361. return undef;
  362. }
  363. #####################################
  364. sub
  365. ComfoAir_Set($@)
  366. {
  367. my ($hash, @a) = @_;
  368. return "\"set ComfoAir\" needs at least an argument" if(@a < 2);
  369. my $name = $hash->{NAME};
  370. my ($cmd,$fmt,$data);
  371. my $setName = $a[1];
  372. my $setVal = $a[2];
  373. my $rawVal = "";
  374. if (defined($requestHash{$setName})) {
  375. # set Option ist Daten-Abfrage-Request aus parseInfo
  376. Log3 $name, 5, "$name: Request found in requestHash created from parseInfo data";
  377. ComfoAir_Send($hash, $requestHash{$setName}{request}, "", $requestHash{$setName}{replyCode});
  378. return "";
  379. }
  380. if (defined($setHash{$setName})) {
  381. # set Option für einen einzelnen Wert, in parseInfo definiert -> generische Verarbeitung
  382. if (!defined($setVal)) {
  383. Log3 $name, 3, "$name: No Value given to set $setName";
  384. return "No Value given to set $setName";
  385. }
  386. Log3 $name, 5, "$name: Set found option $setName in setHash created from parseInfo data";
  387. ($cmd, $fmt) = split(":", $setHash{$setName}{set});
  388. # 1. Schritt, falls definiert per Umkehrung der Map umwandeln (z.B. Text in numerische Codes)
  389. if (defined($setHash{$setName}{rmap})) {
  390. if (defined($setHash{$setName}{rmap}{$setVal})) {
  391. # reverse map für das Reading und den Wert definiert
  392. $rawVal = $setHash{$setName}{rmap}{$setVal};
  393. Log3 $name, 5, "$name: found $setVal in setHash rmap and converted to $rawVal";
  394. } else {
  395. Log3 $name, 3, "$name: Set Value $setVal did not match defined map";
  396. return "Set Value $setVal did not match defined map";
  397. }
  398. } else {
  399. # wenn keine map, dann wenigstens sicherstellen, dass numerisch.
  400. if ($setVal !~ /^-?\d+\.?\d*$/) {
  401. Log3 $name, 3, "$name: Set Value $setVal is not numeric";
  402. return "Set Value $setVal is not numeric";
  403. }
  404. $rawVal = $setVal;
  405. }
  406. # 2. Schritt: falls definiert Min- und Max-Werte prüfen
  407. if (defined($setHash{$setName}{setmin})) {
  408. Log3 $name, 5, "$name: checking Value $rawVal against Min $setHash{$setName}{setmin}";
  409. return "Set Value $rawVal is smaller than Min ($setHash{$setName}{setmin})"
  410. if ($rawVal < $setHash{$setName}{setmin});
  411. }
  412. if (defined($setHash{$setName}{setmax})) {
  413. Log3 $name, 5, "$name: checking Value $rawVal against Max $setHash{$setName}{setmax}";
  414. return "Set Value $rawVal is bigger than Max ($setHash{$setName}{setmax})"
  415. if ($rawVal > $setHash{$setName}{setmax});
  416. }
  417. # 3. Schritt: Konvertiere mit setexpr falls definiert
  418. if (defined($setHash{$setName}{setexpr})) {
  419. my $val = $rawVal;
  420. $rawVal = eval($setHash{$setName}{setexpr});
  421. Log3 $name, 5, "$name: converted Value $val to $rawVal using expr $setHash{$setName}{setexpr}";
  422. }
  423. # 4. Schritt: mit sprintf umwandeln und senden.
  424. $data = sprintf($fmt, $rawVal); # in parseInfo angegebenes Format bei set=> - meist Konvert in Hex
  425. ComfoAir_Send($hash, $cmd, $data, 0);
  426. # Nach dem Set gleich den passenden Datenblock nochmals anfordern, damit die Readings de neuen Wert haben
  427. if ($setHash{$setName}{msgHash}{request}) {
  428. ComfoAir_Send($hash, $setHash{$setName}{msgHash}{request}, "",
  429. $setHash{$setName}{msgHash}{replyCode},1);
  430. # falls ein minDelay bei Send implementiert wäre, müsste ReadAnswer optimiert werden, sonst wird der 2. send ggf nicht vor einem Timeout gesendet ...
  431. my ($err, $result) = ComfoAir_ReadAnswer($hash, $setName, $setHash{$setName}{msgHash}{replyCode});
  432. #return "$setName -> $result";
  433. return $err if ($err);
  434. }
  435. return undef;
  436. } elsif (defined($ComfoAir_AddSets{$setName})) {
  437. # Additional set option not defined in parseInfo but ComfoAir_AddSets
  438. if($setName eq "SendRawData") {
  439. return "please specify data as cmd or cmd -> data in hex"
  440. if (!defined($setVal));
  441. ($cmd, $data) = split("->",$setVal); # eingegebener Wert ist HexCmd -> HexData
  442. $data="" if(!defined($data));
  443. }
  444. ComfoAir_Send($hash, $cmd, $data, 0);
  445. } else {
  446. # undefiniertes Set
  447. Log3 $name, 5, "$name: Set $setName not found, return list @setList " . join (" ", keys %ComfoAir_AddSets);
  448. return "Unknown argument $a[1], choose one of @setList " . join (" ", keys %ComfoAir_AddSets);
  449. }
  450. return undef;
  451. }
  452. #####################################
  453. # Called from the read functions
  454. sub
  455. ComfoAir_ParseFrames($)
  456. {
  457. my $hash = shift;
  458. my $name = $hash->{NAME};
  459. my $frame = $hash->{helper}{buffer};
  460. $hash->{RAWBUFFER} = unpack ('H*', $frame);
  461. Log3 $name, 5, "$name: raw buffer: $hash->{RAWBUFFER}";
  462. # check for full frame in buffer
  463. if ($frame =~ /\x07\xf0(.{3}(?:[^\x07]|(?:\x07\x07))*)\x07\x0f(.*)/s) {
  464. # got full frame (and maybe Ack before but that's ok)
  465. my $framedata = $1;
  466. $hash->{helper}{buffer} = $2; # only keep the rest after the frame
  467. $framedata =~ s/\x07\x07/\x07/g; # remove double x07
  468. $hash->{LASTFRAMEDATA} = unpack ('H*', $framedata);
  469. Log3 $name, 5, "$name: ParseFrames got frame: $hash->{RAWBUFFER}" .
  470. " data $hash->{LASTFRAMEDATA} Rest " . unpack ('H*', $hash->{helper}{buffer});
  471. return $framedata;
  472. } elsif ($frame =~ /\x07\xf3(.*)/s) {
  473. my $level = ($hash->{INTERVAL} ? 4 : 5);
  474. Log3 $name, $level, "$name: read got Ack";
  475. $hash->{helper}{buffer} = $1; # only keep the rest after the frame
  476. if (!$hash->{EXPECT}) {
  477. $hash->{BUSY} = 0;
  478. # es wird keine weitere Antwort erwartet -> gleich weiter Send Queue abarbeiten und nicht auf alten Timer warten
  479. RemoveInternalTimer ("timeout:".$name);
  480. RemoveInternalTimer ("queue:".$name);
  481. ComfoAir_HandleSendQueue ("direct:".$name); # don't wait for next regular handle queue slot
  482. }
  483. return undef;
  484. } else {
  485. return undef; # continue reading, probably frame not fully received yet
  486. }
  487. }
  488. #####################################
  489. # Called from the read functions
  490. sub
  491. ComfoAir_InterpretFrame($$)
  492. {
  493. my $hash = shift;
  494. my $framedata = shift;
  495. my $name = $hash->{NAME};
  496. my ($cmd, $hexcmd, $hexdata, $len, $data, $chk);
  497. if (defined($framedata)) {
  498. if ($framedata =~ /(.{2})(.)(.*)(.)/s) {
  499. $cmd = $1;
  500. $len = $2;
  501. $data = $3;
  502. $chk = unpack ('C', $4);
  503. $hexcmd = unpack ('H*', $cmd);
  504. $hexdata = unpack ('H*', $data);
  505. Log3 $name, 5, "$name: read split frame into cmd $hexcmd, len " . unpack ('C', $len) .
  506. ", data $hexdata chk $chk";
  507. } else {
  508. Log3 $name, 3, "$name: read: error splitting frame into fields: $hash->{LASTFRAMEDATA}";
  509. return;
  510. }
  511. }
  512. # Länge prüfen
  513. if (unpack ('C', $len) != length($data)) {
  514. Log3 $name, 4, "$name: read: wrong length: " . length($data) .
  515. " (calculated) != " . unpack ('C', $len) . " (header)" .
  516. " cmd=$hexcmd, data=$hexdata, chk=$chk";
  517. #return;
  518. }
  519. # Checksum prüfen
  520. my $csum = unpack ('%8C*', $cmd . $len . $data . "\xad"); # berechne csum
  521. if($csum != $chk) {
  522. Log3 $name, 4, "$name: read: wrong checksum: $csum (calculated) != $chk (frame) cmd $hexcmd, data $hexdata";
  523. return;
  524. };
  525. # Parse Data
  526. if ($parseInfo{$hexcmd}) {
  527. if (!AttrVal($name, "hide-$parseInfo{$hexcmd}{name}", 0)) {
  528. # Definition für diesen Nachrichten-Typ gefunden
  529. my %p = %{$parseInfo{$hexcmd}};
  530. Log3 $name, 4, "$name: read got " . $p{"name"} . " (reply code $hexcmd) with data $hexdata";
  531. # Definition der einzelnen Felder abarbeiten
  532. my @fields = unpack($p{"unpack"}, $data);
  533. my $filter = 0;
  534. if ($p{check}) {
  535. Log3 $name, 5, "$name: cmd $hexcmd check is " . eval($p{check}) .
  536. ', $fields[5] = ' . $fields[5] if ($fields[5] > 15);
  537. if (!eval($p{check})) {
  538. Log3 $name, 5, "$name: filter data for failed check: @fields";
  539. $filter = 1;
  540. }
  541. }
  542. if (!$filter) {
  543. readingsBeginUpdate($hash);
  544. for (my $i = 0; $i < scalar(@fields); $i++) {
  545. # einzelne Felder verarbeiten
  546. my $reading = $p{"readings"}[$i]{"name"};
  547. my $val = $fields[$i];
  548. # Exp zur Nachbearbeitung der Werte?
  549. if ($p{"readings"}[$i]{"expr"}) {
  550. Log3 $name, 5, "$name: read evaluate $val with expr " . $p{"readings"}[$i]{"expr"};
  551. $val = eval($p{"readings"}[$i]{"expr"});
  552. }
  553. # Map zur Nachbereitung der Werte?
  554. if ($p{"readings"}[$i]{"map"}) {
  555. my %map = split (/[,: ]+/, $p{"readings"}[$i]{"map"});
  556. Log3 $name, 5, "$name: read maps value $val with " . $p{"readings"}[$i]{"map"};
  557. $val = $map{$val} if ($map{$val});
  558. }
  559. Log3 $name, 5, "$name: read assign $reading with $val";
  560. readingsBulkUpdate($hash, $reading, $val);
  561. }
  562. readingsEndUpdate($hash, 1);
  563. }
  564. }
  565. } else {
  566. my $level = ($hash->{INTERVAL} ? 4 : 5);
  567. Log3 $name, $level, "$name: read: unknown cmd $hexcmd, len " . unpack ('C', $len) .
  568. ", data $hexdata, chk $chk";
  569. }
  570. if ($hash->{EXPECT}) {
  571. # der letzte Request erwartet eine Antwort -> ist sie das?
  572. if ($hexcmd eq $hash->{EXPECT}) {
  573. $hash->{BUSY} = 0;
  574. $hash->{EXPECT} = "";
  575. Log3 $name, 5, "$name: read got expected reply ($hexcmd), setting BUSY=0";
  576. } else {
  577. Log3 $name, 3, "$name: read did not get expected reply (" . $hash->{EXPECT} . ") but $hexcmd";
  578. }
  579. }
  580. ComfoAir_SendAck($hash) if ($hash->{INTERVAL});
  581. if (!$hash->{EXPECT}) {
  582. # es wird keine Antwort mehr erwartet -> gleich weiter Send Queue abarbeiten und nicht auf Timer warten
  583. $hash->{BUSY} = 0; # zur Sicherheit falls ein Ack versäumt wurde
  584. RemoveInternalTimer ("timeout:".$name);
  585. RemoveInternalTimer ("queue:".$name);
  586. ComfoAir_HandleSendQueue ("direct:".$name); # don't wait for next regular handle queue slot
  587. }
  588. }
  589. #####################################
  590. # Called from the global loop, when the select for hash->{FD} reports data
  591. sub
  592. ComfoAir_Read($)
  593. {
  594. my $hash = shift;
  595. my $name = $hash->{NAME};
  596. my $buf = DevIo_SimpleRead($hash);
  597. return if(!defined($buf));
  598. $hash->{helper}{buffer} .= $buf;
  599. for (my $i = 0;$i < 2;$i++) {
  600. my $framedata = ComfoAir_ParseFrames($hash);
  601. return if (!$framedata);
  602. ComfoAir_InterpretFrame($hash, $framedata);
  603. }
  604. }
  605. #####################################
  606. # Called from get / set to get a direct answer
  607. sub
  608. ComfoAir_ReadAnswer($$$)
  609. {
  610. my ($hash, $arg, $expectReply) = @_;
  611. my $name = $hash->{NAME};
  612. return ("No FD", undef)
  613. if(!$hash || ($^O !~ /Win/ && !defined($hash->{FD})));
  614. my ($buf, $framedata, $cmd);
  615. my $rin = '';
  616. my $to = AttrVal($name, "timeout", 2); # default is 2 seconds timeout
  617. Log3 $name, 5, "$name: ReadAnswer called for get $arg";
  618. for(;;) {
  619. if($^O =~ m/Win/ && $hash->{USBDev}) {
  620. $hash->{USBDev}->read_const_time($to*1000); # set timeout (ms)
  621. $buf = $hash->{USBDev}->read(999);
  622. if(length($buf) == 0) {
  623. Log3 $name, 3, "$name: Timeout in ReadAnswer for get $arg";
  624. return ("Timeout reading answer for $arg", undef);
  625. }
  626. } else {
  627. if(!$hash->{FD}) {
  628. Log3 $name, 3, "$name: Device lost in ReadAnswer for get $arg";
  629. return ("Device lost when reading answer for get $arg", undef);
  630. }
  631. vec($rin, $hash->{FD}, 1) = 1; # setze entsprechendes Bit in rin
  632. my $nfound = select($rin, undef, undef, $to);
  633. if($nfound < 0) {
  634. next if ($! == EAGAIN() || $! == EINTR() || $! == 0);
  635. my $err = $!;
  636. DevIo_Disconnected($hash);
  637. Log3 $name, 3, "$name: ReadAnswer $arg: error $err";
  638. return("ComfoAir_ReadAnswer $arg: $err", undef);
  639. }
  640. if($nfound == 0) {
  641. Log3 $name, 3, "$name: Timeout2 in ReadAnswer for $arg";
  642. return ("Timeout reading answer for $arg", undef);
  643. }
  644. $buf = DevIo_SimpleRead($hash);
  645. if(!defined($buf)) {
  646. Log3 $name, 3, "$name: ReadAnswer for $arg got no data";
  647. return ("No data", undef);
  648. }
  649. }
  650. if($buf) {
  651. $hash->{helper}{buffer} .= $buf;
  652. Log3 $name, 5, "$name: ReadAnswer got: " . unpack ("H*", $hash->{helper}{buffer});
  653. }
  654. $framedata = ComfoAir_ParseFrames($hash);
  655. if ($framedata) {
  656. ComfoAir_InterpretFrame($hash, $framedata);
  657. $cmd = unpack ('H4x*', $framedata);
  658. if ($cmd eq $expectReply) {
  659. # das war's worauf wir gewartet haben
  660. Log3 $name, 5, "$name: ReadAnswer done with success";
  661. return (undef, ReadingsVal($name, $arg, ""));
  662. }
  663. }
  664. ComfoAir_HandleSendQueue("direct:".$name);
  665. }
  666. }
  667. #####################################
  668. sub
  669. ComfoAir_Ready($)
  670. {
  671. my ($hash) = @_;
  672. return DevIo_OpenDev($hash, 1, undef)
  673. if($hash->{STATE} eq "disconnected");
  674. # This is relevant for windows/USB only
  675. my $po = $hash->{USBDev};
  676. my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags) = $po->status;
  677. return ($InBytes>0);
  678. }
  679. #####################################
  680. sub
  681. ComfoAir_GetUpdate($$) {
  682. my ($hash) = @_;
  683. my $name = $hash->{NAME};
  684. InternalTimer(gettimeofday()+$hash->{INTERVAL}, "ComfoAir_GetUpdate", $hash, 0)
  685. if ($hash->{INTERVAL});
  686. foreach my $msgHashRef (values %parseInfo) {
  687. if (defined($msgHashRef->{request})) {
  688. my $default = ($msgHashRef->{defaultpoll} ? 1 : 0); # verwende als Defaultwert für Attribut, falls gesetzt in %parseInfo
  689. if (AttrVal($name, "poll-$msgHashRef->{name}", $default)) {
  690. Log3 $name, 5, "$name: GetUpdate requests $msgHashRef->{name}, default is $default";
  691. ComfoAir_Send($hash, $msgHashRef->{request}, "", $msgHashRef->{replyCode});
  692. }
  693. }
  694. }
  695. }
  696. #####################################
  697. sub
  698. ComfoAir_Send($$$;$$){
  699. my ($hash, $hexcmd, $hexdata, $expectReply, $first) = @_;
  700. my $name = $hash->{NAME};
  701. my $cmd = pack ('H*', $hexcmd);
  702. my $data = pack ('H*', $hexdata);
  703. my $len = pack ('C', length ($data));
  704. my $csum = pack ('C', unpack ('%8C*', $cmd . $len . $data. "\xad"));
  705. my $framedata = $data.$csum;
  706. $framedata =~ s/\x07/\x07\x07/g; # double 07 in contents of frame including Checksum!
  707. my $frame = "\x07\xF0".$cmd.$len.$framedata."\x07\x0F";
  708. my $hexframe = unpack ('H*', $frame);
  709. $expectReply = "" if (!$expectReply);
  710. Log3 $name, 4, "$name: send adds frame to queue with cmd $hexcmd" .
  711. ($cmdHash{$hexcmd} ? " (get " . $cmdHash{$hexcmd}{name} . ")" : "") .
  712. " / frame " . $hexframe;
  713. my %entry;
  714. $entry{DATA} = $frame;
  715. $entry{EXPECT} = $expectReply;
  716. my $qlen = ($hash->{QUEUE} ? scalar(@{$hash->{QUEUE}}) : 0);
  717. Log3 $name, 5, "$name: send queue length : $qlen";
  718. if(!$qlen) {
  719. $hash->{QUEUE} = [ \%entry ];
  720. } else {
  721. if ($qlen > AttrVal($name, "queueMax", 20)) {
  722. Log3 $name, 3, "$name: send queue too long, dropping request";
  723. } else {
  724. if ($first) {
  725. unshift (@{$hash->{QUEUE}}, \%entry);
  726. } else {
  727. push(@{$hash->{QUEUE}}, \%entry);
  728. }
  729. }
  730. }
  731. ComfoAir_HandleSendQueue("direct:".$name);
  732. }
  733. #######################################
  734. sub
  735. ComfoAir_TimeoutSend($)
  736. {
  737. my $param = shift;
  738. my (undef,$name) = split(':',$param);
  739. my $hash = $defs{$name};
  740. Log3 $name, 3, "$name: timeout waiting for reply" .
  741. ($hash->{EXPECT} ? " expecting " . $hash->{EXPECT} : "") .
  742. " Request was " . $hash->{LASTREQUEST};
  743. $hash->{BUSY} = 0;
  744. $hash->{EXPECT} = "";
  745. };
  746. #######################################
  747. sub
  748. ComfoAir_HandleSendQueue($)
  749. {
  750. my $param = shift;
  751. my (undef,$name) = split(':',$param);
  752. my $hash = $defs{$name};
  753. my $arr = $hash->{QUEUE};
  754. my $now = gettimeofday();
  755. my $queueDelay = AttrVal($name, "queueDelay", 1);
  756. Log3 $name, 5, "$name: handle send queue";
  757. if(defined($arr) && @{$arr} > 0) {
  758. if (!$init_done) { # fhem not initialized, wait with IO
  759. RemoveInternalTimer ("queue:".$name);
  760. InternalTimer($now+$queueDelay, "ComfoAir_HandleSendQueue", "queue:".$name, 0);
  761. Log3 $name, 3, "$name: init not done, delay writing from queue";
  762. return;
  763. }
  764. if ($hash->{BUSY}) { # still waiting for reply to last request
  765. RemoveInternalTimer ("queue:".$name);
  766. InternalTimer($now+$queueDelay, "ComfoAir_HandleSendQueue", "queue:".$name, 0);
  767. Log3 $name, 5, "$name: send busy, delay writing from queue";
  768. return;
  769. }
  770. my $entry = $arr->[0];
  771. my $bstring = $entry->{DATA};
  772. my $hexcmd = unpack ('xxH4x*', $bstring);
  773. if($bstring ne "") { # if something to send - do so
  774. $hash->{LASTREQUEST} = unpack ('H*', $bstring);
  775. $hash->{BUSY} = 1; # at least wait for ACK
  776. Log3 $name, 4, "$name: handle queue sends" .
  777. ($cmdHash{$hexcmd} ? " get " . $cmdHash{$hexcmd}{name} : "") .
  778. " code: $hexcmd" .
  779. " frame: " . $hash->{LASTREQUEST} .
  780. ($entry->{EXPECT} ? " and wait for " . $entry->{EXPECT} : "") .
  781. ", V " . $hash->{ModuleVersion};
  782. DevIo_SimpleWrite($hash, $bstring, 0);
  783. if ($entry->{EXPECT}) {
  784. # we expect a reply
  785. $hash->{EXPECT} = $entry->{EXPECT};
  786. }
  787. my $to = AttrVal($name, "timeout", 2); # default is 2 seconds timeout
  788. RemoveInternalTimer ("timeout:".$name);
  789. InternalTimer($now+$to, "ComfoAir_TimeoutSend", "timeout:".$name, 0);
  790. }
  791. shift(@{$arr});
  792. if(@{$arr} == 0) { # last item was sent -> delete queue
  793. delete($hash->{QUEUE});
  794. } else { # more items in queue -> schedule next handle invocation
  795. RemoveInternalTimer ("queue:".$name);
  796. InternalTimer($now+$queueDelay, "ComfoAir_HandleSendQueue", "queue:".$name, 0);
  797. }
  798. }
  799. }
  800. #######################################
  801. sub
  802. ComfoAir_SendAck($)
  803. {
  804. my $hash = shift;
  805. my $name = $hash->{NAME};
  806. Log3 $name, 4, "$name: sending Ack";
  807. DevIo_SimpleWrite($hash, "\x07\xf3", 0);
  808. }
  809. 1;
  810. =pod
  811. =item device
  812. =item summary module for Zehnder ComfoAir, StorkAir WHR930, Wernig G90-380 and Santos 370
  813. =item summary_DE Modul für Zehnder ComfoAir, StorkAir WHR930, Wernig G90-380 and Santos 370
  814. =begin html
  815. <a name="ComfoAir"></a>
  816. <h3>ComfoAir</h3>
  817. <ul>
  818. ComfoAir provides a way to communicate with ComfoAir ventilation systems from Zehnder, especially the ComfoAir 350 (CA350).
  819. It seems that many other ventilation systems use the same communication device and protocol,
  820. e.g. WHR930 from StorkAir, G90-380 from Wernig and Santos 370 DC from Paul.
  821. They are connected via serial line to the fhem computer.
  822. This module is based on the protocol description at http://www.see-solutions.de/sonstiges/Protokollbeschreibung_ComfoAir.pdf
  823. and copies some ideas from earlier modules for the same devices that were posted in the fhem forum from danhauck(Santos) and Joachim (WHR962).
  824. <br>
  825. The module can be used in two ways depending on how fhem and / or a vendor supplied remote control device
  826. like CC Ease or CC Luxe are connected to the system. If a remote control device is connected it is strongly advised that
  827. fhem does not send data to the ventilation system as well and only listens to the communication betweem the vendor equipment.
  828. The RS232 interface used is not made to support more than two parties communicating and connecting fhem in parallel to a CC Ease or similar device can lead to
  829. collisions when sending data which can corrupt the ventilation system.
  830. If connected in parallel fhem should only passively listen and &lt;Interval&gt; is to be set to 0. <br>
  831. If no remote control device is connected to the ventilation systems then fhem has to take control and actively request data
  832. in the interval to be defined. Otherwiese fhem will not see any data. In this case fhem can also send commands to modify settings.
  833. <br><br>
  834. <b>Prerequisites</b>
  835. <ul>
  836. <br>
  837. <li>
  838. This module requires the Device::SerialPort or Win32::SerialPort module.
  839. </li>
  840. </ul>
  841. <br>
  842. <a name="ComfoAirDefine"></a>
  843. <b>Define</b>
  844. <ul>
  845. <br>
  846. <code>define &lt;name&gt; ComfoAir &lt;device&gt; &lt;Interval&gt;</code>
  847. <br><br>
  848. The module connects to the ventialation system through the given Device and either passively listens to data that is communicated
  849. between the ventialation system and its remote control device (e.g. CC Luxe) or it actively requests data from the
  850. ventilation system every &lt;Interval&gt; seconds <br>
  851. If &lt;Interval&gt; is set to 0 then no polling will be done and the module only listens to messages on the line.<br>
  852. <br>
  853. Example:<br>
  854. <br>
  855. <ul><code>define ZL ComfoAir /dev/ttyUSB1@9600 60</code></ul>
  856. </ul>
  857. <br>
  858. <a name="ComfoAirConfiguration"></a>
  859. <b>Configuration of the module</b><br><br>
  860. <ul>
  861. apart from the serial connection and the interval which both are specified in the define command there are several attributes that
  862. can optionally be used to modify the behavior of the module. <br><br>
  863. The module internally gives names to all the protocol messages that are defined in the module and these names can be used
  864. in attributes to define which requests are periodically sent to the ventilation device. The same nams can also be used with
  865. set commands to manually send a request. Since all messages and readings are generically defined in a data structure in the module, it should be
  866. quite easy to add more protocol details if needed without programming.
  867. <br>
  868. The names currently defined are:
  869. <pre>
  870. Bootloader-Version
  871. Firmware-Version
  872. RS232-Modus
  873. Sensordaten
  874. KonPlatine-Version
  875. Verzoegerungen
  876. Ventilation-Levels
  877. Temperaturen
  878. Betriebsstunden
  879. Status-Bypass
  880. Status-Vorheizung
  881. </pre>
  882. The attributes that control which messages are sent / which data is requested every &lt;Interval&gt; seconds are:
  883. <pre>
  884. poll-Bootloader-Version
  885. poll-Firmware-Version
  886. poll-RS232-Modus
  887. poll-Sensordaten
  888. poll-KonPlatine-Version
  889. poll-Verzoegerungen
  890. poll-Ventilation-Levels
  891. poll-Temperaturen
  892. poll-Betriebsstunden
  893. poll-Status-Bypass
  894. poll-Status-Vorheizung
  895. </pre>
  896. if the attribute is set to 1, the corresponding data is requested every &lt;Interval&gt; seconds. If it is set to 0, then the data is not requested.
  897. by default Ventilation-Levels, Temperaturen and Status-Bypass are requested if no attributes are set.
  898. <br><br>
  899. Example:<br><br>
  900. <pre>
  901. define ZL ComfoAir /dev/ttyUSB1@9600 60
  902. attr ZL poll-Status-Bypass 0
  903. define FileLog_Lueftung FileLog ./log/Lueftung-%Y.log ZL
  904. </pre>
  905. </ul>
  906. <a name="ComfoAirSet"></a>
  907. <b>Set-Commands</b><br>
  908. <ul>
  909. like with the attributes mentioned above, set commands can be used to send a request for data manually. The following set options are available for this:
  910. <pre>
  911. request-Status-Bypass
  912. request-Bootloader-Version
  913. request-Sensordaten
  914. request-Temperaturen
  915. request-Firmware-Version
  916. request-KonPlatine-Version
  917. request-Ventilation-Levels
  918. request-Verzoegerungen
  919. request-Betriebsstunden
  920. request-Status-Vorheizung
  921. </pre>
  922. additionally important fields can be set:
  923. <pre>
  924. Temp_Komfort (target temperature for comfort)
  925. Stufe (ventilation level)
  926. </pre>
  927. </ul>
  928. <a name="ComfoAirGet"></a>
  929. <b>Get-Commands</b><br>
  930. <ul>
  931. All readings that are derived from the responses to protocol requests are also available as Get commands. Internally a Get command triggers the corresponding
  932. request to the device and then interprets the data and returns the right field value. To avoid huge option lists in FHEMWEB, only the most important Get options
  933. are visible in FHEMWEB. However this can easily be changed since all the readings and protocol messages are internally defined in the modue in a data structure
  934. and to make a Reading visible as Get option only a little option (e.g. <code>showget => 1</code> has to be added to this data structure
  935. </ul>
  936. <a name="ComfoAirattr"></a>
  937. <b>Attributes</b><br><br>
  938. <ul>
  939. <li><a href="#do_not_notify">do_not_notify</a></li>
  940. <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
  941. <br>
  942. <li><b>poll-Bootloader-Version</b></li>
  943. <li><b>poll-Firmware-Version</b></li>
  944. <li><b>poll-RS232-Modus</b></li>
  945. <li><b>poll-Sensordaten</b></li>
  946. <li><b>poll-KonPlatine-Version</b></li>
  947. <li><b>poll-Verzoegerungen</b></li>
  948. <li><b>poll-Ventilation-Levels</b></li>
  949. <li><b>poll-Temperaturen</b></li>
  950. <li><b>poll-Betriebsstunden</b></li>
  951. <li><b>poll-Status-Bypass</b></li>
  952. <li><b>poll-Status-Vorheizung</b></li>
  953. include a request for the data belonging to the named group when sending requests every interval seconds <br>
  954. <li><b>hide-Bootloader-Version</b></li>
  955. <li><b>hide-Firmware-Version</b></li>
  956. <li><b>hide-RS232-Modus</b></li>
  957. <li><b>hide-Sensordaten</b></li>
  958. <li><b>hide-KonPlatine-Version</b></li>
  959. <li><b>hide-Verzoegerungen</b></li>
  960. <li><b>hide-Ventilation-Levels</b></li>
  961. <li><b>hide-Temperaturen</b></li>
  962. <li><b>hide-Betriebsstunden</b></li>
  963. <li><b>hide-Status-Bypass</b></li>
  964. <li><b>hide-Status-Vorheizung</b></li>
  965. prevent readings of the named group from being created even if used passively without polling and an external remote control requests this data.
  966. please note that this attribute doesn't delete already existing readings.<br>
  967. <li><b>queueDelay</b></li>
  968. modify the delay used when sending requests to the device from the internal queue, defaults to 1 second <br>
  969. <li><b>queueMax</b></li>
  970. max length of the send queue, defaults to 50<br>
  971. <li><b>timeout</b></li>
  972. set the timeout for reads, defaults to 2 seconds <br>
  973. </ul>
  974. <br>
  975. </ul>
  976. =end html
  977. =cut