#! /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 --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())