def APRSISRXDevice(reactor, client, name=None, filter=None): """ client: an aprs.APRS object (see <https://pypi.python.org/pypi/aprs>) name: device label filter: filter on incoming data (see <http://www.aprs-is.net/javAPRSFilter.aspx>) """ # pylint: disable=redefined-builtin if name is None: name = 'APRS-IS ' + filter info = TelemetryStore( ) # TODO this is wrong, need to be able to output_message. def main_callback(line): # TODO: This print-both-formats code is duplicated from multimon.py; it should be a utility in this module instead. Also, we should maybe have a try block. log.msg(u'APRS: %r' % (line, )) message = parse_tnc2(line, time.time()) log.msg(u' -> %s' % (message, )) parsed = parse_tnc2(line, time.time()) info.receive(message) # client blocks in a loop, so set up a thread alive = True def threaded_callback(line): if not alive: raise StopIteration() reactor.callFromThread(main_callback, line) reactor.callInThread(client.receive, callback=threaded_callback, filter=filter) # TODO: Arrange so we can get close() callbacks and set alive=false # TODO: Allow the filter to be changed at runtime return Device(name=name, components={'aprs-is': info})
def __init__(self, devices={}, audio_config=None, features=_stub_features): if len(devices) <= 0: raise ValueError('Must have at least one RF device') gr.top_block.__init__(self, "SDR top block") self.__running = False # duplicate of GR state we can't reach, see __start_or_stop self.__has_a_useful_receiver = False # Configuration # TODO: device refactoring: Remove vestigial 'accessories' self._sources = {k: d for k, d in devices.iteritems() if d.can_receive()} self._accessories = accessories = {k: d for k, d in devices.iteritems() if not d.can_receive()} self.source_name = self._sources.keys()[0] # arbitrary valid initial value self.__rx_device_type = Enum({k: v.get_name() or k for (k, v) in self._sources.iteritems()}) # Audio early setup self.__audio_manager = AudioManager( # must be before contexts graph=self, audio_config=audio_config, stereo=features['stereo']) # Blocks etc. # TODO: device refactoring: remove 'source' concept (which is currently a device) # TODO: remove legacy no-underscore names, maybe get rid of self.source self.source = None self.__monitor_rx_driver = None self.monitor = MonitorSink( signal_type=SignalType(sample_rate=10000, kind='IQ'), # dummy value will be updated in _do_connect context=Context(self)) self.monitor.get_interested_cell().subscribe(self.__start_or_stop_later) self.__clip_probe = MaxProbe() # Receiver blocks (multiple, eventually) self._receivers = {} self._receiver_valid = {} # collections # TODO: No longer necessary to have these non-underscore names self.sources = CollectionState(self._sources) self.receivers = ReceiverCollection(self._receivers, self) self.accessories = CollectionState(accessories) self.__telemetry_store = TelemetryStore() # Flags, other state self.__needs_reconnect = [u'initialization'] self.__in_reconnect = False self.receiver_key_counter = 0 self.receiver_default_state = {} self.__cpu_calculator = LazyRateCalculator(lambda: time.clock()) # Initialization def hookup_vfo_callback(k, d): # function so as to not close over loop variable d.get_vfo_cell().subscribe(lambda: self.__device_vfo_callback(k)) for k, d in devices.iteritems(): hookup_vfo_callback(k, d) self._do_connect()
class TestAPRSTelemetryStore(unittest.TestCase): """ This is a test of APRSStation's implementation of ITelemetryObject. """ def setUp(self): self.clock = Clock() self.clock.advance(_dummy_receive_time) self.store = TelemetryStore(time_source=self.clock) def __receive(self, msg): expand_aprs_message(msg, self.store) def test_new_station(self): self.assertEqual([], self.store.state().keys()) self.__receive( parse_tnc2( 'N6WKZ-3>APU25N,WB6TMS-3*,N6ZX-3*,WIDE2*:=3746.42N112226.00W# {UIV32N}', _dummy_receive_time)) self.assertEqual(['N6WKZ-3'], self.store.state().keys()) # TODO: this test makes less sense now that expand_aprs_message is separate def test_object_item_report(self): self.__receive( parse_tnc2( 'KE6AFE-2>APU25N,WR6ABD*,NCA1:;TFCSCRUZ *160323z3655.94N\12200.92W?70 In 10 Minutes', _dummy_receive_time)) self.assertEqual({'KE6AFE-2', 'TFCSCRUZ '}, set(self.store.state().keys())) # TODO test value of object def test_object_kill(self): self.__receive( parse_tnc2( 'KE6AFE-2>APU25N,WR6ABD*,NCA1:;TFCSCRUZ *160323z3655.94N\12200.92W?70 In 10 Minutes', _dummy_receive_time)) self.assertEqual({'KE6AFE-2', 'TFCSCRUZ '}, set(self.store.state().keys())) self.__receive( parse_tnc2('FOO>BAR:;TFCSCRUZ _160323z3655.94N\12200.92W?', _dummy_receive_time)) self.clock.advance(0) self.assertEqual({'FOO', 'KE6AFE-2'}, set(self.store.state().keys())) def test_drop_old(self): self.__receive(parse_tnc2('FOO>RX:>', _dummy_receive_time)) self.assertEqual(['FOO'], self.store.state().keys()) self.clock.advance(1799.5) self.__receive(parse_tnc2('BAR>RX:>', _dummy_receive_time + 1799.5)) self.assertEqual({'BAR', 'FOO'}, set(self.store.state().keys())) self.clock.advance(0.5) self.__receive(parse_tnc2('BAR>RX:>', _dummy_receive_time + 1800)) self.assertEqual(['BAR'], self.store.state().keys())
class TestAPRSTelemetryStore(unittest.TestCase): """ This is a test of APRSStation's implementation of ITelemetryObject. """ def setUp(self): self.clock = Clock() self.clock.advance(_dummy_receive_time) self.store = TelemetryStore(time_source=self.clock) def __receive(self, msg): expand_aprs_message(msg, self.store) def test_new_station(self): self.assertEqual([], self.store.state().keys()) self.__receive(parse_tnc2( 'N6WKZ-3>APU25N,WB6TMS-3*,N6ZX-3*,WIDE2*:=3746.42N112226.00W# {UIV32N}', _dummy_receive_time)) self.assertEqual(['N6WKZ-3'], self.store.state().keys()) # TODO: this test makes less sense now that expand_aprs_message is separate def test_object_item_report(self): self.__receive(parse_tnc2( 'KE6AFE-2>APU25N,WR6ABD*,NCA1:;TFCSCRUZ *160323z3655.94N\12200.92W?70 In 10 Minutes', _dummy_receive_time)) self.assertEqual({'KE6AFE-2', 'TFCSCRUZ '}, set(self.store.state().keys())) # TODO test value of object def test_object_kill(self): self.__receive(parse_tnc2( 'KE6AFE-2>APU25N,WR6ABD*,NCA1:;TFCSCRUZ *160323z3655.94N\12200.92W?70 In 10 Minutes', _dummy_receive_time)) self.assertEqual({'KE6AFE-2', 'TFCSCRUZ '}, set(self.store.state().keys())) self.__receive(parse_tnc2( 'FOO>BAR:;TFCSCRUZ _160323z3655.94N\12200.92W?', _dummy_receive_time)) self.clock.advance(0) self.assertEqual({'FOO', 'KE6AFE-2'}, set(self.store.state().keys())) def test_drop_old(self): self.__receive(parse_tnc2('FOO>RX:>', _dummy_receive_time)) self.assertEqual(['FOO'], self.store.state().keys()) self.clock.advance(1799.5) self.__receive(parse_tnc2('BAR>RX:>', _dummy_receive_time + 1799.5)) self.assertEqual({'BAR', 'FOO'}, set(self.store.state().keys())) self.clock.advance(0.5) self.__receive(parse_tnc2('BAR>RX:>', _dummy_receive_time + 1800)) self.assertEqual(['BAR'], self.store.state().keys())
def setUp(self): self.clock = Clock() self.clock.advance(1000) self.store = TelemetryStore(time_source=self.clock)
class TestTelemetryStore(unittest.TestCase): def setUp(self): self.clock = Clock() self.clock.advance(1000) self.store = TelemetryStore(time_source=self.clock) def test_new_object(self): self.assertEqual([], self.store.state().keys()) self.store.receive(Msg("foo", 1000)) self.assertEqual(["foo"], self.store.state().keys()) obj = self.store.state()["foo"].get() self.assertIsInstance(obj, Obj) def test_receive_called(self): self.store.receive(Msg("foo", 1000, 1)) obj = self.store.state()["foo"].get() self.assertEquals(obj.last_msg, 1) self.store.receive(Msg("foo", 1000, 2)) self.assertEquals(obj.last_msg, 2) def test_drop_old(self): self.store.receive(Msg("foo", 1000)) self.assertEqual(["foo"], self.store.state().keys()) self.clock.advance(1799.5) self.store.receive(Msg("bar", 2799.5)) self.assertEqual({"bar", "foo"}, set(self.store.state().keys())) self.clock.advance(0.5) self.store.receive(Msg("bar", 2800)) self.assertEqual(["bar"], self.store.state().keys()) def test_become_interesting(self): self.store.receive(Msg("foo", 1000, "boring")) self.assertEqual([], self.store.state().keys()) self.store.receive(Msg("foo", 1001, "interesting")) self.assertEqual(["foo"], self.store.state().keys()) # 'become boring' is not implemented, so also not tested yet def test_drop_old_boring(self): """ Make sure that dropping a boring object doesn't fail. """ self.store.receive(Msg("foo", 1000, "boring")) self.assertEqual([], self.store.state().keys()) self.clock.advance(1800) self.store.receive(Msg("bar", 2800, "boring")) self.assertEqual([], self.store.state().keys())
class TestTelemetryStore(unittest.TestCase): def setUp(self): self.clock = SlightlyBetterClock() self.clock.advance(1000) self.store = TelemetryStore(time_source=self.clock) def test_new_object(self): self.assertEqual(set(), set(self.store.state().keys())) self.store.receive(Msg('foo', 1000)) self.assertEqual({'foo'}, set(self.store.state().keys())) obj = self.store.state()['foo'].get() self.assertIsInstance(obj, Obj) def test_receive_called(self): self.store.receive(Msg('foo', 1000, 1)) obj = self.store.state()['foo'].get() self.assertEqual(obj.last_msg, 1) self.store.receive(Msg('foo', 1000, 2)) self.assertEqual(obj.last_msg, 2) def test_drop_old(self): self.store.receive(Msg('foo', 1000)) self.assertEqual({'foo'}, set(self.store.state().keys())) self.clock.advance(1799.5) self.store.receive(Msg('bar', 2799.5)) self.assertEqual({'bar', 'foo'}, set(self.store.state().keys())) self.clock.advance(0.5) self.assertEqual({'bar'}, set(self.store.state().keys())) self.clock.advance(10000) self.assertEqual(set(), set(self.store.state().keys())) # Expect complete cleanup -- that is, even if a TelemetryStore is created, filled, and thrown away, it will eventually be garbage collected when the objects expire. self.assertEqual(set(), set(self.clock.getDelayedCalls())) def test_become_interesting(self): self.store.receive(Msg('foo', 1000, 'boring')) self.assertEqual(set(), set(self.store.state().keys())) self.store.receive(Msg('foo', 1001, 'interesting')) self.assertEqual({'foo'}, set(self.store.state().keys())) # 'become boring' is not implemented, so also not tested yet def test_drop_old_boring(self): """ Make sure that dropping a boring object doesn't fail. """ self.store.receive(Msg('foo', 1000, 'boring')) self.assertEqual(set(), set(self.store.state().keys())) self.clock.advance(1800) self.store.receive(Msg('bar', 2800, 'boring')) self.assertEqual(set(), set(self.store.state().keys())) def test_expire_in_the_past(self): """ An ITelemetryObject expiring in the past is not an error. """ self.clock.advance(10000) self.store.receive(Msg('foo', 0, 'long ago')) self.clock.advance(2000)
def setUp(self): self.clock = Clock() self.clock.advance(_dummy_receive_time) self.store = TelemetryStore(time_source=self.clock)
class TestTelemetryStore(unittest.TestCase): def setUp(self): self.clock = Clock() self.clock.advance(1000) self.store = TelemetryStore(time_source=self.clock) def test_new_object(self): self.assertEqual([], self.store.state().keys()) self.store.receive(Msg('foo', 1000)) self.assertEqual(['foo'], self.store.state().keys()) obj = self.store.state()['foo'].get() self.assertIsInstance(obj, Obj) def test_receive_called(self): self.store.receive(Msg('foo', 1000, 1)) obj = self.store.state()['foo'].get() self.assertEquals(obj.last_msg, 1) self.store.receive(Msg('foo', 1000, 2)) self.assertEquals(obj.last_msg, 2) def test_drop_old(self): self.store.receive(Msg('foo', 1000)) self.assertEqual(['foo'], self.store.state().keys()) self.clock.advance(1799.5) self.store.receive(Msg('bar', 2799.5)) self.assertEqual({'bar', 'foo'}, set(self.store.state().keys())) self.clock.advance(0.5) self.store.receive(Msg('bar', 2800)) self.assertEqual(['bar'], self.store.state().keys()) def test_become_interesting(self): self.store.receive(Msg('foo', 1000, 'boring')) self.assertEqual([], self.store.state().keys()) self.store.receive(Msg('foo', 1001, 'interesting')) self.assertEqual(['foo'], self.store.state().keys()) # 'become boring' is not implemented, so also not tested yet def test_drop_old_boring(self): """ Make sure that dropping a boring object doesn't fail. """ self.store.receive(Msg('foo', 1000, 'boring')) self.assertEqual([], self.store.state().keys()) self.clock.advance(1800) self.store.receive(Msg('bar', 2800, 'boring')) self.assertEqual([], self.store.state().keys())
class TestTelemetryStore(unittest.TestCase): def setUp(self): self.clock = SlightlyBetterClock() self.clock.advance(1000) self.store = TelemetryStore(time_source=self.clock) def test_new_object(self): self.assertEqual([], self.store.state().keys()) self.store.receive(Msg('foo', 1000)) self.assertEqual(['foo'], self.store.state().keys()) obj = self.store.state()['foo'].get() self.assertIsInstance(obj, Obj) def test_receive_called(self): self.store.receive(Msg('foo', 1000, 1)) obj = self.store.state()['foo'].get() self.assertEquals(obj.last_msg, 1) self.store.receive(Msg('foo', 1000, 2)) self.assertEquals(obj.last_msg, 2) def test_drop_old(self): self.store.receive(Msg('foo', 1000)) self.assertEqual(['foo'], self.store.state().keys()) self.clock.advance(1799.5) self.store.receive(Msg('bar', 2799.5)) self.assertEqual({'bar', 'foo'}, set(self.store.state().keys())) self.clock.advance(0.5) self.assertEqual({'bar'}, set(self.store.state().keys())) self.clock.advance(10000) self.assertEqual([], self.store.state().keys()) # Expect complete cleanup -- that is, even if a TelemetryStore is created, filled, and thrown away, it will eventually be garbage collected when the objects expire. self.assertEqual([], self.clock.getDelayedCalls()) def test_become_interesting(self): self.store.receive(Msg('foo', 1000, 'boring')) self.assertEqual([], self.store.state().keys()) self.store.receive(Msg('foo', 1001, 'interesting')) self.assertEqual(['foo'], self.store.state().keys()) # 'become boring' is not implemented, so also not tested yet def test_drop_old_boring(self): """ Make sure that dropping a boring object doesn't fail. """ self.store.receive(Msg('foo', 1000, 'boring')) self.assertEqual([], self.store.state().keys()) self.clock.advance(1800) self.store.receive(Msg('bar', 2800, 'boring')) self.assertEqual([], self.store.state().keys()) def test_expire_in_the_past(self): """ An ITelemetryObject expiring in the past is not an error. """ self.clock.advance(10000) self.store.receive(Msg('foo', 0, 'long ago')) self.clock.advance(2000)
def __init__(self, devices={}, audio_config=None, features=_STUB_FEATURES): # pylint: disable=dangerous-default-value if len(devices) <= 0: raise ValueError( 'Must be configured with at least one RF device! (This should normally be caught by the configuration validator.)' ) gr.top_block.__init__(self, type(self).__name__) self.__running = False # duplicate of GR state we can't reach, see __start_or_stop self.__has_a_useful_receiver = False # Configuration # TODO: device refactoring: Remove vestigial 'accessories' self._sources = CellDict( {k: d for k, d in six.iteritems(devices) if d.can_receive()}) self._accessories = accessories = { k: d for k, d in six.iteritems(devices) if not d.can_receive() } for key in self._sources: # arbitrary valid initial value self.source_name = key break self.__rx_device_type = EnumT( {k: v.get_name() or k for (k, v) in six.iteritems(self._sources)}) # Audio early setup self.__audio_manager = AudioManager( # must be before contexts graph=self, audio_config=audio_config, stereo=features['stereo']) # Blocks etc. # TODO: device refactoring: remove 'source' concept (which is currently a device) # TODO: remove legacy no-underscore names, maybe get rid of self.source self.source = None self.__monitor_rx_driver = None self.monitor = MonitorSink( signal_type=SignalType( sample_rate=10000, kind='IQ'), # dummy value will be updated in _do_connect context=Context(self)) self.monitor.get_interested_cell().subscribe2( self.__start_or_stop_later, the_subscription_context) self.__clip_probe = MaxProbe() # Receiver blocks (multiple, eventually) self._receivers = CellDict(dynamic=True) self._receiver_valid = {} # collections # TODO: No longer necessary to have these non-underscore names self.sources = CollectionState(CellDict(self._sources)) self.receivers = ReceiverCollection(self._receivers, self) self.accessories = CollectionState(CellDict(accessories)) self.__telemetry_store = TelemetryStore() # Flags, other state self.__needs_reconnect = [u'initialization'] self.__in_reconnect = False self.receiver_key_counter = 0 self.receiver_default_state = {} # Initialization def hookup_vfo_callback( k, d): # function so as to not close over loop variable d.get_vfo_cell().subscribe2( lambda value: self.__device_vfo_callback(k), the_subscription_context) for k, d in six.iteritems(devices): hookup_vfo_callback(k, d) device_context = DeviceContext(self.__telemetry_store.receive) for device in six.itervalues(devices): device.attach_context(device_context) self._do_connect()