36_Vallox.pm 52 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322
  1. ###############################################################
  2. # $Id: 36_Vallox.pm 14590 2017-06-28 20:56:14Z Skjall $
  3. #
  4. # @File 36_Vallox.pm
  5. #
  6. # @Author Skjall
  7. # @Created 21.07.2016 10:18:23
  8. # @Version 1.4.0
  9. #
  10. # The modul reads and writes parameters via RS485 from and to a Vallox
  11. # ventilation bus.
  12. #
  13. # This module was made possible by Heinz from mysensors Community
  14. # (https://forum.mysensors.org/user/heinz). His insights to the
  15. # Vallox bus were nessecary to make this script. - Thanks!
  16. #
  17. # This script 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. # The GNU General Public License can be found at
  23. # http://www.gnu.org/copyleft/gpl.html.
  24. # A copy is found in the textfile GPL.txt and important notices to the license
  25. # from the author is found in LICENSE.txt distributed with these scripts.
  26. #
  27. # This script is distributed in the hope that it will be useful,
  28. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  29. # MERCHANTABILITY || FITNESS FOR A PARTICULAR PURPOSE. See the
  30. # GNU General Public License for more details.
  31. #
  32. # This copyright notice MUST APPEAR in all copies of the script!
  33. #
  34. ################################################################
  35. ##############################################
  36. package main;
  37. use strict;
  38. use warnings;
  39. ##############################################
  40. # Global Variables
  41. ##############################################
  42. my %Vallox_datatypes;
  43. my %Vallox_datatypesReverse;
  44. ##################################
  45. # List of Datatyps of the Vallox bus
  46. ##################################
  47. my %Vallox_datatypes_base = (
  48. "06" => "FanSpeed-Relays",
  49. "07" => "MultiPurpose1",
  50. "08" => "MultiPurpose2",
  51. "29" => "FanSpeed",
  52. "2A" => "Humidity",
  53. "2B" => "CO2High",
  54. "2C" => "CO2Low",
  55. "2D" => "MachineInstalledCO2Sensors",
  56. "2E" => "CurrentVoltageIncomingOnMachine",
  57. "2F" => "HumiditySensor1",
  58. "30" => "HumiditySensor2",
  59. "32" => "TempOutside",
  60. "33" => "TempExhaust",
  61. "34" => "TempInside",
  62. "35" => "TempIncoming",
  63. "36" => "LastSystemFault",
  64. "55" => "PostHeatingOnCounter",
  65. "56" => "PostHeatingOffTime",
  66. "57" => "PostHeatingTargetValue",
  67. "6C" => "Flags1",
  68. "6D" => "Flags2",
  69. "6E" => "Flags3",
  70. "6F" => "Flags4",
  71. "70" => "Flags5",
  72. "71" => "Flags6",
  73. "79" => "FireplaceBoosterCountdownMinutes",
  74. "8F" => "ResumeBus",
  75. "91" => "SuspendBusForCO2Communication",
  76. "A3" => "Select",
  77. "A4" => "HeatingSetPoint",
  78. "A5" => "FanSpeedMax",
  79. "A6" => "ServiceReminderMonths",
  80. "A7" => "PreheatingSetPoint",
  81. "A8" => "InputFanStopTemperatureThreshold",
  82. "A9" => "FanSpeedMin",
  83. "AA" => "Program",
  84. "AB" => "MaintencanceCountdownMonths",
  85. "AE" => "BasicHumidityLevel",
  86. "AF" => "HeatRecoveryCellBypassSetpointTemperature",
  87. "B0" => "DCFanInputAdjustment",
  88. "B1" => "DCFanOutputAdjustment",
  89. "B2" => "CellDefrostingSetpointTemperature",
  90. "B3" => "CO2SetPointUpper",
  91. "B4" => "CO2SetPointLower",
  92. "B5" => "Program2",
  93. "C0" => "Initial1",
  94. "C6" => "Initial2",
  95. "C7" => "Initial3",
  96. "C8" => "Initial4",
  97. "C9" => "Initial5",
  98. );
  99. my %Vallox_datatypesReverse_base = reverse %Vallox_datatypes_base;
  100. my %Vallox_datatypes_legacy1 = (
  101. "49" => "Legacy49", # Unknown legacy Reading
  102. "4A" => "Legacy4A", # Unknown legacy Reading
  103. "4C" => "Legacy4C", # Unknown legacy Reading
  104. "53" => "Legacy53", # Unknown legacy Reading
  105. "54" => "Legacy54", # Unknown legacy Reading
  106. "58" => "Legacy58", # Unknown legacy Reading
  107. "5A" => "Legacy5A", # Unknown legacy Reading
  108. "5B" => "Legacy5B", # Unknown legacy Reading
  109. "5C" => "Legacy5C", # Unknown legacy Reading
  110. );
  111. my %Vallox_datatypesReverse_legacy1 = reverse %Vallox_datatypes_legacy1;
  112. ##################################
  113. # Mapping of the fan speeds
  114. ##################################
  115. my %Vallox_levelTable = (
  116. "01" => "1",
  117. "03" => "2",
  118. "07" => "3",
  119. "0F" => "4",
  120. "1F" => "5",
  121. "3F" => "6",
  122. "7F" => "7",
  123. "FF" => "8",
  124. );
  125. my %Vallox_levelTableReverse = reverse %Vallox_levelTable;
  126. ##################################
  127. # Mapping of the temperatures
  128. ##################################
  129. my %Vallox_temperatureTable = (
  130. "00" => "-74", "01" => "-70", "02" => "-66", "03" => "-62", "04" => "-59", "05" => "-56", "06" => "-54", "07" => "-52",
  131. "08" => "-50", "09" => "-48", "0A" => "-47", "0B" => "-46", "0C" => "-44", "0D" => "-43", "0E" => "-42", "0F" => "-41",
  132. "10" => "-40", "11" => "-39", "12" => "-38", "13" => "-37", "14" => "-36", "15" => "-35", "16" => "-34", "17" => "-33",
  133. "18" => "-33", "19" => "-32", "1A" => "-31", "1B" => "-30", "1C" => "-30", "1D" => "-29", "1E" => "-28", "1F" => "-28",
  134. "20" => "-27", "21" => "-27", "22" => "-26", "23" => "-25", "24" => "-25", "25" => "-24", "26" => "-24", "27" => "-23",
  135. "28" => "-23", "29" => "-22", "2A" => "-22", "2B" => "-21", "2C" => "-21", "2D" => "-20", "2E" => "-20", "2F" => "-19",
  136. "30" => "-19", "31" => "-19", "32" => "-18", "33" => "-18", "34" => "-17", "35" => "-17", "36" => "-16", "37" => "-16",
  137. "38" => "-16", "39" => "-15", "3A" => "-15", "3B" => "-14", "3C" => "-14", "3D" => "-14", "3E" => "-13", "3F" => "-13",
  138. "40" => "-12", "41" => "-12", "42" => "-12", "43" => "-11", "44" => "-11", "45" => "-11", "46" => "-10", "47" => "-10",
  139. "48" => "-9", "49" => "-9", "4A" => "-9", "4B" => "-8", "4C" => "-8", "4D" => "-8", "4E" => "-7", "4F" => "-7",
  140. "50" => "-7", "51" => "-6", "52" => "-6", "53" => "-6", "54" => "-5", "55" => "-5", "56" => "-5", "57" => "-4",
  141. "58" => "-4", "59" => "-4", "5A" => "-3", "5B" => "-3", "5C" => "-3", "5D" => "-2", "5E" => "-2", "5F" => "-2",
  142. "60" => "-1", "61" => "-1", "62" => "-1", "63" => "-1", "64" => "0", "65" => "0", "66" => "0", "67" => "1",
  143. "68" => "1", "69" => "1", "6A" => "2", "6B" => "2", "6C" => "2", "6D" => "3", "6E" => "3", "6F" => "3",
  144. "70" => "4", "71" => "4", "72" => "4", "73" => "5", "74" => "5", "75" => "5", "76" => "5", "77" => "6",
  145. "78" => "6", "79" => "6", "7A" => "7", "7B" => "7", "7C" => "7", "7D" => "8", "7E" => "8", "7F" => "8",
  146. "80" => "9", "81" => "9", "82" => "9", "83" => "10", "84" => "10", "85" => "10", "86" => "11", "87" => "11",
  147. "88" => "11", "89" => "12", "8A" => "12", "8B" => "12", "8C" => "13", "8D" => "13", "8E" => "13", "8F" => "14",
  148. "90" => "14", "91" => "14", "92" => "15", "93" => "15", "94" => "15", "95" => "16", "96" => "16", "97" => "16",
  149. "98" => "17", "99" => "17", "9A" => "18", "9B" => "18", "9C" => "18", "9D" => "19", "9E" => "19", "9F" => "19",
  150. "A0" => "20", "A1" => "20", "A2" => "21", "A3" => "21", "A4" => "21", "A5" => "22", "A6" => "22", "A7" => "22",
  151. "A8" => "23", "A9" => "23", "AA" => "24", "AB" => "24", "AC" => "24", "AD" => "25", "AE" => "25", "AF" => "26",
  152. "B0" => "26", "B1" => "27", "B2" => "27", "B3" => "27", "B4" => "28", "B5" => "28", "B6" => "29", "B7" => "29",
  153. "B8" => "30", "B9" => "30", "BA" => "31", "BB" => "31", "BC" => "32", "BD" => "32", "BE" => "33", "BF" => "33",
  154. "C0" => "34", "C1" => "34", "C2" => "35", "C3" => "35", "C4" => "36", "C5" => "36", "C6" => "37", "C7" => "37",
  155. "C8" => "38", "C9" => "38", "CA" => "39", "CB" => "40", "CC" => "40", "CD" => "41", "CE" => "41", "CF" => "42",
  156. "D0" => "43", "D1" => "43", "D2" => "44", "D3" => "45", "D4" => "45", "D5" => "46", "D6" => "47", "D7" => "48",
  157. "D8" => "49", "D9" => "49", "DA" => "50", "DB" => "51", "DC" => "52", "DD" => "53", "DE" => "53", "DF" => "54",
  158. "E0" => "55", "E1" => "56", "E2" => "57", "E3" => "59", "E4" => "60", "E5" => "61", "E6" => "62", "E7" => "63",
  159. "E8" => "65", "E9" => "66", "EA" => "68", "EB" => "69", "EC" => "71", "ED" => "73", "EE" => "75", "EF" => "77",
  160. "F0" => "79", "F1" => "81", "F2" => "82", "F3" => "86", "F4" => "90", "F5" => "93", "F6" => "97", "F7" => "100",
  161. "F8" => "100", "F9" => "100", "FA" => "100", "FB" => "100", "FC" => "100", "FD" => "100", "FE" => "100", "FF" => "100"
  162. );
  163. my %Vallox_temperatureTableReverse = reverse %Vallox_temperatureTable;
  164. ##################################
  165. # Mapping of the percentages (eg humidity)
  166. ##################################
  167. my %Vallox_percentageTable = (
  168. "34" => "0", "36" => "1", "38" => "2", "3A" => "3", "3C" => "4", "3E" => "5", "40" => "6", "42" => "7", "44" => "8", "46" => "9",
  169. "48" => "10", "4A" => "11", "4C" => "12", "4E" => "13", "50" => "14", "52" => "15", "54" => "16", "56" => "17", "58" => "18", "5A" => "19",
  170. "5C" => "20", "5E" => "21", "60" => "22", "62" => "23", "64" => "24", "66" => "25", "68" => "26", "6A" => "27", "6C" => "28", "6E" => "29",
  171. "70" => "30", "72" => "31", "74" => "32", "76" => "33", "78" => "34", "7A" => "35", "7C" => "36", "7E" => "37", "80" => "38", "82" => "39",
  172. "84" => "40", "86" => "41", "88" => "42", "8A" => "43", "8C" => "44", "8E" => "45", "90" => "46", "92" => "47", "94" => "48", "96" => "49",
  173. "98" => "50", "9A" => "51", "9C" => "52", "9E" => "53", "A0" => "54", "A2" => "55", "A4" => "56", "A6" => "57", "A8" => "58", "AA" => "59",
  174. "AC" => "60", "AE" => "61", "B0" => "62", "B2" => "63", "B4" => "64", "B6" => "65", "B8" => "66", "BA" => "67", "BC" => "68", "BE" => "69",
  175. "C0" => "70", "C2" => "71", "C4" => "72", "C6" => "73", "C8" => "74", "CA" => "75", "CC" => "76", "CE" => "77", "D0" => "78", "D2" => "79",
  176. "D4" => "80", "D6" => "81", "D8" => "82", "DA" => "83", "DC" => "84", "DE" => "85", "E0" => "86", "E2" => "87", "E4" => "88", "E6" => "89",
  177. "E8" => "90", "EA" => "91", "EC" => "92", "EE" => "93", "F0" => "94", "F2" => "95", "F4" => "96", "F6" => "97", "F8" => "98", "FA" => "99",
  178. "FC" => "100"
  179. );
  180. my %Vallox_percentageTableReverse = reverse %Vallox_percentageTable;
  181. ##################################
  182. # Mapping of the faults
  183. ##################################
  184. my %Vallox_faultTable = (
  185. "00" => "No fault stored",
  186. "05" => "Supply air temperature sensor fault",
  187. "06" => "Carbon dioxide alarm",
  188. "07" => "Outdoor air sensor fault",
  189. "08" => "Extract air sensor fault",
  190. "09" => "Water radiator danger of freezing",
  191. "0A" => "Exhaust air sensor fault",
  192. );
  193. my %Vallox_faultTableReverse = reverse %Vallox_faultTable;
  194. ##################################
  195. # Mapping of the MultiReadings with R/W
  196. # TODO: Find s.th. more elegant for all MR
  197. ##################################
  198. my %Vallox_multiReadingTable_realcmd = (
  199. "SupplyFan" => "MultiPurpose2",
  200. "ExhaustFan" => "MultiPurpose2",
  201. "CO2HigherSpeedRequest" => "Flags2",
  202. "CO2LowerRatePublicInvitation" => "Flags2",
  203. "HumidityLowerRatePublicInvitation" => "Flags2",
  204. "SwitchLowerSpeedRequest" => "Flags2",
  205. "CO2Alarm" => "Flags2",
  206. "FrostAlarmSensor" => "Flags2",
  207. "FrostAlarmWaterRadiator" => "Flags4",
  208. "MasterSlaveSelection" => "Flags4",
  209. "PreHeatingStatus" => "Flags5",
  210. "FireplaceSwitchActivation" => "Flags6",
  211. "PowerState" => "Select",
  212. "CO2AdjustState" => "Select",
  213. "RHAdjustState" => "Select",
  214. "HeatingState" => "Select",
  215. "AutomaticHumidityBasicLevelSeekerState" => "Program",
  216. "BoostSwitchMode" => "Program",
  217. "RadiatorType" => "Program",
  218. "CascadeAdjust" => "Program",
  219. "MaxSpeedLimitFunction" => "Program2",
  220. );
  221. my %Vallox_multiReadingTable_digit = (
  222. "SupplyFan" => 3,
  223. "ExhaustFan" => 5,
  224. "CO2HigherSpeedRequest" => 0,
  225. "CO2LowerRatePublicInvitation" => 1,
  226. "HumidityLowerRatePublicInvitation" => 2,
  227. "SwitchLowerSpeedRequest" => 3,
  228. "CO2Alarm" => 6,
  229. "FrostAlarmSensor" => 7,
  230. "FrostAlarmWaterRadiator" => 4,
  231. "MasterSlaveSelection" => 7,
  232. "PreHeatingStatus" => 7,
  233. "FireplaceSwitchActivation" => 5,
  234. "PowerState" => 0,
  235. "CO2AdjustState" => 1,
  236. "RHAdjustState" => 2,
  237. "HeatingState" => 3,
  238. "AutomaticHumidityBasicLevelSeekerState" => 4,
  239. "BoostSwitchMode" => 5,
  240. "RadiatorType" => 6,
  241. "CascadeAdjust" => 7,
  242. "MaxSpeedLimitFunction" => 0,
  243. );
  244. ##################################
  245. # Initialize Buffer fillings
  246. ##################################
  247. my $bufferRead = "00";
  248. my $bufferDevIO = "00";
  249. my $bufferDebug = "--";
  250. ##################################
  251. # basic get commands
  252. ##################################
  253. my %Vallox_gets = (
  254. "raw" => "raw"
  255. );
  256. ##################################
  257. # basic set commands
  258. ##################################
  259. my %Vallox_sets = (
  260. "raw" => "raw"
  261. );
  262. ##############################################
  263. # Custom Functions
  264. ##############################################
  265. ##################################
  266. # Create a valid message to send
  267. ##################################
  268. sub Vallox_CreateMsg ($@)
  269. {
  270. my ($hash, $readingIdentifier) = @_;
  271. my $domain = hex "0x".AttrVal($hash->{NAME}, "ValloxIDDomain", "01"); # Domain (1 by default)
  272. my $sender = hex "0x".AttrVal($hash->{NAME}, "ValloxIDFHEM", "2F"); # ID of this FHEM
  273. my $receiver = hex "0x".AttrVal($hash->{NAME}, "ValloxIDCentral", "11"); # ID of the central
  274. my $datatype = hex "0x".$readingIdentifier;
  275. my $checksum = ($domain + $sender + $receiver + $datatype) % 0x100;
  276. my $msg = lc(sprintf("%02x", $domain).sprintf("%02x", $sender).sprintf("%02x", $receiver)."00".sprintf("%02x", $datatype).sprintf("%02x", $checksum));
  277. return $msg;
  278. }
  279. ##############################################
  280. # Check if a message is valid
  281. ##############################################
  282. sub Vallox_ValidateStream ($@)
  283. {
  284. my ($hash, @a) = @_;
  285. my $name = shift @a;
  286. return undef if (length($bufferRead) < 12);
  287. my $domain = hex "0x".substr($bufferRead,0,2);
  288. my $sender = hex "0x".substr($bufferRead,2,2);
  289. my $receiver = hex "0x".substr($bufferRead,4,2);
  290. my $data_1 = hex "0x".substr($bufferRead,6,2);
  291. my $data_2 = hex "0x".substr($bufferRead,8,2);
  292. my $checksum = ($domain + $sender + $receiver + $data_1 + $data_2) % 0x100;
  293. #++$hash->{"CheckCount"};
  294. if (
  295. lc($domain) eq 01 &&
  296. lc(sprintf("%02x", $domain).sprintf("%02x", $sender).sprintf("%02x", $receiver).sprintf("%02x", $data_1).sprintf("%02x", $data_2).sprintf("%02x", $checksum)) eq lc($bufferRead)
  297. ) {
  298. #Log3 ($name, 5, "Vallox: Debug: DO ".$domain." - SE ".$sender." - RE ".$receiver." - D1 ".$data_1." - D2 ".$data_2." - CS ".$checksum." NE ".$bufferRead);
  299. #++$hash->{"MessageCount"};
  300. #$hash->{"BufferDatagramRatio"} = "1 : ".$hash->{"CheckCount"} / $hash->{"MessageCount"};
  301. if ((
  302. (
  303. $sender >= 17 &&
  304. $sender <= 31
  305. ) || (
  306. $sender >= 33 &&
  307. $sender <= 47
  308. )
  309. ) && (
  310. (
  311. $receiver >= 16 &&
  312. $receiver <= 31
  313. ) || (
  314. $receiver >= 32 &&
  315. $receiver <= 47
  316. )
  317. )) {
  318. return 1;
  319. } else {
  320. ++$hash->{"ErrorCount"};
  321. return 2;
  322. }
  323. } else {
  324. Log3 ($name, 4, "Vallox: Debug: DO ".$domain." - SE ".$sender." - RE ".$receiver." - D1 ".$data_1." - D2 ".$data_2." - CS ".$checksum." NE ".$bufferRead);
  325. return 0;
  326. }
  327. }
  328. ##############################################
  329. # Change bit in MultiReading
  330. # (bitnumber = 0 (rightest) - 7 (leftest)!)
  331. ##############################################
  332. sub Vallox_ReplaceBit ($@) {
  333. my ($hash, $bitstring, $bitnumber, $value) = @_;
  334. return substr($bitstring,0,8-$bitnumber-1).$value.substr($bitstring,8-$bitnumber,$bitnumber);
  335. }
  336. ##############################################
  337. # Update reading bulk for binary reading
  338. ##############################################
  339. sub Vallox_ReadingsBulkUpdateMultiReading($@) {
  340. my ($hash, $rawReadingType, $readingname, $bitnumber,) = @_;
  341. readingsBulkUpdate($hash, $readingname, substr($hash->{"MR_".$Vallox_datatypes{$rawReadingType}},8-$bitnumber-1,1)); # if (ReadingsVal($hash->{NAME},$readingname,"unknown") ne substr($hash->{"MR_".$Vallox_datatypes{$rawReadingType}},8-$bitnumber-1,1));
  342. return;
  343. }
  344. ##############################################
  345. # Interpret datagram and handle it
  346. ##############################################
  347. sub Vallox_InterpretAndUpdate(@) {
  348. my ($hash,$datagram) = @_;
  349. my $name = $hash->{NAME};
  350. my $rawReadingType;
  351. my $rawReadingValue;
  352. my $rawReadingChecksum;
  353. my $singlereading = 1;
  354. my $fineReadingValue = 1;
  355. $datagram = uc($datagram);
  356. # get the type of the datagram
  357. $rawReadingType = substr($datagram,6,2);
  358. # get the value of the datagram
  359. $rawReadingValue = substr($datagram,8,2);
  360. # get the value of the datagram
  361. $rawReadingChecksum = substr($datagram,10,2);
  362. if ($rawReadingType ne "00") {
  363. # Decoding for the final readings.
  364. # - rawReading... is the original information from the datagram
  365. # - fineReading... is the human readable information
  366. # Starting with the "One information in one datagram"-Section
  367. # Convert FanSpeeds by the Vallox_levelTable
  368. if ($rawReadingType eq "29" || $rawReadingType eq "A5" || $rawReadingType eq "A9") {
  369. $fineReadingValue = $Vallox_levelTable{$rawReadingValue};
  370. Log3 ($name, 4, "Vallox: Incoming Status-Info (FanSpeed): $datagram (Level $fineReadingValue)");
  371. # Convert Temperatures by the Vallox_temperatureTable
  372. } elsif ($rawReadingType eq "32" || $rawReadingType eq "33" || $rawReadingType eq "34" || $rawReadingType eq "35" || $rawReadingType eq "A4" || $rawReadingType eq "A7" || $rawReadingType eq "A8" || $rawReadingType eq "AF") {
  373. if ( $rawReadingType eq "A4" && $hash->{BusVersion} eq "1") {
  374. $fineReadingValue = $Vallox_levelTable{$rawReadingValue};
  375. Log3 ($name, 4, "Vallox: Incoming Status-Info (HeatingSetPoint): $datagram (Level $fineReadingValue)");
  376. return;
  377. } else {
  378. $fineReadingValue = $Vallox_temperatureTable{$rawReadingValue};
  379. }
  380. if (($rawReadingType eq "32" || $rawReadingType eq "33" || $rawReadingType eq "34" || $rawReadingType eq "35") && $fineReadingValue < -40) {
  381. Log3 ($name, 4, "Vallox: Incoming Status-Info (Temperature) invalid: $datagram ($fineReadingValue deg.)");
  382. return;
  383. }
  384. Log3 ($name, 4, "Vallox: Incoming Status-Info (Temperature): $datagram ($fineReadingValue deg.)");
  385. # Convert Percentages by the Vallox_percentageTable
  386. } elsif ($rawReadingType eq "AE") {
  387. $fineReadingValue = $Vallox_percentageTable{$rawReadingValue};
  388. Log3 ($name, 4, "Vallox: Incoming Status-Info (Percentage): $datagram ($fineReadingValue pct.)");
  389. # Convert Faults by the Vallox_faultTable
  390. } elsif ($rawReadingType eq "36") {
  391. $fineReadingValue = $Vallox_faultTable{$rawReadingValue};
  392. Log3 ($name, 4, "Vallox: Incoming Status-Info (Fault): $datagram ($fineReadingValue)");
  393. # Convert Decimal Values
  394. } elsif ($rawReadingType eq "2B" || $rawReadingType eq "2C" || $rawReadingType eq "2E" || $rawReadingType eq "57" || $rawReadingType eq "A6" || $rawReadingType eq "79" || $rawReadingType eq "8F" || $rawReadingType eq "91" || $rawReadingType eq "AB" || $rawReadingType eq "B0" || $rawReadingType eq "B1" || $rawReadingType eq "B3" || $rawReadingType eq "B4") {
  395. $fineReadingValue = sprintf("%d", hex "0x".$rawReadingValue);
  396. Log3 ($name, 4, "Vallox: Incoming Status-Info (Decimal): $datagram ($fineReadingValue)");
  397. # Convert PostHeating Time Values
  398. } elsif ($rawReadingType eq "55" || $rawReadingType eq "56") {
  399. $fineReadingValue = sprintf("%d", hex "0x".$rawReadingValue)/2.5;
  400. Log3 ($name, 4, "Vallox: Incoming Status-Info (PostHeatingCounter): $datagram ($fineReadingValue)");
  401. # Convert CellDefrostingSetpointTemperature Values
  402. } elsif ($rawReadingType eq "B2") {
  403. $fineReadingValue = sprintf("%d", hex "0x".$rawReadingValue)/3;
  404. Log3 ($name, 4, "Vallox: Incoming Status-Info (CellDefrostingSetpointTemperature): $datagram ($fineReadingValue)");
  405. # Convert measured humidity by formula (if it is negative then you don't have a humidity sensor)
  406. } elsif ($rawReadingType eq "2A" || $rawReadingType eq "2F" || $rawReadingType eq "30") {
  407. $fineReadingValue = (sprintf("%d", hex "0x".$rawReadingValue)-51)/2.04;
  408. # Negative Humidity impossible: No Sensor attatched
  409. if ($fineReadingValue < 0) {
  410. Log3 ($name, 4, "Vallox: Incoming Status-Info (Humidity) invalid: $datagram ($fineReadingValue Perc. rH)");
  411. return;
  412. }
  413. Log3 ($name, 4, "Vallox: Incoming Status-Info (Humidity): $datagram ($fineReadingValue pct. rH)");
  414. # Starting with the "Up to eight informations in one datagram"-Section
  415. # Disabled lines are unused.
  416. # FanSpeed-Relays
  417. } elsif ($rawReadingType eq "06") {
  418. $hash->{"MR_".$Vallox_datatypes{$rawReadingType}} = sprintf('%08b', hex("0x".$rawReadingValue));
  419. $singlereading = 0;
  420. readingsBeginUpdate($hash);
  421. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "Speed1", 0); #RO
  422. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "Speed2", 1); #RO
  423. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "Speed3", 2); #RO
  424. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "Speed4", 3); #RO
  425. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "Speed5", 4); #RO
  426. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "Speed6", 5); #RO
  427. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "Speed7", 6); #RO
  428. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "Speed8", 7); #RO
  429. readingsEndUpdate($hash, 1);
  430. Log3 ($name, 4, "Vallox: Incoming Status-Info (FanSpeed-Relays): $datagram (Bits ".$hash->{"MR_".$Vallox_datatypes{$rawReadingType}}.")");
  431. # MultiPurpose1
  432. } elsif ($rawReadingType eq "07") {
  433. $hash->{"MR_".$Vallox_datatypes{$rawReadingType}} = sprintf('%08b', hex("0x".$rawReadingValue));
  434. $singlereading = 0;
  435. readingsBeginUpdate($hash);
  436. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "PostHeating", 5); # RO
  437. readingsEndUpdate($hash, 1);
  438. Log3 ($name, 4, "Vallox: Incoming Status-Info (MultiPurpose1): $datagram (Bits ".$hash->{"MR_".$Vallox_datatypes{$rawReadingType}}.")");
  439. #MultiPurpose2
  440. } elsif ($rawReadingType eq "08") {
  441. $hash->{"MR_".$Vallox_datatypes{$rawReadingType}} = sprintf('%08b', hex("0x".$rawReadingValue));
  442. $singlereading = 0;
  443. readingsBeginUpdate($hash);
  444. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "DamperMotorPosition", 1); #RO
  445. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "FaultSignalRelay", 2); #RO
  446. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "SupplyFan", 3);
  447. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "PreHeating", 4); #RO
  448. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "ExhaustFan", 5);
  449. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "FireplaceBooster", 6); #RO
  450. readingsEndUpdate($hash, 1);
  451. Log3 ($name, 4, "Vallox: Incoming Status-Info (MultiPurpose2): $datagram (Bits ".$hash->{"MR_".$Vallox_datatypes{$rawReadingType}}.")");
  452. #MachineInstalledCO2Sensor
  453. } elsif ($rawReadingType eq "2D") {
  454. $hash->{"MR_".$Vallox_datatypes{$rawReadingType}} = sprintf('%08b', hex("0x".$rawReadingValue));
  455. $singlereading = 0;
  456. readingsBeginUpdate($hash);
  457. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "CO2Sensor1", 1); #RO
  458. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "CO2Sensor2", 2); #RO
  459. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "CO2Sensor3", 3); #RO
  460. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "CO2Sensor4", 4); #RO
  461. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "CO2Sensor5", 5); #RO
  462. readingsEndUpdate($hash, 1);
  463. Log3 ($name, 4, "Vallox: Incoming Status-Info (MultiPurpose2): $datagram (Bits ".$hash->{"MR_".$Vallox_datatypes{$rawReadingType}}.")");
  464. #Flags2
  465. } elsif ($rawReadingType eq "6D") {
  466. $hash->{"MR_".$Vallox_datatypes{$rawReadingType}} = sprintf('%08b', hex("0x".$rawReadingValue));
  467. $singlereading = 0;
  468. readingsBeginUpdate($hash);
  469. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "CO2HigherSpeedRequest", 0);
  470. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "CO2LowerRatePublicInvitation", 1);
  471. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "HumidityLowerRatePublicInvitation", 2);
  472. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "SwitchLowerSpeedRequest", 3);
  473. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "CO2Alarm", 6);
  474. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "FrostAlarmSensor", 7);
  475. readingsEndUpdate($hash, 1);
  476. Log3 ($name, 4, "Vallox: Incoming Status-Info (Flags2): $datagram (Bits ".$hash->{"MR_".$Vallox_datatypes{$rawReadingType}}.")");
  477. #Flags4
  478. } elsif ($rawReadingType eq "6F") {
  479. $hash->{"MR_".$Vallox_datatypes{$rawReadingType}} = sprintf('%08b', hex("0x".$rawReadingValue));
  480. $singlereading = 0;
  481. readingsBeginUpdate($hash);
  482. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "FrostAlarmWaterRadiator", 4);
  483. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "MasterSlaveSelection", 7);
  484. readingsEndUpdate($hash, 1);
  485. Log3 ($name, 4, "Vallox: Incoming Status-Info (Flags4): $datagram (Bits ".$hash->{"MR_".$Vallox_datatypes{$rawReadingType}}.")");
  486. #Flags5
  487. } elsif ($rawReadingType eq "70") {
  488. $hash->{"MR_".$Vallox_datatypes{$rawReadingType}} = sprintf('%08b', hex("0x".$rawReadingValue));
  489. $singlereading = 0;
  490. readingsBeginUpdate($hash);
  491. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "PreHeatingStatus", 7);
  492. readingsEndUpdate($hash, 1);
  493. Log3 ($name, 4, "Vallox: Incoming Status-Info (Flags5): $datagram (Bits ".$hash->{"MR_".$Vallox_datatypes{$rawReadingType}}.")");
  494. #Flags6
  495. } elsif ($rawReadingType eq "71") {
  496. $hash->{"MR_".$Vallox_datatypes{$rawReadingType}} = sprintf('%08b', hex("0x".$rawReadingValue));
  497. $singlereading = 0;
  498. readingsBeginUpdate($hash);
  499. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "RemoteMonitoringControl", 4); #RO
  500. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "FireplaceSwitchActivation", 5);
  501. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "FireplaceBoosterStatus", 6); #RO
  502. readingsEndUpdate($hash, 1);
  503. Log3 ($name, 4, "Vallox: Incoming Status-Info (Flags6): $datagram (Bits ".$hash->{"MR_".$Vallox_datatypes{$rawReadingType}}.")");
  504. # Select
  505. } elsif ($rawReadingType eq "A3") {
  506. $hash->{"MR_".$Vallox_datatypes{$rawReadingType}} = sprintf('%08b', hex("0x".$rawReadingValue));
  507. $singlereading = 0;
  508. readingsBeginUpdate($hash);
  509. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "PowerState", 0);
  510. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "CO2AdjustState", 1);
  511. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "RHAdjustState", 2);
  512. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "HeatingState", 3);
  513. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "FilterGuardIndicator", 4); #RO
  514. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "HeatingIndicator", 5); #RO
  515. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "FaultIndicator", 6); #RO
  516. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "ServiceReminderIndicator", 7);
  517. readingsEndUpdate($hash, 1);
  518. Log3 ($name, 4, "Vallox: Incoming Status-Info (Select): $datagram (Bits ".$hash->{"MR_".$Vallox_datatypes{$rawReadingType}}.")");
  519. # Program
  520. } elsif ($rawReadingType eq "AA") {
  521. $hash->{"MR_".$Vallox_datatypes{$rawReadingType}} = sprintf('%08b', hex("0x".$rawReadingValue));
  522. $singlereading = 0;
  523. readingsBeginUpdate($hash);
  524. # ----xxxx - Nibble is one value // # TODO: Adopt Function
  525. readingsBulkUpdate($hash, "HumidityCO2AdjustmentInterval", oct("0b"."0000".substr($hash->{"MR_".$Vallox_datatypes{$rawReadingType}},4,4))) if (ReadingsVal($name,"HumidityCO2AdjustmentInterval","unknown") ne oct("0b"."0000".substr($hash->{"MR_".$Vallox_datatypes{$rawReadingType}},4,4)));
  526. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "AutomaticHumidityBasicLevelSeekerState", 4);
  527. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "BoostSwitchMode", 5);
  528. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "RadiatorType", 6);
  529. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "CascadeAdjust", 7);
  530. readingsEndUpdate($hash, 1);
  531. Log3 ($name, 4, "Vallox: Incoming Status-Info (Program): $datagram (Bits ".$hash->{"MR_".$Vallox_datatypes{$rawReadingType}}.")");
  532. # Program2
  533. } elsif ($rawReadingType eq "B5") {
  534. $hash->{"MR_".$Vallox_datatypes{$rawReadingType}} = sprintf('%08b', hex("0x".$rawReadingValue));
  535. $singlereading = 0;
  536. readingsBeginUpdate($hash);
  537. Vallox_ReadingsBulkUpdateMultiReading($hash, $rawReadingType, "MaxSpeedLimitFunction", 0);
  538. readingsEndUpdate($hash, 1);
  539. Log3 ($name, 4, "Vallox: Incoming Status-Info (Program2): $datagram (Bits ".$hash->{"MR_".$Vallox_datatypes{$rawReadingType}}.")");
  540. # Convert Unused Binarys to bits (Just not to have Hex Readings)
  541. } elsif ($rawReadingType eq "6C" || $rawReadingType eq "6E") {
  542. $fineReadingValue = sprintf('%08b', hex("0x".$rawReadingValue));
  543. Log3 ($name, 4, "Vallox: Incoming Status-Info (Unused Binary): $datagram (Bits $fineReadingValue)");
  544. # Everything else
  545. # All Readings shall be handled before
  546. } else {
  547. $fineReadingValue = $rawReadingValue;
  548. $singlereading = 1;
  549. Log3 ($name, 2, "Vallox: Incoming unhandled datagram: $datagram");
  550. }
  551. if($Vallox_datatypes{$rawReadingType}) {
  552. if ($singlereading == 1) {
  553. Log3 ($name, 5, "Vallox: Update Reading: $fineReadingValue");
  554. readingsSingleUpdate($hash,$Vallox_datatypes{$rawReadingType}, $fineReadingValue, 1);
  555. # Efficiency Calculation
  556. # Is this Reading a Temp?
  557. if (substr($Vallox_datatypes{$rawReadingType},0,4) eq "Temp") {
  558. # If HRC is in Bypass - Efficiency is 0
  559. if (ReadingsVal($name,"DamperMotorPosition",1) == 1) {
  560. readingsSingleUpdate($hash,"EfficiencyIn", 0, 1);
  561. readingsSingleUpdate($hash,"EfficiencyOut", 0, 1);
  562. readingsSingleUpdate($hash,"EfficiencyAverage", 0, 1);
  563. Log3 ($name, 5, "Vallox: Efficiency Override: HRC Bypass");
  564. } else {
  565. my ($EfficiencyIn,$EfficiencyOut,$TempIncoming,$TempOutside,$TempInside,$TempExhaust) = 0;
  566. # Efficiency on Keep Temp Inside
  567. # Do we have all nessecary Readings?
  568. if (ReadingsVal($name,"TempIncoming","unknown") ne "unknown" &&
  569. ReadingsVal($name,"TempOutside","unknown") ne "unknown" &&
  570. ReadingsVal($name,"TempInside","unknown") ne "unknown") {
  571. $TempIncoming = ReadingsVal($name,"TempIncoming",-100);
  572. $TempOutside = ReadingsVal($name,"TempOutside",-100);
  573. $TempInside = ReadingsVal($name,"TempInside",-100);
  574. # Prevent DIV/0 (if Inside=Outside the HRC does nothing = 100% Efficient)
  575. if ($TempInside-$TempOutside != 0) {
  576. $EfficiencyIn = ($TempIncoming-$TempOutside)/($TempInside-$TempOutside)*100;
  577. $EfficiencyIn = 100 if ($EfficiencyIn > 100);
  578. Log3 ($name, 5, "Vallox: Efficiency Inside: (".$TempIncoming."-".$TempIncoming.")/(".$TempInside."-".$TempOutside.")*100 = ".$EfficiencyIn);
  579. readingsSingleUpdate($hash,"EfficiencyIn", $EfficiencyIn, 1);
  580. } else {
  581. Log3 ($name, 5, "Vallox: Efficiency Inside (DIV/0 Prevention): (".$TempIncoming."-".$TempIncoming.")/(".$TempInside."-".$TempOutside.")*100 = 100");
  582. readingsSingleUpdate($hash,"EfficiencyIn", 100, 1);
  583. }
  584. }
  585. # Efficiency on Keep Temp Outside
  586. # Do we have all nessecary Readings?
  587. if (ReadingsVal($name,"TempOutside","unknown") ne "unknown" &&
  588. ReadingsVal($name,"TempIncoming","unknown") ne "unknown" &&
  589. ReadingsVal($name,"TempExhaust","unknown") ne "unknown") {
  590. $TempOutside = ReadingsVal($name,"TempOutside",-100);
  591. $TempIncoming = ReadingsVal($name,"TempIncoming",-100);
  592. $TempExhaust = ReadingsVal($name,"TempExhaust",-100);
  593. # Prevent DIV/0 (if Inside=Outside the HRC does nothing = 100% Efficient)
  594. if ($TempOutside-$TempIncoming != 0) {
  595. $EfficiencyOut = ($TempExhaust-$TempIncoming)/($TempOutside-$TempIncoming)*100;
  596. $EfficiencyOut = 100 if ($EfficiencyOut > 100);
  597. Log3 ($name, 5, "Vallox: Efficiency Outside: (".$TempExhaust."-".$TempIncoming.")/(".$TempOutside."-".$TempIncoming.")*100 = ".$EfficiencyOut);
  598. readingsSingleUpdate($hash,"EfficiencyOut", $EfficiencyOut, 1);
  599. } else {
  600. Log3 ($name, 5, "Vallox: Efficiency Outside (DIV/0 Protection): (".$TempExhaust."-".$TempIncoming.")/(".$TempOutside."-".$TempIncoming.")*100 = 100");
  601. readingsSingleUpdate($hash,"EfficiencyOut", 100, 1);
  602. }
  603. }
  604. # Average Efficiency
  605. if (ReadingsVal($name,"EfficiencyIn","unknown") ne "unknown" &&
  606. ReadingsVal($name,"EfficiencyOut","unknown") ne "unknown") {
  607. $EfficiencyIn = ReadingsVal($name,"EfficiencyIn",-100);
  608. $EfficiencyOut = ReadingsVal($name,"EfficiencyOut",-100);
  609. my $EfficiencyAverage = ($EfficiencyIn+$EfficiencyOut) / 2;
  610. Log3 ($name, 5, "Vallox: Efficiency Average: (".$EfficiencyIn."+".$EfficiencyOut.")/2 = ".$EfficiencyAverage);
  611. readingsSingleUpdate($hash,"EfficiencyAverage", $EfficiencyAverage, 1);
  612. } else {
  613. Log3 ($name, 5, "Vallox: Efficiency Average unknown: (".ReadingsVal($name,"EfficiencyIn","unknown")."+".ReadingsVal($name,"EfficiencyOut","unknown").")/2");
  614. }
  615. }
  616. }
  617. }
  618. } else {
  619. Log3 ($name, 4, "Vallox: Datagram not in Datatypes-Table: ".$datagram);
  620. }
  621. } else {
  622. Log3 ($name, 5, "Vallox: Incoming Status-Request: $datagram");
  623. }
  624. }
  625. ##############################################
  626. # Default Functions
  627. ##############################################
  628. ##################################
  629. sub
  630. Vallox_Initialize($)
  631. {
  632. my ($hash) = @_;
  633. require "$attr{global}{modpath}/FHEM/DevIo.pm";
  634. $hash->{DefFn} = "Vallox_Define";
  635. $hash->{UndefFn} = "Vallox_Undef";
  636. $hash->{DeleteFn} = "Vallox_Delete";
  637. $hash->{SetFn} = "Vallox_Set";
  638. $hash->{GetFn} = "Vallox_Get";
  639. $hash->{AttrFn} = "Vallox_Attr";
  640. # $hash->{NotifyFn} = "Vallox_Notify";
  641. $hash->{ReadFn} = "Vallox_Read";
  642. # $hash->{ReadyFn} = "Vallox_Ready";
  643. $hash->{ShutdownFn} = "Vallox_Shutdown";
  644. $hash->{AttrList} = "ValloxBufferDebug:0,1 ValloxForceBroadcast:0,1 ValloxProcessOwnCommands:0,1 ValloxIDDomain ValloxIDFHEM ValloxIDCentral ".$readingFnAttributes;
  645. }
  646. ##################################
  647. sub Vallox_Define($) {
  648. my ($hash, $def) = @_;
  649. my @a = split("[ \t][ \t]*", $def);
  650. if(@a != 3 && @a != 4) {
  651. my $msg = "wrong syntax: define <name> Vallox devicename[\@baudrate] [busversion]";
  652. Log3 undef, 2, "Vallox: ".$msg;
  653. return $msg;
  654. }
  655. DevIo_CloseDev($hash);
  656. my $name = $a[0];
  657. my $dev = $a[2];
  658. $hash-> {BusVersion} = "2";
  659. $dev .= "\@9600" if( $dev !~ m/\@/ && $def !~ m/:/ );
  660. $hash-> {DeviceName} = $dev;
  661. if(@a == 4) {
  662. $hash-> {BusVersion} = $a[3];
  663. }
  664. if ($hash->{BusVersion} eq "1") {
  665. %Vallox_datatypes = (%Vallox_datatypes_legacy1, %Vallox_datatypes_base);
  666. %Vallox_datatypesReverse = (%Vallox_datatypesReverse_legacy1, %Vallox_datatypesReverse_base);
  667. } else {
  668. %Vallox_datatypes = %Vallox_datatypes_base;
  669. %Vallox_datatypesReverse = %Vallox_datatypesReverse_base;
  670. }
  671. my $ret = DevIo_OpenDev($hash, 0, undef);
  672. return $ret;
  673. }
  674. ##################################
  675. sub Vallox_Undef($$)
  676. {
  677. my ($hash, $name) = @_;
  678. DevIo_CloseDev($hash);
  679. RemoveInternalTimer($hash);
  680. return undef;
  681. }
  682. ##################################
  683. sub Vallox_Delete($$)
  684. {
  685. my ( $hash, $name ) = @_;
  686. return undef;
  687. }
  688. ##################################
  689. sub Vallox_Get($@)
  690. {
  691. my ( $hash, @a ) = @_;
  692. return "\"get Vallox\" needs at least one argument" if ( @a < 2 );
  693. my $name = shift @a;
  694. my $cmd = shift @a;
  695. my $arg = shift @a;
  696. # "reading" is a predefined list of readings from the bus
  697. if ($cmd eq "reading") {
  698. my $argKey = $Vallox_datatypesReverse{$arg};
  699. my $msg = Vallox_CreateMsg ($hash,$argKey);
  700. DevIo_SimpleWrite ($hash,$msg,1);
  701. Log3 ($name, 3, "Vallox: Request ".$msg." has been sent.");
  702. return undef;
  703. # "update" shall be ask for all possible data.
  704. # Not working at the moment. Need a new idea :(
  705. } elsif ($cmd eq "update"){
  706. while (my ($argKey, $argValue) = each %Vallox_datatypes) {
  707. my $msg = Vallox_CreateMsg ($hash,$argKey);
  708. DevIo_SimpleWrite ($hash,$msg,1);
  709. }
  710. return undef;
  711. # "raw is a custom Hex Code
  712. } elsif ($cmd eq "raw"){
  713. return "Usage: get $name raw {HexValue}" if (!defined ($arg) || $arg =~ m/[^a-fA-F0-9]{2}/);
  714. my $msg = Vallox_CreateMsg ($hash,$arg);
  715. DevIo_SimpleWrite ($hash,$msg,1);
  716. Log3 ($name, 3, "Vallox: Request ".$msg." has been sent.");
  717. return undef;
  718. } else {
  719. my $retmsg;
  720. my @commandList = keys %Vallox_gets;
  721. my $commandCount = keys %Vallox_gets;
  722. my @readingList = keys %Vallox_datatypesReverse;
  723. my $readingCount = keys %Vallox_datatypesReverse;
  724. $retmsg .= join(" ", @commandList) if ($commandCount > 0);
  725. $retmsg .= " reading:".join(",", sort @readingList) if ($readingCount > 0);
  726. ## Ich kann nicht alle befehle bulken. ... :(
  727. # $retmsg .= " update:noArg";
  728. Log3 ($name, 2, "Vallox: Unknown argument $cmd.") if( $cmd ne '?' && $cmd ne '');
  729. return "Unknown argument $cmd, choose one of " . $retmsg;
  730. }
  731. }
  732. ##################################
  733. sub Vallox_Set($@)
  734. {
  735. my ( $hash, @a ) = @_;
  736. return "\"set Vallox\" needs at least an argument" if ( @a < 2 );
  737. my $domain = hex "0x".AttrVal($hash->{NAME}, "ValloxIDDomain", "01"); # Domain (1 by default)
  738. my $sender = hex "0x".AttrVal($hash->{NAME}, "ValloxIDFHEM", "2F"); # ID of this FHEM
  739. my $receiver = hex "0x".AttrVal($hash->{NAME}, "ValloxIDCentral", "11"); # ID of the central
  740. my $name = shift @a;
  741. my $cmd = shift @a;
  742. my $arg = shift @a;
  743. my $datatype;
  744. my $datavalue;
  745. my $setCommands;
  746. my @commandList = keys %Vallox_sets;
  747. my $commandCount = keys %Vallox_sets;
  748. $setCommands .= join(" ", @commandList) if ($commandCount > 0);
  749. $setCommands .= " FanSpeed:1,2,3,4,5,6,7,8";
  750. $setCommands .= " BasicHumidityLevel:slider,0,1,100";
  751. $setCommands .= " HeatRecoveryCellBypassSetpointTemperature:slider,0,1,20";
  752. foreach my $MR_key (keys %Vallox_multiReadingTable_realcmd) {
  753. $setCommands .= " ".$MR_key.":0,1";
  754. }
  755. # MR: Prepare Values and Command for datagram
  756. if (exists($Vallox_multiReadingTable_realcmd{$cmd}) && exists($hash->{"MR_".$cmd})) {
  757. # TODO: Integrate get before set;
  758. return "Vallox: Internal ".$Vallox_multiReadingTable_realcmd{$cmd}." empty (".$hash->{"MR_".$cmd}."). Read ".$Vallox_multiReadingTable_realcmd{$cmd}." first!" if ($hash->{"MR_".$Vallox_multiReadingTable_realcmd{$cmd}} eq "");
  759. $arg = Vallox_ReplaceBit($hash, $hash->{"MR_".$cmd}, $Vallox_multiReadingTable_digit{$cmd}, $arg);
  760. $cmd = $Vallox_multiReadingTable_realcmd{$cmd}
  761. }
  762. ## TODO
  763. if (exists $Vallox_datatypesReverse{$cmd}) {
  764. $datatype = hex "0x".$Vallox_datatypesReverse{$cmd};
  765. if ($datatype == 0x29) {
  766. $datavalue = hex "0x".$Vallox_levelTableReverse{$arg};
  767. } elsif ($datatype == 0xae) {
  768. $datavalue = hex "0x".$Vallox_percentageTableReverse{$arg};
  769. } elsif ($datatype == 0xaf) {
  770. $datavalue = hex "0x".$Vallox_temperatureTableReverse{$arg};
  771. } else {
  772. $datavalue = hex "0x".$arg;
  773. }
  774. } elsif ($cmd eq "raw") {
  775. $datatype = hex "0x".substr($arg,0,2);
  776. $datavalue = hex "0x".substr($arg,2,2);
  777. } else {
  778. Log3 ($name, 2, "Vallox: Unknown argument $cmd.") if( $cmd ne '?' && $cmd ne '');
  779. return "Unknown argument $cmd, choose one of " . $setCommands;
  780. }
  781. my $checksum = ($domain + $sender + $receiver + $datatype + $datavalue) % 0x100;
  782. my $msg = lc(sprintf("%02x", $domain).sprintf("%02x", $sender).sprintf("%02x", $receiver).sprintf("%02x", $datatype).sprintf("%02x", $datavalue).sprintf("%02x", $checksum));
  783. DevIo_SimpleWrite ($hash,$msg,1);
  784. Log3 ($name, 3, "Vallox: Command ".$msg." has been sent.");
  785. if (AttrVal($hash->{NAME}, "ValloxProcessOwnCommands", "0") == 1 || $hash->{BusVersion} eq "1") {
  786. Vallox_InterpretAndUpdate($hash,$msg);
  787. Log3 ($name, 3, "Vallox: Command ".$msg." has been internal processed.");
  788. }
  789. if (AttrVal($hash->{NAME}, "ValloxForceBroadcast", "0") == 1 || $hash->{BusVersion} eq "1") {
  790. $checksum = ($domain + $sender + 0x10 + $datatype + $datavalue) % 0x100;
  791. $msg = lc(sprintf("%02x", $domain).sprintf("%02x", $sender). 10 .sprintf("%02x", $datatype).sprintf("%02x", $datavalue).sprintf("%02x", $checksum));
  792. DevIo_SimpleWrite ($hash,$msg,1);
  793. Log3 ($name, 3, "Vallox: Broadcast-Command ".$msg." has been sent.");
  794. $checksum = ($domain + $sender + 0x20 + $datatype + $datavalue) % 0x100;
  795. $msg = lc(sprintf("%02x", $domain).sprintf("%02x", $sender). 20 .sprintf("%02x", $datatype).sprintf("%02x", $datavalue).sprintf("%02x", $checksum));
  796. DevIo_SimpleWrite ($hash,$msg,1);
  797. Log3 ($name, 3, "Vallox: Broadcast-Command ".$msg." has been sent.");
  798. }
  799. return undef;
  800. }
  801. sub Vallox_Attr(@)
  802. {
  803. my ($cmd,$name,$aName,$aVal) = @_;
  804. # $cmd can be "del" || "set"
  805. # $name is device name
  806. # aName and aVal are Attribute name and value
  807. if ($cmd eq "set") {
  808. if ($aName eq "ValloxIDDomain" || $aName eq "ValloxIDFHEM" || $aName eq "ValloxIDCentral") {
  809. if ($aVal =~ m/[^a-fA-F0-9]/ || length($aVal) != 2) {
  810. Log3 $name, 2, "Vallox: Invalid HexValue in attr $name $aName $aVal: $@";
  811. return "Invalid HexValue $aVal";
  812. }
  813. }
  814. }
  815. return undef;
  816. }
  817. sub Vallox_Read($)
  818. {
  819. my ($hash) = @_;
  820. my $name = $hash->{NAME};
  821. my $datagram;
  822. my $rawReadingType;
  823. my $rawReadingValue;
  824. my $rawReadingChecksum;
  825. my $singlereading = 1;
  826. my $fineReadingValue = 1;
  827. my $bufferDebugName = AttrVal($hash->{NAME}, "ValloxBufferDebug", "0");
  828. # read from serial device
  829. my $buf = DevIo_SimpleRead($hash);
  830. return "" if ( !defined($buf) );
  831. # Convert read data to hex and add to debug if nessecary
  832. if ($bufferDebugName eq 1) {
  833. $hash->{"BufferDebug"} .= unpack ('H*', $buf);
  834. }
  835. # Convert read data to hex and fill DevIO-Buffer
  836. $bufferDevIO .= unpack ('H*', $buf);
  837. # DO Run Validation until DevIO-Buffer is less than 2 chars long
  838. do {
  839. # If DevIO-Buffer is filled add difference to 14 Chars to ReadBuffer and remove it from DevIO-Buffer
  840. if (length($bufferDevIO) >= 2) {
  841. my $bufferReadSpace = 14 - length($bufferRead);
  842. $bufferRead .= substr($bufferDevIO,0,$bufferReadSpace);
  843. # If the bufferDevIO buffer is shorter than the filling, set it to empty to avoid error
  844. if (length($bufferDevIO) >= $bufferReadSpace) {
  845. $bufferDevIO = substr($bufferDevIO,$bufferReadSpace);
  846. } else {
  847. $bufferDevIO = "";
  848. }
  849. }
  850. #$hash->{"BufferDevIOLength"} = length($bufferDevIO);
  851. # Once ReadBuffer filled up, remove first Byte
  852. if (length($bufferRead) >= 14) {
  853. $bufferRead = substr($bufferRead,2,12);
  854. }
  855. # If ReadBuffer has valid length start validating content
  856. if (length($bufferRead) == 12) {
  857. if (Vallox_ValidateStream($hash) == 1) {
  858. Log3 ($name, 5, "Vallox: Buffer: ".$bufferRead);
  859. my $datagram = uc($bufferRead);
  860. Vallox_InterpretAndUpdate($hash,$datagram);
  861. } elsif (Vallox_ValidateStream($hash) == 2) {
  862. Log3 ($name, 4, "Vallox: Invalid Status-Request: $bufferRead");
  863. }
  864. }
  865. } while (length($bufferDevIO) >= 2);
  866. }
  867. sub Vallox_Shutdown($)
  868. {
  869. my ($hash) = @_;
  870. DevIo_CloseDev($hash);
  871. return undef;
  872. }
  873. 1;
  874. =pod
  875. =item device
  876. =item summary Reads and writes parameters via RS485 from and to a Vallox ventilation bus.
  877. =item summary_DE Liest und schreibt über RS485 aus und in einen Bus einer Vallox Belüftungsanlage
  878. =begin html
  879. <a name="Vallox"></a>
  880. <h3>Vallox</h3>
  881. <div>
  882. <ul>
  883. Vallox is a manufacturer for ventilation devices.
  884. <br>
  885. Their products have a built-in RS485-Interface on the central ventilation unit as well as on connected control units on which all control communication is handeled.
  886. <br>
  887. More Info on the particular <a href="http://www.fhemwiki.de/wiki/Vallox">page of FHEM-Wiki</a> (in German).
  888. <br>
  889. &nbsp;
  890. <br>
  891. <a name="Valloxdefine"></a>
  892. <b>Define</b>
  893. <ul>
  894. <code>define &lt;name&gt; Vallox &lt;RS485-Device[@baud]&gt; [BusVersion]</code><br>
  895. If the baudrate is omitted, it is set to 9600 (default Vallox baudrate).<br>
  896. The BusVersion can be set to 1 for older systems. (Default: 2).<br>
  897. <br>
  898. Example: <code>define Ventilation Vallox /dev/ttyUSB1</code>
  899. </ul>
  900. <br>
  901. <a name="Valloxset"></a>
  902. <b>Set</b>
  903. <ul>
  904. <li><code>FanSpeed &lt; 1-8 &gt;</code>
  905. <br>
  906. Allows to set the fan speed (1 = lowest; 8 = highest).<br>
  907. </li><br>
  908. <li><code>BasicHumidityLevel &lt; 0-100 &gt;</code>
  909. <br>
  910. Allows to set the basic humidity level in percentage.<br>
  911. </li><br>
  912. <li><code>HeatRecoveryCellBypassSetpointTemperature &lt; 0-20 &gt;</code>
  913. <br>
  914. Allows to set the heat recovery cell bypass setpoint temperature.<br>
  915. </li><br>
  916. <li><code>raw &lt; HexValue &gt;</code><br>
  917. HexValue is two 2-digit hex number to identify the type and value of setting.
  918. </li><br>
  919. <br>
  920. Example to set the fan speed to 3:<br>
  921. <code>set Ventilation raw 2907</code><br>
  922. or:<br>
  923. <code>set Ventilation FanSpeed 3</code>
  924. </ul>
  925. <br>
  926. <a name="Valloxget"></a>
  927. <b>Get</b>
  928. <ul>
  929. <li><code>reading &lt; readingname &gt;</code>
  930. <br>
  931. Allows to get any predefined reading.<br>
  932. </li><br>
  933. <li><code>raw &lt; HexValue &gt;</code><br>
  934. HexValue is a 2-digit hex number to identify the requested reading.
  935. </li><br>
  936. </ul>
  937. <br>
  938. <a name="Valloxattr"></a>
  939. <b>Attributes</b>
  940. <ul><li><code>ValloxIDDomain &lt; HexValue &gt;</code>
  941. <br>
  942. HexValue is a 2-digit hex number to identify the &QUOT;address&QUOT; of the bus domain. (01 by default).
  943. </li><br>
  944. <li><code>ValloxIDCentral &lt; HexValue &gt;</code>
  945. <br>
  946. HexValue is a 2-digit hex number to identify the &QUOT;address&QUOT; of the central ventilation unit. (11 by default).<br>
  947. In a normal installation ventilation units in the scope 11 to 19 and are addressed with 10 for broadcast-messages.
  948. </li><br>
  949. <li><code>ValloxIDFHEM &lt; HexValue &gt;</code>
  950. <br>
  951. HexValue is a 2-digit hex number to identify the &QUOT;address&QUOT; of this system as a virtual control terminal. (2F by default)<br>
  952. In a normal installation control terminals are in the scope 21 to 29 and are addressed with 20 for broadcast-messages.<br>
  953. The address must be unique.<br>
  954. The &QUOT;panel address&QUOT; of the physical control terminal can be set on the settings of it. Possible values are 1-15 which is the second digit of the Hex-Value (1-F). The first digit is always 2.<br>
  955. The physical control terminal is usually 21.
  956. </li><br>
  957. <li><code>ValloxBufferDebug &lt; 0/1 &gt;</code>
  958. <br>
  959. When 1, modul creates an Internal which fills with the raw Hex-Data from the bus. DEBUG ONLY! (0 by default).
  960. </li><br>
  961. <li><code>ValloxForceBroadcast &lt; 0/1 &gt;</code>
  962. <br>
  963. When 1, modul sends commands not only to the central ventilation unit (11) but to all possible addresses by broadcast (10/20). This is sometimes nessecary for older systems. (0 by default; Function always on on BusVersion 1).
  964. </li><br>
  965. <li><code>ValloxProcessOwnCommands &lt; 0/1 &gt;</code>
  966. <br>
  967. When 1, modul sends commands not only to the bus but processes it as a received reading. This is sometimes nessecary for older systems. (0 by default; Function always on on BusVersion 1).
  968. </li><br>
  969. <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
  970. </ul>
  971. </ul>
  972. </div>
  973. =end html
  974. =begin html_DE
  975. <a name="Vallox"></a>
  976. <h3>Vallox</h3>
  977. <div>
  978. <ul>
  979. Vallox ist ein Hersteller von Bel&uuml;ftungsanlagen mit W&auml;rmetauscher.
  980. <br>
  981. Die Systeme verf&uuml;gen sowohl an der zentralen L&uuml;ftungskomponente, als auch an den Terminals &uuml;ber eine RS485-Schnittstelle &uuml;ber die die gesamte interne Kommunikation abgewickelt wird.
  982. <br>
  983. Mehr Informationen sind auf der <a href="http://www.fhemwiki.de/wiki/Vallox">FHEM-Wiki-Seite</a> verf&uuml;gbar.
  984. <br>
  985. &nbsp;
  986. <br>
  987. <a name="Valloxdefine"></a>
  988. <b>Define</b>
  989. <ul>
  990. <code>define &lt;name&gt; Vallox &lt;RS485-Device[@baud]&gt; [BusVersion]</code><br>
  991. Wird die Baudrate weggelassen wird mit 9600 baud kommuniziert. (Standardrate des Vallox-Busses).<br>
  992. Die BusVersion kann bei &auml;lteren Anlagen auf 1 gesetzt werden. (Standard: 2).<br>
  993. <br>
  994. Beispiel: <code>define Ventilation Vallox /dev/ttyUSB1</code>
  995. </ul>
  996. <br>
  997. <a name="Valloxset"></a>
  998. <b>Set</b>
  999. <ul>
  1000. <li><code>FanSpeed &lt; 1-8 &gt;</code>
  1001. <br>
  1002. Erlaubt das &Auml;ndern der L&uuml;ftergeschwindigkeit (1 = minimal; 8 = maximal).<br>
  1003. </li><br>
  1004. <li><code>BasicHumidityLevel &lt; 0-100 &gt;</code>
  1005. <br>
  1006. Erlaubt das &Auml;ndern des Luftfeuchtigkeits-Grenzwertes (Terminaldisplay: <code>Grenzwert &#037;RH</code>).<br>
  1007. </li><br>
  1008. <li><code>HeatRecoveryCellBypassSetpointTemperature &lt; 0-20 &gt;</code>
  1009. <br>
  1010. Erlaubt das &Auml;ndern des Grenzwertes f&uuml;r den W&auml;rmetauscher-Bypass (Terminaldisplay: <code>WRG Bypass</code>)<br>
  1011. </li><br>
  1012. <li><code>raw &lt; HexWert &gt;</code><br>
  1013. HexWert sind <u>zwei</u> 2-stellige Hex-Zahlen, welche den Typ und den Wert der Einstellung identifiziert.
  1014. </li><br>
  1015. <br>
  1016. Beispiel um die L&uuml;ftergeschwindigkeit auf 3 zu setzen:<br>
  1017. <code>set Ventilation raw 2907</code><br>
  1018. oder:<br>
  1019. <code>set Ventilation FanSpeed 3</code>
  1020. </ul>
  1021. <br>
  1022. <a name="Valloxget"></a>
  1023. <b>Get</b>
  1024. <ul>
  1025. <li><code>reading &lt; readingname &gt;</code>
  1026. <br>
  1027. Erlaubt das Auslesen der vorgegebenen Datenpunkte aus dem Bus.<br>
  1028. </li><br>
  1029. <li><code>raw &lt; HexWert &gt;</code><br>
  1030. HexWert ist <u>eine</u> 2-stellige Hex-Zahl, welche den Typ der abzufragenden Einstellung identifiziert.
  1031. </li><br>
  1032. </ul>
  1033. <br>
  1034. <a name="Valloxattr"></a>
  1035. <b>Attribute</b>
  1036. <ul><li><code>ValloxIDDomain &lt; HexWert &gt;</code>
  1037. <br>
  1038. HexWert ist eine 2-stellige Hex-Zahl die als &QUOT;Adresse&QUOT; der Bus-Dom&auml;ne dient. (Standard: 01).
  1039. </li><br>
  1040. <li><code>ValloxIDCentral &lt; HexWert &gt;</code>
  1041. <br>
  1042. HexWert ist eine 2-stellige Hex-Zahl die als &QUOT;Adresse&QUOT; der zentralen Ventilationseinheit dient. (Standard: 11).<br>
  1043. In einer normalen Umgebung werden die Ventilationseinheiten mit 11 - 1F adressiert. 10 ist die Broadcast-Adresse.<br>
  1044. </li><br>
  1045. <li><code>ValloxIDFHEM &lt; HexWert &gt;</code>
  1046. <br>
  1047. HexWert ist eine 2-stellige Hex-Zahl die als &QUOT;Adresse&QUOT; dieses Systems als virtuelles Kontrollterminal dient. (Standard: 2F).<br>
  1048. Sie darf nicht bereits im Bus genutzt werden.<br>
  1049. In einer normalen Umgebung werden die Kontrollterminals mit 21 - 2F adressiert. 20 ist die Broadcast-Adresse.<br>
  1050. In den Einstellungen der physikalisch vorhandenen Terminals kann die &QUOT;FBD-Adresse&QUOT; des jeweiligen Terminals eingestellt werden.<br>
  1051. Hierbei stehen die Werte 1-15 zur Verf&uuml;gung, was der zweiten Stelle dieser Adresse (1-F) entspricht. Die erste Stelle ist immer 2.<br>
  1052. Das physikalische Kontrollterminal ist &uuml;blicherweise die 21.
  1053. </li><br>
  1054. <li><code>ValloxBufferDebug &lt; 0/1 &gt;</code>
  1055. <br>
  1056. Wenn 1, erzeugt das Modul ein Internal in welches die rohen Hex-Daten aus dem Bus herein geschrieben. NUR ZUM DEBUGGEN! (Standard: 0).
  1057. </li><br>
  1058. <li><code>ValloxForceBroadcast &lt; 0/1 &gt;</code>
  1059. <br>
  1060. Wenn 1, sendet das Modul die Befehle nicht nur an die zentrale Ventilationseinheit (11), sondern auch an alle Broadcast-Adressen (10/20). Dies ist manchmal bei &auml;lteren Anlagen notwendig, wenn sich die Anzeige auf den Kontrollterminals nicht mit aktualisiert. (Standard: 0; Funktion immer an bei BusVersion 1).
  1061. </li><br>
  1062. <li><code>ValloxProcessOwnCommands &lt; 0/1 &gt;</code>
  1063. <br>
  1064. Wenn 1, behandelt das Modul die eigenen Befehle auch als Empfangene Befehle und verarbeitet sie intern weiter. Dies ist manchmal bei &auml;lteren Anlagen notwendig. (Standard: 0; Funktion immer an bei BusVersion 1).
  1065. </li><br>
  1066. <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
  1067. </ul>
  1068. </ul>
  1069. </div>
  1070. =end html_DE
  1071. =cut