Skip to content
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
2 changes: 1 addition & 1 deletion homeassistant/components/actron_air/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@
"integration_type": "hub",
"iot_class": "cloud_polling",
"quality_scale": "silver",
"requirements": ["actron-neo-api==0.5.3"]
"requirements": ["actron-neo-api==0.5.5"]
}
21 changes: 9 additions & 12 deletions homeassistant/components/airly/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
import voluptuous as vol

from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession

from .const import CONF_USE_NEAREST, DOMAIN, NO_AIRLY_SENSORS
from .const import CONF_USE_NEAREST, DEFAULT_NAME, DOMAIN, NO_AIRLY_SENSORS

DESCRIPTION_PLACEHOLDERS = {
"developer_registration_url": "https://developer.airly.eu/register",
Expand Down Expand Up @@ -45,16 +45,16 @@ async def async_step_user(
try:
location_point_valid = await check_location(
websession,
user_input["api_key"],
user_input["latitude"],
user_input["longitude"],
user_input[CONF_API_KEY],
user_input[CONF_LATITUDE],
user_input[CONF_LONGITUDE],
)
if not location_point_valid:
location_nearest_valid = await check_location(
websession,
user_input["api_key"],
user_input["latitude"],
user_input["longitude"],
user_input[CONF_API_KEY],
user_input[CONF_LATITUDE],
user_input[CONF_LONGITUDE],
use_nearest=True,
)
except AirlyError as err:
Expand All @@ -68,7 +68,7 @@ async def async_step_user(
return self.async_abort(reason="wrong_location")
use_nearest = True
return self.async_create_entry(
title=user_input[CONF_NAME],
title=DEFAULT_NAME,
data={**user_input, CONF_USE_NEAREST: use_nearest},
)

Expand All @@ -83,9 +83,6 @@ async def async_step_user(
vol.Optional(
CONF_LONGITUDE, default=self.hass.config.longitude
): cv.longitude,
vol.Optional(
CONF_NAME, default=self.hass.config.location_name
): str,
}
),
errors=errors,
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/airly/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,5 @@
MIN_UPDATE_INTERVAL: Final = 5
NO_AIRLY_SENSORS: Final = "There are no Airly sensors in this area yet."
URL = "https://airly.org/map/#{latitude},{longitude}"

DEFAULT_NAME: Final = "Airly"
2 changes: 1 addition & 1 deletion homeassistant/components/airly/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Airly sensor entities based on a config entry."""
name = entry.data[CONF_NAME]
name = entry.data.get(CONF_NAME) or entry.title

coordinator = entry.runtime_data

Expand Down
3 changes: 1 addition & 2 deletions homeassistant/components/airly/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
"data": {
"api_key": "[%key:common::config_flow::data::api_key%]",
"latitude": "[%key:common::config_flow::data::latitude%]",
"longitude": "[%key:common::config_flow::data::longitude%]",
"name": "[%key:common::config_flow::data::name%]"
"longitude": "[%key:common::config_flow::data::longitude%]"
},
"description": "To generate API key go to {developer_registration_url}"
}
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/broadlink/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
DOMAINS_AND_TYPES = {
Platform.CLIMATE: {"HYS"},
Platform.LIGHT: {"LB1", "LB2"},
Platform.RADIO_FREQUENCY: {"RM4PRO", "RMPRO"},
Platform.REMOTE: {"RM4MINI", "RM4PRO", "RMMINI", "RMMINIB", "RMPRO"},
Platform.SELECT: {"HYS"},
Platform.SENSOR: {
Expand Down
132 changes: 132 additions & 0 deletions homeassistant/components/broadlink/radio_frequency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
"""Radio Frequency platform for Broadlink."""

from __future__ import annotations

import logging

from broadlink.exceptions import BroadlinkException
from rf_protocols import RadioFrequencyCommand

from homeassistant.components.radio_frequency import RadioFrequencyTransmitterEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from .const import DOMAIN
from .device import BroadlinkDevice
from .entity import BroadlinkEntity

_LOGGER = logging.getLogger(__name__)

PARALLEL_UPDATES = 0

_TICK_US = 32.84

_RF_433_TYPE_BYTE = 0xB2
_RF_315_TYPE_BYTE = 0xB4

_RF_433_RANGE = (433_050_000, 434_790_000)
_RF_315_RANGE = (314_950_000, 315_250_000)

SUPPORTED_FREQUENCY_RANGES: list[tuple[int, int]] = [_RF_433_RANGE, _RF_315_RANGE]


def _type_byte_for_frequency(frequency: int) -> int:
"""Return the Broadlink RF type byte for a given carrier frequency."""
if _RF_433_RANGE[0] <= frequency <= _RF_433_RANGE[1]:
return _RF_433_TYPE_BYTE
if _RF_315_RANGE[0] <= frequency <= _RF_315_RANGE[1]:
return _RF_315_TYPE_BYTE
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="frequency_not_supported",
translation_placeholders={"frequency": f"{frequency / 1_000_000:g}"},
)


def encode_rf_packet(
*,
type_byte: int,
repeat_count: int,
timings_us: list[int],
) -> bytes:
"""Encode raw OOK timings as a Broadlink RF pulse-length packet.

The layout is::

byte 0 type byte (0xB2 for 433 MHz, 0xB4 for 315 MHz)
byte 1 repeat count (additional transmissions after the first)
bytes 2..3 payload length (little-endian), counted from byte 4
bytes 4..N-1 pulses: 1 byte when ticks < 256, otherwise
0x00 followed by a 2-byte big-endian tick count

Each pulse is expressed as multiples of 32.84 µs ticks, which is the
timing resolution of the Broadlink RF front-end.
"""
buf = bytearray([type_byte, repeat_count, 0, 0])
for duration in timings_us:
ticks = round(abs(duration) / _TICK_US)
div, mod = divmod(ticks, 256)
if div:
buf.append(0x00)
buf.append(div)
buf.append(mod)
payload_len = len(buf) - 4
buf[2] = payload_len & 0xFF
buf[3] = (payload_len >> 8) & 0xFF
return bytes(buf)


async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up a Broadlink radio frequency transmitter."""
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
device: BroadlinkDevice = hass.data[DOMAIN].devices[config_entry.entry_id]
async_add_entities([BroadlinkRadioFrequency(device)])


class BroadlinkRadioFrequency(BroadlinkEntity, RadioFrequencyTransmitterEntity):
"""Representation of a Broadlink RF transmitter."""

_attr_has_entity_name = True
_attr_name = None

def __init__(self, device: BroadlinkDevice) -> None:
"""Initialize the entity."""
super().__init__(device)
self._attr_unique_id = device.unique_id

@property
def supported_frequency_ranges(self) -> list[tuple[int, int]]:
"""Return the Broadlink-supported narrow RF bands."""
return SUPPORTED_FREQUENCY_RANGES

async def async_send_command(self, command: RadioFrequencyCommand) -> None:
"""Encode an OOK command and transmit it via the Broadlink device."""
type_byte = _type_byte_for_frequency(command.frequency)
packet = encode_rf_packet(
type_byte=type_byte,
repeat_count=command.repeat_count,
timings_us=command.get_raw_timings(),
)
_LOGGER.debug(
"Transmitting RF packet: %d bytes on %d Hz (repeat=%d)",
len(packet),
command.frequency,
command.repeat_count,
)

device = self._device
try:
await device.async_request(device.api.send_data, packet)
except (BroadlinkException, OSError) as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="transmit_failed",
translation_placeholders={"error": str(err)},
) from err
8 changes: 8 additions & 0 deletions homeassistant/components/broadlink/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,13 @@
"name": "Total consumption"
}
}
},
"exceptions": {
"frequency_not_supported": {
"message": "Broadlink devices cannot transmit on {frequency} MHz"
},
"transmit_failed": {
"message": "Failed to transmit RF command: {error}"
}
}
}
25 changes: 12 additions & 13 deletions homeassistant/components/easyenergy/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from typing import Any

