Website and live demo → steps-re.github.io/peakshift
Run your window AC on cheap electricity. PeakShift is an open-source controller that charges a plug-in battery during off-peak hours and runs your AC from that battery during peak hours. No electrician, no interconnection, no grid export. If you rent an apartment, this is battery arbitrage you can actually do.
Peak-hour electricity costs 2 to 10 times the off-peak rate on time-of-use plans. A window AC is the biggest plug-in load in most apartments and it runs hardest exactly when rates peak. Shifting it to stored off-peak power saves roughly $65 to $280 per year per 2 kWh battery depending on your utility (estimates, see Savings below).
Inspired by the category Every Electric pioneered in NYC. They give you managed hardware in ConEd territory. PeakShift is the DIY version for a battery you buy yourself, anywhere with a time-of-use rate.
Most LiFePO4 portable power stations (EcoFlow Delta, Bluetti, Anker Solix and others) have a UPS or passthrough mode. Your AC plugs into the battery. The battery plugs into the wall. When wall power is present the unit charges and passes power through. When wall power disappears the AC keeps running from the battery, switching in about 10 ms.
So the whole control problem reduces to one decision: when does the battery's input get power?
- Off-peak: input on. Battery charges, AC runs on passthrough.
- Peak: input off. AC runs from the battery.
- Battery gets low during peak: input back on. Your AC never dies to save a dollar.
PeakShift makes that decision every 30 seconds, two ways:
- Smart plug (recommended, works with any passthrough battery): a Shelly Plug US between the wall and the battery input. PeakShift switches it locally over your LAN. No cloud.
- Battery API: for EcoFlow devices, PeakShift sets the AC charge rate to zero during peak through EcoFlow's developer API. Written to their published protocol but not yet bench-tested on hardware. Treat it as beta and prefer the smart plug.
git clone https://github.com/steps-re/peakshift && cd peakshift
python3 -m venv .venv && .venv/bin/pip install -e .
cp config.example.yaml config.yaml # edit: your peak hours, your driver
.venv/bin/peakshift test-drive # one off/on cycle to verify wiring
.venv/bin/peakshift runSet your peak window by hand in config.yaml (it's printed on your utility bill), or
look it up:
export NLR_API_KEY=... # free key from https://developer.nlr.gov/signup/
.venv/bin/peakshift rates --zip 11201peakshift rates lists the time-of-use tariffs for your ZIP from the NLR Utility
Rate Database with the peak/off-peak spread for each. Rate databases go stale, so
check the window against your bill before trusting it.
Outside fixed ToU windows, PeakShift can plan against live market prices. Swap the
schedule: block for a prices: block and it picks the most expensive hours of each
day to run on battery:
prices:
provider: awattar # see the table below
params: {country: de}
mode: top_hours # battery during the day's priciest hours
battery_hours: 4 # how long your battery carries the load
currency: EURpeakshift prices shows today's price curve and the hours it plans to shift.
| Provider | Region | Auth | Verified live |
|---|---|---|---|
urdb (schedule block) |
US ToU tariffs | free NLR key | yes |
octopus_agile |
UK | none | yes |
awattar |
Germany, Austria | none | yes |
elering |
EE, LV, LT, FI | none | yes |
spothintafi |
Finland | none | yes |
comed |
US Illinois hourly | none | yes |
pvpc |
Spain | none | yes |
entsoe |
~25 EU bidding zones | free token | not yet |
tibber |
NO, SE, DE, NL | account token | not yet |
amber |
Australia | account token | not yet |
"Not yet" means written to the published API and unit-tested against recorded responses, but we haven't held a real token. If you run one, tell us how it went.
- A time-of-use rate or a dynamic-price plan (ask your utility, most offer one).
- A LiFePO4 power station with UPS/passthrough mode, sized to your AC. A 500 W window unit running a 5-hour peak wants roughly 2 kWh usable plus margin.
- A way to switch the battery's input. Simplest is a smart plug: Shelly, Tasmota, or
Kasa (about $25). Batteries that Home Assistant can control work through the
homeassistantbridge driver, EcoFlow through its developer API, and Victron through Modbus TCP. Full matrix in docs/batteries.md. - Something to run PeakShift on: a Raspberry Pi, an old laptop, any always-on machine on the same LAN.
- Keep total draw through a smart plug under 12 A (1,440 W) continuous. A big 15 A window unit does not belong behind a 15 A-rated plug at 100% duty.
- Buy a battery listed to UL 2743 for indoor use. We don't certify anything, check the maker's spec page.
- The battery cycles daily in this setup. LiFePO4 units are rated for 3,000+ cycles, which is 8+ years of summers. NMC units are not, avoid them here.
- PeakShift fails toward grid power. Any error, low battery, or guard trip restores wall power to your AC. Comfort beats savings, always.
For a 2 kWh battery cycled once daily at 85% round-trip efficiency, our mid-2026
estimates by utility land between $65/yr (National Grid MA) and about $280/yr (SDG&E),
with ConEd around $200/yr. Hardware runs $400 to $600 per kWh, so the battery pays
itself back in roughly 4 to 6 years on arbitrage alone in the better territories.
These are estimates from published tariffs, not guarantees. The data/ledger.csv
file tracks what PeakShift actually shifted so you can see your own number.
Utilities and aggregators also run bring-your-own-device demand-response programs that pay for exactly the load reduction PeakShift performs. Enrollment and payment are between you and those programs. PeakShift doesn't enroll you in anything, it just does the physical load shifting.
The default is behind-the-meter load shifting, which needs no interconnection anywhere. If you run a balcony-storage battery with a grid-tie microinverter (Marstek Venus and similar) in a market that allows it, PeakShift can also sell stored power back during the priciest hours. This is opt-in and gated:
export:
enabled: true
region: DE # DE, AT, UK, AU only. US is refused.
max_watts: 800 # capped to your region's legal limit
min_sell_price: 0.20 # only sell above this price
i_confirm_local_rules: true # startup aborts unless this is trueUS export is refused outright, because plug-in export is not permitted under UL 1741 and IEEE 1547. Read docs/regulations.md before enabling this anywhere. Passthrough power stations cannot export and will reject the config.
Most homes, and almost the entire US South, have central AC that's hardwired, so the plug-in battery trick doesn't apply. For those homes PeakShift can store cold instead of electricity: it talks to a smart thermostat (Nest, ecobee, anything via Home Assistant), over-cools the house before the peak, then lets it coast through the peak with the compressor off, using the building's thermal mass as the battery.
thermostat:
driver: homeassistant_climate
params: {base_url: http://homeassistant.local:8123, climate_entity: climate.house}
normal_temp: 74
precool_temp: 70 # bank cold before the peak
coast_temp: 78 # let it drift during the peak
comfort_max_temp: 80 # hard ceiling, never coast past it
precool_lead_minutes: 120
occupancy_entity: binary_sensor.home_occupied # optional
away_max_temp: 84 # ride the peak harder when nobody's homeNo hardware, works nationwide, and it's occupancy-aware: pre-cool gently when people or pets are home, coast harder when the house is empty. Comfort always wins, and any error falls back to normal cooling. Savings are lower per home than a battery, but the addressable market is every central-AC home instead of only window units.
Energy arbitrage saves cents. Capacity and transmission-demand charges are set by a handful of system-peak intervals a year (ERCOT 4CP is four 15-minute intervals a summer, PJM PLC is five hours, ISO-NE ICAP is one hour), and ducking exactly those is worth far more, because the charge is a $/kW tag on a whole year of billing.
Add a capacity block and PeakShift shifts load during the predicted capacity window
even when the energy price isn't spiking (capacity cost isn't in the price):
prices: {provider: comed, mode: threshold, peak_threshold: 0.50}
capacity:
iso: ercot # ercot | pjm | isone | caiso | generic
threshold: 0.7 # call an event above this day-risk
# event_dates: ["2026-07-15"] # or force specific daysThe day-risk model ships as a season/weekday heuristic. The real signal is the ISO's
day-ahead system-load forecast against the running seasonal max, which plugs into one
hook (risk_for_date). At fleet scale, peakshift fleet-plan --return-window 30
staggers each home's return to normal so the fleet doesn't create a rebound peak when
the event ends.
One operator can coordinate many PeakShift homes into a virtual power plant. A utility, an aggregator, or a data center funding batteries for its neighbors can size and plan a fleet from a roster of sites (opaque ids only, no personal data):
peakshift fleet-report --roster sites.json # capex, MW, annual value
peakshift fleet-plan --roster sites.json --shed-kw 100000 --window 16:00-21:00The no-export load-shifting fleet needs no interconnection studies, so it scales at the speed of shipping boxes. An export fleet is regulated at every site.
v0.3. Working and verified live: manual schedules, URDB lookup, seven price feeds,
Shelly/Tasmota drivers, simulate driver, guards, savings ledger, and the fleet
planning and reporting commands. Written but untested on hardware: EcoFlow, Home
Assistant bridge, Kasa, MQTT (covers bluetti_mqtt setups), Victron and Marstek
Modbus, and the grid-export path. If you own any of these, running
peakshift test-drive and filing an issue with the result is the single most
useful contribution right now. Wanted next: Home Assistant add-on packaging and a
real control plane for fleet dispatch (OpenADR / IEEE 2030.5).
MIT.