Skip to content

Release Notes v1.0.7

Latest

Choose a tag to compare

@cheapest-energy-windows cheapest-energy-windows released this 10 Dec 10:22

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 strategy
  • already_included: Export full power
  • subtract_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 usage
  • total_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, attributes
  • config_flow.py - Added to costs step
  • coordinator.py - Configuration loading
  • number.py - Base usage number entity
  • select.py - 4 strategy select entities
  • calculation_engine.py - Strategy-based cost calculations
  • sensor.py - Expose new attributes
  • dashboard.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:

  1. Added constant in const.py (line 113):

    ATTR_PLANNED_TOTAL_COST: Final = "planned_total_cost"
  2. Calculation logic in calculation_engine.py (after line 920):

    • Calculates total cost for ALL windows (charge + base usage - discharge revenue)
    • Unlike total_cost which 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
  3. 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

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 constant
  • calculation_engine.py - Implemented planned_total_cost calculation
  • sensor.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.yaml
  • cheapest_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:

  1. Added VERSION constant in const.py:

    VERSION: Final = "1.0.7"
  2. Updated all entity files to import and use VERSION:

    • __init__.py - Device registry
    • sensor.py - 3 sensor classes
    • number.py - Number entities
    • select.py - Select entities
    • switch.py - Switch entities
    • time.py - Time entities
    • text.py - Text entities
  3. 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 constant
  • manifest.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:

  1. 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..."
  2. Daily Cost Cards (dashboard.yaml):

    • Title Change: "Daily Net Cost" → "Daily Cost"
    • Multi-line Display: Added multiline_secondary: true property
    • 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)
  3. 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
  4. 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

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.yaml
  • cheapest_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/...

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:

  1. Config Flow (config_flow.py):

    • Removed base_usage fields from async_step_costs
    • Added new async_step_base_usage step between costs → power
    • Uses SelectSelector with descriptive option labels
  2. Translations (translations/en.json, strings.json):

    • Added base_usage step with title, data labels, descriptions
    • Added entity.select translations for option names in HA UI
    • Added selector translations for config flow dropdowns
  3. Entity Names (select.py):

    • Updated friendly names: "Base Usage - Charge Strategy" → "Base Usage: During Charging"
    • Added _attr_translation_key for 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_covers to battery_covers

Files Modified: 5 files

  • config_flow.py - New step, removed from costs
  • const.py - Updated default
  • select.py - Updated names, added translation_key
  • translations/en.json - New step + entity translations
  • strings.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)
    continue

This bypass meant:

  1. Low price spread → No charge windows selected (correct)
  2. No charge windows → Bypass activates for discharge selection
  3. 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_difference is 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