00_KM271.pm 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320
  1. ##############################################
  2. # Thx to Himtronics
  3. # http://www.mikrocontroller.net/topic/141831
  4. # http://www.mikrocontroller.net/attachment/63563/km271-protokoll.txt
  5. # Buderus documents: 63011376, 63011377, 63011378
  6. # e.g. http://www.buderus.de/pdf/unterlagen/0063061377.pdf
  7. # $Id: 00_KM271.pm 13323 2017-02-03 16:42:06Z Physikus1 $
  8. package main;
  9. use strict;
  10. use warnings;
  11. use Time::HiRes qw( time );
  12. my %km271_sets = (
  13. "hk1_nachtsoll" => {SET => "07006565%02x656565:0702%02x", # 0.5 celsius
  14. OPT => ":slider,10,0.5,30,1"},
  15. "hk1_tagsoll" => {SET => "0700656565%02x6565:0703%02x", # 0.5 celsius
  16. OPT => ":slider,10,0.5,30,1"},
  17. "hk1_urlaubsoll" => {SET => "07006565656565%02x:0705%02x", # 0.5 celsius
  18. OPT => ":slider,10,0.5,30,1"},
  19. "hk1_betriebsart" => {SET => "070065656565%02x65:0704%02x",
  20. OPT => ""},
  21. "hk1_aussenhalt_ab" => {SET => "07156565%02x656565:0706%02x",
  22. OPT => ":slider,-20,1,10"},
  23. "hk2_nachtsoll" => {SET => "08006565%02x656565:0802%02x", # 0.5 celsius
  24. OPT => ":slider,10,0.5,30,1"},
  25. "hk2_tagsoll" => {SET => "0800656565%02x6565:0803%02x", # 0.5 celsius
  26. OPT => ":slider,10,0.5,30,1"},
  27. "hk2_urlaubsoll" => {SET => "08006565656565%02x:0805%02x", # 0.5 celsius
  28. OPT => ":slider,10,0.5,30,1"},
  29. "hk2_betriebsart" => {SET => "080065656565%02x65:0804%02x",
  30. OPT => ""},
  31. "hk2_aussenhalt_ab" => {SET => "08156565%02x656565:0806%02x",
  32. OPT => ":slider,-20,1,10"},
  33. "ww_soll" => {SET => "0C07656565%02x6565:0c07%02x", # 1.0 celsius
  34. OPT => ":slider,30,1,60"},
  35. "ww_betriebsart" => {SET => "0C0E%02x6565656565:0c0e%02x",
  36. OPT => ""},
  37. "ww_on-till" => {SET => "0C0E%02x6565656565:0c0e%02x",
  38. OPT => ":time"},
  39. "ww_zirkulation" => {SET => "0C0E6565656565%02x:0c0f%02x",
  40. OPT => ":slider,0,1,7"},
  41. "hk1_programm" => {SET => "1100%02x6565656565",
  42. OPT => ""},
  43. "hk1_timer" => {SET => "11%s",
  44. OPT => ""},
  45. "hk2_programm" => {SET => "1200%02x6565656565",
  46. OPT => ""},
  47. "hk2_timer" => {SET => "12%s",
  48. OPT => ""},
  49. "sommer_ab" => {SET => "070065%02x65656565:0701%02x",
  50. OPT => ":slider,9,1,31"},
  51. "frost_ab" => {SET => "07316565656565%02x:0707%02x",
  52. OPT => ":slider,-20,1,10"},
  53. "urlaub" => {SET => "1100656565%02x6565",
  54. OPT => ":slider,0,1,99"},
  55. "logmode" => {SET => "EE0000",
  56. OPT => ":noArg"}
  57. );
  58. my %km271_gets = (
  59. "l_fehler" => ":noArg",
  60. "l_fehlerzeitpunkt" => ":noArg",
  61. "l_fehleraktualisierung" => ":noArg"
  62. );
  63. # Message address:byte_offset in the message
  64. # Attributes:
  65. # d:x (divide), p:x (add), bf:x (bitfield), a:x (array), ne (generate no event)
  66. # mb:x (multi-byte-message, x-bytes, low byte), s (signed value)
  67. # t (timer - special handling), eh (error history - special handling)
  68. my %km271_tr = (
  69. "CFG_Sommer_ab" => "0000:1,p:-9,a:8",
  70. "cFG_Sommer_ab" => "0701:0,p:-9,a:8", # fake reading for internal notify
  71. "CFG_HK1_Nachttemperatur" => "0000:2,d:2",
  72. "cFG_HK1_Nachttemperatur" => "0702:0,d:2", # fake reading for internal notify
  73. "CFG_HK1_Tagtemperatur" => "0000:3,d:2",
  74. "cFG_HK1_Tagtemperatur" => "0703:0,d:2", # fake reading for internal notify
  75. "CFG_HK1_Betriebsart" => "0000:4,a:4",
  76. "cFG_HK1_Betriebsart" => "0704:0,a:4", # fake reading for internal notify
  77. "CFG_HK1_Urlaubtemperatur" => "0000:5,d:2",
  78. "cFG_HK1_Urlaubtemperatur" => "0705:0,d:2", # fake reading for internal notify
  79. "CFG_HK1_Max_Temperatur" => "000e:2",
  80. "CFG_HK1_Auslegung" => "000e:4",
  81. "CFG_HK1_Aufschalttemperatur" => "0015:0,a:9",
  82. "CFG_HK1_Aussenhalt_ab" => "0015:2,s",
  83. "cFG_HK1_Aussenhalt_ab" => "0706:0,s", # fake reading for internal notify
  84. "CFG_HK1_Absenkungsart" => "001c:1,a:6",
  85. "CFG_HK1_Heizsystem" => "001c:2,a:7",
  86. "CFG_HK1_Temperatur_Offset" => "0031:3,s,d:2",
  87. "CFG_HK1_Fernbedienung" => "0031:4,a:0",
  88. "CFG_Frost_ab" => "0031:5,s",
  89. "cFG_Frost_ab" => "0707:0,s", # fake reading for internal notify
  90. "CFG_HK2_Nachttemperatur" => "0038:2,d:2",
  91. "cFG_HK2_Nachttemperatur" => "0802:0,d:2", # fake reading for internal notify
  92. "CFG_HK2_Tagtemperatur" => "0038:3,d:2",
  93. "cFG_HK2_Tagtemperatur" => "0803:0,d:2", # fake reading for internal notify
  94. "CFG_HK2_Betriebsart" => "0038:4,a:4",
  95. "cFG_HK2_Betriebsart" => "0804:0,a:4", # fake reading for internal notify
  96. "CFG_HK2_Urlaubtemperatur" => "0038:5,d:2",
  97. "cFG_HK2_Urlaubtemperatur" => "0805:0,d:2", # fake reading for internal notify
  98. "CFG_HK2_Max_Temperatur" => "0046:2",
  99. "CFG_HK2_Auslegung" => "0046:4",
  100. "CFG_HK2_Aufschalttemperatur" => "004d:0,a:9",
  101. "CFG_WW_Vorrang" => "004d:1,a:0",
  102. "CFG_HK2_Aussenhalt_ab" => "004d:2,s",
  103. "cFG_HK2_Aussenhalt_ab" => "0806:0,s", # fake reading for internal notify
  104. "CFG_HK2_Absenkungsart" => "0054:1,a:6",
  105. "CFG_HK2_Heizsystem" => "0054:2,a:7",
  106. "CFG_HK2_Temperatur_Offset" => "0069:3,s,d:2",
  107. "CFG_HK2_Fernbedienung" => "0069:4,a:0",
  108. "CFG_Gebaeudeart" => "0070:2,a:13",
  109. "CFG_WW_Temperatur" => "007e:3",
  110. "cFG_WW_Temperatur" => "0c07:0", # fake reading for internal notify
  111. "CFG_WW_Betriebsart" => "0085:0,a:4",
  112. "cFG_WW_Betriebsart" => "0c0e:0,a:4", # fake reading for internal notify
  113. "CFG_WW_Aufbereitung" => "0085:3,a:0",
  114. "CFG_WW_Zirkulation" => "0085:5,a:11",
  115. "cFG_WW_Zirkulation" => "0c0f:0,a:11", # fake reading for internal notify
  116. "CFG_Sprache" => "0093:0,a:3",
  117. "CFG_Anzeige" => "0093:1,a:1",
  118. "CFG_Brennerart" => "009a:1,p:-1,a:12",
  119. "CFG_Max_Kesseltemperatur" => "009a:3",
  120. "CFG_Pumplogik" => "00a1:0",
  121. "CFG_Abgastemperaturschwelle" => "00a1:5,p:-9,a:5",
  122. "CFG_Brenner_Min_Modulation" => "00a8:0",
  123. "CFG_Brenner_Mod_Laufzeit" => "00a8:1",
  124. "PRG_HK1_Programm" => "0100:0,a:2",
  125. "CFG_Urlaubstage" => "0100:3",
  126. "PRG_HK1_Timer01" => "0107:0,t",
  127. "PRG_HK1_Timer02" => "010e:0,t",
  128. "PRG_HK1_Timer03" => "0115:0,t",
  129. "PRG_HK1_Timer04" => "011c:0,t",
  130. "PRG_HK1_Timer05" => "0123:0,t",
  131. "PRG_HK1_Timer06" => "012a:0,t",
  132. "PRG_HK1_Timer07" => "0131:0,t",
  133. "PRG_HK1_Timer08" => "0138:0,t",
  134. "PRG_HK1_Timer09" => "013f:0,t",
  135. "PRG_HK1_Timer10" => "0146:0,t",
  136. "PRG_HK1_Timer11" => "014d:0,t",
  137. "PRG_HK1_Timer12" => "0154:0,t",
  138. "PRG_HK1_Timer13" => "015b:0,t",
  139. "PRG_HK1_Timer14" => "0162:0,t",
  140. "PRG_HK2_Programm" => "0169:0,a:2",
  141. "PRG_HK2_Timer01" => "0170:0,t",
  142. "PRG_HK2_Timer02" => "0177:0,t",
  143. "PRG_HK2_Timer03" => "017e:0,t",
  144. "PRG_HK2_Timer04" => "0185:0,t",
  145. "PRG_HK2_Timer05" => "018c:0,t",
  146. "PRG_HK2_Timer06" => "0193:0,t",
  147. "PRG_HK2_Timer07" => "019a:0,t",
  148. "PRG_HK2_Timer08" => "01a1:0,t",
  149. "PRG_HK2_Timer09" => "01a8:0,t",
  150. "PRG_HK2_Timer10" => "01af:0,t",
  151. "PRG_HK2_Timer11" => "01b6:0,t",
  152. "PRG_HK2_Timer12" => "01bd:0,t",
  153. "PRG_HK2_Timer13" => "01c4:0,t",
  154. "PRG_HK2_Timer14" => "01cb:0,t",
  155. "CFG_Uhrzeit_Offset" => "01e0:1,s",
  156. "ERR_Fehlerspeicher1" => "0300:0,eh",
  157. "ERR_Fehlerspeicher2" => "0307:0,eh",
  158. "ERR_Fehlerspeicher3" => "030e:0,eh",
  159. "ERR_Fehlerspeicher4" => "0315:0,eh",
  160. "HK1_Betriebswerte1" => "8000:0,bf:0",
  161. "HK1_Betriebswerte2" => "8001:0,bf:1",
  162. "HK1_Vorlaufsolltemperatur" => "8002:0",
  163. "HK1_Vorlaufisttemperatur" => "8003:0,ne", # great part of all messages
  164. "HK1_Raumsolltemperatur" => "8004:0,d:2",
  165. "HK1_Raumisttemperatur" => "8005:0,d:2",
  166. "HK1_Einschaltoptimierung" => "8006:0",
  167. "HK1_Ausschaltoptimierung" => "8007:0",
  168. "HK1_Pumpe" => "8008:0",
  169. "HK1_Mischerstellung" => "8009:0,ne", # great part of all messages
  170. "HK1_Heizkennlinie_10_Grad" => "800c:0",
  171. "HK1_Heizkennlinie_0_Grad" => "800d:0",
  172. "HK1_Heizkennlinie_-10_Grad" => "800e:0",
  173. "HK2_Betriebswerte1" => "8112:0,bf:0",
  174. "HK2_Betriebswerte2" => "8113:0,bf:1",
  175. "HK2_Vorlaufsolltemperatur" => "8114:0",
  176. "HK2_Vorlaufisttemperatur" => "8115:0,ne", # great part of all messages
  177. "HK2_Raumsolltemperatur" => "8116:0,d:2",
  178. "HK2_Raumisttemperatur" => "8117:0,d:2",
  179. "HK2_Einschaltoptimierung" => "8118:0",
  180. "HK2_Ausschaltoptimierung" => "8119:0",
  181. "HK2_Pumpe" => "811a:0",
  182. "HK2_Mischerstellung" => "811b:0,ne", # great part of all messages
  183. "HK2_Heizkennlinie_10_Grad" => "811e:0",
  184. "HK2_Heizkennlinie_0_Grad" => "811f:0",
  185. "HK2_Heizkennlinie_-10_Grad" => "8120:0",
  186. "WW_Betriebswerte1" => "8424:0,bf:2",
  187. "WW_Betriebswerte2" => "8425:0,bf:3",
  188. "WW_Solltemperatur" => "8426:0",
  189. "WW_Isttemperatur" => "8427:0",
  190. "WW_Einschaltoptimierung" => "8428:0",
  191. "WW_Pumpentyp" => "8429:0,bf:5",
  192. "Kessel_Vorlaufsolltemperatur" => "882a:0",
  193. "Kessel_Vorlaufisttemperatur" => "882b:0,ne", # great part of all messages
  194. "Brenner_Einschalttemperatur" => "882c:0",
  195. "Brenner_Ausschalttemperatur" => "882d:0",
  196. "Kessel_Integral1" => "882e:0,ne",
  197. "Kessel_Integral" => "882f:0,mb:2,ne", # great part of all messages
  198. "Kessel_Fehler" => "8830:0,bf:6",
  199. "Kessel_Betrieb" => "8831:0,bf:4",
  200. "Brenner_Ansteuerung" => "8832:0,a:10",
  201. "Abgastemperatur" => "8833:0",
  202. "Brenner_Mod_Stellglied" => "8834:0",
  203. "Brenner_Laufzeit1_Minuten2" => "8836:0",
  204. "Brenner_Laufzeit1_Minuten1" => "8837:0",
  205. "Brenner_Laufzeit1_Minuten" => "8838:0,mb:3",
  206. "Brenner_Laufzeit2_Minuten2" => "8839:0",
  207. "Brenner_Laufzeit2_Minuten1" => "883a:0",
  208. "Brenner_Laufzeit2_Minuten" => "883b:0,mb:3",
  209. "Aussentemperatur" => "893c:0,s",
  210. "Aussentemperatur_gedaempft" => "893d:0,s",
  211. "Versionsnummer_VK" => "893e:0",
  212. "Versionsnummer_NK" => "893f:0",
  213. "Modulkennung" => "8940:0",
  214. "ERR_Alarmstatus" => "aa42:0,bf:7"
  215. );
  216. my %km271_rev;
  217. my @km271_bitarrays = (
  218. # 0 - HK_Betriebswerte1
  219. [ "-", "Ausschaltoptimierung", "Einschaltoptimierung", "Automatik",
  220. "Warmwasservorrang", "Estrichtrocknung", "Ferien", "Frostschutz",
  221. "Manuell" ],
  222. # 1 - HK_Betriebswerte2
  223. [ "-", "Sommer", "Tag", "Keine Kommunikation mit FB", "FB fehlerhaft",
  224. "Fehler Vorlauffuehler", "Maximaler Vorlauf",
  225. "Externer Stoereingang", "Frei" ],
  226. # 2 - WW_Betriebswerte1
  227. [ "-", "Automatik", "Desinfektion", "Nachladung", "Ferien",
  228. "Fehler Desinfektion", "Fehler Fuehler", "Fehler WW bleibt kalt",
  229. "Fehler Anode" ],
  230. # 3 - WW_Betriebswerte2
  231. [ "-", "Laden", "Manuell", "Nachladen", "Ausschaltoptimierung",
  232. "Einschaltoptimierung", "Tag", "Warm", "Vorrang" ],
  233. # 4 - Kessel_Betrieb
  234. [ "-", "Abgastest", "Betrieb 1.Stufe", "Kesselschutz",
  235. "Unter Betrieb", "Leistung frei", "Leistung hoch", "Betrieb 2.Stufe", "Frei" ],
  236. # 5 - WW_Pumpentyp
  237. [ "-", "Ladepumpe", "Zirkulationspumpe", "Absenkung Solar",
  238. "Frei", "Frei", "Frei", "Frei", "Frei" ],
  239. # 6 - Kessel_Fehler
  240. [ "-", "Brennerstoerung", "Kesselfuehler", "Zusatzfuehler", "Kessel bleibt kalt",
  241. "Abgasfuehler", "Abgas ueber Grenzwert", "Sicherungskette ausgeloest", "Externe Stoerung" ],
  242. # 7 - Alarmstatus
  243. [ "-", "Abgasfuehler", "02", "Kesselvorlauffuehler", "08",
  244. "Brenner", "20", "HK2-Vorlauffuehler", "80" ]
  245. );
  246. my @km271_arrays = (
  247. # 0 - CFG_Fernbedienung, CFG_WW_Vorrang, CFG_Warmwasser
  248. [ "Aus", "An" ],
  249. # 1 - CFG_Anzeige
  250. [ "Automatik", "Kessel", "Warmwasser", "Aussen" ],
  251. # 2 - CFG_Programm
  252. [ "Eigen", "Familie", "Frueh", "Spaet", "Vormittag", "Nachmittag",
  253. "Mittag", "Single", "Senior" ],
  254. # 3 - CFG_Sprache
  255. [ "DE", "FR", "IT", "NL", "EN", "PL" ],
  256. # 4 - CFG_Betriebsart
  257. [ "Nacht", "Tag", "Automatik" ],
  258. # 5 - CFG_Abgastemperaturschwelle
  259. [ "Aus","50","55","60","65","70","75","80","85","90","95","100","105",
  260. "110","115","120","125","130","135","140","145","150","155","160","165",
  261. "170","175","180","185","190","195","200","205","210","215","220","225",
  262. "230","235","240","245","250" ],
  263. # 6 - CFG_Absenkungsart
  264. [ "Abschalt","Reduziert","Raumhalt","Aussenhalt" ],
  265. # 7 - CFG_Heizsystem
  266. [ "Aus","Heizkoerper","-","Fussboden" ],
  267. # 8 - CFG_Sommer_ab
  268. [ "Sommer","10","11","12","13","14","15","16","17","18","19",
  269. "20","21","22","23","24","25","26","27","28","29","30","Winter" ],
  270. # 9 - CFG_Aufschalttemperatur
  271. [ "Aus","1","2","3","4","5","6","7","8","9","10" ],
  272. # 10 - Brenneransteuerung
  273. [ "Kessel aus", "1.Stufe an", "-", "-", "2.Stufe an bzw. Modulation frei" ],
  274. # 11 - CFG_Zirkulation
  275. [ "Aus","1","2","3","4","5","6","An" ],
  276. # 12 - CFG_Brennerart
  277. [ "1-stufig","2-stufig","Modulierend" ],
  278. # 13 - CFG_Gebaeudeart
  279. [ "Leicht","Mittel","Schwer" ]
  280. );
  281. # PRG_HK1_TimerXX, PRG_HK2_TimerXX
  282. my %km271_days = (
  283. 0x00 => "Mo",
  284. 0x20 => "Di",
  285. 0x40 => "Mi",
  286. 0x60 => "Do",
  287. 0x80 => "Fr",
  288. 0xa0 => "Sa",
  289. 0xc0 => "So"
  290. );
  291. my %km271_set_betriebsart = (
  292. "nacht" => 0,
  293. "tag" => 1,
  294. "automatik" => 2
  295. );
  296. # Used by set hk?_programm
  297. my %km271_set_programm = (
  298. "eigen" => 0,
  299. "familie" => 1,
  300. "frueh" => 2,
  301. "spaet" => 3,
  302. "vormittag" => 4,
  303. "nachmittag" => 5,
  304. "mittag" => 6,
  305. "single" => 7,
  306. "senior" => 8
  307. );
  308. # Used by set hk?_timer
  309. my %km271_set_day = (
  310. "mo" => 0x00,
  311. "di" => 0x20,
  312. "mi" => 0x40,
  313. "do" => 0x60,
  314. "fr" => 0x80,
  315. "sa" => 0xa0,
  316. "so" => 0xc0
  317. );
  318. # Used by get last_error
  319. my %km271_errormsg = (
  320. 0 => "Kein Fehler",
  321. 2 => "Aussenfuehler defekt",
  322. 3 => "HK1-Vorlauffuehler defekt",
  323. 4 => "HK2-Vorlauffuehler defekt",
  324. 8 => "Warmwasserfuehler defekt",
  325. 9 => "Warmwasser bleibt kalt",
  326. 10 => "Stoerung thermische Desinfektion",
  327. 11 => "HK1-Fernbedienung defekt",
  328. 12 => "HK2-Fernbedienung defekt",
  329. 15 => "Keine Kommunikation mit HK1-Fernbedienung",
  330. 16 => "Keine Kommunikation mit HK2-Fernbedienung",
  331. 20 => "Stoerung Brenner 1",
  332. 24 => "Keine Verbindung mit Kessel 1",
  333. 30 => "Interner Fehler Nr. 1",
  334. 31 => "Interner Fehler Nr. 2",
  335. 32 => "Interner Fehler Nr. 3",
  336. 33 => "Interner Fehler Nr. 4",
  337. 49 => "Kesselvorlauffuehler defekt",
  338. 50 => "Kesselzusatzfuehler defekt",
  339. 51 => "Kessel bleibt kalt",
  340. 52 => "Stoerung Brenner",
  341. 53 => "Stoerung Sicherheitskette",
  342. 54 => "Externe Stoerung Kessel",
  343. 55 => "Abgasfuehler defekt",
  344. 56 => "Abgasgrenze ueberschritten",
  345. 87 => "Ruecklauffuehler defekt",
  346. 92 => "RESET"
  347. );
  348. #####################################
  349. sub
  350. KM271_Initialize($)
  351. {
  352. my $hash = shift;
  353. require "$attr{global}{modpath}/FHEM/DevIo.pm";
  354. $hash->{ReadFn} = "KM271_Read";
  355. $hash->{ReadyFn} = "KM271_Ready";
  356. $hash->{DefFn} = "KM271_Define";
  357. $hash->{UndefFn} = "KM271_Undef";
  358. $hash->{SetFn} = "KM271_Set";
  359. $hash->{GetFn} = "KM271_Get";
  360. $hash->{AttrFn} = "KM271_Attr";
  361. $hash->{AttrList} = "do_not_notify:1,0 all_km271_events:1,0 ww_timermode:automatik,tag readingsFilter additionalNotify $readingFnAttributes";
  362. %km271_rev = ();
  363. foreach my $k (sort keys %km271_tr) { # Reverse map
  364. my $v = $km271_tr{$k};
  365. my ($addr, $b) = split("[:,]", $v);
  366. $km271_rev{$addr}{$b} = $k;
  367. }
  368. my $optionList = join(",", sort keys %km271_set_betriebsart);
  369. $km271_sets{"hk1_betriebsart"}{OPT} = ":$optionList";
  370. $km271_sets{"hk2_betriebsart"}{OPT} = ":$optionList";
  371. $km271_sets{"ww_betriebsart"}{OPT} = ":$optionList";
  372. $optionList = join(",", sort keys %km271_set_programm);
  373. $km271_sets{"hk1_programm"}{OPT} = ":$optionList";
  374. $km271_sets{"hk2_programm"}{OPT} = ":$optionList";
  375. }
  376. #####################################
  377. sub
  378. KM271_Define($$)
  379. {
  380. my ($hash, $def) = @_;
  381. my @a = split("[ \t][ \t]*", $def);
  382. return "wrong syntax: define <name> KM271 [devicename|none]" if(@a != 3);
  383. DevIo_CloseDev($hash);
  384. my $name = $a[0];
  385. my $dev = $a[2];
  386. if($dev eq "none") {
  387. Log3 $name, 2, "$name: KM271 device is none, commands will be echoed only";
  388. return undef;
  389. }
  390. $hash->{DeviceName} = $dev;
  391. my @b = ();
  392. $hash->{SENDBUFFER} = \@b;
  393. # Internal hash for storing actual timing parameter of heater, populated by "logmode" command
  394. my %c = ();
  395. $hash->{PRG_TIMER} = \%c;
  396. # Internal array for storing the last 4 error states and timestamps
  397. my @d = ([0,0], [0,0], [0,0], [0,0]);
  398. $hash->{ERR_STATE} = \@d;
  399. my $ret = DevIo_OpenDev($hash, 0, "KM271_DoInit");
  400. return $ret;
  401. }
  402. #####################################
  403. sub
  404. KM271_Undef($$)
  405. {
  406. my ($hash, $name) = @_;
  407. DevIo_CloseDev($hash);
  408. return undef;
  409. }
  410. #####################################
  411. sub
  412. KM271_Set($@)
  413. {
  414. my ($hash, @a) = @_;
  415. return "\"set KM271\" needs at least an argument" if(@a < 2);
  416. my $name = shift @a;
  417. if(!defined($km271_sets{$a[0]})) {
  418. my $msg = "";
  419. foreach my $para (sort keys %km271_sets) {
  420. $msg .= " $para" . $km271_sets{$para}{OPT};
  421. }
  422. return "Unknown argument $a[0], choose one of" . $msg;
  423. }
  424. my $cmd = $km271_sets{$a[0]}{SET};
  425. my ($err, $hr, $min, $sec, $fn);
  426. my ($val, $numeric_val);
  427. if($cmd =~ m/%/) {
  428. return "\"set KM271 $a[0]\" needs at least one parameter" if(@a < 2);
  429. $val = $a[1];
  430. $numeric_val = ($val =~ m/^-?[.0-9]+$/);
  431. }
  432. if($a[0] =~ m/^hk.*soll$/) {
  433. return "Argument must be numeric (between 10 and 30)" if(!$numeric_val || $val < 10 || $val > 30);
  434. $val *= 2;
  435. }
  436. elsif($a[0] =~ m/^ww.*soll$/) {
  437. return "Argument must be numeric (between 30 and 60)" if(!$numeric_val || $val < 30 || $val > 60);
  438. }
  439. elsif($a[0] =~ m/_betriebsart$/) {
  440. $val = $km271_set_betriebsart{$val};
  441. return "Unknown parameter for $a[0], use one of " .
  442. join(" ", sort keys %km271_set_betriebsart) if(!defined($val));
  443. }
  444. elsif($a[0] =~ m/_programm$/) {
  445. $val = $km271_set_programm{$val};
  446. return "Unknown parameter for $a[0], use one of " .
  447. join(" ", sort keys %km271_set_programm) if(!defined($val));
  448. }
  449. elsif($a[0] =~ m/^ww.*-till$/) { # WW on-till command
  450. ($err, $hr, $min, $sec, $fn) = GetTimeSpec($val);
  451. return $err if($err);
  452. my @lt = localtime;
  453. my $hms_till = sprintf("%02d:%02d:%02d", $hr, $min, $sec);
  454. my $hms_now = sprintf("%02d:%02d:%02d", $lt[2], $lt[1], $lt[0]);
  455. if($hms_now ge $hms_till) {
  456. Log3 $name, 1, "$name: ww_on-till won't switch as now $hms_now is later than till $hms_till";
  457. return "Won't switch as now is later than $hms_till";
  458. }
  459. my $tname = $name . "_ww_autoOff";
  460. CommandDelete(undef, $tname) if($defs{$tname});
  461. CommandDefine(undef, "$tname at $hms_till set $name ww_betriebsart nacht");
  462. $val = $km271_set_betriebsart{AttrVal($name, "ww_timermode", "tag")};
  463. }
  464. elsif($a[0] =~ m/^ww.*zirkulation$/) {
  465. return "Argument must be numeric (between 0 and 7)" if(!$numeric_val || $val < 0 || $val > 7);
  466. }
  467. elsif($a[0] =~ m/^hk.*aussenhalt_ab$/) {
  468. return "Argument must be numeric (between -20 and 10)" if(!$numeric_val || $val < -20 || $val > 10);
  469. $val += 256 if($val < 0);
  470. }
  471. elsif($a[0] eq 'sommer_ab') {
  472. return "Argument must be numeric (between 9 and 31)" if(!$numeric_val || $val < 9 || $val > 31);
  473. # Two updates needed, here additionally HK2
  474. push @{$hash->{SENDBUFFER}}, sprintf("080065%02x65656565", $val);
  475. }
  476. elsif($a[0] eq 'frost_ab') {
  477. return "Argument must be numeric (between -20 and 10)" if(!$numeric_val || $val < -20 || $val > 10);
  478. $val += 256 if($val < 0);
  479. # Two updates needed, here additionally HK2
  480. push @{$hash->{SENDBUFFER}}, sprintf("08316565656565%02x", $val);
  481. }
  482. elsif($a[0] eq 'urlaub') {
  483. return "Argument must be numeric (between 0 and 99)" if(!$numeric_val || $val < 0 || $val > 99);
  484. # Two updates needed, here additionally HK2
  485. push @{$hash->{SENDBUFFER}}, sprintf("1200656565%02x6565", $val);
  486. }
  487. elsif($a[0] =~ m/^hk.*timer$/) { # Timer calculation
  488. return "\"set KM271 $a[0]\" needs typically 5 parameters (position on-day on-time off-day off-time)" if(@a < 3);
  489. $val = $a[1];
  490. $numeric_val = ($val =~ m/^[0-9]+$/);
  491. # 42 slots for a timer, but each interval uses two of them (on and off)
  492. return "Position must be numeric (between 1 and 21)" if(!$numeric_val || $val < 1 || $val > 21);
  493. my $pos = $val;
  494. my $offval;
  495. if($a[2] eq "delete") {
  496. # Delete the interval
  497. $offval = "c290"; # Code for not used
  498. $val = $offval;
  499. } else {
  500. # Set interval: more arguments are needed
  501. return "\"set KM271 $a[0]\" needs at least 5 parameters (position day on-time day off-time)" if(@a < 6);
  502. my $offday = $km271_set_day{$a[4]};
  503. return "Unknown day, use one of " .
  504. join(" ", sort keys %km271_set_day) if(!defined($offday));
  505. # Time validation off-time
  506. ($err, $hr, $min, $sec, $fn) = GetTimeSpec($a[5]);
  507. return $err if($err);
  508. # Calculate off-day and -time (unit: 10 min) for heater
  509. $offval = sprintf("%02x%02x", $offday, int(($hr*60 + $min) / 10));
  510. my $onday = $km271_set_day{$a[2]};
  511. return "Unknown day, use one of " .
  512. join(" ", sort keys %km271_set_day) if(!defined($onday));
  513. # Time validation on-time
  514. ($err, $hr, $min, $sec, $fn) = GetTimeSpec($a[3]);
  515. return $err if($err);
  516. # Calculate on-day and time (unit: 10 min) for heater
  517. $val = sprintf("%02x%02x", $onday | 0x01, int(($hr*60 + $min) / 10));
  518. return "On- and off timepoints must not be identical" if(substr($val, 2, 2) eq substr($offval, 2, 2) && $onday == $offday);
  519. }
  520. # Calculate offsets for command and internal timer hash
  521. my $km271Timer = $hash->{PRG_TIMER};
  522. my $offset = int(($pos*2 + 1)/3)*7;
  523. my $keyoffset = $offset + ($a[0] =~ m/^hk1/ ? 0 : 15)*7;
  524. my $key = sprintf("01%02x", $keyoffset);
  525. # Are two updates needed (interval is spread over two lines)?
  526. if(($pos + 1) % 3 == 0) {
  527. my $key2 = sprintf("01%02x", $keyoffset + 7);
  528. return "Internal timer-hash is not populated, use logmode command and try again later"
  529. if(!defined($km271Timer->{$key}{0}) || !defined($km271Timer->{$key}{1}) || !defined($km271Timer->{$key2}{1}) || !defined($km271Timer->{$key2}{2}));
  530. # Check if update for key2 is needed
  531. if(defined($km271Timer->{$key2}{0}) && $km271Timer->{$key2}{0} eq $offval) {
  532. Log3 $name, 5, "$name: Update for second timer-part not needed";
  533. } else {
  534. # Update internal hash
  535. $km271Timer->{$key2}{0} = $offval;
  536. $offval .= $km271Timer->{$key2}{1} . $km271Timer->{$key2}{2};
  537. # Dirty trick: Changes of the timer are not notified by the heater, so internal notification is added after the colon
  538. $offval = sprintf("%02x%s:%s%s", $offset + 7, $offval, $key2, $offval);
  539. # Push first command
  540. push @{$hash->{SENDBUFFER}}, sprintf($cmd, $offval);
  541. }
  542. # Check if update for key is needed
  543. if(defined($km271Timer->{$key}{2}) && $km271Timer->{$key}{2} eq $val) {
  544. Log3 $name, 5, "$name: Update for first timer-part not needed";
  545. goto END_SET;
  546. } else {
  547. # Update internal hash
  548. $km271Timer->{$key}{2} = $val;
  549. }
  550. } else {
  551. # Only one update needed
  552. if($pos % 3 == 1) {
  553. return "Internal timer-hash is not populated, use logmode command and try again later" if(!defined($km271Timer->{$key}{2}));
  554. # Check if update for key is needed
  555. if(defined($km271Timer->{$key}{0}) && defined($km271Timer->{$key}{1}) && $km271Timer->{$key}{0} eq $val && $km271Timer->{$key}{1} eq $offval) {
  556. Log3 $name, 5, "$name: Update for timer not needed";
  557. goto END_SET;
  558. } else {
  559. # Update internal hash
  560. $km271Timer->{$key}{0} = $val;
  561. $km271Timer->{$key}{1} = $offval;
  562. }
  563. } else {
  564. return "Internal timer-hash is not populated, use logmode command and try again later" if(!defined($km271Timer->{$key}{0}));
  565. # Check if update for key is needed
  566. if(defined($km271Timer->{$key}{1}) && defined($km271Timer->{$key}{2}) && $km271Timer->{$key}{1} eq $val && $km271Timer->{$key}{2} eq $offval) {
  567. Log3 $name, 5, "$name: Update for timer not needed";
  568. goto END_SET;
  569. } else {
  570. # Update internal hash
  571. $km271Timer->{$key}{1} = $val;
  572. $km271Timer->{$key}{2} = $offval;
  573. }
  574. }
  575. }
  576. $val = $km271Timer->{$key}{0} . $km271Timer->{$key}{1} . $km271Timer->{$key}{2};
  577. # Dirty trick: Changes of the timer are not notified by the heater, so internal notification is added after the colon
  578. $val = sprintf("%02x%s:%s%s", $offset, $val, $key, $val);
  579. }
  580. push @{$hash->{SENDBUFFER}}, sprintf($cmd, $val, $val);
  581. END_SET:
  582. Log3 $name, 3, "$name: set " . join(" ", @a);
  583. if(!exists($hash->{WAITING}) && !exists($hash->{DATASENT})) {
  584. DevIo_DoSimpleRead($hash);
  585. DevIo_SimpleWrite($hash, "02", 1);
  586. }
  587. return undef;
  588. }
  589. #####################################
  590. sub
  591. KM271_Get($@)
  592. {
  593. my ($hash, @a) = @_;
  594. return "\"get KM271\" needs at least an argument" if(@a < 2);
  595. my $name = shift @a;
  596. my $para = shift @a;
  597. if(!defined($km271_gets{$para})) {
  598. my $msg = "";
  599. foreach my $p (sort keys %km271_gets) {
  600. $msg .= " $p" . $km271_gets{$p};
  601. }
  602. return "Unknown argument $para, choose one of" . $msg;
  603. }
  604. my $lastTimestamp = 0;
  605. if($para eq "l_fehler") {
  606. my $lastError = 0;
  607. foreach my $row (0..@{$hash->{ERR_STATE}}-1) {
  608. $lastError = ${$hash->{ERR_STATE}}[$row][0];
  609. last if($lastError);
  610. }
  611. my $errorMsg = $km271_errormsg{$lastError};
  612. $errorMsg = "Unbekannter Fehler" if(!defined($errorMsg));
  613. return sprintf("%02d: %s", $lastError, $errorMsg);
  614. }
  615. elsif($para eq "l_fehlerzeitpunkt") {
  616. foreach my $row (0..@{$hash->{ERR_STATE}}-1) {
  617. if(${$hash->{ERR_STATE}}[$row][0]) {
  618. $lastTimestamp = ${$hash->{ERR_STATE}}[$row][1];
  619. last;
  620. }
  621. }
  622. return $lastTimestamp;
  623. }
  624. elsif($para eq "l_fehleraktualisierung") {
  625. foreach my $row (0..@{$hash->{ERR_STATE}}-1) {
  626. my $lts = ${$hash->{ERR_STATE}}[$row][1];
  627. $lastTimestamp = $lts if($lts > $lastTimestamp);
  628. }
  629. return $lastTimestamp;
  630. }
  631. return undef;
  632. }
  633. #####################################
  634. # Called from the global loop, when the select for hash->{FD} reports data
  635. sub
  636. KM271_Read($)
  637. {
  638. my $hash = shift;
  639. my $name = $hash->{NAME};
  640. my ($data, $crc);
  641. my $buf = DevIo_SimpleRead($hash);
  642. return "" if(!defined($buf));
  643. $buf = unpack('H*', $buf);
  644. Log3 $name, 5, "$name: KM271RAW <$buf>";
  645. # Check, if we are waiting for a message from the heater
  646. if(exists($hash->{WAITING})) {
  647. # After timeout get out of waiting mode
  648. delete($hash->{WAITING}) if(time - $hash->{WAITING} > 2.5);
  649. } else {
  650. # Send data or waiting for acknowlegde
  651. if(@{$hash->{SENDBUFFER}} || $hash->{DATASENT}) {
  652. if($buf eq "10") {
  653. if($hash->{DATASENT}) {
  654. delete($hash->{DATASENT});
  655. delete($hash->{RETRYCOUNT});
  656. # Delete the command from the list
  657. shift @{$hash->{SENDBUFFER}};
  658. if($hash->{NOTIFY}) {
  659. $data = $hash->{NOTIFY};
  660. delete($hash->{NOTIFY});
  661. goto INTERNAL_NOTIFY; # Timer changes are not reflected by the heater
  662. }
  663. DevIo_SimpleWrite($hash, "02", 1) if(@{$hash->{SENDBUFFER}});
  664. } else {
  665. # Delete the command only after receiving ACK
  666. $data = shift @{$hash->{SENDBUFFER}};
  667. unshift @{$hash->{SENDBUFFER}}, $data;
  668. # Dirty trick: separate notify message after the colon
  669. my @dataList = split(":", $data);
  670. $data = $dataList[0];
  671. $data = KM271_encode($data);
  672. $data .= "1003";
  673. $crc = KM271_crc($data);
  674. $data .= $crc;
  675. $hash->{DATASENT} = $data;
  676. $hash->{RETRYCOUNT} = 0;
  677. if(@dataList > 1) {
  678. # Set notify message
  679. $hash->{NOTIFY} = $dataList[1];
  680. } else {
  681. delete($hash->{NOTIFY});
  682. }
  683. DevIo_SimpleWrite($hash, $data, 1); # Send the data
  684. }
  685. } else {
  686. if($hash->{DATASENT}) {
  687. if($buf eq "15") {
  688. Log3 $name, 2, "$name: NAK received"; # NACK from the KM271
  689. } else {
  690. Log3 $name, 2, "$name: Bogus data after sending packet <$buf>"; # Strange response from the KM271
  691. }
  692. # Start all over again
  693. if(++$hash->{RETRYCOUNT} > 3) {
  694. # Abort sending the actual command
  695. Log3 $name, 1, "$name: Sending <$hash->{DATASENT}> aborted and not successful!";
  696. shift @{$hash->{SENDBUFFER}};
  697. delete($hash->{RETRYCOUNT});
  698. } else {
  699. Log3 $name, 2, "$name: Sending attempt for <$hash->{DATASENT}> failed, retrying";
  700. }
  701. delete($hash->{DATASENT});
  702. delete($hash->{NOTIFY});
  703. DevIo_SimpleWrite($hash, "02", 1) if(@{$hash->{SENDBUFFER}});
  704. } else {
  705. DevIo_SimpleWrite($hash, "02", 1);
  706. }
  707. }
  708. } else {
  709. if($buf eq "02") { # KM271 Wants to send
  710. DevIo_SimpleWrite($hash, "10", 1); # We are ready
  711. $hash->{PARTIAL} = "";
  712. $hash->{WAITING} = time;
  713. }
  714. }
  715. return;
  716. }
  717. $hash->{PARTIAL} .= $buf;
  718. return if($hash->{PARTIAL} !~ m/^(.*)1003(..)$/);
  719. ($data, $crc) = ($1, $2);
  720. $hash->{PARTIAL} = "";
  721. delete($hash->{WAITING});
  722. if(KM271_crc($data . "1003") ne $crc) {
  723. Log3 $name, 1, "$name: Wrong CRC in datapacket <$crc>";
  724. DevIo_SimpleWrite($hash, "15", 1); # NAK
  725. DevIo_SimpleWrite($hash, "02", 1) if(@{$hash->{SENDBUFFER}}); # Want to send
  726. return;
  727. }
  728. DevIo_SimpleWrite($hash, "10", 1); # ACK, Data received ok
  729. $data = KM271_decode($data);
  730. INTERNAL_NOTIFY:
  731. DevIo_SimpleWrite($hash, "02", 1) if(@{$hash->{SENDBUFFER}}); # Want to send
  732. if($data !~ m/^(....)(.*)/) {
  733. Log3 $name, 2, "$name: Bogus message <$data>";
  734. return;
  735. }
  736. ######################################
  737. # Analyze the data
  738. my ($fn, $arg) = ($1, $2);
  739. my $msghash = $km271_rev{$fn};
  740. my $all_events = AttrVal($name, 'all_km271_events', '');
  741. my $notifyFilter = AttrVal($name, 'additionalNotify', '');
  742. if($msghash) {
  743. my $km271Timer = $hash->{PRG_TIMER};
  744. foreach my $off (keys %{$msghash}) {
  745. my $key = $msghash->{$off};
  746. my $val = hex(substr($arg, $off*2, 2));
  747. my $ntfy = 1;
  748. my @postprocessing = split(",", $km271_tr{$key});
  749. shift @postprocessing;
  750. while(@postprocessing) {
  751. my ($f,$farg) = split(":", shift @postprocessing);
  752. if($f eq "d") { $val /= $farg; }
  753. elsif($f eq "p") { $val += $farg; }
  754. elsif($f eq "ne") { $ntfy = ($notifyFilter && $key =~ m/$notifyFilter/s) ? 1 : $all_events; }
  755. elsif($f eq "s") { $val = $val-256 if($val > 127); }
  756. elsif($f eq "bf") { $val = KM271_setbits($val, $farg); }
  757. elsif($f eq "a") { $val = $km271_arrays[$farg][$val]; }
  758. elsif($f eq "mb") {
  759. $val += ReadingsVal($name, $key."1", 0) * 256;
  760. $val += ReadingsVal($name, $key."2", 0) * 65536 if($farg == 3); }
  761. elsif($f eq "t") { $val = sprintf("%s | %s | %s", KM271_setprg($val, hex(substr($arg, ($off+1)*2, 2)))
  762. , KM271_setprg(hex(substr($arg, ($off+2)*2, 2)), hex(substr($arg, ($off+3)*2, 2)))
  763. , KM271_setprg(hex(substr($arg, ($off+4)*2, 2)), hex(substr($arg, ($off+5)*2, 2))));
  764. # Fill internal timer hash
  765. $km271Timer->{$fn}{0} = substr($arg, 0, 4);
  766. $km271Timer->{$fn}{1} = substr($arg, 4, 4);
  767. $km271Timer->{$fn}{2} = substr($arg, 8, 4); }
  768. elsif($f eq "eh") { $val = KM271_seterror($hash->{ERR_STATE}, substr($key, -1) -1, $arg); }
  769. }
  770. $key = ucfirst($key); # Hack to match the original and the fake reading
  771. KM271_SetReading($hash, $key, $val, $ntfy);
  772. }
  773. } elsif($all_events) {
  774. if($fn eq "0400") {
  775. KM271_SetReading($hash, "NoData", $arg, 0);
  776. } else {
  777. KM271_SetReading($hash, "UNKNOWN_$fn", $data, 1);
  778. }
  779. }
  780. }
  781. #####################################
  782. sub
  783. KM271_Ready($)
  784. {
  785. my $hash = shift;
  786. return DevIo_OpenDev($hash, 1, undef) if($hash->{STATE} eq "disconnected");
  787. # This is relevant for windows/USB only
  788. my $po = $hash->{USBDev};
  789. my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags);
  790. ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags) = $po->status if($po);
  791. return ($InBytes>0);
  792. }
  793. #####################################
  794. sub
  795. KM271_Attr(@)
  796. {
  797. my ($cmd, $name, $attrName, $attrVal) = @_;
  798. # $cmd can be "del" or "set"
  799. # $name is device name
  800. # attrName and attrVal are Attribute name and value
  801. if($cmd eq 'set') {
  802. if($attrName eq 'ww_timermode') {
  803. return "Invalid $attrName <$attrVal>" if(!$km271_set_betriebsart{$attrVal});
  804. } elsif($attrName eq 'readingsFilter' || $attrName eq 'additionalNotify') {
  805. eval {qr/$attrVal/};
  806. if($@) {
  807. Log3 $name, 1, "$name: Invalid Regex for $attrName <$attrVal> '$@'";
  808. return "Invalid Regex for $attrName <$attrVal>";
  809. }
  810. }
  811. }
  812. return undef;
  813. }
  814. #####################################
  815. sub
  816. KM271_DoInit($)
  817. {
  818. my $hash = shift;
  819. push @{$hash->{SENDBUFFER}}, $km271_sets{"logmode"}{SET};
  820. DevIo_DoSimpleRead($hash);
  821. DevIo_SimpleWrite($hash, "02", 1); # STX
  822. return undef;
  823. }
  824. #####################################
  825. sub
  826. KM271_setbits($$)
  827. {
  828. my ($val, $arridx) = @_;
  829. my @ret;
  830. for(my $idx = 1; $idx <= 8; $idx++) {
  831. push(@ret, $km271_bitarrays[$arridx][$idx]) if($val & (1<<($idx-1)));
  832. }
  833. return $km271_bitarrays[$arridx][0] if(!int(@ret));
  834. return join(",", @ret);
  835. }
  836. #####################################
  837. sub
  838. KM271_setprg($$)
  839. {
  840. my ($val, $time) = @_;
  841. my $ret = "-";
  842. my $switch = $val & 0x0f;
  843. if($switch < 2) {
  844. $ret = $switch == 0 ? "Aus: " : "An: ";
  845. $ret .= $km271_days{$val & 0xf0};
  846. $ret .= sprintf(" %02d:%02d", int($time / 6), ($time % 6)*10);
  847. }
  848. return $ret;
  849. }
  850. #####################################
  851. sub
  852. KM271_seterror($$$)
  853. {
  854. my ($errState, $slot, $val) = @_;
  855. my $error = hex(substr($val, 0, 2));
  856. my $ret = "Kein Fehler";
  857. my $timestamp = 0;
  858. if($error) {
  859. my $hr = hex(substr($val, 2, 2));
  860. my $mi = hex(substr($val, 4, 2));
  861. my $days = hex(substr($val, 6, 2));
  862. $ret = sprintf("Code %02d (+): %02d:%02dUhr vor ", $error, $hr, $mi);
  863. my $hr2 = hex(substr($val, 8, 2));
  864. # get actual time
  865. $timestamp = int(time);
  866. my @ts = localtime($timestamp);
  867. if($hr2 == 0xff) {
  868. $ret .= sprintf("%d Tagen | Fehler noch offen", $days);
  869. # time calculation without DateTime module
  870. $timestamp -= (($ts[2] - $hr)*60 + $ts[1] - $mi)*60 + $days*86400;
  871. } else {
  872. $mi = hex(substr($val, 10, 2));
  873. my $days2 = hex(substr($val, 12, 2));
  874. $ret .= sprintf("%d Tagen | (-): %02d:%02dUhr vor %d Tagen", $days + $days2, $hr2, $mi, $days2);
  875. # time calculation without DateTime module
  876. $timestamp -= (($ts[2] - $hr2)*60 + $ts[1] - $mi)*60 + $days2*86400;
  877. $error = 0;
  878. }
  879. }
  880. if($slot < 4 && $slot >= 0) {
  881. $errState->[$slot][0] = $error;
  882. $errState->[$slot][1] = $timestamp;
  883. }
  884. return $ret;
  885. }
  886. #####################################
  887. # Replacement for regular expression - s/10/1010/g - which works wrong for "0101"
  888. sub
  889. KM271_encode($)
  890. {
  891. my $in = shift;
  892. my $out = '';
  893. foreach my $a (split("", pack('H*', $in))) {
  894. my $c = sprintf("%02x", ord($a));
  895. $c =~ s/10/1010/g;
  896. $out .= $c;
  897. }
  898. return $out;
  899. }
  900. #####################################
  901. # Replacement for regular expression - s/1010/10/g - which works wrong for "010101"
  902. sub
  903. KM271_decode($)
  904. {
  905. my $in = shift;
  906. my $out = '';
  907. my $flag = 0;
  908. foreach my $a (split("", pack('H*', $in))) {
  909. my $c = sprintf("%02x", ord($a));
  910. if($c eq "10") {
  911. if($flag) {
  912. $flag = 0;
  913. $c = '';
  914. } else {
  915. $flag = 1;
  916. }
  917. } else {
  918. $flag = 0;
  919. }
  920. $out .= $c;
  921. }
  922. return $out;
  923. }
  924. #####################################
  925. sub
  926. KM271_crc($)
  927. {
  928. my $in = shift;
  929. my $x = 0;
  930. foreach my $a (split("", pack('H*', $in))) {
  931. $x ^= ord($a);
  932. }
  933. return sprintf("%02x", $x);
  934. }
  935. #####################################
  936. sub
  937. KM271_SetReading($$$$)
  938. {
  939. my ($hash, $key, $val, $ntfy) = @_;
  940. my $name = $hash->{NAME};
  941. my $filter = AttrVal($name, 'readingsFilter', '');
  942. return if($filter && $key !~ m/$filter/s);
  943. Log3 $name, 4, "$name: $key $val" if($key ne 'NoData');
  944. readingsSingleUpdate($hash, $key, $val, $ntfy);
  945. }
  946. 1;
  947. =pod
  948. =item helper
  949. =item summary Interface for Buderus Logamatic 2105/2107 heating controller
  950. =item summary_DE Anbindung für Buderus Logamatic 2105/2107 Heizungssteuerung
  951. =begin html
  952. <a name="KM271"></a>
  953. <h3>KM271</h3>
  954. <ul>
  955. KM271 is the name of the communication device for the Buderus Logamatic 2105
  956. or 2107 heating controller. It is connected via a serial line to the fhem
  957. computer. The fhem module sets the communication device into log-mode, which
  958. then will generate an event on change of the inner parameters. There are
  959. about 20.000 events a day, the FHEM module ignores about 90% of them, if the
  960. <a href="#all_km271_events">all_km271_events</a> attribute is not set.<br>
  961. <br><br>
  962. Note: this module requires the Device::SerialPort or Win32::SerialPort module.
  963. <br><br>
  964. <a name="KM271define"></a>
  965. <b>Define</b>
  966. <ul>
  967. <code>define &lt;name&gt; KM271 &lt;serial-device-name&gt;</code>
  968. <br><br>
  969. Example:
  970. <ul>
  971. <code>define KM271 KM271 /dev tyS0@2400</code><br>
  972. </ul>
  973. </ul>
  974. <br>
  975. <a name="KM271set"></a>
  976. <b>Set</b>
  977. <ul>
  978. <code>set KM271 &lt;param&gt; [&lt;value&gt; [&lt;values&gt;]]</code><br><br>
  979. where param is one of:
  980. <ul>
  981. <li>hk1_tagsoll &lt;temp&gt;<br>
  982. sets the by day temperature for heating circuit 1<br>
  983. 0.5 celsius resolution - temperature between 10 and 30 celsius</li>
  984. <li>hk2_tagsoll &lt;temp&gt;<br>
  985. sets the by day temperature for heating circuit 2<br>
  986. (see above)</li>
  987. <li>hk1_nachtsoll &lt;temp&gt;<br>
  988. sets the by night temperature for heating circuit 1<br>
  989. (see above)</li>
  990. <li>hk2_nachtsoll &lt;temp&gt;<br>
  991. sets the by night temperature for heating circuit 2<br>
  992. (see above)</li>
  993. <li>hk1_urlaubsoll &lt;temp&gt;<br>
  994. sets the temperature during holiday mode for heating circuit 1<br>
  995. (see above)</li>
  996. <li>hk2_urlaubsoll &lt;temp&gt;<br>
  997. sets the temperature during holiday mode for heating circuit 2<br>
  998. (see above)</li>
  999. <li>hk1_aussenhalt_ab &lt;temp&gt;<br>
  1000. sets the threshold for working mode Aussenhalt for heating circuit 1<br>
  1001. 1.0 celsius resolution - temperature between -20 and 10 celsius</li>
  1002. <li>hk2_aussenhalt_ab &lt;temp&gt;<br>
  1003. sets the threshold for working mode Aussenhalt for heating circuit 2<br>
  1004. (see above)</li>
  1005. <li>hk1_betriebsart [automatik|nacht|tag]<br>
  1006. sets the working mode for heating circuit 1<br>
  1007. <ul>
  1008. <li>automatik: the timer program is active and the summer configuration is in effect</li>
  1009. <li>nacht: manual by night working mode, no timer program is in effect</li>
  1010. <li>tag: manual by day working mode, no timer program is in effect</li>
  1011. </ul></li>
  1012. <li>hk2_betriebsart [automatik|nacht|tag]<br>
  1013. sets the working mode for heating circuit 2<br>
  1014. (see above)</li>
  1015. <li>ww_soll &lt;temp&gt;<br>
  1016. sets the hot water temperature<br>
  1017. 1.0 celsius resolution - temperature between 30 and 60 celsius</li>
  1018. <li>ww_betriebsart [automatik|nacht|tag]<br>
  1019. sets the working mode for hot water<br>
  1020. <ul>
  1021. <li>automatik: hot water production according to the working modes of both heating circuits</li>
  1022. <li>nacht: no hot water at all</li>
  1023. <li>tag: manual permanent hot water</li>
  1024. </ul></li>
  1025. <li>ww_on-till [localtime]<br>
  1026. start hot water production till the given time is reached<br>
  1027. localtime must have the format HH:MM[:SS]<br>
  1028. ww_betriebsart is set according to the attribut ww_timermode. For switching-off hot water a single one-time at command is automatically generated which will set ww_betriebsart back to nacht</li>
  1029. <li>ww_zirkulation [count]<br>
  1030. count pumping phases for hot water circulation per hour<br>
  1031. count must be between 0 and 7 with special meaning for<br>
  1032. <ul>
  1033. <li>0: no circulation at all</li>
  1034. <li>7: circulation is always on</li>
  1035. </ul></li>
  1036. <li>sommer_ab &lt;temp&gt;<br>
  1037. temp defines the threshold for switching between summer or winter mode of the heater<br>
  1038. 1.0 celsius resolution - temp must be between 9 and 31 with special meaning for<br>
  1039. <ul>
  1040. <li> 9: fixed summer mode (only hot water and frost protection)</li>
  1041. <li>31: fixed winter mode</li>
  1042. </ul></li>
  1043. <li>frost_ab &lt;temp&gt;<br>
  1044. temp defines the threshold for activation of frost protection of the heater<br>
  1045. 1.0 celsius resolution - temp must be between -20 and 10 celsius</li>
  1046. <li>urlaub [count]<br>
  1047. sets the duration of the holiday mode to count days<br>
  1048. count must be between 0 and 99 with special meaning for<br>
  1049. <ul>
  1050. <li> 0: holiday mode is deactivated</li>
  1051. </ul></li>
  1052. <li>hk1_programm [eigen|familie|frueh|spaet|vormittag|nachmittag|mittag|single|senior]<br>
  1053. sets the timer program for heating circuit 1<br>
  1054. <ul>
  1055. <li>eigen: the custom program defined by the user (see below) is used</li>
  1056. <li>all others: predefined programs from Buderus for various situations (see Buderus manual for details)</li>
  1057. </ul></li>
  1058. <li>hk2_programm [eigen|familie|frueh|spaet|vormittag|nachmittag|mittag|single|senior]<br>
  1059. sets the timer program for heating circuit 2<br>
  1060. (see above)</li>
  1061. <li>hk1_timer [&lt;position&gt; delete|&lt;position&gt; &lt;on-day&gt; &lt;on-time&gt; &lt;off-day&gt; &lt;off-time&gt;]<br>
  1062. sets (or deactivates) a by day working mode time interval for the custom program of heating circuit 1<br>
  1063. <ul>
  1064. <li>position: addresses a slot of the custom timer program and must be between 1 and 21<br>
  1065. The slot will be set to the interval specified by the following on- and off-timepoints or is deactivated when the next argument is <b>delete</b>.</li>
  1066. <li>on-day: first part of the on-timepoint<br>
  1067. valid arguments are [mo|di|mi|do|fr|sa|so]</li>
  1068. <li>on-time: second part of the on-timepoint<br>
  1069. valid arguments have the format HH:MM (supported resolution: 10 min)</li>
  1070. <li>off-day: first part of the off-timepoint<br>
  1071. (see above)</li>
  1072. <li>off-time: second part of the off-timepoint<br>
  1073. valid arguments have the format HH:MM (supported resolution: 10 min)</li>
  1074. </ul>
  1075. As the on-timepoint is reached, the heating circuit is switched to by day working mode and when the off-timepoint is attained, the circuit falls back to by night working mode.
  1076. A program can be build up by chaining up to 21 of these intervals. They are ordered by the position argument. There's no behind the scene magic that will automatically consolidate the list.
  1077. The consistency of the program is in the responsibility of the user.
  1078. <br><br>
  1079. Example:
  1080. <ul>
  1081. <code>set KM271 hk1_timer 1 mo 06:30 mo 08:20</code><br>
  1082. </ul><br>
  1083. This will toogle the by day working mode every Monday at 6:30 and will fall back to by night working mode at 8:20 the same day.</li>
  1084. <li>hk2_timer [&lt;position&gt; delete|&lt;position&gt; &lt;on-day&gt; &lt;on-time&gt; &lt;off-day&gt; &lt;off-time&gt;]<br>
  1085. sets (or deactivates) a by day working mode time interval for the custom program of heating circuit 2<br>
  1086. (see above)</li>
  1087. <li>logmode<br>set to logmode / request all readings again</li>
  1088. </ul>
  1089. </ul>
  1090. <br>
  1091. <a name="KM271get"></a>
  1092. <b>Get</b>
  1093. <ul>
  1094. <code>get KM271 &lt;param&gt;</code><br><br>
  1095. where param is one of:
  1096. <ul>
  1097. <li>l_fehler<br>
  1098. gets the latest active error (code and message)</li>
  1099. <li>l_fehlerzeitpunkt<br>
  1100. gets the timestamp, when the latest active error occured (format of Perl-function 'time')</li>
  1101. <li>l_fehleraktualisierung<br>
  1102. gets the timestamp, of the latest update of an error status (format of Perl-function 'time')</li>
  1103. </ul>
  1104. </ul>
  1105. <br>
  1106. <a name="KM271attr"></a>
  1107. <b>Attributes</b>
  1108. <ul>
  1109. <li><a href="#do_not_notify">do_not_notify</a></li>
  1110. <a name="all_km271_events"></a>
  1111. <li>all_km271_events<br>
  1112. If this attribute is set to 1, do not ignore following events:<br>
  1113. HK1_Vorlaufisttemperatur, HK1_Mischerstellung, HK2_Vorlaufisttemperatur, HK2_Mischerstellung,
  1114. Kessel_Vorlaufisttemperatur, Kessel_Integral, Kessel_Integral1<br>
  1115. These events account for ca. 92% of all events.<br>
  1116. All UNKNOWN events are ignored too, most of them were only seen
  1117. directly after setting the device into logmode.
  1118. </li>
  1119. <a name="ww_timermode"></a>
  1120. <li>ww_timermode [automatik|tag]<br>
  1121. Defines the working mode for the ww_on-till command (default is tag).<br>
  1122. ww_on-till will set the ww_betriebsart of the heater according to this attribute.
  1123. </li>
  1124. <a name="readingsFilter"></a>
  1125. <li>readingsFilter<br>
  1126. Regular expression for selection of desired readings.<br>
  1127. Only readings which will match the regular expression will be used. All other readings are
  1128. suppressed in the device and even in the logfile.
  1129. </li>
  1130. <a name="additionalNotify"></a>
  1131. <li>additionalNotify<br>
  1132. Regular expression for activation of notify for readings with normally suppressed events.<br>
  1133. Useful for *_Vorlaufisttemperatur readings if notification should be activated only for a specific reading
  1134. and not for all like all_km271_events.
  1135. </li>
  1136. </ul>
  1137. <br>
  1138. <a name="KM271events"></a>
  1139. <b>Generated events:</b>
  1140. <ul>
  1141. <li>Abgastemperatur</li>
  1142. <li>Aussentemperatur</li>
  1143. <li>Aussentemperatur_gedaempft</li>
  1144. <li>Brenner_Ansteuerung</li>
  1145. <li>Brenner_Ausschalttemperatur</li>
  1146. <li>Brenner_Einschalttemperatur</li>
  1147. <li>Brenner_Laufzeit1_Minuten2</li>
  1148. <li>Brenner_Laufzeit1_Minuten1</li>
  1149. <li>Brenner_Laufzeit1_Minuten</li>
  1150. <li>Brenner_Laufzeit2_Minuten2</li>
  1151. <li>Brenner_Laufzeit2_Minuten1</li>
  1152. <li>Brenner_Laufzeit2_Minuten</li>
  1153. <li>Brenner_Mod_Stellglied</li>
  1154. <li>ERR_Fehlerspeicher1</li>
  1155. <li>ERR_Fehlerspeicher2</li>
  1156. <li>ERR_Fehlerspeicher3</li>
  1157. <li>ERR_Fehlerspeicher4</li>
  1158. <li>ERR_Alarmstatus</li>
  1159. <li>HK1_Ausschaltoptimierung</li>
  1160. <li>HK1_Aussenhalt_ab</li>
  1161. <li>HK1_Betriebswerte1</li>
  1162. <li>HK1_Betriebswerte2</li>
  1163. <li>HK1_Einschaltoptimierung</li>
  1164. <li>HK1_Heizkennlinie_10_Grad</li>
  1165. <li>HK1_Heizkennlinie_-10_Grad</li>
  1166. <li>HK1_Heizkennlinie_0_Grad</li>
  1167. <li>HK1_Mischerstellung</li>
  1168. <li>HK1_Pumpe</li>
  1169. <li>HK1_Raumisttemperatur</li>
  1170. <li>HK1_Raumsolltemperatur</li>
  1171. <li>HK1_Vorlaufisttemperatur</li>
  1172. <li>HK1_Vorlaufsolltemperatur</li>
  1173. <li>HK2_Ausschaltoptimierung</li>
  1174. <li>HK2_Aussenhalt_ab</li>
  1175. <li>HK2_Betriebswerte1</li>
  1176. <li>HK2_Betriebswerte2</li>
  1177. <li>HK2_Einschaltoptimierung</li>
  1178. <li>HK2_Heizkennlinie_10_Grad</li>
  1179. <li>HK2_Heizkennlinie_-10_Grad</li>
  1180. <li>HK2_Heizkennlinie_0_Grad</li>
  1181. <li>HK2_Mischerstellung</li>
  1182. <li>HK2_Pumpe</li>
  1183. <li>HK2_Raumisttemperatur</li>
  1184. <li>HK2_Raumsolltemperatur</li>
  1185. <li>HK2_Vorlaufisttemperatur</li>
  1186. <li>HK2_Vorlaufsolltemperatur</li>
  1187. <li>Kessel_Betrieb</li>
  1188. <li>Kessel_Fehler</li>
  1189. <li>Kessel_Integral</li>
  1190. <li>Kessel_Integral1</li>
  1191. <li>Kessel_Vorlaufisttemperatur</li>
  1192. <li>Kessel_Vorlaufsolltemperatur</li>
  1193. <li>Modulkennung</li>
  1194. <li>NoData</li>
  1195. <li>Versionsnummer_NK</li>
  1196. <li>Versionsnummer_VK</li>
  1197. <li>WW_Betriebswerte1</li>
  1198. <li>WW_Betriebswerte2</li>
  1199. <li>WW_Einschaltoptimierung</li>
  1200. <li>WW_Isttemperatur</li>
  1201. <li>WW_Pumpentyp</li>
  1202. <li>WW_Solltemperatur</li>
  1203. </ul>
  1204. <br>
  1205. As I cannot explain all the values, I logged data for a period and plotted
  1206. each received value in the following logs:
  1207. <ul>
  1208. <li><a href="km271/km271_Aussentemperatur.png">Aussentemperatur</a></li>
  1209. <li><a href="km271/km271_Betriebswerte.png">Betriebswerte</a></li>
  1210. <li><a href="km271/km271_Brenneransteuerung.png">Brenneransteuerung</a></li>
  1211. <li><a href="km271/km271_Brennerlaufzeit.png">Brennerlaufzeit</a></li>
  1212. <li><a href="km271/km271_Brennerschalttemperatur.png">Brennerschalttemperatur</a></li>
  1213. <li><a href="km271/km271_Heizkennlinie.png">Heizkennlinie</a></li>
  1214. <li><a href="km271/km271_Kesselbetrieb.png">Kesselbetrieb</a></li>
  1215. <li><a href="km271/km271_Kesselintegral.png">Kesselintegral</a></li>
  1216. <li><a href="km271/km271_Ladepumpe.png">Ladepumpe</a></li>
  1217. <li><a href="km271/km271_Raumsolltemperatur_HK1.png">Raumsolltemperatur_HK1</a></li>
  1218. <li><a href="km271/km271_Vorlauftemperatur.png">Vorlauftemperatur</a></li>
  1219. <li><a href="km271/km271_Warmwasser.png">Warmwasser</a></li>
  1220. </ul>
  1221. All of these events are reported directly after initialization (or after
  1222. requesting logmode), along with some 60 configuration records (6byte long
  1223. each). Most parameters from these records are reverse engeneered, they
  1224. all start with CFG_ for configuration and PRG_ for timer program information.
  1225. </ul>
  1226. =end html
  1227. =cut