Skip to content

Commit

Permalink
2024.4.2 (#115186)
Browse files Browse the repository at this point in the history
  • Loading branch information
frenck committed Apr 8, 2024
2 parents b1fb77c + 05082fc commit 04072cb
Show file tree
Hide file tree
Showing 42 changed files with 513 additions and 123 deletions.
2 changes: 2 additions & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Expand Up @@ -1249,6 +1249,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/sms/ @ocalvo
/homeassistant/components/snapcast/ @luar123
/tests/components/snapcast/ @luar123
/homeassistant/components/snmp/ @nmaggioni
/tests/components/snmp/ @nmaggioni
/homeassistant/components/snooz/ @AustinBrunkhorst
/tests/components/snooz/ @AustinBrunkhorst
/homeassistant/components/solaredge/ @frenck
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/axis/manifest.json
Expand Up @@ -26,7 +26,7 @@
"iot_class": "local_push",
"loggers": ["axis"],
"quality_scale": "platinum",
"requirements": ["axis==60"],
"requirements": ["axis==61"],
"ssdp": [
{
"manufacturer": "AXIS"
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/brother/manifest.json
Expand Up @@ -8,7 +8,7 @@
"iot_class": "local_polling",
"loggers": ["brother", "pyasn1", "pysmi", "pysnmp"],
"quality_scale": "platinum",
"requirements": ["brother==4.0.2"],
"requirements": ["brother==4.1.0"],
"zeroconf": [
{
"type": "_printer._tcp.local.",
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/cloud/manifest.json
Expand Up @@ -8,5 +8,5 @@
"integration_type": "system",
"iot_class": "cloud_push",
"loggers": ["hass_nabucasa"],
"requirements": ["hass-nabucasa==0.79.0"]
"requirements": ["hass-nabucasa==0.78.0"]
}
1 change: 1 addition & 0 deletions homeassistant/components/fibaro/sensor.py
Expand Up @@ -121,6 +121,7 @@ async def async_setup_entry(
Platform.COVER,
Platform.LIGHT,
Platform.LOCK,
Platform.SENSOR,
Platform.SWITCH,
)
for device in controller.fibaro_devices[platform]
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/fyta/manifest.json
Expand Up @@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/fyta",
"integration_type": "hub",
"iot_class": "cloud_polling",
"requirements": ["fyta_cli==0.3.3"]
"requirements": ["fyta_cli==0.3.5"]
}
10 changes: 5 additions & 5 deletions homeassistant/components/fyta/sensor.py
Expand Up @@ -46,35 +46,35 @@ class FytaSensorEntityDescription(SensorEntityDescription):
translation_key="plant_status",
device_class=SensorDeviceClass.ENUM,
options=PLANT_STATUS_LIST,
value_fn=lambda value: PLANT_STATUS[value],
value_fn=PLANT_STATUS.get,
),
FytaSensorEntityDescription(
key="temperature_status",
translation_key="temperature_status",
device_class=SensorDeviceClass.ENUM,
options=PLANT_STATUS_LIST,
value_fn=lambda value: PLANT_STATUS[value],
value_fn=PLANT_STATUS.get,
),
FytaSensorEntityDescription(
key="light_status",
translation_key="light_status",
device_class=SensorDeviceClass.ENUM,
options=PLANT_STATUS_LIST,
value_fn=lambda value: PLANT_STATUS[value],
value_fn=PLANT_STATUS.get,
),
FytaSensorEntityDescription(
key="moisture_status",
translation_key="moisture_status",
device_class=SensorDeviceClass.ENUM,
options=PLANT_STATUS_LIST,
value_fn=lambda value: PLANT_STATUS[value],
value_fn=PLANT_STATUS.get,
),
FytaSensorEntityDescription(
key="salinity_status",
translation_key="salinity_status",
device_class=SensorDeviceClass.ENUM,
options=PLANT_STATUS_LIST,
value_fn=lambda value: PLANT_STATUS[value],
value_fn=PLANT_STATUS.get,
),
FytaSensorEntityDescription(
key="temperature",
Expand Down
6 changes: 5 additions & 1 deletion homeassistant/components/homematic/climate.py
Expand Up @@ -113,7 +113,11 @@ def preset_mode(self):
@property
def preset_modes(self):
"""Return a list of available preset modes."""
return [HM_PRESET_MAP[mode] for mode in self._hmdevice.ACTIONNODE]
return [
HM_PRESET_MAP[mode]
for mode in self._hmdevice.ACTIONNODE
if mode in HM_PRESET_MAP
]

@property
def current_humidity(self):
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/knx/manifest.json
Expand Up @@ -12,7 +12,7 @@
"quality_scale": "platinum",
"requirements": [
"xknx==2.12.2",
"xknxproject==3.7.0",
"xknxproject==3.7.1",
"knx-frontend==2024.1.20.105944"
],
"single_config_entry": true
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/litterrobot/manifest.json
Expand Up @@ -12,5 +12,5 @@
"integration_type": "hub",
"iot_class": "cloud_push",
"loggers": ["pylitterbot"],
"requirements": ["pylitterbot==2023.4.9"]
"requirements": ["pylitterbot==2023.4.11"]
}
1 change: 1 addition & 0 deletions homeassistant/components/litterrobot/vacuum.py
Expand Up @@ -35,6 +35,7 @@
LitterBoxStatus.CLEAN_CYCLE: STATE_CLEANING,
LitterBoxStatus.EMPTY_CYCLE: STATE_CLEANING,
LitterBoxStatus.CLEAN_CYCLE_COMPLETE: STATE_DOCKED,
LitterBoxStatus.CAT_DETECTED: STATE_DOCKED,
LitterBoxStatus.CAT_SENSOR_TIMING: STATE_DOCKED,
LitterBoxStatus.DRAWER_FULL_1: STATE_DOCKED,
LitterBoxStatus.DRAWER_FULL_2: STATE_DOCKED,
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/nobo_hub/manifest.json
Expand Up @@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/nobo_hub",
"integration_type": "hub",
"iot_class": "local_push",
"requirements": ["pynobo==1.8.0"]
"requirements": ["pynobo==1.8.1"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/prometheus/__init__.py
Expand Up @@ -258,7 +258,7 @@ def _remove_labelsets(
self, entity_id: str, friendly_name: str | None = None
) -> None:
"""Remove labelsets matching the given entity id from all metrics."""
for metric in self._metrics.values():
for metric in list(self._metrics.values()):
for sample in cast(list[prometheus_client.Metric], metric.collect())[
0
].samples:
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/snapcast/config_flow.py
Expand Up @@ -45,7 +45,7 @@ async def async_step_user(self, user_input=None) -> ConfigFlowResult:
except OSError:
errors["base"] = "cannot_connect"
else:
await client.stop()
client.stop()
return self.async_create_entry(title=DEFAULT_TITLE, data=user_input)
return self.async_show_form(
step_id="user", data_schema=SNAPCAST_SCHEMA, errors=errors
Expand Down
158 changes: 109 additions & 49 deletions homeassistant/components/snmp/device_tracker.py
Expand Up @@ -5,8 +5,19 @@
import binascii
import logging

from pysnmp.entity import config as cfg
from pysnmp.entity.rfc3413.oneliner import cmdgen
from pysnmp.error import PySnmpError
from pysnmp.hlapi.asyncio import (
CommunityData,
ContextData,
ObjectIdentity,
ObjectType,
SnmpEngine,
Udp6TransportTarget,
UdpTransportTarget,
UsmUserData,
bulkWalkCmd,
isEndOfMib,
)
import voluptuous as vol

from homeassistant.components.device_tracker import (
Expand All @@ -24,7 +35,13 @@
CONF_BASEOID,
CONF_COMMUNITY,
CONF_PRIV_KEY,
DEFAULT_AUTH_PROTOCOL,
DEFAULT_COMMUNITY,
DEFAULT_PORT,
DEFAULT_PRIV_PROTOCOL,
DEFAULT_TIMEOUT,
DEFAULT_VERSION,
SNMP_VERSIONS,
)

_LOGGER = logging.getLogger(__name__)
Expand All @@ -40,9 +57,12 @@
)


def get_scanner(hass: HomeAssistant, config: ConfigType) -> SnmpScanner | None:
async def async_get_scanner(
hass: HomeAssistant, config: ConfigType
) -> SnmpScanner | None:
"""Validate the configuration and return an SNMP scanner."""
scanner = SnmpScanner(config[DOMAIN])
await scanner.async_init()

return scanner if scanner.success_init else None

Expand All @@ -51,78 +71,118 @@ class SnmpScanner(DeviceScanner):
"""Queries any SNMP capable Access Point for connected devices."""

def __init__(self, config):
"""Initialize the scanner."""

self.snmp = cmdgen.CommandGenerator()

self.host = cmdgen.UdpTransportTarget((config[CONF_HOST], 161))
if CONF_AUTH_KEY not in config or CONF_PRIV_KEY not in config:
self.auth = cmdgen.CommunityData(config[CONF_COMMUNITY])
"""Initialize the scanner and test the target device."""
host = config[CONF_HOST]
community = config[CONF_COMMUNITY]
baseoid = config[CONF_BASEOID]
authkey = config.get(CONF_AUTH_KEY)
authproto = DEFAULT_AUTH_PROTOCOL
privkey = config.get(CONF_PRIV_KEY)
privproto = DEFAULT_PRIV_PROTOCOL

try:
# Try IPv4 first.
target = UdpTransportTarget((host, DEFAULT_PORT), timeout=DEFAULT_TIMEOUT)
except PySnmpError:
# Then try IPv6.
try:
target = Udp6TransportTarget(
(host, DEFAULT_PORT), timeout=DEFAULT_TIMEOUT
)
except PySnmpError as err:
_LOGGER.error("Invalid SNMP host: %s", err)
return

if authkey is not None or privkey is not None:
if not authkey:
authproto = "none"
if not privkey:
privproto = "none"

request_args = [
SnmpEngine(),
UsmUserData(
community,
authKey=authkey or None,
privKey=privkey or None,
authProtocol=authproto,
privProtocol=privproto,
),
target,
ContextData(),
]
else:
self.auth = cmdgen.UsmUserData(
config[CONF_COMMUNITY],
config[CONF_AUTH_KEY],
config[CONF_PRIV_KEY],
authProtocol=cfg.usmHMACSHAAuthProtocol,
privProtocol=cfg.usmAesCfb128Protocol,
)
self.baseoid = cmdgen.MibVariable(config[CONF_BASEOID])
request_args = [
SnmpEngine(),
CommunityData(community, mpModel=SNMP_VERSIONS[DEFAULT_VERSION]),
target,
ContextData(),
]

self.request_args = request_args
self.baseoid = baseoid
self.last_results = []
self.success_init = False

# Test the router is accessible
data = self.get_snmp_data()
async def async_init(self):
"""Make a one-off read to check if the target device is reachable and readable."""
data = await self.async_get_snmp_data()
self.success_init = data is not None

def scan_devices(self):
async def async_scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
self._update_info()
await self._async_update_info()
return [client["mac"] for client in self.last_results if client.get("mac")]

def get_device_name(self, device):
async def async_get_device_name(self, device):
"""Return the name of the given device or None if we don't know."""
# We have no names
return None

def _update_info(self):
async def _async_update_info(self):
"""Ensure the information from the device is up to date.
Return boolean if scanning successful.
"""
if not self.success_init:
return False

if not (data := self.get_snmp_data()):
if not (data := await self.async_get_snmp_data()):
return False

self.last_results = data
return True

def get_snmp_data(self):
async def async_get_snmp_data(self):
"""Fetch MAC addresses from access point via SNMP."""
devices = []

errindication, errstatus, errindex, restable = self.snmp.nextCmd(
self.auth, self.host, self.baseoid
walker = bulkWalkCmd(
*self.request_args,
0,
50,
ObjectType(ObjectIdentity(self.baseoid)),
lexicographicMode=False,
)

if errindication:
_LOGGER.error("SNMPLIB error: %s", errindication)
return
if errstatus:
_LOGGER.error(
"SNMP error: %s at %s",
errstatus.prettyPrint(),
errindex and restable[int(errindex) - 1][0] or "?",
)
return

for resrow in restable:
for _, val in resrow:
try:
mac = binascii.hexlify(val.asOctets()).decode("utf-8")
except AttributeError:
continue
_LOGGER.debug("Found MAC address: %s", mac)
mac = ":".join([mac[i : i + 2] for i in range(0, len(mac), 2)])
devices.append({"mac": mac})
async for errindication, errstatus, errindex, res in walker:
if errindication:
_LOGGER.error("SNMPLIB error: %s", errindication)
return
if errstatus:
_LOGGER.error(
"SNMP error: %s at %s",
errstatus.prettyPrint(),
errindex and res[int(errindex) - 1][0] or "?",
)
return

for _oid, value in res:
if not isEndOfMib(res):
try:
mac = binascii.hexlify(value.asOctets()).decode("utf-8")
except AttributeError:
continue
_LOGGER.debug("Found MAC address: %s", mac)
mac = ":".join([mac[i : i + 2] for i in range(0, len(mac), 2)])
devices.append({"mac": mac})
return devices
2 changes: 1 addition & 1 deletion homeassistant/components/snmp/manifest.json
@@ -1,7 +1,7 @@
{
"domain": "snmp",
"name": "SNMP",
"codeowners": [],
"codeowners": ["@nmaggioni"],
"documentation": "https://www.home-assistant.io/integrations/snmp",
"iot_class": "local_polling",
"loggers": ["pyasn1", "pysmi", "pysnmp"],
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/synology_dsm/binary_sensor.py
Expand Up @@ -116,7 +116,7 @@ def is_on(self) -> bool:
@property
def available(self) -> bool:
"""Return True if entity is available."""
return bool(self._api.security)
return bool(self._api.security) and super().available

@property
def extra_state_attributes(self) -> dict[str, str]:
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/synology_dsm/camera.py
Expand Up @@ -108,7 +108,7 @@ def device_info(self) -> DeviceInfo:
@property
def available(self) -> bool:
"""Return the availability of the camera."""
return self.camera_data.is_enabled and self.coordinator.last_update_success
return self.camera_data.is_enabled and super().available

@property
def is_recording(self) -> bool:
Expand Down

0 comments on commit 04072cb

Please sign in to comment.