9595
9696_HOLD_NOT_EVALUATED = object ()
9797
98+ # The behaviour an AlphaESSCloud inverter has, stated for the discovery record's capabilities. A
99+ # literal, never read back from INVERTER_DEF: the record has to rebuild the row on its own, or the
100+ # completeness test proves nothing. support_feedin_first is False because the row leaves it out and
101+ # inverter.py defaults it to False; there is no feed-in-first mode in the Open API.
102+ ALPHAESS_CAPABILITIES = {
103+ "support_charge_freeze" : True ,
104+ "support_discharge_freeze" : True ,
105+ "support_feedin_first" : False ,
106+ "can_span_midnight" : False ,
107+ "charge_discharge_with_rate" : False ,
108+ "charge_control_immediate" : False ,
109+ "target_soc_used_for_discharge" : True ,
110+ }
111+
98112
99113class AlphaESSAPI (ComponentBase ):
100114 """AlphaESS Open API cloud component."""
@@ -866,28 +880,79 @@ async def automatic_config(self):
866880
867881 await self .apply_hybrid_verdict ()
868882
883+ def _discovery_entities (self , sn ):
884+ """The settings automatic_config() binds for one system, as discovery entity descriptors.
885+
886+ Each entity id is formed with the same _sensor_name()/_control_name() call automatic_config()
887+ makes, so the two cannot drift apart. The record describes this system alone (spec D10): a
888+ setting automatic_config() binds only when a figure is known - an energy counter, the
889+ capacity, poinv - is described whenever this system reports it, although automatic_config()
890+ binds it only once every system does. pv_power and pv_today are described even under
891+ automatic_ignore_pv, which is the user's opt-out rather than a fact about the device (D11).
892+ The three *_power_invert settings are always False, which is the descriptor's default, so no
893+ descriptor carries invert. car_charging_* belong to the chargers and cars sections, and
894+ export_limit and battery_min_soc are never bound (see automatic_config()).
895+ """
896+ entities = {
897+ "soc_percent" : {"entity_id" : self ._sensor_name (sn , "soc" ), "access" : "r" , "unit" : "%" },
898+ "battery_power" : {"entity_id" : self ._sensor_name (sn , "battery_power" ), "access" : "r" , "unit" : "W" },
899+ "grid_power" : {"entity_id" : self ._sensor_name (sn , "grid_power" ), "access" : "r" , "unit" : "W" },
900+ "load_power" : {"entity_id" : self ._sensor_name (sn , "load_power" ), "access" : "r" , "unit" : "W" },
901+ "pv_power" : {"entity_id" : self ._sensor_name (sn , "pv_power" ), "access" : "r" , "unit" : "W" },
902+ }
903+ energy = self .device_energy .get (sn , {})
904+ for leaf in ("load_today" , "import_today" , "export_today" , "pv_today" ):
905+ if leaf in energy :
906+ entities [leaf ] = {"entity_id" : self ._sensor_name (sn , leaf ), "access" : "r" , "unit" : "kWh" }
907+ if self .battery_capacity (sn ) > 0 :
908+ entities ["soc_max" ] = {"entity_id" : self ._sensor_name (sn , "battery_capacity" ), "access" : "r" , "unit" : "kWh" }
909+ if self .inverter_limit (sn ) > 0 :
910+ entities ["inverter_limit" ] = {"entity_id" : self ._sensor_name (sn , "inverter_limit" ), "access" : "r" , "unit" : "W" }
911+ if self .battery_rate_max (sn ) > 0 :
912+ entities ["battery_rate_max" ] = {"entity_id" : self ._sensor_name (sn , "battery_rate_max" ), "access" : "r" , "unit" : "W" }
913+
914+ entities ["reserve" ] = {"entity_id" : self ._control_name ("number" , sn , "battery_schedule_reserve" ), "access" : "rw" , "unit" : "%" }
915+ for direction , prefix in (("charge" , "charge" ), ("export" , "discharge" )):
916+ entities ["{}_start_time" .format (prefix )] = {"entity_id" : self ._control_name ("select" , sn , "battery_schedule_{}_start_time" .format (direction )), "access" : "rw" , "domain" : "select" , "format" : "HH:MM:SS" }
917+ entities ["{}_end_time" .format (prefix )] = {"entity_id" : self ._control_name ("select" , sn , "battery_schedule_{}_end_time" .format (direction )), "access" : "rw" , "domain" : "select" , "format" : "HH:MM:SS" }
918+ entities ["scheduled_{}_enable" .format (prefix )] = {"entity_id" : self ._control_name ("switch" , sn , "battery_schedule_{}_enable" .format (direction )), "access" : "rw" , "domain" : "switch" }
919+ entities ["charge_limit" ] = {"entity_id" : self ._control_name ("number" , sn , "battery_schedule_charge_soc" ), "access" : "rw" , "unit" : "%" }
920+ entities ["charge_rate" ] = {"entity_id" : self ._control_name ("number" , sn , "battery_schedule_charge_power" ), "access" : "rw" , "unit" : "W" , "step" : 100 }
921+ entities ["discharge_target_soc" ] = {"entity_id" : self ._control_name ("number" , sn , "battery_schedule_export_soc" ), "access" : "rw" , "unit" : "%" }
922+ entities ["discharge_rate" ] = {"entity_id" : self ._control_name ("number" , sn , "battery_schedule_export_power" ), "access" : "rw" , "unit" : "W" , "step" : 100 }
923+ entities ["schedule_write_button" ] = {"entity_id" : self ._control_name ("switch" , sn , "battery_schedule_charge_write" ), "access" : "rw" , "domain" : "switch" }
924+ return entities
925+
869926 def build_discovery (self ):
870927 """
871928 Describe the discovered AlphaESS systems for the discovery catalogue.
872929
873930 Reads only what get_device_list() already holds - self.device_list and self.device_detail -
874- plus the EV-charger verdict _apply_live_payload() records, so this adds no API calls and
875- cannot change what AlphaESS does. Reporting is independent of self.automatic: the catalogue
876- records the hardware, and the report's own "automatic" flag says whether Predbat wired
877- apps.yaml to it.
931+ plus the EV-charger verdict _apply_live_payload() records and the energy counters already
932+ read, so this adds no API calls and cannot change what AlphaESS does. Reporting is
933+ independent of self.automatic: the catalogue records the hardware, and the report's own
934+ "automatic" flag says whether Predbat wired apps.yaml to it.
878935
879936 self.device_list holds serial strings (sysSn), and only systems that passed has_battery()
880937 at discovery: a battery-less system (the VT1000 family, cobat 0 or missing) is dropped in
881938 get_device_list() and never reaches device_detail. automatic_config() applies no further
882939 test - it registers every serial in device_list as "AlphaESSCloud" - so inverter_type is set
883940 on every record, mirroring automatic_config() as the source of truth.
884941
885- Ratings are getEssList's own figures: poinv (kW) as inverter_w via inverter_limit(), popv
886- (kW) as pv_w, cobat (kWh) as battery_kwh via battery_capacity(). cobat is one scalar per
887- system, so there is no list of entries to mis-sum. A system reports "solar" only when popv
888- says PV is attached.
942+ capabilities is the ALPHAESS_CAPABILITIES constant and entities is _discovery_entities(sn):
943+ together they rebuild the AlphaESSCloud INVERTER_DEF row, and entities holds every setting
944+ automatic_config() binds for the system.
945+
946+ Ratings are getEssList's own figures under their Predbat setting names: poinv (kW) as
947+ inverter_limit (W) via inverter_limit(), cobat (kWh) as soc_max via battery_capacity(), and
948+ popv (kW) as the descriptive pv_w. cobat is one scalar per system, so there is no list of
949+ entries to mis-sum. A system reports "solar" only when popv says PV is attached.
889950
890951 Deliberately not reported:
952+ - export_limit: AlphaESS reports no export power limit, and poinv is the inverter rating,
953+ not the grid connection's cap (automatic_config() warns about this).
954+ - battery_rate_max as a rating: the API reports no battery power limit; battery_rate_max()
955+ is poinv or the user's override, so only its entity binding is described.
891956 - emsStatus: a status that changes. refresh_discovery() compares whole reports, so a
892957 changing value would re-file the report every time it moved.
893958 - usCapacity and surplusCobat: they fit "current SoC" and "configured usable depth" equally
@@ -926,12 +991,12 @@ def build_discovery(self):
926991 ratings = {}
927992 inverter_w = self .inverter_limit (sn )
928993 if inverter_w > 0 :
929- ratings ["inverter_w " ] = inverter_w
994+ ratings ["inverter_limit " ] = inverter_w
930995 if pv_kw > 0 :
931996 ratings ["pv_w" ] = pv_kw * 1000.0
932997 battery_kwh = self .battery_capacity (sn )
933998 if battery_kwh > 0 :
934- ratings ["battery_kwh " ] = battery_kwh
999+ ratings ["soc_max " ] = battery_kwh
9351000
9361001 flags = ["ev_charger" ] if self ._ev_present .get (sn ) is True else []
9371002
@@ -941,11 +1006,12 @@ def build_discovery(self):
9411006 inverter_type = "AlphaESSCloud" ,
9421007 composition = "direct" ,
9431008 functions = functions ,
944- capabilities = [ "schedule" , "target_soc" , "discharge_target" , "charge_rate_power" ] ,
1009+ capabilities = dict ( ALPHAESS_CAPABILITIES ) ,
9451010 flags = flags ,
9461011 hardware_ids = {"serial" : sn },
9471012 info = info ,
9481013 ratings = ratings ,
1014+ entities = self ._discovery_entities (sn ),
9491015 )
9501016 )
9511017
0 commit comments