Skip to content

Interfaces

Interfaces are the connection points of a station. They are responsible for serializing packets out to other stations. Interfaces are not responsible for tracking connection state or routing packets-- that is the job of the Frame Router. Instead, they are only responsible for sending and receiving packets 'over the air'.

Some examples are in order. The simplest useful interface is the file interface . This simulates the arrival of packets by reading characters from a file, pretending each character arrives as a packet in a message frame.

By contrast, a serial interface is planned which will handle sending packets over KISS mode to a TNC.

Creating new interfaces

Aside from the existing interfaces, you can create your own! This could allow you to interface with a new type of hardware, or to simulate a new type of connection. For example, you could create a new interface which sends packets over a TCP connection, or which builds them out of HTTP requests. As long as you can come up with a way to serialize and deserialize packets, you can create a new interface.

The best way to learn how to create a new interface is to look at the existing interfaces, and to read the documentation for the interface base class, detailed below.

pax25.interfaces.types.Interface

The interface base class, which defines the required functions for an interface. All the actual functions must be defined on the subclass-- this base class just raises NotImplementedError for all of them.

Attributes:

Name Type Description
listening bool

bool flag to indicate whether the interface is up and listening for packets.

name str

str name of the interface as instantiated, for internal reference.

station Station

Station the interface is initialized for.

sudo bool

Whether this interface can be used for superuser connections.

settings S

Current settings on the interface.

Source code in pax25/interfaces/types.py
class Interface(Generic[S]):
    """
    The interface base class, which defines the required functions for an interface.
    All the actual functions must be defined on the subclass-- this base class
    just raises NotImplementedError for all of them.

    Attributes:
        listening: bool flag to indicate whether the interface is up and
            listening for packets.
        name: str name of the interface as instantiated, for internal reference.
        station: Station the interface is initialized for.
        sudo: Whether this interface can be used for superuser connections.
        settings: Current settings on the interface.
    """

    name: str
    type: str
    station: "Station"
    _settings: S

    @property
    def listening(self) -> bool:
        """
        Returns a flag indicating that the interface is active and listening.
        """
        raise NotImplementedError  # pragma: no cover

    @property
    def gateway(self) -> bool:
        """
        Whether this interface can be used to connect to the outside world.
        """
        raise NotImplementedError  # pragma: no cover

    @property
    def sudo(self) -> bool:
        """
        Whether connections from this interface should automatically be given
        administrative access.
        """
        raise NotImplementedError  # pragma: no cover

    @property
    def settings(self) -> S:
        """
        Returns a copy of our settings that's (mostly) safe to manipulate.
        """
        return smart_clone(self._settings)

    @classmethod
    def install(cls, station: "Station", *, name: str, settings: S) -> Self:
        """
        Installs this interface on a station.
        """
        instance = cls(name=name, settings=settings, station=station)
        station.add_interface(name, instance)
        return instance

    async def reload_settings(self, settings: S) -> None:
        """
        Interfaces should be able to hot-reload settings.
        """
        raise NotImplementedError  # pragma: no cover

    def __init__(self, name: str, settings: S, station: "Station"):
        """
        Initialize the interface. The interface will be initialized with
        its name, settings, and the station it is being initialized for.

        Under what conditions to set sudo is up to you, but it is set to False by
        default. The sudo flag indicates whether this interface can be used for
        superuser connections.

        It does not automatically mean that connections on this interface will be
        treated as superuser connections, but the base Application class will consider
        a user a superuser if they are connected to an interface while its sudo flag is
        True, and their name matches the station's default name.
        """
        raise NotImplementedError  # pragma: no cover

    def send_frame(self, frame: Frame) -> None:
        """
        Send this frame out on this interface.

        **NOTE**: While this function is a synchronous function, the implementation must
        queue the frame for processing asynchronously. The reason is that if the frame
        is processed synchronously, it will be tied in with the same codepath that sent
        the frame. This can result in unpredictable behavior such as a connection
        sending out another frame before it has a chance to increase its frame counter.
        """
        raise NotImplementedError  # pragma: no cover

    def start(self) -> None:
        """
        Interfaces should implement a 'start' function. This function should create
        the read-loop in a non-blocking manner-- specifically, it should create an async
        loop. See example implementations for how this is done.
        """
        raise NotImplementedError  # pragma: no cover

    async def shutdown(self) -> None:
        """
        This handles any cleanup needed to bring this interface offline, closing
        whatever read loops are in effect.
        """
        raise NotImplementedError  # pragma: no cover

