diff --git a/README.md b/README.md index 0b0e0d93354ca6f49dd5754a72494c3a469c75f6..50878c634959ecb6e92c141d1ea0c28a831a64cf 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,8 @@ dyson_fan_power | enum | all | ON if the fan is powered on, OFF otherwise dyson_auto_mode | enum | all | ON if the fan is in auto mode, OFF otherwise dyson_fan_state | enum | all | FAN, OFF (what the fan is actually doing) dyson_fan_speed_units | gauge | all | 0-10 (or -1 if on AUTO) -dyson_oscillation_mode | enum | all | ON if the fan is oscillating, OFF otherwise +dyson_oscillation_mode | enum | all | ON if the fan in oscillation mode, OFF otherwise +dyson_oscillation_state | enum | all | ON, OFF, IDLE. ON means the fan is currently oscillating, IDLE means the fan is in auto mode and the fan is paused dyson_oscillation_angle_low_degrees | gauge | V2 fans only | low angle of oscillation in degrees dyson_oscillation_angle_high_degrees | gauge | V2 fans only | high angle of oscillation in degrees dyson_night_mode | enum | all | ON if the fan is in night mode, OFF otherwise diff --git a/metrics.py b/metrics.py index 8d3f99747a3fd02661399e0feacc8c2c5f3c2ece..7b1e73377bb16576c1606e7a22f90b1cbe3fc1c2 100644 --- a/metrics.py +++ b/metrics.py @@ -1,5 +1,6 @@ """Creates and maintains Prometheus metric values.""" +import enum import logging from libpurecool import const, dyson_pure_state, dyson_pure_state_v2 @@ -24,6 +25,13 @@ def update_enum(enum, name: str, serial: str, state): enum.labels(name=name, serial=serial).state(state) +class _OscillationState(enum.Enum): + """On V2 devices, oscillation_status can return 'IDLE' in auto mode.""" + ON = 'ON' + OFF = 'OFF' + IDLE = 'IDLE' + + class Metrics: """Registers/exports and updates Prometheus metrics for DysonLink fans.""" @@ -71,7 +79,9 @@ class Metrics: self.fan_speed = make_gauge( 'dyson_fan_speed_units', 'Current speed of fan (-1 = AUTO)') self.oscillation = make_enum( - 'dyson_oscillation_mode', 'Current oscillation mode', const.Oscillation) + 'dyson_oscillation_mode', 'Current oscillation mode (will the fan move?)', const.Oscillation) + self.oscillation_state = make_enum( + 'dyson_oscillation_state', 'Current oscillation state (is the fan moving?)', _OscillationState) self.night_mode = make_enum( 'dyson_night_mode', 'Night mode', const.NightMode) self.heat_mode = make_enum( @@ -188,7 +198,6 @@ class Metrics: self.updatePureCoolStateCommon(name, serial, message) update_enum(self.fan_mode, name, serial, message.fan_mode) - update_enum(self.oscillation, name, serial, message.oscillation) update_gauge(self.quality_target, name, serial, message.quality_target) @@ -203,6 +212,14 @@ class Metrics: update_enum(self.auto_mode, name, serial, auto) update_enum(self.fan_power, name, serial, power) + oscillation_state = message.oscillation + if message.fan_mode == const.FanMode.AUTO.value and message.fan_state == const.FanState.FAN_OFF: + # Compatibility with V2's behaviour for this value. + oscillation_state = _OscillationState.IDLE.value + + update_enum(self.oscillation, name, serial, message.oscillation) + update_enum(self.oscillation_state, name, serial, oscillation_state) + # Convert filter_life from hours to seconds. filter_life = int(message.filter_life) * 60 * 60 update_gauge(self.filter_life, name, serial, filter_life) @@ -228,10 +245,26 @@ class Metrics: update_gauge(self.night_mode_speed, name, serial, int(message.night_mode_speed)) - # V2 provides oscillation_status and oscillation as fields, - # oscillation_status provides values compatible with V1, so we use that. - # oscillation returns as 'OION', 'OIOF.' - update_enum(self.oscillation, name, serial, + # V2 devices differentiate between _current_ oscillation status ('on', 'off', 'idle') + # and configured mode ('on', 'off'). This is roughly the difference between + # "is it oscillating right now" (oscillation_status) and "will it oscillate" (oscillation). + # + # This issue https://github.com/etheralm/libpurecool/issues/4#issuecomment-563358021 + # seems to indicate that oscillation can be one of the OscillationV2 values (OION, OIOF) + # or one of the Oscillation values (ON, OFF) -- so support and translate both. + v2_to_v1_map = { + const.OscillationV2.OSCILLATION_ON.value: const.Oscillation.OSCILLATION_ON.value, + const.OscillationV2.OSCILLATION_OFF.value: const.Oscillation.OSCILLATION_OFF.value, + const.Oscillation.OSCILLATION_ON.value: const.Oscillation.OSCILLATION_ON.value, + const.Oscillation.OSCILLATION_OFF.value: const.Oscillation.OSCILLATION_OFF.value + } + oscillation = v2_to_v1_map.get(message.oscillation, None) + if oscillation: + update_enum(self.oscillation, name, serial, oscillation) + else: + logging.warning('Received unknown oscillation setting from "%s" (serial=%s): %s; ignoring', + name, serial, message.oscillation) + update_enum(self.oscillation_state, name, serial, message.oscillation_status) update_gauge(self.oscillation_angle_low, name, serial, int(message.oscillation_angle_low))