-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathpower.lua
More file actions
873 lines (824 loc) · 38.8 KB
/
power.lua
File metadata and controls
873 lines (824 loc) · 38.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
-- scripts/lua/power.lua - Called by script_device_master.lua
-- Written by Creasol, https://creasol.it linux@creasol.it
-- Used to check power from energy meter (SDM120, SDM230, ...) and performs the following actions
-- 1. Send notification when consumed power is above a threshold (to avoid power outage)
-- 2. Enabe/Disable electric heaters or other appliances, to reduced power consumption from the electric grid
-- 3. Emergency lights: turn ON some LED devices in case of power outage, and turn off when power is restored
-- 4. Show on DomBusTH LEDs red and green the produced/consumed power: red LED flashes 1..N times if power consumption is greater than 1..N kW;
-- green LED flashes 1..M times if photovoltaic produces up to 1..M kWatt
--
-- At least a device with "Power" in its name has changed: let's go!
DEBUG_LEVEL=E_WARNING
--DEBUG_LEVEL=E_INFO
--DEBUG_LEVEL=E_DEBUG
dofile "scripts/lua/config_power.lua" -- configuration file
-- ENABLE THE FOLLOWING LINE, BUT ONLY THE FIRST TIME TO CREATE GridPower variable, then disable it to save execution time
--checkVar('GridPower',0,0) -- check that uservariable at1 exists, else create it with type 0 (integer) and value 0
timeNow=os.date("*t")
function PowerInit()
if (Power==nil) then Power={} end
if (Power['th1']==nil) then Power['th1']=0 end
if (Power['th2']==nil) then Power['th2']=0 end
if (Power['above']==nil) then Power['above']=0 end
if (Power['usage']==nil) then Power['usage']=0 end
if (Power['disc']==nil) then Power['disc']=0 end
if (Power['min']==nil) then Power['min']=0 end -- current time minute: used to check something only 1 time per minute
if (Power['ev']==nil) then Power['ev']=0 end -- used to force EV management now, without waiting 1 minute
if (Power['EV']==nil) then Power['EV']=0 end -- EV Charge power
if (Power['HL']==nil and HOYMILES_ID~='') then Power['HL']=HOYMILES_LIMIT_MAX end -- current limit value
if (Power['HS']==nil and HOYMILES_ID~='') then Power['HS']=0 end -- Inverter producing status (0=Off, 1=On)
if (Power['Ht']==nil and HOYMILES_ID~='') then Power['Ht']=0 end -- time, since epoch, when hoymiles inverter limit was set
--if (PowerAux==nil) then PowerAux={} end
end
function EVSEInit()
if (EVSE==nil) then EVSE={} end
if (EVSE['T']==nil) then EVSE['T']=0 end -- EVSE: absolute time used to compute when power can stay over Threshold1 and below Threshold2 (27% over the contractual power)
if (EVSE['t']==nil) then EVSE['t']=0 end -- EVSE: time the EVSE is over Threshold2, to determine if charging must be stopped
if (EVSE['S']==nil) then EVSE['S']='Dis' end -- EVSE: last state
end
function evseSetGreenPower(Er, Et) -- Er=green energy used to charge the vehicle in the last Et seconds
local Eo=getEnergyValue(otherdevices_svalues[EVSE_RENEWABLE]) -- old renewable energy value
local Pr=Er*3600/Et -- current renewable power
local Pc=getPowerValue(otherdevices[EVSE_POWERMETER])
local Prperc=0
--log(E_DEBUG,"EVSE: greenPower: Eo="..Eo.." Pr="..Pr.." Pc="..Pc)
if (Pc>0) then Prperc=math.floor(Pr*100/Pc) end
-- if (Prperc>100) then Prperc=100 end
table.insert(commandArray, {['UpdateDevice'] = otherdevices_idx[EVSE_RENEWABLE].."|0|"..Pr..';'..tostring(Eo+Er)}) -- Update EVSE_greenPower
table.insert(commandArray,{['UpdateDevice'] = otherdevices_idx[EVSE_RENEWABLE_PERCENTAGE].."|0|"..Prperc}) -- Update EVSE_green/total percentage
log(E_DEBUG,"EVSE: greenPower="..Pr.." "..Prperc.."%")
end
function setAvgPower() -- store in the user variable avgPower the building power usage
if (uservariables['avgPower']==nil) then
-- create a Domoticz variable, coded in json, within all variables used in this module
avgPower=currentPower
url=DOMOTICZ_URL..'/json.htm?type=command¶m=adduservariable&vname=avgPower&vtype=0&vvalue='..tostring(currentPower)
os.execute('curl -m 1 "'..url..'"')
-- initialize variable
else
avgPower=tonumber(uservariables['avgPower'])
end
log(E_INFO,"currentPower="..currentPower.." Usage="..Power['usage'].." EV="..Power['EV'])
avgPower=(math.floor((avgPower*11 + currentPower - Power['usage'] - Power['EV'])/12)) -- average on 12*5s=60s
commandArray['Variable:zPower']=json.encode(Power)
end
function getPower() -- extract the values coded in JSON format from domoticz zPower variable, into Power dictionary
if (Power==nil) then
-- check variable zPower
json=require("dkjson")
if (uservariables['zPower']==nil) then
-- create a Domoticz variable, coded in json, within all variables used in this module
PowerInit() -- initialize Power dictionary
url=DOMOTICZ_URL..'/json.htm?type=command¶m=adduservariable&vname=zPower&vtype=2&vvalue='
os.execute('curl -m 1 "'..url..'"')
-- initialize variable
else
Power=json.decode(uservariables['zPower'])
end
PowerInit()
end
if (PowerAux==nil) then
-- check variable zPower
json=require("dkjson")
if (uservariables['zPowerAux']==nil) then
-- create a Domoticz variable, coded in json, within all variables used in this module
log(E_INFO,"ERROR: creating variable zPowerAux")
PowerAux={} -- initialize PowerAux dictionary
url=DOMOTICZ_URL..'/json.htm?type=command¶m=adduservariable&vname=zPowerAux&vtype=2&vvalue='
os.execute('curl -m 1 "'..url..'"')
-- initialize variable
else
PowerAux=json.decode(uservariables['zPowerAux'])
if (otherdevices['Pranzo_Stufetta']=='On' and PowerAux['f1']==nil) then
log(E_INFO,"ERROR, Pranzo_Stufetta On manually")
end
end
PowerInit()
else
log(E_INFO,"PowerAux!=nil")
log(E_INFO,"PowerAux="..json.encode(PowerAux))
end
if (uservariables['zHeatPump']~=nil) then
HP=json.decode(uservariables['zHeatPump']) -- get HP[] with info from HeatPump
end
if (HP==nil or HP['Level']==nil) then
HP={}
HP['Level']=1
end
if (EVSE==nil) then
-- check variable zPower
json=require("dkjson")
if (uservariables['zEVSE']==nil) then
-- create a Domoticz variable, coded in json, within all variables used in this module
EVSEInit() -- initialize Power dictionary
url=DOMOTICZ_URL..'/json.htm?type=command¶m=adduservariable&vname=zEVSE&vtype=2&vvalue='
os.execute('curl -m 1 "'..url..'"')
-- initialize variable
else
EVSE=json.decode(uservariables['zEVSE'])
end
EVSEInit()
end
end
function powerMeterAlert(on)
for k,pma in pairs(PowerMeterAlerts) do
if (on~=0) then
if (otherdevices[ pma[1] ]~=pma[3]) then
log(E_INFO,"Activate sound alert "..pma[1])
commandArray[ pma[1] ]=pma[3]
end
else
-- OFF command
if (otherdevices[ pma[1] ]~=pma[2]) then
log(E_DEBUG,"Disable sound alert "..pma[1])
commandArray[ pma[1] ]=pma[2]
end
end
end
end
function powerDisconnect()
-- disconnect the last device in Heater table, that is ON. Return 0 in case that no devices have been disconnected
disc=0 -- number of devices that will be disconnected
for k,f in pairs(DeviceToDisconnect) do
-- log(E_WARNING,"Device "..f[1].." = "..otherdevices[ f[1] ])
if (otherdevices[ f[1] ]~=f[2]) then
log(E_CRITICAL, "Potenza="..currentPower..": Disconnesso "..f[1])
commandArray[ f[1] ]=f[2]
disc=1
break
end
end
if (disc==0) then
log(E_ERROR, "No loads available for disconnection")
end
return disc
end
currentPower=10000000 -- dummy value (10MW) used to determine if currentPower has changed or not
HPmode=otherdevices[HPMode] -- 'Off', 'Winter' or 'Summer'
if (HPmode==nil) then
HPmode='Off'
log(E_INFO,"You should create a selector switch named "..HPMode.." with 3 levels: Off, Winter, Summer")
end
getPower() -- get Power, PowerAUX, HP, EVSE structures from domoticz variables (coded in JSON format)
if (otherdevices['116493522530']~=nil) then
print("DEVICE '116493522530' EXISTS")
end
if (otherdevices[116493522530]~=nil) then
print("DEVICE 116493522530 EXISTS")
end
for devName,devValue in pairs(devicechanged) do
-- check for device named PowerMeter and update all DomBusEVSE GRIDPOWER virtual devices
if (PowerMeter~='') then
-- use PowerMeter device, measuring instant power (goes negative in case of exporting)
if (devName==PowerMeter) then
currentPower=getPowerValue(devValue)
log(E_DEBUG,"currentPower1="..currentPower.." from devName="..PowerMeter.." devValue="..devValue)
end
else
-- use PowerMeterImport and PowerMeterExport (if available)
if ((PowerMeterImport~='' and devName==PowerMeterImport) or (PowerMeterExport~='' and devName==PowerMeterExport)) then
currentPower=getPowerValue(otherdevices[PowerMeterImport])
if (PowerMeterExport~='') then
currentPower=currentPower-getPowerValue(otherdevices[PowerMeterExport])
end
end
end
if (EVPowerMeter ~= '') then
-- get actual EV charging power
if (devName==EVPowerMeter) then
-- new value from the electric vehicle charging power meter
Power['EV']=getPowerValue(devValue)
log(E_INFO,"Power[EV]="..Power['EV'])
commandArray['Variable:zPower']=json.encode(Power) -- save Power['EV']
-- output current value to led status
if (EVLedStatus ~= '') then
l=(math.floor(Power['EV']/1000))*10 -- 0=0..999W, 1=1000..1999, 2=2000..2999W, ...
for k,led in pairs(EVLedStatus) do
if (otherdevices_svalues[led]~=tostring(l)) then
commandArray[led]="Set Level "..tostring(l)
log(E_DEBUG,"EV: ChargingPower >= " .. l/10 .. "kW => Set leds")
end
end
end
end
end
if (devName == 'EV Current') then
if (batteryLevel==nil) then
if (otherdevices_svalues['eNiro: EV battery level']~=nil) then
batteryLevel=otherdevices_svalues['eNiro: EV battery level']
else
batteryLevel=unknown
end
end
log(E_INFO, "V="..otherdevices['EV Voltage'].." => EVCurr="..otherdevices_svalues['EV Current'].."A (EVPower="..getPowerValue(otherdevices['EV Energy']).." GridPower="..getPowerValue(otherdevices['Grid Power']).." PV="..math.floor(getPowerValue(otherdevices['PV_PowerMeter'])).."+"..getPowerValue(otherdevices['PV_Garden']).."W CarSoC="..batteryLevel.."%)" )
end
-- if blackout, turn on white leds in the building!
if (devName==blackoutDevice) then
log(E_WARNING,"========== BLACKOUT: "..devName.." is "..devValue.." ==========")
if (devValue=='Off') then -- blackout
log(E_WARNING, "Turn on white leds")
for k,led in pairs(ledsWhite) do
if (otherdevices[led]~=nil and otherdevices[led]~='0n') then
commandArray[led]='On'
Power['BL_'..k]='On' -- store in a variable that this led was activated by blackout check
end
end
for k,led in pairs(ledsWhiteSelector) do
if (otherdevices_svalues[led]~=nil and otherdevices_svalues[led]~='1') then
commandArray[led]="Set Level 1"
Power['BLS_'..k]='On' -- store in a variable that this led was activated by blackout check
end
end
for k,buzzer in pairs(blackoutBuzzers) do
if (otherdevices_svalues[buzzer]~=nil) then
commandArray[buzzer]="On for 10"
end
end
else -- power restored
for k,led in pairs(ledsWhite) do
if (otherdevices[led]~=nil and otherdevices[led]~='0ff' and (Power['BL_'..k]==nil or Power['BL_'..k]=='On')) then
commandArray[led]='Off'
Power['BL_'..k]=nil
end
end
for k,led in pairs(ledsWhiteSelector) do
if (otherdevices_svalues[led]~=nil and otherdevices_svalues[led]~='0' and (Power['BLS_'..k]==nil or Power['BLS_'..k]=='On')) then
commandArray[led]="Set Level 0"
Power['BLS_'..k]=nil
end
end
for k,buzzer in pairs(blackoutBuzzers) do
if (otherdevices_svalues[buzzer]~=nil) then
commandArray[buzzer]="Off"
end
end
end
end
if (devName=='Voltage_Battery') then
-- check battery voltage, AC detector and disable internet router when battery voltage is very low
batteryVoltage=tonumber(otherdevices_svalues['Voltage_Battery'])
if (otherdevices[blackoutDevice]=='Off' and otherdevices['Router_WAN_Reset']=='Off' and batteryVoltage<12.4) then
log(E_CRITICAL, "Blackout e tensione batteria bassa " .. batteryVoltage .." => tolgo alimentazione al router internet")
commandArray['Router_WAN_Reset']='On'
elseif (otherdevices[blackoutDevice]=='On' and otherdevices['Router_WAN_Reset']=='On') then
log(E_CRITICAL, "Blackout restored => riabilito alimentazione al router internet")
commandArray['Router_WAN_Reset']='Off'
end
end
end
-- check that main powermeter is really working...
if (PowerMeter~="" and otherdevices_lastupdate[PowerMeter]~=nil and timedifference(otherdevices_lastupdate[PowerMeter])>60) then
-- power meter is not working !!!!! Use PowerMeterImport and PowerMeterExport as backup grid power meter
if (PowerMeterImport~='' and PowerMeterExport~='' and (devicechanged[PowerMeterImport]~=nil or devicechanged[PowerMeterExport]~=nil)) then
currentPower=getPowerValue(otherdevices[PowerMeterImport])-getPowerValue(otherdevices[PowerMeterExport]) -- use PowerMeterImport and PowerMeterExport to determine current grid power
log(E_WARNING, "Main power meter is broken: get grid power from PowerMeterImport-PowerMeterExport")
end
end
-- if currentPower~=10MW => currentPower was just updated => check power consumption, ....
if (currentPower>-20000 and currentPower<20000) then
-- currentPower is good
commandArray['Variable:GridPower']=tostring(currentPower)
prodPower=0-currentPower
--[[
if (DOMBUSEVSE_GRIDPOWER~=nil) then -- update the DomBusEVSE virtual device used to know the current power from electricity grid
for k,name in DOMBUSEVSE_GRIDPOWER do
commandArray[name]=tostring(currentPower)..';0'
end
end
]]
setAvgPower()
incMinute=0 -- zero if script was executed not at the start of the current minute
if (Power['min']~=timeNow.min) then
-- minute was incremented
Power['min']=timeNow.min
incMinute=1 -- minute incremented => set this variable to exec some checking and functions
end
-- update LED statuses (on Creasol DomBusTH modules, with red/green leds)
-- red led when power usage >=0 (1=> <1000W, 2=> <2000W, ...)
-- green led when power production >0 (1 if <1000W, 2 if <2000W, ...)
--
if (currentPower<0) then
-- green leds
l=math.floor(1-currentPower/1000)*10 -- 1=0..999W, 2=1000..1999W, ...
else
l=0 -- used power >0 => turn off green leds
end
for k,led in pairs(ledsGreen) do
if (otherdevices_svalues[led]~=tostring(l)) then
commandArray[led]="Set Level "..tostring(l)
end
end
if (currentPower>0) then
-- red leds
l=(math.floor(currentPower/1000)+1)*10 -- 1=0..999W, 2=1000..1999, 3=2000..2999W, ...
else
l=0 -- used power >0 => turn off green leds
end
for k,led in pairs(ledsRed) do
if (otherdevices_svalues[led]~=tostring(l)) then
commandArray[led]="Set Level "..tostring(l)
end
end
toleratedUsagePower=0
if (timeNow.month<=3 or timeNow.month>=10) then -- winter
toleratedUsagePower=300 -- from October to March, activate electric heaters even if the usage power will be >0W but <300W
end
if (DOMBUSEVSE_GRIDPOWER~=nil) then -- update the DomBusEVSE virtual device used to know the current power from electricity grid
for k,name in pairs(DOMBUSEVSE_GRIDPOWER) do
commandArray[name]=tostring(currentPower)..';0'
log(E_DEBUG,"Update "..name.."="..currentPower)
end
end
if (HOYMILES_ID~='' and (os.time()-Power['Ht'])>=10) then
-- more than 10s since last time the hoymiles limit was set
Power['Ht']=os.time()
-- check when value have been modified last time
local newlimit=HOYMILES_LIMIT_MAX -- always MAX power if vehicle is charging
local hoymilesVoltage=tonumber(otherdevices[HOYMILES_VOLTAGE_DEV])
if (otherdevices[EVSE_STATE_DEV]~='Ch') then
-- not charging EV => modulate power
-- set inverter limit to avoid exporting too much power to the grid (max 6000W in Italy, in case of single phase)
newlimit=Power['HL']+currentPower-HOYMILES_TARGET_POWER
-- log(E_DEBUG, "HOYMILES: Power[HL]="..Power['HL'].." currentPower="..currentPower.." HOYMILES_TARGET_POWER="..HOYMILES_TARGET_POWER.." newlimit="..newlimit)
if (newlimit>HOYMILES_LIMIT_MAX) then
newlimit=HOYMILES_LIMIT_MAX
elseif (newlimit<100) then
newlimit=100 -- avoid turning off the inverter completely
end
if (hoymilesVoltage>=251.5) then
log(E_WARNING,"HOYMILES: Reduce inverter power")
if (newlimit>Power['HL']/2) then
newlimit=Power['HL']/2
end
else
if (Power['HL']<1600 and newlimit>(Power['HL']*1.35)) then
log(E_INFO,"HOYMILES: Increase inverter power")
newlimit=Power['HL']*1.35
end
end
newlimit=math.floor(newlimit)
if (newlimit>HOYMILES_LIMIT_MAX) then
newlimit=HOYMILES_LIMIT_MAX
elseif (newlimit<100) then
newlimit=100 -- 100 Watt: avoid turning off the inverter completely
end
end
local newlimitPerc=math.floor(newlimit*100/HOYMILES_LIMIT_MAX)
-- log(E_INFO, "HOYMILES new limit="..newlimit.." old limit="..Power['HL'])
if (newlimit~=Power['HL'] or (timeNow.min==0 and timeNow.sec>45)) then
log(E_INFO,"HOYMILES: Voltage="..hoymilesVoltage.."V currentPower="..currentPower.."W target="..HOYMILES_TARGET_POWER.."W => Transmit newlimit="..newlimit.." "..newlimitPerc.."%")
os.execute('/usr/bin/mosquitto_pub -u '..MQTT_OWNER..' -P '..MQTT_PASSWORD..' -t '..HOYMILES_ID..' -m '..newlimit)
Power['HL']=newlimit
commandArray[#commandArray + 1]={['UpdateDevice']=otherdevices_idx[HOYMILES_LIMIT_PERC_DEV].."|0|".. newlimitPerc}
end
-- Now check that inverter is producing
if (otherdevices[HOYMILES_PRODUCING_DEV]=='Off') then
-- inverter not producing
if (Power['HS']==1 and hoymilesVoltage>=240) then
-- inverter not producing due to overvoltage => restart it
newlimit=100 -- start inverter from 100W only to prevent overvoltage
os.execute('/usr/bin/mosquitto_pub -u ' .. MQTT_OWNER .. ' -P ' .. MQTT_PASSWORD .. ' -t ' .. HOYMILES_ID .. ' -m ' .. newlimit)
Power['HL']=newlimit
log(E_WARNING,"HOYMILES: inverter not producing => restart now with limit="..newlimit.."W")
commandArray[HOYMILES_RESTART_DEV]='On'
end
Power['HS']=0
else
Power['HS']=1
end
end
if (currentPower<PowerThreshold[1]) then
log(E_DEBUG,"currentPower="..currentPower.." < PowerThreshold[1]="..PowerThreshold[1])
-- low power consumption => reset threshold timers, used to count from how many seconds power usage is above thresholds
if (incMinute==1 or Power['ev']==1) then --Power['ev'] used to force EV management now
Power['ev']=0
for k,evRow in pairs(eVehicles) do
-- evRow[1]=ON/OFF device
-- evRow[2]=charging power
-- evRow[3]=current battery level
-- evRow[10]=current range (used to avoid problem with Kia battery level that often is not updated)
-- evRow[4]=min battery level (charge to that level using imported energy!)
-- evRow[5]=max battery level (stop when battery reached that level)
if (otherdevices[ evRow[1] ]==nil or otherdevices[ evRow[4] ]==nil or otherdevices[ evRow[5] ]==nil) then
log(E_WARNING,"EV: invalid device names in eVehicles structure, row number "..k)
else
if (Power['ev'..k]==nil) then
Power['ev'..k]=0 --initialize counter, incremented every minute when there is not enough power from renewables to charge the vehicle
end
evPower=evRow[2]
if (otherdevices[ evRow[4] ] == nil) then
-- user must create the selector switch used to set the minimum level of battery
log(E_WARNING,"EV: please create a virtual sensor, selector switch, named '"..evRow[4].."' with levels 0,10,20,..100")
batteryMin=50
else
if (otherdevices[ evRow[4] ]=='Off') then
batteryMin=0
else
batteryMin=tonumber(otherdevices[ evRow[4] ])
end
end
if (otherdevices[ evRow[5] ] == nil) then
-- user must create the selector switch used to set the maximum level of battery
log(E_WARNING,"EV: please create a virtual sensor, selector switch, named '"..evRow[4].."' with levels 0,10,20,..100")
batteryMax=80
else
if (otherdevices[ evRow[5] ]=='Off') then
batteryMax=0
else
batteryMax=tonumber(otherdevices[ evRow[5] ])
end
end
if (evRow[3]~='' and otherdevices[ evRow[3] ]~=nil) then
-- battery state of charge is a device
batteryLevel=tonumber(otherdevices[ evRow[3] ]) -- battery level device exists
-- compare batteryLevel with battery range, because KIA UVO has a trouble with battery range not updating
if (evRow[10]~='' and otherdevices[ evRow[10] ]~=nil) then
batteryRange=tonumber(otherdevices[ evRow[10] ])
if (batteryLevel<batteryRange/5.2) then
log(E_WARNING,"EV: batteryLevel too low if compared with range")
batteryLevel=batteryRange/5 -- 400km = 80%
end
end
elseif (uservariables[ evRow[3] ]~=nil) then
-- battery state of charge is a variable
batteryLevel=tonumber(uservariables[ evRow[3] ])
else
-- battery state of charge not available
batteryLevel=batteryMin -- battery level device does not exist => set to 50%
end
log(E_DEBUG, "EV: batteryLevel="..batteryLevel.." batteryMin="..batteryMin.." batteryMax="..batteryMax);
evDistance=0
if (evRow[6]~='') then
-- car distance sensor exists
for name,value in pairs(otherdevices) do
if (name:sub(1,evRow[6]:len()) == evRow[6]) then
evDistance=tonumber(value)
end
end
end
evSpeed=0
if (evRow[7]~='') then
-- car speed sensor exists
evSpeed=tonumber(otherdevices[ evRow[7] ])
end
log(E_DEBUG,"EV: Battery level="..batteryLevel.." Min="..batteryMin.." Max="..batteryMax)
if (otherdevices[ evRow[1] ]=='Off') then
-- not charging
if (avgPower+evPower<PowerThreshold[1] and batteryLevel<batteryMax and ((evDistance<5 and evSpeed==0) or batteryMin==100)) then
-- it's possible to charge without exceeding electricity meter threshold, and current battery level < battery max
toleratedUsagePowerEV=evPower/3*(1-(batteryLevel-batteryMin)/(batteryMax-batteryMin))
if (HPmode=='Winter') then toleratedUsagePowerEV=toleratedUsagePowerEV*2 end -- in Winter, don't care if the car is partially charged by grid
log(E_INFO,"EV: not charging, avgPower="..avgPower.." toleratedUsagePowerEV="..toleratedUsagePowerEV)
if (batteryLevel<batteryMin or (avgPower+evPower)<toleratedUsagePowerEV) then
-- if battery level > min level => charge only if power is available from renewable sources
log(E_INFO,"EV: start charging - batteryLevel="..batteryLevel.."<"..batteryMin.." or ("..avgPower.."+"..evPower.."<"..toleratedUsagePowerEV)
deviceOn(evRow[1],Power,'de'..k)
Power['ev'..k]=0 -- counter
end
end
else
-- charging
if ((evDistance>5 or evSpeed>0) and batteryMin<100) then
log(E_INFO,"EV: car is moving or is not near home => stop charging")
deviceOff(evRow[1],Power,'de'..k)
elseif (batteryLevel>=batteryMin) then
if (batteryLevel>=batteryMax) then
-- reached the max battery level
log(E_INFO,"EV: stop charging: reach the max battery level")
deviceOff(evRow[1],Power,'de'..k)
else
-- still charging: check available power
toleratedUsagePowerEV=evPower/2*(1-(batteryLevel-batteryMin)/(batteryMax-batteryMin))
if (HPmode=='Winter') then toleratedUsagePowerEV=toleratedUsagePowerEV*2 end -- in Winter, don't care if the car is partially charged by grid
log(E_DEBUG,"EV: charging with batteryLevel>batteryMin, avgPower="..avgPower.." toleratedUsagePowerEV="..toleratedUsagePowerEV)
if (avgPower>toleratedUsagePowerEV) then
-- too much power consumption -> increment counter and stop when counter is high
Power['ev'..k]=Power['ev'..k]+1
log(E_INFO,"EV: no enough energy from renewables since "..Power['ev'..k].." minutes")
if (Power['ev'..k]>5) then
log(E_INFO,"EV: stop charging")
deviceOff(evRow[1],Power,'de'..k)
end
else
log(E_DEBUG,"EV: enough energy to charge! ")
Power['ev'..k]=0 -- enough energy from renewable => reset counter
end
end
else
-- batteryLevel < battery min level
log(E_DEBUG,"EV: battery level lower than min value "..batteryMin)
end
end
end
end
-- Every minute
------------------------------------ check DEVauxlist to enable/disable aux devices (when we have/haven't got enough power from photovoltaic -----------------------------
Power['usage']=0 -- compute power delivered to aux loads
if (DEVauxlist~=nil) then
log(E_DEBUG,"Parsing DEVauxlist...")
if (HPmode=='Winter') then
devLevel=2 -- min HP['Level' to start this device if sufficient power from photovoltaic
else
devLevel=3 -- min HP['Level' to start this device if sufficient power from photovoltaic
end
for n,v in pairs(DEVauxlist) do
-- load conditions to turn ON/OFF this aux device
if (v[5]~='') then
con=load("return "..v[5]) -- expression that needs to turn off device
else
con=load("return TRUE")
end
if (v[6]~='') then
coff=load("return "..v[6]) -- expression that needs to turn off device
else
coff=load("return FALSE")
end
-- check timeout for this device (useful for dehumidifiers)
s=""
if (v[7]~=nil and PowerAux['s'..n]~=nil and PowerAux['s'..n]>0) then
s=" ["..PowerAux['s'..n].."/"..v[7].."m]"
end
log(E_INFO,"Aux "..otherdevices[ v[1] ]..": "..v[1] .." (" .. v[4].."/"..prodPower.."W)"..s)
auxTimeout=0
auxMaxTimeout=1440
if (v[7]~=nil and v[7]>0) then
-- max timeout defined => check that device has not reached the working time = max timeout in minutes
auxMaxTimeout=v[7]
checkVar('Timeout_'..v[1],0,0) -- check that uservariable at1 exists, else create it with type 0 (integer) and value 0
auxTimeout=uservariables['Timeout_'..v[1]]
if (otherdevices[ v[1] ]~='Off') then
-- device is actually ON => increment timeout
auxTimeout=auxTimeout+1
commandArray['Variable:Timeout_'..v[1]]=tostring(auxTimeout)
end
end
-- change state only if previous heatpump level match the current one (during transitions from a power level to another, power consumption changes)
if (otherdevices[ v[1] ]~='Off') then
-- device was ON
log(E_DEBUG,'Device was On: '..v[1]..'='..otherdevices[ v[1] ])
if (auxTimeout>=auxMaxTimeout) then
-- timeout reached -> send notification and stop device
deviceOff(v[1],PowerAux,'a'..n)
prodPower=prodPower+v[4] -- update prodPower, adding the power consumed by this device that now we're going to switch off
log(TELEGRAM_LEVEL,"Timeout reached for "..v[1]..": device was stopped")
elseif (peakPower() or prodPower<-100 or (HP['Level']<v[devLevel] and HPmode~='Off') or coff()) then
-- no power from photovoltaic, or heat pump is below the minimum level defined in config, or condition is not satisified, or OFF condition returns TRUE
-- stop device because conditions are not satisfied
deviceOff(v[1],PowerAux,'a'..n)
prodPower=prodPower+v[4] -- update prodPower, adding the power consumed by this device that now we're going to switch off
else
-- device On, and can remain On
Power['usage']=Power['usage']+v[4]
end
else
-- device is OFF
log(E_DEBUG,'Device was Off: '..v[1]..'='..otherdevices[ v[1] ])
if (peakPower()==false and auxTimeout<auxMaxTimeout and prodPower>=(v[4]+100) and (HP['Level']>=v[devLevel] or HPmode=='Off') and con()) then
deviceOn(v[1],PowerAux,'a'..n)
prodPower=prodPower-v[4] -- update prodPower
Power['usage']=Power['usage']+v[4]
end
end
end
end -- DEVauxlist exists
end -- every 1 minute
-- currentPower=-1200
limit=toleratedUsagePower+100
if (currentPower<limit) then
-- usage power < than first threshold
Power['above']=0
if (HPmode=='Winter') then
devCond=5
devLevel=2
else
devCond=8
devLevel=3
end
-- exported power => activate fast loads?
for n,v in pairs(DEVauxfastlist) do
if (otherdevices[ v[devCond] ]~=nil) then
log(E_DEBUG,"Auxfast "..otherdevices[ v[1] ]..": "..v[1] .." (" .. v[4].."/"..prodPower.."W)")
if (tonumber(otherdevices[ v[devCond] ])<v[devCond+2]) then cond=1 else cond=0 end
log(E_DEBUG,v[1] .. ": is " .. tonumber(otherdevices[ v[devCond] ]) .." < ".. v[devCond+2] .."? " .. cond)
-- change state only if previous heatpump level match the current one (during transitions from a power level to another, power consumption changes)
if (otherdevices[ v[1] ]~='Off') then
-- device was ON
log(E_DEBUG,'Device is not Off: '..v[1]..'='..otherdevices[ v[1] ])
if (v[13]~='') then
coff=load("return "..v[13]) -- expression that needs to turn off device
else
coff=load("return FALSE")
end
if (peakPower() or prodPower<-200 or (EVSEON_DEV~='' and otherdevices[EVSEON_DEV]=='On') or (HP['Level']<v[devLevel] and HPmode~='Off') or cond==v[devCond+1] or coff()) then
-- no power from photovoltaic, or heat pump is below the minimum level defined in config, or condition is not satisified, or OFF condition returns TRUE or EVSE is on (vehicle in charging)
-- stop device because conditions are not satisfied, or for more than v[11] minutes (timeout)
deviceOff(v[1],PowerAux,'f'..n)
prodPower=prodPower+v[4] -- update prodPower, adding the power consumed by this device that now we're going to switch off
Power['usage']=Power['usage']-v[4]
-- else device On, and can remain On
end
else
-- device is OFF
log(E_DEBUG,prodPower..">="..v[4]+100 .." and "..cond.."~="..v[devCond+1].." and "..HP['Level']..">="..v[devLevel])
if (v[12]~='') then
con=load("return "..v[12]) -- expression that needs to turn on device
else
con=load("return 1")
end
log(E_DEBUG,prodPower..">=".. (v[4]) .." and "..cond.."~="..v[devCond+1] .." and (".. HP['Level'] ..">=".. v[devLevel] .." or "..HPmode.."=='Off') and "..tostring(con()))
if (peakPower()==false and prodPower>=(v[4]) and cond~=v[devCond+1] and (HP['Level']>=v[devLevel] or HPmode=='Off') and con()) then
deviceOn(v[1],PowerAux,'f'..n)
prodPower=prodPower-v[4] -- update prodPower
Power['usage']=Power['usage']+v[4]
end
end
end
end
powerMeterAlert(0)
end
powerMeterAlert(0)
end
---------------------------------- EVSE: check electric vehicle --------------------------------------------------------------------
if (EVSE_CURRENT_DEV~=nil and EVSE_CURRENT_DEV~='' and otherdevices[EVSE_CURRENT_DEV]~=nil) then
-- EVSE device exists
-- EVSE_CURRENT_DEV = device used to set the charging current
-- EVSE_STATE_DEV = device with the current charging state
-- EVSE['T']=time when charging has been started. Used to charge 80min at highest power (+27%) and 80m at high power (+10%) ^^^^^^^^^^__________^^^^^^^^_______
if (EVSE_SOC_DEV~='' and otherdevices[EVSE_SOC_DEV]~=nil) then
batteryLevel=tonumber(otherdevices[EVSE_SOC_DEV])
else
batteryLevel=50 -- don't know battery level => set to 50%
end
if (batteryLevel>=tonumber(otherdevices_svalues[EVSE_SOC_MAX])) then
-- battery charged => stop charging
log(E_DEBUG, "EV: battery full: batteryLevel>="..otherdevices_svalues[EVSE_SOC_MAX]);
commandArray[EVSE_CURRENT_DEV]="Off"
else
if (otherdevices[EVSE_STATE_DEV]=='Con' and tonumber(otherdevices[EVSE_CURRENTMAX])>0 and batteryLevel<tonumber(otherdevices_svalues[EVSE_SOC_MAX]) and (PowerThreshold[1]-currentPower)>1800 and (currentPower<-800 or (batteryLevel<tonumber(otherdevices_svalues[EVSE_SOC_MIN]) and (timeNow.hour>=EVSE_NIGHT_START or timeNow.hour<EVSE_NIGHT_STOP or otherdevices[EVSE_SOC_MIN]=='On')))) then
-- Connected, batteryLevel<EVSE_SOC_MAX, enough power from energy meter, and
-- * extra power available from renewables, or
-- * in the night, or
-- * EVSE_SOC_MIN slide is active (On) => charge everytime
--
-- To charge only in the night, Disable the EVSE_SOC_MIN slider
-- To enable charge now, just enable EVSE_SOC_MIN slider
setCurrent=10 -- start charging
EVSE['t']=0
log(E_INFO,"EV: Start EV charging, setCurrent="..setCurrent)
commandArray[EVSE_CURRENT_DEV]="Set Level "..tostring(setCurrent)
if (otherdevices[EVSE_CURRENT_DEV]=='Off') then
commandArray[EVSE_CURRENT_DEV]="On"
end
log(E_INFO,"EVSE_CURRENT_DEV="..commandArray[EVSE_CURRENT_DEV])
log(E_INFO,"otherdevices_svalues[EVSE_CURRENT_DEV]="..otherdevices_svalues[EVSE_CURRENT_DEV])
elseif (otherdevices[EVSE_STATE_DEV]=='Ch') then
-- Cable connected and device is charging
-- charging!
if (EVSE['S']~='Ch' and EVSE['S']~='Vent') then
-- previous state: not charging
-- start charging, and start measuring how much renewable energy is used
EVSE['Et']=os.time() -- start measuring energy
EVSE['Ec']=getEnergyValue(otherdevices[EVSE_POWERMETER])
EVSE['Ei']=getEnergyValue(otherdevices[EVSE_POWERIMPORT])
end
evtime=os.difftime(os.time(), EVSE['T'])
if (evtime>PowerThreshold[3]*2) then
EVSE['T']=os.time()
evtime=0
end
currentNow=tonumber(otherdevices_svalues[EVSE_CURRENT_DEV])
if (batteryLevel<tonumber(otherdevices_svalues[EVSE_SOC_MIN])) then
-- use any power source, reneable and grid
if (evtime<PowerThreshold[3]-60) then
-- First 90 minutes => higest power (Power+27%)
maxPower=PowerThreshold[2]
else
-- Remaining 90 minutes at high power (Power+10%)
maxPower=PowerThreshold[1]
end
else
-- SOC_MIN <= SOC < SOC_MAX => use only renewable energy
maxPower=0 -- currentPower should be negative (exported)
if (currentNow>=6 and currentNow<=12 and batteryLevel<tonumber(otherdevices_svalues[EVSE_SOC_MAX])-5) then
-- if charging current is really low and batteryLevel<BatteryMax-5, try to charge using some energy from grid, to improve charging efficiency
maxPower=500
end
end
-- Regulate the charging current
availablePower=maxPower-currentPower
setCurrent=0 -- default: do not change anything
-- Charge at the maximum power
availableCurrent=math.floor(availablePower/230)
if (availableCurrent>=4 or availableCurrent<=-4) then
availableCurrent=math.floor(availableCurrent/2) -- increase or decrease slowly
elseif (availableCurrent>=1) then
availableCurrent=1 -- increase only 1 Ampere
elseif (availableCurrent<=-1) then
availableCurrent=-1
else
availableCurrent=0
end
-- if (availableCurrent~=0) then log(E_INFO,"EVSE: currentPower="..currentPower.." availablePower="..availablePower.." availableCurrent="..availableCurrent) end
setCurrent=currentNow+availableCurrent
if (setCurrent<6) then
-- charge current should be reduced
EVSE['t']=EVSE['t']+1
maxtime=PowerThreshold[4] -- max time after which the EVSE must be stopped to prevent disconnections
if (currentPower<PowerThreshold[1]) then maxtime=180 end -- probably setCurrent is low because only renewable energy should be use: increase maxtime
log(E_INFO,"EV: Overload for ".. (EVSE['t']*5) .."/"..maxtime.."s")
if (EVSE['t']*5>=maxtime) then
log(E_INFO,"EV: disable charging because Power[EVt]>=maxtime")
setCurrent=0
else
log(E_INFO,"EV: overload => set current=6A")
setCurrent=6
end
else
-- charge current ok
if (EVSE['t']>=4) then EVSE['t']=EVSE['t']-4 end -- decrease overload timeout
if (setCurrent>tonumber(otherdevices[EVSE_CURRENTMAX])) then
setCurrent=tonumber(otherdevices[EVSE_CURRENTMAX])
end
if (setCurrent>EVSE_MAXCURRENTVALUE) then
setCurrent=EVSE_MAXCURRENTVALUE
end
end
if (setCurrent~=currentNow) then
log(E_INFO,"EVSE: available="..availablePower.."W, I="..currentNow.."->"..setCurrent.."A, batteryLevel="..batteryLevel.." ("..otherdevices_svalues[EVSE_SOC_MIN].."->"..otherdevices_svalues[EVSE_SOC_MAX]..")")
if (setCurrent>=6 and
otherdevices[EVSE_CURRENT_DEV]=='Off') then
commandArray[EVSE_CURRENT_DEV]="On"
end
commandArray[EVSE_CURRENT_DEV]="Set Level "..setCurrent
end
end -- while charging
end
Et=os.difftime(os.time(),EVSE['Et'])
if (Et>=18) then
-- while charging -> update EVSE_RENEWABLE energy meter
Ec=getEnergyValue(otherdevices[EVSE_POWERMETER])
Ei=getEnergyValue(otherdevices[EVSE_POWERIMPORT])
Er=(Ec-EVSE['Ec'])-(Ei-EVSE['Ei']) -- renewable energy used to charge the car
if (Er<0) then Er=0 end
evseSetGreenPower(Er,Et) -- update the greenPower energy meter
EVSE['Ec']=Ec
EVSE['Ei']=Ei
EVSE['Et']=os.time()
end
EVSE['S']=otherdevices[EVSE_STATE_DEV] -- save current state
end
if (currentPower>PowerThreshold[1]) then
Power['th1']=Power['th1']+POWERMETER_INTERVAL
if (currentPower<PowerThreshold[2]) then
-- power consumption a little bit more than available power => long intervention time, before disconnecting
if (Power['th2']>=POWERMETER_INTERVAL) then Power['th2']=Power['th2']-POWERMETER_INTERVAL else Power['th2']=0 end
else -- very high power consumption : short time to disconnect some loads
Power['th2']=Power['th2']+POWERMETER_INTERVAL
log(E_WARNING, "Power="..currentPower.." > "..PowerThreshold[2].." for "..Power['th2'].."/"..PowerThreshold[4].."s")
if (Power['th2']>=PowerThreshold[4]) then
-- can I disconnect anything?
-- very high power consumption: short intervention time before power outage
time=os.time()-Power['disc'] -- disconnect devices every 10s
if (time>=10 and powerDisconnect()==0) then
-- nothing to disconnect
powerMeterAlert(1) -- send alert
log(E_CRITICAL,"Potenza assorbita="..currentPower.."W: Pericolo di disconnessione. Spegnere elettrodomestici!") -- send alert by Telegram
else
-- one device has been disconnected
powerMeterAlert(0)
Power['disc']=os.time()
end
end
end
log(E_WARNING, "Power="..currentPower.." > "..PowerThreshold[1].." for "..Power['th1'].."/"..PowerThreshold[3].."s")
if (Power['th1']>=PowerThreshold[3]) then
-- can I disconnect anything?
time=os.time()-Power['disc'] -- disconnect devices every 60s
if (time>=60 and powerDisconnect()==0) then
-- nothing to disconnect
powerMeterAlert(1) -- send alert
log(E_CRITICAL,"Potenza assorbita="..currentPower.."W: Pericolo di disconnessione. Spegnere elettrodomestici!") -- send alert by Telegram
else
powerMeterAlert(0)
Power['disc']=os.time()
end
end
else
-- currentPower has a right value
if (Power['th1']>=POWERMETER_INTERVAL) then Power['th1']=Power['th1']-POWERMETER_INTERVAL else Power['th1']=0 end
if (Power['th2']>=POWERMETER_INTERVAL) then Power['th2']=Power['th2']-POWERMETER_INTERVAL else Power['th2']=0 end
end
-- DEBUG: TRACKER WIND SENSOR
--commandArray['dombusLab - (ffd0.d) Wind']='3'
-- save variables in Domoticz, in a json variable Power
-- log(E_INFO,"commandArray['Variable:zPower']="..json.encode(Power))
commandArray['Variable:zPower']=json.encode(Power)
commandArray['Variable:zPowerAux']=json.encode(PowerAux)
commandArray['Variable:zEVSE']=json.encode(EVSE)
commandArray['Variable:avgPower']=tostring(avgPower)
log(E_DEBUG,"currentPower="..currentPower.." avgPower="..avgPower.." Used_by_heaters="..Power['usage'])
if (PowerAux~=nil) then
log(E_DEBUG,"PowerAux="..json.encode(PowerAux))
end
end -- if currentPower is set
--print("power end: "..os.clock()-startTime) --DEBUG