from homeassistant.core import HomeAssistant
from homeassistant.util import dt as dt_util

from .coordinator import EasyEnergyConfigEntry, EasyEnergyData

Expand All @@ -23,9 +24,7 @@ def get_gas_price(data: EasyEnergyData, hours: int) -> float | None:
"""
if not data.gas_today:
return None
return data.gas_today.price_at_time(
data.gas_today.utcnow() + timedelta(hours=hours)
)
return data.gas_today.price_at_time(dt_util.utcnow() + timedelta(hours=hours))


async def async_get_config_entry_diagnostics(
Expand All @@ -40,21 +39,21 @@ async def async_get_config_entry_diagnostics(
"title": entry.title,
},
"energy_usage": {
"current_hour_price": energy_today.current_usage_price,
"current_hour_price": energy_today.current_price,
"next_hour_price": energy_today.price_at_time(
energy_today.utcnow() + timedelta(hours=1)
dt_util.utcnow() + timedelta(hours=1)
),
"average_price": energy_today.average_usage_price,
"max_price": energy_today.extreme_usage_prices[1],
"min_price": energy_today.extreme_usage_prices[0],
"highest_price_time": energy_today.highest_usage_price_time,
"lowest_price_time": energy_today.lowest_usage_price_time,
"percentage_of_max": energy_today.pct_of_max_usage,
"average_price": energy_today.average_price,
"max_price": energy_today.extreme_prices[1],
"min_price": energy_today.extreme_prices[0],
"highest_price_time": energy_today.highest_price_time,
"lowest_price_time": energy_today.lowest_price_time,
"percentage_of_max": energy_today.pct_of_max,
},
"energy_return": {
"current_hour_price": energy_today.current_return_price,
"next_hour_price": energy_today.price_at_time(
energy_today.utcnow() + timedelta(hours=1), "return"
"next_hour_price": energy_today.return_price_at_time(
dt_util.utcnow() + timedelta(hours=1)
),
"average_price": energy_today.average_return_price,
"max_price": energy_today.extreme_return_prices[1],
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/easyenergy/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
"documentation": "https://www.home-assistant.io/integrations/easyenergy",
"integration_type": "service",
"iot_class": "cloud_polling",
"requirements": ["easyenergy==2.2.0"],
"requirements": ["easyenergy==3.0.0"],
"single_config_entry": true
}
Loading
Loading