Skip to main content

Serious about serial: migrating from pyserial to serialx

· 2 min read

Existing integrations and libraries communicating with serial ports should migrate from pyserial, pyserial-asyncio, and pyserial-asyncio-fast to serialx. This new library features native asyncio support on all platforms and will allow your integrations and libraries to take advantage of ESPHome serial proxies in Home Assistant, and includes critical fixes for asyncio event loop stability.

Background

pyserial has been the de facto serial library in Python for many years and has broad support for all popular platforms. Its API, however, predates asyncio in Python and is sync-only. The pyserial-asyncio package was eventually released to bridge pyserial with asyncio. Unfortunately, pyserial-asyncio development never reached 1.0 stability or cross-platform support, and neither pyserial nor pyserial-asyncio have had PyPI releases in almost five years. We forked pyserial-asyncio and released pyserial-asyncio-fast to fix outstanding issues affecting Core event loop stability.

We developed serialx from the ground up as a modern Python serial library with native sync and async APIs. It is import-compatible with the serial, serial_asyncio, and serial_asyncio_fast modules and allows existing packages to migrate with very minimal changes, if any.

Migration

The serialx documentation has an extensive migration guide that goes into more detail.

Most packages just need to replace pyserial, pyserial-asyncio, and pyserial-asyncio-fast with serialx in their setup.py or pyproject.toml file and update exception handling to replace broad catching of SerialException with more granular error handling (such as OSError and TimeoutError).

Packages directly constructing a sync serial.Serial() object should migrate to using the serialx.serial_for_url() helper to ensure automatic compatibility with all supported protocols.

Prompt for your agent

The migration itself is mechanical, paste the following instructions into your agent of choice:

Migrate my code from pyserial, pyserial-asyncio, and pyserial-asyncio-fast to serialx using https://raw.githubusercontent.com/puddly/serialx/refs/heads/dev/docs/how-to/pyserial-migration.md

New radio frequency entity platform for RF device integrations

· 3 min read

Home Assistant now has a radio_frequency entity platform that decouples RF transceiver hardware from the devices it controls. Instead of each device integration talking directly to specific RF hardware, transmitter integrations (like esphome) expose RadioFrequencyTransmitterEntity instances, and device integrations send commands through them via helper functions.

This mirrors the infrared entity platform and was approved in architecture discussion #1365.

Registering custom dashboard strategies

· 2 min read

As of Home Assistant 2026.5, you can now register custom dashboard strategies, just as you can with custom cards, making them easier to discover and add using the new dashboard dialog under the Community dashboards section.

Previously, you had to send users to create a blank dashboard, edit in YAML mode, and paste in your custom strategy. Now you can register a friendly name, description, and documentation.

To register your strategy, call window.customStrategies.push() with an object containing the following keys:

  • type: The strategy type without the custom: prefix, for example "my-demo".
  • strategyType: Set to "dashboard" to register a dashboard strategy.
  • name: The friendly name of the strategy.
  • description (optional): A short description of the strategy.
  • documentationURL (optional): A URL to the documentation for the strategy. This is not shown in the strategy UI yet but may in the future.

Example:

window.customStrategies = window.customStrategies || [];
window.customStrategies.push({
type: "my-demo",
strategyType: "dashboard",
name: "My demo dashboard",
description: "A starter dashboard generated from JavaScript.",
documentationURL: "https://example.com/my-demo-dashboard",
});

This metadata is separate from the custom element itself. Your strategy still needs to be registered with a tag like ll-strategy-dashboard-my-demo, and users still need the resource loaded before Home Assistant can discover it. You can use HACS for this as other resources can be added, like custom cards.

Take a look at the updated custom strategies documentation with example code and further details.

Deprecation of legacy device tracker platform API

· 3 min read

Summary

The legacy (non-config-entry) device tracker platform API is deprecated and will be removed in the Home Assistant 2027.5 release. By the end of the 12-month deprecation period, all remaining legacy device tracker platforms will be removed from the core repository, and custom integrations implementing the legacy API will stop working.

Integrations authors need to update integrations to implement the modern device tracker platform API.

Background

Config entry device trackers were introduced in May 2019, which means integration authors have had 8 years to migrate integrations when support is removed in May 2027.

As of today (April 2026) most widely used core device tracker integrations have already been migrated.

Note that the most popular integration that has not yet been migrated, xiaomi_miio, has a wide mix of functionality including other things than device tracker.

The proposal to deprecate the legacy device tracker API was approved in architecture proposal 1375.

List of core integrations, sorted by reported use

The list was generated from https://analytics.home-assistant.io/ in March 2026

