Files
qt5-legatus-legacy/Lts/Tools/InfrastructureManagement/python/ism_client.py
T
2026-08-13 18:33:10 +02:00

1019 lines
37 KiB
Python
Executable File

#! /usr/bin/env python3
#
# Dieses Tool kann die Websocket-Schnittstelle vom InfrastructureManagement bedienen.
# Bisher sind folgende Funktionalitäten implementiert:
# - Man kann sich eine Übersicht über die Gebäude, deren MountingPoints und der Assets geben lassen.
# - Man kann Assets mounten und unmounten
#
# Für Hilfe:
# $ ism_client.py --help
# $ ism_client.py <subcommand> --help
#
# @author Christian Fröbel
#
from argparse import ArgumentParser, RawDescriptionHelpFormatter
from websocket import WebSocketApp, ABNF
from uuid import uuid1
import json
#import traceback
import functools
from io import StringIO
from queue import Queue
from threading import Thread
from contextlib import contextmanager
import sys
LIGHT_GREEN = '\x1b[32;1m'
LIGHT_PURPLE = '\x1b[35;1m'
RESET = '\x1b[0m'
IN_MARKER = '{}<=={}'.format(LIGHT_PURPLE, RESET)
OUT_MARKER = '{}==>{}'.format(LIGHT_GREEN, RESET)
class Cache(object):
# @param entity: String, der den Datentyp repräsentiert; nur zu Debugging-Zwecken.
def __init__(self, entity):
self._entity = entity
self._cache = {} # id -> item
def register(self, item):
assert item.id not in self._cache, 'Item is registered already'
self._cache[item.id] = item
def findById(self, id_):
try:
return self._cache[id_]
except KeyError:
assert False, 'Invalid {}-Id: {}'.format(self._entity, id_)
def findByName(self, name):
foundItem = None
for item in self._cache.values():
if item.name == name:
assert foundItem is None, 'Name of {} is not unique: {}'.format(self._entity, name)
foundItem = item
assert foundItem is not None, 'No {} with that name: {}'.format(self._entity, name)
return foundItem
def findAll(self):
return self._cache.values()
def __len__(self):
return len(self._cache)
buildings = Cache('Building')
mountingPoints = Cache('MountingPoint')
assets = Cache('Asset')
assetTypes = Cache('AssetType')
mountingPlans = Cache('Plan')
class Building(object):
@staticmethod
def create(id_, name, mseId):
building = Building(id_, name, mseId)
buildings.register(building)
return building
def __init__(self, id_, name, mseId):
self._id = id_
self._name = name
self._mseId = mseId
self._mountingPoints = []
#
# Die Verknüpfung ist bidirektional und wird implizit beim Erzeugen des MountingPoints angelegt.
#
def addMountingPoint(self, mountingPoint):
self._mountingPoints.append(mountingPoint)
def mountingPoints(self):
return iter(self._mountingPoints)
def findMountingPointByName(self, name):
found = None
for mp in self._mountingPoints:
if mp.name != name:
continue
assert found is None, 'Name of MountingPoint is not unique in Building {}: {}'.format(self._name, name)
found = mp
assert found is not None, 'No MountingPoint with that name in Building {}:{}'.format(self._name, name)
return found
@property
def id(self): return self._id
@property
def name(self): return self._name
@property
def mseId(self): return self._mseId
def __str__(self):
s = StringIO()
mseId = ''
if self._mseId is not None:
mseId = ', mse: {}'.format(self._mseId)
s.write('Building: {}({}{})\n'.format(self._name, self._id, mseId))
for mp in sorted(self._mountingPoints, key=lambda mp: mp.type):
s.write(' {}\n'.format(mp))
return s.getvalue()
class MountingPoint(object):
@staticmethod
def create(id_, name, buildingId, assetTypeId):
mountingPoint = MountingPoint(id_, name, buildingId, assetTypeId)
mountingPoints.register(mountingPoint)
return mountingPoint
def __init__(self, id_, name, buildingId, assetTypeId):
self._id = id_
self._name = name
self._building = buildings.findById(buildingId)
self._building.addMountingPoint(self)
self._assetType = assetTypes.findById(assetTypeId)
self._mountedAsset = None # optional
#
# Die Verknüpfung ist bidirektional und wird implizit beim Erzeugen des Assets angelegt.
#
def mountAsset(self, asset):
self._mountedAsset = asset
@property
def id(self): return self._id
@property
def name(self): return self._name
@property
def type(self): return self._assetType
@property
def isMounted(self): return self._mountedAsset is not None
@property
def building(self): return self._building
@property
def asset(self):
assert self.isMounted, 'MountingPoint is not mounted'
return self._mountedAsset
def __str__(self):
s = 'MountingPoint: {}({}), {},'.format(self._name, self._id, self._assetType)
if self._mountedAsset is None:
s += ' not mounted'
else:
s += ' {}mounted{}: {}({})'.format(LIGHT_GREEN, RESET, self._mountedAsset.name, self._mountedAsset.id)
return s
class Asset(object):
@staticmethod
def create(id_, name, assetTypeId, mountingPointId):
asset = Asset(id_, name, assetTypeId, mountingPointId)
assets.register(asset)
return asset
def __init__(self, id_, name, assetTypeId, mountingPointId):
self._id = id_
self._name = name
self._assetType = assetTypes.findById(assetTypeId)
self._mountedOn = None # optional, der MountingPoint, an dem das Asset gemountet ist
if mountingPointId != -1:
self._mountedOn = mountingPoints.findById(mountingPointId)
self._mountedOn.mountAsset(self)
@property
def id(self): return self._id
@property
def name(self): return self._name
@property
def type(self): return self._assetType
@property
def isMounted(self): return self._mountedOn is not None
@property
def mountingPoint(self):
assert self.isMounted, 'Asset is not mounted'
return self._mountedOn
def __str__(self):
s = 'Asset: {}({}), {},'.format(self._name, self._id, self._assetType)
if self._mountedOn is None:
s += ' not mounted'
else:
s += ' {}mounted{}: {}({}) [{}]'.format(LIGHT_GREEN, RESET, self._mountedOn.name, self._mountedOn.id, self._mountedOn.building.name)
return s
class AssetType(object):
@staticmethod
def create(id_, name):
assetType = AssetType(id_, name)
assetTypes.register(assetType)
return assetType
def __init__(self, id_, name):
self._id = id_
self._name = name
@property
def id(self): return self._id
@property
def name(self): return self._name
def __str__(self):
return 'AssetType: {}'.format(self._name)
def __lt__(self, other):
return self._id < other._id
#
# Der Einrüstplan.
# Er enthält eine Liste von MountingPoints, die eingerüstet werden sollen.
#
class MountingPlan(object):
@staticmethod
def create(id_, name):
plan = MountingPlan(id_, name)
mountingPlans.register(plan)
return plan
def __init__(self, id_, name):
self._id = id_
self._name = name
self._mountingPoints = []
def add(self, mountingPoints):
self._mountingPoints += mountingPoints
# Eindeutigkeit sicherstellen
uniqueMountingPoints = set(self._mountingPoints)
assert len(uniqueMountingPoints) == len(self._mountingPoints)
@property
def id(self): return self._id
@property
def name(self): return self._name
def __str__(self):
byBuilding = {} # Building -> [MountingPoint, ...]
for mp in self._mountingPoints:
building = mp.building
if building in byBuilding:
byBuilding[building].append(mp)
else:
byBuilding[building] = [mp]
s = StringIO()
s.write('Plan: {}({})\n'.format(self._name, self._id))
for building in byBuilding:
s.write(' {}({})\n'.format(building.name, building.id))
for mp in sorted(byBuilding[building], key=lambda mp: mp.name):
s.write(' {}\n'.format(mp))
return s.getvalue()
#
# So eine Klasse kapselt einfach nur den Zugriff auf ein Dictionary, dessen Keys
# Strings sind, die außerdem valide Identifier darstellen: die Keys werden Attribute der GeneralConfig-Instanz.
# @code
# config = DictAccess({'HOME': '/path/to/home', 'USER': 'froebi'})
# config.HOME # => '/path/to/name'
# @endcode
#
class DictAccess(object):
#
# @param definedVariables: Ein Dictionary mit den Variablennamen als Keys und deren Wert als Values.
#
def __init__(self, definedVariables):
self._variables = definedVariables
def __getattribute__(self, name):
if name == '_variables':
return object.__getattribute__(self, name)
try:
return self._variables[name]
except KeyError:
return object.__getattribute__(self, name)
class WsClient(object):
def __init__(self, host, port, messageQueue, doLog):
self._doLog = doLog
self._messageQueue = messageQueue
self._ws = WebSocketApp('ws://{}:{}'.format(host, port), on_open=self._onOpen, on_close=self._onClose, on_data=self._onData, on_error=self._onError)
def run(self):
self._ws.run_forever(ping_timeout=1)
def close(self):
# Komischerweise dauert das Schließen des WebSockets manchmal etwas länger.
# Intern wird noch ein Close-Frame geschickt und auf die Antwort gewartet.
# Wie lange gewartet wird, sollte man über den Timeout steuern können;
# das scheint aber nicht zuverlässig zu funktionieren.
# Wie es aussieht, hängt er im select() (websocket/_app.py), wo ein Timeout von 10 angegeben ist.
# Der Timeout im select() ist der als ping_timeout übergebene Parameter von run_forever().
#
# Das Problem scheint vielmehr darin begründet zu sein, dass wir das close() aus einem anderen
# Thread heraus machen als dem, in dem run_forever() läuft.
# Intern wird bei close() tatsächlich auch der Socket gelesen; der eine Thread hängt also im select(),
# der andere macht das recv().
self._ws.close(timeout=1)
#
# @param commandType: z.B. 'mount'
# @param data: das data-Property des Commands JSON-kompatibler Typ -- also z.B dict oder None.
# @return Die UUID, die für das Command vergeben wurde.
#
def sendCommand(self, commandType, data):
uuid = uuid1()
command = {'type': commandType, 'uuid': str(uuid), 'data': data}
if self._doLog:
print('{} {}: {} '.format(OUT_MARKER, command['type'], json.dumps(command)))
self._ws.send(json.dumps(command), ABNF.OPCODE_TEXT)
return uuid
def _onOpen(self, ws):
#
# Achtung: spezielle interne Message, die dazu verwendet wird, dem
# Consumer zu sagen, dass wir verbunden sind.
#
self._messageQueue.put('connected')
def _onClose(self, ws):
pass
#
# Wenn opcode == TEXT, dann ist data ein Unicode-String,
# sonst ist data ein Byte-String.
#
def _onData(self, ws, data, opcode, fin):
# print('_onData: opcode: {}, data: {}, fin: {}'.format(ABNF.OPCODE_MAP[opcode], data, fin))
# Workaround für lange Messages, die in mehr als einem Frame gesendet werden:
# Wir bekommen hier offensichtlich schon die zusammengesetzten Daten, aber der opcode ist falsch: der ist dann OPCODE_CONT.
# fin ist also immer True. Das Problem ist, dass man eben nicht mehr erkennen kann, ob es sich um Text oder Binary Data handelt.
# Wir gehen hier jetzt einfach davon aus, dass es immer Text ist.
assert fin, 'currently only atomic messages are supported'
assert opcode in (ABNF.OPCODE_TEXT, ABNF.OPCODE_CONT), 'currently only text messages supported'
jsonObject = json.loads(data)
assert isinstance(jsonObject, dict), 'data must be a dictionary'
messageType = jsonObject['type']
if self._doLog:
print('{} {}: {}'.format(IN_MARKER, messageType, data))
self._messageQueue.put(jsonObject)
def _onError(self, ws, exception):
#traceback.print_exception(exception.__class__, exception, exception.__traceback__, sys.stdout)
print('{}: {}'.format(exception.__class__.__name__, exception))
#
# Eine kleine Hilfsklasse, die auf einfache Weise hilft, zu erkennen,
# wann der Initialisierungsprozess abgeschlossen ist.
# Nach getEssentials bekommen wir (u.a.) die AssetTypes.
# Für jeden AssetType fragen wir alle Assets an (getAssets).
# Wenn wir zu jeder Anfrage die Antworte gesehen haben,
# sind wir fertig.
#
class InitHelper(object):
def __init__(self):
self._pendingRequestCount = 0
@property
def pendingCount(self):
return self._pendingRequestCount
# Muss man rufen, wenn man eine neue Anfrage rausgeschickt hat.
def push(self):
self._pendingRequestCount += 1
# Muss man rufen, wenn man eine Antwort bekommen hat.
# Liefert True, wenn fertig.
def pop(self):
self._pendingRequestCount -= 1
return self._pendingRequestCount == 0
#
# Der Initializer liest Commands aus der CommandQueue und baut daraus
# die Strukturen für Buildings, MountingPoints und Assets auf.
# Dazu macht er auch eigene Anfragen an den Server.
# Wenn er fertig ist, beendet sich die run()-Methode.
#
class Initializer(object):
def __init__(self, messageQueue, sender):
self._sender = sender
self._messageQueue = messageQueue
self._initHelper = InitHelper()
self._finished = False
def run(self):
self._waitForConnected()
while not self._finished:
self._processMessage()
#
# Die erste Message in der Queue ist ein internes Command -- einfach nur ein String.
# Das ist für uns das Signal, dass wir mit dem Server verbunden sind und
# die Initialisierung via getEssentials starten können.
#
def _waitForConnected(self):
internalCommand = self._messageQueue.get()
assert internalCommand == 'connected'
self._getEssentials()
def _processMessage(self):
jsonObject = self._messageQueue.get()
messageType = jsonObject['type']
if messageType == 'essentials':
self._handleEssentials(jsonObject)
elif messageType == 'assets':
self._handleAssets(jsonObject)
elif messageType == 'error':
print('Service responded with error: {}'.format(jsonObject['data']['errorMessage']))
sys.exit(1)
else:
assert False, 'unknown/unhandled messageType: {}'.format(messageType)
#
# Mittlerweile antwortet der Server auf getEssentials mit nur einem Telegramm -- essentials --,
# welches die vorherigen Einzeltelegramme einfach zusammenfasst.
#
def _handleEssentials(self, jsonObject):
data = DictAccess(jsonObject['data'])
buildings = data.objects
mountingPoints = data.mountingPoints
assetTypes = data.assetTypes
for b in buildings:
self._handleObject(b)
# AssetTypes vor den MountingPoints behandeln, denn das Erzeugen der MountingPoints
# setzt die AssetTypes voraus.
for t in assetTypes:
self._handleAssetType(t)
for mp in mountingPoints:
self._handleMountingPoint(mp)
#
# object wird als Antwort auf getEssentials gesendet.
#
def _handleObject(self, dataObject):
data = DictAccess(dataObject)
buildingId = data.id
name = data.designation
try:
mseId = data.mseId
except KeyError:
mseId = None
building = Building.create(buildingId, name, mseId)
#
# Hier bekommen wir alle MountingPoints -- auch die nicht gemounteten.
#
def _handleMountingPoint(self, dataObject):
data = DictAccess(dataObject)
mountingPointId = data.id
name = data.designation
buildingId = data.objectId
supportedAssetTypeIds = data.supportedAssetTypeIds
assert len(supportedAssetTypeIds)
mountingPoint = MountingPoint.create(mountingPointId, name, buildingId, supportedAssetTypeIds[0])
#
# Als Antwort auf getEssentials bekommen wir auch eine Liste der vorhandenen Asset-Typen.
# Daraufhin fragen wir alle Assets von diesem Typ an.
#
def _handleAssetType(self, dataObject):
data = DictAccess(dataObject)
assetTypeId = data.id
name = data.designation
AssetType.create(assetTypeId, name)
self._getAssets(assetTypeId)
self._initHelper.push()
#
# Antwort auf getAssets
#
def _handleAssets(self, jsonObject):
data = jsonObject['data']
for asset in [DictAccess(d) for d in data]:
assetId = asset.id
assetTypeId = asset.assetTypeId
name = asset.designation
mountingPointId = asset.mountingPointId
asset = Asset.create(assetId, name, assetTypeId, mountingPointId)
self._finished = self._initHelper.pop()
#
# getEssentials ist der Einstiegspunkt.
# Der Server antwortet darauf mit einer Message, die folgende Dinge enthält:
# - trainingArea
# - area
# - object
# - level
# - room
# - mountingPoint
# - assetType
# Im Moment interessieren wir uns nur für AssetType, Object (Building) und MountingPoint
#
def _getEssentials(self):
self._sender.sendCommand('getEssentials', None)
def _getAssets(self, assetTypeId):
data = {'assetTypeId': assetTypeId}
self._sender.sendCommand('getAssets', data)
#
# Initialisiert die Strukturen für die Einrüstpläne.
# Die run()-Methode beendet sich, wenn die Initialisierung abgeschlossen ist.
#
class PlanInitializer(object):
def __init__(self, messageQueue, client):
self._messageQueue = messageQueue
self._client = client
self._initHelper = InitHelper()
self._finished = False
def run(self):
self._getPlannedMounting()
while not self._finished:
self._processMessage()
def _getPlannedMounting(self):
self._client.sendCommand('getPlannedMountings', None)
def _getObjectMounting(self, planId, buildingId):
data = {'plannedMountingId': planId, 'objectId': buildingId}
self._client.sendCommand('getObjectMounting', data)
def _processMessage(self):
jsonObject = self._messageQueue.get()
messageType = jsonObject['type']
if messageType == 'plannedMountings':
self._handlePlannedMountings(jsonObject)
elif messageType == 'objectMounting':
self._handleObjectMounting(jsonObject)
elif messageType == 'error':
print('Service responded with error: {}'.format(jsonObject['data']['errorMessage']))
sys.exit(1)
def _handlePlannedMountings(self, jsonObject):
mountings = jsonObject['data']
for m in mountings:
self._handlePlannedMounting(m)
# Falls es keine Pläne gibt oder alle Pläne noch leer sind, werden ja keine
# getObjectMounting-Anfragen gemacht. Für den Fall müssen wir hier explizit
# self._finished auf True setzen.
if self._initHelper.pendingCount == 0:
self._finished = True
def _handlePlannedMounting(self, dataObject):
data = DictAccess(dataObject)
planId = data.plannedMountingId
name = data.designation
MountingPlan.create(planId, name)
buildings = data.mounting
for building in buildings:
buildingId = building['id']
self._getObjectMounting(planId, buildingId)
self._initHelper.push()
def _handleObjectMounting(self, jsonObject):
data = jsonObject['data']
plan = mountingPlans.findById(data['plannedMountingId'])
mps = []
for mp in data['planned']:
mps.append(mountingPoints.findById(mp['id']))
plan.add(mps)
self._finished = self._initHelper.pop()
#
# Hilfsklasse für das Abfragen des ISM-Ports über das WebSocket-Interface
# des ServiceManagements.
#
class IsmPortFinder(object):
def __init__(self, messageQueue, client):
self._messageQueue = messageQueue
self._sender = client
self._ismPort = None
self._finished = False
@property
def port(self):
assert self._ismPort is not None
return self._ismPort
def run(self):
self._waitForConnected()
while not self._finished:
self._processMessage()
#
# Die erste Message in der Queue ist ein internes Command -- einfach nur ein String.
# Das ist für uns das Signal, dass wir mit dem Server verbunden sind und
# die Initialisierung via getEssentials starten können.
#
def _waitForConnected(self):
internalCommand = self._messageQueue.get()
assert internalCommand == 'connected'
self._getPortInfo()
def _processMessage(self):
jsonObject = self._messageQueue.get()
messageType = jsonObject['type']
assert messageType == 'servicePortsInformation'
self._handlePortInfo(jsonObject)
def _getPortInfo(self):
self._sender.sendCommand('servicePortsInformationRequest', None)
def _handlePortInfo(self, jsonObject):
data = jsonObject['data']
for exerciseInfo in [DictAccess(d) for d in data]:
if exerciseInfo.exerciseId != 0:
continue
for serviceInfo in [DictAccess(d) for d in exerciseInfo.services]:
if serviceInfo.nameAbbrevation != 'ism':
continue
assert len(serviceInfo.ports) == 1 # Wie finden wir den gewünschten Port, wenn es mehrere gibt?
self._ismPort = serviceInfo.ports[0]['number']
break
assert self._ismPort is not None
self._finished = True
#
# Kleine Hilfsklasse, die einfach nur auf die Ok- oder Error-Reponse
# wartet, die z.B. auf ein mount-Command gesendet wird.
#
class ResponseWaiter(object):
#
# @param uuid: die UUID des Commands, auf dessen Response wir warten.
#
def __init__(self, messageQueue, uuid):
self._messageQueue = messageQueue
self._uuid = uuid
def wait(self):
while True:
jsonObject = self._messageQueue.get()
messageType = jsonObject['type']
data = DictAccess(jsonObject['data'])
if data.uuid != str(self._uuid):
continue
if messageType == 'ok':
return True, data.info
if messageType == 'error':
return False, data.errorMessage
assert False, 'Response must be either ok or error'
@contextmanager
def autoClose(client):
try:
yield client
finally:
client.close()
@contextmanager
def autoJoin(thread):
try:
yield thread
finally:
thread.join()
def _mainList(args, messageQueue, client):
for b in sorted(buildings.findAll(), key=lambda b: b.name):
mseId = ''
if b.mseId is not None:
mseId = ', mseId: {}'.format(b.mseId)
print('Building: {}({}{})'.format(b.name, b.id, mseId))
return 0
def _mainListBuildingByName(args, messageQueue, client):
print(buildings.findByName(args.building))
return 0
def _mainListBuildingById(args, messageQueue, client):
print(buildings.findById(args.buildingId))
return 0
def _mainListAssets(args, messageQueue, client):
for a in sorted(assets.findAll(), key=lambda a: a.type):
print(a)
return 0
def _mainMountByName(args, messageQueue, client):
building = None
if args.building is not None:
building = buildings.findByName(args.building)
return _mountByName(args.asset, args.mountingPoint, building, messageQueue, client)
def _mainMountById(args, messageQueue, client):
asset = assets.findById(args.assetId)
mountingPoint = mountingPoints.findById(args.mountingPointId)
return _mount(asset, mountingPoint, messageQueue, client)
def _mainMountBarcode(args, messageQueue, client):
data = {'assetTag': args.asset, 'mountingPointTag': args.mountingPoint}
uuid = client.sendCommand('mountTag', data)
waiter = ResponseWaiter(messageQueue, uuid)
return _waitForRequest(waiter, 'mounted successfully', 'mounting failed')
def _mainMountManyByName(args, messageQueue, client):
building = None
if args.building is not None:
building = buildings.findByName(args.building)
jsonList = json.loads(args.json)
for assetMountingPoint in jsonList:
assetName = assetMountingPoint[0]
mountingPointName = assetMountingPoint[1]
status = _mountByName(assetName, mountingPointName, building, messageQueue, client)
if status != 0:
return status
return 0
def _findMountingPointByName(name, buildingContext=None):
if buildingContext is None:
return mountingPoints.findByName(name)
else:
return buildingContext.findMountingPointByName(name)
def _mountByName(assetName, mountingPointName, building, messageQueue, client):
asset = assets.findByName(assetName)
mountingPoint = _findMountingPointByName(mountingPointName, building)
return _mount(asset, mountingPoint, messageQueue, client)
def _mount(asset, mountingPoint, messageQueue, client):
print('mounting {}({}) -> {}({})'.format(asset.name, asset.id, mountingPoint.name, mountingPoint.id))
data = {'assetId': asset.id, 'mountingPointId': mountingPoint.id}
uuid = client.sendCommand('mount', data)
waiter = ResponseWaiter(messageQueue, uuid)
return _waitForRequest(waiter, 'mounted successfully', 'mounting failed')
def _mainUnmountAssetByName(args, messageQueue, client):
asset = assets.findByName(args.asset)
return _unmountMountingPoint(asset.mountingPoint, messageQueue, client)
def _mainUnmountAssetById(args, messageQueue, client):
asset = assets.findById(args.assetId)
return _unmountMountingPoint(asset.mountingPoint, messageQueue, client)
def _mainUnmountMountingPointByName(args, messageQueue, client):
building = None
if args.building is not None:
building = buildings.findByName(args.building)
mountingPoint = _findMountingPointByName(args.mountingPoint, building)
return _unmountMountingPoint(mountingPoint, messageQueue, client)
def _mainUnmountMountingPointById(args, messageQueue, client):
mountingPoint = mountingPoints.findById(args.mountingPointId)
return _unmountMountingPoint(mountingPoint, messageQueue, client)
def _mainUnmountBuildingByName(args, messageQueue, client):
building = buildings.findByName(args.building)
return _unmountBuilding(building, messageQueue, client)
def _mainUnmountBuildingById(args, messageQueue, client):
building = buildings.findById(args.buildingId)
return _unmountBuilding(building, messageQueue, client)
def _unmountBuilding(building, messageQueue, client):
for mountingPoint in building.mountingPoints():
if not mountingPoint.isMounted:
continue
status = _unmountMountingPoint(mountingPoint, messageQueue, client)
if status != 0:
return status
return 0
def _mainUnmountAll(args, messageQueue, client):
for a in assets.findAll():
if not a.isMounted:
continue
status = _unmountMountingPoint(a.mountingPoint, messageQueue, client)
if status != 0:
return status
return 0
def _unmountMountingPoint(mountingPoint, messageQueue, client):
asset = mountingPoint.asset
print('unmounting {}({}) from {}({})'.format(asset.name, asset.id, mountingPoint.name, mountingPoint.id))
data = {'mountingPointId': mountingPoint.id}
uuid = client.sendCommand('unmountMountingPoint', data)
waiter = ResponseWaiter(messageQueue, uuid)
return _waitForRequest(waiter, 'unmounted successfully', 'unmounting failed')
def _mainListMountingPlans(args, messageQueue, client):
planInitializer = PlanInitializer(messageQueue, client)
planInitializer.run()
if len(mountingPlans) == 0:
print('No mounting plans available')
return 0
for p in mountingPlans.findAll():
print('Plan: {}({})'.format(p.name, p.id))
return 0
def _mainListMountingPlanByName(args, messageQueue, client):
planInitializer = PlanInitializer(messageQueue, client)
planInitializer.run()
plan = mountingPlans.findByName(args.plan)
print(plan)
def _mainListMountingPlanById(args, messageQueue, client):
planInitializer = PlanInitializer(messageQueue, client)
planInitializer.run()
plan = mountingPlans.findById(args.planId)
print(plan)
def _waitForRequest(waiter, okMessage, errorMessage):
status, info = waiter.wait()
if status:
print(okMessage)
return 0
else:
print('{}: {}'.format(errorMessage, info))
return 1
def _parseCmdLine():
description = 'hello'
parser = ArgumentParser(description=description, formatter_class=RawDescriptionHelpFormatter)
sharedArgs = ArgumentParser(add_help=False)
sharedArgs.add_argument('--host', default='localhost', help='hostname/ip; default: localhost')
sharedArgs.add_argument('--port', type=int, help='ISM port; if not given, the port will be determined via SCM WebSocket interface on port 7300')
sharedArgs.add_argument('--log-messages', '-l', action='store_true', default=False, dest='doLog', help='log ingoing and outgoing messages')
subparsers = parser.add_subparsers()
parser_list = subparsers.add_parser('list', parents=[sharedArgs], help='list all buildings', description='List buildings.')
parser_list.set_defaults(commandFunc=_mainList)
parser_listBuildingByName = subparsers.add_parser('list-building', parents=[sharedArgs], help='list a building', description='List all mounting points for a given building.')
parser_listBuildingByName.add_argument('building', help='name of building')
parser_listBuildingByName.set_defaults(commandFunc=_mainListBuildingByName)
parser_listBuildingById = subparsers.add_parser('list-building-by-id', parents=[sharedArgs], help='list a building by ID', description='List all mounting points for a given building.')
parser_listBuildingById.add_argument('buildingId', type=int, help='building ID')
parser_listBuildingById.set_defaults(commandFunc=_mainListBuildingById)
parser_listAssets = subparsers.add_parser('list-assets', parents=[sharedArgs], help='list all assets', description='List all assets.')
parser_listAssets.set_defaults(commandFunc=_mainListAssets)
parser_mountByName = subparsers.add_parser('mount', parents=[sharedArgs], help='mount asset', description='Mount an asset to a mounting point by name.')
parser_mountByName.add_argument('asset', help='name of asset')
parser_mountByName.add_argument('mountingPoint', help='name of mounting point')
parser_mountByName.add_argument('--building', help='search for mounting point in the building with that name in case the name is not globally unique')
parser_mountByName.set_defaults(commandFunc=_mainMountByName)
parser_mountById = subparsers.add_parser('mount-by-id', parents=[sharedArgs], help='mount by ID', description='Mount an asset to a mounting point by ID')
parser_mountById.add_argument('assetId', type=int, help='ID of asset')
parser_mountById.add_argument('mountingPointId', type=int, help='ID of mounting point')
parser_mountById.set_defaults(commandFunc=_mainMountById)
parser_mountBarcode = subparsers.add_parser('mount-barcode', parents=[sharedArgs], help='mount asset via barcode', description='Mount an asset to a mounting point by barcode.')
parser_mountBarcode.add_argument('asset', help='barcode of asset')
parser_mountBarcode.add_argument('mountingPoint', help='barcode of mounting point')
parser_mountBarcode.set_defaults(commandFunc=_mainMountBarcode)
parser_mountManyByName = subparsers.add_parser('mount-many', parents=[sharedArgs], help='mount many assets', description='Mount several assets to mounting points at once using their names.')
parser_mountManyByName.add_argument('json', help='JSON list of asset-mounting point pairs, e.g. [ ["asset1", "mp1"], ["asset2", "mp2"] ]')
parser_mountManyByName.add_argument('--building', help='search for mounting point in the building with that name in case the name is not globally unique')
parser_mountManyByName.set_defaults(commandFunc=_mainMountManyByName)
parser_unmountAssetByName = subparsers.add_parser('unmount-asset', parents=[sharedArgs], help='unmout asset', description='Unmount an asset by name.')
parser_unmountAssetByName.add_argument('asset', help='name of asset')
parser_unmountAssetByName.set_defaults(commandFunc=_mainUnmountAssetByName)
parser_unmountAssetById = subparsers.add_parser('unmount-asset-by-id', parents=[sharedArgs], help='unmout asset by ID', description='Unmount an asset by ID.')
parser_unmountAssetById.add_argument('assetId', type=int, help='ID of asset')
parser_unmountAssetById.set_defaults(commandFunc=_mainUnmountAssetById)
parser_unmountMountingPointByName = subparsers.add_parser('unmount-mounting-point', parents=[sharedArgs], help='unmout mounting point', description='Unmount a mounting point by name.')
parser_unmountMountingPointByName.add_argument('mountingPoint', help='name of mounting point')
parser_unmountMountingPointByName.add_argument('--building', help='search for mounting point in the building with that name in case the name is not globally unique')
parser_unmountMountingPointByName.set_defaults(commandFunc=_mainUnmountMountingPointByName)
parser_unmountMountingPointById = subparsers.add_parser('unmount-mounting-point-by-id', parents=[sharedArgs], help='unmout mounting point', description='Unmount a mounting point by ID.')
parser_unmountMountingPointById.add_argument('mountingPointId', type=int, help='ID of mounting point')
parser_unmountMountingPointById.set_defaults(commandFunc=_mainUnmountMountingPointById)
parser_unmountBuildingByName = subparsers.add_parser('unmount-building', parents=[sharedArgs], help='unmount building', description='Unmount all assets in a building.')
parser_unmountBuildingByName.add_argument('building', help='name of building')
parser_unmountBuildingByName.set_defaults(commandFunc=_mainUnmountBuildingByName)
parser_unmountBuildingById = subparsers.add_parser('unmount-building-by-id', parents=[sharedArgs], help='unmount building by ID', description='Unmount all assets in a building.')
parser_unmountBuildingById.add_argument('buildingId', type=int, help='ID of building')
parser_unmountBuildingById.set_defaults(commandFunc=_mainUnmountBuildingById)
parser_unmountAll = subparsers.add_parser('unmount-all', parents=[sharedArgs], help='unmount all assets', description='Unmount all assets.')
parser_unmountAll.set_defaults(commandFunc=_mainUnmountAll)
parser_listPlans = subparsers.add_parser('list-plans', parents=[sharedArgs], help='list available mounting plans', description='List available mounting plans')
parser_listPlans.set_defaults(commandFunc=_mainListMountingPlans)
parser_listPlanByName = subparsers.add_parser('list-plan', parents=[sharedArgs], help='list a mounting plan', description='List a mounting plan.')
parser_listPlanByName.add_argument('plan', help='name of plan')
parser_listPlanByName.set_defaults(commandFunc=_mainListMountingPlanByName)
parser_listPlanById = subparsers.add_parser('list-plan-by-id', parents=[sharedArgs], help='list a mounting plan by ID', description='List a mounting plan by ID.')
parser_listPlanById.add_argument('planId', type=int, help='ID of plan')
parser_listPlanById.set_defaults(commandFunc=_mainListMountingPlanById)
return parser.parse_args()
#
# Herausfinden des ISM-Ports über das Abfragen der WebSocket-Schnittstelle
# des ServiceManagements.
#
def _determineIsmPort(args):
messageQueue = Queue()
client = WsClient(args.host, 7300, messageQueue, args.doLog) # SCM-Port is fix
portFinder = IsmPortFinder(messageQueue, client)
clientThread = Thread(target=client.run)
clientThread.start()
with autoJoin(clientThread):
with autoClose(client):
portFinder.run() # beendet sich, wenn der Port gefunden wurde
return portFinder.port
def _main():
args = _parseCmdLine()
ismPort = args.port
if ismPort is None:
ismPort = _determineIsmPort(args)
messageQueue = Queue()
client = WsClient(args.host, ismPort, messageQueue, args.doLog)
initializer = Initializer(messageQueue, client)
clientThread = Thread(target=client.run)
clientThread.start()
with autoJoin(clientThread):
with autoClose(client):
initializer.run() # beendet sich, wenn initialisiert
retVal = args.commandFunc(args, messageQueue, client)
return retVal
if __name__ == '__main__':
sys.exit(_main())