# 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. from lifecycle import * import time try: # Python 2 import OpenThings import TwoBit import radio except ImportError: # Python 3 from . import OpenThings from . import TwoBit from . import radio 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_times = 4 outer_delay = 0 outer_times = 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_config=None): # payload is a pydict suitable for OpenThings # radio_params is an overlay on top of radio tx defaults ##print("SEND payload %s config %s" % (payload, radio_config)) p = OpenThings.encode(payload) #TODO: Override self.tx_defaults with any settings in radio_config, if provided outer_times = 1 outer_delay = 0 inner_times = 4 radio.transmitter(fsk=True) radio.transmit(p, outer_times=outer_times, inner_times=inner_times, outer_delay=outer_delay) # radio auto-returns to previous state after transmit completes ##@log_method def receive(self, radio_config=None): # -> (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() now = time.time() p = OpenThings.decode(payload, receive_timestamp=now) #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_times = 8 outer_delay = 0 outer_times = 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_config=None): # payload is just a list of bytes, or a byte buffer # radio_config 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) # temporary hard-coded defaults #TODO: Use self.tx_defaults instead outer_times = 1 outer_delay = 0 inner_times = 8 # Merge any wanted radio params, if provided if radio_config != None: try: outer_times = radio_config.outer_times except AttributeError: pass try: outer_delay = radio_config.outer_delay except AttributeError: pass try: inner_times = radio_config.inner_times except AttributeError: pass print("Will use inner_times %s" % str(inner_times)) radio.transmit(bytes, outer_times=outer_times, inner_times=inner_times, outer_delay=outer_delay) # radio auto-pops to state before transmit ##@log_method def receive(self, radio_config=None): # -> (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