Integration Installations API Type Details
----------------------------------------------------------------------------------------------------
mobile_app 415,204 modern tracker TrackerEntity
mqtt 233,161 modern tracker TrackerEntity
zha 126,903 modern scanner ScannerEntity
ibeacon 89,099 modern unknown BaseTrackerEntity
fritz 42,934 modern scanner ScannerEntity
ping 33,439 modern scanner ScannerEntity
unifi 33,136 modern scanner ScannerEntity
xiaomi_miio 12,866 legacy scanner async_scan_devices
nmap_tracker 7,397 modern scanner ScannerEntity
icloud 5,772 modern tracker TrackerEntity
freebox 5,312 modern scanner ScannerEntity
asuswrt 4,686 modern scanner ScannerEntity
tile 4,212 modern tracker TrackerEntity
renault 3,646 modern tracker TrackerEntity
private_ble_device 3,231 modern unknown BaseTrackerEntity
keenetic_ndms2 3,080 modern scanner ScannerEntity
owntracks 3,022 modern tracker TrackerEntity
snmp 2,923 legacy scanner async_scan_devices
devolo_home_network 2,910 modern scanner ScannerEntity
tesla_fleet 2,798 modern tracker TrackerEntity
netgear 2,782 modern scanner ScannerEntity
mikrotik 2,761 modern scanner ScannerEntity
tplink_omada 2,708 modern scanner ScannerEntity
starlink 2,250 modern tracker TrackerEntity
tractive 2,144 modern tracker TrackerEntity
volvo 2,077 modern tracker TrackerEntity
husqvarna_automower 1,924 modern tracker TrackerEntity
tessie 1,562 modern tracker TrackerEntity
bluetooth_le_tracker 1,011 legacy
traccar_server 978 modern tracker TrackerEntity
huawei_lte 827 modern scanner ScannerEntity
gpslogger 813 modern tracker TrackerEntity
teslemetry 720 modern tracker TrackerEntity
traccar 609 modern tracker TrackerEntity
luci 590 legacy scanner scan_devices
subaru 502 modern tracker TrackerEntity
mysensors 474 modern tracker TrackerEntity
starline 460 modern tracker TrackerEntity
geofency 443 modern tracker TrackerEntity
google_maps 389 legacy
locative 378 modern tracker TrackerEntity
opnsense 355 legacy scanner scan_devices
fing 266 modern scanner ScannerEntity
ruckus_unleashed 232 modern scanner ScannerEntity
synology_srm 193 legacy scanner scan_devices
unifi_direct 190 legacy scanner scan_devices
mqtt_json 182 legacy
vodafone_station 166 modern scanner ScannerEntity
xiaomi 143 legacy scanner scan_devices
demo 141 legacy
ubus 111 legacy scanner scan_devices
bt_smarthub 95 legacy scanner scan_devices
aprs 93 legacy
nrgkick 81 modern tracker TrackerEntity
ddwrt 79 legacy scanner scan_devices
fressnapf_tracker 74 modern tracker TrackerEntity
linksys_smart 69 legacy scanner scan_devices
fortios 53 legacy scanner scan_devices
swisscom 51 legacy scanner scan_devices
tomato 45 legacy scanner scan_devices
quantum_gateway 42 legacy scanner scan_devices
aruba 24 legacy scanner scan_devices
meraki 24 legacy
sky_hub 22 legacy scanner async_scan_devices
ituran 19 modern tracker TrackerEntity
cisco_ios 10 legacy scanner scan_devices
upc_connect 10 legacy scanner async_scan_devices
cisco_mobility_express 6 legacy scanner scan_devices
arris_tg2492lg 2 legacy scanner async_scan_devices
bbox 1 legacy scanner scan_devices
actiontec 0 legacy scanner scan_devices
autoskope 0 modern tracker TrackerEntity
bt_home_hub_5 0 legacy scanner scan_devices
cppm_tracker 0 legacy scanner scan_devices
fleetgo 0 legacy
hitron_coda 0 legacy scanner scan_devices
lojack 0 modern tracker TrackerEntity
thomson 0 legacy scanner scan_devices

Entity IDs with mismatched domains are deprecated

· One min read

Integrations that set entity_id directly on an entity will now be validated to ensure the domain portion matches the platform's domain. For example, a light entity must use light.my_light, not cover.my_light.

Setting an entity ID with the wrong domain will log a deprecation warning and will stop working in Home Assistant 2027.5.

In most cases, integrations should not set entity_id at all — Home Assistant will generate it automatically.

Migrating app builds to Docker BuildKit

· 4 min read

The legacy home-assistant/builder container and the old home-assistant/builder GitHub Action have been retired. We recommend migrating all GitHub workflows and Dockerfiles for apps (formerly add-ons) as described in this post.

What changed and why

The old builder ran every architecture build inside a single privileged Docker-in-Docker container using QEMU emulation. This was slow, required elevated privileges, and those who were already familiar with Docker needed to learn how to use the custom Home Assistant's builder container. The old builder also had unnecessary maintenance overhead. Today, what the builder does can be fully replaced with Docker BuildKit, which is natively supported on GitHub Actions runners and has built-in multi-arch support with QEMU emulation if needed.

