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
6 changes: 6 additions & 0 deletions debian/changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
mtda (0.47-0) testing; urgency=medium

* debian: open development window for version 0.47

-- Cedric Hombourger <[email protected]> Thu, 09 Apr 2026 08:00:00 +0100

mtda (0.46-1) testing; urgency=medium

[ Cedric Hombourger ]
Expand Down
31 changes: 24 additions & 7 deletions mtda-cli
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ from argparse import ArgumentParser, RawTextHelpFormatter
from mtda.main import MultiTenantDeviceAccess
from mtda.client import Client
from mtda.console.screen import ScreenOutput
import mtda.constants as CONSTS


def human_readable_bytes(size):
Expand All @@ -39,11 +40,27 @@ class AppOutput(ScreenOutput):
self.app = app

def on_event(self, event):
event = event.split()
info = event.split()
domain = info[0] if info else ""

if domain == CONSTS.EVENTS.CONNECTION:
status = info[1] if len(info) > 1 else ""
if status == CONSTS.CONNECTION.LOST:
return
elif status == CONSTS.CONNECTION.RECONNECTING:
backoff = info[2] if len(info) > 2 else "?"
msg = f"\r*** connection lost, reconnecting in {backoff}s..."
msg = f"{msg:<60}"
sys.stdout.write(msg)
sys.stdout.flush()
elif status == CONSTS.CONNECTION.ESTABLISHED:
sys.stdout.write(f"\r{'':60}\r")
sys.stdout.flush()
return

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

app = self.app
Expand All @@ -52,11 +69,11 @@ class AppOutput(ScreenOutput):
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])
bytes_total = int(event[3])
speed = float(event[4])
bytes_read = int(info[2])
bytes_total = int(info[3])
speed = float(info[4])
# bytes written contains the total processed bytes of the output stream (writes + seeks)
bytes_written = int(event[5])
bytes_written = int(info[5])

self._progress(image, bytes_read, bytes_total, bytes_written, speed)

Expand Down
2 changes: 1 addition & 1 deletion mtda/__version__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@

__license__ = 'MIT'
__copyright__ = 'Copyright (C) 2026 Siemens AG'
__version__ = '0.46'
__version__ = '0.47'
58 changes: 41 additions & 17 deletions mtda/console/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

# System imports
import grpc
import time

from mtda.grpc import mtda_pb2, mtda_pb2_grpc

Expand Down Expand Up @@ -50,26 +51,49 @@ def dispatch(self, topic, data):
data = data.encode("utf-8")
self.write(data)

def _connection_event(self, status):
"""Emit a client-side CONNECTION event through the dispatch chain."""
event = f"{CONSTS.EVENTS.CONNECTION} {status}"
self.dispatch(CONSTS.CHANNEL.EVENTS, event.encode("utf-8"))

def reader(self):
target = f"{self.host}:{self.port}"
self._channel = grpc.insecure_channel(target)
stub = mtda_pb2_grpc.MtdaServiceStub(self._channel)
try:
self._stream = stub.Subscribe(mtda_pb2.Empty())
topics = self._topics()
for msg in self._stream:
backoff = 1
max_backoff = 30
while not self.exiting:
try:
self._channel = grpc.insecure_channel(target)
stub = mtda_pb2_grpc.MtdaServiceStub(self._channel)
self._stream = stub.Subscribe(mtda_pb2.Empty())
topics = self._topics()
connected = False
for msg in self._stream:
if not connected:
backoff = 1
self._connection_event(CONSTS.CONNECTION.ESTABLISHED)
connected = True
if self.exiting:
return
topic = msg.topic
if isinstance(topic, str):
topic = topic.encode("utf-8")
if topic in topics:
self.dispatch(topic, msg.data)
except grpc.RpcError:
if self.exiting:
break
# Normalise topic to bytes for consistent comparison
topic = msg.topic
if isinstance(topic, str):
topic = topic.encode("utf-8")
if topic in topics:
self.dispatch(topic, msg.data)
except grpc.RpcError:
pass
finally:
self._stream = None
return
self._connection_event(CONSTS.CONNECTION.LOST)
finally:
self._stream = None
if self._channel is not None:
try:
self._channel.close()
except Exception:
pass
self._channel = None
self._connection_event(f"{CONSTS.CONNECTION.RECONNECTING} {backoff}")
time.sleep(backoff)
backoff = min(backoff * 2, max_backoff)

def stop(self):
super().stop()
Expand Down
7 changes: 7 additions & 0 deletions mtda/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,15 @@ class DEFAULTS:
IMAGE_FILESIZE = 8*1024**3


class CONNECTION:
ESTABLISHED = "ESTABLISHED"
LOST = "LOST"
RECONNECTING = "RECONNECTING"


class EVENTS:
INTERVAL = 30
CONNECTION = "CONNECTION"
POWER = "POWER"
SESSION = "SESSION"
STORAGE = "STORAGE"
Expand Down
Loading