def run_async(args): logger.info("Starting up!") loop = asyncio.get_event_loop() logger.debug("Setting up Demo Satellite") demo_sat = DemoSat(name="Space Oddity") logger.debug("Setting up MajorTom") gateway = GatewayAPI(host=args.majortomhost, gateway_token=args.gatewaytoken, basic_auth=args.basicauth, command_callback=demo_sat.command_callback, cancel_callback=demo_sat.cancel_callback, http=args.http) logger.debug("Connecting to MajorTom") asyncio.ensure_future(gateway.connect_with_retries()) logger.debug("Sending Command Definitions") asyncio.ensure_future( gateway.update_command_definitions(system=demo_sat.name, definitions=demo_sat.definitions)) logger.debug("Starting Event Loop") loop.run_forever()
def run_sync(args): logger.debug("Starting Event Loop") loop = asyncio.get_event_loop() # Gateways are the link between the generic interfaces of Major Tom and the specifics of your # satellite(s) and groundstation(s). They can be designed to handle one or more satellites and # one or more groundstations. logger.debug("Setting up Gateway") gateway = Gateway() # Gateways use a websocket API, and we have a library to make the interface easier. # We instantiate the API, making sure to specify both sides of the connection: # - The Major Tom side, which requires a host and authentication # - The Gateway side, which specifies all the callbacks to be used when Major Tom communicates with this Gateway logger.debug("Setting up websocket connection") websocket_connection = GatewayAPI( host=args.majortomhost, gateway_token=args.gatewaytoken, basic_auth=args.basicauth, http=args.http, command_callback=gateway.command_callback, error_callback=gateway.error_callback, rate_limit_callback=gateway.rate_limit_callback, cancel_callback=gateway.cancel_callback, transit_callback=gateway.transit_callback, received_blob_callback=gateway.received_blob_callback, ) # It is useful to have a reference to the websocket api within your Gateway gateway.api = websocket_connection # Connect to MT asyncio.ensure_future(websocket_connection.connect_with_retries()) # To make it easier to interact with this Gateway, we are going to configure a bunch of commands for a satellite # called "Example FlatSat". Please see the associated json file to see the list of commands. logger.debug( "Setting up Example Flatsat satellite and associated commands") with open('satellite/example_commands.json', 'r') as f: command_defs = json.loads(f.read()) asyncio.ensure_future( websocket_connection.update_command_definitions( system="Example FlatSat", definitions=command_defs["definitions"])) try: loop.run_forever() except KeyboardInterrupt: loop.run_until_complete(loop.shutdown_asyncgens()) loop.close()
async def test_sync_callbacks_can_be_run_async(): def cb(command, *args, **kwargs): time.sleep(1) logging.debug(f"Finished command {command.id}") gw = GatewayAPI("host", "gateway_token", command_callback=cb) async def ticker(): for i in range(10): yield json.dumps({ "type": "command", "command": { "id": i, "type": "get_batt", "system": "ISS", "fields": [] } }) await asyncio.sleep(0.1) logging.info("start await") async for message in ticker(): await gw.handle_message(message) logging.info("end await") # The sync commands should finish before this is done # If they don't, a bunch of errors will (hopefully) be generated. await asyncio.sleep(3)
async def test_calls_command_callback(callback_mock): gw = GatewayAPI("host", "gateway_token", command_callback=callback_mock) await gw.handle_message(MESSAGE) await asyncio.sleep(1) # Make sure that the command callback was called with the command and Gateway callback_mock.assert_called_once_with(TypeMatcher(Command), TypeMatcher(GatewayAPI))
async def test_fails_when_no_command_callback(monkeypatch): gw = GatewayAPI("host", "gateway_token") # Monkey-patch fail command, which should be called when a command callback doesn't exist mock_fail_command = AsyncMock() monkeypatch.setattr(gw, "fail_command", mock_fail_command) await gw.handle_message(MESSAGE) await asyncio.sleep(1) assert mock_fail_command.called
async def test_calls_transit_callback(callback_mock): gw = GatewayAPI("host", "gateway_token", transit_callback=callback_mock) # ToDo: Update this example message message = { "type": "transit", } res = await gw.handle_message(json.dumps(message)) # The transit callback is given the raw message callback_mock.assert_called_once_with(message)
async def test_error_propagation_on_SYNC_command_callback(caplog): def cb(*args, **kwargs): # You should NOT see 'Task exception was never retrieved' raise RuntimeError("This exception message should be visible.") gw = GatewayAPI("host", "gateway_token", command_callback=cb) await gw.handle_message(MESSAGE) await asyncio.sleep(1) # Give time for the callback to run assert 'This exception message should be visible' in caplog.text
async def test_calls_SYNC_command_callback(): result = {"worked": False} def cb(*args, **kwargs): result["worked"] = True gw = GatewayAPI("host", "gateway_token", command_callback=cb) await gw.handle_message(MESSAGE) await asyncio.sleep(1) assert result["worked"]
async def test_calls_error_callback(callback_mock): gw = GatewayAPI("host", "gateway_token", error_callback=callback_mock) message = { "type": "error", "error": "This Gateway's token has been rotated. Please use the new one.", "disconnect": True } res = await gw.handle_message(json.dumps(message)) # The error callback is given the raw message callback_mock.assert_called_once_with(message)
async def test_calls_command_callback_v2(): ''' This test uses an inline async coroutine instead of a mock. ''' result = {"worked": False} async def cb(*args, **kwargs): result["worked"] = True gw = GatewayAPI("host", "gateway_token", command_callback=cb) await gw.handle_message(MESSAGE) await asyncio.sleep(1) assert result["worked"]
async def test_initializes_mission_name_from_hello_message(callback_mock): gw = GatewayAPI("host", "gateway_token") message = json.dumps({ "type": "hello", "hello": { "mission": "MISSION NAME" }, }) await gw.handle_message(message) await asyncio.sleep(1) assert gw.mission_name == "MISSION NAME"
async def test_calls_transit_callback(): result = {"worked": False} def cb(*args, **kwargs): result["worked"] = True gw = GatewayAPI("host", "gateway_token", transit_callback=cb) # ToDo: Update this example message message = { "type": "transit", } await gw.handle_message(json.dumps(message)) await asyncio.sleep(1)
async def test_calls_cancel_callback(callback_mock): gw = GatewayAPI("host", "gateway_token", cancel_callback=callback_mock) message = json.dumps({ "type": "cancel", "timestamp": 1528391020767, "command": { "id": 20 } }) res = await gw.handle_message(message) # The cancel callback is called with the command id and the gateway callback_mock.assert_called_once_with(20, TypeMatcher(GatewayAPI))
async def test_calls_SYNC_error_callback(): result = {"worked": False} def cb(*args, **kwargs): result["worked"] = True gw = GatewayAPI("host", "gateway_token", error_callback=cb) message = { "type": "error", "error": "This Gateway's token has been rotated. Please use the new one.", "disconnect": True } await gw.handle_message(json.dumps(message)) await asyncio.sleep(1)
async def test_calls_command_callback(callback_mock): gw = GatewayAPI("host", "gateway_token", command_callback=callback_mock) message = json.dumps({ "type": "command", "command": { "id": 4, "type": "get_battery", "system": "ISS", "fields": [] } }) res = await gw.handle_message(message) # Make sure that the command callback was called with the command and Gateway callback_mock.assert_called_once_with(TypeMatcher(Command), TypeMatcher(GatewayAPI))
async def test_calls_rate_limit_callback(callback_mock): gw = GatewayAPI("host", "gateway_token", rate_limit_callback=callback_mock) message = { "type": "rate_limit", "rate_limit": { "rate": 60, "retry_after": 0.5, "error": "Rate limit exceeded. Please limit request rate to a burst of 20 and an average of 60/second.", } } res = await gw.handle_message(json.dumps(message)) # The rate limit callback is given the raw message callback_mock.assert_called_once_with(message)
async def test_calls_SYNC_cancel_callback(): result = {"worked": False} def cb(*args, **kwargs): result["worked"] = True gw = GatewayAPI("host", "gateway_token", cancel_callback=cb) message = json.dumps({ "type": "cancel", "timestamp": 1528391020767, "command": { "id": 20 } }) await gw.handle_message(message) await asyncio.sleep(1) assert result["worked"]
async def test_fails_when_no_command_callback(monkeypatch): gw = GatewayAPI("host", "gateway_token") # Monkey-patch fail command, which should be called when a command callback doesn't exist mock_fail_command = AsyncMock() monkeypatch.setattr(gw, "fail_command", mock_fail_command) message = json.dumps({ "type": "command", "command": { "id": 4, "type": "get_battery", "system": "ISS", "fields": [] } }) res = await gw.handle_message(message) assert (None == res) assert mock_fail_command.called
async def test_calls_received_blob_callback(callback_mock): gw = GatewayAPI("host", "gateway_token", received_blob_callback=callback_mock) blob = b"I am a blob" message = json.dumps({ "type": "received_blob", "blob": base64.b64encode(blob).decode("utf-8"), "context": { "norad_id": 12345 }, "metadata": { "gsn_timestamp": 1234567890, "majortom_timestamp": 1234567890 } }) res = await gw.handle_message(message) # The received_blob callback is given the decoded blob, the context, and the gateway callback_mock.assert_called_once_with(blob, ANY, TypeMatcher(GatewayAPI))
async def test_calls_SYNC_received_blob_callback(): result = {"worked": False} def cb(*args, **kwargs): result["worked"] = True gw = GatewayAPI("host", "gateway_token", received_blob_callback=cb) blob = b"I am a blob" message = json.dumps({ "type": "received_blob", "blob": base64.b64encode(blob).decode("utf-8"), "context": { "norad_id": 12345 }, "metadata": { "gsn_timestamp": 1234567890, "majortom_timestamp": 1234567890 } }) await gw.handle_message(message) await asyncio.sleep(1)
async def test_calls_SYNC_rate_limit_callback(): result = {"worked": False} def cb(*args, **kwargs): result["worked"] = True gw = GatewayAPI("host", "gateway_token", rate_limit_callback=cb) message = { "type": "rate_limit", "rate_limit": { "rate": 60, "retry_after": 0.5, "error": "Rate limit exceeded. Please limit request rate to a burst of 20 and an average of 60/second.", } } await gw.handle_message(json.dumps(message)) await asyncio.sleep(1) assert result["worked"]
level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') else: logging.basicConfig( level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger.info("Starting up!") loop = asyncio.get_event_loop() logger.debug("Setting up WinSAT-1 Satellite") winsat = WinSat(name="WinSAT-1") logger.debug("Setting up MajorTom") gateway = GatewayAPI(host=args.majortomhost, gateway_token=args.gatewaytoken, basic_auth=args.basicauth, command_callback=winsat.command_callback, cancel_callback=winsat.cancel_callback, http=args.http) logger.debug("Connecting to MajorTom") asyncio.ensure_future(gateway.connect_with_retries()) logger.debug("Sending Command Definitions") asyncio.ensure_future( gateway.update_command_definitions(system=winsat.name, definitions=winsat.definitions)) logger.debug("Starting Event Loop") loop.run_forever()
def test_required_args(): with pytest.raises(TypeError): gw = GatewayAPI()