OzWrapper.OzWindV2.OzWindV2.OzWindV2¶
- class OzWrapper.OzWindV2.OzWindV2.OzWindV2¶
Bases:
SensorBase.SensorBase.GenericSensorV2 wind sensor wrapper for Honguv ultrasonic anemometers.
Subclass of
SensorBase.SensorBase.GenericSensorthat wraps thedrivers.Honguv.Honguv.Honguvdriver and reports three derived quantities to the Oizom gateway:Wind speed (m/s) - vector magnitude of the accumulated samples.
Wind direction (deg, 0-360) - vector-averaged bearing computed via trigonometric decomposition.
Gust - the instantaneous peak captured during the aggregation window (the raw ultrasonic samples preserve short bursts that a mechanical anemometer would smooth out).
Compared to v1 (
OzWrapper.OzWind.OzWind.OzWind) this wrapper swaps the cup-and-vane reading model for the ultrasonic Honguv head and replaces scalar averaging with vector averaging.- Variables:
configuration (
list[dict]) – Per-sensor configuration dicts from the gateway. Each entry carries the part number, enable flag, GPIO/UART settings, and the list of parameters to publish.honguv (
drivers.Honguv.Honguv.Honguv | None) – Honguv driver instance, orNonebeforeinitialize()runs.wind_angle (
list[float]) – Accumulated wind direction samples in degrees, drained on everyputSensorValue()call.wind_speed (
list[float]) – Accumulated wind speed samples in m/s, drained on everyputSensorValue()call.
Example
>>> from OzWrapper.OzWindV2.OzWindV2 import OzWindV2 >>> wrapper = OzWindV2() >>> wrapper.initialize(config_list, {}) >>> wrapper.getSensorReading() >>> wrapper.putSensorValue({})
See also
SensorBase.SensorBase.GenericSensor: Base contract this wrapper implements (partNumber,parameter,baseConfig).OzWrapper.OzWind.OzWind.OzWind: V1 wrapper for the mechanical cup-and-vane sensor.drivers.Wind.Wind.Wind: V1 mechanical wind driver.drivers.Honguv.Honguv.Honguv: Underlying ultrasonic driver used by v2.Initialise the OzWindV2 wrapper with empty state.
Invokes the
SensorBase.SensorBase.GenericSensorconstructor to pick up the basepartNumber,parameter, andbaseConfigdescriptors, then prepares the empty buffers used bygetSensorReading()andputSensorValue().- Parameters:
None.
- Returns:
None. Instance attributes are mutated in place.
- Raises:
Exception – Propagated from
SensorBase.SensorBase.GenericSensor.__init__()if the base class fails to construct.
Example
>>> wrapper = OzWindV2() >>> wrapper.honguv is None True
Note
configurationis initialised to an empty dict here but is overwritten with the gateway’s config list insideinitialize(); callers should not rely on the initial type.- getSensorReading()¶
Sample every initialised wind sensor and accumulate samples.
For each sensor whose init flag is
1the method:Calls
getWind()for every declared parameter.Applies the configured sensitivity (
seas a percentage, divided by 100) and correction offset (cr, divided by 10) so the raw value matches the calibrated short-code value.Appends the corrected sample to
wind_angle(pm == 1) orwind_speed(pm == 2) soputSensorValue()can compute the vector average on flush.Mirrors the latest corrected value into the returned dict so the gateway can show “instantaneous” telemetry alongside the aggregated payload.
- Parameters:
None.
- Returns:
Mapping of parameter short-code (
sc) to the latest corrected reading taken in this cycle. Returns an empty dict when no sensors are initialised.- Return type:
- Raises:
Exception – Per-parameter exceptions are caught and logged; this method does not raise on read failures, only the affected short-code is skipped.
Example
>>> wrapper = OzWindV2() >>> wrapper.getSensorReading() {'wd': 137, 'ws': 4.2}
Note
Sensitivity/correction are applied to the rounded integer direction too, so callers that need the raw bearing should call
getWind()directly instead of relying on this method’s output.
- getWind(partNo: int, pm: int)¶
Read one wind quantity (direction or speed) from the Honguv head.
Dispatches on the part number and parameter id, calls
drivers.Honguv.Honguv.Honguv.getWindData()(with the direction-only flag when only the bearing is needed), and rounds the direction to an integer to keep the cloud payload free of redundant float precision.- Parameters:
- Returns:
Wind direction in degrees (
int,0-360) whenpm == 1, wind speed in m/s (float) whenpm == 2, otherwiseNone.- Return type:
- Raises:
Exception – Propagated from
drivers.Honguv.Honguv.Honguv.getWindData()on Modbus or parsing errors.
Example
>>> wrapper = OzWindV2() >>> wrapper.getWind(214, 1) 123 >>> wrapper.getWind(214, 2) 3.7
Note
Wind direction is rounded with
round()(not truncated) so that, for example,359.6becomes360rather than359; downstream code can then safely apply a% 360wrap if needed.
- initialize(config: dict, init_value)¶
Initialise every Honguv wind sensor declared in the gateway config.
Stores the configuration list, iterates each entry, and delegates the actual hardware bring-up to
initializeSensor(). Any per-sensor exception is caught and recorded asinit = 0so a single faulty unit does not abort the rest of the rig.- Parameters:
config (
list[dict]) – List of sensor configuration dicts from the gateway. The argument is namedconfigfor parity with the base API even though it is iterated as a list.init_value (
dict) – Mutable dict updated in place byputInitvalues()with awindv2key carrying the per-sensor init flags.
- Returns:
Truewhen at least one sensor was matched (the wrapper’s part number was present in some entry), otherwiseFalse. The return value is treated as a “wrapper relevant for this rig” hint by the gateway loader.- Return type:
- Raises:
Exception – Per-sensor exceptions are caught and logged; this method itself does not raise.
Example
>>> wrapper = OzWindV2() >>> status = {} >>> wrapper.initialize(gateway_config, status) True
Note
The wrapper only acts on entries whose
enflag matchesbaseConfig["oz_enable"]and whose part number equals214; all others are silently ignored.
- initializeSensor(sensor: dict)¶
Initialise a single Honguv ultrasonic anemometer.
Builds a
drivers.Honguv.Honguv.Honguvdriver, opens the Modbus RTU port (defaulting to/dev/ttyAMA2at 9600 baud when the config omitsgpiosettings), enables debug mode if requested, and primes each declared parameter with one initial reading so the gateway has a non-empty cache from the first cycle.- Parameters:
sensor (
dict) – Single sensor configuration dict. Expected keys include the part-number field (must equal214),en(must matchbaseConfig["oz_enable"]),parameters(list of parameter dicts), and an optionalgpiosub-dict withport,baud, anddebug.- Returns:
1if the sensor was matched and the driver came up,-1if the part number / enable flag didn’t match or any step failed.- Return type:
- Raises:
Exception – Propagated from
drivers.Honguv.Honguv.Honguv.initialize()orgetWind()if Modbus communication fails; callers ininitialize()catch these.
Example
>>> wrapper = OzWindV2() >>> wrapper.initializeSensor(sensor_cfg) 1
Note
The “TODO: Add gpio configuration” marker in the body is a reminder that GPIO line pinning (e.g. RS-485 DE/RE control) is still handled by the kernel device tree rather than this wrapper.
- putInitvalues(value: dict) dict¶
Record per-sensor init status under the
windv2key.Walks the stored configuration list and copies each entry’s
initflag (1for success,-1for skip/failure,0for caught exception) into a positional list, then attaches it to the gateway’s init-values dict so downstream code can distinguish the v2 wrapper from the v1OzWrapper.OzWind.OzWind.OzWindresults.- Parameters:
value (
dict) – Mutable dict shared across all wrappers; this method adds (or overwrites) thewindv2key.- Returns:
The same dict, now containing
value["windv2"] == [init_flag_for_sensor_0, ...].- Return type:
- Raises:
KeyError – If any configuration entry is missing its
initflag (i.e.initialize()was not run).
Example
>>> wrapper = OzWindV2() >>> wrapper.putInitvalues({}) {'windv2': [1]}
Note
The iteration variable
Xis unused on purpose; the index is kept around in case future revisions want to expose per-slot diagnostics alongside the bare init flag.
- putSensorValue(result: dict) dict¶
Flush accumulated samples into vector-averaged wind values.
For every initialised sensor, writes the aggregated direction (
vector_direction()) under the parameter’spm == 1short-code and the aggregated speed (vector_magnitude()) under thepm == 2short-code. The internalwind_angleandwind_speedbuffers are cleared at the end so the next aggregation window starts empty.- Parameters:
result (
dict) – Mutable output dict in which to publish the averaged values. IfNoneis passed a fresh dict is created and returned.- Returns:
The same dict (or the newly created one) with the
pm == 1/pm == 2short-codes filled in.- Return type:
- Raises:
KeyError – If a parameter dict is missing the
scshort-code key.
Example
>>> wrapper = OzWindV2() >>> payload = wrapper.putSensorValue({}) >>> sorted(payload) ['wd', 'ws']
Note
This method is destructive: after it returns, the sample buffers are empty, so it must be called exactly once per aggregation window. Calling it twice in succession will yield zeros from
vector_direction()andvector_magnitude().
- vector_direction() float¶
Compute the vector-averaged wind direction from buffered samples.
Decomposes each accumulated direction sample into sine and cosine components weighted by the matching speed sample (so calm-period bearings have less influence than gust-period bearings), averages the components, and recovers the bearing via
math.atan2(). Bearings are wrapped into the0-360range. A safety fallback to the arithmetic mean is used when the vector sum cancels exactly to zero.- Parameters:
self.wind_speed. (None. Reads from self.wind_angle and)
- Returns:
Vector-averaged wind direction in degrees rounded to an
int(declared asfloatfor the base contract but actually integer-valued). Returns0.0when no samples have been buffered.- Return type:
- Raises:
ValueError – From
math.radians()/math.atan2()if a sample is non-numeric.
Example
>>> wrapper = OzWindV2() >>> wrapper.wind_angle = [350, 10] >>> wrapper.wind_speed = [3.0, 3.0] >>> wrapper.vector_direction() 0
Note
The method iterates
range(len(u))with an expliciti >= len(v)break so that a mismatch between the two buffers (e.g. one parameter failing to read for a cycle) does not raise anIndexError.
- vector_magnitude() float¶
Compute the vector-averaged wind speed from buffered samples.
Mirrors
vector_direction()but returns the magnitude of the averaged sine/cosine vector rather than its bearing. The magnitude is always less than or equal to the arithmetic mean of the speeds because direction variability cancels part of the wind energy - this is the desired behaviour for reporting a “mean wind” alongside a “gust” peak.- Parameters:
self.wind_speed. (None. Reads from self.wind_angle and)
- Returns:
Vector-averaged wind speed in m/s, rounded to two decimal places. Returns
0.0when no samples have been buffered.- Return type:
- Raises:
ValueError – From
math.radians()/math.sqrt()if a sample is non-numeric or negative under the square root (the latter should be unreachable for real data).
Example
>>> wrapper = OzWindV2() >>> wrapper.wind_angle = [90, 90] >>> wrapper.wind_speed = [5.0, 5.0] >>> wrapper.vector_magnitude() 5.0
Note
The buffers are not cleared here; clearing happens once per cycle inside
putSensorValue()so that bothvector_direction()andvector_magnitude()see the same samples.
- configuration¶
- honguv = None¶
- parameter = 'pm'¶
- partNumber = 'pn'¶
- wind_angle = []¶
- wind_speed = []¶