Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Efergy ems #228

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions TWCManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"EMS.SolarLog",
"EMS.TeslaPowerwall2",
"EMS.TED",
"EMS.Efergy",
"Status.HASSStatus",
"Status.MQTTStatus",
]
Expand Down Expand Up @@ -97,6 +98,16 @@
debugLog(1, "Unable to find a configuration file.")
sys.exit()




########################################################################
# Write the PID in order to let a supervisor restart it in case of crash
PIDfile=config["config"]["settingsPath"] + "/TWCManager.pid"
PIDTWCManager=open(PIDfile,"w")
PIDTWCManager.write(str(os.getpid()))
PIDTWCManager.close()

# All TWCs ship with a random two-byte TWCID. We default to using 0x7777 as our
# fake TWC ID. There is a 1 in 64535 chance that this ID will match each real
# TWC on the network, in which case you should pick a different random id below.
Expand Down Expand Up @@ -237,6 +248,9 @@ def background_tasks_thread(master):
requests.post(task["url"], json=body)
elif task["cmd"] == "saveSettings":
master.saveSettings()
elif task["cmd"] == "checkMaxPowerFromGrid":
check_max_power_from_grid()


except:
master.debugLog(
Expand Down Expand Up @@ -285,6 +299,24 @@ def check_green_energy():
master.setMaxAmpsToDivideAmongSlaves(master.getMaxAmpsToDivideGreenEnergy())


def check_max_power_from_grid():
global config, hass, master

# Check solar panel generation using an API exposed by
# the HomeAssistant API.
#
# You may need to customize the sensor entity_id values
# to match those used in your environment. This is configured
# in the config section at the top of this file.
#
# Poll all loaded EMS modules for consumption and generation values
for module in master.getModulesByType("EMS"):
master.setConsumption(module["name"], module["ref"].getConsumption())
master.setGeneration(module["name"], module["ref"].getGeneration())
master.setMaxAmpsToDivideFromGrid(master.getMaxAmpsToDivideFromGrid())



def update_statuses():

# Print a status update if we are on track green energy showing the
Expand Down
51 changes: 51 additions & 0 deletions etc/twcmanager/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,32 @@
# wiringMaxAmpsPerTWC = 50 * 0.8 = 40 and wiringMaxAmpsAllTWCs = 40 + 40 = 80.
"wiringMaxAmpsPerTWC": 6,


# If you what to limit the power drawn from the Grid you need to set this
# maxAmpsAllowedFromGrid and extend the policy you what it to apply, i.e.:
# { "name": "Charge Now with Grid power limit",
# "match": [
# "settings.chargeNowAmps",
# "settings.chargeNowTimeEnd",
# "settings.chargeNowTimeEnd",
# ],
# "condition": ["gt", "gt", "gt"],
# "value": [0, 0, "now"],
# "background_task": "checkMaxPowerFromGrid",
# "charge_amps": "settings.chargeNowAmps",
# "charge_limit": "config.chargeNowLimit"},

# { "name": "Scheduled Charging with Grid power limit",
# "match": [ "checkScheduledCharging()" ],
# "condition": [ "eq" ],
# "value": [ 1 ],
# "background_task": "checkMaxPowerFromGrid",
# "charge_amps": "settings.scheduledAmpsMax",
# "charge_limit": "config.scheduledLimit"},

"maxAmpsAllowedFromGrid": 15,


# https://teslamotorsclub.com/tmc/threads/model-s-gen2-charger-efficiency-testing.78740/#post-1844789
# says you're using 10.85% more power (91.75/82.77=1.1085) charging at 5A vs 40A,
# 2.48% more power at 10A vs 40A, and 1.9% more power at 20A vs 40A. This is
Expand Down Expand Up @@ -258,6 +284,25 @@
#
# They should primarily be used to abort charging when necessary.
"emergency":[
{ "name": "Charge Now with Grid power limit",
"match": [
"settings.chargeNowAmps",
"settings.chargeNowTimeEnd",
"settings.chargeNowTimeEnd",
],
"condition": ["gt", "gt", "gt"],
"value": [0, 0, "now"],
"background_task": "checkMaxPowerFromGrid",
"charge_amps": "settings.chargeNowAmps",
"charge_limit": "config.chargeNowLimit"},

{ "name": "Scheduled Charging with Grid power limit",
"match": [ "checkScheduledCharging()" ],
"condition": [ "eq" ],
"value": [ 1 ],
"background_task": "checkMaxPowerFromGrid",
"charge_amps": "settings.scheduledAmpsMax",
"charge_limit": "config.scheduledLimit"},
],
# Rules in the before section here are evaluated after the Charge Now rule
"before":[
Expand Down Expand Up @@ -495,7 +540,13 @@
"generationItem": "Generation item name",
"serverIP": "192.168.1.2",
"serverPort": "8080"
},
# The Efergy server allows fetching of consumption from https://engage.efergy.com/ token is needed
"Efergy": {
"enabled": false,
"token": "xx"
}

},

