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
1 change: 1 addition & 0 deletions .github/wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ gnutls
googlegroups
gpg
GPIO
gRPC
GStreamer
hombourger
homekit
Expand Down
36 changes: 36 additions & 0 deletions .github/workflows/proto.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# ---------------------------------------------------------------------------
# Verify mtda.proto is valid and the build step can generate stubs from it
# ---------------------------------------------------------------------------
#
# This software is a part of MTDA.
# Copyright (C) 2026 Siemens AG
#
# ---------------------------------------------------------------------------
# SPDX-License-Identifier: MIT
# ---------------------------------------------------------------------------

name: Proto stubs

on:
pull_request:
paths:
- 'mtda/grpc/mtda.proto'

jobs:
check-stubs:
name: Verify stubs can be generated from mtda.proto
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install grpcio-tools
run: pip3 install grpcio-tools

- name: Generate stubs
run: |
python3 -m grpc_tools.protoc \
-I mtda/grpc \
--python_out=mtda/grpc \
--grpc_python_out=mtda/grpc \
mtda/grpc/mtda.proto
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
*.changes
*.img
*.pyc
*_pb2.py
*_pb2_grpc.py
*.swp
build
cip-core
Expand Down
6 changes: 6 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ Contribution Checklist

- follow the existing coding style (run `pycodestyle` on changed files) [**required**]

- if `mtda/grpc/mtda.proto` is modified, the Python stubs
(`mtda_pb2.py`, `mtda_pb2_grpc.py`) are **not** committed — they are
generated automatically during `python setup.py build` / `pip install`.
The CI will verify that `grpc_tools.protoc` can still consume the updated
`.proto` without errors.

- add the required copyright header to each new file introduced, see
[licensing information](COPYING) [**required**]

Expand Down
2 changes: 1 addition & 1 deletion configs/docker.ini
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ debug = 2
# ---------------------------------------------------------------------------
# Remote settings
# ---------------------------------------------------------------------------
# Set "control" to the TCP/IP port number for the gRPC control interface
[remote]
control = 5556
console = 5557
host = localhost

# ---------------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion configs/qemu.ini
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ variant=qemu
# ---------------------------------------------------------------------------
# Remote settings
# ---------------------------------------------------------------------------
# Set "control" to the TCP/IP port number for the gRPC control interface
[remote]
control = 5556
console = 5557
host = localhost

# ---------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions debian/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ mtda-docker
mtda-kvm
mtda-pytest
mtda-service
mtda-systemd-helper
mtda-www
4 changes: 2 additions & 2 deletions debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Build-Depends: debhelper (>=10) | dh-systemd,
dh-python,
libpython3-all-dev,
python3-all-dev:any,
python3-grpc-tools,
python3-setuptools,
python3-sphinx:native <!nodoc>,
python3-sphinx-rtd-theme:native <!nodoc>
Expand All @@ -32,7 +33,6 @@ Depends: mtda-common,
python3-systemd,
python3-usb,
python3-zeroconf,
python3-zmq,
python3-zstandard,
usbrelay,
${misc:Depends},
Expand Down Expand Up @@ -82,7 +82,7 @@ Package: mtda-common
Architecture: all
Multi-Arch: foreign
Depends: python3:any (>= 3.7~),
python3-pyro5 | python3-pyro4
python3-grpcio
Description: common modules for Multi-Tenant Device Access
Modules shared between the service and the client.

Expand Down
14 changes: 4 additions & 10 deletions docs/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -83,19 +83,13 @@ General settings
``pduclient``, ``qemu``, ``shellcmd`` and ``usbrelay``.

* ``remote``: section [optional]
Specify the host and ports to connect to when using a MTDA client (such as
Specify the host and port to connect to when using a MTDA client (such as
``mtda-cli``).

* ``control``: integer [optional]
Remote port listening for control commands (defaults to ``5556``).

* ``console``: integer [optional]
Remote port to connect to in order to get console messages (defaults to
``5557``).

* ``data``: integer [optional]
Remote port for data transfers between the client and agent (defaults to
``0`` for a dynamic port assignment).
Remote port listening for gRPC requests (defaults to ``5556``). All
traffic — control commands, console streaming and data transfers — flows
over this single port.

* ``host``: string [optional]
Remote host name or ip to connect to as a client to interact with the
Expand Down
8 changes: 4 additions & 4 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ command. A remote agent may be specified on the command line using the
The remote may be alternatively specified in the ``[remote]]`` section
of the local configuration file.

Power commands
Power commands
~~~~~~~~~~~~~~

When MTDA is configured with a ``[power]`` control block in its configuration,
Expand Down Expand Up @@ -168,15 +168,15 @@ Keyboard
~~~~~~~~

