Exemple #1
0
class NEC_IR():
    edgecount = 68
    block_time = 73  # 68.1ms nominal. Allow for some tx tolerance (?)

    def __init__(self, pin, callback):
        self._ev_start = Event()
        self._callback = callback
        self._times = array(
            'i', (0 for _ in range(self.edgecount + 1)))  # 1 for overrun
        ExtInt(pin, ExtInt.IRQ_RISING_FALLING, Pin.PULL_NONE, self._cb_pin)
        self._reset()
        loop = asyncio.get_event_loop()
        loop.create_task(self._run())

    def _reset(self):
        self._edge = 0
        self._overrun = False
        self._ev_start.clear()

    async def _run(self):
        while True:
            await self._ev_start  # Wait unitl data collection has started
            await asyncio.sleep_ms(self.block_time
                                   )  # Data block should have ended
            self._decode()  # decode, clear event, prepare for new rx, call cb

    # Pin interrupt. Save time of each edge for later decode.
    def _cb_pin(self, line):
        if not self._overrun:  # Overrun: ignore pulses until software timer times out
            if not self._ev_start.is_set():  # First edge received
                self._ev_start.set()
            self._times[self._edge] = ticks_us()
            if self._edge < self.edgecount:
                self._edge += 1
            else:
                self._overrun = True  # Overrun. decode() will test and reset

    def _decode(self):
        val = -3 if self._overrun else 0
        if not self._overrun:
            width = ticks_diff(self._times[1], self._times[0])
            if width > 4000:  # 9ms leading mark for all valid data
                width = ticks_diff(self._times[2], self._times[1])
                if width > 3000:  # 4.5ms space for normal data
                    if self._edge < self.edgecount:
                        val = -1  # Haven't received the correct number of edges
                    else:  # Time spaces only (marks are identical)
                        for edge_no in range(3, self.edgecount, 2):
                            val &= 0x1fffffff  # Constrain to 32 bit integer (b30 == b31)
                            val <<= 1  # nos. will still be unique because of logical inverse address
                            width = ticks_diff(self._times[edge_no + 1],
                                               self._times[edge_no])
                            if width > 1120:
                                val += 1
                elif width > 1700:  # 2.5ms space for a repeat code. Should have exactly 4 edges.
                    val = 1 if self._edge == 4 else -2
        self._reset()
        self._callback(val)
Exemple #2
0
class LED_Flashable():
    def __init__(self, loop, led_no):
        self.led = pyb.LED(led_no)
        self.event = Event()
        loop.create_task(self.flash_task())

    def flash(self, period_ms):
        if self.event.is_set():
            return
        self.event.set(period_ms)

    async def flash_task(self):
        while True:
            await self.event
            period_ms = self.event.value()
            self.event.clear()
            self.led.on()
            await asyncio.sleep_ms(period_ms)
            self.led.off()
Exemple #3
0
async def run_ack():
    loop = asyncio.get_event_loop()
    event = Event()
    ack1 = Event()
    ack2 = Event()
    count = 0
    while True:
        loop.create_task(event_wait(event, ack1, 1))
        loop.create_task(event_wait(event, ack2, 2))
        event.set(count)
        count += 1
        print('event was set')
        await ack1
        ack1.clear()
        print('Cleared ack1')
        await ack2
        ack2.clear()
        print('Cleared ack2')
        event.clear()
        print('Cleared event')
        await asyncio.sleep(1)
class NEC_IR():
    def __init__(self, pin, callback, extended,
                 *args):  # Optional args for callback
        self._ev_start = Event()
        self._callback = callback
        self._extended = extended
        self._addr = 0
        self.block_time = 80 if extended else 73  # Allow for some tx tolerance (?)
        self._args = args
        self._times = array('i',
                            (0
                             for _ in range(_EDGECOUNT + 1)))  # +1 for overrun
        if platform == 'pyboard':
            ExtInt(pin, ExtInt.IRQ_RISING_FALLING, Pin.PULL_NONE, self._cb_pin)
        else:  # PR5962 ESP8266 hard IRQ's not supported
            pin.irq(handler=self._cb_pin,
                    trigger=(Pin.IRQ_FALLING | Pin.IRQ_RISING))
        #elif ESP32:
        #pin.irq(handler = self._cb_pin, trigger = (Pin.IRQ_FALLING | Pin.IRQ_RISING))
        #else:
        #pin.irq(handler = self._cb_pin, trigger = (Pin.IRQ_FALLING | Pin.IRQ_RISING), hard = True)
        self._edge = 0
        self._ev_start.clear()
        loop = asyncio.get_event_loop()
        loop.create_task(self._run())

    async def _run(self):
        loop = asyncio.get_event_loop()
        while True:
            await self._ev_start  # Wait until data collection has started
            # Compensate for asyncio latency
            latency = ticks_diff(loop.time(), self._ev_start.value())
            await asyncio.sleep_ms(self.block_time - latency
                                   )  # Data block should have ended
            self._decode()  # decode, clear event, prepare for new rx, call cb

    # Pin interrupt. Save time of each edge for later decode.
    def _cb_pin(self, line):
        t = ticks_us()
        # On overrun ignore pulses until software timer times out
        if self._edge <= _EDGECOUNT:  # Allow 1 extra pulse to record overrun
            if not self._ev_start.is_set():  # First edge received
                loop = asyncio.get_event_loop()
                self._ev_start.set(loop.time())  # asyncio latency compensation
            self._times[self._edge] = t
            self._edge += 1

    def _decode(self):
        overrun = self._edge > _EDGECOUNT
        val = OVERRUN if overrun else BADSTART
        if not overrun:
            width = ticks_diff(self._times[1], self._times[0])
            if width > 4000:  # 9ms leading mark for all valid data
                width = ticks_diff(self._times[2], self._times[1])
                if width > 3000:  # 4.5ms space for normal data
                    if self._edge < _EDGECOUNT:
                        # Haven't received the correct number of edges
                        val = BADBLOCK
                    else:
                        # Time spaces only (marks are always 562.5µs)
                        # Space is 1.6875ms (1) or 562.5µs (0)
                        # Skip last bit which is always 1
                        val = 0
                        for edge in range(3, _EDGECOUNT - 2, 2):
                            val >>= 1
                            if ticks_diff(self._times[edge + 1],
                                          self._times[edge]) > 1120:
                                val |= 0x80000000
                elif width > 1700:  # 2.5ms space for a repeat code. Should have exactly 4 edges.
                    val = REPEAT if self._edge == 4 else BADREP
        addr = 0
        if val >= 0:  # validate. Byte layout of val ~cmd cmd ~addr addr
            addr = val & 0xff
            cmd = (val >> 16) & 0xff
            if addr == ((val >> 8) ^ 0xff) & 0xff:  # 8 bit address OK
                val = cmd if cmd == (val >> 24) ^ 0xff else BADDATA
                self._addr = addr
            else:
                addr |= val & 0xff00  # pass assumed 16 bit address to callback
                if self._extended:
                    val = cmd if cmd == (val >> 24) ^ 0xff else BADDATA
                    self._addr = addr
                else:
                    val = BADADDR
        if val == REPEAT:
            addr = self._addr  # Last valid addresss
        self._edge = 0  # Set up for new data burst and run user callback
        self._ev_start.clear()
        self._callback(val, addr, *self._args)
Exemple #5
0
class Copernicus_GPS(gps.GPS):
    _enable_sentences = {
        'GGA': (1 << 0),
        'GLL': (1 << 1),
        'VTG': (1 << 2),
        'GSV': (1 << 3),
        'GSA': (1 << 4),
        'ZDA': (1 << 5),
        'RMC': (1 << 8),
        'TF': (1 << 9),
        'BA': (1 << 13)
    }

    class PPS_Mode:
        OFF = 0
        ON = 1
        FIX = 2

    class PPS_Polarity:
        ACTIVE_LOW = 0
        ACTIVE_HIGH = 1

    # we have some special events for config apply
    def __init__(self, uart):
        super().__init__(uart)
        self._nm_ack = Event()
        self._ps_ack = Event()

    # Additional incoming message processing (on to of gps.py)

    # PTNLRNM - Automatic Message Output (Response)
    async def _rx_ptnlrnm(self, segs):
        self._nm_ack.set()
        return

    # PTNLRPS - PPS Configuration (Response)
    async def _rx_ptnlrps(self, segs):
        self._ps_ack.set()
        return

    async def set_auto_messages(self, types, interval):
        # compute bitmask to enable messages
        bitmask = 0
        for type in types:
            bitmask |= self._enable_sentences[type]
        #print(bin(bitmask))
        # send the command
        fmt = 'PTNLSNM,{:04x},{:02d}'
        #print(fmt.format(bitmask,interval))
        print('gps: setting auto messages to', types, 'every', interval,
              'seconds')
        await self._send(fmt.format(bitmask, interval))
        # wait for ack
        print('gps: awaiting messages config ack')
        await self._nm_ack
        self._nm_ack.clear()
        print('gps: messages config accepted')
        return

    async def set_pps_mode(self, mode, length_ns, polarity, cable_ns):
        fmt = 'PTNLSPS,{},{},{},{}'
        # length is in 1/100th of ns
        length_ns = int(length_ns / 100)
        print('gps: setting PPS config')
        await self._send(fmt.format(mode, length_ns, polarity, cable_ns))
        print('gps: awaiting ack to PPS config')
        await self._ps_ack
        self._ps_ack.clear()
        print('gps: PPS config accepted')
        return
Exemple #6
0
class SyncedClock_RTC(syncedclock.SyncedClock):

    # since pyb.RTC() is unreliable for reads, do it ourselves directly
    _RTC_BASE = const(0x40002800)
    _RTC_SSR_OFFSET = const(0x28)
    _rtc_ssr_struct = {
        "ss": 0 | uctypes.BFUINT32 | 0 << uctypes.BF_POS | 15 << uctypes.BF_LEN
    }
    _RTC_TR_OFFSET = const(0x00)
    _rtc_tr_struct = {
        "pm":
        0 | uctypes.BFUINT32 | 22 << uctypes.BF_POS | 1 << uctypes.BF_LEN,
        "ht":
        0 | uctypes.BFUINT32 | 20 << uctypes.BF_POS | 2 << uctypes.BF_LEN,
        "hu":
        0 | uctypes.BFUINT32 | 16 << uctypes.BF_POS | 4 << uctypes.BF_LEN,
        "mnt":
        0 | uctypes.BFUINT32 | 12 << uctypes.BF_POS | 3 << uctypes.BF_LEN,
        "mnu":
        0 | uctypes.BFUINT32 | 8 << uctypes.BF_POS | 4 << uctypes.BF_LEN,
        "st": 0 | uctypes.BFUINT32 | 4 << uctypes.BF_POS | 3 << uctypes.BF_LEN,
        "su": 0 | uctypes.BFUINT32 | 0 << uctypes.BF_POS | 4 << uctypes.BF_LEN
    }
    _RTC_DR_OFFSET = const(0x04)
    _rtc_dr_struct = {
        "yt":
        0 | uctypes.BFUINT32 | 20 << uctypes.BF_POS | 4 << uctypes.BF_LEN,
        "yu":
        0 | uctypes.BFUINT32 | 16 << uctypes.BF_POS | 4 << uctypes.BF_LEN,
        "wdu":
        0 | uctypes.BFUINT32 | 13 << uctypes.BF_POS | 3 << uctypes.BF_LEN,
        "mt":
        0 | uctypes.BFUINT32 | 12 << uctypes.BF_POS | 1 << uctypes.BF_LEN,
        "mu": 0 | uctypes.BFUINT32 | 8 << uctypes.BF_POS | 4 << uctypes.BF_LEN,
        "dt": 0 | uctypes.BFUINT32 | 4 << uctypes.BF_POS | 2 << uctypes.BF_LEN,
        "du": 0 | uctypes.BFUINT32 | 0 << uctypes.BF_POS | 4 << uctypes.BF_LEN
    }

    _RTC_MAX = const(8191)

    # wrap initialiser
    def __init__(self, *args, **kwargs):
        super().__init__(args, kwargs)
        self._uart = None
        self._pps_pin = None
        if kwargs is not None:
            if 'gps_uart' in kwargs:
                self._uart = kwargs['gps_uart']
            if 'pps_pin' in kwargs:
                self._pps_pin = kwargs['pps_pin']

        if (self._uart == None):
            raise ValueError("need a uart for the gps")
        if (self._pps_pin == None):
            raise ValueError("need a pin that gps sends 1pps to us on")

        # we also need the RTC device
        self._rtc = RTC()
        self._pps_event = Event()
        self._rtc_ssr = uctypes.struct(_RTC_BASE + _RTC_SSR_OFFSET,
                                       self._rtc_ssr_struct, uctypes.NATIVE)
        self._rtc_dr = uctypes.struct(_RTC_BASE + _RTC_DR_OFFSET,
                                      self._rtc_dr_struct, uctypes.NATIVE)
        self._pps_rtc = 0
        self._pps_discard = 0
        self._ss_offset = -10000
        self._refclk = (0, 0, 0, 0, 0, 0)

    # try to get some better perf out of this, it's staticish code
    @micropython.native
    def _pps(self, p):
        # grab RTC data when we tick
        # we need to pull this directly out of the registers because we don't want to
        # allocate ram, and the RTC() module does
        self._pps_rtc = self._rtc_ssr.ss
        # need to read DR to nothing to unlock shadow registers
        self._pps_discard = self._rtc_dr.du
        self._pps_event.set()
        return

    async def _wait_gpslock(self):
        try:
            while True:
                if (self._gps.isLocked()):
                    return True
                await asyncio.sleep(1)
        except asyncio.TimeoutError:
            print("syncedclock_rtc: failed to get lock, reinit gps")
        return False

    async def _wait_pps(self):
        try:
            await self._pps_event
            self._pps_event.clear()
            return True
        except asyncio.TimeoutError:
            print("syncedclock_rtc: failed to get pps event in time")
        return False

    # this will now be running in a thread, safe to do things which block
    async def _calibration_loop(self):
        # start RTC
        print("syncedclock_rtc: start rtc")
        self._rtc.init()
        # initalise gps
        self._gps = GPS(self._uart)
        ppsint = ExtInt(self._pps_pin, ExtInt.IRQ_RISING, Pin.PULL_NONE,
                        self._pps)
        ppsint.disable()
        self._pps_event.clear()
        await asyncio.sleep(0)
        while True:
            print("syncedclock_rtc: initalise gps")
            await self._gps.set_auto_messages(['RMC'], 1)
            await self._gps.set_pps_mode(GPS.PPS_Mode.FIX, 42,
                                         GPS.PPS_Polarity.ACTIVE_HIGH, 0)
            print("syncedclock_rtc: waiting for gps lock (30s)")
            res = await asyncio.wait_for(self._wait_gpslock(), 30)
            if (res == False):
                continue
            print(
                "syncedclock_rtc: gps locked, start pps interrupt and wait for pps (3s)"
            )
            #self._pps_pin.irq(trigger=Pin.IRQ_RISING, handler=self._pps)
            ppsint.enable()
            res = await asyncio.wait_for(self._wait_pps(), 3)
            if (res == False):
                print(
                    "syncedclock_rtc: pps signal never recieved, bad wiring?")
                print("syncedclock_rtc: terminating")
                return
            # PPS signal leads GPS data by about half a second or so
            # so the GPS data contains the *previous* second at this point
            # add 1 second and reset RTC
            print("syncedclock_rtc: pps pulse recieved, set RTC clock")
            date = self._gps.date()
            time = self._gps.time()
            # helpfully utime and pyb.RTC use different order in the tuple
            now = utime.localtime(
                utime.mktime((date[2], date[1], date[0], time[0], time[1],
                              time[2], 0, 0)) + 1)
            self._rtc.datetime(
                (now[0], now[1], now[2], 0, now[3], now[4], now[5], 0))
            print("syncedclock_rtc: rtc clock now", self._rtc.datetime())
            await asyncio.sleep(0)
            print("syncedclock_rtc: calibration loop started")
            # ensure we ignore the first cycle
            last_rtc_ss = -1
            # count 32 seconds and calculate the error
            count = 0
            tick_error = 0
            while True:
                # each time we get an PPS event, work out the ticks difference
                res = await asyncio.wait_for(self._wait_pps(), 3)
                if (res == False):
                    print("syncedclock_rtc: lost pps signal, restarting")
                    self._locked = False
                    #self._pps_pin.irq(handler=None)
                    ppsint.disable()
                    break
                rtc_ss = self._pps_rtc
                await asyncio.sleep(0)
                # first pass, just discard the value
                if (last_rtc_ss == -1):
                    last_rtc_ss = rtc_ss
                    continue
                await asyncio.sleep(0)
                count += 1
                # compute the difference in ticks between this and the last
                error = (rtc_ss - last_rtc_ss)
                if (abs(error) > _RTC_MAX - 50):
                    # probably actually rollover problem
                    if (rtc_ss < last_rtc_ss):
                        error = (rtc_ss + _RTC_MAX + 1 - last_rtc_ss)
                    else:
                        error = (rtc_ss - last_rtc_ss + _RTC_MAX + 1)
                await asyncio.sleep(0)
                #print(error)
                tick_error += (error)
                last_rtc_ss = rtc_ss
                # always use the last top of second as current offset if it's not a huge error
                # when locked
                if (self._locked and tick_error > -1 and tick_error < 1):
                    self._ss_offset = rtc_ss
                await asyncio.sleep(0)
                if (count == 32):
                    # if the tick error is +/-1 ticks, it's locked enough
                    # we're only then about 3.81ppm but that's close enough
                    if (self._locked == True
                            and (tick_error > 1 or tick_error < -1)):
                        print("syncedclock_rtc: lost lock")
                        self._locked = False
                    await asyncio.sleep(0)
                    if (self._locked == False
                            and (tick_error <= 1 and tick_error >= -1)):
                        print("syncedclock_rtc: locked with",
                              self._rtc.calibration())
                        # only cache top of second when we enter lock
                        #self._ss_offset = (_RTC_MAX-rtc_ss)
                        self._locked = True
                    await asyncio.sleep(0)
                    if (self._locked == True):
                        # update reference clock point
                        self._refclk = self._rtc.datetime()
                    await asyncio.sleep(0)
                    if (self._locked == False):
                        print("syncedclock_rtc: error now", tick_error)
                    # the total ticks missing should be applied to the calibration
                    # we do this continously so we can ensure the clock is always remaining in sync
                    await asyncio.sleep(0)
                    try:
                        self._rtc.calibration(self._rtc.calibration() +
                                              tick_error)
                    except:
                        print("syncedclock_rtc: error too large, ignoring")
                    # allow us to to be interrupted now
                    await asyncio.sleep(0)
                    #print(rtc.calibration())
                    count = 0
                    tick_error = 0

    def _rtc_to_unixtime(self, rtc_tuple, rtc_offset):
        ts = utime.mktime((
            rtc_tuple[0],  # year
            rtc_tuple[1],  # month
            rtc_tuple[2],  # day
            rtc_tuple[4],  # hour
            rtc_tuple[5],  # minute
            rtc_tuple[6],  # second
            0,
            0)) + 946684800  # weekday and dayofyear are ignored
        tss = (_RTC_MAX - rtc_tuple[7]) + rtc_offset
        if tss >= _RTC_MAX:
            tss -= _RTC_MAX + 1
            ts += 1
        if tss < 0:
            tss += _RTC_MAX + 1
            ts -= 1
        return (ts, tss << 19)

    def now(self):
        if not self._locked:
            return None
        return self._rtc_to_unixtime(self._rtc.datetime(), self._ss_offset)

    def refclk(self):
        if not self._locked:
            return None
        return self._rtc_to_unixtime(self._refclk, self._ss_offset)

    async def start(self):
        super().start()
        loop = asyncio.get_event_loop()
        loop.create_task(self._calibration_loop())
        print("syncedclock_rtc: calibration loop created")
        await asyncio.sleep(0)
        return