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)
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()
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)
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
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