98_ComfoAir.pm 47 KB

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