The assist board has the capability. to act as keyboard to the DUT,general usage is
``mtda-cli keyboard write characters....`` Some special characters are supported and
``mtda-cli keyboard write characters....`` Some special characters are supported and
need to be enclosed between < and >

For instance: ``mtda-cli keyboard write "<down><enter>hello world<enter>"``

.. list-table:: Special Keys
:widths: 20 80
:header-rows: 1
:header-rows: 1

* - Supported Special Keys
- String
* - Backspace
Expand Down
2 changes: 2 additions & 0 deletions meta-isar/recipes-python/mtda/mtda_git.bb
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ do_gen_working_repo() {
for file in ${MTDA_FILES}; do
cp -a ${LAYERDIR_mtda}/../$file ${S}/
done
# delete auto-generated files as they need to be re-generated on building
find ${S} -name 'mtda_pb2*.py' -delete
rm -f ${S}/debian/source/format
}
do_gen_working_repo[cleandirs] += "${S}"
Expand Down
15 changes: 11 additions & 4 deletions mtda-cli
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@ class AppOutput(ScreenOutput):

if len(event) != 6:
return
if event[0] != 'STORAGE' and event[1] != 'WRITING':
if event[0] != 'STORAGE' or event[1] != 'WRITING':
return

app = self.app
image = app.imgname
if image is None:
return
# If the backend has information about the bytes to write (i.e. the mapped bytes)
# read and total refer to the output stream writes (without seeks) and mapped bytes.
bytes_read = int(event[2])
Expand Down Expand Up @@ -81,6 +83,7 @@ class Application:
self.remote = None
self.exiting = False
self.channel = "console"
self.imgname = None
self.screen = AppOutput(self)

def client(self):
Expand Down Expand Up @@ -137,10 +140,12 @@ class Application:

client.console_init()

# Get prefix key
# Get prefix key (as bytes for comparison with getkey() return value)
prefix_key = None
if sys.stdin.isatty():
prefix_key = client.console_prefix_key()
pk = client.console_prefix_key()
if pk is not None:
prefix_key = pk.encode("utf-8") if isinstance(pk, str) else pk

# Input loop
while self.exiting is False:
Expand All @@ -158,6 +163,8 @@ class Application:
def console_menukey(self, c):
client = self.agent
server = self.client()
if isinstance(c, bytes):
c = c.decode("latin-1")
if c == 'a':
status = server.target_lock()
if status is True:
Expand Down Expand Up @@ -252,7 +259,7 @@ class Application:
f"\r\n*** console buffer posted to {r.text} ***\r\n")

def console_prompt(self, args):
data = self.client().console_prompt(args.prompt)
data = self.client().console_prompt(args.prompt_str)
if data is not None:
sys.stdout.write(data)
sys.stdout.flush()
Expand Down
28 changes: 16 additions & 12 deletions mtda-service
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

# System imports
import argparse
import concurrent.futures
import lockfile
import netifaces
import os
Expand All @@ -22,16 +23,15 @@ import socket
import zeroconf
from systemd import daemon as sd

# gRPC
import grpc

# Local imports
from mtda.main import MultiTenantDeviceAccess
from mtda.grpc import mtda_pb2_grpc
from mtda.grpc.servicer import MtdaServicer
import mtda.constants as CONSTS

# Pyro
try:
from Pyro5.compatibility import Pyro4
except ImportError:
import Pyro4


class Application:

Expand Down Expand Up @@ -79,11 +79,13 @@ class Application:
if status is False:
return False

# Start our RPC server
Pyro4.config.HOST = "0.0.0.0"
Pyro4.config.SERIALIZER = "marshal"
daemon = Pyro4.Daemon(port=self.agent.ctrlport)
daemon.register(self.agent, objectId="mtda.main")
# Start our gRPC server
self._grpc_server = grpc.server(
concurrent.futures.ThreadPoolExecutor(max_workers=10))
mtda_pb2_grpc.add_MtdaServiceServicer_to_server(
MtdaServicer(self.agent), self._grpc_server)
self._grpc_server.add_insecure_port(f'[::]:{self.agent.ctrlport}')
self._grpc_server.start()

# Initialize ZeroConf
interfaces = zeroconf.InterfaceChoice.All
Expand Down Expand Up @@ -111,14 +113,16 @@ class Application:

try:
sd.notify('READY=1')
daemon.requestLoop()
self._grpc_server.wait_for_termination()
except KeyboardInterrupt:
self.stop()
return True

def stop(self, signum=0, frame=None):
if self.zerosrv is not None:
self.zeroconf.unregister_service(self.zerosrv)
if hasattr(self, '_grpc_server'):
self._grpc_server.stop(grace=5)
if self.agent is not None:
self.agent.stop()
sys.exit(signum)
Expand Down
Loading
Loading