# Status plugins allow us to export status detail out of TWCManager.
Expand Down
2 changes: 1 addition & 1 deletion lib/TWCManager/Control/HTTPControl.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def do_chargeSchedule(self):
curday = settings.get(day, {})
if (today.get("enabled", None) == "on" and
(int(curday.get("start", 0)[:2]) <= int(i)) and
(int(curday.get("end", 0)[:2]) >= int(i))):
(int(curday.get("end", 0)[:2]) > int(i))):
page += "<td bgcolor='#CFFAFF'>SC @ " + str(settings.get("Settings", {}).get("scheduledAmpsMax", 0)) + "A</td>"
else:
#Todo - need to mark track green + non scheduled chg
Expand Down
2 changes: 1 addition & 1 deletion lib/TWCManager/Control/themes/Default/jsrefresh.html.j2
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ $(document).ready(function() {
}

// Change the state of the Charge Now button based on Charge Policy
if (json["currentPolicy"] == "Charge Now") {
if (json["currentPolicy"] == "Charge Now" || json["currentPolicy"] == "Charge Now with Grid power limit") {
document.getElementById("start_chargenow").value = "Update Charge Now";
document.getElementById("cancel_chargenow").disabled = false;
} else {
Expand Down
124 changes: 124 additions & 0 deletions lib/TWCManager/EMS/Efergy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Efergy


class Efergy:

import requests
import time

cacheTime = 10
config = None
configConfig = None
configEfergy = None
consumedW = 0
debugLevel = 0
fetchFailed = False
token=0
generatedW = 0
importW = 0
exportW = 0
lastFetch = 0
master = None
status = False
timeout = 10
voltage = 0

def __init__(self, master):
self.master = master
self.config = master.config
try:
self.configConfig = master.config["config"]
except KeyError:
self.configConfig = {}
try:
self.configEfergy = master.config["sources"]["Efergy"]
except KeyError:
self.configEfergy = {}
self.debugLevel = self.configConfig.get("debugLevel", 0)
self.status = self.configEfergy.get("enabled", False)
self.token = self.configEfergy.get("token", None)
self.serverPort = self.configEfergy.get("serverPort", "80")

# Unload if this module is disabled or misconfigured
if (not self.status):
self.master.releaseModule("lib.TWCManager.EMS", "Efergy")
return None

def debugLog(self, minlevel, message):
self.master.debugLog(minlevel, "Efergy", message)

def getConsumption(self):

if not self.status:
self.debugLog(10, "Efergy EMS Module Disabled. Skipping getConsumption")
return 0

# Perform updates if necessary
self.update()

# Return consumption value
return float(self.consumedW)

def getGeneration(self):

if not self.status:
self.debugLog(10, "Efergy EMS Module Disabled. Skipping getGeneration")
return 0

# Perform updates if necessary
self.update()

# Return generation value
if not self.generatedW:
self.generatedW = 0
return float(self.generatedW)


def getValue(self, url):

# Fetch the specified URL from the Efergy and return the data
self.fetchFailed = False

try:
r = self.requests.get(url, timeout=self.timeout)
except self.requests.exceptions.ConnectionError as e:
self.debugLog(
4, "Error connecting to Efergy to fetch sensor value"
)
self.debugLog(10, str(e))
self.fetchFailed = True
return False

r.raise_for_status()
jsondata = r.json()
return jsondata

def getMeterData(self):
url = "https://engage.efergy.com/mobile_proxy/getCurrentValuesSummary?token="+self.token

return self.getValue(url)

def update(self):

if (int(self.time.time()) - self.lastFetch) > self.cacheTime:
# Cache has expired. Fetch values from Efergy.

meterData = self.getMeterData()

if meterData:
try:
self.consumedW = list(meterData[0]['data'][0].values())[0]
except (KeyError, TypeError) as e:
self.debugLog(
4, "Exception during parsing Meter Data (Consumption)"
)
self.debugLog(10, e)

# Update last fetch time
if self.fetchFailed is not True:
self.lastFetch = int(self.time.time())

return True
else:
# Cache time has not elapsed since last fetch, serve from cache.
return False
38 changes: 38 additions & 0 deletions lib/TWCManager/TWCMaster.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class TWCMaster:
lastTWCResponseMsg = None
masterTWCID = ""
maxAmpsToDivideAmongSlaves = 0
maxAmpsToDivideFromGrid = 0
modules = {}
nextHistorySnap = 0
overrideMasterHeartbeatData = b""
Expand Down Expand Up @@ -532,6 +533,29 @@ def getMaxAmpsToDivideGreenEnergy(self):
amps = amps / self.getRealPowerFactor(amps)
return round(amps, 2)

def getMaxAmpsToDivideFromGrid(self):
# Calculate our current generation and consumption in watts
generationW = float(self.getGeneration())
consumptionW = float(self.getConsumption())

currentOffer = min(
self.getTotalAmpsInUse(),
self.getMaxAmpsToDivideAmongSlaves(),
)

# Calculate what we should max offer to align with max grid energy
amps = self.config["config"]["maxAmpsAllowedFromGrid"] + \
self.convertWattsToAmps(generationW - consumptionW) + \
currentOffer

amps = amps / self.getRealPowerFactor(amps)
self.debugLog(
10, "TWCMaster", "MaxAmpsToDivideFromGrid: +++++++++++++++: " + str(amps)
)

return round(amps, 2)


def getNormalChargeLimit(self, ID):
if "chargeLimits" in self.settings and str(ID) in self.settings["chargeLimits"]:
result = self.settings["chargeLimits"][str(ID)]
Expand Down Expand Up @@ -1167,6 +1191,15 @@ def setMaxAmpsToDivideAmongSlaves(self, amps):
)
amps = self.config["config"]["wiringMaxAmpsAllTWCs"]


if amps > self.maxAmpsToDivideFromGrid:
# Never tell the slaves to draw more amps from grid than allowed
amps = self.maxAmpsToDivideFromGrid
self.debugLog(
10, "TWCMaster","maxAmpsToDivideAmongSlaves limited to not draw more power from the grid than allowed: " + str(amps)
)


self.maxAmpsToDivideAmongSlaves = amps

self.releaseBackgroundTasksLock()
Expand All @@ -1175,6 +1208,11 @@ def setMaxAmpsToDivideAmongSlaves(self, amps):
# to console / MQTT / etc
self.queue_background_task({"cmd": "updateStatus"})

def setMaxAmpsToDivideFromGrid(self, amps):
# This is called when check_max_power_from_grid is run
# It stablished how much power we allow getting from the grid
self.maxAmpsToDivideFromGrid = amps

def setNonScheduledAmpsMax(self, amps):
self.settings["nonScheduledAmpsMax"] = amps

Expand Down
24 changes: 24 additions & 0 deletions svisorTWC.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
PROGRAM=/usr/bin/python3.5
PIDFILE=/etc/twcmanager/TWCManager.pid
TWCMANAGER_PATH=/home/pi/TWCManager

while true
do

if [ -f $PIDFILE ]; then
read PID <$PIDFILE
echo $PID
if [ -d /proc/$PID ] && [ "$(readlink -f /proc/$PID/exe)" = "$PROGRAM" ]; then
echo "done."
else
echo "PID not found, Starting..."
screen -dm -S TWCManager $TWCMANAGER_PATH/TWCManager.py
fi
else
echo "PID file not found "; echo $PIDFILE; echo ", Starting..."
screen -dm -S TWCManager $TWCMANAGER_PATH/TWCManager.py
fi
sleep 30
done