def test_events_equal(self, ): e1 = farc.Event(0, 0) e2 = farc.Event(1 - 1, 2 - 2) # First prove the events are separate objects self.assertNotEqual(id(e1), id(e2)) # Now prove the event equality method works self.assertTrue(e1 == e2)
def test_events_not_equal(self, ): e1 = farc.Event(4, "four") e2 = farc.Event(2, "two") # both fields differ e3 = farc.Event(2, "four") # signal differs e4 = farc.Event(4, "four!") # value differs self.assertFalse(e1 == e2) self.assertFalse(e1 == e3) self.assertFalse(e1 == e4)
def _initial(self, event): """Pseudostate: UartHsm:_initial""" farc.Signal.register("_UART_OPEN") farc.Signal.register("_UART_CLOSE") self._open_evt = farc.Event(farc.Signal._UART_OPEN, None) self._close_evt = farc.Event(farc.Signal._UART_CLOSE, None) self._tm_evt = farc.TimeEvent("_UART_TMOUT") return self.tran(UartHsm._ready)
def _serving(self, event): sig = event.signal if sig == farc.Signal.HUNGRY: # BSP.busyDelay() n = event.value assert n < N_PHILO and not self.isHungry[n] print(n, "hungry") m = LEFT(n) if self.fork[m] == "FREE" and self.fork[n] == "FREE": self.fork[m] = "USED" self.fork[n] = "USED" e = farc.Event(farc.Signal.EAT, n) farc.Framework.publish(e) print(n, "eating") else: self.isHungry[n] = True return self.handled(event) elif sig == farc.Signal.DONE: # BSP.busyDelay() n = event.value assert n < N_PHILO and not self.isHungry[n] print(n, "thinking") m = LEFT(n) assert self.fork[n] == "USED" and self.fork[m] == "USED" self.fork[m] = "FREE" self.fork[n] = "FREE" m = RIGHT(n) if self.isHungry[m] and self.fork[m] == "FREE": self.fork[n] = "USED" self.fork[m] = "USED" self.isHungry[m] = False e = farc.Event(farc.Signal.EAT, m) farc.Framework.publish(e) print(m, "eating") m = LEFT(n) n = LEFT(m) if self.isHungry[m] and self.fork[n] == "FREE": self.fork[m] = "USED" self.fork[n] = "USED" self.isHungry[m] = False e = farc.Event(farc.Signal.EAT, m) farc.Framework.publish(e) print(m, "eating") return self.handled(event) elif sig == farc.Signal.TERMINATE: farc.Framework.stop() return self.super(self.top)
def tx_ebcn(self, abs_time): """Builds a HeyMac V1 Extended Beacon and passes it to the PHY for transmit. """ my_bcn_slotmap = bytearray((2**self.sf_order) // 8) my_bcn_slotmap[self.bcn_slot // 8] |= (1 << (self.bcn_slot % 8)) frame = self.build_mac_frame(self) frame.data = mac_cmds.HeyMacCmdEbcn( sf_order=self.sf_order, eb_order=self.eb_order, dscpln=self.dscpln.get_dscpln_value(), caps=0, status=0, asn=self.asn, tx_slots=my_bcn_slotmap, # FIXME ngbr_tx_slots=self.mac_data.get_bcn_slotmap( mac_tdma_cfg.FRAME_SPEC_SF_ORDER), # extended fields: station_id=socket.gethostname().encode(), geoloc=getattr(self, "gps_gprmc", b""), #TODO: extract lat/lon from gprmc ) tx_args = (abs_time, phy_cfg.tx_freq, bytes(frame) ) # tx time, freq and data farc.Framework.post_by_name( farc.Event(farc.Signal.PHY_TRANSMIT, tx_args), "SX127xSpiAhsm")
def _lurking(me, event): """State: HeyMacAhsm:_running:_lurking Passively receives radio and GPS for timing discipline sources. Transitions to Scheduling after lurking for N superframes. """ sig = event.signal if sig == farc.Signal.ENTRY: logging.info("LURKING") # rx continuously on the rx_freq value = (-1, phy_cfg.rx_freq) farc.Framework.post_by_name( farc.Event(farc.Signal.PHY_RECEIVE, value), "SX127xSpiAhsm") listen_secs = mac_tdma_cfg.N_SFRAMES_TO_LISTEN * ( 2**me.sf_order) / mac_tdma_cfg.TSLOTS_PER_SEC me.tm_evt.postIn(me, listen_secs) return me.handled(me, event) elif sig == farc.Signal._MAC_TDMA_TM_EVT_TMOUT: # listening timer has expired, transition to _beaconing return me.tran(me, me._beaconing) # NOTE: This handler is for logging print and may be removed elif sig == farc.Signal.PHY_GPS_PPS: # GPS pulse per second pin event logging.info("pps %f", event.value) # process PPS in the _running state, too return me.super(me, me._running) return me.super(me, me._running)
def _transmitting(self, event): """State: SX127xSpiAhsm:_working:_transmitting """ sig = event.signal if sig == farc.Signal.ENTRY: logging.info("tx %f", farc.Framework._event_loop.time()) self.sx127x.set_op_mode("tx") self.tm_evt.post_in(self, 1.0) # TODO: make time scale with datarate return self.handled(event) elif sig == farc.Signal.PHY_DIO0: # TX_DONE return self.tran(SX127xSpiAhsm._idling) elif sig == farc.Signal._PHY_SPI_TMOUT: # software timeout self.sx127x.set_op_mode("stdby") return self.tran(SX127xSpiAhsm._idling) elif sig == farc.Signal.EXIT: self.tm_evt.disarm() farc.Framework.publish(farc.Event(farc.Signal.PHY_TX_DONE, None)) return self.handled(event) return self.super(self._working)
def _gpio_input_handler(sig): """Emits the given signal upon a pin change. The event's value is the current time. """ time = farc.Framework._event_loop.time() evt = farc.Event(sig, time) farc.Framework.publish(evt)
def _initial(self, event): """Pseudostate: _initial State machine framework initialization """ # Self-signaling farc.Signal.register("_ALWAYS") farc.Signal.register("_PHY_RQST") # DIO Signal table (DO NOT CHANGE ORDER) # This table is dual maintenance with phy_sx127x.PhySX127x.DIO_* self._dio_sig_lut = ( farc.Signal.register("_DIO_MODE_RDY"), farc.Signal.register("_DIO_CAD_DETECTED"), farc.Signal.register("_DIO_CAD_DONE"), farc.Signal.register("_DIO_FHSS_CHG_CHNL"), farc.Signal.register("_DIO_RX_TMOUT"), farc.Signal.register("_DIO_RX_DONE"), farc.Signal.register("_DIO_CLK_OUT"), farc.Signal.register("_DIO_PLL_LOCK"), farc.Signal.register("_DIO_VALID_HDR"), farc.Signal.register("_DIO_TX_DONE"), farc.Signal.register("_DIO_PAYLD_CRC_ERR"), ) # Self-signaling events self._evt_always = farc.Event(farc.Signal._ALWAYS, None) # Time events self.tmout_evt = farc.TimeEvent("_PHY_TMOUT") self.prdc_evt = farc.TimeEvent("_PHY_PRDC") return self.tran(self._initializing)
def _initializing(me, event): """State: HeyMacCsmaAhsm:_initializing - initializes MAC related variables and the tx-pkt queue - always transitions to the _lurking state """ sig = event.signal if sig == farc.Signal.ENTRY: logging.info("INITIALIZING") # Data Link Layer data me.mac_csma_data = mac_csma_data.MacData() # Transmit queue me.mac_txq = [] me.postFIFO(farc.Event(farc.Signal._ALWAYS, None)) return me.handled(me, event) elif sig == farc.Signal._ALWAYS: return me.tran(me, HeyMacCsmaAhsm._lurking) elif sig == farc.Signal.EXIT: return me.handled(me, event) return me.super(me, me.top)
def setUp(self): global v # create an event with the mutable value farc.Signal.register("APPEND") self.event = farc.Event(farc.Signal.APPEND, v) self.sm = SimpleSM() self.sm.start(0)
def _dio_isr_clbk(self, dio): """A callback given to the PHY for when a DIO pin event occurs. The Rpi.GPIO's thread calls this procedure (like an interrupt). This procedure posts an Event to this state machine corresponding to the DIO pin that transitioned. The pin edge's arrival time is the value of the Event. """ now = farc.Framework._event_loop.time() self.post_fifo(farc.Event(self._dio_sig_lut[dio], now))
def post_tx_action(self, tx_time, tx_stngs, tx_bytes): """Posts the _PHY_RQST event to this state machine with the container-ized arguments as the value. """ assert type(tx_bytes) is bytes # Convert NOW to an actual time if tx_time == PhySX127xAhsm.TM_NOW: tx_time = farc.Framework._event_loop.time() # The order MUST begin: (action, stngs, ...) tx_action = ("tx", tx_stngs, tx_bytes) self.post_fifo(farc.Event(farc.Signal._PHY_RQST, (tx_time, tx_action)))
def tx_from_txq(self, abs_time): """Creates a frame, inserts the payload that is next in the queue and dispatches the frame to the PHY for transmission. Assumes caller checked that the queue is not empty. """ frame = self.build_mac_frame(self) frame.data = self.mac_txq.pop() tx_args = (abs_time, phy_cfg.tx_freq, bytes(frame) ) # tx time, freq and data farc.Framework.post_by_name( farc.Event(farc.Signal.PHY_TRANSMIT, tx_args), "SX127xSpiAhsm")
def mainFunction(): # create an flick event. farc.Signal.register("FLICK") event = farc.Event(farc.Signal.FLICK, None) sw = OnOffSwitch() sw.start(0) loop = asyncio.get_event_loop() loop.call_later(2.0, postFlickEvent, sw, event) farc.run_forever()
def _tx_bcn(self, ): """Builds a HeyMac CsmaBeacon and passes it to the PHY for transmit. """ frame = mac_frame.HeyMacFrame() frame.saddr = self.saddr frame.data = mac_cmds.HeyMacCmdCbcn( caps=0, status=0, ) tx_args = (-1, phy_cfg.tx_freq, bytes(frame)) # immediate transmit farc.Framework.post_by_name( farc.Event(farc.Signal.PHY_TRANSMIT, tx_args), "SX127xSpiAhsm")
def post_rx_action(self, rx_time, rx_stngs, rx_durxn, rx_clbk): """Posts the _PHY_RQST event to this state machine with the container-ized arguments as the value. """ assert not self._lstn_by_dflt, \ """post_rx_action() should not be used when the PHY is listen-by-default. Use set_dflt_rx_clbk() once, instead.""" # Convert NOW to an actual time if rx_time == PhySX127xAhsm.TM_NOW: rx_time = farc.Framework._event_loop.time() # The order MUST begin: (action, stngs, ...) rx_action = ("rx", rx_stngs, rx_durxn, rx_clbk) self.post_fifo(farc.Event(farc.Signal._PHY_RQST, (rx_time, rx_action)))
def test_transitions(self, ): trans_seq = ( ("_s211", "g"), ("_s11", "i"), ("_s11", "a"), ("_s11", "d"), ("_s11", "d"), ("_s11", "c"), ("_s211", "e"), ("_s11", "e"), ("_s11", "g"), ("_s211", "i"), ("_s211", "i"), ("_s211", "t"), ("_exiting", "t") ) # this last input is irrelevant; test that we reached the exiting state for st, sig in trans_seq: self.assertEqual(self.sm.state.__name__, st) # check the current state event = farc.Event(getattr(farc.Signal, sig), None) # create event from the input signal self.sm.postFIFO(event)
def _attempt_tx_from_q(self, ): """Creates a frame, inserts the payload that is next in the queue and dispatches the frame to the PHY for transmission. Assumes caller checked that the queue is not empty. """ # TODO: check that phy layer has not received a header if self.mac_txq: logging.info("tx from q") frame = mac_frame.HeyMacFrame() frame.data = self.mac_txq.pop() tx_args = (-1, phy_cfg.tx_freq, bytes(frame) ) # tx immediately, freq and data farc.Framework.post_by_name( farc.Event(farc.Signal.PHY_TRANSMIT, tx_args), "SX127xSpiAhsm")
def _initializing(me, event): """State: HeyMacAhsm:_initializing """ sig = event.signal if sig == farc.Signal.ENTRY: me.postFIFO(farc.Event(farc.Signal._ALWAYS, None)) return me.handled(me, event) elif sig == farc.Signal._ALWAYS: return me.tran(me, HeyMacAhsm._lurking) elif sig == farc.Signal.EXIT: return me.handled(me, event) return me.super(me, me.top)
def _listening(self, event): """State SX127xSpiAhsm:_working:_listening If the rx_time is less than zero, listen continuously; the caller must establish a way to end the continuous mode. """ sig = event.signal if sig == farc.Signal.ENTRY: self.hdr_time = 0 if self.rx_time < 0: self.sx127x.set_op_mode("rxcont") else: self.sx127x.set_op_mode("rxonce") return self.handled(event) elif sig == farc.Signal.PHY_DIO0: # RX_DONE if self.sx127x.check_lora_rx_flags(): payld, rssi, snr = self.sx127x.get_lora_rxd() pkt_data = (self.hdr_time, payld, rssi, snr) farc.Framework.publish( farc.Event(farc.Signal.PHY_RXD_DATA, pkt_data)) else: # TODO: crc error stats logging.info("rx CRC error") return self.tran(SX127xSpiAhsm._idling) elif sig == farc.Signal.PHY_DIO1: # RX_TIMEOUT self.sx127x.clear_lora_irqs(phy_sx127x_spi.IRQFLAGS_RXTIMEOUT_MASK) return self.tran(SX127xSpiAhsm._idling) elif sig == farc.Signal.PHY_DIO3: # ValidHeader self.hdr_time = event.value self.sx127x.clear_lora_irqs( phy_sx127x_spi.IRQFLAGS_VALIDHEADER_MASK) return self.tran(SX127xSpiAhsm._receiving) # We haven't received anything yet # and a request to Transmit arrives, # cancel the listening and go do the Transmit elif sig == farc.Signal.PHY_TRANSMIT: self.sx127x.set_op_mode("stdby") self.tx_time = event.value[0] self.tx_freq = event.value[1] self.tx_data = event.value[2] return self.tran(self._tx_prepping) return self.super(self._working)
def _initial(self, event): """PseudoState: _initial State machine framework initialization """ # Self-signaling farc.Signal.register("_ALWAYS") farc.Signal.register("_LNK_RXD_FROM_PHY") # Self-signaling events self._evt_always = farc.Event(farc.Signal._ALWAYS, None) # Timer events self._bcn_evt = farc.TimeEvent("_LNK_BCN_TMOUT") self._tm_evt = farc.TimeEvent("_LNK_TMOUT") return self.tran(self._initializing)
def _scheduling(self, event): """"State: _scheduling Writes any outstanding settings and always transitions to _txing, _sleeping or _listening """ sig = event.signal if sig == farc.Signal.ENTRY: logging.debug("PHY._scheduling") # TODO: remove unecessary read once sm design is proven assert self.sx127x.OPMODE_STBY == self.sx127x.read_opmode() self.post_fifo(farc.Event(farc.Signal._ALWAYS, None)) return self.handled(event) elif sig == farc.Signal._ALWAYS: # If the next action is soon, go to its state next_action = self._top_soon_action() self._default_action = not bool(next_action) if next_action: _, action = next_action if action[0] == "rx": st = self._listening elif action[0] == "tx": st = self._txing else: # Placeholder for CAD, sleep assert True, "Got here by accident" # Otherwise, go to the default elif self._lstn_by_dflt: st = self._listening else: st = self._sleeping return self.tran(st) elif sig == farc.Signal._PHY_RQST: tm, action = event.value self._enqueue_action(tm, action) return self.handled(event) return self.super(self.top)
def parse_nmea(nmea_ba): """Callback to parse NMEA data for the UartHsm The caller must re-use nmea_ba to retain partial sentences. The caller must do this once: farc.Signal.register("GPS_GPRMC") """ n = nmea_ba.find(b"\r\n") while n >= 0: nmea_sentence = bytes(nmea_ba[0:n]) del nmea_ba[0:n + 2] if nmea_sentence.startswith(b"$GPRMC"): farc.Framework.publish( farc.Event(farc.Signal.GPS_GPRMC, nmea_sentence.decode())) n = nmea_ba.find(b"\r\n") # Flush junk data or UART rate mismatch if n < 0 and len(nmea_ba) >= 256: nmea_ba.clear()
def _hungry(self, event): sig = event.signal if sig == farc.Signal.ENTRY: e = farc.Event(farc.Signal.HUNGRY, PHILO_ID(self)) farc.Framework.post_by_name(e, "Table") status = self.handled(event) elif sig == farc.Signal.EAT: if event.value == PHILO_ID(self): status = self.tran(Philo._eating) else: status = self.super(self.top) # UNHANDLED elif sig == farc.Signal.DONE: assert event.value != PHILO_ID(self) status = self.handled(event) else: status = self.super(self.top) return status
def _eating(self, event): sig = event.signal if sig == farc.Signal.ENTRY: self.timeEvt.post_in(self, EAT_TIME()) status = self.handled(event) elif sig == farc.Signal.EXIT: e = farc.Event(farc.Signal.DONE, PHILO_ID(self)) farc.Framework.publish(e) status = self.handled(event) elif sig == farc.Signal.TIMEOUT: status = self.tran(Philo._thinking) elif sig == farc.Signal.EAT or sig == farc.Signal.DONE: assert event.value != PHILO_ID(self) status = self.handled(event) else: status = self.super(self.top) return status
def _phy_rx_clbk(self, rx_time, rx_bytes, rx_rssi, rx_snr): """A method given to the PHY layer as a callback. The PHY calls this method with these arguments when it receives a frame with no errors. This method collects the arguments in a container and posts an event to this state machine. """ # Parse the bytes into a frame # and store reception meta-data try: frame = HeymacFrame.parse(rx_bytes) frame.rx_meta = (rx_time, rx_rssi, rx_snr) except HeymacFrameError: logging.info( "LNK:rxd frame is not valid Heymac\n\t{}".format(rx_bytes)) # TODO: lnk stats incr rxd frame is not Heymac return # The frame is valid, post it to the state machine self.post_fifo(farc.Event(farc.Signal._LNK_RXD_FROM_PHY, frame))
def tx_bcn(self, abs_time): """Builds a HeyMac V1 Small Beacon and passes it to the PHY for transmit. """ my_bcn_slotmap = bytearray((2**self.sf_order) // 8) my_bcn_slotmap[self.bcn_slot // 8] |= (1 << (self.bcn_slot % 8)) frame = self.build_mac_frame(self) frame.data = mac_cmds.HeyMacCmdSbcn( sf_order=self.sf_order, eb_order=self.eb_order, dscpln=self.dscpln.get_dscpln_value(), caps=0, status=0, asn=self.asn, tx_slots=my_bcn_slotmap, # FIXME ngbr_tx_slots=self.mac_data.get_bcn_slotmap( mac_tdma_cfg.FRAME_SPEC_SF_ORDER), ) tx_args = (abs_time, phy_cfg.tx_freq, bytes(frame) ) # tx time, freq and data farc.Framework.post_by_name( farc.Event(farc.Signal.PHY_TRANSMIT, tx_args), "SX127xSpiAhsm")
def _running(me, event): """State: HeyMacAhsm:_running The _running state: - uses PPS events from the GPIO to establish timing discipline - receives continuously (hearing a ngbr will lead to state change) - uses GPS NMEA events to get position information """ sig = event.signal if sig == farc.Signal.ENTRY: return me.handled(me, event) elif sig == farc.Signal.PHY_GPS_PPS: time_of_pps = event.value me.dscpln.update_pps(time_of_pps) return me.handled(me, event) elif sig == farc.Signal.PHY_RXD_DATA: rx_time, payld, rssi, snr = event.value me.on_rxd_frame(me, rx_time, payld, rssi, snr) # immediate rx continuous rx_args = (-1, phy_cfg.rx_freq) farc.Framework.post_by_name( farc.Event(farc.Signal.PHY_RECEIVE, rx_args), "SX127xSpiAhsm") return me.handled(me, event) elif sig == farc.Signal.MAC_TX_REQ: me.mac_txq.insert(0, event.value) return me.handled(me, event) elif sig == farc.Signal.PHY_GPS_NMEA: me.gps_gprmc = event.value return me.handled(me, event) elif sig == farc.Signal.SIGTERM: return me.tran(me, me._exiting) elif sig == farc.Signal.EXIT: return me.handled(me, event) return me.super(me, me.top)
def _tx_prepping(me, event): """State: SX127xSpiAhsm:_idling:_tx_prepping """ sig = event.signal if sig == farc.Signal.ENTRY: # Enable only the TX interrupts (disable all others) me.sx127x.disable_lora_irqs() me.sx127x.enable_lora_irqs(phy_sx127x_spi.IRQFLAGS_TXDONE_MASK) me.sx127x.clear_lora_irqs(phy_sx127x_spi.IRQFLAGS_TXDONE_MASK) # Set DIO, TX/FIFO_PTR, FIFO and freq in prep for transmit me.sx127x.set_dio_mapping(dio0=1) me.sx127x.set_lora_fifo_ptr() me.sx127x.set_tx_data(me.tx_data) me.sx127x.set_tx_freq(me.tx_freq) # Reminder pattern me.postFIFO(farc.Event(farc.Signal._ALWAYS, None)) return me.handled(me, event) elif sig == farc.Signal._ALWAYS: if me.tx_time > 0: # Calculate precise sleep time and apply a TX margin # to allow receivers time to get ready tiny_sleep = me.tx_time - farc.Framework._event_loop.time() tiny_sleep += SX127xSpiAhsm.TX_MARGIN # If TX time has passed, don't sleep # Else use sleep to get ~1ms precision # Cap sleep at 50ms so we don't block for too long if 0.0 < tiny_sleep: # because MAC layer uses 40ms PREP time if tiny_sleep > SX127xSpiAhsm.MAX_BLOCKING_TIME: tiny_sleep = SX127xSpiAhsm.MAX_BLOCKING_TIME time.sleep(tiny_sleep) return me.tran(me, SX127xSpiAhsm._transmitting) return me.super(me, me._idling)