Skip to content
Merged
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
11 changes: 10 additions & 1 deletion apps/predbat/axle.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,22 @@ class AxleAPI(ComponentBase):

def initialize(self, api_key, pence_per_kwh, automatic):
"""Initialize the AxleAPI component"""
if not isinstance(api_key, str) or not api_key:
self.log("Error: AxleAPI: axle_api_key is missing or invalid, you must set it to a string (not a list or number). Axle Energy integration will not function correctly.")
api_key = None
Comment on lines +32 to +34
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description states that when api_key is a list, the code should "fall back to first element if available" and log "Warn: AxleAPI: Using first element of the list as a fallback.". However, the actual implementation sets api_key = None for all non-string types without any fallback. Either the PR description should be updated to match this simpler behavior, or the code should implement the described fallback. The current simpler approach (rejecting non-strings entirely) is arguably better since it forces users to fix their configuration, but the description is misleading.

Copilot uses AI. Check for mistakes.
self.api_key = api_key
self.pence_per_kwh = pence_per_kwh
self.automatic = automatic
self.failures_total = 0
self.history_loaded = False
self.event_history = [] # List of past events
self.current_event = { # Current event
"start_time": None,
"end_time": None,
"import_export": None,
"pence_per_kwh": None,
}
self.updated_at: None # Last updated moved out to separate attribute to not pollute triggering on change of current_event
self.updated_at = None # Last updated moved out to separate attribute to not pollute triggering on change of current_event

def load_event_history(self):
"""
Expand Down Expand Up @@ -185,6 +189,11 @@ async def fetch_axle_event(self):
"""
Fetch the latest VPP event from Axle Energy API
"""
if not self.api_key:
self.log("Error: AxleAPI: Cannot fetch event - axle_api_key is not set or invalid. Please check your apps.yaml configuration.")
self.failures_total += 1
return

self.log("AxleAPI: Fetching latest VPP event data")

url = "https://api.axle.energy/vpp/home-assistant/event"
Expand Down
22 changes: 22 additions & 0 deletions apps/predbat/tests/test_axle.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ def test_axle(my_predbat=None):
# Sub-test registry - each entry is (key, function, description)
sub_tests = [
("initialization", _test_axle_initialization, "Axle API initialization"),
("list_api_key", _test_axle_list_api_key_validation, "List API key validation and error logging"),
("active_event", _test_axle_fetch_with_active_event, "Fetch with active event"),
("duplicate_event", _test_axle_duplicate_event_detection, "Duplicate event detection"),
("notify_config", _test_axle_fetch_with_notify_config, "Notification config control"),
Expand Down Expand Up @@ -174,6 +175,7 @@ def _test_axle_initialization(my_predbat=None):
assert axle.pence_per_kwh == 150, "Pence per kWh not set correctly"
assert axle.failures_total == 0, "Failures should be 0 on init"
assert axle.last_updated_timestamp is None, "Last updated should be None on init"
assert axle.updated_at is None, "updated_at should be None on init"
assert axle.current_event["start_time"] is None, "Current event should be None on init"
assert axle.event_history == [], "Event history should be empty on init"
assert axle.history_loaded is False, "History should not be loaded on init"
Expand All @@ -182,6 +184,26 @@ def _test_axle_initialization(my_predbat=None):
return False


def _test_axle_list_api_key_validation(my_predbat=None):
"""Test that a list-type API key (incorrect YAML format) is handled with clear error logging"""
print("Test: Axle API list API key validation")

# Case 1: api_key is a non-empty list (most common misconfiguration)
axle = MockAxleAPI()
axle.initialize(api_key=["correct_key_as_list"], pence_per_kwh=100, automatic=False)

assert axle.api_key is None, "Should failed to allow a list as an API key"

# Case 2: api_key is an empty list
axle2 = MockAxleAPI()
axle2.initialize(api_key=[], pence_per_kwh=100, automatic=False)

assert axle2.api_key is None, "Empty list should result in None api_key"

print(" ✓ List API key validation works correctly")
return False


def _test_axle_fetch_with_active_event(my_predbat=None):
"""Test fetching an active VPP event from API"""
print("Test: Axle API fetch with active event")
Expand Down