WMBus.pm 48 KB

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