Hass.async_create_task

I have created a custom component that reads the location from a gps device (it’s similar to the gaps integration but without the dependency on package gpsd).

In async_setup I create a task with hass.async_create_task that periodically reads from the gps and updates state variables with the current location.

This work - EXCEPT homeassistant.bootstrap takes forever to start and eventually times out.

How can I start the task without interfering with homeassistant.bootstrap?

For completeness, here is the complete code of my component:

from __future__ import annotations

import asyncio
import logging
import pynmea2
import time

from homeassistant.core import HomeAssistant
from homeassistant.helpers.typing import ConfigType

from homeassistant.const import (
    ATTR_LATITUDE,
    ATTR_LONGITUDE,
    CONF_ELEVATION
)

from serial.tools import list_ports
from serial_asyncio import open_serial_connection

_LOGGER = logging.getLogger(__name__)

DOMAIN = "gps"
BAUDRATE = 4800

def discover() -> str:
    for port in list_ports.comports():
        _LOGGER.info(f"Found device {port.vid:04x}:{port.pid:04x} by {port.manufacturer} @ {port.device}")
        if port.vid == 0x067b and port.pid == 0x23a3 and "Prolific" in port.manufacturer:
            return port.device
        return None

    
async def read_gps(hass: HomeAssistant, reader):
    # read & decode gps messages, send location to home assistant
    low_quality = 0
    while True:
        line = await reader.readline()
        line = line.decode()
        try:
            msg = pynmea2.parse(line)
            if msg.gps_qual < 1:
                low_quality += 1
                if low_quality % 120 == 0:
                    _LOGGER.warning(f"{skip_count} low quality gps readings ignored")
                continue
            hass.states.async_set(f'{DOMAIN}.{ATTR_LATITUDE}', msg.latitude)
            hass.states.async_set(f'{DOMAIN}.{ATTR_LONGITUDE}', msg.longitude)
            hass.states.async_set(f'{DOMAIN}.{CONF_ELEVATION}', msg.altitude)
            hass.states.async_set(f'{DOMAIN}.signal_quality', msg.gps_qual)
            hass.states.async_set(f'{DOMAIN}.timestamp', time.ctime())
            # update ~ once a minute
            await asyncio.sleep(60)
        except (AttributeError, pynmea2.ParseError) as e:
            pass
            
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
    # scan usb ports for gps device
    print(f"print Search gps device ...")
    _LOGGER.info(f"info Search gps device ...")
    device = discover()
    if device == None:
        _LOGGER.error(f"No gps device found, stopping.")
        return False
    # start gps reader task
    _LOGGER.info(f"Starting reader for gps at {device}")
    reader, _ = await open_serial_connection(url=device, baudrate=BAUDRATE)
    # PROBLEM: homeassistant.bootstrap won't finish startup
    # hass.async_create_task(read_gps(hass, reader))
    _LOGGER.info(f"Started gps reader as background task")
    return True

And the manifest.json:

{
  "domain": "gps",
  "name": "GPS reader",
  "documentation": "https://github.com/iot49/HA-components.git/gps/",
  "dependencies": [],
  "codeowners": [],
  "requirements": ["pyserial_asyncio", "pynmea2"],
  "iot_class": "local_polling",
  "version": "0.1.3"
}

Use asyncio.create_task instead as it won’t wait for it to finish before declaring startup successful.

1 Like

That’s the problem I am asking about = asyncio.create_task blocks!

I know this is now 3 weeks old and you may have sorted but maybe i can help. The issue is that your read_gps never returns so that is why you get a hang. Much better to remove the loop and the sleep and use similar code to below to schedule this to be called on an interval. Put this in your async_setup

# Poll for updates in the background
    update_track = async_track_time_interval(
        hass,
        lambda now: read_gps(hass, reader),
        timedelta(
            seconds=60)
        ),
    )

Thanks, this works!