WMBus.pm 62 KB

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