For your CI, the replacement is a set of focused composite GitHub Actions that delegate building to the runner's native Docker with Docker BuildKit. Outside the CI, the migration means that your Dockerfile is now the single source of truth for building your app image, and you can use docker build directly to build and test your app locally without needing to use the builder container.

Migration process

The migration has two parts: updating your Dockerfiles and updating your GitHub Actions workflows.

Update Dockerfiles

The new build workflow doesn't use build.yaml anymore. Move the content into your Dockerfile as follows:

  • build_from - replace the build_from key in build.yaml with a FROM statement in your Dockerfile:

    FROM ghcr.io/home-assistant/base:latest

    As the base images are now published as multi-platform manifests, there is usually no need to define per-arch base images anymore. The build-image action still supplies BUILD_ARCH as a build argument though, so you can use that in your Dockerfile if you need to use it in the template for the base image name.

  • labels - move any custom Docker labels directly into your Dockerfile with a LABEL statement:

    LABEL \
    org.opencontainers.image.title="Your awesome app" \
    org.opencontainers.image.description="Description of your app." \
    org.opencontainers.image.source="https://github.com/your/repo" \
    org.opencontainers.image.licenses="Apache License 2.0"

    If you are creating a custom workflow, note that the legacy builder used to add the io.hass.type, io.hass.name, io.hass.description, and io.hass.url labels automatically. The new actions do not infer these values, so add them explicitly via the labels input of the build-image (or similar) action.

  • args - move custom build arguments into your Dockerfile as ARG definitions with default values:

    ARG MY_BUILD_ARG="default-value"

    Default values in ARG replace what was previously supplied via build.yaml's args dictionary. They can still be overridden at build time with --build-arg if needed.

With the content of build.yaml migrated, you can delete the file from your repository.

Update GitHub Actions workflows

Remove any workflow steps using home-assistant/builder@master and replace them with the new composite actions. See the example workflow in our example app repository for a complete working example. Alternatively, use the individual actions in a more custom workflow as needed.

Image naming

The preferred way to reference a published app image is now the generic (multi-arch) name without an architecture prefix:

# config.yaml
image: "ghcr.io/my-org/my-app"

The {arch} placeholder (e.g. ghcr.io/my-org/{arch}-my-app) is still supported as a compatibility fallback, but it's encouraged to use the generic name and let the manifest handle the platform resolution.

Local builds

After updating your Dockerfile, you can use docker build to build the app image directly - you can refer to Local app testing for more details.

Apps built locally by Supervisor

For backward compatibility, Supervisor still reads build.yaml file if it's present and populates the image build arguments with values read from this file. This will produce warnings and eventually be removed in the future, so it's recommended to migrate to the new Dockerfile-based approach as described above.

New infrared entity platform for IR device integrations

· 2 min read

Home Assistant now has an infrared entity platform that decouples IR emitter hardware from the devices they control. Instead of each device integration talking directly to specific IR hardware, emitter integrations (like esphome) expose InfraredEntity instances, and device integrations (like lg_infrared) send commands through them via helper functions.

See the architecture discussion for the full background.

Frontend component updates 2026.4

· One min read
info

We do not officially support or encourage custom card developers to use our built in components. This component APIs can always change and you should build your card as independent component.

ha-input

We keep migrating our Material Design based components to Web Awesome based. This time we migrated the input components, which leads to an API change but the look and feel stays the same for now.

  • ha-input is the successor of ha-textfield
    • ha-textfield API stays but the component is migrated to use ha-input internally and will be removed in 2026.5
    • Also replaces ha-outlined-text-field
  • ha-input-search replaces search-input and search-input-outlined
  • ha-input-multi replaces ha-multi-textfield
  • ha-input-copy replaces copy-textfield

This component also introduces new semantic theme variables for form backgrounds:

--ha-color-form-background: var(--ha-color-neutral-95);
--ha-color-form-background-hover: var(--ha-color-neutral-90);
--ha-color-form-background-disabled: var(--ha-color-neutral-80);

Date picker

We finally removed the Vue 2 dependency by replacing the date and date range picker with Cally.

Frontend new way of dialogs

· One min read

The Problem

Each dialog managed by the dialog manager was only opened once and stayed in the DOM for the lifetime of the application. This causes:

  • More memory usage: Dialogs accumulate in the DOM even when not visible
  • More bugs because of missing state reset: Dialog state persists between opens, leading to stale data or unexpected behavior

The Solution: DialogMixin

We implemented a new way of handling dialogs using DialogMixin. With this approach:

  • Dialogs are created when opened and destroyed when closed: No need to manually reset the state of the dialog when it is closed
  • Closed events are automatically handled: The dialog mixin takes care of cleanup
  • Subscribe mixin can now be used in dialogs: Since dialogs are properly destroyed, subscriptions are cleaned up automatically
  • Use normal Lit lifecycle methods: Use connectedCallback to initialize when the dialog is opened instead of relying on the showDialog method

Example

Check out ha-dialog-date-picker for a reference implementation. DialogMixin adds dialog params to this.params if available.