# OnAir.py 19/05/2016 D.J.Whale # # A set of adaptors to allow device classes to interact with radio interfaces. # At the moment, the main purpose of this abstraction is to prevent the need # for device classes to know specifics about radio technology and focus # on the device aspects only. # # In the future, this will be a useful point in the architecture to add # an intelligent message scheduler, that learns the report patterns of # specific devices, builds a timeslotted schedule, and schedules transmits # into mostly free slots. # # NOTE: This also might include intelligent power level selection based # on RSSI reports from different devices. import OpenThings import TwoBit import radio def log_method(m): def inner(*args, **kwargs): print("CALL %s with %s %s" % (m, args, kwargs)) r = m(*args, **kwargs) print("RETURN %s with %s" % (m, r)) return r return inner class OpenThingsAirInterface(): def __init__(self): self.radio = radio # aids mocking later class RadioDefaults(): frequency = 433.92 modulation = radio.RADIO_MODULATION_FSK class TxDefaults(RadioDefaults): power_level = 0 inner_repeats = 4 outer_delay = 0 outer_repeats = 0 self.tx_defaults = TxDefaults() class RxDefaults(RadioDefaults): poll_rate = 100 #ms timeout = 1000 #ms self.rx_defaults = RxDefaults() @log_method def send(self, payload, radio_params=None): # payload is a pydict suitable for OpenThings # radio_params is an overlay on top of radio tx defaults pass #TODO p = OpenThings.encode(payload) #TODO: merge radio_params with self.tx_defaults #TODO: configure radio modulation based on merged params radio.transmitter(fsk=True) #TODO: configure other radio parameters #TODO: transmit payload radio.transmit(p, outer_times=0, inner_times=4, outer_delay=0) # radio auto-returns to previous state after transmit completes @log_method def receive(self, radio_params): # -> (radio_measurements, address or None, payload or None) # radio_params is an overlay on top of radio rx defaults (e.g. poll rate, timeout, min payload, max payload) # radio_measurements might include rssi reading, short payload report, etc pass # TODO #TODO: set radio to receive mode #TODO: merge radio_params with self.tx_defaults #TODO: configure radio modulation based on merged params #TODO: poll radio at rate until timeout or received #TODO: start timeout timer payload = None radio.receiver(fsk=True) while True: # timer not expired if radio.is_receive_waiting(): payload = radio.receive() # TODO payload, radio_measurements = radio.receive() p = OpenThings.decode(payload) #TODO: if crc failure, report it, but keep trying #if crc check passes... break #TODO: inter-try delay #TODO: return radio to state it was before receiver (e.g. standby) - radio needs a pop() on this too? if payload == None: # nothing received in timeout return (None, None, None) # (radio_measurements, address, payload) # TODO: might be measurements, average min max? #TODO: extract addresses: header_manufacturerid, header_productid header_deviceid -> (m, p, d) m, p, d = None, None, None radio_measurements = None # TODO get from radio.receive() address = (m, p, d) return (radio_measurements, address, payload) class TwoBitAirInterface(): def __init__(self): self.radio = radio # aids mocking later class RadioDefaults(): frequency = 433.92 modulation = radio.RADIO_MODULATION_OOK class TxDefaults(RadioDefaults): power_level = 0 inner_repeats = 8 outer_delay = 0 outer_repeats = 0 self.tx_defaults = TxDefaults() class RxDefaults(RadioDefaults): poll_rate = 100 #ms timeout = 1000 #ms self.rx_defaults = RxDefaults() @log_method def send(self, payload, radio_params=None): # payload is just a list of bytes, or a byte buffer # radio_params is an overlay on top of radio tx defaults house_address = payload["house_address"] device_index = payload["device_index"] state = payload["on"] bytes = TwoBit.encode_switch_message(state, device_index, house_address) radio.modulation(ook=True) #TODO: merge radio_params with self.tx_defaults #TODO: configure radio modulation based on merged params #TODO: transmit payload radio.transmit(bytes, outer_times=1, inner_times=8, outer_delay=0) #TODO: radio params # radio auto-pops to state before transmit @log_method def receive(self, radio_params): # -> (radio_measurements, address or None, payload or None) # radio_params is an overlay on top of radio rx defaults (e.g. poll rate, timeout, min payload, max payload) # radio_measurements might include rssi reading, short payload report, etc #TODO: merge radio_params with self.tx_defaults #TODO: configure radio modulation based on merged params #TODO: poll radio at rate until timeout or received #TODO: start timeout timer payload = None radio.receiver(ook=True) while True: # timer not expired if radio.is_receive_waiting(): #TODO: radio config should set receive preamble 4 bytes to prevent false triggers payload = radio.receive(size=12) # TODO payload, radio_measurements = radio.receive() p = TwoBit.decode(payload) #TODO: if failure, report it, but keep trying #if check passes... break #TODO: inter-try delay #TODO: return radio to state it was before receiver (e.g. standby) - radio needs a pop() on this too? if payload == None: # nothing received in timeout return (None, None, None) # (radio_measurements, address, payload) # TODO: might be measurements, average min max? #TODO: extract addresses (house_address, device_index) radio_measurements = None #TODO: return this from radio.receive() h = 0xC8C8C #TODO: Get house address from TwoBit.decode()[:10] d = 0xEE #TODO: Get device command from TwoBit.decode()[11:12] address = (h, d) return (radio_measurements, address, payload) # END