WMBus.pm 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601
  1. # $Id: WMBus.pm 12532 2016-11-08 19:34:33Z kaihs $
  2. package WMBus;
  3. use strict;
  4. use warnings;
  5. use feature qw(say);
  6. use Crypt::CBC; # libcrypt-cbc-perl
  7. use Digest::CRC; # libdigest-crc-perl
  8. # there seems to be no debian package for Crypt::OpenSSL::AES, so use
  9. # sudo apt-get install libssl-dev
  10. # sudo cpan -i Crypt::OpenSSL::AES
  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. # DIF types (Data Information Field), see page 32
  38. DIF_NONE => 0x00,
  39. DIF_INT8 => 0x01,
  40. DIF_INT16 => 0x02,
  41. DIF_INT24 => 0x03,
  42. DIF_INT32 => 0x04,
  43. DIF_FLOAT32 => 0x05,
  44. DIF_INT48 => 0x06,
  45. DIF_INT64 => 0x07,
  46. DIF_READOUT => 0x08,
  47. DIF_BCD2 => 0x09,
  48. DIF_BCD4 => 0x0a,
  49. DIF_BCD6 => 0x0b,
  50. DIF_BCD8 => 0x0c,
  51. DIF_VARLEN => 0x0d,
  52. DIF_BCD12 => 0x0e,
  53. DIF_SPECIAL => 0x0f,
  54. DIF_IDLE_FILLER => 0x2f,
  55. DIF_EXTENSION_BIT => 0x80,
  56. VIF_EXTENSION => 0xFB, # true VIF is given in the first VIFE and is coded using table 8.4.4 b) (128 new VIF-Codes)
  57. VIF_EXTENSION_BIT => 0x80,
  58. ERR_NO_ERROR => 0,
  59. ERR_CRC_FAILED => 1,
  60. ERR_UNKNOWN_VIFE => 2,
  61. ERR_UNKNOWN_VIF => 3,
  62. ERR_TOO_MANY_DIFE => 4,
  63. ERR_UNKNOWN_LVAR => 5,
  64. ERR_UNKNOWN_DATAFIELD => 6,
  65. ERR_UNKNOWN_CIFIELD => 7,
  66. ERR_DECRYPTION_FAILED => 8,
  67. ERR_NO_AESKEY => 9,
  68. ERR_UNKNOWN_ENCRYPTION => 10,
  69. ERR_TOO_MANY_VIFE => 11,
  70. ERR_MSG_TOO_SHORT => 12,
  71. };
  72. sub valueCalcNumeric($$) {
  73. my $value = shift;
  74. my $dataBlock = shift;
  75. return $value * $dataBlock->{valueFactor};
  76. }
  77. sub valueCalcDate($$) {
  78. my $value = shift;
  79. my $dataBlock = shift;
  80. #value is a 16bit int
  81. #day: UI5 [1 to 5] <1 to 31>
  82. #month: UI4 [9 to 12] <1 to 12>
  83. #year: UI7[6 to 8,13 to 16] <0 to 99>
  84. # YYYY MMMM YYY DDDDD
  85. # 0b0000 1100 111 11111 = 31.12.2007
  86. # 0b0000 0100 111 11110 = 30.04.2007
  87. my $day = ($value & 0b11111);
  88. my $month = (($value & 0b111100000000) >> 8);
  89. my $year = ((($value & 0b1111000000000000) >> 9) | (($value & 0b11100000) >> 5)) + 2000;
  90. if ($day > 31 || $month > 12 || $year > 2099) {
  91. return sprintf("invalid: %x", $value);
  92. } else {
  93. return sprintf("%04d-%02d-%02d", $year, $month, $day);
  94. }
  95. }
  96. sub valueCalcDateTime($$) {
  97. my $value = shift;
  98. my $dataBlock = shift;
  99. #min: UI6 [1 to 6] <0 to 59>
  100. #hour: UI5 [9 to13] <0 to 23>
  101. #day: UI5 [17 to 21] <1 to 31>
  102. #month: UI4 [25 to 28] <1 to 12>
  103. #year: UI7[22 to 24,29 to 32] <0 to 99>
  104. # IV:
  105. # B1[8] {time invalid}:
  106. # IV<0> :=
  107. #valid,
  108. #IV>1> := invalid
  109. #SU: B1[16] {summer time}:
  110. #SU<0> := standard time,
  111. #SU<1> := summer time
  112. #RES1: B1[7] {reserved}: <0>
  113. #RES2: B1[14] {reserved}: <0>
  114. #RES3: B1[15] {reserved}: <0>
  115. my $datePart = $value >> 16;
  116. my $timeInvalid = $value & 0b10000000;
  117. my $dateTime = valueCalcDate($datePart, $dataBlock);
  118. if ($timeInvalid == 0) {
  119. my $min = ($value & 0b111111);
  120. my $hour = ($value >> 8) & 0b11111;
  121. my $su = ($value & 0b1000000000000000);
  122. if ($min > 59 || $hour > 23) {
  123. $dateTime = sprintf('invalid: %x', $value);
  124. } else {
  125. $dateTime .= sprintf(' %02d:%02d %s', $hour, $min, $su ? 'DST' : '');
  126. }
  127. }
  128. return $dateTime;
  129. }
  130. sub valueCalcHex($$) {
  131. my $value = shift;
  132. my $dataBlock = shift;
  133. return sprintf("%x", $value);
  134. }
  135. sub valueCalcu($$) {
  136. my $value = shift;
  137. my $dataBlock = shift;
  138. my $result = '';
  139. $result = ($value & 0b00001000 ? 'upper' : 'lower') . ' limit';
  140. return $result;
  141. }
  142. sub valueCalcufnn($$) {
  143. my $value = shift;
  144. my $dataBlock = shift;
  145. my $result = '';
  146. $result = ($value & 0b00001000 ? 'upper' : 'lower') . ' limit';
  147. $result .= ', ' . ($value & 0b00000100 ? 'first' : 'last');
  148. $result .= sprintf(', duration %d', $value & 0b11);
  149. return $result;
  150. }
  151. sub valueCalcMultCorr1000($$) {
  152. my $value = shift;
  153. my $dataBlock = shift;
  154. $dataBlock->{value} *= 1000;
  155. return "correction by factor 1000";
  156. }
  157. my %TimeSpec = (
  158. 0b00 => 's', # seconds
  159. 0b01 => 'm', # minutes
  160. 0b10 => 'h', # hours
  161. 0b11 => 'd', # days
  162. );
  163. sub valueCalcTimeperiod($$) {
  164. my $value = shift;
  165. my $dataBlock = shift;
  166. $dataBlock->{unit} = $TimeSpec{$dataBlock->{exponent}};
  167. return $value;
  168. }
  169. # VIF types (Value Information Field), see page 32
  170. my %VIFInfo = (
  171. VIF_ENERGY_WATT => { # 10(nnn-3) Wh 0.001Wh to 10000Wh
  172. typeMask => 0b01111000,
  173. expMask => 0b00000111,
  174. type => 0b00000000,
  175. bias => -3,
  176. unit => 'Wh',
  177. calcFunc => \&valueCalcNumeric,
  178. },
  179. VIF_ENERGY_JOULE => { # 10(nnn) J 0.001kJ to 10000kJ
  180. typeMask => 0b01111000,
  181. expMask => 0b00000111,
  182. type => 0b00001000,
  183. bias => 0,
  184. unit => 'J',
  185. calcFunc => \&valueCalcNumeric,
  186. },
  187. VIF_VOLUME => { # 10(nnn-6) m3 0.001l to 10000l
  188. typeMask => 0b01111000,
  189. expMask => 0b00000111,
  190. type => 0b00010000,
  191. bias => -6,
  192. unit => 'm³',
  193. calcFunc => \&valueCalcNumeric,
  194. },
  195. VIF_MASS => { # 10(nnn-3) kg 0.001kg to 10000kg
  196. typeMask => 0b01111000,
  197. expMask => 0b00000111,
  198. type => 0b00011000,
  199. bias => -3,
  200. unit => 'kg',
  201. calcFunc => \&valueCalcNumeric,
  202. },
  203. VIF_ON_TIME_SEC => { # seconds
  204. typeMask => 0b01111111,
  205. expMask => 0b00000000,
  206. type => 0b00100000,
  207. bias => 0,
  208. unit => 'sec',
  209. calcFunc => \&valueCalcNumeric,
  210. },
  211. VIF_ON_TIME_MIN => { # minutes
  212. typeMask => 0b01111111,
  213. expMask => 0b00000000,
  214. type => 0b00100001,
  215. bias => 0,
  216. unit => 'min',
  217. calcFunc => \&valueCalcNumeric,
  218. },
  219. VIF_ON_TIME_HOURS => { # hours
  220. typeMask => 0b01111111,
  221. expMask => 0b00000000,
  222. type => 0b00100010,
  223. bias => 0,
  224. unit => 'hours',
  225. },
  226. VIF_ON_TIME_DAYS => { # days
  227. typeMask => 0b01111111,
  228. expMask => 0b00000000,
  229. type => 0b00100011,
  230. bias => 0,
  231. unit => 'days',
  232. },
  233. VIF_OP_TIME_SEC => { # seconds
  234. typeMask => 0b01111111,
  235. expMask => 0b00000000,
  236. type => 0b00100100,
  237. bias => 0,
  238. unit => 'sec',
  239. },
  240. VIF_OP_TIME_MIN => { # minutes
  241. typeMask => 0b01111111,
  242. expMask => 0b00000000,
  243. type => 0b00100101,
  244. bias => 0,
  245. unit => 'min',
  246. },
  247. VIF_OP_TIME_HOURS => { # hours
  248. typeMask => 0b01111111,
  249. expMask => 0b00000000,
  250. type => 0b00100110,
  251. bias => 0,
  252. unit => 'hours',
  253. },
  254. VIF_OP_TIME_DAYS => { # days
  255. typeMask => 0b01111111,
  256. expMask => 0b00000000,
  257. type => 0b00100111,
  258. bias => 0,
  259. unit => 'days',
  260. },
  261. VIF_ELECTRIC_POWER => { # 10(nnn-3) W 0.001W to 10000W
  262. typeMask => 0b01111000,
  263. expMask => 0b00000111,
  264. type => 0b00101000,
  265. bias => -3,
  266. unit => 'W',
  267. calcFunc => \&valueCalcNumeric,
  268. },
  269. VIF_THERMAL_POWER => { # 10(nnn) J/h 0.001kJ/h to 10000kJ/h
  270. typeMask => 0b01111000,
  271. expMask => 0b00000111,
  272. type => 0b00110000,
  273. bias => 0,
  274. unit => 'J/h',
  275. calcFunc => \&valueCalcNumeric,
  276. },
  277. VIF_VOLUME_FLOW => { # 10(nnn-6) m3/h 0.001l/h to 10000l/h
  278. typeMask => 0b01111000,
  279. expMask => 0b00000111,
  280. type => 0b00111000,
  281. bias => -6,
  282. unit => 'm³/h',
  283. calcFunc => \&valueCalcNumeric,
  284. },
  285. VIF_VOLUME_FLOW_EXT1 => { # 10(nnn-7) m3/min 0.0001l/min to 10000l/min
  286. typeMask => 0b01111000,
  287. expMask => 0b00000111,
  288. type => 0b01000000,
  289. bias => -7,
  290. unit => 'm³/min',
  291. calcFunc => \&valueCalcNumeric,
  292. },
  293. VIF_VOLUME_FLOW_EXT2 => { # 10(nnn-9) m3/s 0.001ml/s to 10000ml/s
  294. typeMask => 0b01111000,
  295. expMask => 0b00000111,
  296. type => 0b01001000,
  297. bias => -9,
  298. unit => 'm³/s',
  299. calcFunc => \&valueCalcNumeric,
  300. },
  301. VIF_MASS_FLOW => { # 10(nnn-3) kg/h 0.001kg/h to 10000kg/h
  302. typeMask => 0b01111000,
  303. expMask => 0b00000111,
  304. type => 0b01010000,
  305. bias => -3,
  306. unit => 'kg/h',
  307. calcFunc => \&valueCalcNumeric,
  308. },
  309. VIF_FLOW_TEMP => { # 10(nn-3) °C 0.001°C to 1°C
  310. typeMask => 0b01111100,
  311. expMask => 0b00000011,
  312. type => 0b01011000,
  313. bias => -3,
  314. unit => '°C',
  315. calcFunc => \&valueCalcNumeric,
  316. },
  317. VIF_RETURN_TEMP => { # 10(nn-3) °C 0.001°C to 1°C
  318. typeMask => 0b01111100,
  319. expMask => 0b00000011,
  320. type => 0b01011100,
  321. bias => -3,
  322. unit => '°C',
  323. calcFunc => \&valueCalcNumeric,
  324. },
  325. VIF_TEMP_DIFF => { # 10(nn-3) K 1mK to 1000mK
  326. typeMask => 0b01111100,
  327. expMask => 0b00000011,
  328. type => 0b01100000,
  329. bias => -3,
  330. unit => 'K',
  331. calcFunc => \&valueCalcNumeric,
  332. },
  333. VIF_EXTERNAL_TEMP => { # 10(nn-3) °C 0.001°C to 1°C
  334. typeMask => 0b01111100,
  335. expMask => 0b00000011,
  336. type => 0b01100100,
  337. bias => -3,
  338. unit => '°C',
  339. calcFunc => \&valueCalcNumeric,
  340. },
  341. VIF_PRESSURE => { # 10(nn-3) bar 1mbar to 1000mbar
  342. typeMask => 0b01111100,
  343. expMask => 0b00000011,
  344. type => 0b01101000,
  345. bias => -3,
  346. unit => 'bar',
  347. calcFunc => \&valueCalcNumeric,
  348. },
  349. VIF_TIME_POINT_DATE => { # data type G
  350. typeMask => 0b01111111,
  351. expMask => 0b00000000,
  352. type => 0b01101100,
  353. bias => 0,
  354. unit => '',
  355. calcFunc => \&valueCalcDate,
  356. },
  357. VIF_TIME_POINT_DATE_TIME => { # data type F
  358. typeMask => 0b01111111,
  359. expMask => 0b00000000,
  360. type => 0b01101101,
  361. bias => 0,
  362. unit => '',
  363. calcFunc => \&valueCalcDateTime,
  364. },
  365. VIF_HCA => { # Unit for Heat Cost Allocator, dimensonless
  366. typeMask => 0b01111111,
  367. expMask => 0b00000000,
  368. type => 0b01101110,
  369. bias => 0,
  370. unit => '',
  371. calcFunc => \&valueCalcNumeric,
  372. },
  373. VIF_FABRICATION_NO => { # Fabrication No
  374. typeMask => 0b01111111,
  375. expMask => 0b00000000,
  376. type => 0b01111000,
  377. bias => 0,
  378. unit => '',
  379. calcFunc => \&valueCalcNumeric,
  380. },
  381. VIF_OWNER_NO => { # Eigentumsnummer (used by Easymeter even though the standard allows this only for writing to a slave)
  382. typeMask => 0b01111111,
  383. expMask => 0b00000000,
  384. type => 0b01111001,
  385. bias => 0,
  386. unit => '',
  387. },
  388. VIF_AVERAGING_DURATION_SEC => { # seconds
  389. typeMask => 0b01111111,
  390. expMask => 0b00000000,
  391. type => 0b01110000,
  392. bias => 0,
  393. unit => 'sec',
  394. calcFunc => \&valueCalcNumeric,
  395. },
  396. VIF_AVERAGING_DURATION_MIN => { # minutes
  397. typeMask => 0b01111111,
  398. expMask => 0b00000000,
  399. type => 0b01110001,
  400. bias => 0,
  401. unit => 'min',
  402. calcFunc => \&valueCalcNumeric,
  403. },
  404. VIF_AVERAGING_DURATION_HOURS => { # hours
  405. typeMask => 0b01111111,
  406. expMask => 0b00000000,
  407. type => 0b01110010,
  408. bias => 0,
  409. unit => 'hours',
  410. },
  411. VIF_AVERAGING_DURATION_DAYS => { # days
  412. typeMask => 0b01111111,
  413. expMask => 0b00000000,
  414. type => 0b01110011,
  415. bias => 0,
  416. unit => 'days',
  417. },
  418. VIF_ACTUALITY_DURATION_SEC => { # seconds
  419. typeMask => 0b01111111,
  420. expMask => 0b00000000,
  421. type => 0b01110100,
  422. bias => 0,
  423. unit => 'sec',
  424. calcFunc => \&valueCalcNumeric,
  425. },
  426. VIF_ACTUALITY_DURATION_MIN => { # minutes
  427. typeMask => 0b01111111,
  428. expMask => 0b00000000,
  429. type => 0b01110101,
  430. bias => 0,
  431. unit => 'min',
  432. calcFunc => \&valueCalcNumeric,
  433. },
  434. VIF_ACTUALITY_DURATION_HOURS => { # hours
  435. typeMask => 0b01111111,
  436. expMask => 0b00000000,
  437. type => 0b01110110,
  438. bias => 0,
  439. unit => 'hours',
  440. },
  441. VIF_ACTUALITY_DURATION_DAYS => { # days
  442. typeMask => 0b01111111,
  443. expMask => 0b00000000,
  444. type => 0b01110111,
  445. bias => 0,
  446. unit => 'days',
  447. },
  448. );
  449. # Codes used with extension indicator $FD, see 8.4.4 on page 80
  450. my %VIFInfo_FD = (
  451. VIF_CREDIT => { # Credit of 10nn-3 of the nominal local legal currency units
  452. typeMask => 0b01111100,
  453. expMask => 0b00000011,
  454. type => 0b00000000,
  455. bias => -3,
  456. unit => '€',
  457. calcFunc => \&valueCalcNumeric,
  458. },
  459. VIF_DEBIT => { # Debit of 10nn-3 of the nominal local legal currency units
  460. typeMask => 0b01111100,
  461. expMask => 0b00000011,
  462. type => 0b00000100,
  463. bias => -3,
  464. unit => '€',
  465. calcFunc => \&valueCalcNumeric,
  466. },
  467. VIF_ACCESS_NO => { # Access number (transmission count)
  468. typeMask => 0b01111111,
  469. expMask => 0b00000000,
  470. type => 0b00001000,
  471. bias => 0,
  472. unit => '',
  473. calcFunc => \&valueCalcNumeric,
  474. },
  475. VIF_MEDIUM => { # Medium (as in fixed header)
  476. typeMask => 0b01111111,
  477. expMask => 0b00000000,
  478. type => 0b00001001,
  479. bias => 0,
  480. unit => '',
  481. calcFunc => \&valueCalcNumeric,
  482. },
  483. VIF_MODEL_VERSION => { # Model / Version
  484. typeMask => 0b01111111,
  485. expMask => 0b00000000,
  486. type => 0b00001100,
  487. bias => 0,
  488. unit => '',
  489. calcFunc => \&valueCalcNumeric,
  490. },
  491. VIF_ERROR_FLAGS => { # Error flags (binary)
  492. typeMask => 0b01111111,
  493. expMask => 0b00000000,
  494. type => 0b00010111,
  495. bias => 0,
  496. unit => '',
  497. calcFunc => \&valueCalcHex,
  498. },
  499. VIF_DURATION_SINCE_LAST_READOUT => { # Duration since last readout [sec(s)..day(s)]
  500. typeMask => 0b01111100,
  501. expMask => 0b00000011,
  502. type => 0b00101100,
  503. bias => 0,
  504. unit => 's',
  505. calcFunc => \&valueCalcTimeperiod,
  506. },
  507. VIF_VOLTAGE => { # 10nnnn-9 Volts
  508. typeMask => 0b01110000,
  509. expMask => 0b00001111,
  510. type => 0b01000000,
  511. bias => -9,
  512. unit => 'V',
  513. calcFunc => \&valueCalcNumeric,
  514. },
  515. VIF_ELECTRICAL_CURRENT => { # 10nnnn-12 Ampere
  516. typeMask => 0b01110000,
  517. expMask => 0b00001111,
  518. type => 0b01010000,
  519. bias => -12,
  520. unit => 'A',
  521. calcFunc => \&valueCalcNumeric,
  522. },
  523. VIF_RECEPTION_LEVEL => { # reception level of a received radio device.
  524. typeMask => 0b01111111,
  525. expMask => 0b00000000,
  526. type => 0b01110001,
  527. bias => 0,
  528. unit => 'dBm',
  529. calcFunc => \&valueCalcNumeric,
  530. },
  531. VIF_FD_RESERVED => { # Reserved
  532. typeMask => 0b01110000,
  533. expMask => 0b00000000,
  534. type => 0b01110000,
  535. bias => 0,
  536. unit => 'Reserved',
  537. },
  538. );
  539. # Codes used with extension indicator $FB
  540. my %VIFInfo_FB = (
  541. VIF_ENERGY => { # Energy 10(n-1) MWh 0.1MWh to 1MWh
  542. typeMask => 0b01111110,
  543. expMask => 0b00000001,
  544. type => 0b00000000,
  545. bias => -1,
  546. unit => 'MWh',
  547. calcFunc => \&valueCalcNumeric,
  548. },
  549. );
  550. # Codes used for an enhancement of VIFs other than $FD and $FB
  551. my %VIFInfo_other = (
  552. VIF_ERROR_NONE => {
  553. typeMask => 0b01111111,
  554. expMask => 0b00000000,
  555. type => 0b00000000,
  556. bias => 0,
  557. unit => 'No error',
  558. },
  559. VIF_TOO_MANY_DIFES => {
  560. typeMask => 0b01111111,
  561. expMask => 0b00000000,
  562. type => 0b00000001,
  563. bias => 0,
  564. unit => 'Too many DIFEs',
  565. },
  566. VIF_ILLEGAL_VIF_GROUP => {
  567. typeMask => 0b01111111,
  568. expMask => 0b00000000,
  569. type => 0b00001100,
  570. bias => 0,
  571. unit => 'Illegal VIF-Group',
  572. },
  573. VIF_PER_SECOND => {
  574. typeMask => 0b01111111,
  575. expMask => 0b00000000,
  576. type => 0b00100000,
  577. bias => 0,
  578. unit => 'per second',
  579. },
  580. VIF_PER_MINUTE => {
  581. typeMask => 0b01111111,
  582. expMask => 0b00000000,
  583. type => 0b00100001,
  584. bias => 0,
  585. unit => 'per minute',
  586. },
  587. VIF_PER_HOUR => {
  588. typeMask => 0b01111111,
  589. expMask => 0b00000000,
  590. type => 0b00100010,
  591. bias => 0,
  592. unit => 'per hour',
  593. },
  594. VIF_PER_DAY => {
  595. typeMask => 0b01111111,
  596. expMask => 0b00000000,
  597. type => 0b00100011,
  598. bias => 0,
  599. unit => 'per day',
  600. },
  601. VIF_PER_WEEK => {
  602. typeMask => 0b01111111,
  603. expMask => 0b00000000,
  604. type => 0b00100100,
  605. bias => 0,
  606. unit => 'per week',
  607. },
  608. VIF_PER_MONTH => {
  609. typeMask => 0b01111111,
  610. expMask => 0b00000000,
  611. type => 0b00100101,
  612. bias => 0,
  613. unit => 'per month',
  614. },
  615. VIF_PER_YEAR => {
  616. typeMask => 0b01111111,
  617. expMask => 0b00000000,
  618. type => 0b00100110,
  619. bias => 0,
  620. unit => 'per year',
  621. },
  622. VIF_PER_REVOLUTION => {
  623. typeMask => 0b01111111,
  624. expMask => 0b00000000,
  625. type => 0b00100111,
  626. bias => 0,
  627. unit => 'per revolution/measurement',
  628. },
  629. VIF_PER_INCREMENT_INPUT => {
  630. typeMask => 0b01111110,
  631. expMask => 0b00000000,
  632. type => 0b00101000,
  633. bias => 0,
  634. unit => 'increment per input pulse on input channnel #',
  635. calcFunc => \&valueCalcNumeric,
  636. },
  637. VIF_PER_INCREMENT_OUTPUT => {
  638. typeMask => 0b01111110,
  639. expMask => 0b00000000,
  640. type => 0b00101010,
  641. bias => 0,
  642. unit => 'increment per output pulse on output channnel #',
  643. calcFunc => \&valueCalcNumeric,
  644. },
  645. VIF_PER_LITER => {
  646. typeMask => 0b01111111,
  647. expMask => 0b00000000,
  648. type => 0b00101100,
  649. bias => 0,
  650. unit => 'per liter',
  651. },
  652. VIF_START_DATE_TIME => {
  653. typeMask => 0b01111111,
  654. expMask => 0b00000000,
  655. type => 0b00111001,
  656. bias => 0,
  657. unit => 'start date(/time) of',
  658. },
  659. VIF_ACCUMULATION_IF_POSITIVE => {
  660. typeMask => 0b01111111,
  661. expMask => 0b00000000,
  662. type => 0b00111011,
  663. bias => 0,
  664. unit => 'Accumulation only if positive contribution',
  665. },
  666. VIF_DURATION_NO_EXCEEDS => {
  667. typeMask => 0b01110111,
  668. expMask => 0b00000000,
  669. type => 0b01000001,
  670. bias => 0,
  671. unit => '# of exceeds',
  672. calcFunc => \&valueCalcu,
  673. },
  674. VIF_DURATION_LIMIT_EXCEEDED => {
  675. typeMask => 0b01110000,
  676. expMask => 0b00000000,
  677. type => 0b01010000,
  678. bias => 0,
  679. unit => 'duration of limit exceeded',
  680. calcFunc => \&valueCalcufnn,
  681. },
  682. VIF_MULTIPLICATIVE_CORRECTION_FACTOR => {
  683. typeMask => 0b01111000,
  684. expMask => 0b00000111,
  685. type => 0b01110000,
  686. bias => -6,
  687. unit => '',
  688. },
  689. VIF_MULTIPLICATIVE_CORRECTION_FACTOR_1000 => {
  690. typeMask => 0b01111111,
  691. expMask => 0b00000000,
  692. type => 0b01111101,
  693. bias => 0,
  694. unit => '',
  695. calcFunc => \&valueCalcMultCorr1000,
  696. },
  697. VIF_FUTURE_VALUE => {
  698. typeMask => 0b01111111,
  699. expMask => 0b00000000,
  700. type => 0b01111110,
  701. bias => 0,
  702. unit => '',
  703. },
  704. VIF_MANUFACTURER_SPECIFIC => {
  705. typeMask => 0b01111111,
  706. expMask => 0b00000000,
  707. type => 0b01111111,
  708. bias => 0,
  709. unit => 'manufacturer specific',
  710. },
  711. );
  712. # For Easymeter (manufacturer specific)
  713. my %VIFInfo_ESY = (
  714. VIF_ELECTRIC_POWER_PHASE => {
  715. typeMask => 0b01000000,
  716. expMask => 0b00000000,
  717. type => 0b00000000,
  718. bias => -2,
  719. unit => 'W',
  720. calcFunc => \&valueCalcNumeric,
  721. },
  722. VIF_ELECTRIC_POWER_PHASE_NO => {
  723. typeMask => 0b01111110,
  724. expMask => 0b00000000,
  725. type => 0b00101000,
  726. bias => 0,
  727. unit => 'phase #',
  728. calcFunc => \&valueCalcNumeric,
  729. },
  730. );
  731. # see 4.2.3, page 24
  732. my %validDeviceTypes = (
  733. 0x00 => 'Other',
  734. 0x01 => 'Oil',
  735. 0x02 => 'Electricity',
  736. 0x03 => 'Gas',
  737. 0x04 => 'Heat',
  738. 0x05 => 'Steam',
  739. 0x06 => 'Warm Water (30 °C ... 90 °C)',
  740. 0x07 => 'Water',
  741. 0x08 => 'Heat Cost Allocator',
  742. 0x09 => 'Compressed Air',
  743. 0x0a => 'Cooling load meter (Volume measured at return temperature: outlet)',
  744. 0x0b => 'Cooling load meter (Volume measured at flow temperature: inlet)',
  745. 0x0c => 'Heat (Volume measured at flow temperature: inlet)',
  746. 0x0d => 'Heat / Cooling load meter',
  747. 0x0e => 'Bus / System component',
  748. 0x0f => 'Unknown Medium',
  749. 0x10 => 'Reserved for utility meter',
  750. 0x11 => 'Reserved for utility meter',
  751. 0x12 => 'Reserved for utility meter',
  752. 0x13 => 'Reserved for utility meter',
  753. 0x14 => 'Calorific value',
  754. 0x15 => 'Hot water (> 90 °C)',
  755. 0x16 => 'Cold water',
  756. 0x17 => 'Dual register (hot/cold) Water meter',
  757. 0x18 => 'Pressure',
  758. 0x19 => 'A/D Converter',
  759. 0x1a => 'Smokedetector',
  760. 0x1b => 'Room sensor (e.g. temperature or humidity)',
  761. 0x1c => 'Gasdetector',
  762. 0x1d => 'Reserved for sensors',
  763. 0x1e => 'Reserved for sensors',
  764. 0x1f => 'Reserved for sensors',
  765. 0x20 => 'Breaker (electricity)',
  766. 0x21 => 'Valve (gas)',
  767. 0x22 => 'Reserved for switching devices',
  768. 0x23 => 'Reserved for switching devices',
  769. 0x24 => 'Reserved for switching devices',
  770. 0x25 => 'Customer unit (Display device)',
  771. 0x26 => 'Reserved for customer units',
  772. 0x27 => 'Reserved for customer units',
  773. 0x28 => 'Waste water',
  774. 0x29 => 'Garbage',
  775. 0x2a => 'Carbon dioxide',
  776. 0x2b => 'Environmental meter',
  777. 0x2c => 'Environmental meter',
  778. 0x2d => 'Environmental meter',
  779. 0x2e => 'Environmental meter',
  780. 0x2f => 'Environmental meter',
  781. 0x31 => 'OMS MUC',
  782. 0x32 => 'OMS unidirectional repeater',
  783. 0x33 => 'OMS bidirectional repeater',
  784. 0x37 => 'Radio converter (Meter side)',
  785. );
  786. # bitfield, errors can be combined, see 4.2.3.2 on page 22
  787. my %validStates = (
  788. 0x00 => 'no errors',
  789. 0x01 => 'application busy',
  790. 0x02 => 'any application error',
  791. 0x03 => 'abnormal condition/alarm',
  792. 0x04 => 'battery low',
  793. 0x08 => 'permanent error',
  794. 0x10 => 'temporary error',
  795. 0x20 => 'specific to manufacturer',
  796. 0x40 => 'specific to manufacturer',
  797. 0x80 => 'specific to manufacturer',
  798. );
  799. my %encryptionModes = (
  800. 0x00 => 'standard unsigned',
  801. 0x01 => 'signed data telegram',
  802. 0x02 => 'static telegram',
  803. 0x03 => 'reserved',
  804. );
  805. my %functionFieldTypes = (
  806. 0b00 => 'Instantaneous value',
  807. 0b01 => 'Maximum value',
  808. 0b10 => 'Minimum value',
  809. 0b11 => 'Value during error state',
  810. );
  811. sub type2string($$) {
  812. my $class = shift;
  813. my $type = shift;
  814. return $validDeviceTypes{$type} || 'unknown';
  815. }
  816. sub state2string($$) {
  817. my $class = shift;
  818. my $state = shift;
  819. my @result = ();
  820. if ($state) {
  821. foreach my $stateMask ( keys %validStates ) {
  822. push @result, $validStates{$stateMask} if $state & $stateMask;
  823. }
  824. } else {
  825. @result = ($validStates{0});
  826. }
  827. return @result;
  828. }
  829. sub checkCRC($$) {
  830. my $self = shift;
  831. my $data = shift;
  832. my $ctx = Digest::CRC->new(width=>16, init=>0x0000, xorout=>0xffff, refout=>0, poly=>0x3D65, refin=>0, cont=>0);
  833. $ctx->add($data);
  834. return $ctx->digest;
  835. }
  836. sub removeCRC($$)
  837. {
  838. my $self = shift;
  839. my $msg = shift;
  840. my $i;
  841. my $res;
  842. my $crc;
  843. my $blocksize = LL_BLOCK_SIZE;
  844. my $blocksize_with_crc = LL_BLOCK_SIZE + $self->{crc_size};
  845. my $crcoffset;
  846. my $msgLen = $self->{datalen}; # size without CRCs
  847. my $noOfBlocks = $self->{datablocks}; # total number of data blocks, each with a CRC appended
  848. my $rest = $msgLen % LL_BLOCK_SIZE; # size of the last data block, can be smaller than 16 bytes
  849. #print "crc_size $self->{crc_size}\n";
  850. return $msg if $self->{crc_size} == 0;
  851. # each block is 16 bytes + 2 bytes CRC
  852. #print "Länge $msgLen Anz. Blöcke $noOfBlocks rest $rest\n";
  853. for ($i=0; $i < $noOfBlocks; $i++) {
  854. $crcoffset = $blocksize_with_crc * $i + LL_BLOCK_SIZE;
  855. #print "$i: crc offset $crcoffset\n";
  856. if ($rest > 0 && $crcoffset + $self->{crc_size} > ($noOfBlocks - 1) * $blocksize_with_crc + $rest) {
  857. # last block is smaller
  858. $crcoffset = ($noOfBlocks - 1) * $blocksize_with_crc + $rest;
  859. #print "last crc offset $crcoffset\n";
  860. $blocksize = $msgLen - ($i * $blocksize);
  861. }
  862. $crc = unpack('n',substr($msg, $crcoffset, $self->{crc_size}));
  863. #printf("%d: CRC %x, calc %x blocksize $blocksize\n", $i, $crc, $self->checkCRC(substr($msg, $blocksize_with_crc*$i, $blocksize)));
  864. if ($crc != $self->checkCRC(substr($msg, $blocksize_with_crc*$i, $blocksize))) {
  865. $self->{errormsg} = "crc check failed for block $i";
  866. $self->{errorcode} = ERR_CRC_FAILED;
  867. return 0;
  868. }
  869. $res .= substr($msg, $blocksize_with_crc*$i, $blocksize);
  870. }
  871. return $res;
  872. }
  873. sub manId2hex($$)
  874. {
  875. my $class = shift;
  876. my $idascii = shift;
  877. return (ord(substr($idascii,1,1))-64) << 10 | (ord(substr($idascii,2,1))-64) << 5 | (ord(substr($idascii,3,1))-64);
  878. }
  879. sub manId2ascii($$)
  880. {
  881. my $class = shift;
  882. my $idhex = shift;
  883. return chr(($idhex >> 10) + 64) . chr((($idhex >> 5) & 0b00011111) + 64) . chr(($idhex & 0b00011111) + 64);
  884. }
  885. sub new {
  886. my $class = shift;
  887. my $self = {};
  888. bless $self, $class;
  889. $self->_initialize();
  890. return $self;
  891. }
  892. sub _initialize {
  893. my $self = shift;
  894. $self->{crc_size} = CRC_SIZE;
  895. }
  896. sub setCRCsize {
  897. my $self = shift;
  898. $self->{crc_size} = shift;
  899. }
  900. sub getCRCsize {
  901. my $self = shift;
  902. return $self->{crc_size};
  903. }
  904. sub decodeConfigword($) {
  905. my $self = shift;
  906. #if ($self->{cw_parts}{mode} == 5) {
  907. $self->{cw_parts}{bidirectional} = $self->{cw} & 0b1000000000000000 >> 15;
  908. $self->{cw_parts}{accessability} = $self->{cw} & 0b0100000000000000 >> 14;
  909. $self->{cw_parts}{synchronous} = $self->{cw} & 0b0010000000000000 >> 13;
  910. $self->{cw_parts}{mode} = $self->{cw} & 0b0000111100000000 >> 8;
  911. $self->{cw_parts}{encrypted_blocks} = $self->{cw} & 0b0000000011110000 >> 4;
  912. $self->{cw_parts}{content} = $self->{cw} & 0b0000000000001100 >> 2;
  913. $self->{cw_parts}{repeated_access} = $self->{cw} & 0b0000000000000010 >> 1;
  914. $self->{cw_parts}{hops} = $self->{cw} & 0b0000000000000001;
  915. #} elsif ($self->{cw_parts}{mode} == 7) {
  916. # ToDo: wo kommt das dritte Byte her?
  917. # $self->{cw_parts}{mode} = $self->{cw} & 0b0000111100000000 >> 8;
  918. #}
  919. }
  920. sub decodeBCD($$$) {
  921. my $self = shift;
  922. my $digits = shift;
  923. my $bcd = shift;
  924. my $byte;
  925. my $val=0;
  926. my $mult=1;
  927. #print "bcd:" . unpack("H*", $bcd) . "\n";
  928. for (my $i = 0; $i < $digits/2; $i++) {
  929. $byte = unpack('C',substr($bcd, $i, 1));
  930. $val += ($byte & 0x0f) * $mult;
  931. $mult *= 10;
  932. $val += (($byte & 0xf0) >> 4) * $mult;
  933. $mult *= 10;
  934. }
  935. return $val;
  936. }
  937. sub findVIF($$$) {
  938. my $vif = shift;
  939. my $vifInfoRef = shift;
  940. my $dataBlockRef = shift;
  941. my $bias;
  942. if (defined $vifInfoRef) {
  943. VIFID: foreach my $vifType ( keys %$vifInfoRef ) {
  944. #printf "vifType $vifType VIF $vif typeMask $vifInfoRef->{$vifType}{typeMask} type $vifInfoRef->{$vifType}{type}\n";
  945. if (($vif & $vifInfoRef->{$vifType}{typeMask}) == $vifInfoRef->{$vifType}{type}) {
  946. #printf " match vifType $vifType\n";
  947. $bias = $vifInfoRef->{$vifType}{bias};
  948. $dataBlockRef->{exponent} = $vif & $vifInfoRef->{$vifType}{expMask};
  949. $dataBlockRef->{type} = $vifType;
  950. $dataBlockRef->{unit} = $vifInfoRef->{$vifType}{unit};
  951. if (defined $dataBlockRef->{exponent} && defined $bias) {
  952. $dataBlockRef->{valueFactor} = 10 ** ($dataBlockRef->{exponent} + $bias);
  953. } else {
  954. $dataBlockRef->{valueFactor} = 1;
  955. }
  956. $dataBlockRef->{calcFunc} = $vifInfoRef->{$vifType}{calcFunc};
  957. #printf("type %s bias %d exp %d valueFactor %d unit %s\n", $dataBlockRef->{type}, $bias, $dataBlockRef->{exponent}, $dataBlockRef->{valueFactor},$dataBlockRef->{unit});
  958. return 1;
  959. }
  960. }
  961. #printf "no match!\n";
  962. return 0;
  963. }
  964. return 1;
  965. }
  966. sub decodeValueInformationBlock($$$) {
  967. my $self = shift;
  968. my $vib = shift;
  969. my $dataBlockRef = shift;
  970. my $offset = 0;
  971. my $vif;
  972. my $vifInfoRef;
  973. my $vifExtension = 0;
  974. my $vifExtNo = 0;
  975. my $isExtension;
  976. my $dataBlockExt;
  977. my @VIFExtensions = ();
  978. my $analyzeVIF = 1;
  979. $dataBlockRef->{type} = '';
  980. # The unit and multiplier is taken from the table for primary VIF
  981. $vifInfoRef = \%VIFInfo;
  982. EXTENSION: while (1) {
  983. $vif = unpack('C', substr($vib,$offset++,1));
  984. $isExtension = $vif & VIF_EXTENSION_BIT;
  985. #printf("vif: %x isExtension %d\n", $vif, $isExtension);
  986. # Is this an extension?
  987. last EXTENSION if (!$isExtension);
  988. # yes, process extension
  989. $vifExtNo++;
  990. if ($vifExtNo > 10) {
  991. $dataBlockRef->{errormsg} = 'too many VIFE';
  992. $dataBlockRef->{errorcode} = ERR_TOO_MANY_VIFE;
  993. last EXTENSION;
  994. }
  995. # switch to extension codes
  996. $vifExtension = $vif;
  997. $vif &= ~VIF_EXTENSION_BIT;
  998. #printf("vif ohne extension: %x\n", $vif);
  999. if ($vif == 0x7D) {
  1000. $vifInfoRef = \%VIFInfo_FD;
  1001. } elsif ($vif == 0x7B) {
  1002. $vifInfoRef = \%VIFInfo_FB;
  1003. } elsif ($vif == 0x7C) {
  1004. # Plaintext VIF
  1005. my $vifLength = unpack('C', substr($vib,$offset++,1));
  1006. $dataBlockRef->{type} = "see unit";
  1007. $dataBlockRef->{unit} = unpack(sprintf("C%d",$vifLength), substr($vib, $offset, $vifLength));
  1008. $offset += $vifLength;
  1009. $analyzeVIF = 0;
  1010. last EXTENSION;
  1011. } elsif ($vif == 0x7F) {
  1012. if ($self->{manufacturer} eq 'ESY') {
  1013. # Easymeter
  1014. $vif = unpack('C', substr($vib,$offset++,1));
  1015. $vifInfoRef = \%VIFInfo_ESY;
  1016. } else {
  1017. # manufacturer specific data, can't be interpreted
  1018. $dataBlockRef->{type} = "MANUFACTURER SPECIFIC";
  1019. $dataBlockRef->{unit} = "";
  1020. $analyzeVIF = 0;
  1021. }
  1022. last EXTENSION;
  1023. } else {
  1024. # enhancement of VIFs other than $FD and $FB (see page 84ff.)
  1025. #print "other extension\n";
  1026. $dataBlockExt = {};
  1027. if ($self->{manufacturer} eq 'ESY') {
  1028. $vifInfoRef = \%VIFInfo_ESY;
  1029. $dataBlockExt->{value} = unpack('C',substr($vib,2,1)) * 100;
  1030. } else {
  1031. $dataBlockExt->{value} = $vif;
  1032. $vifInfoRef = \%VIFInfo_other;
  1033. }
  1034. if (findVIF($vif, $vifInfoRef, $dataBlockExt)) {
  1035. push @VIFExtensions, $dataBlockExt;
  1036. } else {
  1037. $dataBlockRef->{type} = 'unknown';
  1038. $dataBlockRef->{errormsg} = "unknown VIFE " . sprintf("%x", $vifExtension) . " at offset " . ($offset-1);
  1039. $dataBlockRef->{errorcode} = ERR_UNKNOWN_VIFE;
  1040. }
  1041. }
  1042. last EXTENSION if (!$isExtension);
  1043. }
  1044. if ($analyzeVIF) {
  1045. if (findVIF($vif, $vifInfoRef, $dataBlockRef) == 0) {
  1046. $dataBlockRef->{errormsg} = "unknown VIF " . sprintf("%x", $vifExtension) . " at offset " . ($offset-1);
  1047. $dataBlockRef->{errorcode} = ERR_UNKNOWN_VIFE;
  1048. }
  1049. }
  1050. $dataBlockRef->{VIFExtensions} = \@VIFExtensions;
  1051. if ($dataBlockRef->{type} eq '') {
  1052. $dataBlockRef->{type} = 'unknown';
  1053. $dataBlockRef->{errormsg} = sprintf("in VIFExtension %x unknown VIF %x",$vifExtension, $vif);
  1054. $dataBlockRef->{errorcode} = ERR_UNKNOWN_VIF;
  1055. }
  1056. return $offset;
  1057. }
  1058. sub decodeDataInformationBlock($$$) {
  1059. my $self = shift;
  1060. my $dib = shift;
  1061. my $dataBlockRef = shift;
  1062. my $dif;
  1063. my $tariff = 0;
  1064. my $difExtNo = 0;
  1065. my $offset;
  1066. my $devUnit = 0;
  1067. $dif = unpack('C', $dib);
  1068. $offset = 1;
  1069. my $isExtension = $dif & DIF_EXTENSION_BIT;
  1070. my $storageNo = ($dif & 0b01000000) >> 6;
  1071. my $functionField = ($dif & 0b00110000) >> 4;
  1072. my $df = $dif & 0b00001111;
  1073. #printf("dif %x storage %d\n", $dif, $storageNo);
  1074. EXTENSION: while ($isExtension) {
  1075. $dif = unpack('C', substr($dib,$offset,1));
  1076. last EXTENSION if (!defined $dif);
  1077. $offset++;
  1078. $isExtension = $dif & DIF_EXTENSION_BIT;
  1079. $difExtNo++;
  1080. if ($difExtNo > 10) {
  1081. $dataBlockRef->{errormsg} = 'too many DIFE';
  1082. $dataBlockRef->{errorcode} = ERR_TOO_MANY_DIFE;
  1083. last EXTENSION;
  1084. }
  1085. $storageNo |= ($dif & 0b00001111) << ($difExtNo*4)+1;
  1086. $tariff |= (($dif & 0b00110000 >> 4)) << (($difExtNo-1)*2);
  1087. $devUnit |= (($dif & 0b01000000 >> 6)) << ($difExtNo-1);
  1088. #printf("dife %x extno %d storage %d\n", $dif, $difExtNo, $storageNo);
  1089. }
  1090. $dataBlockRef->{functionField} = $functionField;
  1091. $dataBlockRef->{functionFieldText} = $functionFieldTypes{$functionField};
  1092. $dataBlockRef->{dataField} = $df;
  1093. $dataBlockRef->{storageNo} = $storageNo;
  1094. $dataBlockRef->{tariff} = $tariff;
  1095. $dataBlockRef->{devUnit} = $devUnit;
  1096. #printf("in DIF: datafield %x\n", $dataBlockRef->{dataField});
  1097. #print "offset in dif $offset\n";
  1098. return $offset;
  1099. }
  1100. sub decodeDataRecordHeader($$$) {
  1101. my $self = shift;
  1102. my $drh = shift;
  1103. my $dataBlockRef = shift;
  1104. my $offset = $self->decodeDataInformationBlock($drh,$dataBlockRef);
  1105. $offset += $self->decodeValueInformationBlock(substr($drh,$offset),$dataBlockRef);
  1106. #printf("in DRH: type %s\n", $dataBlockRef->{type});
  1107. return $offset;
  1108. }
  1109. sub decodePayload($$) {
  1110. my $self = shift;
  1111. my $payload = shift;
  1112. my $offset = 0;
  1113. my $dif;
  1114. my $vif;
  1115. my $scale;
  1116. my $value;
  1117. my $dataBlockNo = 0;
  1118. my @dataBlocks = ();
  1119. my $dataBlock;
  1120. PAYLOAD: while ($offset < length($payload)) {
  1121. $dataBlockNo++;
  1122. # create a new anonymous hash reference
  1123. $dataBlock = {};
  1124. $dataBlock->{number} = $dataBlockNo;
  1125. $dataBlock->{unit} = '';
  1126. while (unpack('C',substr($payload,$offset,1)) == 0x2f) {
  1127. # skip filler bytes
  1128. #printf("skipping filler at offset %d of %d\n", $offset, length($payload));
  1129. $offset++;
  1130. if ($offset >= length($payload)) {
  1131. last PAYLOAD;
  1132. }
  1133. }
  1134. $offset += $self->decodeDataRecordHeader(substr($payload,$offset), $dataBlock);
  1135. #printf("No. %d, type %x at offset %d\n", $dataBlockNo, $dataBlock->{dataField}, $offset-1);
  1136. if ($dataBlock->{dataField} == DIF_NONE) {
  1137. } elsif ($dataBlock->{dataField} == DIF_READOUT) {
  1138. $self->{errormsg} = "in datablock $dataBlockNo: unexpected DIF_READOUT";
  1139. $self->{errorcode} = ERR_UNKNOWN_DATAFIELD;
  1140. return 0;
  1141. } elsif ($dataBlock->{dataField} == DIF_BCD2) {
  1142. $value = $self->decodeBCD(2, substr($payload,$offset,1));
  1143. $offset += 1;
  1144. } elsif ($dataBlock->{dataField} == DIF_BCD4) {
  1145. $value = $self->decodeBCD(4, substr($payload,$offset,2));
  1146. $offset += 2;
  1147. } elsif ($dataBlock->{dataField} == DIF_BCD6) {
  1148. $value = $self->decodeBCD(6, substr($payload,$offset,3));
  1149. $offset += 3;
  1150. } elsif ($dataBlock->{dataField} == DIF_BCD8) {
  1151. $value = $self->decodeBCD(8, substr($payload,$offset,4));
  1152. $offset += 4;
  1153. } elsif ($dataBlock->{dataField} == DIF_BCD12) {
  1154. $value = $self->decodeBCD(12, substr($payload,$offset,6));
  1155. $offset += 6;
  1156. } elsif ($dataBlock->{dataField} == DIF_INT8) {
  1157. $value = unpack('C', substr($payload, $offset, 1));
  1158. $offset += 1;
  1159. } elsif ($dataBlock->{dataField} == DIF_INT16) {
  1160. $value = unpack('v', substr($payload, $offset, 2));
  1161. $offset += 2;
  1162. } elsif ($dataBlock->{dataField} == DIF_INT24) {
  1163. my @bytes = unpack('CCC', substr($payload, $offset, 3));
  1164. $offset += 3;
  1165. $value = $bytes[0] + $bytes[1] << 8 + $bytes[2] << 16;
  1166. } elsif ($dataBlock->{dataField} == DIF_INT32) {
  1167. $value = unpack('V', substr($payload, $offset, 4));
  1168. $offset += 4;
  1169. } elsif ($dataBlock->{dataField} == DIF_INT48) {
  1170. my @words = unpack('vvv', substr($payload, $offset, 6));
  1171. $value = $words[0] + $words[1] << 16 + $words[2] << 32;
  1172. $offset += 6;
  1173. } elsif ($dataBlock->{dataField} == DIF_INT64) {
  1174. $value = unpack('Q<', substr($payload, $offset, 8));
  1175. $offset += 8;
  1176. } elsif ($dataBlock->{dataField} == DIF_FLOAT32) {
  1177. #not allowed according to wmbus standard, Qundis seems to use it nevertheless
  1178. $value = unpack('f', substr($payload, $offset, 4));
  1179. $offset += 4;
  1180. } elsif ($dataBlock->{dataField} == DIF_VARLEN) {
  1181. my $lvar = unpack('C',substr($payload, $offset++, 1));
  1182. #print "in datablock $dataBlockNo: LVAR field " . sprintf("%x", $lvar) . "\n";
  1183. #printf "payload len %d offset %d\n", length($payload), $offset;
  1184. if ($lvar <= 0xbf) {
  1185. if ($dataBlock->{type} eq "MANUFACTURER SPECIFIC") {
  1186. # special handling, LSE seems to lie about this
  1187. $value = unpack('H*',substr($payload, $offset, $lvar));
  1188. #print "VALUE: " . $value . "\n";
  1189. } else {
  1190. # ASCII string with LVAR characters
  1191. $value = unpack('a*',substr($payload, $offset, $lvar));
  1192. if ($self->{manufacturer} eq 'ESY') {
  1193. # Easymeter stores the string backwards!
  1194. $value = reverse($value);
  1195. }
  1196. }
  1197. $offset += $lvar;
  1198. } elsif ($lvar >= 0xc0 && $lvar <= 0xcf) {
  1199. # positive BCD number with (LVAR - C0h) • 2 digits
  1200. $value = $self->decodeBCD(($lvar-0xc0)*2, substr($payload,$offset,($lvar-0xc0)));
  1201. $offset += ($lvar-0xc0);
  1202. } elsif ($lvar >= 0xd0 && $lvar <= 0xdf) {
  1203. # negative BCD number with (LVAR - D0h) • 2 digits
  1204. $value = -$self->decodeBCD(($lvar-0xd0)*2, substr($payload,$offset,($lvar-0xd0)));
  1205. $offset += ($lvar-0xd0);
  1206. } else {
  1207. $self->{errormsg} = "in datablock $dataBlockNo: unhandled LVAR field " . sprintf("%x", $lvar);
  1208. $self->{errorcode} = ERR_UNKNOWN_LVAR;
  1209. return 0;
  1210. }
  1211. } elsif ($dataBlock->{dataField} == DIF_SPECIAL) {
  1212. # special functions
  1213. #print "DIF_SPECIAL at $offset\n";
  1214. $value = unpack("H*", substr($payload,$offset));
  1215. last PAYLOAD;
  1216. } else {
  1217. $self->{errormsg} = "in datablock $dataBlockNo: unhandled datafield " . sprintf("%x",$dataBlock->{dataField});
  1218. $self->{errorcode} = ERR_UNKNOWN_DATAFIELD;
  1219. return 0;
  1220. }
  1221. if (defined $dataBlock->{calcFunc}) {
  1222. $dataBlock->{value} = $dataBlock->{calcFunc}->($value, $dataBlock);
  1223. #print "Value raw " . $value . " value calc " . $dataBlock->{value} ."\n";
  1224. } elsif (defined $value) {
  1225. $dataBlock->{value} = $value;
  1226. } else {
  1227. $dataBlock->{value} = "";
  1228. }
  1229. my $VIFExtensions = $dataBlock->{VIFExtensions};
  1230. for my $VIFExtension (@$VIFExtensions) {
  1231. $dataBlock->{extension} = $VIFExtension->{unit};
  1232. if (defined $VIFExtension->{calcFunc}) {
  1233. #printf("Extension value %d, valueFactor %d\n", $VIFExtension->{value}, $VIFExtension->{valueFactor});
  1234. $dataBlock->{extension} .= ", " . $VIFExtension->{calcFunc}->($VIFExtension->{value}, $dataBlock);
  1235. } elsif (defined $VIFExtension->{value}) {
  1236. $dataBlock->{extension} .= ", " . sprintf("%x",$VIFExtension->{value});
  1237. } else {
  1238. #$dataBlock->{extension} = "";
  1239. }
  1240. }
  1241. undef $value;
  1242. push @dataBlocks, $dataBlock;
  1243. }
  1244. $self->{datablocks} = \@dataBlocks;
  1245. return 1;
  1246. }
  1247. sub decrypt($) {
  1248. my $self = shift;
  1249. my $encrypted = shift;
  1250. # see 4.2.5.3, page 26
  1251. my $initVector = substr($self->{msg},2,8);
  1252. for (1..8) {
  1253. $initVector .= pack('C',$self->{access_no});
  1254. }
  1255. my $cipher = Crypt::CBC->new(
  1256. -key => $self->{aeskey},
  1257. -cipher => "Crypt::OpenSSL::AES",
  1258. -header => "none",
  1259. -iv => $initVector,
  1260. -literal_key => "true",
  1261. -keysize => 16,
  1262. );
  1263. return $cipher->decrypt($encrypted);
  1264. }
  1265. sub decodeApplicationLayer($) {
  1266. my $self = shift;
  1267. my $applicationlayer = $self->removeCRC(substr($self->{msg},TL_BLOCK_SIZE + $self->{crc_size}));
  1268. #print unpack("H*", $applicationlayer) . "\n";
  1269. if ($self->{errorcode} != ERR_NO_ERROR) {
  1270. # CRC check failed
  1271. return 0;
  1272. }
  1273. $self->{cifield} = unpack('C', $applicationlayer);
  1274. my $offset = 1;
  1275. if ($self->{cifield} == CI_RESP_4) {
  1276. # Short header
  1277. #print "short header\n";
  1278. ($self->{access_no}, $self->{status}, $self->{cw}) = unpack('CCn', substr($applicationlayer,$offset));
  1279. $offset += 4;
  1280. } elsif ($self->{cifield} == CI_RESP_12) {
  1281. # Long header
  1282. #print "Long header\n";
  1283. ($self->{meter_id}, $self->{meter_man}, $self->{meter_vers}, $self->{meter_dev}, $self->{access_no}, $self->{status}, $self->{cw})
  1284. = unpack('VvCCCCn', substr($applicationlayer,$offset));
  1285. $self->{meter_id} = sprintf("%08d", $self->{meter_id});
  1286. $self->{meter_devtypestring} = $validDeviceTypes{$self->{meter_dev}} || 'unknown';
  1287. $self->{meter_manufacturer} = uc($self->manId2ascii($self->{meter_man}));
  1288. $offset += 12;
  1289. } elsif ($self->{cifield} == CI_RESP_0) {
  1290. # no header
  1291. $self->{cw} = 0;
  1292. } else {
  1293. # unsupported
  1294. $self->{cw} = 0;
  1295. $self->decodeConfigword();
  1296. $self->{errormsg} = 'Unsupported CI Field ' . sprintf("%x", $self->{cifield}) . ", remaining payload is " . unpack("H*", substr($applicationlayer,$offset));
  1297. $self->{errorcode} = ERR_UNKNOWN_CIFIELD;
  1298. return 0;
  1299. }
  1300. $self->{statusstring} = join(", ", $self->state2string($self->{status}));
  1301. $self->decodeConfigword();
  1302. my $payload;
  1303. $self->{encryptionMode} = $encryptionModes{$self->{cw_parts}{mode}};
  1304. if ($self->{cw_parts}{mode} == 0) {
  1305. # no encryption
  1306. $self->{isEncrypted} = 0;
  1307. $self->{decrypted} = 1;
  1308. $payload = substr($applicationlayer, $offset);
  1309. } elsif ($self->{cw_parts}{mode} == 5) {
  1310. # data is encrypted with AES 128, dynamic init vector
  1311. # decrypt data before further processing
  1312. $self->{isEncrypted} = 1;
  1313. $self->{decrypted} = 0;
  1314. if ($self->{aeskey}) {
  1315. $payload = $self->decrypt(substr($applicationlayer,$offset));
  1316. if (unpack('n', $payload) == 0x2f2f) {
  1317. $self->{decrypted} = 1;
  1318. #printf("decrypted payload %s\n", unpack("H*", $payload));
  1319. } else {
  1320. # Decryption verification failed
  1321. $self->{errormsg} = 'Decryption failed, wrong key?';
  1322. $self->{errorcode} = ERR_DECRYPTION_FAILED;
  1323. #printf("%x\n", unpack('n', $payload));
  1324. return 0;
  1325. }
  1326. } else {
  1327. $self->{errormsg} = 'encrypted message and no aeskey provided';
  1328. $self->{errorcode} = ERR_NO_AESKEY;
  1329. return 0;
  1330. }
  1331. } else {
  1332. # error, encryption mode not implemented
  1333. $self->{errormsg} = sprintf('Encryption mode %x not implemented', $self->{cw_parts}{mode});
  1334. $self->{errorcode} = ERR_UNKNOWN_ENCRYPTION;
  1335. $self->{decrypted} = 0;
  1336. return 0;
  1337. }
  1338. return $self->decodePayload($payload);
  1339. }
  1340. sub decodeLinkLayer($$)
  1341. {
  1342. my $self = shift;
  1343. my $linklayer = shift;
  1344. ($self->{lfield}, $self->{cfield}, $self->{mfield}) = unpack('CCv', $linklayer);
  1345. $self->{afield_id} = sprintf("%08d", $self->decodeBCD(8,substr($linklayer,4,4)));
  1346. ($self->{afield_ver}, $self->{afield_type}) = unpack('CC', substr($linklayer,8,2));
  1347. #printf("lfield %d\n", $self->{lfield});
  1348. if ($self->{crc_size} > 0) {
  1349. $self->{crc0} = unpack('n', substr($linklayer,TL_BLOCK_SIZE, $self->{crc_size}));
  1350. #printf("crc0 %x calc %x\n", $self->{crc0}, $self->checkCRC(substr($linklayer,0,10)));
  1351. if ($self->{crc0} != $self->checkCRC(substr($linklayer,0,TL_BLOCK_SIZE))) {
  1352. $self->{errormsg} = "CRC check failed on link layer";
  1353. $self->{errorcode} = ERR_CRC_FAILED;
  1354. #print "CRC check failed on link layer\n";
  1355. return 0;
  1356. }
  1357. }
  1358. # header block is 10 bytes + 2 bytes CRC, each following block is 16 bytes + 2 bytes CRC, the last block may be smaller
  1359. $self->{datalen} = $self->{lfield} - (TL_BLOCK_SIZE - 1); # this is without CRCs and the lfield itself
  1360. $self->{datablocks} = int($self->{datalen} / LL_BLOCK_SIZE);
  1361. $self->{datablocks}++ if $self->{datalen} % LL_BLOCK_SIZE != 0;
  1362. $self->{msglen} = TL_BLOCK_SIZE + $self->{crc_size} + $self->{datalen} + $self->{datablocks} * $self->{crc_size};
  1363. #printf("calc len %d, actual %d\n", $self->{msglen}, length($self->{msg}));
  1364. if (length($self->{msg}) > $self->{msglen}) {
  1365. $self->{remainingData} = substr($self->{msg},$self->{msglen});
  1366. } elsif (length($self->{msg}) < $self->{msglen}) {
  1367. $self->{errormsg} = "message too short, expected " . $self->{msglen} . ", got " . length($self->{msg}) . " bytes";
  1368. $self->{errorcode} = ERR_MSG_TOO_SHORT;
  1369. return 0;
  1370. }
  1371. # according to the MBus spec only upper case letters are allowed.
  1372. # some devices send lower case letters none the less
  1373. # convert to upper case to make them spec conformant
  1374. $self->{manufacturer} = uc($self->manId2ascii($self->{mfield}));
  1375. $self->{typestring} = $validDeviceTypes{$self->{afield_type}} || 'unknown';
  1376. return 1;
  1377. }
  1378. sub parse($$)
  1379. {
  1380. my $self = shift;
  1381. $self->{msg} = shift;
  1382. $self->{errormsg} = '';
  1383. $self->{errorcode} = ERR_NO_ERROR;
  1384. if ($self->decodeLinkLayer(substr($self->{msg},0,12)) != 0) {
  1385. $self->{linkLayerOk} = 1;
  1386. return $self->decodeApplicationLayer();
  1387. }
  1388. return 0;
  1389. }
  1390. sub parseLinkLayer($$)
  1391. {
  1392. my $self = shift;
  1393. $self->{msg} = shift;
  1394. $self->{errormsg} = '';
  1395. $self->{errorcode} = ERR_NO_ERROR;
  1396. $self->{linkLayerOk} = $self->decodeLinkLayer(substr($self->{msg},0,12));
  1397. return $self->{linkLayerOk};
  1398. }
  1399. sub parseApplicationLayer($)
  1400. {
  1401. my $self = shift;
  1402. $self->{errormsg} = '';
  1403. $self->{errorcode} = ERR_NO_ERROR;
  1404. return $self->decodeApplicationLayer();
  1405. }
  1406. 1;