15_EMX.pm 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915
  1. ########################################################################################
  2. #
  3. # 15_EMX.pm MUST be saved as 15_CUL_EM.pm !!!
  4. #
  5. # FHEM module to read the data from an EM1000 WZ/EM/GZ power sensor
  6. #
  7. # Prof. Dr. Peter A. Henning, 2011
  8. #
  9. # $Id: 15_EMX.pm 2.0 2013-02 - pahenning $
  10. #
  11. ########################################################################################
  12. #
  13. # define <emx> EMX <code> <rpunit>
  14. #
  15. # where
  16. # <name> may be replaced by any name string
  17. # <code> is a number 1 - 12 or the keyword "emulator".
  18. # <rpunit> is the scale factor = rotations per kWh or m^3 (not needed for emulator)
  19. #
  20. # get <name> midnight => todays starting value for counter and power meter
  21. # get <name> cnt_midnight => todays starting value for counter
  22. # get <name> pm_midnight => todays starting value for power meter
  23. # get <name> month => summary of current month
  24. #
  25. # set <name> cnt_midnight => todays starting value for counter
  26. # set <name> pm_midnight => todays starting value for power meter
  27. # set <name> pm_current => current power meter reading
  28. #
  29. # Attributes are set as
  30. #
  31. # Monthly and yearly log file
  32. # attr emx LogM EnergyM
  33. # attr emx LogY EnergyY
  34. #
  35. # Basic fee per Month (€ per Month)
  36. # attr emx CostM
  37. #
  38. # Cost rate during daytime (€ per kWh)
  39. # attr emx CostD <cost rate in €/unit>
  40. #
  41. # Start and end of daytime cost rate - optional
  42. # attr emx CDStart <time as hh:mm>
  43. # attr emx CDEnd <time as hh:mm>
  44. #
  45. # Cost rate during nighttime (cost per unit) - only if needed
  46. # attr emx CostN <cost rate in €/unit>
  47. #
  48. ########################################################################################
  49. #
  50. # This programm is free software; you can redistribute it and/or modify
  51. # it under the terms of the GNU General Public License as published by
  52. # the Free Software Foundation; either version 2 of the License, or
  53. # (at your option) any later version.
  54. #
  55. # The GNU General Public License can be found at
  56. # http://www.gnu.org/copyleft/gpl.html.
  57. # A copy is found in the textfile GPL.txt and important notices to the license
  58. # from the author is found in LICENSE.txt distributed with these scripts.
  59. #
  60. # This script is distributed in the hope that it will be useful,
  61. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  62. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  63. # GNU General Public License for more details.
  64. #
  65. ########################################################################################
  66. package main;
  67. use strict;
  68. use warnings;
  69. my %gets = (
  70. "midnight" => "",
  71. "cnt_midnight" => "",
  72. "pm_midnight" => "",
  73. "month" => ""
  74. );
  75. my %sets = (
  76. "cnt_midnight" => "C",
  77. "pm_midnight" => "P",
  78. "pm_current" => "M",
  79. );
  80. #-- Global variables for the raw readings
  81. my $emx_seqno; # number of received datagram in sequence, runs from 2 to 255
  82. my $emx_cnt; # current count from device. This value has an arbitrary offset at each start of the device
  83. my $emx_5min; # count during last 5 min interval
  84. my $emx_peak; # peak count during last 5 min interval
  85. #--Forward definition
  86. sub EMX_Parse($$);
  87. ########################################################################################
  88. #
  89. # EMX_Initialize
  90. #
  91. ########################################################################################
  92. #-- stub function for CUL_EM replacement
  93. sub CUL_EM_Initialize ($) {
  94. my ($hash) = @_;
  95. return EMX_Initialize ($hash);
  96. }
  97. #-- real initialization function
  98. sub EMX_Initialize ($) {
  99. my ($hash) = @_;
  100. $hash->{DefFn} = "EMX_Define";
  101. $hash->{UndefFn} = "EMX_Undef";
  102. $hash->{ParseFn} = "EMX_Parse";
  103. $hash->{SetFn} = "EMX_Set";
  104. $hash->{GetFn} = "EMX_Get";
  105. $hash->{Match} = "^E0.................\$";
  106. $hash->{AttrList} = "IODev " .
  107. "model:EMEM,EMWZ,EMGZ loglevel LogM LogY CostD CDStart CDEnd CostN CostM ".
  108. $readingFnAttributes;
  109. }
  110. ########################################################################################
  111. #
  112. # EMX_Define - Implements DefFn function
  113. #
  114. # Parameter hash, definition string
  115. #
  116. ########################################################################################
  117. sub EMX_Define ($$) {
  118. my ($hash, $def) = @_;
  119. my @a = split("[ \t][ \t]*", $def);
  120. return "wrong syntax: define <name> EMX <code> <rpunit>"
  121. if(int(@a) < 3 || int(@a) > 4);
  122. my $name = $a[0];
  123. #-- emulator mode ------------------------------------------------------------
  124. if( $a[2] eq "emulator") {
  125. $hash->{CODE} = "emulator";
  126. Log 1, "EMX with emulator mode";
  127. #-- counts per unit etc.
  128. $hash->{READINGS}{"energy"}{FACTOR} = 150;
  129. $hash->{READINGS}{"energy"}{UNIT} = "Kilowattstunden";
  130. $hash->{READINGS}{"energy"}{UNITABBR}= "kWh";
  131. $hash->{READINGS}{"power"}{PERIOD} = "h";
  132. $hash->{READINGS}{"power"}{UNIT} = "Kilowatt";
  133. $hash->{READINGS}{"power"}{UNITABBR} = "kW";
  134. CommandAttr(undef,"$name model emulator");
  135. #-- set/ get artificial data
  136. my $msg=EMX_emu(0,12345);
  137. $hash->{READINGS}{"count"}{midnight} = 12345;
  138. $hash->{READINGS}{"pmeter"}{midnight} = 0;
  139. EMX_store($hash);
  140. $hash->{emumsg}=$msg;
  141. $modules{EMX}{defptr}{0} = $hash;
  142. # Call emulator in 15 seconds again, and then cyclic repetition
  143. InternalTimer(gettimeofday()+15, "EMX_Parse", $hash, 0);
  144. } else {
  145. #-- Real device definition -----------------------------------------------------
  146. return "EMX_Define $a[0]: wrong CODE format: valid is 1-12 or \"emulator\""
  147. if( $a[2] !~ m/^\d+$/ || $a[2] < 1 || $a[2] > 12 );
  148. $hash->{CODE} = $a[2];
  149. #--counts per unit etc.
  150. if($a[2] >= 1 && $a[2] <= 4) { # EMWZ
  151. $hash->{READINGS}{"energy"}{FACTOR} = $a[3];
  152. $hash->{READINGS}{"energy"}{UNIT} = "Kilowattstunden";
  153. $hash->{READINGS}{"energy"}{UNITABBR} = "kWh";
  154. $hash->{READINGS}{"power"}{PERIOD} = "h";
  155. $hash->{READINGS}{"power"}{UNIT} = "Kilowatt";
  156. $hash->{READINGS}{"power"}{UNITABBR} = "kW";
  157. CommandAttr (undef,"$name model EMWZ");
  158. } elsif($a[2] >= 5 && $a[2] <= 8) { # EMEM
  159. $hash->{READINGS}{"energy"}{FACTOR} = $a[3];
  160. $hash->{READINGS}{"energy"}{UNIT} = "Kilowattstunden";
  161. $hash->{READINGS}{"energy"}{UNITABBR} = "kWh";
  162. $hash->{READINGS}{"power"}{PERIOD} = "h";
  163. $hash->{READINGS}{"power"}{UNIT} = "Kilowatt";
  164. $hash->{READINGS}{"power"}{UNITABBR} = "kW";
  165. CommandAttr (undef,"$name model EMEM");
  166. } elsif($a[2] >= 9 && $a[2] <= 12) { # EMGZ
  167. $hash->{READINGS}{"energy"}{FACTOR} = $a[3];
  168. $hash->{READINGS}{"energy"}{UNIT} = "Kubikmeter";
  169. $hash->{READINGS}{"energy"}{UNITABBR} = "m^3";
  170. $hash->{READINGS}{"power"}{PERIOD} = "h";
  171. $hash->{READINGS}{"power"}{UNIT} = "Kubikmeter/Stunde";
  172. $hash->{READINGS}{"power"}{UNITABBR} = "m^3/h";
  173. CommandAttr (undef,"$name model EMGZ");
  174. }
  175. #-- Couple to I/O device
  176. $modules{EMX}{defptr}{$a[2]} = $hash;
  177. AssignIoPort($hash);
  178. }
  179. readingsSingleUpdate($hash,"state","defined",1);
  180. Log 3, "EMX: Device $name defined.";
  181. #-- Start timer for initialization in a few seconds
  182. InternalTimer(time()+3, "EMX_InitializeDevice", $hash, 0);
  183. return undef;
  184. }
  185. ########################################################################################
  186. #
  187. # EMX_InitializeDevice - Sets up the device after start
  188. #
  189. # Parameter hash
  190. #
  191. ########################################################################################
  192. sub EMX_InitializeDevice ($) {
  193. my ($hash) = @_;
  194. my $ret;
  195. my $name = $hash->{NAME};
  196. Log 1,"EMX_InitializeDevice $name";
  197. #-- read starting value of the day
  198. $ret = EMX_recall($hash);
  199. Log 1, $ret
  200. if( defined($ret));
  201. return $ret
  202. if( defined($ret));
  203. return undef;
  204. }
  205. ########################################################################################
  206. #
  207. # EMX_FormatValues - Calculate display values
  208. #
  209. # Parameter hash
  210. #
  211. ########################################################################################
  212. sub EMX_FormatValues ($) {
  213. my ($hash) = @_;
  214. #Log 1," seqno $emx_seqno cnt $emx_cnt 5min $emx_5min peak $emx_peak";
  215. my $name = $hash->{NAME};
  216. my ($model,$factor,$period,$unit,$runit,$midnight,$cval,$vval,$tval,$rval,$pval,$dval,$deltim,$delcnt,$msg);
  217. my ($svalue,$dvalue,$mvalue) = ("","","");
  218. my $cost = 0;
  219. my ($sec, $min, $hour, $day, $month, $year, $wday,$yday,$isdst) = localtime(time);
  220. my ($seco,$mino,$houro,$dayo,$montho,$yearo,$dayrest);
  221. my $daybreak = 0;
  222. my $monthbreak = 0;
  223. #-- Check, whether we have a new "day"
  224. # emulator: less than 15 seconds from 5-minute period
  225. if( $hash->{CODE} eq "emulator"){
  226. $deltim = $min%5+$sec/60.0 - 4.75;
  227. if( $deltim>0 ){
  228. $yearo = $year+1900;
  229. $montho = $month;
  230. $dayo = $day."-".$hour."-".$min."-".$sec;
  231. $daybreak = 1;
  232. #-- Check, whether we have a new "month" = three 5 minute periods
  233. if( $min%15 == 0){
  234. $monthbreak = 1;
  235. }
  236. }
  237. # normal mode: less than 5 minutes from midnight
  238. }else {
  239. $deltim = $hour*60.0+$min+$sec/60.0 - 1435.0;
  240. if( $deltim>=0 ){
  241. $daybreak = 1;
  242. #-- Timer data from tomorrow
  243. my ($secn,$minn,$hourn,$dayn,$monthn,$yearn,$wdayn,$ydayn,$isdstn) = localtime(time() + 3600);
  244. #-- Check, whether we have a new month
  245. if( $dayn == 1 ){
  246. $monthbreak = 1;
  247. }
  248. }
  249. }
  250. $model = $main::attr{$name}{"model"};
  251. $midnight = $hash->{READINGS}{"count"}{midnight};
  252. $factor = $hash->{READINGS}{"energy"}{FACTOR};
  253. $unit = $hash->{READINGS}{"energy"}{UNITABBR};
  254. $period = $hash->{READINGS}{"power"}{PERIOD};
  255. $runit = $hash->{READINGS}{"power"}{UNITABBR};
  256. my $emx_cnt_prev;
  257. my $emx_cnt_tim;
  258. #-- skip some things if undefined
  259. if( $emx_cnt eq ""){
  260. $svalue = "???";
  261. }else {
  262. #-- put into READINGS
  263. readingsBeginUpdate($hash);
  264. $svalue = "raw $emx_cnt";
  265. #-- get the old values (raw counts, always integer)
  266. $emx_cnt_prev = $hash->{READINGS}{"count"}{VAL};
  267. $emx_cnt_tim = $hash->{READINGS}{"count"}{TIME};
  268. $emx_cnt_tim = "" if(!defined($emx_cnt_tim));
  269. #-- safeguard against the case where no previous measurement
  270. if( length($emx_cnt_tim) > 0 ){
  271. #-- correct counter wraparound since last reading
  272. if( $emx_cnt < $emx_cnt_prev) {
  273. $emx_cnt_prev -= 65536;
  274. }
  275. #-- correct counter wraparound since last day
  276. if( $emx_cnt < $midnight) {
  277. $midnight -= 65536;
  278. }
  279. #-- For this calculation we could use either $emx_5min
  280. # or ($emx_cnt - $emx_cnt_prev) since they are the same.
  281. # But careful: we cannot be sure that measurement intervals are really
  282. # 5 minutes. Differ up to a second per interval !
  283. # Affects the rate by 0.3%, absolute count is not affected
  284. my $fivemin = 5.0;
  285. my $delcnt = ($emx_cnt-$emx_cnt_prev);
  286. #-- Extrapolate these values when a new day will be started (0<deltim<5)
  287. if( $daybreak==1 ) {
  288. $emx_cnt += $deltim/$fivemin *$delcnt;
  289. $cval = $emx_cnt-$midnight;
  290. #-- no daybreak -> subtract only midnight count
  291. }else{
  292. $cval = $emx_cnt-$midnight;
  293. }
  294. #-- Translate from device into physical units
  295. # $factor = no. of counts per unit
  296. # $emx_peak has to be divided by 20 = 60 min/ 5 min
  297. if( ($model eq "EMWZ") || ($model eq "emulator") ){
  298. $vval = int($cval/$factor*1000)/1000;
  299. $rval = int($emx_5min*12/$factor*1000)/1000;
  300. $pval = int($emx_peak/($factor*20)*1000)/1000;
  301. } elsif( $model eq "EMEM" ){
  302. $vval = int($cval/($factor*10)*1000)/1000;
  303. $rval = int($emx_5min/$factor*1000)/1000;
  304. $pval = int($emx_peak/($factor*20)*1000)/1000;
  305. } elsif( $model eq "EMGZ" ){
  306. $vval = int($cval/$factor*1000)/1000;
  307. $rval = int($emx_5min/$factor*1000)/1000;
  308. $pval = int($emx_peak/($factor*20)*1000)/1000;
  309. } else {
  310. Log 3,"EMX: Wrong device model $model";
  311. }
  312. #-- power meter value
  313. $tval = $vval + $hash->{READINGS}{"pmeter"}{midnight};
  314. #-- calculate cost
  315. if( defined($main::attr{$name}{"CostD"}) ){
  316. #-- single rate counter
  317. if( !defined($main::attr{$name}{"CostN"}) ){
  318. $cost = $vval*$main::attr{$name}{"CostD"};
  319. #-- dual rate counter
  320. }else{
  321. #--determine period 1 = still night, 2 = day, 3 = night again
  322. my @crs = split(':',$main::attr{$name}{"CDStart"});
  323. my @cre = split(':',$main::attr{$name}{"CDEnd"});
  324. my @tim = split(/[- :]/,$emx_cnt_tim);
  325. #-- if one of them fails, we switch to single rate mode
  326. if( (int(@crs) ne 2) || (int(@cre) ne 2) ){
  327. $cost = $vval*$main::attr{$name}{"CostD"};
  328. delete $main::attr{$name}{"CostN"};
  329. Log 3,"EMX: $name has improper cost rate time specification";
  330. } else {
  331. #-- period 1
  332. if ( (($hour-$crs[0])*60 + $min-$crs[1])<0 ){
  333. $cost = $vval*$main::attr{$name}{"CostN"};
  334. #-- period 2
  335. }elsif ( (($hour-$cre[0])*60 + $min-$cre[1])<0 ){
  336. my $delta = ($tim[3]-$crs[0])*60 + ($tim[4]-$crs[1]) + $tim[5]/60.0;
  337. my $oldval = $hash->{READINGS}{"energy"}{VAL};
  338. #-- previous measurement was in period 1
  339. if( $delta < 0 ){
  340. $cost = $hash->{READINGS}{"cost"}{VAL} +
  341. $main::attr{$name}{"CostN"}*($vval-$oldval)*(1+$delta/$fivemin)+
  342. $main::attr{$name}{"CostD"}*($vval-$oldval)*(-$delta/$fivemin);
  343. } else{
  344. $cost = $hash->{READINGS}{"cost"}{VAL} + $main::attr{$name}{"CostD"}*($vval-$oldval);
  345. }
  346. #-- period 3
  347. }else{
  348. my $delta = ($tim[3]-$cre[0])*60 + ($tim[4]-$cre[1]) +$tim[5]/60.0;
  349. my $oldval = $hash->{READINGS}{"energy"}{VAL};
  350. #-- previous measurement was in period 2
  351. if( $delta < 0 ){
  352. $cost = $hash->{READINGS}{"cost"}{VAL} +
  353. $main::attr{$name}{"CostD"}*($vval-$oldval)*(1+$delta/$fivemin)+
  354. $main::attr{$name}{"CostN"}*($vval-$oldval)*(-$delta/$fivemin);
  355. } else{
  356. $cost = $hash->{READINGS}{"cost"}{VAL} + $main::attr{$name}{"CostN"}*($vval-$oldval);
  357. }
  358. }
  359. }
  360. }
  361. $cost = floor($cost*10000+0.5)/10000;
  362. }
  363. #-- state format
  364. $svalue = sprintf("W: %5.2f %s P: %5.2f %s Pmax: %5.3f %s",$vval,$unit,$rval,$runit,$pval,$runit);
  365. #-- put into READINGS
  366. readingsBulkUpdate($hash,"count",$emx_cnt);
  367. readingsBulkUpdate($hash,"energy",$vval);
  368. readingsBulkUpdate($hash,"pmeter",$tval);
  369. readingsBulkUpdate($hash,"power",$rval);
  370. readingsBulkUpdate($hash,"peak",$pval);
  371. readingsBulkUpdate($hash,"cost",$cost);
  372. #-- daybreak postprocessing
  373. if( $daybreak == 1 ){
  374. #-- store corrected counter value at midnight
  375. $hash->{READINGS}{"count"}{midnight} = $emx_cnt;
  376. $hash->{READINGS}{"pmeter"}{midnight} = $tval;
  377. EMX_store($hash);
  378. #-- daily/monthly accumulated value
  379. my @monthv = EMX_GetMonth($hash);
  380. my $total = $monthv[0]+$vval;
  381. $dvalue = sprintf("D%02d Wd: %5.2f %s Wm: %6.2f %s Cd: %5.2f €",$day,$vval,$unit,$total,$unit,int($cost*100)/100);
  382. readingsBulkUpdate($hash,"day",$dvalue);
  383. if( $monthbreak == 1){
  384. $mvalue = sprintf("M%02d Wm: %6.2f %s",$month+1,$total,$unit);
  385. readingsBulkUpdate($hash,"month",$mvalue);
  386. Log 1,$name." has monthbreak $msg ".$mvalue;
  387. }
  388. }
  389. }
  390. #-- STATE
  391. readingsBulkUpdate($hash,"state",$svalue);
  392. readingsEndUpdate($hash,1);
  393. }
  394. }
  395. ########################################################################################
  396. #
  397. # EMX_Get - Implements GetFn function
  398. #
  399. # Parameter hash, argument array
  400. #
  401. ########################################################################################
  402. sub EMX_Get ($@) {
  403. my ($hash, @a) = @_;
  404. #-- empty argument list
  405. return join(" ", sort keys %gets)
  406. if(@a < 2);
  407. #-- check syntax
  408. my $name = $hash->{NAME};
  409. return "EMX_Get with unknown argument $a[1], choose one of " . join(" ", sort keys %gets)
  410. if(!defined($gets{$a[1]}));
  411. $name = shift @a;
  412. my $key = shift @a;
  413. my $value;
  414. my $ret;
  415. #-- both midnight values
  416. if($key eq "midnight"){
  417. return "EMX_Get => midhight counter ".$hash->{READINGS}{"count"}{midnight}." (pmeter ".$hash->{READINGS}{"pmeter"}{midnight}.")";
  418. }
  419. #-- midnight counter value
  420. if($key eq "cnt_midnight"){
  421. $value = $hash->{READINGS}{"count"}{midnight};
  422. }
  423. #-- midnight power meter value
  424. if($key eq "pm_midnight"){
  425. $value = $hash->{READINGS}{"pmeter"}{midnight};
  426. }
  427. #-- monthly summary
  428. if($key eq "month"){
  429. my @month = EMX_GetMonth($hash);
  430. $value = "Wm ".$month[1]." kWh (av. ".$month[2]." kWh)";
  431. }
  432. Log GetLogLevel($name,3), "EMX_Get => $key $value";
  433. return "EMX_Get => $key $value";
  434. }
  435. ########################################################################################
  436. #
  437. # EMX_Set - Implements SetFn function
  438. #
  439. # Parameter hash, argument array
  440. #
  441. ########################################################################################
  442. sub EMX_Set ($@) {
  443. my ($hash, @a) = @_;
  444. #-- empty argument list
  445. return join(" ", sort keys %sets)
  446. if(@a < 3);
  447. #-- check syntax
  448. return "EMX_Set needs at least two parameters"
  449. if(@a < 3);
  450. my $name = $hash->{NAME};
  451. Log GetLogLevel($name,3), "EMX Set request $a[1] $a[2]";
  452. return "EMX_Set with unknown argument $a[1], choose one of " . join(" ", sort keys %sets)
  453. if(!defined($sets{$a[1]}));
  454. $name = shift @a;
  455. my $key = shift @a;
  456. my $value = join("", @a);
  457. my $tn = TimeNow();
  458. my $ret;
  459. #-- value of midnight power meter reading may be set at runtime
  460. if($key eq "pm_midnight"){
  461. return "EMX_Set: Wrong midnight value for power meter, must be 0 <= value < 99999"
  462. if( ($value < 0) || ($value > 99999) );
  463. $hash->{READINGS}{"pmeter"}{midnight}=$value;
  464. #-- store this for later usage
  465. $ret = EMX_store($hash);
  466. return "EMX_Set: ".$ret
  467. if( defined($ret) );
  468. }
  469. #-- value of current power meter reading may be set at runtime
  470. if($key eq "pm_current"){
  471. return "EMX_Set: Wrong current value for power meter, must be 0 <= value < 99999"
  472. if( ($value < 0) || ($value > 99999) );
  473. $hash->{READINGS}{"pmeter"}{midnight}=$value-$hash->{READINGS}{"energy"}{VAL};
  474. #-- store this for later usage
  475. $ret = EMX_store($hash);
  476. return "EMX_Set: ".$ret
  477. if( defined($ret) );
  478. }
  479. #-- midnight counter value may be set at runtime
  480. if( ($key eq "midnight") || ($key eq "cnt_midnight") ){
  481. return "EMX_Set: Wrong midnight value for counter, must be -65536 <= value < 65536"
  482. if( ($value < -65536) || ($value > 65535) );
  483. $hash->{READINGS}{"count"}{midnight}=$value;
  484. #-- store this for later usage
  485. $ret = EMX_store($hash);
  486. return "EMX_Set: ".$ret
  487. if( defined($ret) );
  488. }
  489. Log GetLogLevel($name,3), "EMX_Set => $key $value";
  490. return "EMX_Set => $key $value";
  491. }
  492. ########################################################################################
  493. #
  494. # EMX_Undef - Implements UndefFn function
  495. #
  496. # Parameter hash, name
  497. #
  498. ########################################################################################
  499. sub EMX_Undef ($$) {
  500. my ($hash, $name) = @_;
  501. delete($modules{EMX}{defptr}{$hash->{CODE}});
  502. return undef;
  503. }
  504. ########################################################################################
  505. #
  506. # EMX_Parse - Parse the message string send by CUL_EM
  507. #
  508. # Parameter hash, msg = message string
  509. #
  510. ########################################################################################
  511. sub EMX_Parse ($$) {
  512. my ($hash,$msg) = @_;
  513. if( !($msg) ) {
  514. $msg=$hash->{emumsg};
  515. }
  516. # 0123456789012345678
  517. # E01012471B80100B80B -> Type 01, Code 01, Cnt 10
  518. my @a = split("", $msg);
  519. my $tpe = ($a[1].$a[2])+0;
  520. my $cde = hex($a[3].$a[4]);
  521. #-- emulator
  522. if( $cde eq "00"){
  523. $cde = "emulator";
  524. }
  525. #-- return, if the defice is undefided
  526. if( not($modules{EMX}{defptr}{$cde}) ){
  527. Log 1, "EMX detected, Code $cde";
  528. return "EMX_Parse: Undefined EMX_$cde EMX $cde";
  529. }
  530. my $def = $modules{EMX}{defptr}{$cde};
  531. $hash = $def;
  532. my $name = $hash->{NAME};
  533. return "" if(IsIgnored($name));
  534. $emx_seqno = hex($a[5].$a[6]);
  535. $emx_cnt = hex($a[ 9].$a[10].$a[ 7].$a[ 8]);
  536. $emx_5min = hex($a[13].$a[14].$a[11].$a[12]);
  537. $emx_peak = hex($a[17].$a[18].$a[15].$a[16]);
  538. EMX_FormatValues($hash);
  539. #-- emulator mode - must be triggered here since not received by CUL
  540. # Call us in 15 seconds minutes again.
  541. if( $hash->{CODE} eq "emulator"){
  542. # Next sequence number
  543. $emx_seqno++;
  544. $emx_seqno =0 if($emx_seqno > 255);
  545. # Get artificial data
  546. my $msg=EMX_emu($emx_seqno, $emx_cnt);
  547. $hash->{emumsg}=$msg;
  548. #-- restart timer for updates
  549. RemoveInternalTimer($hash);
  550. InternalTimer(gettimeofday()+15, "EMX_Parse", $hash,1);
  551. }
  552. return $hash->{NAME};
  553. }
  554. ########################################################################################
  555. #
  556. # Store daily start value in a file
  557. #
  558. # Parameter hash
  559. #
  560. ########################################################################################
  561. sub EMX_store($) {
  562. my ($hash) = @_;
  563. my $name = $hash->{NAME};
  564. my $mp = AttrVal("global", "modpath", ".");
  565. my $ret = open(EMXFILE, "> $mp/FHEM/EMX_$name.dat" );
  566. my $msg;
  567. if( $ret) {
  568. #-- Timer data
  569. my ($sec,$min,$hour,$day,$month,$year,$wday,$yday,$isdst) = localtime(time);
  570. if( $hash->{CODE} eq "emulator"){
  571. $msg = sprintf "%4d-%02d-%02d %02d:%02d:%02d %d %d",
  572. $year+1900,$month+1,$day,$hour,$min,$sec,
  573. $hash->{READINGS}{"count"}{midnight},
  574. $hash->{READINGS}{"pmeter"}{midnight};
  575. } else {
  576. $msg = sprintf "%4d-%02d-%02d midnight %7.2f %7.2f",
  577. $year+1900,$month+1,$day,
  578. $hash->{READINGS}{"count"}{midnight},
  579. $hash->{READINGS}{"pmeter"}{midnight};
  580. }
  581. print EMXFILE $msg;
  582. Log 1, "EMX_store: $name $msg";
  583. close(EMXFILE);
  584. } else {
  585. Log 1,"EMX_store: Cannot open EMX_$name.dat for writing!";
  586. }
  587. return undef;
  588. }
  589. ########################################################################################
  590. #
  591. # Recall daily start value from a file
  592. #
  593. # Parameter hash
  594. #
  595. ########################################################################################
  596. sub EMX_recall($) {
  597. my ($hash) = @_;
  598. my $name= $hash->{NAME};
  599. my $mp = AttrVal("global", "modpath", ".");
  600. my $ret = open(EMXFILE, "< $mp/FHEM/EMX_$name.dat" );
  601. my $msg;
  602. if( $ret ){
  603. my $line = readline EMXFILE;
  604. close(EMXFILE);
  605. my @a=split(' ',$line);
  606. #-- Timer data from yesterday
  607. my ($sec,$min,$hour,$day,$month,$year,$wday,$yday,$isdst) = localtime(time() - 24*60*60);
  608. $msg = sprintf "%4d-%02d-%02d", $year+1900,$month+1,$day;
  609. if( $msg ne $a[0]){
  610. Log 1, "EMX_recall: midnight value $a[2] for $name not from last day, but from $a[0]";
  611. $hash->{READINGS}{"count"}{midnight} = $a[2];
  612. $hash->{READINGS}{"pmeter"}{midnight} = defined($a[3]) ? $a[3] : 0;
  613. } else {
  614. Log 1, "EMX_recall: recalled midnight value $a[2] for $name";
  615. $hash->{READINGS}{"count"}{midnight} = $a[2];
  616. $hash->{READINGS}{"pmeter"}{midnight} = defined($a[3]) ? $a[3] : 0;
  617. }
  618. } else {
  619. Log 1, "EMX_recall: Cannot open EMX_$name.dat for reading!";
  620. $hash->{READINGS}{"count"}{midnight}=0;
  621. $hash->{READINGS}{"pmeter"}{midnight}=0;
  622. }
  623. return undef;
  624. }
  625. ########################################################################################
  626. #
  627. # Read monthly data from a file
  628. #
  629. # Parameter hash
  630. #
  631. # Returns total value up to last day, including this day and average including this day
  632. #
  633. ########################################################################################
  634. sub EMX_GetMonth($) {
  635. my ($hash) = @_;
  636. my $name = $hash->{NAME};
  637. my $regexp = ".*$name.*";
  638. my @month;
  639. #-- Check current logfile
  640. my $ln = $attr{$name}{"LogM"};
  641. if( !(defined($ln))){
  642. Log 1,"EMX_GetMonth: Attribute LogM is missing";
  643. return undef;
  644. } else {
  645. my $lf = $defs{$ln}{currentlogfile};
  646. my $ret = open(EMXFILE, "< $lf" );
  647. if( $ret) {
  648. while( <EMXFILE> ){
  649. #-- line looks like
  650. # 2013-02-09_23:59:31 <name> day D_09 Wd: 0.00 Wm: 171.70
  651. my $line = $_;
  652. chomp($line);
  653. if ( $line =~ m/$regexp/i){
  654. my @linarr = split(' ',$line);
  655. my $day = $linarr[3];
  656. $day =~ s/D_0+//;
  657. my $val = $linarr[5];
  658. push(@month,$val);
  659. }
  660. }
  661. }
  662. #-- sum and average
  663. my $total = 0.0;
  664. foreach(@month){
  665. $total +=$_;
  666. }
  667. #-- add data from current day
  668. $total = int($total*100)/100;
  669. my $total2 = int(100*($total+$hash->{READINGS}{"energy"}{VAL}))/100;
  670. #-- number of days so far, including the present day
  671. my ($sec,$min,$hour,$day,$month,$year,$wday,$yday,$isdst) = localtime(time);
  672. my $deltim = int(@month)+($hour+$min/60.0 + $sec/3600.0)/24.0;
  673. my $av = int(100*$total2/$deltim)/100;
  674. #-- output format
  675. return ($total,$total2,$av);
  676. }
  677. }
  678. ########################################################################################
  679. #
  680. # Emulator section - to be used, if the real device is not attached.
  681. #
  682. ########################################################################################
  683. sub EMX_emu ($$) {
  684. #-- Timer data
  685. my ($sec,$min,$hour,$day,$month,$year,$wday,$yday,$isdst) = localtime(time);
  686. #-- parse incoming parameters
  687. my ($seqno,$Wd_cnt_old)=@_;
  688. #-- setup message
  689. my $sj=sprintf("%02x",$seqno);
  690. # power value = 0.6 from 0:00 - 06:00 / 1.8 kW from 6:00 - 22:00/1.2 kW from 22:00 - 24:00
  691. my $Pac_cnt;
  692. my $Wd_cnt = $Wd_cnt_old%65536;
  693. if( ($hour+$min/60.0)<6.0 ){
  694. $Pac_cnt= 0.64*150.0/12.0;
  695. $Wd_cnt+= 0.64*150.0/12.0;
  696. } elsif ( ($hour+$min/60.0)<22.0 ) {
  697. $Pac_cnt= 1.92*150.0/12.0;
  698. $Wd_cnt+= 1.92*150.0/12.0;
  699. } else {
  700. $Pac_cnt= 1.28*150.0/12.0;
  701. $Wd_cnt+= 1.28*150.0/12.0;
  702. }
  703. my $cj=sprintf("%02x",int($Pac_cnt/256));
  704. my $ck=sprintf("%02x",$Pac_cnt%256);
  705. my $tj=sprintf("%02x",int($Wd_cnt/256));
  706. my $tk=sprintf("%02x",$Wd_cnt%256);
  707. my $msg="E0100".$sj.$tk.$tj.$ck.$cj."0000";
  708. #Log 1,"cj = $cj, ck=$ck, tj=$tj, tk=$tk";
  709. return $msg;
  710. }
  711. 1;
  712. =pod
  713. =begin html
  714. <a name="EMX"></a>
  715. <h3>EMX</h3>
  716. <p>FHEM module to commmunicate with the EM1000 WZ/EM/GZ power/gas sensors <br />
  717. <br /> <b>NOTE:</b> This module is currently NOT registered in the client list of 00_CUL.pm.
  718. Therefore ist must be saved under the name 15_CUL_EM.pm or entered into the client list manually.
  719. <br /></p>
  720. <br /><h4>Example</h4>
  721. <p>
  722. <code>define E_Verbrauch EMX 1 75</code>
  723. </p>
  724. <br />
  725. <a name="EMXdefine"></a>
  726. <h4>Define</h4>
  727. <p>
  728. <code>define &lt;name&gt; EMX &lt;code&gt; &lt;rpunit&gt;</code> or <br/>
  729. <code>define &lt;name&gt; EMX emulator</code>
  730. <br /><br /> Define an EMX device or an emulated EM1000-WZ device <br /><br />
  731. </p>
  732. <ul>
  733. <li>
  734. <code>&lt;code&gt;</code><br /> Defines the sensor model, currently the following values are permitted:
  735. <ul>
  736. <li>1 .. 4: EM1000-WZ power meter sensor => unit is kWh</li>
  737. <li>5 .. 8: EM1000-EM power sensor => unit is kWh</li>
  738. <li>9 .. 12: EM1000-GZ gas meter sensor => unit is m<sup>3</sup></li>
  739. </ul>
  740. </li>
  741. <li>
  742. <code>&lt;rpunit&gt;</code><br/>Factor to scale the reading into units
  743. <ul>
  744. <li>EM1000-WZ devices: rotations per kWh, usually 75 or 150</li>
  745. <li>EM1000-EM devices: digits per kWh, usually 100</li>
  746. <li>EM1000-GZ devices: digits per <sup>3</sup>, usually 100</li>
  747. </ul>
  748. </li>
  749. </ul>
  750. <a name="EMXset"></a>
  751. <h4>Set</h4>
  752. <ul>
  753. <li><a name="emx_cnt_midnight">
  754. <code>set &lt;name&gt; cnt_midnight &lt;int&gt;</code></a><br /> Midnight Value of internal counter </li>
  755. <li><a name="emx_pm_midnight">
  756. <code>set &lt;name&gt; pm_midnight &lt;int&gt;</code></a><br /> Midnight value of external power meter</li>
  757. <li><a name="emx_pmeter">
  758. <code>set &lt;name&gt; pm_current &lt;int&gt;</code></a><br /> Current value of external power meter</li>
  759. </ul>
  760. <br />
  761. <a name="EMXget"></a>
  762. <h4>Get</h4>
  763. <ul>
  764. <li><a name="emx_midnight">
  765. <code>get &lt;name&gt; midnight</code></a>
  766. <br /> Returns the midnight value of the counter and power meter </li>
  767. <li><a name="emx_cnt_midnight2">
  768. <code>get &lt;name&gt; cnt_midnight</code></a>
  769. <br /> Returns the midnight value of the internal counter</li>
  770. <li><a name="emx_pm_midnight2">
  771. <code>get &lt;name&gt; pm_midnight</code></a>
  772. <br /> Returns the midnight value of the power meter</li>
  773. <li><a name="emx_month">
  774. <code>get &lt;name&gt; month</code>
  775. </a>
  776. <br /> Returns a summary of the current month</li>
  777. </ul>
  778. <br />
  779. <a name="EMXattr"></a>
  780. <h4>Attributes</h4>
  781. <ul>
  782. <li><a name="emx_logm"><code>attr &lt;name&gt; &lt;LogM&gt;
  783. &lt;string&gt;</code></a>
  784. <br />Device name (<i>not file name</i>) of the monthly logfile </li>
  785. <li><a name="emx_logy"><code>attr &lt;name&gt; &lt;LogY&gt;
  786. &lt;string&gt;</code></a>
  787. <br />Device name (<i>not file name</i>) of the yearly logfile </li>
  788. <li><a name="emx_costm"><code>attr &lt;name&gt; &lt;CostM&gt;
  789. &lt;float&gt;</code></a>
  790. <br />Cost per month</li>
  791. <li><a name="emx_costd"><code>attr &lt;name&gt; &lt;CostD&gt;
  792. &lt;float&gt;</code></a>
  793. <br />Cost per unit (during daytime, when the following attributes are given)</li>
  794. <li><a name="emx_costn"><code>attr &lt;name&gt; &lt;CostN&gt;
  795. &lt;float&gt;</code></a>
  796. <br />Cost per unit during night time</li>
  797. <li><a name="emx_cdstart"><code>attr &lt;name&gt; &lt;CDStart&gt;
  798. &lt;hh:mm&gt;</code></a>
  799. <br />Time of day when daytime cost rate starts</li>
  800. <li><a name="emx_cdend"><code>attr &lt;name&gt; &lt;CDEnd&gt;
  801. &lt;hh:mm&gt;</code></a>
  802. <br />Time of day when daytime cost rate ends</li>
  803. <li>Standard attributes <a href="#alias">alias</a>, <a href="#comment">comment</a>, <a
  804. href="#event-on-update-reading">event-on-update-reading</a>, <a
  805. href="#event-on-change-reading">event-on-change-reading</a>, <a
  806. href="#stateFormat">stateFormat</a>, <a href="#room"
  807. >room</a>, <a href="#eventMap">eventMap</a>, <a href="#loglevel">loglevel</a>,
  808. <a href="#webCmd">webCmd</a></li>
  809. </ul>
  810. =end html
  811. =cut