-
Notifications
You must be signed in to change notification settings - Fork 119
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
Local power sharing group #940
Comments
The concept of a group could be extended to include sharing metering data to facilitate the export to CSV like #941 proposes. |
A simple FIFO method for doing this could also be implemented by adding in support to OpenEVSE to talk to the API of a meter. Sample of how this might work: Suppose there are 2 EVSE and one A/C unit: EV1, EV2, and AC. Also, there is 14 kW available to share among these three. When EV1 requests power, it queries the API of a meter, and finds there is 14 kW available. It has a minimum draw of 4 kW. It counts 4 cycles (1 cycle per kW), and, if there is still 4 kW, starts drawing that. Every additional cycle it draws an additional 1 kW until it maxes out at 10 kW. checking the meter API each time to see that power is available. When EV2 requests power, it queries the API of the meter and sees that there is 4 kW available. It has a minimum draw of 4 kW. Following the pattern above, it waits for cycles, checking each time that there is still 4 kW available. At that time, it sees there are 4 kW and draws that much. The AC turns on. It immediately draws 4 kW. EV1 and EV2 notice that the meter reports that 18 kW of the 14 kW they've been programmed to know is the maximum is being drawn. They each reduce their load by 4 kW. This means that EV1 is drawing 6 kW and EV2 isn't drawing any power. At this point both EVSE read the meter API and see that there is 4 kW available. EV1 notices this and draws an additional kW, for a total of 7 kW. EV2 notices this and starts counting cycles until it can meet the minimum. On the second cycles post-AC-load, EV1 notices that there are 3 kW, so it starts drawing 8 kW. EV2 notices that there is no longer 4 kW, its minimum, available, so it starts over waiting for 4 kW. EV1 continues to ramp until it is at 10 kW. After the AC turns off, EV2 ramps up to 4 kW because EV1 is already at its local maximum of 10 kW. After EV1 throttles down to 4 kW as the battery gets full (or whatever reason), EV2 will notice this due to its own calls to the meter API and start increasing the demand 1 kW every cycle as long as there is more power available. If both EV1 and EV2 were plugged in at the same time, they would both wait 4 cycles looking to see that there is 4 kW each time and, since there would be, they would both start drawing 4 kW. On their next cycle, they would each independently read the meter API and see that 6 kW remain, and would therefor increase their load 1 kW/cycle until they are both at 7 kW. The AC coming on would cause the same behavior described above. If EV2 came online 1 cycle after EV1, then EV1 would get its 4 kW first, and 1 cycle later, EV2 would get its 4 kW, by which time EV1 would be at 5 kW. This would continue until all 14 kW is used and EV1 is drawing 8 kW and EV2 is drawing 6 kW (assuming it is polling moments after EV1). If the cycle times were randomized between 2-4 seconds, then it could be a 7 kW-7kW split. Ultimately, this would mean that just installing the EVSE with a meter that has an API that is supported would result in both dynamic load management and power sharing without any groups needed. The only support that would have to be added to OpenEVSE would be for calling the API of a meter. There are several meters that support such APIs: |
In case you're interested in what this code might look like, here is some Python. This assume that the meter has an API that can be called to get the current reading, which all of the above-posted meters do. import random
class Device:
def __init__(self, name, min_power, max_power, is_ev=False):
self.name = name
self.min_power = min_power
self.max_power = max_power
self.is_ev = is_ev
self.current_power = 0
self.is_on = False
self.wait_cycles = 0
def poll_meter(self, available_power):
if not self.is_on:
self.current_power = 0 # Ensure no power is drawn if device is off
self.wait_cycles = 0
return
if self.is_ev: # EV-specific behavior
if self.current_power == 0: # Not drawing power yet
if available_power >= self.min_power:
self.wait_cycles += 1
if self.wait_cycles >= self.min_power: # Wait enough cycles for power to be consistently available
self.current_power = self.min_power
self.wait_cycles = 0
print(f"{self.name} starts drawing {self.current_power} kW.")
else:
self.wait_cycles = 0 # Reset wait cycles if power is insufficient
else: # Already drawing power
increase = min(1, available_power, self.max_power - self.current_power)
if increase > 0:
self.current_power += increase
print(f"{self.name} increases power to {self.current_power} kW.")
else: # Non-EV behavior
if self.current_power == 0: # Non-EVs switch on with a random power draw
random_power = random.randint(self.min_power, self.max_power)
self.current_power = random_power
print(f"{self.name} starts drawing {self.current_power} kW.")
def handle_overdraw(self, overdraw):
if self.is_ev and self.is_on and self.current_power > 0: # EVs shed load to accommodate
shed_amount = min(overdraw, self.current_power)
self.current_power -= shed_amount
if self.current_power < self.min_power:
print(f"{self.name} drops to 0 kW due to insufficient power.")
self.current_power = 0 # If shedding drops below minimum, shut off
else:
print(f"{self.name} sheds {shed_amount} kW, now drawing {self.current_power} kW.")
def main():
num_evs = int(input("Enter the number of EVs: "))
total_power = int(input("Enter the total power available (kW): "))
devices = []
evs = []
for i in range(num_evs):
ev = Device(f"EV{i+1}", min_power=2, max_power=12, is_ev=True)
devices.append(ev)
evs.append(ev)
devices.append(Device("AC", min_power=2, max_power=12))
devices.append(Device("WH", min_power=2, max_power=12))
devices.append(Device("HT", min_power=2, max_power=12))
while True:
# Get user input to turn devices on or off
user_input = input("Enter devices to toggle on/off (comma-separated, e.g., EV1,AC): ").strip()
if user_input:
toggled_devices = [name.strip().upper() for name in user_input.split(",")]
for device in devices:
if device.name.upper() in toggled_devices:
device.is_on = not device.is_on
state = "on" if device.is_on else "off"
print(f"{device.name} turned {state}.")
random.shuffle(devices)
available_power = total_power - sum(device.current_power for device in devices)
for device in devices:
device.poll_meter(available_power)
available_power = total_power - sum(device.current_power for device in devices)
# Handle overdraw for EVs to accommodate non-EVs
if available_power < 0:
overdraw = abs(available_power)
for ev in evs:
ev.handle_overdraw(overdraw)
available_power = total_power - sum(device.current_power for device in devices)
# Print the draw of each device and the meter reading
print("\nCurrent power draw:")
for device in sorted(devices, key=lambda x: x.name):
status = "ON" if device.is_on else "OFF"
print(f"{device.name}: {device.current_power} kW ({status})")
print(f"Meter reading: {total_power - available_power} kW out of {total_power} kW available\n")
if __name__ == "__main__":
main() |
I really like the idea that a group of OpenEVSEs could manage power sharing locally, even if a cloud server, local external controller, HA instance, or MQTT-reporting meter goes down. I would prefer to define members of the group by (static) IP than mDNS. There may be a use case where, on communication loss, the EVSE group owner might prefer to have one full power station and the rest disabled. I suppose this could be accomplished with the "min safe current" being set to zero on one or more members of the group. Edit: |
I wrote up a description of the way the old pre-Enel Juicenet was able to do load sharing, because I think it was nearly ideal in its flexibility. Using it (just the free consumer version, not the paid commercial version) we managed dozens of chargers sharing circuits in multiple adjacent carport buildings sharing a 100A service. This is probably more than would be reasonable for peer-to-peer charger management in OpenEVSE, but I wanted to bring the model to the discussion in case it informs ways your designs might become expandable in future releases. The basic concept is that a load group can contain chargers or other load groups. Load balancing in JuiceNet A load group could be set as a member of a higher-level load group. So, for example, a parking structure might have 4 40A circuits with 5 chargers on each, and a 100A service feeding them. In this case, you would create 4 load groups, each with a 40 A limit, and place them into another load group with a 100 A limit. At any given moment, it was highly unlikely that all 20 chargers would be going at once, but if they were, the 100A group would tell the 4 40 amp groups that they could use no more than 25 A, and those groups would tell the chargers to use no more than 5 A. As charging sessions ended, charge rates would trend upward for all remaining chargers/groups. Alternatively, if only 2 chargers were active, and they were in different 40 A groups, each would be allowed to use the full 40A. Whatever the combination, the software would dynamically adjust charge rates to be as high as the load limits allowed, and no more. This made it practical to install the 220 v charging outlets in a cost-efficient daisy chain of 5-10 outlets in a row. This facilitates a buildout of outlets in each parking space for minimal cost. At first, only a few outlets will actually get a charger installed, and load sharing is not that important. But as the density of chargers increases, load sharing becomes critical. In our case, we had a set of 3 adjacent carport structures with around 30 parking spaces all successfully sharing a 100 A service. As more EVs were purchased, we made the investment to put in a larger service (400A). All we had to do to maintain fast charging rates was to just split the daisy chains into shorter segments, add some additional home-run wires back to the panel, and adjust our load groups to match. This allowed us to enable universal charging with minimal initial investment while allowing us to add capacity over time as EV density increased with trivial re-working of those initial assets. I created a white paper targeted to state regulators and managers of multifamiliy properties that explains this in greater detail: https://localforce.io/misc/Cost_Effective_EV_Charging_for_Multifamily_Residential_Communities.pdf |
This is an expansion #592.
With Enel X shutting down servers in North America, many Juicebox owners are looking for solutions to continue operating their stations. OpenEVSE has developed drop in replacement controllers.
One popular feature on the JuiceBox was cloud server based load sharing. Load sharing on OpenEVSE is possible with external hardware and software such as Home Assistant or MQTT and logic from Node-Red or a Python script.
This issue proposes a local load sharing setup that can be easily implemented without and dependencies on additional hardware and software.
On the primary station:
Add new share group.
Define a static maximum power draw for the group OR click a box to follow the existing MQTT dynamic load available topic for Shaper/Divert.
Add members to the share group by mDNS name.
Select sharing Profile (Same current for all active stations, First station highest priority, Reduce all by same percentage, etc).
Select lost communications safe current
On each member station:
Enter mDNS name of Primary Station to allow Sharing
Select lost communications safe current
Primary would establish a http connection to each client, and send a claim to set the current and request status every xx seconds.
Each client would set the claim and respond with status.
Primary and Client would note group as responsive and countdown to the next heartbeat.
The text was updated successfully, but these errors were encountered: