Ejemplo n.º 1
0
 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)
Ejemplo n.º 2
0
    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)
Ejemplo n.º 3
0
    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)
Ejemplo n.º 4
0
Archivo: dpp.py Proyecto: bestnike/farc
    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)
Ejemplo n.º 5
0
 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")
Ejemplo n.º 6
0
    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)
Ejemplo n.º 7
0
    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)
Ejemplo n.º 8
0
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)
Ejemplo n.º 9
0
    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)
Ejemplo n.º 10
0
    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)
Ejemplo n.º 11
0
    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)
Ejemplo n.º 12
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))
Ejemplo n.º 13
0
 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)))
Ejemplo n.º 14
0
 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")
Ejemplo n.º 15
0
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()
Ejemplo n.º 16
0
 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")
Ejemplo n.º 17
0
 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)))
Ejemplo n.º 18
0
    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)
Ejemplo n.º 19
0
 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")
Ejemplo n.º 20
0
    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)
Ejemplo n.º 21
0
    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)
Ejemplo n.º 22
0
    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)
Ejemplo n.º 23
0
    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)
Ejemplo n.º 24
0
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()
Ejemplo n.º 25
0
Archivo: dpp.py Proyecto: bestnike/farc
    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
Ejemplo n.º 26
0
Archivo: dpp.py Proyecto: bestnike/farc
    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
Ejemplo n.º 27
0
    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))
Ejemplo n.º 28
0
 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")
Ejemplo n.º 29
0
    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)
Ejemplo n.º 30
0
    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)