WMBus.pm 62 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003
  1. # $Id: WMBus.pm 16996 2018-07-17 14:58:33Z kaihs $
  2. package WMBus;
  3. use strict;
  4. use warnings;
  5. use feature qw(say);
  6. use Digest::CRC; # libdigest-crc-perl
  7. eval "use Crypt::Mode::CBC"; # cpan -i Crypt::Mode::CBC
  8. my $hasCBC = ($@)?0:1;
  9. eval "use Crypt::Mode::CTR"; # cpan -i Crypt::Mode::CTR
  10. my $hasCTR = ($@)?0:1;
  11. require Exporter;
  12. my @ISA = qw(Exporter);
  13. my @EXPORT = qw(new parse parseLinkLayer parseApplicationLayer manId2ascii type2string);
  14. sub manId2ascii($$);
  15. use constant {
  16. # Transport Layer block size
  17. TL_BLOCK_SIZE => 10,
  18. # Link Layer block size
  19. LL_BLOCK_SIZE => 16,
  20. # size of CRC in bytes
  21. CRC_SIZE => 2,
  22. # sent by meter
  23. SND_NR => 0x44, # Send, no reply
  24. SND_IR => 0x46, # Send installation request, must reply with CNF_IR
  25. ACC_NR => 0x47,
  26. ACC_DMD => 0x48,
  27. # sent by controller
  28. SND_NKE => 0x40, # Link reset
  29. CNF_IR => 0x06,
  30. # CI field
  31. CI_RESP_4 => 0x7a, # Response from device, 4 Bytes
  32. CI_RESP_12 => 0x72, # Response from device, 12 Bytes
  33. CI_RESP_0 => 0x78, # Response from device, 0 Byte header, variable length
  34. CI_ERROR => 0x70, # Error from device, only specified for wired M-Bus but used by Easymeter WMBUS module
  35. CI_TL_4 => 0x8a, # Transport layer from device, 4 Bytes
  36. CI_TL_12 => 0x8b, # Transport layer from device, 12 Bytes
  37. CI_ELL_2 => 0x8c, # Extended Link Layer, 2 Bytes
  38. CI_ELL_6 => 0x8e, # Extended Link Layer, 6 Bytes
  39. CI_ELL_8 => 0x8d, # Extended Link Layer, 8 Bytes (see https://www.telit.com/wp-content/uploads/2017/09/Telit_Wireless_M-bus_2013_Part4_User_Guide_r14.pdf, 2.3.4)
  40. CI_ELL_16 => 0x8f, # Extended Link Layer, 16 Bytes (see https://www.telit.com/wp-content/uploads/2017/09/Telit_Wireless_M-bus_2013_Part4_User_Guide_r14.pdf, 2.3.4)
  41. CI_AFL => 0x90, # Authentification and Fragmentation Layer, variable size
  42. CI_RESP_SML_4 => 0x7e, # Response from device, 4 Bytes, application layer SML encoded
  43. CI_RESP_SML_12 => 0x7f, # Response from device, 12 Bytes, application layer SML encoded
  44. # DIF types (Data Information Field), see page 32
  45. DIF_NONE => 0x00,
  46. DIF_INT8 => 0x01,
  47. DIF_INT16 => 0x02,
  48. DIF_INT24 => 0x03,
  49. DIF_INT32 => 0x04,
  50. DIF_FLOAT32 => 0x05,
  51. DIF_INT48 => 0x06,
  52. DIF_INT64 => 0x07,
  53. DIF_READOUT => 0x08,
  54. DIF_BCD2 => 0x09,
  55. DIF_BCD4 => 0x0a,
  56. DIF_BCD6 => 0x0b,
  57. DIF_BCD8 => 0x0c,
  58. DIF_VARLEN => 0x0d,
  59. DIF_BCD12 => 0x0e,
  60. DIF_SPECIAL => 0x0f,
  61. DIF_IDLE_FILLER => 0x2f,
  62. DIF_EXTENSION_BIT => 0x80,
  63. VIF_EXTENSION => 0xFB, # true VIF is given in the first VIFE and is coded using table 8.4.4 b) (128 new VIF-Codes)
  64. VIF_EXTENSION_BIT => 0x80,
  65. ERR_NO_ERROR => 0,
  66. ERR_CRC_FAILED => 1,
  67. ERR_UNKNOWN_VIFE => 2,
  68. ERR_UNKNOWN_VIF => 3,
  69. ERR_TOO_MANY_DIFE => 4,
  70. ERR_UNKNOWN_LVAR => 5,
  71. ERR_UNKNOWN_DATAFIELD => 6,
  72. ERR_UNKNOWN_CIFIELD => 7,
  73. ERR_DECRYPTION_FAILED => 8,
  74. ERR_NO_AESKEY => 9,
  75. ERR_UNKNOWN_ENCRYPTION => 10,
  76. ERR_TOO_MANY_VIFE => 11,
  77. ERR_MSG_TOO_SHORT => 12,
  78. ERR_SML_PAYLOAD => 13,
  79. ERR_FRAGMENT_UNSUPPORTED => 14,
  80. ERR_UNKNOWN_COMPACT_FORMAT => 15,
  81. ERR_CIPHER_NOT_INSTALLED => 16,
  82. # TYPE C transmission uses two different frame types
  83. # see http://www.st.com/content/ccc/resource/technical/document/application_note/3f/fb/35/5a/25/4e/41/ba/DM00233038.pdf/files/DM00233038.pdf/jcr:content/translations/en.DM00233038.pdf
  84. FRAME_TYPE_A => 'A',
  85. FRAME_TYPE_B => 'B',
  86. };
  87. sub valueCalcNumeric($$) {
  88. my $value = shift;
  89. my $dataBlock = shift;
  90. return $value * $dataBlock->{valueFactor};
  91. }
  92. sub valueCalcDate($$) {
  93. my $value = shift;
  94. my $dataBlock = shift;
  95. #value is a 16bit int
  96. #day: UI5 [1 to 5] <1 to 31>
  97. #month: UI4 [9 to 12] <1 to 12>
  98. #year: UI7[6 to 8,13 to 16] <0 to 99>
  99. # YYYY MMMM YYY DDDDD
  100. # 0b0000 1100 111 11111 = 31.12.2007
  101. # 0b0000 0100 111 11110 = 30.04.2007
  102. my $day = ($value & 0b11111);
  103. my $month = (($value & 0b111100000000) >> 8);
  104. my $year = ((($value & 0b1111000000000000) >> 9) | (($value & 0b11100000) >> 5)) + 2000;
  105. if ($day > 31 || $month > 12 || $year > 2099) {
  106. return sprintf("invalid: %x", $value);
  107. } else {
  108. return sprintf("%04d-%02d-%02d", $year, $month, $day);
  109. }
  110. }
  111. sub valueCalcDateTime($$) {
  112. my $value = shift;
  113. my $dataBlock = shift;
  114. #min: UI6 [1 to 6] <0 to 59>
  115. #hour: UI5 [9 to13] <0 to 23>
  116. #day: UI5 [17 to 21] <1 to 31>
  117. #month: UI4 [25 to 28] <1 to 12>
  118. #year: UI7[22 to 24,29 to 32] <0 to 99>
  119. # IV:
  120. # B1[8] {time invalid}:
  121. # IV<0> :=
  122. #valid,
  123. #IV>1> := invalid
  124. #SU: B1[16] {summer time}:
  125. #SU<0> := standard time,
  126. #SU<1> := summer time
  127. #RES1: B1[7] {reserved}: <0>
  128. #RES2: B1[14] {reserved}: <0>
  129. #RES3: B1[15] {reserved}: <0>
  130. my $datePart = $value >> 16;
  131. my $timeInvalid = $value & 0b10000000;
  132. my $dateTime = valueCalcDate($datePart, $dataBlock);
  133. if ($timeInvalid == 0) {
  134. my $min = ($value & 0b111111);
  135. my $hour = ($value >> 8) & 0b11111;
  136. my $su = ($value & 0b1000000000000000);
  137. if ($min > 59 || $hour > 23) {
  138. $dateTime = sprintf('invalid: %x', $value);
  139. } else {
  140. $dateTime .= sprintf(' %02d:%02d %s', $hour, $min, $su ? 'DST' : '');
  141. }
  142. }
  143. return $dateTime;
  144. }
  145. sub valueCalcHex($$) {
  146. my $value = shift;
  147. my $dataBlock = shift;
  148. return sprintf("%x", $value);
  149. }
  150. sub valueCalcu($$) {
  151. my $value = shift;
  152. my $dataBlock = shift;
  153. my $result = '';
  154. $result = ($value & 0b00001000 ? 'upper' : 'lower') . ' limit';
  155. return $result;
  156. }
  157. sub valueCalcufnn($$) {
  158. my $value = shift;
  159. my $dataBlock = shift;
  160. my $result = '';
  161. $result = ($value & 0b00001000 ? 'upper' : 'lower') . ' limit';
  162. $result .= ', ' . ($value & 0b00000100 ? 'first' : 'last');
  163. $result .= sprintf(', duration %d', $value & 0b11);
  164. return $result;
  165. }
  166. sub valueCalcMultCorr1000($$) {
  167. my $value = shift;
  168. my $dataBlock = shift;
  169. $dataBlock->{value} *= 1000;
  170. return "correction by factor 1000";
  171. }
  172. my %TimeSpec = (
  173. 0b00 => 's', # seconds
  174. 0b01 => 'm', # minutes
  175. 0b10 => 'h', # hours
  176. 0b11 => 'd', # days
  177. );
  178. sub valueCalcTimeperiod($$) {
  179. my $value = shift;
  180. my $dataBlock = shift;
  181. $dataBlock->{unit} = $TimeSpec{$dataBlock->{exponent}};
  182. return $value;
  183. }
  184. # VIF types (Value Information Field), see page 32
  185. my %VIFInfo = (
  186. VIF_ENERGY_WATT => { # 10(nnn-3) Wh 0.001Wh to 10000Wh
  187. typeMask => 0b01111000,
  188. expMask => 0b00000111,
  189. type => 0b00000000,
  190. bias => -3,
  191. unit => 'Wh',
  192. calcFunc => \&valueCalcNumeric,
  193. },
  194. VIF_ENERGY_JOULE => { # 10(nnn) J 0.001kJ to 10000kJ
  195. typeMask => 0b01111000,
  196. expMask => 0b00000111,
  197. type => 0b00001000,
  198. bias => 0,
  199. unit => 'J',
  200. calcFunc => \&valueCalcNumeric,
  201. },
  202. VIF_VOLUME => { # 10(nnn-6) m3 0.001l to 10000l
  203. typeMask => 0b01111000,
  204. expMask => 0b00000111,
  205. type => 0b00010000,
  206. bias => -6,
  207. unit => 'm³',
  208. calcFunc => \&valueCalcNumeric,
  209. },
  210. VIF_MASS => { # 10(nnn-3) kg 0.001kg to 10000kg
  211. typeMask => 0b01111000,
  212. expMask => 0b00000111,
  213. type => 0b00011000,
  214. bias => -3,
  215. unit => 'kg',
  216. calcFunc => \&valueCalcNumeric,
  217. },
  218. VIF_ON_TIME_SEC => { # seconds
  219. typeMask => 0b01111111,
  220. expMask => 0b00000000,
  221. type => 0b00100000,
  222. bias => 0,
  223. unit => 'sec',
  224. calcFunc => \&valueCalcNumeric,
  225. },
  226. VIF_ON_TIME_MIN => { # minutes
  227. typeMask => 0b01111111,
  228. expMask => 0b00000000,
  229. type => 0b00100001,
  230. bias => 0,
  231. unit => 'min',
  232. calcFunc => \&valueCalcNumeric,
  233. },
  234. VIF_ON_TIME_HOURS => { # hours
  235. typeMask => 0b01111111,
  236. expMask => 0b00000000,
  237. type => 0b00100010,
  238. bias => 0,
  239. unit => 'hours',
  240. },
  241. VIF_ON_TIME_DAYS => { # days
  242. typeMask => 0b01111111,
  243. expMask => 0b00000000,
  244. type => 0b00100011,
  245. bias => 0,
  246. unit => 'days',
  247. },
  248. VIF_OP_TIME_SEC => { # seconds
  249. typeMask => 0b01111111,
  250. expMask => 0b00000000,
  251. type => 0b00100100,
  252. bias => 0,
  253. unit => 'sec',
  254. },
  255. VIF_OP_TIME_MIN => { # minutes
  256. typeMask => 0b01111111,
  257. expMask => 0b00000000,
  258. type => 0b00100101,
  259. bias => 0,
  260. unit => 'min',
  261. },
  262. VIF_OP_TIME_HOURS => { # hours
  263. typeMask => 0b01111111,
  264. expMask => 0b00000000,
  265. type => 0b00100110,
  266. bias => 0,
  267. unit => 'hours',
  268. },
  269. VIF_OP_TIME_DAYS => { # days
  270. typeMask => 0b01111111,
  271. expMask => 0b00000000,
  272. type => 0b00100111,
  273. bias => 0,
  274. unit => 'days',
  275. },
  276. VIF_ELECTRIC_POWER => { # 10(nnn-3) W 0.001W to 10000W
  277. typeMask => 0b01111000,
  278. expMask => 0b00000111,
  279. type => 0b00101000,
  280. bias => -3,
  281. unit => 'W',
  282. calcFunc => \&valueCalcNumeric,
  283. },
  284. VIF_THERMAL_POWER => { # 10(nnn) J/h 0.001kJ/h to 10000kJ/h
  285. typeMask => 0b01111000,
  286. expMask => 0b00000111,
  287. type => 0b00110000,
  288. bias => 0,
  289. unit => 'J/h',
  290. calcFunc => \&valueCalcNumeric,
  291. },
  292. VIF_VOLUME_FLOW => { # 10(nnn-6) m3/h 0.001l/h to 10000l/h
  293. typeMask => 0b01111000,
  294. expMask => 0b00000111,
  295. type => 0b00111000,
  296. bias => -6,
  297. unit => 'm³/h',
  298. calcFunc => \&valueCalcNumeric,
  299. },
  300. VIF_VOLUME_FLOW_EXT1 => { # 10(nnn-7) m3/min 0.0001l/min to 10000l/min
  301. typeMask => 0b01111000,
  302. expMask => 0b00000111,
  303. type => 0b01000000,
  304. bias => -7,
  305. unit => 'm³/min',
  306. calcFunc => \&valueCalcNumeric,
  307. },
  308. VIF_VOLUME_FLOW_EXT2 => { # 10(nnn-9) m3/s 0.001ml/s to 10000ml/s
  309. typeMask => 0b01111000,
  310. expMask => 0b00000111,
  311. type => 0b01001000,
  312. bias => -9,
  313. unit => 'm³/s',
  314. calcFunc => \&valueCalcNumeric,
  315. },
  316. VIF_MASS_FLOW => { # 10(nnn-3) kg/h 0.001kg/h to 10000kg/h
  317. typeMask => 0b01111000,
  318. expMask => 0b00000111,
  319. type => 0b01010000,
  320. bias => -3,
  321. unit => 'kg/h',
  322. calcFunc => \&valueCalcNumeric,
  323. },
  324. VIF_FLOW_TEMP => { # 10(nn-3) °C 0.001°C to 1°C
  325. typeMask => 0b01111100,
  326. expMask => 0b00000011,
  327. type => 0b01011000,
  328. bias => -3,
  329. unit => '°C',
  330. calcFunc => \&valueCalcNumeric,
  331. },
  332. VIF_RETURN_TEMP => { # 10(nn-3) °C 0.001°C to 1°C
  333. typeMask => 0b01111100,
  334. expMask => 0b00000011,
  335. type => 0b01011100,
  336. bias => -3,
  337. unit => '°C',
  338. calcFunc => \&valueCalcNumeric,
  339. },
  340. VIF_TEMP_DIFF => { # 10(nn-3) K 1mK to 1000mK
  341. typeMask => 0b01111100,
  342. expMask => 0b00000011,
  343. type => 0b01100000,
  344. bias => -3,
  345. unit => 'K',
  346. calcFunc => \&valueCalcNumeric,
  347. },
  348. VIF_EXTERNAL_TEMP => { # 10(nn-3) °C 0.001°C to 1°C
  349. typeMask => 0b01111100,
  350. expMask => 0b00000011,
  351. type => 0b01100100,
  352. bias => -3,
  353. unit => '°C',
  354. calcFunc => \&valueCalcNumeric,
  355. },
  356. VIF_PRESSURE => { # 10(nn-3) bar 1mbar to 1000mbar
  357. typeMask => 0b01111100,
  358. expMask => 0b00000011,
  359. type => 0b01101000,
  360. bias => -3,
  361. unit => 'bar',
  362. calcFunc => \&valueCalcNumeric,
  363. },
  364. VIF_TIME_POINT_DATE => { # data type G
  365. typeMask => 0b01111111,
  366. expMask => 0b00000000,
  367. type => 0b01101100,
  368. bias => 0,
  369. unit => '',
  370. calcFunc => \&valueCalcDate,
  371. },
  372. VIF_TIME_POINT_DATE_TIME => { # data type F
  373. typeMask => 0b01111111,
  374. expMask => 0b00000000,
  375. type => 0b01101101,
  376. bias => 0,
  377. unit => '',
  378. calcFunc => \&valueCalcDateTime,
  379. },
  380. VIF_HCA => { # Unit for Heat Cost Allocator, dimensonless
  381. typeMask => 0b01111111,
  382. expMask => 0b00000000,
  383. type => 0b01101110,
  384. bias => 0,
  385. unit => '',
  386. calcFunc => \&valueCalcNumeric,
  387. },
  388. VIF_FABRICATION_NO => { # Fabrication No
  389. typeMask => 0b01111111,
  390. expMask => 0b00000000,
  391. type => 0b01111000,
  392. bias => 0,
  393. unit => '',
  394. calcFunc => \&valueCalcNumeric,
  395. },
  396. VIF_OWNER_NO => { # Eigentumsnummer (used by Easymeter even though the standard allows this only for writing to a slave)
  397. typeMask => 0b01111111,
  398. expMask => 0b00000000,
  399. type => 0b01111001,
  400. bias => 0,
  401. unit => '',
  402. },
  403. VIF_AVERAGING_DURATION_SEC => { # seconds
  404. typeMask => 0b01111111,
  405. expMask => 0b00000000,
  406. type => 0b01110000,
  407. bias => 0,
  408. unit => 'sec',
  409. calcFunc => \&valueCalcNumeric,
  410. },
  411. VIF_AVERAGING_DURATION_MIN => { # minutes
  412. typeMask => 0b01111111,
  413. expMask => 0b00000000,
  414. type => 0b01110001,
  415. bias => 0,
  416. unit => 'min',
  417. calcFunc => \&valueCalcNumeric,
  418. },
  419. VIF_AVERAGING_DURATION_HOURS => { # hours
  420. typeMask => 0b01111111,
  421. expMask => 0b00000000,
  422. type => 0b01110010,
  423. bias => 0,
  424. unit => 'hours',
  425. },
  426. VIF_AVERAGING_DURATION_DAYS => { # days
  427. typeMask => 0b01111111,
  428. expMask => 0b00000000,
  429. type => 0b01110011,
  430. bias => 0,
  431. unit => 'days',
  432. },
  433. VIF_ACTUALITY_DURATION_SEC => { # seconds
  434. typeMask => 0b01111111,
  435. expMask => 0b00000000,
  436. type => 0b01110100,
  437. bias => 0,
  438. unit => 'sec',
  439. calcFunc => \&valueCalcNumeric,
  440. },
  441. VIF_ACTUALITY_DURATION_MIN => { # minutes
  442. typeMask => 0b01111111,
  443. expMask => 0b00000000,
  444. type => 0b01110101,
  445. bias => 0,
  446. unit => 'min',
  447. calcFunc => \&valueCalcNumeric,
  448. },
  449. VIF_ACTUALITY_DURATION_HOURS => { # hours
  450. typeMask => 0b01111111,
  451. expMask => 0b00000000,
  452. type => 0b01110110,
  453. bias => 0,
  454. unit => 'hours',
  455. },
  456. VIF_ACTUALITY_DURATION_DAYS => { # days
  457. typeMask => 0b01111111,
  458. expMask => 0b00000000,
  459. type => 0b01110111,
  460. bias => 0,
  461. unit => 'days',
  462. },
  463. );
  464. # Codes used with extension indicator $FD, see 8.4.4 on page 80
  465. my %VIFInfo_FD = (
  466. VIF_CREDIT => { # Credit of 10nn-3 of the nominal local legal currency units
  467. typeMask => 0b01111100,
  468. expMask => 0b00000011,
  469. type => 0b00000000,
  470. bias => -3,
  471. unit => '€',
  472. calcFunc => \&valueCalcNumeric,
  473. },
  474. VIF_DEBIT => { # Debit of 10nn-3 of the nominal local legal currency units
  475. typeMask => 0b01111100,
  476. expMask => 0b00000011,
  477. type => 0b00000100,
  478. bias => -3,
  479. unit => '€',
  480. calcFunc => \&valueCalcNumeric,
  481. },
  482. VIF_ACCESS_NO => { # Access number (transmission count)
  483. typeMask => 0b01111111,
  484. expMask => 0b00000000,
  485. type => 0b00001000,
  486. bias => 0,
  487. unit => '',
  488. calcFunc => \&valueCalcNumeric,
  489. },
  490. VIF_MEDIUM => { # Medium (as in fixed header)
  491. typeMask => 0b01111111,
  492. expMask => 0b00000000,
  493. type => 0b00001001,
  494. bias => 0,
  495. unit => '',
  496. calcFunc => \&valueCalcNumeric,
  497. },
  498. VIF_MODEL_VERSION => { # Model / Version
  499. typeMask => 0b01111111,
  500. expMask => 0b00000000,
  501. type => 0b00001100,
  502. bias => 0,
  503. unit => '',
  504. calcFunc => \&valueCalcNumeric,
  505. },
  506. VIF_ERROR_FLAGS => { # Error flags (binary)
  507. typeMask => 0b01111111,
  508. expMask => 0b00000000,
  509. type => 0b00010111,
  510. bias => 0,
  511. unit => '',
  512. calcFunc => \&valueCalcHex,
  513. },
  514. VIF_DURATION_SINCE_LAST_READOUT => { # Duration since last readout [sec(s)..day(s)]
  515. typeMask => 0b01111100,
  516. expMask => 0b00000011,
  517. type => 0b00101100,
  518. bias => 0,
  519. unit => 's',
  520. calcFunc => \&valueCalcTimeperiod,
  521. },
  522. VIF_VOLTAGE => { # 10nnnn-9 Volts
  523. typeMask => 0b01110000,
  524. expMask => 0b00001111,
  525. type => 0b01000000,
  526. bias => -9,
  527. unit => 'V',
  528. calcFunc => \&valueCalcNumeric,
  529. },
  530. VIF_ELECTRICAL_CURRENT => { # 10nnnn-12 Ampere
  531. typeMask => 0b01110000,
  532. expMask => 0b00001111,
  533. type => 0b01010000,
  534. bias => -12,
  535. unit => 'A',
  536. calcFunc => \&valueCalcNumeric,
  537. },
  538. VIF_RECEPTION_LEVEL => { # reception level of a received radio device.
  539. typeMask => 0b01111111,
  540. expMask => 0b00000000,
  541. type => 0b01110001,
  542. bias => 0,
  543. unit => 'dBm',
  544. calcFunc => \&valueCalcNumeric,
  545. },
  546. VIF_FD_RESERVED => { # Reserved
  547. typeMask => 0b01110000,
  548. expMask => 0b00000000,
  549. type => 0b01110000,
  550. bias => 0,
  551. unit => 'Reserved',
  552. },
  553. );
  554. # Codes used with extension indicator $FB
  555. my %VIFInfo_FB = (
  556. VIF_ENERGY => { # Energy 10(n-1) MWh 0.1MWh to 1MWh
  557. typeMask => 0b01111110,
  558. expMask => 0b00000001,
  559. type => 0b00000000,
  560. bias => -1,
  561. unit => 'MWh',
  562. calcFunc => \&valueCalcNumeric,
  563. },
  564. );
  565. # Codes used for an enhancement of VIFs other than $FD and $FB
  566. my %VIFInfo_other = (
  567. VIF_ERROR_NONE => {
  568. typeMask => 0b01111111,
  569. expMask => 0b00000000,
  570. type => 0b00000000,
  571. bias => 0,
  572. unit => 'No error',
  573. },
  574. VIF_TOO_MANY_DIFES => {
  575. typeMask => 0b01111111,
  576. expMask => 0b00000000,
  577. type => 0b00000001,
  578. bias => 0,
  579. unit => 'Too many DIFEs',
  580. },
  581. VIF_ILLEGAL_VIF_GROUP => {
  582. typeMask => 0b01111111,
  583. expMask => 0b00000000,
  584. type => 0b00001100,
  585. bias => 0,
  586. unit => 'Illegal VIF-Group',
  587. },
  588. VIF_PER_SECOND => {
  589. typeMask => 0b01111111,
  590. expMask => 0b00000000,
  591. type => 0b00100000,
  592. bias => 0,
  593. unit => 'per second',
  594. },
  595. VIF_PER_MINUTE => {
  596. typeMask => 0b01111111,
  597. expMask => 0b00000000,
  598. type => 0b00100001,
  599. bias => 0,
  600. unit => 'per minute',
  601. },
  602. VIF_PER_HOUR => {
  603. typeMask => 0b01111111,
  604. expMask => 0b00000000,
  605. type => 0b00100010,
  606. bias => 0,
  607. unit => 'per hour',
  608. },
  609. VIF_PER_DAY => {
  610. typeMask => 0b01111111,
  611. expMask => 0b00000000,
  612. type => 0b00100011,
  613. bias => 0,
  614. unit => 'per day',
  615. },
  616. VIF_PER_WEEK => {
  617. typeMask => 0b01111111,
  618. expMask => 0b00000000,
  619. type => 0b00100100,
  620. bias => 0,
  621. unit => 'per week',
  622. },
  623. VIF_PER_MONTH => {
  624. typeMask => 0b01111111,
  625. expMask => 0b00000000,
  626. type => 0b00100101,
  627. bias => 0,
  628. unit => 'per month',
  629. },
  630. VIF_PER_YEAR => {
  631. typeMask => 0b01111111,
  632. expMask => 0b00000000,
  633. type => 0b00100110,
  634. bias => 0,
  635. unit => 'per year',
  636. },
  637. VIF_PER_REVOLUTION => {
  638. typeMask => 0b01111111,
  639. expMask => 0b00000000,
  640. type => 0b00100111,
  641. bias => 0,
  642. unit => 'per revolution/measurement',
  643. },
  644. VIF_PER_INCREMENT_INPUT => {
  645. typeMask => 0b01111110,
  646. expMask => 0b00000000,
  647. type => 0b00101000,
  648. bias => 0,
  649. unit => 'increment per input pulse on input channnel #',
  650. calcFunc => \&valueCalcNumeric,
  651. },
  652. VIF_PER_INCREMENT_OUTPUT => {
  653. typeMask => 0b01111110,
  654. expMask => 0b00000000,
  655. type => 0b00101010,
  656. bias => 0,
  657. unit => 'increment per output pulse on output channnel #',
  658. calcFunc => \&valueCalcNumeric,
  659. },
  660. VIF_PER_LITER => {
  661. typeMask => 0b01111111,
  662. expMask => 0b00000000,
  663. type => 0b00101100,
  664. bias => 0,
  665. unit => 'per liter',
  666. },
  667. VIF_START_DATE_TIME => {
  668. typeMask => 0b01111111,
  669. expMask => 0b00000000,
  670. type => 0b00111001,
  671. bias => 0,
  672. unit => 'start date(/time) of',
  673. },
  674. VIF_ACCUMULATION_IF_POSITIVE => {
  675. typeMask => 0b01111111,
  676. expMask => 0b00000000,
  677. type => 0b00111011,
  678. bias => 0,
  679. unit => 'Accumulation only if positive contribution',
  680. },
  681. VIF_DURATION_NO_EXCEEDS => {
  682. typeMask => 0b01110111,
  683. expMask => 0b00000000,
  684. type => 0b01000001,
  685. bias => 0,
  686. unit => '# of exceeds',
  687. calcFunc => \&valueCalcu,
  688. },
  689. VIF_DURATION_LIMIT_EXCEEDED => {
  690. typeMask => 0b01110000,
  691. expMask => 0b00000000,
  692. type => 0b01010000,
  693. bias => 0,
  694. unit => 'duration of limit exceeded',
  695. calcFunc => \&valueCalcufnn,
  696. },
  697. VIF_MULTIPLICATIVE_CORRECTION_FACTOR => {
  698. typeMask => 0b01111000,
  699. expMask => 0b00000111,
  700. type => 0b01110000,
  701. bias => -6,
  702. unit => '',
  703. },
  704. VIF_MULTIPLICATIVE_CORRECTION_FACTOR_1000 => {
  705. typeMask => 0b01111111,
  706. expMask => 0b00000000,
  707. type => 0b01111101,
  708. bias => 0,
  709. unit => '',
  710. calcFunc => \&valueCalcMultCorr1000,
  711. },
  712. VIF_FUTURE_VALUE => {
  713. typeMask => 0b01111111,
  714. expMask => 0b00000000,
  715. type => 0b01111110,
  716. bias => 0,
  717. unit => '',
  718. },
  719. VIF_MANUFACTURER_SPECIFIC => {
  720. typeMask => 0b01111111,
  721. expMask => 0b00000000,
  722. type => 0b01111111,
  723. bias => 0,
  724. unit => 'manufacturer specific',
  725. },
  726. );
  727. # For Easymeter (manufacturer specific)
  728. my %VIFInfo_ESY = (
  729. VIF_ELECTRIC_POWER_PHASE => {
  730. typeMask => 0b01000000,
  731. expMask => 0b00000000,
  732. type => 0b00000000,
  733. bias => -2,
  734. unit => 'W',
  735. calcFunc => \&valueCalcNumeric,
  736. },
  737. VIF_ELECTRIC_POWER_PHASE_NO => {
  738. typeMask => 0b01111110,
  739. expMask => 0b00000000,
  740. type => 0b00101000,
  741. bias => 0,
  742. unit => 'phase #',
  743. calcFunc => \&valueCalcNumeric,
  744. },
  745. );
  746. # For Kamstrup (manufacturer specific)
  747. my %VIFInfo_KAM = (
  748. VIF_KAMSTRUP_INFO => {
  749. typeMask => 0b00000000,
  750. expMask => 0b00000000,
  751. type => 0b00000000,
  752. bias => 0,
  753. unit => '',
  754. },
  755. );
  756. # see 4.2.3, page 24
  757. my %validDeviceTypes = (
  758. 0x00 => 'Other',
  759. 0x01 => 'Oil',
  760. 0x02 => 'Electricity',
  761. 0x03 => 'Gas',
  762. 0x04 => 'Heat',
  763. 0x05 => 'Steam',
  764. 0x06 => 'Warm Water (30 °C ... 90 °C)',
  765. 0x07 => 'Water',
  766. 0x08 => 'Heat Cost Allocator',
  767. 0x09 => 'Compressed Air',
  768. 0x0a => 'Cooling load meter (Volume measured at return temperature: outlet)',
  769. 0x0b => 'Cooling load meter (Volume measured at flow temperature: inlet)',
  770. 0x0c => 'Heat (Volume measured at flow temperature: inlet)',
  771. 0x0d => 'Heat / Cooling load meter',
  772. 0x0e => 'Bus / System component',
  773. 0x0f => 'Unknown Medium',
  774. 0x10 => 'Reserved for utility meter',
  775. 0x11 => 'Reserved for utility meter',
  776. 0x12 => 'Reserved for utility meter',
  777. 0x13 => 'Reserved for utility meter',
  778. 0x14 => 'Calorific value',
  779. 0x15 => 'Hot water (> 90 °C)',
  780. 0x16 => 'Cold water',
  781. 0x17 => 'Dual register (hot/cold) Water meter',
  782. 0x18 => 'Pressure',
  783. 0x19 => 'A/D Converter',
  784. 0x1a => 'Smokedetector',
  785. 0x1b => 'Room sensor (e.g. temperature or humidity)',
  786. 0x1c => 'Gasdetector',
  787. 0x1d => 'Reserved for sensors',
  788. 0x1e => 'Reserved for sensors',
  789. 0x1f => 'Reserved for sensors',
  790. 0x20 => 'Breaker (electricity)',
  791. 0x21 => 'Valve (gas)',
  792. 0x22 => 'Reserved for switching devices',
  793. 0x23 => 'Reserved for switching devices',
  794. 0x24 => 'Reserved for switching devices',
  795. 0x25 => 'Customer unit (Display device)',
  796. 0x26 => 'Reserved for customer units',
  797. 0x27 => 'Reserved for customer units',
  798. 0x28 => 'Waste water',
  799. 0x29 => 'Garbage',
  800. 0x2a => 'Carbon dioxide',
  801. 0x2b => 'Environmental meter',
  802. 0x2c => 'Environmental meter',
  803. 0x2d => 'Environmental meter',
  804. 0x2e => 'Environmental meter',
  805. 0x2f => 'Environmental meter',
  806. 0x31 => 'OMS MUC',
  807. 0x32 => 'OMS unidirectional repeater',
  808. 0x33 => 'OMS bidirectional repeater',
  809. 0x37 => 'Radio converter (Meter side)',
  810. );
  811. # bitfield, errors can be combined, see 4.2.3.2 on page 22
  812. my %validStates = (
  813. 0x00 => 'no errors',
  814. 0x01 => 'application busy',
  815. 0x02 => 'any application error',
  816. 0x03 => 'abnormal condition/alarm',
  817. 0x04 => 'battery low',
  818. 0x08 => 'permanent error',
  819. 0x10 => 'temporary error',
  820. 0x20 => 'specific to manufacturer',
  821. 0x40 => 'specific to manufacturer',
  822. 0x80 => 'specific to manufacturer',
  823. );
  824. my %encryptionModes = (
  825. 0x00 => 'standard unsigned',
  826. 0x01 => 'signed data telegram',
  827. 0x02 => 'static telegram',
  828. 0x03 => 'reserved',
  829. );
  830. my %functionFieldTypes = (
  831. 0b00 => 'Instantaneous value',
  832. 0b01 => 'Maximum value',
  833. 0b10 => 'Minimum value',
  834. 0b11 => 'Value during error state',
  835. );
  836. sub type2string($$) {
  837. my $class = shift;
  838. my $type = shift;
  839. return $validDeviceTypes{$type} || 'unknown';
  840. }
  841. sub state2string($$) {
  842. my $class = shift;
  843. my $state = shift;
  844. my @result = ();
  845. if ($state) {
  846. foreach my $stateMask ( keys %validStates ) {
  847. push @result, $validStates{$stateMask} if $state & $stateMask;
  848. }
  849. } else {
  850. @result = ($validStates{0});
  851. }
  852. return @result;
  853. }
  854. sub checkCRC($$) {
  855. my $self = shift;
  856. my $data = shift;
  857. my $ctx = Digest::CRC->new(width=>16, init=>0x0000, xorout=>0xffff, refout=>0, poly=>0x3D65, refin=>0, cont=>0);
  858. $ctx->add($data);
  859. return $ctx->digest;
  860. }
  861. sub removeCRC($$)
  862. {
  863. my $self = shift;
  864. my $msg = shift;
  865. my $i;
  866. my $res;
  867. my $crc;
  868. my $blocksize = LL_BLOCK_SIZE;
  869. my $blocksize_with_crc = LL_BLOCK_SIZE + $self->{crc_size};
  870. my $crcoffset;
  871. my $msgLen = $self->{datalen}; # size without CRCs
  872. my $noOfBlocks = $self->{datablocks}; # total number of data blocks, each with a CRC appended
  873. my $rest = $msgLen % LL_BLOCK_SIZE; # size of the last data block, can be smaller than 16 bytes
  874. #print "crc_size $self->{crc_size}\n";
  875. return $msg if $self->{crc_size} == 0;
  876. # each block is 16 bytes + 2 bytes CRC
  877. #print "Länge $msgLen Anz. Blöcke $noOfBlocks rest $rest\n";
  878. for ($i=0; $i < $noOfBlocks; $i++) {
  879. $crcoffset = $blocksize_with_crc * $i + LL_BLOCK_SIZE;
  880. #print "$i: crc offset $crcoffset\n";
  881. if ($rest > 0 && $crcoffset + $self->{crc_size} > ($noOfBlocks - 1) * $blocksize_with_crc + $rest) {
  882. # last block is smaller
  883. $crcoffset = ($noOfBlocks - 1) * $blocksize_with_crc + $rest;
  884. #print "last crc offset $crcoffset\n";
  885. $blocksize = $msgLen - ($i * $blocksize);
  886. }
  887. $crc = unpack('n',substr($msg, $crcoffset, $self->{crc_size}));
  888. #printf("%d: CRC %x, calc %x blocksize $blocksize\n", $i, $crc, $self->checkCRC(substr($msg, $blocksize_with_crc*$i, $blocksize)));
  889. if ($crc != $self->checkCRC(substr($msg, $blocksize_with_crc*$i, $blocksize))) {
  890. $self->{errormsg} = "crc check failed for block $i";
  891. $self->{errorcode} = ERR_CRC_FAILED;
  892. return 0;
  893. }
  894. $res .= substr($msg, $blocksize_with_crc*$i, $blocksize);
  895. }
  896. return $res;
  897. }
  898. sub manId2hex($$)
  899. {
  900. my $class = shift;
  901. my $idascii = shift;
  902. return (ord(substr($idascii,1,1))-64) << 10 | (ord(substr($idascii,2,1))-64) << 5 | (ord(substr($idascii,3,1))-64);
  903. }
  904. sub manId2ascii($$)
  905. {
  906. my $class = shift;
  907. my $idhex = shift;
  908. return chr(($idhex >> 10) + 64) . chr((($idhex >> 5) & 0b00011111) + 64) . chr(($idhex & 0b00011111) + 64);
  909. }
  910. sub new {
  911. my $class = shift;
  912. my $self = {};
  913. bless $self, $class;
  914. $self->_initialize();
  915. return $self;
  916. }
  917. sub _initialize {
  918. my $self = shift;
  919. $self->{crc_size} = CRC_SIZE;
  920. $self->{frame_type} = FRAME_TYPE_A; # default
  921. }
  922. sub setCRCsize {
  923. my $self = shift;
  924. $self->{crc_size} = shift;
  925. }
  926. sub getCRCsize {
  927. my $self = shift;
  928. return $self->{crc_size};
  929. }
  930. sub decodeConfigword($) {
  931. my $self = shift;
  932. #printf("cw: %01x %01x\n", $self->{cw_1}, $self->{cw_2});
  933. $self->{cw_parts}{mode} = ($self->{cw_2} & 0b00011111);
  934. #printf("mode: %02x\n", $self->{cw_parts}{mode});
  935. if ($self->{cw_parts}{mode} == 5 || $self->{cw_parts}{mode} == 0) {
  936. $self->{cw_parts}{bidirectional} = ($self->{cw_2} & 0b10000000) >> 7;
  937. $self->{cw_parts}{accessability} = ($self->{cw_2} & 0b01000000) >> 6;
  938. $self->{cw_parts}{synchronous} = ($self->{cw_2} & 0b00100000) >> 5;
  939. $self->{cw_parts}{encrypted_blocks} = ($self->{cw_1} & 0b11110000) >> 4;
  940. $self->{cw_parts}{content} = ($self->{cw_1} & 0b00001100) >> 2;
  941. $self->{cw_parts}{repeated_access} = ($self->{cw_1} & 0b00000010) >> 1;
  942. $self->{cw_parts}{hops} = ($self->{cw_1} & 0b00000001);
  943. } #elsif ($self->{cw_parts}{mode} == 7) {
  944. # ToDo: wo kommt das dritte Byte her?
  945. # $self->{cw_parts}{mode} = $self->{cw} & 0b0000111100000000 >> 8;
  946. #}
  947. }
  948. sub decodeBCD($$$) {
  949. my $self = shift;
  950. my $digits = shift;
  951. my $bcd = shift;
  952. my $byte;
  953. my $val=0;
  954. my $mult=1;
  955. #print "bcd:" . unpack("H*", $bcd) . "\n";
  956. for (my $i = 0; $i < $digits/2; $i++) {
  957. $byte = unpack('C',substr($bcd, $i, 1));
  958. $val += ($byte & 0x0f) * $mult;
  959. $mult *= 10;
  960. $val += (($byte & 0xf0) >> 4) * $mult;
  961. $mult *= 10;
  962. }
  963. return $val;
  964. }
  965. sub findVIF($$$) {
  966. my $vif = shift;
  967. my $vifInfoRef = shift;
  968. my $dataBlockRef = shift;
  969. my $bias;
  970. if (defined $vifInfoRef) {
  971. VIFID: foreach my $vifType ( keys %$vifInfoRef ) {
  972. #printf "vifType $vifType VIF $vif typeMask $vifInfoRef->{$vifType}{typeMask} type $vifInfoRef->{$vifType}{type}\n";
  973. if (($vif & $vifInfoRef->{$vifType}{typeMask}) == $vifInfoRef->{$vifType}{type}) {
  974. #printf " match vifType $vifType\n";
  975. $bias = $vifInfoRef->{$vifType}{bias};
  976. $dataBlockRef->{exponent} = $vif & $vifInfoRef->{$vifType}{expMask};
  977. $dataBlockRef->{type} = $vifType;
  978. $dataBlockRef->{unit} = $vifInfoRef->{$vifType}{unit};
  979. if (defined $dataBlockRef->{exponent} && defined $bias) {
  980. $dataBlockRef->{valueFactor} = 10 ** ($dataBlockRef->{exponent} + $bias);
  981. } else {
  982. $dataBlockRef->{valueFactor} = 1;
  983. }
  984. $dataBlockRef->{calcFunc} = $vifInfoRef->{$vifType}{calcFunc};
  985. #printf("type %s bias %d exp %d valueFactor %d unit %s\n", $dataBlockRef->{type}, $bias, $dataBlockRef->{exponent}, $dataBlockRef->{valueFactor},$dataBlockRef->{unit});
  986. return 1;
  987. }
  988. }
  989. #printf "no match!\n";
  990. return 0;
  991. }
  992. return 1;
  993. }
  994. sub decodeValueInformationBlock($$$) {
  995. my $self = shift;
  996. my $vib = shift;
  997. my $dataBlockRef = shift;
  998. my $offset = 0;
  999. my $vif;
  1000. my $vifInfoRef;
  1001. my $vifExtension = 0;
  1002. my $vifExtNo = 0;
  1003. my $isExtension;
  1004. my $dataBlockExt;
  1005. my @VIFExtensions = ();
  1006. my $analyzeVIF = 1;
  1007. $dataBlockRef->{type} = '';
  1008. # The unit and multiplier is taken from the table for primary VIF
  1009. $vifInfoRef = \%VIFInfo;
  1010. EXTENSION: while (1) {
  1011. $vif = unpack('C', substr($vib,$offset++,1));
  1012. $isExtension = $vif & VIF_EXTENSION_BIT;
  1013. #printf("vif: %x isExtension %d\n", $vif, $isExtension);
  1014. # Is this an extension?
  1015. last EXTENSION if (!$isExtension);
  1016. # yes, process extension
  1017. $vifExtNo++;
  1018. if ($vifExtNo > 10) {
  1019. $dataBlockRef->{errormsg} = 'too many VIFE';
  1020. $dataBlockRef->{errorcode} = ERR_TOO_MANY_VIFE;
  1021. last EXTENSION;
  1022. }
  1023. # switch to extension codes
  1024. $vifExtension = $vif;
  1025. $vif &= ~VIF_EXTENSION_BIT;
  1026. #printf("vif ohne extension: %x\n", $vif);
  1027. if ($vif == 0x7D) {
  1028. $vifInfoRef = \%VIFInfo_FD;
  1029. } elsif ($vif == 0x7B) {
  1030. $vifInfoRef = \%VIFInfo_FB;
  1031. } elsif ($vif == 0x7C) {
  1032. # Plaintext VIF
  1033. my $vifLength = unpack('C', substr($vib,$offset++,1));
  1034. $dataBlockRef->{type} = "see unit";
  1035. $dataBlockRef->{unit} = unpack(sprintf("C%d",$vifLength), substr($vib, $offset, $vifLength));
  1036. $offset += $vifLength;
  1037. $analyzeVIF = 0;
  1038. last EXTENSION;
  1039. } elsif ($vif == 0x7F) {
  1040. if ($self->{manufacturer} eq 'ESY') {
  1041. # Easymeter
  1042. $vif = unpack('C', substr($vib,$offset++,1));
  1043. $vifInfoRef = \%VIFInfo_ESY;
  1044. } elsif ($self->{manufacturer} eq 'KAM') {
  1045. $vif = unpack('C', substr($vib,$offset++,1));
  1046. $vifInfoRef = \%VIFInfo_KAM;
  1047. } else {
  1048. # manufacturer specific data, can't be interpreted
  1049. $dataBlockRef->{type} = "MANUFACTURER SPECIFIC";
  1050. $dataBlockRef->{unit} = "";
  1051. $analyzeVIF = 0;
  1052. }
  1053. last EXTENSION;
  1054. } else {
  1055. # enhancement of VIFs other than $FD and $FB (see page 84ff.)
  1056. #print "other extension\n";
  1057. $dataBlockExt = {};
  1058. if ($self->{manufacturer} eq 'ESY') {
  1059. $vifInfoRef = \%VIFInfo_ESY;
  1060. $dataBlockExt->{value} = unpack('C',substr($vib,2,1)) * 100;
  1061. } else {
  1062. $dataBlockExt->{value} = $vif;
  1063. $vifInfoRef = \%VIFInfo_other;
  1064. }
  1065. if (findVIF($vif, $vifInfoRef, $dataBlockExt)) {
  1066. push @VIFExtensions, $dataBlockExt;
  1067. } else {
  1068. $dataBlockRef->{type} = 'unknown';
  1069. $dataBlockRef->{errormsg} = "unknown VIFE " . sprintf("%x", $vifExtension) . " at offset " . ($offset-1);
  1070. $dataBlockRef->{errorcode} = ERR_UNKNOWN_VIFE;
  1071. }
  1072. }
  1073. last EXTENSION if (!$isExtension);
  1074. }
  1075. if ($analyzeVIF) {
  1076. if (findVIF($vif, $vifInfoRef, $dataBlockRef) == 0) {
  1077. $dataBlockRef->{errormsg} = "unknown VIF " . sprintf("%x", $vifExtension) . " at offset " . ($offset-1);
  1078. $dataBlockRef->{errorcode} = ERR_UNKNOWN_VIFE;
  1079. }
  1080. }
  1081. $dataBlockRef->{VIFExtensions} = \@VIFExtensions;
  1082. if ($dataBlockRef->{type} eq '') {
  1083. $dataBlockRef->{type} = 'unknown';
  1084. $dataBlockRef->{errormsg} = sprintf("in VIFExtension %x unknown VIF %x",$vifExtension, $vif);
  1085. $dataBlockRef->{errorcode} = ERR_UNKNOWN_VIF;
  1086. }
  1087. return $offset;
  1088. }
  1089. sub decodeDataInformationBlock($$$) {
  1090. my $self = shift;
  1091. my $dib = shift;
  1092. my $dataBlockRef = shift;
  1093. my $dif;
  1094. my $tariff = 0;
  1095. my $difExtNo = 0;
  1096. my $offset;
  1097. my $devUnit = 0;
  1098. $dif = unpack('C', $dib);
  1099. $offset = 1;
  1100. my $isExtension = $dif & DIF_EXTENSION_BIT;
  1101. my $storageNo = ($dif & 0b01000000) >> 6;
  1102. my $functionField = ($dif & 0b00110000) >> 4;
  1103. my $df = $dif & 0b00001111;
  1104. #printf("dif %x storage %d\n", $dif, $storageNo);
  1105. EXTENSION: while ($isExtension) {
  1106. $dif = unpack('C', substr($dib,$offset,1));
  1107. last EXTENSION if (!defined $dif);
  1108. $offset++;
  1109. $isExtension = $dif & DIF_EXTENSION_BIT;
  1110. $difExtNo++;
  1111. if ($difExtNo > 10) {
  1112. $dataBlockRef->{errormsg} = 'too many DIFE';
  1113. $dataBlockRef->{errorcode} = ERR_TOO_MANY_DIFE;
  1114. last EXTENSION;
  1115. }
  1116. $storageNo |= ($dif & 0b00001111) << ($difExtNo*4)+1;
  1117. $tariff |= (($dif & 0b00110000 >> 4)) << (($difExtNo-1)*2);
  1118. $devUnit |= (($dif & 0b01000000 >> 6)) << ($difExtNo-1);
  1119. #printf("dife %x extno %d storage %d\n", $dif, $difExtNo, $storageNo);
  1120. }
  1121. $dataBlockRef->{functionField} = $functionField;
  1122. $dataBlockRef->{functionFieldText} = $functionFieldTypes{$functionField};
  1123. $dataBlockRef->{dataField} = $df;
  1124. $dataBlockRef->{storageNo} = $storageNo;
  1125. $dataBlockRef->{tariff} = $tariff;
  1126. $dataBlockRef->{devUnit} = $devUnit;
  1127. #printf("in DIF: datafield %x\n", $dataBlockRef->{dataField});
  1128. #print "offset in dif $offset\n";
  1129. return $offset;
  1130. }
  1131. sub decodeDataRecordHeader($$$) {
  1132. my $self = shift;
  1133. my $drh = shift;
  1134. my $dataBlockRef = shift;
  1135. my $offset = $self->decodeDataInformationBlock($drh,$dataBlockRef);
  1136. $offset += $self->decodeValueInformationBlock(substr($drh,$offset),$dataBlockRef);
  1137. #printf("in DRH: type %s\n", $dataBlockRef->{type});
  1138. return $offset;
  1139. }
  1140. sub decodePayload($$) {
  1141. my $self = shift;
  1142. my $payload = shift;
  1143. my $offset = 0;
  1144. my $dif;
  1145. my $vif;
  1146. my $scale;
  1147. my $value;
  1148. my $dataBlockNo = 0;
  1149. my @dataBlocks = ();
  1150. my $dataBlock;
  1151. PAYLOAD: while ($offset < length($payload)) {
  1152. $dataBlockNo++;
  1153. # create a new anonymous hash reference
  1154. $dataBlock = {};
  1155. $dataBlock->{number} = $dataBlockNo;
  1156. $dataBlock->{unit} = '';
  1157. while (unpack('C',substr($payload,$offset,1)) == 0x2f) {
  1158. # skip filler bytes
  1159. #printf("skipping filler at offset %d of %d\n", $offset, length($payload));
  1160. $offset++;
  1161. if ($offset >= length($payload)) {
  1162. last PAYLOAD;
  1163. }
  1164. }
  1165. $offset += $self->decodeDataRecordHeader(substr($payload,$offset), $dataBlock);
  1166. #printf("No. %d, type %x at offset %d\n", $dataBlockNo, $dataBlock->{dataField}, $offset-1);
  1167. if ($dataBlock->{dataField} == DIF_NONE) {
  1168. } elsif ($dataBlock->{dataField} == DIF_READOUT) {
  1169. $self->{errormsg} = "in datablock $dataBlockNo: unexpected DIF_READOUT";
  1170. $self->{errorcode} = ERR_UNKNOWN_DATAFIELD;
  1171. return 0;
  1172. } elsif ($dataBlock->{dataField} == DIF_BCD2) {
  1173. $value = $self->decodeBCD(2, substr($payload,$offset,1));
  1174. $offset += 1;
  1175. } elsif ($dataBlock->{dataField} == DIF_BCD4) {
  1176. $value = $self->decodeBCD(4, substr($payload,$offset,2));
  1177. $offset += 2;
  1178. } elsif ($dataBlock->{dataField} == DIF_BCD6) {
  1179. $value = $self->decodeBCD(6, substr($payload,$offset,3));
  1180. $offset += 3;
  1181. } elsif ($dataBlock->{dataField} == DIF_BCD8) {
  1182. $value = $self->decodeBCD(8, substr($payload,$offset,4));
  1183. $offset += 4;
  1184. } elsif ($dataBlock->{dataField} == DIF_BCD12) {
  1185. $value = $self->decodeBCD(12, substr($payload,$offset,6));
  1186. $offset += 6;
  1187. } elsif ($dataBlock->{dataField} == DIF_INT8) {
  1188. $value = unpack('C', substr($payload, $offset, 1));
  1189. $offset += 1;
  1190. } elsif ($dataBlock->{dataField} == DIF_INT16) {
  1191. $value = unpack('v', substr($payload, $offset, 2));
  1192. $offset += 2;
  1193. } elsif ($dataBlock->{dataField} == DIF_INT24) {
  1194. my @bytes = unpack('CCC', substr($payload, $offset, 3));
  1195. $offset += 3;
  1196. $value = $bytes[0] + $bytes[1] << 8 + $bytes[2] << 16;
  1197. } elsif ($dataBlock->{dataField} == DIF_INT32) {
  1198. $value = unpack('V', substr($payload, $offset, 4));
  1199. $offset += 4;
  1200. } elsif ($dataBlock->{dataField} == DIF_INT48) {
  1201. my @words = unpack('vvv', substr($payload, $offset, 6));
  1202. $value = $words[0] + $words[1] << 16 + $words[2] << 32;
  1203. $offset += 6;
  1204. } elsif ($dataBlock->{dataField} == DIF_INT64) {
  1205. $value = unpack('Q<', substr($payload, $offset, 8));
  1206. $offset += 8;
  1207. } elsif ($dataBlock->{dataField} == DIF_FLOAT32) {
  1208. #not allowed according to wmbus standard, Qundis seems to use it nevertheless
  1209. $value = unpack('f', substr($payload, $offset, 4));
  1210. $offset += 4;
  1211. } elsif ($dataBlock->{dataField} == DIF_VARLEN) {
  1212. my $lvar = unpack('C',substr($payload, $offset++, 1));
  1213. #print "in datablock $dataBlockNo: LVAR field " . sprintf("%x", $lvar) . "\n";
  1214. #printf "payload len %d offset %d\n", length($payload), $offset;
  1215. if ($lvar <= 0xbf) {
  1216. if ($dataBlock->{type} eq "MANUFACTURER SPECIFIC") {
  1217. # special handling, LSE seems to lie about this
  1218. $value = unpack('H*',substr($payload, $offset, $lvar));
  1219. #print "VALUE: " . $value . "\n";
  1220. } else {
  1221. # ASCII string with LVAR characters
  1222. $value = unpack('a*',substr($payload, $offset, $lvar));
  1223. if ($self->{manufacturer} eq 'ESY') {
  1224. # Easymeter stores the string backwards!
  1225. $value = reverse($value);
  1226. }
  1227. }
  1228. $offset += $lvar;
  1229. } elsif ($lvar >= 0xc0 && $lvar <= 0xcf) {
  1230. # positive BCD number with (LVAR - C0h) • 2 digits
  1231. $value = $self->decodeBCD(($lvar-0xc0)*2, substr($payload,$offset,($lvar-0xc0)));
  1232. $offset += ($lvar-0xc0);
  1233. } elsif ($lvar >= 0xd0 && $lvar <= 0xdf) {
  1234. # negative BCD number with (LVAR - D0h) • 2 digits
  1235. $value = -$self->decodeBCD(($lvar-0xd0)*2, substr($payload,$offset,($lvar-0xd0)));
  1236. $offset += ($lvar-0xd0);
  1237. } else {
  1238. $self->{errormsg} = "in datablock $dataBlockNo: unhandled LVAR field " . sprintf("%x", $lvar);
  1239. $self->{errorcode} = ERR_UNKNOWN_LVAR;
  1240. return 0;
  1241. }
  1242. } elsif ($dataBlock->{dataField} == DIF_SPECIAL) {
  1243. # special functions
  1244. #print "DIF_SPECIAL at $offset\n";
  1245. $value = unpack("H*", substr($payload,$offset));
  1246. last PAYLOAD;
  1247. } else {
  1248. $self->{errormsg} = "in datablock $dataBlockNo: unhandled datafield " . sprintf("%x",$dataBlock->{dataField});
  1249. $self->{errorcode} = ERR_UNKNOWN_DATAFIELD;
  1250. return 0;
  1251. }
  1252. if (defined $dataBlock->{calcFunc}) {
  1253. $dataBlock->{value} = $dataBlock->{calcFunc}->($value, $dataBlock);
  1254. #print "Value raw " . $value . " value calc " . $dataBlock->{value} ."\n";
  1255. } elsif (defined $value) {
  1256. $dataBlock->{value} = $value;
  1257. } else {
  1258. $dataBlock->{value} = "";
  1259. }
  1260. my $VIFExtensions = $dataBlock->{VIFExtensions};
  1261. for my $VIFExtension (@$VIFExtensions) {
  1262. $dataBlock->{extension} = $VIFExtension->{unit};
  1263. if (defined $VIFExtension->{calcFunc}) {
  1264. #printf("Extension value %d, valueFactor %d\n", $VIFExtension->{value}, $VIFExtension->{valueFactor});
  1265. $dataBlock->{extension} .= ", " . $VIFExtension->{calcFunc}->($VIFExtension->{value}, $dataBlock);
  1266. } elsif (defined $VIFExtension->{value}) {
  1267. $dataBlock->{extension} .= ", " . sprintf("%x",$VIFExtension->{value});
  1268. } else {
  1269. #$dataBlock->{extension} = "";
  1270. }
  1271. }
  1272. undef $value;
  1273. push @dataBlocks, $dataBlock;
  1274. }
  1275. $self->{datablocks} = \@dataBlocks;
  1276. return 1;
  1277. }
  1278. sub decrypt($) {
  1279. my $self = shift;
  1280. my $encrypted = shift;
  1281. # see 4.2.5.3, page 26
  1282. my $initVector = substr($self->{msg},2,8);
  1283. for (1..8) {
  1284. $initVector .= pack('C',$self->{access_no});
  1285. }
  1286. my $cipher = Crypt::Mode::CBC->new('AES', 2);
  1287. return $cipher->decrypt($encrypted, $self->{aeskey}, $initVector);
  1288. }
  1289. sub decrypt_mode7($) {
  1290. my $self = shift;
  1291. my $encrypted = shift;
  1292. # see 9.2.4, page 59
  1293. my $initVector = '';
  1294. for (1..16) {
  1295. $initVector .= pack('C',0x00);
  1296. }
  1297. my $cipher = Crypt::Mode::CBC->new('AES', 2);
  1298. return $cipher->decrypt($encrypted, $self->{aeskey}, $initVector);
  1299. }
  1300. # Generate MAC of data
  1301. #
  1302. # Parameter 1: private key as byte string, 16bytes
  1303. # Parameter 2: data fro which mac should be calculated in hexadecimal format, len variable
  1304. # Parameter 3: length of MAC to be generated in bytes
  1305. #
  1306. # Returns: MAC in hexadecimal format
  1307. #
  1308. # This function currently supports data with lentgh of less then 16bytes,
  1309. # MAC for longer data is untested but specified
  1310. #
  1311. # copied from 10_EnOcean.pm
  1312. sub generateMAC($$$$) {
  1313. my $self = shift;
  1314. my $private_key = $_[0];
  1315. my $data = $_[1];
  1316. my $cmac_len = $_[2];
  1317. #print "Calculating MAC for data $data\n";
  1318. # Pack data to 16byte byte string, padd with 10..0 binary
  1319. my $data_expanded = pack('H32', $data.'80');
  1320. #print "Exp. data ".unpack('H32', $data_expanded)."\n";
  1321. # Constants according to specification
  1322. my $const_zero = pack('H32','00');
  1323. my $const_rb = pack('H32', '00000000000000000000000000000087');
  1324. # Encrypt zero data with private key to get L
  1325. my $cipher = Crypt::Rijndael->new($private_key);
  1326. my $l = $cipher->encrypt($const_zero);
  1327. #print "L ".unpack('H32', $l)."\n";
  1328. #print "L ".unpack('B128', $l)."\n";
  1329. # Expand L to 128bit string
  1330. my $l_bit = unpack('B128', $l);
  1331. # K1 and K2 stored as 128bit string
  1332. my $k1_bit;
  1333. my $k2_bit;
  1334. # K1 and K2 as binary
  1335. my $k1;
  1336. my $k2;
  1337. # Store L << 1 in K1
  1338. $l_bit =~ /^.(.{127})/;
  1339. $k1_bit = $1.'0';
  1340. $k1 = pack('B128', $k1_bit);
  1341. # If MSB of L == 1, K1 = K1 XOR const_Rb
  1342. if($l_bit =~ m/^1/) {
  1343. #print "MSB of L is set\n";
  1344. $k1 = $k1 ^ $const_rb;
  1345. $k1_bit = unpack('B128', $k1);
  1346. } else {
  1347. #print "MSB of L is unset\n";
  1348. }
  1349. # Store K1 << 1 in K2
  1350. $k1_bit =~ /^.(.{127})/;
  1351. $k2_bit = $1.'0';
  1352. $k2 = pack('B128', $k2_bit);
  1353. # If MSB of K1 == 1, K2 = K2 XOR const_Rb
  1354. if($k1_bit =~ m/^1/) {
  1355. #print "MSB of K1 is set\n";
  1356. $k2 = $k2 ^ $const_rb;
  1357. } else {
  1358. #print "MSB of K1 is unset\n";
  1359. }
  1360. # XOR data with K2
  1361. $data_expanded ^= $k2;
  1362. # Encrypt data
  1363. my $cmac = $cipher->encrypt($data_expanded);
  1364. #print "CMAC ".unpack('H32', $cmac)."\n";
  1365. # Extract specified len of MAC
  1366. my $cmac_pattern = '^(.{'.($cmac_len * 2).'})';
  1367. unpack('H32', $cmac) =~ /$cmac_pattern/;
  1368. # Return MAC in hexadecimal format
  1369. return uc($1);
  1370. }
  1371. sub decodeAFL($$) {
  1372. my $self = shift;
  1373. my $afl = shift;
  1374. my $offset = 0;
  1375. $self->{afl}{fcl} = unpack('v', $afl);
  1376. $offset += 2;
  1377. $self->{afl}{fcl_mf} = ($self->{afl}{fcl} & 0b0100000000000000) != 0;
  1378. $self->{afl}{fcl_mclp} = ($self->{afl}{fcl} & 0b0010000000000000) != 0;
  1379. $self->{afl}{fcl_mlp} = ($self->{afl}{fcl} & 0b0001000000000000) != 0;
  1380. $self->{afl}{fcl_mcrp} = ($self->{afl}{fcl} & 0b0000100000000000) != 0;
  1381. $self->{afl}{fcl_macp} = ($self->{afl}{fcl} & 0b0000010000000000) != 0;
  1382. $self->{afl}{fcl_kip} = ($self->{afl}{fcl} & 0b0000001000000000) != 0;
  1383. $self->{afl}{fcl_fid} = $self->{afl}{fcl} & 0b0000000011111111;
  1384. if ($self->{afl}{fcl_mclp}) {
  1385. # AFL Message Control Field (AFL.MCL)
  1386. $self->{afl}{mcl} = unpack('C', substr($afl, $offset, 1));
  1387. $offset += 1;
  1388. $self->{afl}{mcl_mlmp} = ($self->{afl}{mcl} & 0b01000000) != 0;
  1389. $self->{afl}{mcl_mcmp} = ($self->{afl}{mcl} & 0b00100000) != 0;
  1390. $self->{afl}{mcl_kimp} = ($self->{afl}{mcl} & 0b00010000) != 0;
  1391. $self->{afl}{mcl_at} = ($self->{afl}{mcl} & 0b00001111);
  1392. }
  1393. if ($self->{afl}{fcl_mcrp}) {
  1394. # AFL Message Counter Field (AFL.MCR)
  1395. $self->{afl}{mcr} = unpack('V', substr($afl, $offset));
  1396. #printf "AFL MC %08x\n", $self->{afl}{mcr};
  1397. $offset += 4;
  1398. }
  1399. if ($self->{afl}{fcl_mlp}) {
  1400. # AFL Message Length Field (AFL.ML)
  1401. $self->{afl}{ml} = unpack('v', substr($afl, $offset));
  1402. $offset += 2;
  1403. }
  1404. if ($self->{afl}{fcl_macp}) {
  1405. # AFL MAC Field (AFL.MCL)
  1406. # The length of the MAC field depends on the selected option AFL.MCL.AT indicated by the
  1407. # AFL.MCL field.
  1408. my $mac_len = 0;
  1409. if ($self->{afl}{mcl_at} == 4) {
  1410. $mac_len = 4;
  1411. $self->{afl}{mac} = unpack('N', substr($afl, $offset, $mac_len));
  1412. } elsif ($self->{afl}{mcl_at} == 5) {
  1413. $mac_len = 8;
  1414. $self->{afl}{mac} = (unpack('N', substr($afl, $offset, 4))) << 32 | ((unpack('N', substr($afl, $offset+4, 4))));
  1415. } elsif ($self->{afl}{mcl_at} == 6) {
  1416. $mac_len = 12;
  1417. } elsif ($self->{afl}{mcl_at} == 7) {
  1418. $mac_len = 16;
  1419. }
  1420. #printf "AFL MAC %16x\n", $self->{afl}{mac};
  1421. $offset += $mac_len;
  1422. }
  1423. if ($self->{afl}{fcl_kip}) {
  1424. # AFL Key Information-Field (AFL.KI)
  1425. $self->{afl}{ki} = unpack('v', $afl);
  1426. $self->{afl}{ki_key_version} = ($self->{afl}{ki} & 0b1111111100000000) >> 8;
  1427. $self->{afl}{ki_kdf_selection} = ($self->{afl}{ki} & 0b0000000001110000) >> 4;
  1428. $self->{afl}{ki_key_id} = ($self->{afl}{ki} & 0b0000000000001111);
  1429. $offset += 2;
  1430. }
  1431. return $offset;
  1432. }
  1433. sub decodeApplicationLayer($) {
  1434. my $self = shift;
  1435. my $applicationlayer = $self->{applicationlayer};
  1436. my $payload;
  1437. #print unpack("H*", $applicationlayer) . "\n";
  1438. if ($self->{errorcode} != ERR_NO_ERROR) {
  1439. # CRC check failed
  1440. return 0;
  1441. }
  1442. $self->{cifield} = unpack('C', $applicationlayer);
  1443. my $offset = 1;
  1444. if ($self->{cifield} == CI_ELL_2) {
  1445. # Extended Link Layer
  1446. ($self->{ell}{cc}, $self->{ell}{access_no}) = unpack('CC', substr($applicationlayer,$offset));
  1447. $offset += 2;
  1448. } elsif ($self->{cifield} == CI_ELL_6) {
  1449. # Extended Link Layer
  1450. ($self->{ell}{cc}, $self->{ell}{access_no}) = unpack('CC', substr($applicationlayer,$offset));
  1451. $offset += 6;
  1452. } elsif ($self->{cifield} == CI_ELL_8) {
  1453. # Extended Link Layer, payload CRC is part of (encrypted) payload
  1454. ($self->{ell}{cc}, $self->{ell}{access_no}, $self->{ell}{session_number}) = unpack('CCV', substr($applicationlayer, $offset));
  1455. $offset += 6;
  1456. } elsif ($self->{cifield} == CI_ELL_16) {
  1457. # Extended Link Layer
  1458. ($self->{ell}{cc}, $self->{ell}{access_no}, $self->{ell}{m2}, $self->{ell}{a2}, $self->{ell}{session_number}) = unpack('CCvC6V', substr($applicationlayer,$offset));
  1459. $offset += 14;
  1460. }
  1461. if (exists($self->{ell})) {
  1462. $self->{ell}{session_number_enc} = $self->{ell}{session_number} >> 29;
  1463. $self->{ell}{session_number_time} = ($self->{ell}{session_number} & 0b0001111111111111111111111111111) >> 4;
  1464. $self->{ell}{session_number_session} = $self->{ell}{session_number} & 0b1111;
  1465. $self->{isEncrypted} = $self->{ell}{session_number_enc} != 0;
  1466. $self->{decrypted} = 0;
  1467. if ($self->{isEncrypted}) {
  1468. if ($self->{aeskey}) {
  1469. if ($hasCTR) {
  1470. # AES IV
  1471. # M-field, A-field, CC, SN, 00, 0000
  1472. my $initVector = pack("v", $self->{mfield}) . $self->{afield} . pack("CV", $self->{ell}{cc}, $self->{ell}{session_number}) . pack("H*", "000000");
  1473. my $m = Crypt::Mode::CTR->new('AES', 1);
  1474. my $ciphertext = substr($applicationlayer,$offset); # payload CRC must also be decrypted
  1475. #printf("##ciphertext: %s\n", unpack("H*", $ciphertext));
  1476. $payload = $m->decrypt($ciphertext, $self->{aeskey}, $initVector);
  1477. #printf("##plaintext %s\n", unpack("H*", $payload));
  1478. } else {
  1479. $self->{errormsg} = 'Crypt::Mode::CTR is not installed, please install it (sudo cpan -i Crypt::Mode::CTR)';
  1480. $self->{errorcode} = ERR_CIPHER_NOT_INSTALLED;
  1481. return 0;
  1482. }
  1483. } else {
  1484. $self->{errormsg} = 'encrypted message and no aeskey provided';
  1485. $self->{errorcode} = ERR_NO_AESKEY;
  1486. return 0;
  1487. }
  1488. }
  1489. $self->{ell}{crc} = unpack('v', $payload);
  1490. $offset += 2;
  1491. # PayloadCRC is a cyclic redundancy check covering the remainder of the frame (excluding the CRC fields)
  1492. # payload CRC is also encrypted
  1493. if ($self->{ell}{crc} != $self->checkCRC(substr($payload, 2, $self->{lfield}-20))) {
  1494. #printf("crc %x, calculated %x\n", $self->{ell}{crc}, $self->checkCRC(substr($payload, 2, $self->{lfield}-20)));
  1495. $self->{errormsg} = "Payload CRC check failed on ELL" . ($self->{isEncrypted} ? ", wrong AES key?" : "");
  1496. $self->{errorcode} = ERR_CRC_FAILED;
  1497. return 0;
  1498. } else {
  1499. $self->{decrypted} = 1;
  1500. }
  1501. $applicationlayer = $payload;
  1502. $offset = 2; # skip PayloadCRC
  1503. }
  1504. if ($offset > 1) {
  1505. $applicationlayer = substr($applicationlayer,$offset);
  1506. $self->{cifield} = unpack('C', $applicationlayer);
  1507. $offset = 1;
  1508. if ($self->{cifield} == CI_AFL) {
  1509. # Authentification and Fragmentation Layer
  1510. $self->{afl}{afll} = unpack('C', substr($applicationlayer, $offset));
  1511. #printf "AFL AFLL %02x\n", $self->{afl}{afll};
  1512. $offset += 1;
  1513. $self->decodeAFL(substr($applicationlayer,$offset,$self->{afl}{afll}));
  1514. $offset += $self->{afl}{afll};
  1515. if ($self->{afl}{fcl_mf}) {
  1516. $self->{errormsg} = "fragmented messages are not yet supported";
  1517. $self->{errorcode} = ERR_FRAGMENT_UNSUPPORTED;
  1518. return 0;
  1519. }
  1520. }
  1521. }
  1522. if ($offset > 1) {
  1523. $applicationlayer = substr($applicationlayer,$offset);
  1524. $self->{cifield} = unpack('C', $applicationlayer);
  1525. $offset = 1;
  1526. }
  1527. # initialize some fields
  1528. $self->{cw_1} = 0;
  1529. $self->{cw_2} = 0;
  1530. $self->{status} = 0;
  1531. $self->{statusstring} = "";
  1532. $self->{access_no} = 0;
  1533. if ($self->{cifield} == CI_RESP_4 || $self->{cifield} == CI_RESP_SML_4) {
  1534. # Short header
  1535. #print "short header\n";
  1536. ($self->{access_no}, $self->{status}, $self->{cw_1}, $self->{cw_2}) = unpack('CCCC', substr($applicationlayer,$offset));
  1537. $offset += 4;
  1538. } elsif ($self->{cifield} == CI_RESP_12 || $self->{cifield} == CI_RESP_SML_12) {
  1539. # Long header
  1540. #print "Long header\n";
  1541. ($self->{meter_id}, $self->{meter_man}, $self->{meter_vers}, $self->{meter_dev}, $self->{access_no}, $self->{status}, $self->{cw_1}, $self->{cw_2})
  1542. = unpack('VvCCCCCC', substr($applicationlayer,$offset));
  1543. $self->{meter_id} = sprintf("%08d", $self->{meter_id});
  1544. $self->{meter_devtypestring} = $validDeviceTypes{$self->{meter_dev}} || 'unknown';
  1545. $self->{meter_manufacturer} = uc($self->manId2ascii($self->{meter_man}));
  1546. $offset += 12;
  1547. } elsif ($self->{cifield} == CI_RESP_0) {
  1548. # no header
  1549. #print "No header\n";
  1550. } elsif ($self->{cifield} == 0x79 && $self->{manufacturer} eq 'KAM') {
  1551. #print "Kamstrup compact frame header\n";
  1552. $self->{format_signature} = unpack("v", substr($applicationlayer,$offset, 2));
  1553. $offset += 2;
  1554. $self->{full_frame_payload_crc} = unpack("v", substr($applicationlayer, $offset, 2));
  1555. $offset += 2;
  1556. if ($self->{format_signature} == $self->checkCRC(pack("H*", "02FF20" . "0413" . "4413"))) {
  1557. # Info, Volume, Target Volume
  1558. # convert into full frame
  1559. $applicationlayer = pack("H*", "02FF20") . substr($applicationlayer, 5, 2) # Info
  1560. . pack("H*", "0413") . substr($applicationlayer,7,4) # volume
  1561. . pack("H*", "4413") . substr($applicationlayer,11,4); # target volume
  1562. $offset = 0;
  1563. } elsif ($self->{format_signature} == $self->checkCRC(pack("H*", "02FF20" . "0413" . "523B"))) {
  1564. # Info, Volume, Max flow
  1565. # convert into full frame
  1566. $applicationlayer = pack("H*", "02FF20") . substr($applicationlayer, 5, 2) # Info
  1567. . pack("H*", "0413") . substr($applicationlayer,7,4) # volume
  1568. . pack("H*", "523B") . substr($applicationlayer,11,2); # max flow
  1569. $offset = 0;
  1570. } elsif ($self->{format_signature} == $self->checkCRC(pack("H*", "02FF20" . "0413" . "4413" . "615B" . "6167"))) {
  1571. # Info, Volume, Max flow, flow temp, external temp
  1572. # convert into full frame
  1573. $applicationlayer = pack("H*", "02FF20") . substr($applicationlayer, 5, 2) # Info
  1574. . pack("H*", "0413") . substr($applicationlayer,7,4) # volume
  1575. . pack("H*", "4413") . substr($applicationlayer,11,4) # target volume
  1576. . pack("H*", "615B") . substr($applicationlayer,15,1) # flow temp
  1577. . pack("H*", "6167") . substr($applicationlayer,16,1); # external temp
  1578. $offset = 0;
  1579. } else {
  1580. $self->{errormsg} = 'Unknown Kamstrup compact frame format';
  1581. $self->{errorcode} = ERR_UNKNOWN_COMPACT_FORMAT;
  1582. return 0;
  1583. }
  1584. if ($self->{full_frame_payload_crc} != $self->checkCRC($applicationlayer)) {
  1585. $self->{errormsg} = 'Kamstrup compact frame format payload CRC error';
  1586. $self->{errorcode} = ERR_CRC_FAILED;
  1587. return 0;
  1588. }
  1589. } else {
  1590. # unsupported
  1591. $self->decodeConfigword();
  1592. $self->{errormsg} = 'Unsupported CI Field ' . sprintf("%x", $self->{cifield}) . ", remaining payload is " . unpack("H*", substr($applicationlayer,$offset));
  1593. $self->{errorcode} = ERR_UNKNOWN_CIFIELD;
  1594. return 0;
  1595. }
  1596. $self->{statusstring} = join(", ", $self->state2string($self->{status}));
  1597. $self->decodeConfigword();
  1598. $self->{encryptionMode} = $encryptionModes{$self->{cw_parts}{mode}};
  1599. if ($self->{cw_parts}{mode} == 0) {
  1600. # no encryption
  1601. if (!defined $self->{isEncrypted}) {
  1602. $self->{isEncrypted} = 0;
  1603. $self->{decrypted} = 1;
  1604. }
  1605. $payload = substr($applicationlayer, $offset);
  1606. } elsif ($self->{cw_parts}{mode} == 5) {
  1607. # data is encrypted with AES 128, dynamic init vector
  1608. # decrypt data before further processing
  1609. $self->{isEncrypted} = 1;
  1610. $self->{decrypted} = 0;
  1611. if ($self->{aeskey}) {
  1612. if ($hasCBC) {
  1613. $payload = $self->decrypt(substr($applicationlayer,$offset));
  1614. #printf("decrypted payload %s\n", unpack("H*", $payload));
  1615. if (unpack('n', $payload) == 0x2f2f) {
  1616. $self->{decrypted} = 1;
  1617. } else {
  1618. # Decryption verification failed
  1619. $self->{errormsg} = 'Decryption failed, wrong key?';
  1620. $self->{errorcode} = ERR_DECRYPTION_FAILED;
  1621. #printf("%x\n", unpack('n', $payload));
  1622. return 0;
  1623. }
  1624. } else {
  1625. $self->{errormsg} = 'Crypt::Mode::CBC is not installed, please install it (sudo cpan -i Crypt::Mode::CBC)';
  1626. $self->{errorcode} = ERR_CIPHER_NOT_INSTALLED;
  1627. return 0;
  1628. }
  1629. } else {
  1630. $self->{errormsg} = 'encrypted message and no aeskey provided';
  1631. $self->{errorcode} = ERR_NO_AESKEY;
  1632. return 0;
  1633. }
  1634. } else {
  1635. # error, encryption mode not implemented
  1636. $self->{errormsg} = sprintf('Encryption mode %x not implemented', $self->{cw_parts}{mode});
  1637. $self->{errorcode} = ERR_UNKNOWN_ENCRYPTION;
  1638. $self->{isEncrypted} = 1;
  1639. $self->{decrypted} = 0;
  1640. return 0;
  1641. }
  1642. if ($self->{cifield} == CI_RESP_SML_4 || $self->{cifield} == CI_RESP_SML_12) {
  1643. # payload is SML encoded, that's not implemented
  1644. $self->{errormsg} = "payload is SML encoded, can't be decoded, SML payload is " . unpack("H*", substr($applicationlayer,$offset));
  1645. $self->{errorcode} = ERR_SML_PAYLOAD;
  1646. return 0;
  1647. } else {
  1648. return $self->decodePayload($payload);
  1649. }
  1650. }
  1651. sub decodeLinkLayer($$)
  1652. {
  1653. my $self = shift;
  1654. my $linklayer = shift;
  1655. ($self->{lfield}, $self->{cfield}, $self->{mfield}) = unpack('CCv', $linklayer);
  1656. $self->{afield} = substr($linklayer,4,6);
  1657. $self->{afield_id} = sprintf("%08d", $self->decodeBCD(8,substr($linklayer,4,4)));
  1658. ($self->{afield_ver}, $self->{afield_type}) = unpack('CC', substr($linklayer,8,2));
  1659. #printf("lfield %d\n", $self->{lfield});
  1660. if ($self->{frame_type} eq FRAME_TYPE_A) {
  1661. if ($self->{crc_size} > 0) {
  1662. $self->{crc0} = unpack('n', substr($linklayer,TL_BLOCK_SIZE, $self->{crc_size}));
  1663. #printf("crc0 %x calc %x\n", $self->{crc0}, $self->checkCRC(substr($linklayer,0,10)));
  1664. if ($self->{crc0} != $self->checkCRC(substr($linklayer,0,TL_BLOCK_SIZE))) {
  1665. $self->{errormsg} = "CRC check failed on link layer";
  1666. $self->{errorcode} = ERR_CRC_FAILED;
  1667. #print "CRC check failed on link layer\n";
  1668. return 0;
  1669. }
  1670. }
  1671. # header block is 10 bytes + 2 bytes CRC, each following block is 16 bytes + 2 bytes CRC, the last block may be smaller
  1672. $self->{datalen} = $self->{lfield} - (TL_BLOCK_SIZE - 1); # this is without CRCs and the lfield itself
  1673. $self->{datablocks} = int($self->{datalen} / LL_BLOCK_SIZE);
  1674. $self->{datablocks}++ if $self->{datalen} % LL_BLOCK_SIZE != 0;
  1675. $self->{msglen} = TL_BLOCK_SIZE + $self->{crc_size} + $self->{datalen} + $self->{datablocks} * $self->{crc_size};
  1676. #printf("calc len %d, actual %d\n", $self->{msglen}, length($self->{msg}));
  1677. $self->{applicationlayer} = $self->removeCRC(substr($self->{msg},TL_BLOCK_SIZE + $self->{crc_size}));
  1678. } else {
  1679. # FRAME TYPE B
  1680. # each block is at most 129 bytes long.
  1681. # first contains the header (TL_BLOCK), L field and trailing crc
  1682. # L field is included in crc calculation
  1683. # each following block contains only data and trailing crc
  1684. my $length = 129;
  1685. if ($self->{lfield} < $length) {
  1686. $length = $self->{lfield};
  1687. }
  1688. if ($self->{crc_size} > 0) {
  1689. $length -= $self->{crc_size};
  1690. $length++; # for L field
  1691. #print "length: $length\n";
  1692. $self->{crc0} = unpack('n', substr($self->{msg}, $length, $self->{crc_size}));
  1693. #printf "crc in msg %x crc calculated %x\n", $self->{crc0}, $self->checkCRC(substr($self->{msg}, 0, $length));
  1694. if ($self->{crc0} != $self->checkCRC(substr($self->{msg}, 0, $length))) {
  1695. $self->{errormsg} = "CRC check failed on block 1";
  1696. $self->{errorcode} = ERR_CRC_FAILED;
  1697. return 0;
  1698. }
  1699. }
  1700. $self->{datablocks} = int($self->{lfield} / 129);
  1701. $self->{datablocks}++ if $self->{lfield} % 129 != 0;
  1702. # header block is 10 bytes, following block
  1703. $self->{datalen} = $self->{lfield} - (TL_BLOCK_SIZE - 1) - ($self->{datablocks} * $self->{crc_size}) ; # this is with CRCs but without the lfield itself
  1704. $self->{msglen} = $self->{lfield};
  1705. if ($self->{datablocks} == 2) {
  1706. # TODO
  1707. } else {
  1708. $self->{applicationlayer} = substr($self->{msg}, TL_BLOCK_SIZE, $length - TL_BLOCK_SIZE); # - $self->{crc_size});
  1709. }
  1710. }
  1711. if (length($self->{msg}) > $self->{msglen}) {
  1712. $self->{remainingData} = substr($self->{msg},$self->{msglen});
  1713. } elsif (length($self->{msg}) < $self->{msglen}) {
  1714. $self->{errormsg} = "message too short, expected " . $self->{msglen} . ", got " . length($self->{msg}) . " bytes";
  1715. $self->{errorcode} = ERR_MSG_TOO_SHORT;
  1716. return 0;
  1717. }
  1718. # according to the MBus spec only upper case letters are allowed.
  1719. # some devices send lower case letters none the less
  1720. # convert to upper case to make them spec conformant
  1721. $self->{manufacturer} = uc($self->manId2ascii($self->{mfield}));
  1722. $self->{typestring} = $validDeviceTypes{$self->{afield_type}} || 'unknown';
  1723. return 1;
  1724. }
  1725. sub setFrameType($)
  1726. {
  1727. my $self = shift;
  1728. $self->{frame_type} = shift;
  1729. }
  1730. sub parse($$)
  1731. {
  1732. my $self = shift;
  1733. $self->{msg} = shift;
  1734. $self->{errormsg} = '';
  1735. $self->{errorcode} = ERR_NO_ERROR;
  1736. if (length($self->{msg}) < 12) {
  1737. $self->{errormsg} = "Message too short";
  1738. $self->{errorcode} = ERR_MSG_TOO_SHORT;
  1739. return 1;
  1740. }
  1741. if (substr($self->{msg}, 0, 4) eq pack("H*", "543D543D")) {
  1742. $self->setFrameType(FRAME_TYPE_B);
  1743. $self->{msg} = substr($self->{msg},4);
  1744. }
  1745. if ($self->decodeLinkLayer(substr($self->{msg},0,12)) != 0) {
  1746. $self->{linkLayerOk} = 1;
  1747. return $self->decodeApplicationLayer();
  1748. }
  1749. return 0;
  1750. }
  1751. sub parseLinkLayer($$)
  1752. {
  1753. my $self = shift;
  1754. $self->{msg} = shift;
  1755. $self->{errormsg} = '';
  1756. $self->{errorcode} = ERR_NO_ERROR;
  1757. $self->{linkLayerOk} = $self->decodeLinkLayer(substr($self->{msg},0,12));
  1758. return $self->{linkLayerOk};
  1759. }
  1760. sub parseApplicationLayer($)
  1761. {
  1762. my $self = shift;
  1763. $self->{errormsg} = '';
  1764. $self->{errorcode} = ERR_NO_ERROR;
  1765. return $self->decodeApplicationLayer();
  1766. }
  1767. 1;