frequenz_microgrid/microgrid/telemetry_tracker/
inverter_battery_group_telemetry_tracker.rs1use std::{collections::HashSet, time::Duration};
11
12use tokio::select;
13
14use crate::{
15 MicrogridClientHandle,
16 client::proto::common::microgrid::electrical_components::ElectricalComponentStateCode,
17 microgrid::telemetry_tracker::battery_pool_telemetry_tracker::InverterBatteryGroup,
18};
19
20use super::component_partition::ComponentHealthPartition;
21use super::component_telemetry_tracker::{ComponentHealthStatus, ComponentTelemetryTracker};
22
23#[derive(Clone)]
34pub(crate) struct InverterBatteryGroupTelemetryTracker {
35 inverter_battery_group: InverterBatteryGroup,
36 status_tx: tokio::sync::mpsc::Sender<(InverterBatteryGroup, InverterBatteryGroupStatus)>,
37 missing_data_tolerance: Duration,
38 healthy_state_codes: HashSet<ElectricalComponentStateCode>,
39 client: MicrogridClientHandle,
40}
41
42#[derive(Clone, Debug, Default, PartialEq)]
46pub struct InverterBatteryGroupStatus {
47 pub inverters: ComponentHealthPartition,
48 pub batteries: ComponentHealthPartition,
49}
50
51impl InverterBatteryGroupTelemetryTracker {
52 pub(crate) fn new(
53 inverter_battery_group: InverterBatteryGroup,
54 missing_data_tolerance: Duration,
55 healthy_state_codes: HashSet<ElectricalComponentStateCode>,
56 client: MicrogridClientHandle,
57 status_tx: tokio::sync::mpsc::Sender<(InverterBatteryGroup, InverterBatteryGroupStatus)>,
58 ) -> Self {
59 Self {
60 inverter_battery_group,
61 status_tx,
62 missing_data_tolerance,
63 healthy_state_codes,
64 client,
65 }
66 }
67
68 pub async fn run(self) {
69 let mut inverters = ComponentHealthPartition::default();
70 let mut batteries = ComponentHealthPartition::default();
71
72 let (inverter_status_tx, mut inverter_status_rx) = tokio::sync::mpsc::channel(100);
73
74 for &inverter_id in &self.inverter_battery_group.inverter_ids {
75 let component_data_stream = match self
76 .client
77 .receive_electrical_component_telemetry_stream(inverter_id)
78 .await
79 {
80 Ok(stream) => stream,
81 Err(e) => {
82 tracing::error!(
83 "Internal error opening telemetry stream for inverter {inverter_id}: {e}; inverter-battery group tracker aborting.",
84 );
85 return;
86 }
87 };
88 let tracker = ComponentTelemetryTracker::new(
89 inverter_id,
90 self.missing_data_tolerance,
91 self.healthy_state_codes.clone(),
92 component_data_stream,
93 inverter_status_tx.clone(),
94 );
95 tokio::spawn(async move {
97 tracker.run().await;
98 });
99 inverters.mark_unhealthy(inverter_id, None);
101 }
102
103 let (battery_status_tx, mut battery_status_rx) = tokio::sync::mpsc::channel(100);
104
105 for &battery_id in &self.inverter_battery_group.battery_ids {
106 let component_data_stream = match self
107 .client
108 .receive_electrical_component_telemetry_stream(battery_id)
109 .await
110 {
111 Ok(stream) => stream,
112 Err(e) => {
113 tracing::error!(
114 "Internal error opening telemetry stream for battery {battery_id}: {e}; inverter-battery group tracker aborting.",
115 );
116 return;
117 }
118 };
119 let tracker = ComponentTelemetryTracker::new(
120 battery_id,
121 self.missing_data_tolerance,
122 self.healthy_state_codes.clone(),
123 component_data_stream,
124 battery_status_tx.clone(),
125 );
126 tokio::spawn(async move {
128 tracker.run().await;
129 });
130 batteries.mark_unhealthy(battery_id, None);
132 }
133
134 drop(inverter_status_tx);
138 drop(battery_status_tx);
139
140 loop {
141 select! {
142 inverter_status = inverter_status_rx.recv() => {
143 let Some(inverter_status) = inverter_status else {
144 tracing::debug!(
147 "Inverter-battery group tracker (inverters {:?}) stopping: all inverter component trackers have exited.",
148 self.inverter_battery_group.inverter_ids
149 );
150 return;
151 };
152 match inverter_status {
153 ComponentHealthStatus::Healthy(component_id, data) => {
154 inverters.mark_healthy(component_id, data);
155 }
156 ComponentHealthStatus::Unhealthy(component_id, data) => {
157 inverters.mark_unhealthy(component_id, data);
158 }
159 }
160 },
161 battery_status = battery_status_rx.recv() => {
162 let Some(battery_status) = battery_status else {
163 tracing::debug!(
166 "Inverter-battery group tracker (batteries {:?}) stopping: all battery component trackers have exited.",
167 self.inverter_battery_group.battery_ids
168 );
169 return;
170 };
171 match battery_status {
172 ComponentHealthStatus::Healthy(component_id, data) => {
173 batteries.mark_healthy(component_id, data);
174 }
175 ComponentHealthStatus::Unhealthy(component_id, data) => {
176 batteries.mark_unhealthy(component_id, data);
177 }
178 }
179 }
180 }
181 if self
182 .status_tx
183 .send((
184 self.inverter_battery_group.clone(),
185 InverterBatteryGroupStatus {
186 inverters: inverters.clone(),
187 batteries: batteries.clone(),
188 },
189 ))
190 .await
191 .is_err()
192 {
193 tracing::debug!(
195 "Inverter-battery group tracker {:?} stopping: the pool tracker dropped its receiver.",
196 self.inverter_battery_group
197 );
198 return;
199 }
200 }
201 }
202}