98_powerMap.pm 58 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933
  1. ################################################################################
  2. # $Id: 98_powerMap.pm 13338 2017-02-05 15:31:14Z loredo $
  3. ##############################################################################
  4. #
  5. # 98_powerMap.pm
  6. # Original version by igami
  7. #
  8. # Copyright by Julian Pawlowski
  9. # e-mail: julian.pawlowski at gmail.com
  10. #
  11. # This file is part of fhem.
  12. #
  13. # Fhem is free software: you can redistribute it and/or modify
  14. # it under the terms of the GNU General Public License as published by
  15. # the Free Software Foundation, either version 2 of the License, or
  16. # (at your option) any later version.
  17. #
  18. # Fhem is distributed in the hope that it will be useful,
  19. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. # GNU General Public License for more details.
  22. #
  23. # You should have received a copy of the GNU General Public License
  24. # along with fhem. If not, see <http://www.gnu.org/licenses/>.
  25. #
  26. ################################################################################
  27. # TODO
  28. # - document how to include powerMap for other module maintainers
  29. # (see 50_HP1000)
  30. #
  31. package main;
  32. use strict;
  33. use warnings;
  34. use Unit;
  35. # forward declarations #########################################################
  36. sub powerMap_Initialize($);
  37. sub powerMap_Define($$);
  38. sub powerMap_Undefine($$);
  39. sub powerMap_Set($@);
  40. sub powerMap_Get($@);
  41. sub powerMap_Attr(@);
  42. sub powerMap_Notify($$);
  43. sub powerMap_AttrVal($$$$);
  44. sub powerMap_load($$;$$);
  45. sub powerMap_unload($$);
  46. sub powerMap_findPowerMaps($;$);
  47. sub powerMap_verifyEventChain($$$);
  48. sub powerMap_power($$$;$);
  49. sub powerMap_energy($$;$);
  50. sub powerMap_update($;$);
  51. # module hashes ################################################################
  52. my %powerMap_tmpl = (
  53. # Format example for devices w/ model support:
  54. #
  55. # '<TYPE>' => {
  56. # '(<INTERNAL>|<Attribute>)' => {
  57. # '<VAL of INTERNAL or Attribute>' => {
  58. #
  59. # # This is the actual powerMap definition
  60. # '<Reading>' => {
  61. # '<VAL>' => '<Watt>',
  62. # },
  63. # },
  64. # },
  65. #
  66. #
  67. # # This is w/ user attributes
  68. #
  69. # '(<INTERNAL>|<Attribute>)' => {
  70. # '<VAL of INTERNAL or Attribute>' => {
  71. # 'attribute1' => 'value1',
  72. # 'attribute2' => 'value2',
  73. #
  74. # # This is the actual powerMap definition
  75. # 'map' => {
  76. # '<Reading>' => {
  77. # '<VAL>' => '<Watt>',
  78. # },
  79. # },
  80. # },
  81. # },
  82. # },
  83. # Format example for devices w/o model support:
  84. #
  85. # '<TYPE>' => {
  86. #
  87. # # This is the actual powerMap definition
  88. # '<Reading>' => {
  89. # '<VAL>' => '<Watt>',
  90. # },
  91. # },
  92. # Format example for mapping table and user attributes:
  93. #
  94. # '<TYPE>' => {
  95. # 'attribute1' => 'value1',
  96. # 'attribute2' => 'value2',
  97. #
  98. # # This is the actual powerMap definition
  99. # 'map' => {
  100. # '<Reading>' => {
  101. # '<VAL>' => '<Watt>',
  102. # },
  103. # },
  104. # },
  105. # TYPE alias to mirror values
  106. #
  107. # '<TYPE1>' => '<TYPE2>',
  108. #
  109. FS20 => {
  110. state => {
  111. 0 => 0.5,
  112. 100 => 60,
  113. },
  114. },
  115. HMCCU => {
  116. state => {
  117. '*' => 7.5,
  118. },
  119. },
  120. HMCCUCHN => "HMCCUDEV", # alias / forward to other TYPE
  121. HMCCUDEV => {
  122. ccutype => {
  123. 'HM-LC-Dim1TPBU-FM' => {
  124. hmstate => {
  125. unreachable => 0,
  126. working => 101,
  127. up => 101,
  128. down => 101,
  129. 0 => 1.0,
  130. 100 => 101,
  131. },
  132. },
  133. 'HM-LC-Dim1T-FM' => {
  134. hmstate => {
  135. unreachable => 0,
  136. working => 23.5,
  137. up => 23.5,
  138. down => 23.5,
  139. 0 => 1.0,
  140. 100 => 23.5,
  141. },
  142. },
  143. 'HM-LC-Sw2-PB-FM' => {
  144. hmstate => {
  145. unreachable => 0,
  146. off => 0.25,
  147. on => 100.25,
  148. },
  149. },
  150. 'HM-LC-Bl1PBU-FM' => {
  151. hmstate => {
  152. unreachable => 0,
  153. working => 121,
  154. up => 121,
  155. down => 121,
  156. '*' => 0.5,
  157. },
  158. },
  159. 'HM-LC-Bl1-SM' => {
  160. hmstate => {
  161. unreachable => 0,
  162. working => 121,
  163. up => 121,
  164. down => 121,
  165. '*' => 0.4,
  166. },
  167. },
  168. },
  169. },
  170. HUEBridge => {
  171. model => 'modelid',
  172. modelid => {
  173. BSB001 => {
  174. rname_E => 'energy',
  175. rname_P => 'consumption',
  176. map => {
  177. state => {
  178. 0 => 0,
  179. '*' => 1.669,
  180. },
  181. },
  182. },
  183. BSB002 => {
  184. rname_E => 'energy',
  185. rname_P => 'consumption',
  186. map => {
  187. state => {
  188. 0 => 0,
  189. '*' => 1.669,
  190. },
  191. },
  192. },
  193. },
  194. },
  195. HUEDevice => {
  196. model => 'modelid',
  197. modelid => {
  198. # Hue Bulb
  199. LCT001 => {
  200. rname_E => 'energy',
  201. rname_P => 'consumption',
  202. map => {
  203. pct => {
  204. 0 => 0.4,
  205. 100 => 8.5,
  206. },
  207. state => {
  208. unreachable => 0,
  209. '*' => 'pct',
  210. },
  211. },
  212. },
  213. # Hue Spot BR30
  214. LCT002 => {},
  215. # Hue Spot GU10
  216. LCT003 => {},
  217. # Hue Bulb V2
  218. LCT007 => {
  219. rname_E => 'energy',
  220. rname_P => 'consumption',
  221. map => {
  222. pct => {
  223. 0 => 0.4,
  224. 100 => 10,
  225. },
  226. state => {
  227. unreachable => 0,
  228. '*' => 'pct',
  229. },
  230. },
  231. },
  232. # Hue Bulb V3
  233. LCT010 => {
  234. rname_E => 'energy',
  235. rname_P => 'consumption',
  236. map => {
  237. pct => {
  238. 0 => 0.4,
  239. 100 => 8.5,
  240. },
  241. state => {
  242. unreachable => 0,
  243. '*' => 'pct',
  244. },
  245. },
  246. },
  247. # Hue BR30
  248. LCT011 => {},
  249. # Hue Bulb V3
  250. LCT014 => {},
  251. # Living Colors G2
  252. LLC001 => {},
  253. # Living Colors Bloom
  254. LLC005 => {},
  255. # Living Colors Gen3 Iris
  256. LLC006 => {},
  257. # Living Colors Gen3 Bloom
  258. LLC007 => {},
  259. # Living Colors Iris
  260. LLC010 => {},
  261. # Living Colors Bloom
  262. LLC011 => {},
  263. # Living Colors Bloom
  264. LLC012 => {},
  265. # Disney Living Colors
  266. LLC013 => {},
  267. # Living Colors Aura
  268. LLC014 => {},
  269. # Hue Go
  270. LLC020 => {},
  271. # Hue LightStrip
  272. LST001 => {
  273. rname_E => 'energy',
  274. rname_P => 'consumption',
  275. map => {
  276. pct => {
  277. 0 => 0.4,
  278. 100 => 12,
  279. },
  280. state => {
  281. unreachable => 0,
  282. '*' => 'pct',
  283. },
  284. },
  285. },
  286. # Hue LightStrip Plus
  287. LST002 => {
  288. rname_E => 'energy',
  289. rname_P => 'consumption',
  290. map => {
  291. pct => {
  292. 0 => 0.4,
  293. 100 => 20.5,
  294. },
  295. state => {
  296. unreachable => 0,
  297. '*' => 'pct',
  298. },
  299. },
  300. },
  301. # Living Whites Bulb
  302. LWB001 => {
  303. rname_E => 'energy',
  304. rname_P => 'consumption',
  305. map => {
  306. pct => {
  307. 0 => 0.4,
  308. 10 => 1.2,
  309. 20 => 1.7,
  310. 30 => 1.9,
  311. 40 => 2.3,
  312. 50 => 2.7,
  313. 60 => 3.4,
  314. 70 => 4.7,
  315. 80 => 5.9,
  316. 90 => 7.5,
  317. 100 => 9.2,
  318. },
  319. state => {
  320. unreachable => 0,
  321. '*' => 'pct',
  322. },
  323. },
  324. },
  325. # Living Whites Bulb
  326. LWB003 => {
  327. rname_E => 'energy',
  328. rname_P => 'consumption',
  329. map => {
  330. pct => {
  331. 0 => 0.4,
  332. 10 => 1.2,
  333. 20 => 1.7,
  334. 30 => 1.9,
  335. 40 => 2.3,
  336. 50 => 2.7,
  337. 60 => 3.4,
  338. 70 => 4.7,
  339. 80 => 5.9,
  340. 90 => 7.5,
  341. 100 => 9.2,
  342. },
  343. state => {
  344. unreachable => 0,
  345. '*' => 'pct',
  346. },
  347. },
  348. },
  349. # Hue Lux
  350. LWB004 => {},
  351. # Hue Lux
  352. LWB006 => {},
  353. # Hue Lux
  354. LWB007 => {},
  355. # Hue A19 White Ambience
  356. LTW001 => {},
  357. # Hue A19 White Ambience
  358. LTW004 => {},
  359. # Hue GU10 White Ambience
  360. LTW013 => {},
  361. # Hue GU10 White Ambience
  362. LTW014 => {},
  363. # Color Light Module
  364. LLM001 => {},
  365. # Color Temperature Module
  366. LLM010 => {},
  367. # Color Temperature Module
  368. LLM011 => {},
  369. # Color Temperature Module
  370. LLM012 => {},
  371. # LivingWhites Outlet
  372. LWL001 => {},
  373. # Hue Dimmer Switch
  374. RWL020 => {},
  375. # Hue Dimmer Switch
  376. RWL021 => {},
  377. # Hue Tap
  378. ZGPSWITCH => {},
  379. # dresden elektronik FLS-H lp
  380. 'FLS-H3' => {},
  381. # dresden elektronik FLS-PP lp
  382. 'FLS-PP3' => {},
  383. # LIGHTIFY Flex RGBW
  384. 'Flex RGBW' => {},
  385. # LIGHTIFY Classic A60 RGBW
  386. 'Classic A60 RGBW' => {},
  387. # LIGHTIFY Gardenspot Mini RGB
  388. 'Gardenspot RGB' => {},
  389. # LIGHTIFY Surface light tunable white
  390. 'Surface Light TW' => {},
  391. # LIGHTIFY Classic A60 tunable white
  392. 'Classic A60 TW' => {},
  393. # LIGHTIFY Classic B40 tunable white
  394. 'Classic B40 TW' => {},
  395. # LIGHTIFY PAR16 50 tunable white
  396. 'PAR16 50 TW' => {},
  397. # LIGHTIFY Plug
  398. 'Plug - LIGHTIFY' => {},
  399. # LIGHTIFY Plug
  400. 'Plug 01' => {},
  401. # Busch-Jaeger ZigBee Light Link Relais
  402. 'RM01' => {},
  403. # Busch-Jaeger ZigBee Light Link Dimmer
  404. 'DM01' => {},
  405. },
  406. },
  407. netatmo => {
  408. model => {
  409. NAMain => {
  410. temperature => {
  411. '*' => 5,
  412. },
  413. },
  414. },
  415. },
  416. Panstamp => {
  417. 'Pumpe_Heizkreis' => {
  418. 'off' => "0,Pumpe_Boiler,Brenner",
  419. 'on' => "30,Pumpe_Boiler,Brenner",
  420. },
  421. 'Pumpe_Boiler' => {
  422. 'off' => "0,Pumpe_Heizkreis,Brenner",
  423. 'on' => "30,Pumpe_Heizkreis,Brenner",
  424. },
  425. 'Brenner' => {
  426. 'off' => "0,Pumpe_Heizkreis,Pumpe_Boiler",
  427. 'on' => "40,Pumpe_Heizkreis,Pumpe_Boiler",
  428. },
  429. },
  430. SONOSPLAYER => {
  431. model => {
  432. Sonos_S6 => {
  433. stateAV => {
  434. disappeared => 0,
  435. off => 2.2,
  436. mute => 2.2,
  437. pause => 2.2,
  438. on => 14.5,
  439. },
  440. },
  441. Sonos_S5 => {
  442. stateAV => {
  443. disappeared => 0,
  444. off => 8.3,
  445. mute => 8.3,
  446. pause => 8.3,
  447. on => 14.5,
  448. },
  449. },
  450. Sonos_S3 => {
  451. stateAV => {
  452. disappeared => 0,
  453. off => 4.4,
  454. mute => 4.4,
  455. pause => 4.4,
  456. on => 11.3,
  457. },
  458. },
  459. Sonos_S1 => {
  460. stateAV => {
  461. disappeared => 0,
  462. off => 3.8,
  463. mute => 3.8,
  464. pause => 3.8,
  465. on => 5.2,
  466. },
  467. },
  468. },
  469. },
  470. );
  471. # initialize ###################################################################
  472. sub powerMap_Initialize($) {
  473. my ($hash) = @_;
  474. my $TYPE = "powerMap";
  475. $hash->{DefFn} = $TYPE . "_Define";
  476. $hash->{UndefFn} = $TYPE . "_Undefine";
  477. $hash->{SetFn} = $TYPE . "_Set";
  478. $hash->{GetFn} = $TYPE . "_Get";
  479. $hash->{AttrFn} = $TYPE . "_Attr";
  480. $hash->{NotifyFn} = $TYPE . "_Notify";
  481. $hash->{AttrList} =
  482. "disable:1,0 "
  483. . $TYPE
  484. . "_gridV:230,110 "
  485. . $TYPE
  486. . "_eventChainWarnOnly:1,0 "
  487. . $readingFnAttributes;
  488. addToAttrList( $TYPE . "_noEnergy:1,0" );
  489. addToAttrList( $TYPE . "_noPower:1,0" );
  490. addToAttrList( $TYPE . "_interval" );
  491. addToAttrList( $TYPE . "_rname_P:textField" );
  492. addToAttrList( $TYPE . "_rname_E:textField" );
  493. addToAttrList( $TYPE . ":textField-long" );
  494. }
  495. # regular Fn ###################################################################
  496. sub powerMap_Define($$) {
  497. my ( $hash, $def ) = @_;
  498. my ( $name, $type, $rest ) = split( /[\s]+/, $def, 3 );
  499. my $TYPE = $hash->{TYPE};
  500. my $d = $modules{$TYPE}{defptr};
  501. return "Usage: define <name> $TYPE" if ($rest);
  502. return "$TYPE device already defined as $d->{NAME}" if ( defined($d) );
  503. my $interval = AttrVal( $name, $TYPE . "_interval", 900 );
  504. $interval = 900 unless ( looks_like_number($interval) );
  505. $interval = 30 if ( $interval < 30 );
  506. $modules{$TYPE}{defptr} = $hash;
  507. $hash->{INTERVAL} = $interval;
  508. $hash->{STATE} = "Initialized";
  509. return;
  510. }
  511. sub powerMap_Undefine($$) {
  512. my ( $hash, $arg ) = @_;
  513. my $name = $hash->{NAME};
  514. my $TYPE = $hash->{TYPE};
  515. delete $modules{$TYPE}{defptr};
  516. # terminate powerMap for each device
  517. foreach ( devspec2array("i:pM_update=.+") ) {
  518. RemoveInternalTimer("$name|$_");
  519. delete $defs{$_}{pM_update};
  520. delete $defs{$_}{pM_interval};
  521. }
  522. return;
  523. }
  524. sub powerMap_Set($@) {
  525. my ( $hash, @a ) = @_;
  526. return "Missing argument" if ( @a < 2 );
  527. my $TYPE = $hash->{TYPE};
  528. my $name = shift @a;
  529. my $argument = shift @a;
  530. my $value = join( " ", @a ) if (@a);
  531. my $assign;
  532. my $maps = powerMap_findPowerMaps($name);
  533. foreach ( sort keys %{$maps} ) {
  534. $assign .= "," if ($assign);
  535. $assign .= $_;
  536. }
  537. my %powerMap_sets = ( "assign" => "assign:$assign", );
  538. return "Unknown argument $argument, choose one of "
  539. . join( " ", values %powerMap_sets )
  540. unless ( exists( $powerMap_sets{$argument} ) );
  541. my $ret;
  542. if ( $argument eq "assign" ) {
  543. my @devices = devspec2array($value);
  544. return "No matching device found." unless (@devices);
  545. foreach my $d (@devices) {
  546. next
  547. unless ( ref( $maps->{$d}{map} ) eq "HASH"
  548. && keys %{ $maps->{$d}{map} } );
  549. # write attributes
  550. $Data::Dumper::Terse = 1;
  551. $Data::Dumper::Deepcopy = 1;
  552. $Data::Dumper::Sortkeys = 1;
  553. foreach ( sort keys %{ $maps->{$d} } ) {
  554. my $n = $_;
  555. $n = $TYPE if ( $_ eq "map" );
  556. $n = $TYPE . "_" . $_ unless ( $n =~ /^$TYPE/ );
  557. my $txt = $maps->{$d}{$_};
  558. $txt = Dumper( $maps->{$d}{$_} ) if ( $_ eq "map" );
  559. $ret .= CommandAttr( undef, "$d $n $txt" );
  560. $ret .= "$d - Added attribute $n\n" if ( @devices > 1 );
  561. }
  562. $Data::Dumper::Terse = 0;
  563. $Data::Dumper::Deepcopy = 0;
  564. $Data::Dumper::Sortkeys = 0;
  565. }
  566. }
  567. return $ret;
  568. }
  569. sub powerMap_Get($@) {
  570. my ( $hash, @a ) = @_;
  571. return "Missing argument" if ( @a < 2 );
  572. my $TYPE = $hash->{TYPE};
  573. my $name = shift @a;
  574. my $argument = shift @a;
  575. my $value = join( " ", @a ) if (@a);
  576. my %powerMap_gets = ( "devices" => "devices:noArg", );
  577. return "Unknown argument $argument, choose one of "
  578. . join( " ", values %powerMap_gets )
  579. unless ( exists( $powerMap_gets{$argument} ) );
  580. my $ret;
  581. if ( $argument eq "devices" ) {
  582. my $pmdevs = powerMap_findPowerMaps( $name, ":PM_ENABLED" );
  583. return keys %{$pmdevs}
  584. ? join( "\n", sort keys %{$pmdevs} )
  585. : "No powerMap enabled devices found.";
  586. }
  587. return $ret;
  588. }
  589. sub powerMap_Attr(@) {
  590. my ( $cmd, $name, $attribute, $value ) = @_;
  591. my $hash = $defs{$name};
  592. my $TYPE = $hash->{TYPE};
  593. if ( $attribute eq "disable" ) {
  594. readingsSingleUpdate( $hash, "state", "disabled", 1 )
  595. if ( $value and $value == 1 );
  596. readingsSingleUpdate( $hash, "state", "enabled", 1 )
  597. if ( $cmd eq "del" or !$value );
  598. }
  599. return if ( IsDisabled($name) );
  600. if ( $attribute eq $TYPE . "_interval" ) {
  601. my $interval = $cmd eq "set" ? $value : 900;
  602. $interval = 900 unless ( looks_like_number($interval) );
  603. $interval = 30 if ( $interval < 30 );
  604. $hash->{INTERVAL} = $interval;
  605. }
  606. return;
  607. }
  608. sub powerMap_Notify($$) {
  609. my ( $hash, $dev_hash ) = @_;
  610. my $name = $hash->{NAME};
  611. my $dev = $dev_hash->{NAME};
  612. my $TYPE = $hash->{TYPE};
  613. return
  614. if (
  615. !$init_done
  616. or IsDisabled($name)
  617. or IsDisabled($dev)
  618. or $name eq $dev # do not process own events
  619. or powerMap_AttrVal( $name, $dev, "noPower", 0 )
  620. or ( !$modules{ $defs{$dev}{TYPE} }{$TYPE}
  621. and !$defs{$dev}{$TYPE}
  622. and $dev ne "global" )
  623. );
  624. my $events = deviceEvents( $dev_hash, 1 );
  625. return unless ($events);
  626. Log3 $name, 5, "$TYPE: Entering powerMap_Notify() for $dev";
  627. # global events
  628. if ( $dev eq "global" ) {
  629. foreach my $event ( @{$events} ) {
  630. next unless ( defined($event) );
  631. # initialize or terminate powerMap for each device
  632. if ( $event =~ /^(INITIALIZED|SHUTDOWN)$/ ) {
  633. foreach ( keys %{ powerMap_findPowerMaps( $name, ":PM_$1" ) } )
  634. {
  635. next
  636. if ( $_ eq "global"
  637. or $_ eq $name
  638. or
  639. powerMap_AttrVal( $name, $_, $TYPE . "_noEnergy", 0 ) );
  640. powerMap_update("$name|$dev") if ( $1 eq "SHUTDOWN" );
  641. next
  642. unless ( $1 eq "SHUTDOWN"
  643. || powerMap_load( $name, $_, undef, 1 ) );
  644. Log3 $name, 4, "$TYPE: $1 for $_";
  645. }
  646. }
  647. # device attribute deleted
  648. elsif ( $event =~ m/^(DELETEATTR)\s(.*)\s($TYPE)(\s+(.*))?/ ) {
  649. powerMap_unload( $name, $2 );
  650. }
  651. # device attribute changed
  652. elsif ( $event =~
  653. m/^(ATTR|DELETEATTR)\s(.*)\s($TYPE[a-zA-Z_]*)(\s+(.*))?/ )
  654. {
  655. next unless ( powerMap_load( $name, $2 ) );
  656. Log3 $name, 4, "$TYPE: UPDATED for $2";
  657. }
  658. # device was deleted
  659. elsif ( $event =~ m/^(DELETED)\s(.*)/ ) {
  660. powerMap_unload( $name, $2 );
  661. }
  662. # device was newly defined, modified or renamed
  663. elsif ( $event =~ m/^(DEFINED|MODIFIED|RENAMED)\s(.*)/ ) {
  664. next unless ( powerMap_load( $name, $2 ) );
  665. Log3 $name, 4, "$TYPE: INITIALIZED for $2";
  666. }
  667. }
  668. return;
  669. }
  670. my $rname_e = powerMap_AttrVal( $name, $dev, "rname_E", "pM_energy" );
  671. my $rname_p = powerMap_AttrVal( $name, $dev, "rname_P", "pM_consumption" );
  672. my $powerRecalcDone;
  673. # foreign device events
  674. foreach my $event ( @{$events} ) {
  675. next
  676. if (!$event
  677. or $event =~ /^($rname_e|$rname_p): /
  678. or $event !~ /: / );
  679. # only recalculate once no matter
  680. # how many events we get at once
  681. unless ($powerRecalcDone) {
  682. my $power = powerMap_power( $name, $dev, $event );
  683. if ( defined($power) ) {
  684. $powerRecalcDone = 1;
  685. powerMap_update( "$name|$dev", $power );
  686. # recalculate CHANGEDWITHSTATE
  687. # for target device in deviceEvents()
  688. $dev_hash->{CHANGEDWITHSTATE} = [];
  689. }
  690. }
  691. }
  692. readingsSingleUpdate( $hash, "state", "Last device: $dev", 1 )
  693. if ($powerRecalcDone);
  694. return undef;
  695. }
  696. # module Fn ####################################################################
  697. sub powerMap_AttrVal($$$$) {
  698. my ( $p, $d, $n, $default ) = @_;
  699. my $TYPE = $defs{$p}{TYPE};
  700. Log3 $p, 6, "$TYPE: Entering powerMap_AttrVal() for $d";
  701. return $default if ( !$TYPE );
  702. # device attribute
  703. #
  704. my $da = AttrVal( $d, $TYPE . "_" . $n, AttrVal( $d, $n, undef ) );
  705. return $da if ( defined($da) );
  706. # device INTERNAL
  707. #
  708. # $defs{device}{TYPE}{attribute}
  709. return $defs{$d}{$TYPE}{$n}
  710. if ( $d
  711. && defined( $defs{$d} )
  712. && defined( $defs{$d}{$TYPE} )
  713. && defined( $defs{$d}{$TYPE}{$n} ) );
  714. # $defs{device}{.TYPE}{attribute}
  715. return $defs{$d}{".$TYPE"}{$n}
  716. if ( $d
  717. && defined( $defs{$d} )
  718. && defined( $defs{$d}{".$TYPE"} )
  719. && defined( $defs{$d}{".$TYPE"}{$n} ) );
  720. # $defs{device}{TYPE_attribute}
  721. return $defs{$d}{ $TYPE . "_" . $n }
  722. if ( $d
  723. && defined( $defs{$d} )
  724. && defined( $defs{$d}{ $TYPE . "_" . $n } ) );
  725. # $defs{device}{attribute}
  726. return $defs{$d}{$n}
  727. if ( $d
  728. && defined( $defs{$d} )
  729. && defined( $defs{$d}{$n} ) );
  730. # $defs{device}{.TYPE_attribute}
  731. return $defs{$d}{ "." . $TYPE . "_" . $n }
  732. if ( $d
  733. && defined( $defs{$d} )
  734. && defined( $defs{$d}{ "." . $TYPE . "_" . $n } ) );
  735. # $defs{device}{.attribute}
  736. return $defs{$d}{".$n"}
  737. if ( $d
  738. && defined( $defs{$d} )
  739. && defined( $defs{$d}{".$n"} ) );
  740. # module HASH
  741. #
  742. my $t = $defs{$d}{TYPE};
  743. # $modules{module}{TYPE}{attribute}
  744. return $modules{$t}{$TYPE}{$n}
  745. if ( $t
  746. && defined( $modules{$t} )
  747. && defined( $modules{$t}{$TYPE} )
  748. && defined( $modules{$t}{$TYPE}{$n} ) );
  749. # $modules{module}{TYPE}{TYPE_attribute}
  750. return $modules{$t}{$TYPE}{ $TYPE . "_" . $n }
  751. if ( $t
  752. && defined( $modules{$t} )
  753. && defined( $modules{$t}{$TYPE} )
  754. && defined( $modules{$t}{$TYPE}{ $TYPE . "_" . $n } ) );
  755. # module attribute
  756. #
  757. return AttrVal( $p, $TYPE . "_" . $n, AttrVal( $p, $n, $default ) );
  758. }
  759. sub powerMap_load($$;$$) {
  760. my ( $name, $dev, $unload, $modSupport ) = @_;
  761. my $dev_hash = $defs{$dev};
  762. my $TYPE = $defs{$name}{TYPE};
  763. Log3 $name, 5, "$TYPE: Entering powerMap_load() for $dev";
  764. unless ($dev_hash) {
  765. RemoveInternalTimer("$name|$dev");
  766. delete $dev_hash->{pM_update}
  767. if ( defined( $dev_hash->{pM_update} ) );
  768. delete $dev_hash->{pM_interval}
  769. if ( defined( $dev_hash->{pM_interval} ) );
  770. return;
  771. }
  772. my $powerMap = $unload ? undef : AttrVal( $dev, $TYPE, undef );
  773. my $rname_e = powerMap_AttrVal( $name, $dev, "rname_E", "pM_energy" );
  774. my $rname_p = powerMap_AttrVal( $name, $dev, "rname_P", "pM_consumption" );
  775. # Support for Unit.pm
  776. $dev_hash->{readingsDesc}{$rname_e} = { rtype => 'whr', };
  777. $dev_hash->{readingsDesc}{$rname_p} = { rtype => 'w', };
  778. # Enable Unit.pm for DbLog
  779. if ( $modules{ $dev_hash->{TYPE} }{DbLog_splitFn}
  780. or $dev_hash->{DbLog_splitFn}
  781. or $dev_hash->{'.DbLog_splitFn'} )
  782. {
  783. Log3 $name, 5,
  784. "$TYPE: $dev has defined it's own DbLog_splitFn; "
  785. . "won't enable unit support with DbLog but rather "
  786. . "let this to the module itself";
  787. }
  788. else {
  789. Log3 $name, 4, "$TYPE: Enabled unit support for $dev";
  790. $dev_hash->{'.DbLog_splitFn'} = "Unit_DbLog_split";
  791. }
  792. # restore original powerMap from module
  793. if ( defined( $dev_hash->{$TYPE}{map} )
  794. and defined( $dev_hash->{$TYPE}{'map.module'} ) )
  795. {
  796. Log3 $dev, 5,
  797. "$TYPE $dev: Updated device hash with module mapping table";
  798. delete $dev_hash->{$TYPE}{map};
  799. $dev_hash->{$TYPE}{map} = $dev_hash->{$TYPE}{'map.module'};
  800. delete $dev_hash->{$TYPE}{'map.module'};
  801. }
  802. # delete device specific map
  803. elsif ( $unload && defined( $dev_hash->{$TYPE}{map} ) ) {
  804. delete $dev_hash->{$TYPE}{map};
  805. }
  806. unless ($powerMap) {
  807. return powerMap_update("$name|$dev")
  808. if ($modSupport);
  809. RemoveInternalTimer("$name|$dev");
  810. delete $dev_hash->{pM_update}
  811. if ( defined( $dev_hash->{pM_update} ) );
  812. delete $dev_hash->{pM_interval}
  813. if ( defined( $dev_hash->{pM_interval} ) );
  814. return;
  815. }
  816. if ( $powerMap =~ m/=>/
  817. and $powerMap !~ m/\$/ )
  818. {
  819. $powerMap = "{" . $powerMap . "}" if ( $powerMap !~ m/^{.*}$/s );
  820. my $map = eval $powerMap;
  821. if ($@) {
  822. Log3 $dev, 3,
  823. "$TYPE $dev: Unable to evaluate attribute $TYPE: " . $@;
  824. }
  825. elsif ( ref($map) ne "HASH" ) {
  826. Log3 $dev, 3,
  827. "$TYPE $dev: Attribute $TYPE was not defined in HASH format";
  828. }
  829. else {
  830. # backup any pre-existing definitions from module
  831. if ( defined( $dev_hash->{$TYPE}{map} ) ) {
  832. Log3 $dev, 4,
  833. "$TYPE $dev: Updated device hash with user mapping table";
  834. $dev_hash->{$TYPE}{'map.module'} =
  835. $dev_hash->{$TYPE}{map};
  836. delete $dev_hash->{$TYPE}{map};
  837. }
  838. else {
  839. Log3 $dev, 4,
  840. "$TYPE $dev: Updated device hash with mapping table";
  841. }
  842. $dev_hash->{$TYPE}{map} = $map;
  843. powerMap_verifyEventChain( $name, $dev, $map );
  844. return powerMap_update("$name|$dev");
  845. }
  846. }
  847. else {
  848. Log3 $dev, 3, "$TYPE $dev: Illegal format for attribute $TYPE";
  849. }
  850. return 0;
  851. }
  852. sub powerMap_unload($$) {
  853. my ( $n, $d ) = @_;
  854. return powerMap_load( $n, $d, 1 );
  855. }
  856. sub powerMap_findPowerMaps($;$) {
  857. my ( $name, $dev ) = @_;
  858. my %maps;
  859. # directly return any existing device specific definition
  860. if ( $dev && $dev !~ /^:/ ) {
  861. return {}
  862. unless ( defined( $defs{$dev} )
  863. && defined( $defs{$dev}{TYPE} ) );
  864. return $defs{$dev}{powerMap}{map}
  865. if (
  866. $defs{$dev}{powerMap}{map}
  867. && ref( $defs{$dev}{powerMap}{map} ) eq "HASH"
  868. && keys %{ $defs{$dev}{powerMap}{map} }
  869. && powerMap_verifyEventChain(
  870. $name, $dev, $defs{$dev}{powerMap}{map}
  871. )
  872. );
  873. }
  874. # get all devices with direct powerMap definitions
  875. else {
  876. foreach ( devspec2array("i:powerMap=.+") ) {
  877. $maps{$_}{map} = $defs{$_}{powerMap}{map}
  878. if ( $defs{$_}{powerMap}{map}
  879. && ref( $defs{$_}{powerMap}{map} ) eq "HASH"
  880. && keys %{ $defs{$_}{powerMap}{map} } );
  881. }
  882. # during initialization, also find devices where we
  883. # need to load their custom attribute into the hash
  884. if ( $dev && $dev eq ":PM_INITIALIZED" ) {
  885. foreach ( devspec2array("a:powerMap=.+") ) {
  886. $maps{$_}{map} = {} if ( !$maps{$_}{map} );
  887. }
  888. }
  889. }
  890. # search templates from modules
  891. foreach
  892. my $TYPE ( $dev && $dev !~ /^:/ ? $defs{$dev}{TYPE} : keys %modules )
  893. {
  894. next
  895. unless ( $modules{$TYPE}{powerMap}
  896. && keys %{ $modules{$TYPE}{powerMap} } );
  897. my $t = $modules{$TYPE}{powerMap};
  898. my $modelSupport = 0;
  899. # modules w/ model support
  900. unless ( $t->{map} ) {
  901. foreach my $ta ( keys %{$t} ) {
  902. my $a = $t->{$ta};
  903. $a = $t->{ $t->{$ta} }
  904. if ( !ref( $t->{$ta} )
  905. && $t->{ $t->{$ta} } );
  906. next unless ( ref($a) eq "HASH" && !$a->{map} );
  907. foreach my $tm ( keys %{$a} ) {
  908. my $m = $a->{$tm};
  909. $m = $a->{ $a->{$tm} }
  910. if ( !ref( $a->{$tm} )
  911. && $a->{ $a->{$tm} } );
  912. next unless ( ref($m) eq "HASH" );
  913. $modelSupport = 1;
  914. foreach ( devspec2array("TYPE=$TYPE:FILTER=$ta=$tm") ) {
  915. next if ( $maps{$_} );
  916. if ( $m->{map} ) {
  917. next unless ( keys %{ $m->{map} } );
  918. $maps{$_} = $m;
  919. }
  920. else {
  921. next unless ( keys %{$m} );
  922. $maps{$_}{map} = $m;
  923. }
  924. }
  925. }
  926. }
  927. }
  928. # modules w/o model support
  929. unless ($modelSupport) {
  930. foreach ( devspec2array("TYPE=$TYPE") ) {
  931. next if ( $maps{$_} );
  932. if ( $t->{map} ) {
  933. next unless ( keys %{ $t->{map} } );
  934. $maps{$_} = $t;
  935. }
  936. else {
  937. next unless ( keys %{$t} );
  938. $maps{$_}{map} = $t;
  939. }
  940. }
  941. }
  942. }
  943. # find possible template for each Fhem device
  944. unless ($dev) {
  945. foreach my $TYPE ( keys %powerMap_tmpl ) {
  946. next unless ( $modules{$TYPE} );
  947. my $t = $powerMap_tmpl{$TYPE};
  948. $t = $powerMap_tmpl{ $powerMap_tmpl{$TYPE} }
  949. if ( !ref( $powerMap_tmpl{$TYPE} )
  950. && $powerMap_tmpl{ $powerMap_tmpl{$TYPE} } );
  951. my $modelSupport = 0;
  952. # modules w/ model support
  953. foreach my $ta ( keys %{$t} ) {
  954. my $a = $t->{$ta};
  955. $a = $t->{ $t->{$ta} }
  956. if ( !ref( $t->{$ta} )
  957. && $t->{ $t->{$ta} } );
  958. next unless ( ref($a) eq "HASH" );
  959. foreach my $m ( keys %{$a} ) {
  960. next
  961. unless ( ref( $a->{$m} ) eq "HASH"
  962. && !$a->{map} );
  963. $modelSupport = 1;
  964. foreach ( devspec2array("TYPE=$TYPE:FILTER=$ta=$m") ) {
  965. next if ( $maps{$_} );
  966. if ( $a->{$m}{map} ) {
  967. next unless ( keys %{ $a->{$m}{map} } );
  968. $maps{$_} = $a->{$m};
  969. }
  970. else {
  971. next unless ( keys %{ $a->{$m} } );
  972. $maps{$_}{map} = $a->{$m};
  973. }
  974. }
  975. }
  976. }
  977. # modules w/o model support
  978. unless ($modelSupport) {
  979. foreach ( devspec2array("TYPE=$TYPE") ) {
  980. next if ( $maps{$_} );
  981. if ( $t->{map} ) {
  982. next unless ( keys %{ $t->{map} } );
  983. $maps{$_} = $t;
  984. }
  985. else {
  986. next unless ( keys %{$t} );
  987. $maps{$_}{map} = $t;
  988. }
  989. }
  990. }
  991. }
  992. }
  993. foreach my $d ( keys %maps ) {
  994. # filter devices where no reading exists
  995. unless ( $dev && $dev eq ":PM_INITIALIZED" ) {
  996. if ( !$maps{$d}{map} || ref( $maps{$d}{map} ) ne "HASH" ) {
  997. delete $maps{$d};
  998. next;
  999. }
  1000. my $verified = 0;
  1001. foreach ( keys %{ $maps{$d}{map} } ) {
  1002. if ( ReadingsVal( $d, $_, undef ) ) {
  1003. $verified = 1;
  1004. last;
  1005. }
  1006. }
  1007. delete $maps{$d} unless ($verified);
  1008. }
  1009. powerMap_verifyEventChain( $name, $d, $maps{$d}{map} );
  1010. }
  1011. return {}
  1012. if ( $dev && $dev !~ /^:/ && !defined( $maps{$dev} ) );
  1013. return $maps{$dev}{map} if ( $dev && $dev !~ /^:/ );
  1014. return \%maps;
  1015. }
  1016. sub powerMap_verifyEventChain($$$) {
  1017. my ( $name, $dev, $map ) = @_;
  1018. my $TYPE = $defs{$name}{TYPE};
  1019. my %filter;
  1020. return 0 unless ( ref($map) eq "HASH" );
  1021. my $attrminint = AttrVal( $dev, "event-min-interval", undef );
  1022. if ($attrminint) {
  1023. my @a = split( /,/, $attrminint );
  1024. $filter{attrminint} = \@a;
  1025. }
  1026. my $attraggr = AttrVal( $dev, "event-aggregator", undef );
  1027. if ($attraggr) {
  1028. my @a = split( /,/, $attraggr );
  1029. $filter{attraggr} = \@a;
  1030. }
  1031. my $attreocr = AttrVal( $dev, "event-on-change-reading", undef );
  1032. if ($attreocr) {
  1033. my @a = split( /,/, $attreocr );
  1034. $filter{attreocr} = \@a;
  1035. }
  1036. my $attreour = AttrVal( $dev, "event-on-update-reading", undef );
  1037. if ($attreour) {
  1038. my @a = split( /,/, $attreour );
  1039. $filter{attreour} = \@a;
  1040. }
  1041. my $attrtocr = AttrVal( $dev, "timestamp-on-change-reading", undef );
  1042. if ($attrtocr) {
  1043. my @a = split( /,/, $attrtocr );
  1044. $filter{attrtocr} = \@a;
  1045. }
  1046. return 1 unless ( keys %filter );
  1047. my $leocr = "";
  1048. foreach my $reading ( keys %{$map} ) {
  1049. # verify reocr + reour
  1050. if ( $filter{attreocr} || $filter{attreour} ) {
  1051. my $eocr = $filter{attreocr}
  1052. && (
  1053. my @eocrv = grep {
  1054. my $l = $_;
  1055. $l =~ s/:.*//;
  1056. ( $reading =~ m/^$l$/ ) ? $_ : undef
  1057. } @{ $filter{attreocr} }
  1058. );
  1059. my $eour = $filter{attreour}
  1060. && grep( $reading =~ m/^$_$/, @{ $filter{attreour} } );
  1061. unless ($eour) {
  1062. if (
  1063. !$eocr
  1064. || ( $eocrv[0] =~ m/.*:(.*)/
  1065. && ( !looks_like_number($1) || $1 > 0 ) )
  1066. )
  1067. {
  1068. $leocr .= "," if ( $leocr ne "" );
  1069. $leocr .= $reading;
  1070. }
  1071. if ( $filter{attrtocr}
  1072. && grep( $reading =~ m/^$_$/, @{ $filter{attrtocr} } ) )
  1073. {
  1074. Log3 $dev, 2,
  1075. "$TYPE $dev: WARNING - Attribute "
  1076. . "timestamp-on-change-reading is not compatible "
  1077. . "when using $TYPE with reading '$reading'";
  1078. }
  1079. }
  1080. }
  1081. # verify min-interval
  1082. my @v = grep {
  1083. my $l = $_;
  1084. $l =~ s/:.*//;
  1085. ( $reading =~ m/^$l$/ ) ? $_ : undef
  1086. } @{ $filter{attrminint} };
  1087. if (@v) {
  1088. Log3 $dev, 2,
  1089. "$TYPE $dev: WARNING - Attribute "
  1090. . "event-min-interval is not compatible "
  1091. . "when using $TYPE with reading '$reading'";
  1092. }
  1093. # verify aggregator
  1094. my @v2 = grep {
  1095. my $l = $_;
  1096. $l =~ s/:.*//;
  1097. ( $reading =~ m/^$l$/ ) ? $_ : undef
  1098. } @{ $filter{attraggr} };
  1099. if (@v2) {
  1100. Log3 $dev, 2,
  1101. "$TYPE $dev: WARNING - Attribute "
  1102. . "event-aggregator is not compatible "
  1103. . "when using $TYPE with reading '$reading'";
  1104. }
  1105. }
  1106. if ( $leocr ne "" ) {
  1107. if ( powerMap_AttrVal( $name, $dev, "eventChainWarnOnly", 0 ) ) {
  1108. Log3 $dev, 2,
  1109. "$TYPE $dev: ERROR: Broken event chain - Attributes "
  1110. . "event-on-change-reading or event-on-update-reading "
  1111. . "need to contain reading(s) '$leocr'";
  1112. }
  1113. else {
  1114. Log3 $dev, 2,
  1115. "$TYPE $dev: NOTE - Attribute "
  1116. . "event-on-change-reading adjusted "
  1117. . "to fulfill event chain for reading(s) '$leocr'";
  1118. $attreocr .= "," if ($attreocr);
  1119. $attreocr .= $leocr;
  1120. CommandAttr( undef, "$dev event-on-change-reading $attreocr" );
  1121. }
  1122. }
  1123. return 1;
  1124. }
  1125. sub powerMap_power($$$;$) {
  1126. my ( $name, $dev, $event, $loop ) = @_;
  1127. my $hash = $defs{$name};
  1128. my $TYPE = $hash->{TYPE};
  1129. my $power = 0;
  1130. my $powerMap = powerMap_findPowerMaps( $name, $dev );
  1131. return unless ( defined($powerMap) and ref($powerMap) eq "HASH" );
  1132. if ( $event =~ /^([A-Za-z\d_\.\-\/]+):\s+(.*)$/ ) {
  1133. my ( $reading, $val ) = ( $1, $2 );
  1134. my $num = $val;
  1135. $num =~ s/[^-\.\d]//g;
  1136. my $valueAliases = {
  1137. initialized => '0',
  1138. unavailable => '0',
  1139. disappeared => '0',
  1140. absent => '0',
  1141. disabled => '0',
  1142. disconnected => '0',
  1143. off => '0',
  1144. on => '100',
  1145. connected => '100',
  1146. enabled => '100',
  1147. present => '100',
  1148. appeared => '100',
  1149. available => '100',
  1150. };
  1151. $num = $valueAliases->{ lc($val) }
  1152. if ( defined( $valueAliases->{ lc($val) } )
  1153. and looks_like_number( $valueAliases->{ lc($val) } ) );
  1154. # no power consumption defined for this reading
  1155. return unless ( defined( $powerMap->{$reading} ) );
  1156. Log3 $name, 5, "$TYPE: Entering powerMap_power() for $dev:$reading";
  1157. Log3 $dev, 5, "$TYPE $dev: $reading: val=$val num=$num";
  1158. # direct assigned power consumption (value)
  1159. if ( defined( $powerMap->{$reading}{$val} ) ) {
  1160. $power = $powerMap->{$reading}{$val};
  1161. }
  1162. # valueAliases mapping
  1163. elsif ( defined( $valueAliases->{ lc($val) } )
  1164. and
  1165. defined( $powerMap->{$reading}{ $valueAliases->{ lc($val) } } ) )
  1166. {
  1167. $power = $powerMap->{$reading}{ $valueAliases->{ lc($val) } };
  1168. }
  1169. # direct assigned power consumption (numeric)
  1170. elsif ( defined( $powerMap->{$reading}{$num} ) ) {
  1171. $power = $powerMap->{$reading}{$num};
  1172. }
  1173. # value interpolation
  1174. elsif ( looks_like_number($num) ) {
  1175. my ( $val1, $val2 );
  1176. foreach ( sort { $a cmp $b } keys( %{ $powerMap->{$reading} } ) ) {
  1177. next unless ( looks_like_number($_) );
  1178. $val1 = $_ if ( $_ < $num );
  1179. $val2 = $_ if ( $_ > $num );
  1180. last if ( defined($val2) );
  1181. }
  1182. if ($val2) {
  1183. Log3 $dev, 5,
  1184. "$TYPE $dev: $reading: Interpolating power value "
  1185. . "between $val1 and $val2";
  1186. my $y1 = $powerMap->{$reading}{$val1};
  1187. $y1 =~ s/^([-\.\d]+)(.*)/$1/g;
  1188. my $y1t = $2;
  1189. $y1 = 0 unless ( looks_like_number($y1) );
  1190. my $y2 = $powerMap->{$reading}{$val2};
  1191. $y2 =~ s/^([-\.\d]+)(.*)/$1/g;
  1192. my $y2t = $2;
  1193. $y2 = 0 unless ( looks_like_number($y2) );
  1194. my $m = ( ($y2) - ($y1) ) / ( ($val2) - ($val1) );
  1195. my $b =
  1196. ( ( ($val2) * ($y1) ) - ( ($val1) * ($y2) ) ) /
  1197. ( ($val2) - ($val1) );
  1198. my $powerFormat =
  1199. powerMap_AttrVal( $name, $dev, "format_P", undef );
  1200. if ($powerFormat) {
  1201. $power =
  1202. sprintf( $powerFormat, ( ($m) * ($num) ) + ($b) );
  1203. }
  1204. else {
  1205. $power = ( ($m) * ($num) ) + ($b);
  1206. }
  1207. if ( !$loop && $power - $y1 < $y2 - $power ) {
  1208. $power .= $y1t;
  1209. }
  1210. elsif ( !$loop ) {
  1211. $power .= $y2t;
  1212. }
  1213. }
  1214. elsif ( defined( $powerMap->{$reading}{'*'} ) ) {
  1215. $power = $powerMap->{$reading}{'*'};
  1216. }
  1217. else {
  1218. Log3 $dev, 3, "$TYPE $dev: Power value interpolation failed";
  1219. }
  1220. }
  1221. elsif ( defined( $powerMap->{$reading}{'*'} ) ) {
  1222. $power = $powerMap->{$reading}{'*'};
  1223. }
  1224. # consider additional readings if desired
  1225. unless ( looks_like_number($power) ) {
  1226. my $sum = 0;
  1227. my $rlist = join( ",", keys %{$powerMap} );
  1228. $power =~ s/\*/$rlist/;
  1229. foreach ( split( ",", $power ) ) {
  1230. next if ( $reading eq $_ );
  1231. if ( looks_like_number($_) ) {
  1232. $sum += $_;
  1233. last if ($loop);
  1234. }
  1235. elsif ( defined( $powerMap->{$_} ) && !$loop ) {
  1236. Log3 $dev, 5, "$TYPE $dev: $_: Adding to total";
  1237. my $ret = powerMap_power( $name, $dev,
  1238. "$_: " . ReadingsVal( $dev, $_, "" ), 1 );
  1239. $sum += $ret if ( looks_like_number($ret) );
  1240. }
  1241. }
  1242. $power = $sum;
  1243. }
  1244. }
  1245. return "?" unless ( looks_like_number($power) );
  1246. return $power;
  1247. }
  1248. sub powerMap_energy($$;$) {
  1249. my ( $name, $dev, $P1 ) = @_;
  1250. my $hash = $defs{$name};
  1251. my $dev_hash = $defs{$dev};
  1252. my $TYPE = $hash->{TYPE};
  1253. my $rname_e = powerMap_AttrVal( $name, $dev, "rname_E", "pM_energy" );
  1254. my $rname_p = powerMap_AttrVal( $name, $dev, "rname_P", "pM_consumption" );
  1255. Log3 $name, 5, "$TYPE: Entering powerMap_energy() for $dev";
  1256. my $E0 = ReadingsVal( $dev, $rname_e, 0 );
  1257. my $P0 = ReadingsVal( $dev, $rname_p, 0 );
  1258. $P0 = 0 unless ( looks_like_number($P0) );
  1259. $P1 = $P0 unless ( defined($P1) );
  1260. $P1 = 0 unless ( looks_like_number($P1) );
  1261. my $Dt = ReadingsAge( $dev, $rname_e, 0 ) / 3600;
  1262. my $DE = $P0 * $Dt;
  1263. my $E1 = $E0 + $DE;
  1264. Log3( $dev, 4,
  1265. "$TYPE $dev: energy calculation results:\n"
  1266. . " energyOld : $E0 Wh\n"
  1267. . " powerOld : $P0 W\n"
  1268. . " power : $P1 W\n"
  1269. . " timeframe : $Dt h\n"
  1270. . " energyDiff: $DE Wh\n"
  1271. . " energy : $E1 Wh" );
  1272. return ( $E1, $P1 );
  1273. }
  1274. sub powerMap_update($;$) {
  1275. my ( $name, $dev ) = split( "\\|", shift );
  1276. my ($power) = @_;
  1277. my $hash = $defs{$name};
  1278. my $dev_hash = $defs{$dev};
  1279. my $TYPE = $hash->{TYPE};
  1280. RemoveInternalTimer("$name|$dev");
  1281. delete $dev_hash->{pM_update}
  1282. if ( defined( $dev_hash->{pM_update} ) );
  1283. delete $dev_hash->{pM_interval}
  1284. if ( defined( $dev_hash->{pM_interval} ) );
  1285. return
  1286. unless ( !IsDisabled($name) and defined($hash) and defined($dev_hash) );
  1287. Log3 $name, 5, "$TYPE: Entering powerMap_update() for $dev";
  1288. my $rname_e = powerMap_AttrVal( $name, $dev, "rname_E", "pM_energy" );
  1289. my $rname_p = powerMap_AttrVal( $name, $dev, "rname_P", "pM_consumption" );
  1290. readingsBeginUpdate($dev_hash);
  1291. unless ( powerMap_AttrVal( $name, $dev, "noEnergy", 0 ) ) {
  1292. my ( $energy, $P1 ) = powerMap_energy( $name, $dev, $power );
  1293. readingsBulkUpdate( $dev_hash, $rname_e . "_begin", time() )
  1294. unless ( ReadingsVal( $dev, $rname_e, undef ) );
  1295. readingsBulkUpdate( $dev_hash, $rname_e, $energy );
  1296. if ($P1) {
  1297. $dev_hash->{pM_interval} =
  1298. powerMap_AttrVal( $name, $dev, $TYPE . "_interval",
  1299. $hash->{INTERVAL} );
  1300. $dev_hash->{pM_interval} = 900
  1301. unless ( looks_like_number( $dev_hash->{pM_interval} ) );
  1302. $dev_hash->{pM_interval} = 30
  1303. if ( $dev_hash->{pM_interval} < 30 );
  1304. my $next = gettimeofday() + $dev_hash->{pM_interval};
  1305. $dev_hash->{pM_update} = FmtDateTime($next);
  1306. Log3 $dev, 5,
  1307. "$TYPE $dev: next update in "
  1308. . $dev_hash->{pM_interval}
  1309. . " s at "
  1310. . $dev_hash->{pM_update};
  1311. InternalTimer( $next, "powerMap_update", "$name|$dev" );
  1312. }
  1313. else {
  1314. Log3 $dev, 5, "$TYPE $dev: no power consumption, update paused";
  1315. }
  1316. }
  1317. readingsBulkUpdate( $dev_hash, $rname_p, $power )
  1318. if ( defined($power) );
  1319. readingsEndUpdate( $dev_hash, 1 );
  1320. return 1;
  1321. }
  1322. 1;
  1323. # commandref ###################################################################
  1324. =pod
  1325. =item helper
  1326. =item summary maps power and calculates energy (as Readings)
  1327. =item summary_DE leitet Leistung ab und berechnet Energie (als Readings)
  1328. =begin html
  1329. <a name="powerMap"></a>
  1330. <h3>powerMap</h3>
  1331. (en | <a href="commandref_DE.html#powerMap">de</a>)
  1332. <div>
  1333. <ul>
  1334. powerMap will help to determine current power consumption and calculates
  1335. energy consumption either when power changes or within regular interval.<br>
  1336. These new values may be used to collect energy consumption for devices w/o
  1337. power meter (e.g. fridge, lighting or FHEM server) and for further processing
  1338. using module <a href="#ElectricityCalculator">ElectricityCalculator</a>.
  1339. <br>
  1340. <a name="powerMapdefine"></a>
  1341. <b>Define</b>
  1342. <ul>
  1343. <code>define &lt;name&gt; powerMap</code><br>
  1344. You may only define one single instance of powerMap.
  1345. </ul><br>
  1346. <a name="powerMapset"></a>
  1347. <b>Set</b>
  1348. <ul>
  1349. <li>
  1350. <code>assign <a href="#devspec">&lt;devspec&gt;</a></code><br>
  1351. Adds pre-defined powerMap attributes to one or more devices
  1352. for further customization.
  1353. </li>
  1354. </ul><br>
  1355. <a name="powerMapget"></a>
  1356. <b>Get</b>
  1357. <ul>
  1358. <li>
  1359. <code>devices</code><br>
  1360. Lists all devices having set an attribute named 'powerMap'.
  1361. </li>
  1362. </ul><br>
  1363. <a name="powerMapreadings"></a>
  1364. <b>Readings</b><br>
  1365. <ul>
  1366. Device specific readings:
  1367. <ul>
  1368. <li>
  1369. <code>pM_energy</code><br>
  1370. A counter for consumed energy in Wh.<br>
  1371. Hint: In order to have the calculation working, attribute
  1372. <code>timestamp-on-change-reading</code> may not be set for
  1373. reading pM_energy!
  1374. </li><br>
  1375. <li>
  1376. <code>pM_energy_begin</code><br>
  1377. Unix timestamp when collection started and device started to consume
  1378. energy for the very first time.
  1379. </li><br>
  1380. <li>
  1381. <code>pM_consumption</code><br>
  1382. Current power consumption of device in W.
  1383. </li>
  1384. </ul><br>
  1385. </ul>
  1386. <a name="powerMapattr"></a>
  1387. <b>Attribute</b>
  1388. <ul>
  1389. <li>
  1390. <code>disable 1</code><br>
  1391. No readings will be created or calculated by this module.
  1392. </li><br>
  1393. <li>
  1394. <code>powerMap_eventChainWarnOnly &lt;1&gt;</code><br>
  1395. When set, event chain will NOT be repaired automatically if readings
  1396. were found to be required for powerMap but their events are currently
  1397. suppressed because they are either missing from attributes event-on-change-reading
  1398. or event-on-update-reading. Instead, manual intervention is required.
  1399. </li><br>
  1400. <li>
  1401. <code>powerMap_interval &lt;seconds&gt;</code><br>
  1402. Interval in seconds to calculate energy.<br>
  1403. Default value is 900 seconds.
  1404. </li><br>
  1405. <li>
  1406. <code>powerMap_noEnergy 1</code><br>
  1407. No energy consumption will be calculated for that device.
  1408. </li><br>
  1409. <li>
  1410. <code>powerMap_noPower 1</code><br>
  1411. No power consumption will be determined for that device and
  1412. consequently no energy consumption at all.
  1413. </li><br>
  1414. <li>
  1415. <code>powerMap_rname_E</code><br>
  1416. Sets reading name for energy consumption.<br>
  1417. Default value is 'pM_energy'.
  1418. </li><br>
  1419. <li>
  1420. <code>powerMap_rname_P</code><br>
  1421. Sets reading name for power consumption.<br>
  1422. Default value is 'pM_consumption'.
  1423. </li><br>
  1424. <li>
  1425. <code>powerMap<pre>
  1426. {
  1427. '&lt;reading&gt;' =&gt; {
  1428. '&lt;value&gt;' =&gt; &lt;power&gt;,
  1429. '&lt;value&gt;' =&gt; &lt;power&gt;,
  1430. ...
  1431. },
  1432. '&lt;reading&gt;' {
  1433. '&lt;value&gt;' =&gt; &lt;power&gt;,
  1434. '&lt;value&gt;' =&gt; &lt;power&gt;,
  1435. ...
  1436. },
  1437. ...
  1438. }</pre>
  1439. </code> (device specific)<br>
  1440. A Hash containing event(=reading) names and possible values of it. Each value can be assigned a
  1441. corresponding power consumption.<br>
  1442. For devices with dimming capability intemediate values will be linearly interpolated. For this
  1443. to work two separate numbers will be sufficient.<br>
  1444. <br>
  1445. Text values will automatically get any numbers extracted from it and be used for interpolation.
  1446. (example: dim50% will automatically be interpreted as 50).<br>
  1447. In addition "off" and "on" will be translated to 0 and 100 respectively.<br>
  1448. If the value cannot be interpreted in any way, 0 power consumption will be assumed.<br>
  1449. Explicitly set definitions in powerMap attribute always get precedence.<br>
  1450. <br>
  1451. In case several power values need to be summarized, the name of other readings may be added after
  1452. number value, separated by comma. The current status of that reading will then be considered for
  1453. total power calculcation. To consider all readings powerMap knows, just add an *.<br>
  1454. <br>
  1455. Example for FS20 socket:
  1456. <ul>
  1457. <code><pre>
  1458. 'state' =&gt; {
  1459. '0' =&gt; 0,
  1460. '100' =&gt; 60,
  1461. },
  1462. </pre></code><br>
  1463. </ul><br>
  1464. Example for HUE white light bulb:
  1465. <ul>
  1466. <code><pre>
  1467. 'pct' =&gt; {
  1468. '0' =&gt; 0.4,
  1469. '10' =&gt; 1.2,
  1470. '20' =&gt; 1.7,
  1471. '30' =&gt; 1.9,
  1472. '40' =&gt; 2.3,
  1473. '50' =&gt; 2.7,
  1474. '60' =&gt; 3.4,
  1475. '70' =&gt; 4.7,
  1476. '80' =&gt; 5.9,
  1477. '90' =&gt; 7.5,
  1478. '100' =&gt; 9.2,
  1479. },
  1480. 'state' =&gt; {
  1481. 'unreachable' =&gt; 0,
  1482. '*' =&gt; 'pct',
  1483. },
  1484. </pre></code><br>
  1485. </ul>
  1486. </li>
  1487. </ul>
  1488. </ul>
  1489. </div>
  1490. =end html
  1491. =begin html_DE
  1492. <a name="powerMap"></a>
  1493. <h3>powerMap</h3>
  1494. (<a href="commandref.html#powerMap">en</a> | de)
  1495. <div>
  1496. <ul>
  1497. powerMap ermittelt die aktuelle Leistungsaufnahme eines Ger&auml;ts und
  1498. berechnet den Energieverbrauch bei &Auml;nderung oder in einem
  1499. regelm&auml;&szlig;igen Intervall.<br>
  1500. Diese neuen Werte k&ouml;nnen genutzt werden, um den Stromverbrauch f&uuml;r
  1501. Ger&auml;te ohne Z&auml;hler (z.B. K&uuml;hlschrank, Beleuchtung oder
  1502. FHEM-Server) zu erfassen und mit dem Modul ElectricityCalculator weiter
  1503. zu verarbeiten.<br>
  1504. <br>
  1505. <a name="powerMapdefine"></a>
  1506. <b>Define</b>
  1507. <ul>
  1508. <code>define &lt;name&gt; powerMap</code><br>
  1509. Es kann immer nur eine powerMap Instanz definiert sein.
  1510. </ul><br>
  1511. <a name="powerMapset"></a>
  1512. <b>Set</b>
  1513. <ul>
  1514. <li>
  1515. <code>assign <a href="#devspec">&lt;devspec&gt;</a></code><br>
  1516. Weist einem oder mehreren Ger&auml;ten vordefinierte powerMap Attribute zu,
  1517. um diese anschlie&szlig;end anpassen zu k&ouml;nnen.
  1518. </li>
  1519. </ul><br>
  1520. <a name="powerMapget"></a>
  1521. <b>Get</b>
  1522. <ul>
  1523. <li>
  1524. <code>devices</code><br>
  1525. Listet alle Ger&auml;te auf, die das Attribut 'powerMap' gesetzt haben.
  1526. </li>
  1527. </ul><br>
  1528. <a name="powerMapreadings"></a>
  1529. <b>Readings</b><br>
  1530. <ul>
  1531. Ger&auml;tespezifische Readings:
  1532. <ul>
  1533. <li>
  1534. <code>pM_energy</code><br>
  1535. Ein Z&auml;hler f&uuml;r die bisher bezogene Energie in Wh.<br>
  1536. Hinweis: F&uuml;r eine korrekte Berechnung darf das Attribut
  1537. <code>timestamp-on-change-reading</code> nicht für das Reading
  1538. pM_energy gesetzt sein!
  1539. </li><br>
  1540. <li>
  1541. <code>pM_energy_begin</code><br>
  1542. Unix Timestamp, an dem die Aufzeichnung begonnen wurde und das
  1543. Ger&auml;t erstmalig Energie verbraucht hat.
  1544. </li><br>
  1545. <li>
  1546. <code>pM_consumption</code><br>
  1547. Die aktuelle Leistungsaufnahme des Ger&auml;tes in W.
  1548. </li>
  1549. </ul><br>
  1550. </ul>
  1551. <a name="powerMapattr"></a>
  1552. <b>Attribute</b>
  1553. <ul>
  1554. <li>
  1555. <code>disable 1</code><br>
  1556. Es werden keine Readings mehr durch das Modul erzeugt oder berechnet.
  1557. </li><br>
  1558. <li>
  1559. <code>powerMap_eventChainWarnOnly &lt;1&gt;</code><br>
  1560. Sofern gesetzt, wird die Ereigniskette NICHT automatisch repariert, falls
  1561. Readings zwar als f&uuml;r powerMap notwendig identifiziert wurden, ihre
  1562. Events jedoch derzeit dadurch unterdr&uuml;ckt werden, weil sie nicht in
  1563. einem der Attribute event-on-change-reading oder event-on-update-reading
  1564. enthalten sind. Stattdessen ist ein manueller Eingriff erforderlich.
  1565. </li><br>
  1566. <li>
  1567. <code>powerMap_interval &lt;seconds&gt;</code><br>
  1568. Intervall in Sekunden, in dem neue Werte f&uuml;r die Energie berechnet
  1569. werden.<br>
  1570. Der Vorgabewert ist 900 Sekunden.
  1571. </li><br>
  1572. <li>
  1573. <code>powerMap_noEnergy 1</code><br>
  1574. F&uuml;r das Ger&auml;t wird kein Energieverbrauch berechnet.
  1575. </li><br>
  1576. <li>
  1577. <code>powerMap_noPower 1</code><br>
  1578. F&uuml;r das Ger&auml;t wird keine Leistungsaufnahme abgeleitet und
  1579. daher auch kein Energieverbrauch berechnet.
  1580. </li><br>
  1581. <li>
  1582. <code>powerMap_rname_E</code><br>
  1583. Definiert den Reading Namen, in dem der Z&auml;hler f&uuml;r die bisher
  1584. bezogene Energie gespeichert wird.<br>
  1585. Der Vorgabewert ist 'pM_energy'.
  1586. </li><br>
  1587. <li>
  1588. <code>powerMap_rname_P</code><br>
  1589. Definiert den Reading Namen, in dem die aktuelle Leistungsaufnahme
  1590. des Ger&auml;tes gespeichert wird.<br>
  1591. Der Vorgabewert ist 'pM_consumption'.
  1592. </li><br>
  1593. <li>
  1594. <code>powerMap<pre>
  1595. {
  1596. '&lt;reading&gt;' =&gt; {
  1597. '&lt;value&gt;' =&gt; &lt;power&gt;,
  1598. '&lt;value&gt;' =&gt; &lt;power&gt;,
  1599. ...
  1600. },
  1601. '&lt;reading&gt;' {
  1602. '&lt;value&gt;' =&gt; &lt;power&gt;,
  1603. '&lt;value&gt;' =&gt; &lt;power&gt;,
  1604. ...
  1605. },
  1606. ...
  1607. }</pre>
  1608. </code> (ger&auml;tespezifisch)<br>
  1609. Ein Hash mit den Event(=Reading) Namen und seinen möglichen Werten, um diesen
  1610. die dazugeh&ouml;rige Leistungsaufnahme zuzuordnen.<br>
  1611. Bei dimmbaren Ger&auml;ten wird f&uuml;r die Zwischenschritte der Wert
  1612. durch eine lineare Interpolation ermittelt, so dass mindestens zwei Zahlenwerte ausreichen.<br>
  1613. <br>
  1614. Aus Textwerten, die eine Zahl enthalten, wird automatisch die Zahl extrahiert und
  1615. f&uuml;r die Interpolation verwendet (Beispiel: dim50% wird automatisch als 50 interpretiert).<br>
  1616. Au&szlig;erdem werden "off" und "on" automatisch als 0 respektive 100 interpretiert.<br>
  1617. Nicht interpretierbare Werte f&uuml;hren dazu, dass eine Leistungsaufnahme von 0 angenommen wird.<br>
  1618. Explizit in powerMap enthaltene Definitionen haben immer vorrang.<br>
  1619. <br>
  1620. F&uuml;r den Fall, dass mehrere Verbrauchswerte addiert werden sollen, kann der Name von anderen
  1621. Readings direkt hinter dem eigentliche Wert mit einem Komma abgetrennt angegeben werden.
  1622. Der aktuelle Status dieses Readings wird dann bei der Berechnung des Gesamtverbrauchs ebenfalls
  1623. ber&uumL;cksichtigt. Sollen alle in powerMap bekannten Readings ber&uuml;cksichtigt werden, kann
  1624. auch einfach ein * angegeben werden.<br>
  1625. <br>
  1626. Beispiel f&uuml;r einen FS20 Stecker:
  1627. <ul>
  1628. <code><pre>
  1629. 'state' =&gt; {
  1630. '0' =&gt; 0,
  1631. '100' =&gt; 60,
  1632. },
  1633. </pre></code><br>
  1634. </ul><br>
  1635. Beispiel f&uuml;r eine HUE white Gl&uuml;hlampe:
  1636. <ul>
  1637. <code><pre>
  1638. 'pct' =&gt; {
  1639. '0' =&gt; 0.4,
  1640. '10' =&gt; 1.2,
  1641. '20' =&gt; 1.7,
  1642. '30' =&gt; 1.9,
  1643. '40' =&gt; 2.3,
  1644. '50' =&gt; 2.7,
  1645. '60' =&gt; 3.4,
  1646. '70' =&gt; 4.7,
  1647. '80' =&gt; 5.9,
  1648. '90' =&gt; 7.5,
  1649. '100' =&gt; 9.2,
  1650. },
  1651. 'state' =&gt; {
  1652. 'unreachable' =&gt; 0,
  1653. '*' =&gt; 'pct',
  1654. },
  1655. </pre></code><br>
  1656. </ul>
  1657. </li>
  1658. </ul>
  1659. </ul>
  1660. </div>
  1661. =end html_DE
  1662. =cut