|
7 | 7 | sensors in a microgrid environment. [`Sensor`][frequenz.client.microgrid.sensor.Sensor]s |
8 | 8 | measure various physical metrics in the surrounding environment, such as temperature, |
9 | 9 | humidity, and solar irradiance. |
| 10 | +
|
| 11 | +# Streaming Sensor Data Samples |
| 12 | +
|
| 13 | +This package also provides several data structures for handling sensor readings |
| 14 | +and states: |
| 15 | +
|
| 16 | +* [`SensorDataSamples`][frequenz.client.microgrid.sensor.SensorDataSamples]: |
| 17 | + Represents a collection of sensor data samples. |
| 18 | +* [`SensorErrorCode`][frequenz.client.microgrid.sensor.SensorErrorCode]: |
| 19 | + Defines error codes that a sensor can report. |
| 20 | +* [`SensorMetric`][frequenz.client.microgrid.sensor.SensorMetric]: Enumerates |
| 21 | + the different metrics a sensor can measure (e.g., temperature, voltage). |
| 22 | +* [`SensorMetricSample`][frequenz.client.microgrid.sensor.SensorMetricSample]: |
| 23 | + Represents a single sample of a sensor metric, including its value and |
| 24 | + timestamp. |
| 25 | +* [`SensorStateCode`][frequenz.client.microgrid.sensor.SensorStateCode]: |
| 26 | + Defines codes representing the operational state of a sensor. |
| 27 | +* [`SensorStateSample`][frequenz.client.microgrid.sensor.SensorStateSample]: |
| 28 | + Represents a single sample of a sensor's state, including its state code |
| 29 | + and timestamp. |
10 | 30 | """ |
11 | 31 |
|
12 | 32 | import dataclasses |
| 33 | +import enum |
| 34 | +from dataclasses import dataclass |
| 35 | +from datetime import datetime |
| 36 | +from typing import assert_never |
13 | 37 |
|
14 | 38 | from ._id import SensorId |
15 | 39 | from ._lifetime import Lifetime |
| 40 | +from .metrics import AggregatedMetricValue, AggregationMethod |
16 | 41 |
|
17 | 42 |
|
18 | 43 | @dataclasses.dataclass(frozen=True, kw_only=True) |
@@ -48,3 +73,170 @@ def __str__(self) -> str: |
48 | 73 | """Return a human-readable string representation of this instance.""" |
49 | 74 | name = f":{self.name}" if self.name else "" |
50 | 75 | return f"<{type(self).__name__}:{self.id}{name}>" |
| 76 | + |
| 77 | + |
| 78 | +@enum.unique |
| 79 | +class SensorMetric(enum.Enum): |
| 80 | + """The metrics that can be reported by sensors in the microgrid. |
| 81 | +
|
| 82 | + These metrics correspond to various sensor readings primarily related to |
| 83 | + environmental conditions and physical measurements. |
| 84 | + """ |
| 85 | + |
| 86 | + UNSPECIFIED = 0 |
| 87 | + """Default value (this should not be normally used and usually indicates an issue).""" |
| 88 | + |
| 89 | + TEMPERATURE = 1 |
| 90 | + """Temperature, in Celsius (°C).""" |
| 91 | + |
| 92 | + HUMIDITY = 2 |
| 93 | + """Humidity, in percentage (%).""" |
| 94 | + |
| 95 | + PRESSURE = 3 |
| 96 | + """Pressure, in Pascal (Pa).""" |
| 97 | + |
| 98 | + IRRADIANCE = 4 |
| 99 | + """Irradiance / Radiation flux, in watts per square meter (W / m²).""" |
| 100 | + |
| 101 | + VELOCITY = 5 |
| 102 | + """Velocity, in meters per second (m / s).""" |
| 103 | + |
| 104 | + ACCELERATION = 6 |
| 105 | + """Acceleration in meters per second per second (m / s²).""" |
| 106 | + |
| 107 | + ANGLE = 7 |
| 108 | + """Angle, in degrees with respect to the (magnetic) North (°).""" |
| 109 | + |
| 110 | + DEW_POINT = 8 |
| 111 | + """Dew point, in Celsius (°C). |
| 112 | +
|
| 113 | + The temperature at which the air becomes saturated with water vapor. |
| 114 | + """ |
| 115 | + |
| 116 | + |
| 117 | +@enum.unique |
| 118 | +class SensorStateCode(enum.Enum): |
| 119 | + """The various states that a sensor can be in.""" |
| 120 | + |
| 121 | + UNSPECIFIED = 0 |
| 122 | + """Default value (this should not be normally used and usually indicates an issue).""" |
| 123 | + |
| 124 | + ON = 1 |
| 125 | + """The sensor is up and running.""" |
| 126 | + |
| 127 | + ERROR = 2 |
| 128 | + """The sensor is in an error state.""" |
| 129 | + |
| 130 | + |
| 131 | +@enum.unique |
| 132 | +class SensorErrorCode(enum.Enum): |
| 133 | + """The various errors that can occur in sensors.""" |
| 134 | + |
| 135 | + UNSPECIFIED = 0 |
| 136 | + """Default value (this should not be normally used and usually indicates an issue).""" |
| 137 | + |
| 138 | + UNKNOWN = 1 |
| 139 | + """An unknown or undefined error. |
| 140 | +
|
| 141 | + This is used when the error can be retrieved from the sensor but it doesn't match |
| 142 | + any known error or can't be interpreted for some reason. |
| 143 | + """ |
| 144 | + |
| 145 | + INTERNAL = 2 |
| 146 | + """An internal error within the sensor.""" |
| 147 | + |
| 148 | + |
| 149 | +@dataclass(frozen=True, kw_only=True) |
| 150 | +class SensorStateSample: |
| 151 | + """A sample of state, warnings, and errors for a sensor at a specific time.""" |
| 152 | + |
| 153 | + sampled_at: datetime |
| 154 | + """The time at which this state was sampled.""" |
| 155 | + |
| 156 | + states: frozenset[SensorStateCode | int] |
| 157 | + """The set of states of the sensor. |
| 158 | +
|
| 159 | + If the reported state is not known by the client (it could happen when using an |
| 160 | + older version of the client with a newer version of the server), it will be |
| 161 | + represented as an `int` and **not** the |
| 162 | + [`SensorStateCode.UNSPECIFIED`][frequenz.client.microgrid.sensor.SensorStateCode.UNSPECIFIED] |
| 163 | + value (this value is used only when the state is not known by the server). |
| 164 | + """ |
| 165 | + |
| 166 | + warnings: frozenset[SensorErrorCode | int] |
| 167 | + """The set of warnings for the sensor.""" |
| 168 | + |
| 169 | + errors: frozenset[SensorErrorCode | int] |
| 170 | + """The set of errors for the sensor. |
| 171 | +
|
| 172 | + This set will only contain errors if the sensor is in an error state. |
| 173 | + """ |
| 174 | + |
| 175 | + |
| 176 | +@dataclass(frozen=True, kw_only=True) |
| 177 | +class SensorMetricSample: |
| 178 | + """A sample of a sensor metric at a specific time. |
| 179 | +
|
| 180 | + This represents a single sample of a specific metric, the value of which is either |
| 181 | + measured at a particular time. |
| 182 | + """ |
| 183 | + |
| 184 | + sampled_at: datetime |
| 185 | + """The moment when the metric was sampled.""" |
| 186 | + |
| 187 | + metric: SensorMetric | int |
| 188 | + """The metric that was sampled.""" |
| 189 | + |
| 190 | + # In the protocol this is float | AggregatedMetricValue, but for live data we can't |
| 191 | + # receive the AggregatedMetricValue, so we limit this to float for now. |
| 192 | + value: float | AggregatedMetricValue | None |
| 193 | + """The value of the sampled metric.""" |
| 194 | + |
| 195 | + def as_single_value( |
| 196 | + self, *, aggregation_method: AggregationMethod = AggregationMethod.AVG |
| 197 | + ) -> float | None: |
| 198 | + """Return the value of this sample as a single value. |
| 199 | +
|
| 200 | + if [`value`][frequenz.client.microgrid.sensor.SensorMetricSample.value] is a `float`, |
| 201 | + it is returned as is. If `value` is an |
| 202 | + [`AggregatedMetricValue`][frequenz.client.microgrid.metrics.AggregatedMetricValue], |
| 203 | + the value is aggregated using the provided `aggregation_method`. |
| 204 | +
|
| 205 | + Args: |
| 206 | + aggregation_method: The method to use to aggregate the value when `value` is |
| 207 | + a `AggregatedMetricValue`. |
| 208 | +
|
| 209 | + Returns: |
| 210 | + The value of the sample as a single value, or `None` if the value is `None`. |
| 211 | + """ |
| 212 | + match self.value: |
| 213 | + case float() | int(): |
| 214 | + return self.value |
| 215 | + case AggregatedMetricValue(): |
| 216 | + match aggregation_method: |
| 217 | + case AggregationMethod.AVG: |
| 218 | + return self.value.avg |
| 219 | + case AggregationMethod.MIN: |
| 220 | + return self.value.min |
| 221 | + case AggregationMethod.MAX: |
| 222 | + return self.value.max |
| 223 | + case unexpected: |
| 224 | + assert_never(unexpected) |
| 225 | + case None: |
| 226 | + return None |
| 227 | + case unexpected: |
| 228 | + assert_never(unexpected) |
| 229 | + |
| 230 | + |
| 231 | +@dataclass(frozen=True, kw_only=True) |
| 232 | +class SensorDataSamples: |
| 233 | + """An aggregate of multiple metrics, states, and errors of a sensor.""" |
| 234 | + |
| 235 | + sensor_id: SensorId |
| 236 | + """The unique identifier of the sensor.""" |
| 237 | + |
| 238 | + metrics: list[SensorMetricSample] |
| 239 | + """The metrics sampled from the sensor.""" |
| 240 | + |
| 241 | + states: list[SensorStateSample] |
| 242 | + """The states sampled from the sensor.""" |
0 commit comments