gateway: bool property

Whether this interface can be used to connect to the outside world.

listening: bool property

Returns a flag indicating that the interface is active and listening.

settings: S property

Returns a copy of our settings that's (mostly) safe to manipulate.

sudo: bool property

Whether connections from this interface should automatically be given administrative access.

__init__(name: str, settings: S, station: Station)

Initialize the interface. The interface will be initialized with its name, settings, and the station it is being initialized for.

Under what conditions to set sudo is up to you, but it is set to False by default. The sudo flag indicates whether this interface can be used for superuser connections.

It does not automatically mean that connections on this interface will be treated as superuser connections, but the base Application class will consider a user a superuser if they are connected to an interface while its sudo flag is True, and their name matches the station's default name.

Source code in pax25/interfaces/types.py
def __init__(self, name: str, settings: S, station: "Station"):
    """
    Initialize the interface. The interface will be initialized with
    its name, settings, and the station it is being initialized for.

    Under what conditions to set sudo is up to you, but it is set to False by
    default. The sudo flag indicates whether this interface can be used for
    superuser connections.

    It does not automatically mean that connections on this interface will be
    treated as superuser connections, but the base Application class will consider
    a user a superuser if they are connected to an interface while its sudo flag is
    True, and their name matches the station's default name.
    """
    raise NotImplementedError  # pragma: no cover

install(station: Station, *, name: str, settings: S) -> Self classmethod

Installs this interface on a station.

Source code in pax25/interfaces/types.py
@classmethod
def install(cls, station: "Station", *, name: str, settings: S) -> Self:
    """
    Installs this interface on a station.
    """
    instance = cls(name=name, settings=settings, station=station)
    station.add_interface(name, instance)
    return instance

reload_settings(settings: S) -> None async

Interfaces should be able to hot-reload settings.

Source code in pax25/interfaces/types.py
async def reload_settings(self, settings: S) -> None:
    """
    Interfaces should be able to hot-reload settings.
    """
    raise NotImplementedError  # pragma: no cover

send_frame(frame: Frame) -> None

Send this frame out on this interface.

NOTE: While this function is a synchronous function, the implementation must queue the frame for processing asynchronously. The reason is that if the frame is processed synchronously, it will be tied in with the same codepath that sent the frame. This can result in unpredictable behavior such as a connection sending out another frame before it has a chance to increase its frame counter.

Source code in pax25/interfaces/types.py
def send_frame(self, frame: Frame) -> None:
    """
    Send this frame out on this interface.

    **NOTE**: While this function is a synchronous function, the implementation must
    queue the frame for processing asynchronously. The reason is that if the frame
    is processed synchronously, it will be tied in with the same codepath that sent
    the frame. This can result in unpredictable behavior such as a connection
    sending out another frame before it has a chance to increase its frame counter.
    """
    raise NotImplementedError  # pragma: no cover

shutdown() -> None async

This handles any cleanup needed to bring this interface offline, closing whatever read loops are in effect.

Source code in pax25/interfaces/types.py
async def shutdown(self) -> None:
    """
    This handles any cleanup needed to bring this interface offline, closing
    whatever read loops are in effect.
    """
    raise NotImplementedError  # pragma: no cover

start() -> None

Interfaces should implement a 'start' function. This function should create the read-loop in a non-blocking manner-- specifically, it should create an async loop. See example implementations for how this is done.

Source code in pax25/interfaces/types.py
def start(self) -> None:
    """
    Interfaces should implement a 'start' function. This function should create
    the read-loop in a non-blocking manner-- specifically, it should create an async
    loop. See example implementations for how this is done.
    """
    raise NotImplementedError  # pragma: no cover