Changelog - v1.0.7
✨ New Features
Base Usage Tracking with Configurable Strategies
Feature: Track constant household power consumption (base load) for accurate 24/7 cost calculations.
Problem Solved:
Previous cost tracking only accounted for battery charge/discharge power. Household base consumption (appliances, lights, etc.) was not included, resulting in incomplete cost/revenue tracking.
Solution Implemented:
Added configurable base usage feature with strategy-based calculations for each battery state.
Configuration
- Base Usage (W): Average household power consumption in Watts (default: 0)
- 4 Strategy Selects: Configure how base load is handled during each battery state
Strategy Options
1. Charge Strategy (how to handle base load during charging):
grid_covers_both(default): Grid provides charge power + base usage- Cost = (charge_power + base_usage) × duration × price
battery_covers_base: Battery covers base load during charging (NoM)- Grid only charges battery, house runs from battery
- Net charge = charge_power - base_usage
2. Idle Strategy (when battery is not charging or discharging):
grid_covers(default): Grid provides base usage- Cost = base_usage × duration × price
battery_covers: Battery covers base load (NoM - Nul op de Meter)- No grid cost, battery usage tracked
3. Discharge Strategy (during battery discharge):
subtract_base(default): Battery covers base first, exports rest (NoM)- Revenue = (discharge_power - base_usage) × duration × price
- Battery energy = discharge_power × duration
already_included: Discharge power already includes base coverage- Revenue = discharge_power × duration × price
4. Aggressive Discharge Strategy (during aggressive discharge):
same_as_discharge(default): Follow discharge strategyalready_included: Export full powersubtract_base: Export net after base coverage (NoM)
New Sensor Attributes
Added to sensor.cew_today:
completed_base_usage_cost: Grid cost for base usage (EUR)completed_base_usage_battery: Battery kWh used for base usagetotal_cost: Combined cost (charge + base - revenue)
Dashboard Updates
Price Calculation Settings Section:
- Base Usage (W) number input
- 4 strategy select dropdowns with visual hierarchy
Current Cost Card (new):
- Real-time accumulated total cost display
- Updates as windows complete throughout the day
- Color-coded: Green (profit), Red (cost), Grey (neutral)
- Located in Today's Energy Windows horizontal stack
Use Cases
Example 1: Standard Grid Usage
- Base Usage: 500W
- Charge Strategy:
grid_covers_both - Idle Strategy:
grid_covers - Discharge Strategy:
already_included - Result: All consumption from grid, battery only for arbitrage
Example 2: NoM (Zero on Meter) Strategy
- Base Usage: 1000W
- Charge Strategy:
battery_covers_base - Idle Strategy:
battery_covers - Discharge Strategy:
subtract_base - Result: Battery covers all household consumption, minimize grid usage
Example 3: Hybrid Strategy
- Base Usage: 750W
- Charge Strategy:
grid_covers_both(cheap charging periods) - Idle Strategy:
battery_covers(minimize daytime grid usage) - Discharge Strategy:
subtract_base(export surplus during peaks) - Result: Optimized balance between grid and battery usage
Technical Details
Files Modified: 8 files
const.py- Configuration constants, defaults, strategy options, attributesconfig_flow.py- Added to costs stepcoordinator.py- Configuration loadingnumber.py- Base usage number entityselect.py- 4 strategy select entitiescalculation_engine.py- Strategy-based cost calculationssensor.py- Expose new attributesdashboard.yaml- Settings section + Current Cost card
Backward Compatibility:
- Default base_usage = 0 (no impact on existing installations)
- When base_usage = 0, all calculations = 0 (identical to previous behavior)
- Optional feature, fully backward compatible
Implementation: Lines 846-920 in calculation_engine.py
- Separate strategy application for charge/discharge/idle periods
- Tracks both grid costs and battery usage
- Handles edge cases (discharge_power < base_usage)
Planned Total Cost Calculation
Feature: Added planned_total_cost attribute to provide estimated total daily cost for all planned windows (not just completed ones).
Problem Solved:
The existing total_cost attribute only tracks completed windows, making it difficult for users to see projected total cost for the day. This enhancement provides both current and planned cost side-by-side.
Implementation:
-
Added constant in
const.py(line 113):ATTR_PLANNED_TOTAL_COST: Final = "planned_total_cost"
-
Calculation logic in
calculation_engine.py(after line 920):- Calculates total cost for ALL windows (charge + base usage - discharge revenue)
- Unlike
total_costwhich only counts completed windows, this estimates the full day - Respects all base usage strategies for accurate cost projection
- Added to result dict at line 983 and
_empty_result()at line 1026
-
Sensor exposure in
sensor.py:- Line 49: Added import for
ATTR_PLANNED_TOTAL_COST - Line 352: Exposed in
CEWTodaySensor._build_attributes - Line 495: Exposed in
CEWTomorrowSensor._build_attributes
- Line 49: Added import for
Impact:
- Users can now see both current cost (completed) and planned cost (projected total) for the day
- Better budget planning and energy usage insights
- Dashboard displays: "€8.80/€15.50" showing current vs. planned costs
Files Modified: 3 files
const.py- Added ATTR_PLANNED_TOTAL_COST constantcalculation_engine.py- Implemented planned_total_cost calculationsensor.py- Exposed attribute in both Today and Tomorrow sensors
Contextual Price Cards
Enhancement: Price cards now show contextual information based on whether windows are selected.
Problem:
When no charge/discharge windows were configured, the Cheap Avg, Expensive Avg, and Spread cards showed €0.00/0% with no context, confusing users about what the values represented.
Solution:
Cards now dynamically display either selected window data or day-wide statistics:
| Card | Windows Selected | No Windows |
|---|---|---|
| Cheap | "Cheap (6)" - avg of 6 selected windows | "Cheap Avg (Day)" - avg of cheapest 50% of day |
| Expensive | "Expensive (3)" - avg of 3 selected windows | "Expensive Avg (Day)" - avg of most expensive 50% of day |
| Spread | "Spread" - spread between selected windows | "Spread (Day)" - spread across entire day |
| Average | "Average (Day)" | "Average (Day)" |
Day Calculations:
- Cheap Avg (Day): Average of the bottom 50% of prices (sorted ascending)
- Expensive Avg (Day): Average of the top 50% of prices (sorted ascending)
- Spread (Day): Percentage difference between adjusted min and max prices
- All day values include Tax, VAT, and Additional Cost adjustments
Cards Updated:
- Today's section: Average, Cheap, Expensive, Spread (4 cards)
- Tomorrow's section: Average, Cheap, Expensive, Spread (4 cards)
Files Modified: 2 files
cheapest_energy_windows_dashboard_yaml/dashboard.yamlcheapest_energy_windows_dashboard/cew-dashboard-strategy.js(regenerated)
🔧 Improvements
Refactored Version Management
Issue: Version numbers were hardcoded in 9 different files (11 locations total), requiring manual updates across the entire codebase for each release.
Previous State:
- Integration version:
manifest.json(1.0.4) - Device firmware: Hardcoded "1.0.0" in 8 entity files
- Problem: Inconsistent versions shown in UI and error-prone release process
Solution Implemented:
Created a single source of truth for version management:
-
Added VERSION constant in
const.py:VERSION: Final = "1.0.7"
-
Updated all entity files to import and use VERSION:
__init__.py- Device registrysensor.py- 3 sensor classesnumber.py- Number entitiesselect.py- Select entitiesswitch.py- Switch entitiestime.py- Time entitiestext.py- Text entities
-
Updated manifest.json to 1.0.7
Impact:
- ✅ All version numbers now synchronized (Integration: 1.0.7, Device Firmware: 1.0.7)
- ✅ Future releases only require updating 2 files (
const.py+manifest.json) - ✅ Eliminates version mismatch issues
- ✅ Reduces maintenance burden and prevents errors
Files Modified: 9 files
const.py- Added VERSION constantmanifest.json- Updated to 1.0.7- 8 entity platform files - Now import and use VERSION
Dashboard Improvements for Cost Tracking
Enhancement: Consolidated and improved cost display throughout the dashboard.
Changes Made:
-
Base Usage Information Cards (dashboard.yaml):
- Split single long info card into two separate cards for better readability
- Card 1: "Base Usage Feature" - Explains what it tracks
- Card 2: "How to Configure" - Configuration guidance
- Fixes truncation issue where text was cut off at "et..."
-
Daily Cost Cards (dashboard.yaml):
- Title Change: "Daily Net Cost" → "Daily Cost"
- Multi-line Display: Added
multiline_secondary: trueproperty - Format Enhancement:
- Line 1: Current/Planned Cost (e.g., "€6.19/€0.98")
- Line 2: Window Progress (e.g., "30/53")
- Applied to both Today (lines 479-518) and Tomorrow (lines 1167-1194)
-
Cost Tracking Section Consolidation (dashboard.yaml):
- Removed redundant Cost Tracking section (~54 lines)
- When base_usage=0, it showed identical values to Daily Cost cards
- Reduced complexity and improved clarity
-
Template Robustness Improvements (dashboard.yaml):
- Fixed Jinja2 default filter: Changed from
or []to| default([], true) - Fixed YAML block scalars: Changed from
>-(folded) to|(literal) for line breaks - Ensures proper handling of None/missing attribute values
- Fixed Jinja2 default filter: Changed from
Impact:
- Clearer visual organization with readable info cards
- Better cost visibility with multi-line format (no truncation)
- Eliminates redundancy (removed ~220 lines of complex template calculations)
- More robust templates handle edge cases gracefully
Files Modified: 2 files
cheapest_energy_windows_dashboard_yaml/dashboard.yamlcheapest_energy_windows_dashboard/cew-dashboard-strategy.js(regenerated)
Updated GitHub Organization References
Issue: References to GitHub organization used old naming convention (cew-hacs), causing HACS to display outdated repository information.
Changes Made:
Integration Files:
- File:
custom_components/cheapest_energy_windows/manifest.json- Line 5: Updated
codeowners:@cew-hacs→@cheapest-energy-windows - Line 8: Updated
documentation:github.com/cew-hacs/...→github.com/cheapest-energy-windows/... - Line 11: Updated
issue_tracker:github.com/cew-hacs/...→github.com/cheapest-energy-windows/...
- Line 5: Updated
Dashboard Files:
- File:
cheapest_energy_windows_dashboard/info.md- Line 10: Updated integration link to new organization URL
Impact:
- HACS will display correct repository organization name after cache refresh
- Documentation and issue tracker links point to correct GitHub location
- Proper attribution to the current GitHub organization
Note: GitHub automatically redirects old URLs, so existing installations continue to work seamlessly.
🎨 UX Improvements
Dedicated Base Usage Setup Page
Enhancement: Moved base usage configuration from cramped "Cost Parameters" page to its own dedicated setup step.
Problem:
- Base usage (1 field) + 4 strategy dropdowns were crammed into the Cost Parameters page
- Users couldn't easily understand the feature during setup
- No explanations for what each strategy means
Solution:
Created new async_step_base_usage config flow step with:
- Clear title: "Base Usage Tracking"
- Comprehensive explanation of what base usage is and why to configure it
- Descriptive strategy labels instead of technical names
- Visual hierarchy with strategy grouping
Changes Made:
-
Config Flow (
config_flow.py):- Removed base_usage fields from
async_step_costs - Added new
async_step_base_usagestep between costs → power - Uses
SelectSelectorwith descriptive option labels
- Removed base_usage fields from
-
Translations (
translations/en.json,strings.json):- Added
base_usagestep with title, data labels, descriptions - Added
entity.selecttranslations for option names in HA UI - Added
selectortranslations for config flow dropdowns
- Added
-
Entity Names (
select.py):- Updated friendly names: "Base Usage - Charge Strategy" → "Base Usage: During Charging"
- Added
_attr_translation_keyfor HA native option translations
Strategy Option Names:
| Technical | User-Friendly |
|---|---|
grid_covers_both |
Grid powers house + charging |
battery_covers_base |
Battery powers house during charging |
grid_covers |
Grid powers house |
battery_covers |
Battery powers house |
subtract_base |
House first, export remainder |
already_included |
Export full discharge power |
same_as_discharge |
Same as discharge strategy |
Default Change:
- Idle strategy default changed from
grid_coverstobattery_covers
Files Modified: 5 files
config_flow.py- New step, removed from costsconst.py- Updated defaultselect.py- Updated names, added translation_keytranslations/en.json- New step + entity translationsstrings.json- Mirror of en.json
🐛 Bug Fixes
Fixed: Discharge Windows Selected Despite Insufficient Price Spread
Issue: #5 - Low price but still discharge?
Problem:
The system was scheduling discharge windows even when the price spread was below the configured min_price_difference threshold.
Example Scenario:
- User configured:
min_price_difference = 0.09 EUR/kWh - Actual max price spread:
0.06 EUR/kWh - Expected: No charging AND no discharging (spread too low to justify arbitrage)
- Actual: System planned discharge windows
Root Cause:
In calculation_engine.py lines 529-532, when no charge windows were selected (because price spread < min_price_difference), the discharge window selection logic had a bypass that skipped validation:
# Bug: Skip validation when no charge windows exist
if not charge_windows:
selected.append(candidate)
continueThis bypass meant:
- Low price spread → No charge windows selected (correct)
- No charge windows → Bypass activates for discharge selection
- Discharge windows added WITHOUT checking min_price_difference (incorrect)
Fix Applied:
- File:
custom_components/cheapest_energy_windows/calculation_engine.py - Location: Lines 529-532 in
_find_discharge_windows()function - Change: Removed the bypass logic
- Result:
min_price_differenceis now always enforced for discharge window selection, regardless of whether charge windows exist
Impact:
- Discharge windows are only selected when price spread justifies the arbitrage strategy
- Battery cycling only occurs when the price difference meets the configured minimum threshold
- Fixes reported issue where discharge was scheduled despite insufficient price spread
Design Clarification:
The min_price_difference parameter is intended for the entire arbitrage strategy, not just charging:
- Ensures price spread justifies battery wear and conversion losses
- If spread too low → no charging AND no discharging
- Both charge and discharge decisions respect this threshold
📝 Notes
Tax/VAT/Additional Costs Handling
During investigation, it was confirmed that tax, VAT, and additional costs are correctly accounted for in all price difference calculations:
- Location:
calculation_engine.py:295-343 - Formula:
total_price = (base_price * (1 + vat)) + tax + additional_cost - All price comparisons use these fully-loaded prices
- No changes needed in this area