Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add formatting, linting, and project config #2

Merged
merged 5 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
*.pyc
.idea
.idea
.venv
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ The communication is done using Python's `asyncio` and requires no external libr

## License

GNU GENERAL PUBLIC LICENSE version 3
GNU GENERAL PUBLIC LICENSE version 3
12 changes: 5 additions & 7 deletions custom_components/extron/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
"""The Extron integration."""

from __future__ import annotations
Jalle19 marked this conversation as resolved.
Show resolved Hide resolved

from dataclasses import dataclass

from homeassistant.config_entries import ConfigEntry
Expand Down Expand Up @@ -36,8 +34,8 @@ async def get_device_information(device: ExtronDevice) -> DeviceInformation:

device_info = DeviceInfo(
identifiers={(DOMAIN, format_mac(mac_address))},
name=f'Extron {model_name}',
manufacturer='Extron',
name=f"Extron {model_name}",
manufacturer="Extron",
model=model_name,
sw_version=firmware_version,
serial_number=part_number,
Expand All @@ -50,12 +48,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Extron from a config entry."""
# Verify we can connect to the device
try:
device = ExtronDevice(entry.data['host'], entry.data['port'], entry.data['password'])
device = ExtronDevice(entry.data["host"], entry.data["port"], entry.data["password"])
await device.connect()
except AuthenticationError as e:
raise ConfigEntryNotReady('Invalid credentials') from e
raise ConfigEntryNotReady("Invalid credentials") from e
except Exception as e:
raise ConfigEntryNotReady('Unable to connect') from e
raise ConfigEntryNotReady("Unable to connect") from e

# Store the device and information about as runtime data in the entry
device_information = await get_device_information(device)
Expand Down
4 changes: 2 additions & 2 deletions custom_components/extron/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ def __init__(self, device: ExtronDevice, device_information: DeviceInformation)

@property
def unique_id(self) -> str | None:
return f'extron_{self._device_information.mac_address}_reboot_button'
return f"extron_{self._device_information.mac_address}_reboot_button"

@property
def device_info(self) -> DeviceInfo:
return self._device_information.device_info

@property
def name(self):
return f'Extron {self._device_information.model_name} reboot button'
return f"Extron {self._device_information.model_name} reboot button"

async def async_press(self) -> None:
await self._device.reboot()
Expand Down
24 changes: 11 additions & 13 deletions custom_components/extron/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
"""Config flow for Extron integration."""

from __future__ import annotations

import logging

from typing import Any
Expand All @@ -21,14 +19,16 @@
vol.Required(CONF_HOST): str,
vol.Required(CONF_PORT, default=23): int,
vol.Required(CONF_PASSWORD): str,
vol.Required(CONF_DEVICE_TYPE): selector({
"select": {
"options": [
{"label": "HDMI Switcher", "value": DeviceType.HDMI_SWITCHER.value},
{"label": "Surround Sound Processor", "value": DeviceType.SURROUND_SOUND_PROCESSOR.value},
]
vol.Required(CONF_DEVICE_TYPE): selector(
{
"select": {
"options": [
{"label": "HDMI Switcher", "value": DeviceType.HDMI_SWITCHER.value},
{"label": "Surround Sound Processor", "value": DeviceType.SURROUND_SOUND_PROCESSOR.value},
]
}
}
})
),
}
)

Expand All @@ -44,7 +44,7 @@ async def async_step_user(self, user_input: dict[str, Any] | None = None) -> Con
if user_input is not None:
try:
# Try to connect to the device
extron_device = ExtronDevice(user_input['host'], user_input['port'], user_input['password'])
extron_device = ExtronDevice(user_input["host"], user_input["port"], user_input["password"])
await extron_device.connect()

# Make a title for the entry
Expand All @@ -60,6 +60,4 @@ async def async_step_user(self, user_input: dict[str, Any] | None = None) -> Con
else:
return self.async_create_entry(title=title, data=user_input)

return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)
return self.async_show_form(step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors)
8 changes: 4 additions & 4 deletions custom_components/extron/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

DOMAIN = "extron"

CONF_HOST = 'host'
CONF_PORT = 'port'
CONF_PASSWORD = 'password'
CONF_DEVICE_TYPE = 'device_type'
CONF_HOST = "host"
CONF_PORT = "port"
CONF_PASSWORD = "password"
CONF_DEVICE_TYPE = "device_type"
38 changes: 19 additions & 19 deletions custom_components/extron/extron.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@


class DeviceType(Enum):
SURROUND_SOUND_PROCESSOR = 'surround_sound_processor'
HDMI_SWITCHER = 'hdmi_switcher'
UNKNOWN = 'unknown'
SURROUND_SOUND_PROCESSOR = "surround_sound_processor"
HDMI_SWITCHER = "hdmi_switcher"
UNKNOWN = "unknown"


class AuthenticationError(Exception):
Expand Down Expand Up @@ -51,7 +51,7 @@ async def connect(self):
try:
await asyncio.wait_for(self.attempt_login(), timeout=5)
self._connected = True
logger.info(f'Connected and authenticated to {self._host}:{self._port}')
logger.info(f"Connected and authenticated to {self._host}:{self._port}")
except TimeoutError:
raise AuthenticationError()

Expand All @@ -75,14 +75,14 @@ async def run_command(self, command: str):
response = await asyncio.wait_for(self._run_command_internal(command), timeout=3)

if response is None:
raise RuntimeError('Command failed')
raise RuntimeError("Command failed")
else:
return response.strip()
except TimeoutError:
raise RuntimeError('Command timed out')
raise RuntimeError("Command timed out")
except (ConnectionResetError, BrokenPipeError):
self._connected = False
logger.warning('Connection seems to be broken, will attempt to reconnect')
logger.warning("Connection seems to be broken, will attempt to reconnect")
finally:
if not self._connected:
await self.connect()
Expand All @@ -100,10 +100,10 @@ async def query_part_number(self):
return await self.run_command("N")

async def query_mac_address(self):
return await self.run_command("\x1B" + "CH")
return await self.run_command("\x1b" + "CH")

async def reboot(self):
await self.run_command("\x1B" + "1BOOT")
await self.run_command("\x1b" + "1BOOT")


class SurroundSoundProcessor:
Expand All @@ -117,33 +117,33 @@ async def view_input(self):
return await self._device.run_command("$")

async def select_input(self, input: int):
await self._device.run_command(f'{str(input)}$')
await self._device.run_command(f"{str(input)}$")

async def mute(self):
await self._device.run_command('1Z')
await self._device.run_command("1Z")

async def unmute(self):
await self._device.run_command('0Z')
await self._device.run_command("0Z")

async def is_muted(self) -> bool:
is_muted = await self._device.run_command('Z')
is_muted = await self._device.run_command("Z")
return is_muted == "1"

async def get_volume_level(self):
volume = await self._device.run_command('V')
volume = await self._device.run_command("V")
return int(volume)

async def set_volume_level(self, level: int):
await self._device.run_command(f'{level}V')
await self._device.run_command(f"{level}V")

async def increment_volume(self):
await self._device.run_command('+V')
await self._device.run_command("+V")

async def decrement_volume(self):
await self._device.run_command('-V')
await self._device.run_command("-V")

async def get_temperature(self) -> int:
temperature = await self._device.run_command("\x1B" + "20STAT")
temperature = await self._device.run_command("\x1b" + "20STAT")
return int(temperature)


Expand All @@ -158,4 +158,4 @@ async def view_input(self):
return await self._device.run_command("!")

async def select_input(self, input: int):
await self._device.run_command(f'{str(input)}!')
await self._device.run_command(f"{str(input)}!")
24 changes: 12 additions & 12 deletions custom_components/extron/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def unique_id(self) -> str | None:
device_type = self.get_device_type()
mac_address = self._device_information.mac_address

return f'extron_{device_type.value}_{mac_address}_media_player'
return f"extron_{device_type.value}_{mac_address}_media_player"

@property
def state(self):
Expand All @@ -62,7 +62,7 @@ def device_info(self) -> DeviceInfo:

@property
def name(self):
return f'Extron {self._device_information.model_name} media player'
return f"Extron {self._device_information.model_name} media player"


class ExtronSurroundSoundProcessor(AbstractExtronMediaPlayerEntity):
Expand All @@ -71,15 +71,15 @@ def __init__(self, ssp: SurroundSoundProcessor, device_information: DeviceInform
self._ssp = ssp

self._source = None
self._source_list = ['1', '2', '3', '4', '5']
self._source_list = ["1", "2", "3", "4", "5"]
self._volume = None
self._muted = False

_attr_supported_features = (
MediaPlayerEntityFeature.SELECT_SOURCE
| MediaPlayerEntityFeature.VOLUME_MUTE
| MediaPlayerEntityFeature.VOLUME_SET
| MediaPlayerEntityFeature.VOLUME_STEP
MediaPlayerEntityFeature.SELECT_SOURCE
| MediaPlayerEntityFeature.VOLUME_MUTE
| MediaPlayerEntityFeature.VOLUME_SET
| MediaPlayerEntityFeature.VOLUME_STEP
)

def get_device_type(self):
Expand Down Expand Up @@ -151,16 +151,16 @@ def source(self):
@property
def source_list(self):
model_name = self._device_information.model_name
sw = model_name.split(' ')[0]
sw = model_name.split(" ")[0]

if sw == "SW2":
return ['1', '2']
return ["1", "2"]
elif sw == "SW4":
return ['1', '2', '3', '4']
return ["1", "2", "3", "4"]
elif sw == "SW6":
return ['1', '2', '3', '4', '5', '6']
return ["1", "2", "3", "4", "5", "6"]
else:
return ['1', '2', '3', '4', '5', '6', '7', '8']
return ["1", "2", "3", "4", "5", "6", "7", "8"]

async def async_select_source(self, source: str):
await self._hdmi_switcher.select_input(int(source))
Expand Down
6 changes: 3 additions & 3 deletions custom_components/extron/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,20 @@ def __init__(self, ssp: SurroundSoundProcessor, device_information: DeviceInform
self._native_value = None

_attr_device_class = SensorDeviceClass.TEMPERATURE
_attr_native_unit_of_measurement = '°C'
_attr_native_unit_of_measurement = "°C"
_attr_state_class = SensorStateClass.MEASUREMENT

@property
def unique_id(self) -> str | None:
return f'extron_{self._device_information.mac_address}_temperature'
return f"extron_{self._device_information.mac_address}_temperature"

@property
def device_info(self) -> DeviceInfo:
return self._device_information.device_info

@property
def name(self):
return f'Extron {self._device_information.model_name} temperature'
return f"Extron {self._device_information.model_name} temperature"

@property
def native_value(self) -> StateType | date | datetime | Decimal:
Expand Down
38 changes: 37 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
[project]
name = "ha-extron"
version = "0.1.0"
description = "Home Assistant integration for Extron switchers and audio processors"
readme = "README.md"
license = {file = "LICENSE"}
requires-python = ">=3.12"
dependencies = ["homeassistant"]

[tool.black]
# https://black.readthedocs.io/en/stable/usage_and_configuration/the_basics.html#configuration-via-a-file
line-length = 120
target-version = ["py311", "py312"]
include = '\.pyi?$'
extend-exclude = '''
(
venv*
)
'''

[tool.ruff]
# https://github.com/astral-sh/ruff#configuration
include = ["*.py", "*.pyi", "**/pyproject.toml"]
Expand Down Expand Up @@ -97,4 +117,20 @@ section-order = [
"third-party",
"first-party",
"local-folder",
]
]

[tool.isort]
# https://pycqa.github.io/isort/docs/configuration/options.html
# profile = "black"
# manually specifying black compatibility to override line length
combine_as_imports = true
ensure_newline_before_comments = true
extend_skip = [".idea", ".vscode", ".venv", "venv"]
extend_skip_glob = ["venv*"]
force_grid_wrap = 0
include_trailing_comma = true
line_length = 120
multi_line_output = 3
py_version = 311
sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"]
use_parentheses = true
Loading