from pathlib import Path from unittest.mock import Mock import pytest from afancontrol.arduino import ( ArduinoConnection, ArduinoName, ArduinoPin, pyserial_available, ) from afancontrol.config import ( Actions, AlertCommands, DaemonCLIConfig, DaemonConfig, FanName, FanSpeedModifier, FansTempsRelation, FilteredTemp, MappingName, ParsedConfig, ReadonlyFanName, TempName, TriggerConfig, parse_config, ) from afancontrol.filters import MovingMedianFilter, NullFilter from afancontrol.pwmfan import ( ArduinoFanPWMRead, ArduinoFanPWMWrite, ArduinoFanSpeed, FanInputDevice, LinuxFanPWMRead, LinuxFanPWMWrite, LinuxFanSpeed, PWMDevice, PWMValue, ) from afancontrol.pwmfannorm import PWMFanNorm, ReadonlyPWMFanNorm from afancontrol.temp import FileTemp, HDDTemp, TempCelsius @pytest.fixture def pkg_conf(): return Path(__file__).parents[1] / "pkg" / "afancontrol.conf" @pytest.fixture def example_conf(): return Path(__file__).parents[0] / "data" / "afancontrol-example.conf" def path_from_str(contents: str) -> Path: p = Mock(spec=Path) p.read_text.return_value = contents return p @pytest.mark.skipif(not pyserial_available, reason="pyserial is not installed") def test_pkg_conf(pkg_conf: Path): daemon_cli_config = DaemonCLIConfig( pidfile=None, logfile=None, exporter_listen_host=None ) parsed = parse_config(pkg_conf, daemon_cli_config) assert parsed == ParsedConfig( arduino_connections={}, daemon=DaemonConfig( pidfile="/run/afancontrol.pid", logfile="/var/log/afancontrol.log", interval=5, exporter_listen_host=None, ), report_cmd=( 'printf "Subject: %s\nTo: %s\n\n%b" ' '"afancontrol daemon report: %REASON%" root "%MESSAGE%" | sendmail -t' ), triggers=TriggerConfig( global_commands=Actions( panic=AlertCommands(enter_cmd=None, leave_cmd=None), threshold=AlertCommands(enter_cmd=None, leave_cmd=None), ), temp_commands={ TempName("mobo"): Actions( panic=AlertCommands(enter_cmd=None, leave_cmd=None), threshold=AlertCommands(enter_cmd=None, leave_cmd=None), ) }, ), fans={ FanName("hdd"): PWMFanNorm( fan_speed=LinuxFanSpeed( FanInputDevice("/sys/class/hwmon/hwmon0/device/fan2_input") ), pwm_read=LinuxFanPWMRead( PWMDevice("/sys/class/hwmon/hwmon0/device/pwm2") ), pwm_write=LinuxFanPWMWrite( PWMDevice("/sys/class/hwmon/hwmon0/device/pwm2") ), pwm_line_start=PWMValue(100), pwm_line_end=PWMValue(240), never_stop=False, ) }, readonly_fans={ ReadonlyFanName("cpu"): ReadonlyPWMFanNorm( fan_speed=LinuxFanSpeed( FanInputDevice("/sys/class/hwmon/hwmon0/device/fan1_input") ), ), }, temps={ TempName("mobo"): FilteredTemp( temp=FileTemp( "/sys/class/hwmon/hwmon0/device/temp1_input", min=TempCelsius(30.0), max=TempCelsius(40.0), panic=None, threshold=None, ), filter=MovingMedianFilter(window_size=3), ) }, mappings={ MappingName("1"): FansTempsRelation( temps=[TempName("mobo")], fans=[FanSpeedModifier(fan=FanName("hdd"), modifier=0.6)], ) }, ) @pytest.mark.skipif(not pyserial_available, reason="pyserial is not installed") def test_example_conf(example_conf: Path): daemon_cli_config = DaemonCLIConfig( pidfile=None, logfile=None, exporter_listen_host=None ) parsed = parse_config(example_conf, daemon_cli_config) assert parsed == ParsedConfig( arduino_connections={ ArduinoName("mymicro"): ArduinoConnection( ArduinoName("mymicro"), "/dev/ttyACM0", baudrate=115200, status_ttl=5 ) }, daemon=DaemonConfig( pidfile="/run/afancontrol.pid", logfile="/var/log/afancontrol.log", exporter_listen_host="127.0.0.1:8083", interval=5, ), report_cmd=( 'printf "Subject: %s\nTo: %s\n\n%b" ' '"afancontrol daemon report: %REASON%" root "%MESSAGE%" | sendmail -t' ), triggers=TriggerConfig( global_commands=Actions( panic=AlertCommands(enter_cmd=None, leave_cmd=None), threshold=AlertCommands(enter_cmd=None, leave_cmd=None), ), temp_commands={ TempName("hdds"): Actions( panic=AlertCommands(enter_cmd=None, leave_cmd=None), threshold=AlertCommands(enter_cmd=None, leave_cmd=None), ), TempName("mobo"): Actions( panic=AlertCommands(enter_cmd=None, leave_cmd=None), threshold=AlertCommands(enter_cmd=None, leave_cmd=None), ), }, ), fans={ FanName("cpu"): PWMFanNorm( fan_speed=LinuxFanSpeed( FanInputDevice("/sys/class/hwmon/hwmon0/device/fan1_input") ), pwm_read=LinuxFanPWMRead( PWMDevice("/sys/class/hwmon/hwmon0/device/pwm1") ), pwm_write=LinuxFanPWMWrite( PWMDevice("/sys/class/hwmon/hwmon0/device/pwm1") ), pwm_line_start=PWMValue(100), pwm_line_end=PWMValue(240), never_stop=True, ), FanName("hdd"): PWMFanNorm( fan_speed=LinuxFanSpeed( FanInputDevice("/sys/class/hwmon/hwmon0/device/fan2_input") ), pwm_read=LinuxFanPWMRead( PWMDevice("/sys/class/hwmon/hwmon0/device/pwm2") ), pwm_write=LinuxFanPWMWrite( PWMDevice("/sys/class/hwmon/hwmon0/device/pwm2") ), pwm_line_start=PWMValue(100), pwm_line_end=PWMValue(240), never_stop=False, ), FanName("my_arduino_fan"): PWMFanNorm( fan_speed=ArduinoFanSpeed( ArduinoConnection( ArduinoName("mymicro"), "/dev/ttyACM0", # linux # "/dev/cu.usbmodem14201", # macos baudrate=115200, status_ttl=5, ), tacho_pin=ArduinoPin(3), ), pwm_read=ArduinoFanPWMRead( ArduinoConnection( ArduinoName("mymicro"), "/dev/ttyACM0", # linux # "/dev/cu.usbmodem14201", # macos baudrate=115200, status_ttl=5, ), pwm_pin=ArduinoPin(9), ), pwm_write=ArduinoFanPWMWrite( ArduinoConnection( ArduinoName("mymicro"), "/dev/ttyACM0", # linux # "/dev/cu.usbmodem14201", # macos baudrate=115200, status_ttl=5, ), pwm_pin=ArduinoPin(9), ), pwm_line_start=PWMValue(100), pwm_line_end=PWMValue(240), never_stop=True, ), }, readonly_fans={}, temps={ TempName("hdds"): FilteredTemp( temp=HDDTemp( "/dev/sd?", min=TempCelsius(35.0), max=TempCelsius(48.0), panic=TempCelsius(55.0), threshold=None, hddtemp_bin="hddtemp", ), filter=NullFilter(), ), TempName("mobo"): FilteredTemp( temp=FileTemp( "/sys/class/hwmon/hwmon0/device/temp1_input", min=TempCelsius(30.0), max=TempCelsius(40.0), panic=None, threshold=None, ), filter=NullFilter(), ), }, mappings={ MappingName("1"): FansTempsRelation( temps=[TempName("mobo"), TempName("hdds")], fans=[ FanSpeedModifier(fan=FanName("cpu"), modifier=1.0), FanSpeedModifier(fan=FanName("hdd"), modifier=0.6), FanSpeedModifier(fan=FanName("my_arduino_fan"), modifier=0.222), ], ), MappingName("2"): FansTempsRelation( temps=[TempName("hdds")], fans=[FanSpeedModifier(fan=FanName("hdd"), modifier=1.0)], ), }, ) def test_minimal_config() -> None: daemon_cli_config = DaemonCLIConfig( pidfile=None, logfile=None, exporter_listen_host=None ) config = """ [daemon] [actions] [temp:mobo] type = file path = /sys/class/hwmon/hwmon0/device/temp1_input [fan: case] pwm = /sys/class/hwmon/hwmon0/device/pwm2 fan_input = /sys/class/hwmon/hwmon0/device/fan2_input [mapping:1] fans = case*0.6, temps = mobo """ parsed = parse_config(path_from_str(config), daemon_cli_config) assert parsed == ParsedConfig( arduino_connections={}, daemon=DaemonConfig( pidfile="/run/afancontrol.pid", logfile=None, exporter_listen_host=None, interval=5, ), report_cmd=( 'printf "Subject: %s\nTo: %s\n\n%b" ' '"afancontrol daemon report: %REASON%" root "%MESSAGE%" | sendmail -t' ), triggers=TriggerConfig( global_commands=Actions( panic=AlertCommands(enter_cmd=None, leave_cmd=None), threshold=AlertCommands(enter_cmd=None, leave_cmd=None), ), temp_commands={ TempName("mobo"): Actions( panic=AlertCommands(enter_cmd=None, leave_cmd=None), threshold=AlertCommands(enter_cmd=None, leave_cmd=None), ) }, ), fans={ FanName("case"): PWMFanNorm( fan_speed=LinuxFanSpeed( FanInputDevice("/sys/class/hwmon/hwmon0/device/fan2_input") ), pwm_read=LinuxFanPWMRead( PWMDevice("/sys/class/hwmon/hwmon0/device/pwm2") ), pwm_write=LinuxFanPWMWrite( PWMDevice("/sys/class/hwmon/hwmon0/device/pwm2") ), pwm_line_start=PWMValue(100), pwm_line_end=PWMValue(240), never_stop=True, ) }, readonly_fans={}, temps={ TempName("mobo"): FilteredTemp( temp=FileTemp( "/sys/class/hwmon/hwmon0/device/temp1_input", min=None, max=None, panic=None, threshold=None, ), filter=NullFilter(), ) }, mappings={ MappingName("1"): FansTempsRelation( temps=[TempName("mobo")], fans=[FanSpeedModifier(fan=FanName("case"), modifier=0.6)], ) }, ) def test_readonly_config() -> None: daemon_cli_config = DaemonCLIConfig( pidfile=None, logfile=None, exporter_listen_host=None ) config = """ [daemon] [actions] [temp:mobo] type = file path = /sys/class/hwmon/hwmon0/device/temp1_input [readonly_fan: cpu] pwm = /sys/class/hwmon/hwmon0/device/pwm1 fan_input = /sys/class/hwmon/hwmon0/device/fan1_input """ parsed = parse_config(path_from_str(config), daemon_cli_config) assert parsed == ParsedConfig( arduino_connections={}, daemon=DaemonConfig( pidfile="/run/afancontrol.pid", logfile=None, exporter_listen_host=None, interval=5, ), report_cmd=( 'printf "Subject: %s\nTo: %s\n\n%b" ' '"afancontrol daemon report: %REASON%" root "%MESSAGE%" | sendmail -t' ), triggers=TriggerConfig( global_commands=Actions( panic=AlertCommands(enter_cmd=None, leave_cmd=None), threshold=AlertCommands(enter_cmd=None, leave_cmd=None), ), temp_commands={ TempName("mobo"): Actions( panic=AlertCommands(enter_cmd=None, leave_cmd=None), threshold=AlertCommands(enter_cmd=None, leave_cmd=None), ) }, ), fans={}, readonly_fans={ ReadonlyFanName("cpu"): ReadonlyPWMFanNorm( fan_speed=LinuxFanSpeed( FanInputDevice("/sys/class/hwmon/hwmon0/device/fan1_input") ), pwm_read=LinuxFanPWMRead( PWMDevice("/sys/class/hwmon/hwmon0/device/pwm1") ), ) }, temps={ TempName("mobo"): FilteredTemp( temp=FileTemp( "/sys/class/hwmon/hwmon0/device/temp1_input", min=None, max=None, panic=None, threshold=None, ), filter=NullFilter(), ) }, mappings={}, )