Esempio n. 1
0
def test():
    freq(160000000)
    mtx = Pin(14, Pin.OUT)  # Define pins
    mckout = Pin(15, Pin.OUT, value=0)  # clocks must be initialised to zero.
    mrx = Pin(13, Pin.IN)
    mckin = Pin(12, Pin.IN)

    objsched = Sched(heartbeat=1)
    channel = SynCom(objsched, True, mckin, mckout, mrx, mtx)
    channel.start()
    objsched.add_thread(passive_thread(channel))
    try:
        objsched.run()
    finally:
        mckout(0)
Esempio n. 2
0
def test():
    stx = Pin(Pin.board.Y5, Pin.OUT_PP)  # Define pins
    sckout = Pin(Pin.board.Y6, Pin.OUT_PP)
    sckout.value(0)  # Don't assert clock until data is set
    srx = Pin(Pin.board.Y7, Pin.IN)
    sckin = Pin(Pin.board.Y8, Pin.IN)
    reset = Pin(Pin.board.Y4, Pin.OPEN_DRAIN)

    objsched = Sched(heartbeat=1)
    channel = SynCom(objsched, False, sckin, sckout, srx, stx)
    channel.start(reset, 0)
    objsched.add_thread(initiator_thread(channel))
    try:
        objsched.run()
    finally:
        sckout.value(0)
Esempio n. 3
0
def test():
    freq(160000000)
    dout = Pin(14, Pin.OUT, value=0)  # Define pins
    ckout = Pin(15, Pin.OUT, value=0)  # clocks must be initialised to zero.
    din = Pin(13, Pin.IN)
    ckin = Pin(12, Pin.IN)

    channel = SynCom(True, ckin, ckout, din, dout)
    loop = asyncio.get_event_loop()
    loop.create_task(heartbeat())
    loop.create_task(channel.start(passive_task))
    try:
        loop.run_forever()
    except KeyboardInterrupt:
        pass
    finally:
        ckout(0)
Esempio n. 4
0
def test():
    dout = Pin(Pin.board.Y5, Pin.OUT_PP, value=0)  # Define pins
    ckout = Pin(Pin.board.Y6, Pin.OUT_PP,
                value=0)  # Don't assert clock until data is set
    din = Pin(Pin.board.Y7, Pin.IN)
    ckin = Pin(Pin.board.Y8, Pin.IN)
    reset = Pin(Pin.board.Y4, Pin.OPEN_DRAIN)
    sig_reset = Signal(reset, invert=True)

    channel = SynCom(False, ckin, ckout, din, dout, sig_reset, 10000)

    loop = asyncio.get_event_loop()
    loop.create_task(heartbeat())
    loop.create_task(channel.start(initiator_task))
    try:
        loop.run_forever()
    except KeyboardInterrupt:
        pass
    finally:
        ckout.value(0)
Esempio n. 5
0
class MQTTlink:
    lw_topic = None
    lw_msg = None
    lw_retain = False
    lw_qos = 0
    @classmethod
    def will(cls, topic, msg, retain=False, qos=0):
        cls.lw_topic = topic
        cls.lw_msg = msg
        cls.lw_retain = retain
        cls.lw_qos = qos
    status_msgs = ('connected to broker', 'awaiting broker',
                'awaiting default network', 'awaiting specified network',
                'publish OK', 'running', 'unknown', 'Will registered', 'Fail to connect to broker',
                'WiFi up', 'WiFi down')

    def __init__(self, *args, **kwargs):
        d = defaults  # d is the config dict: initially populate with default values
        for arg in args:
            d.update(arg)  # Hardware and network configs
        d.update(kwargs)
        # d is now populated.
        self.user_start = d['user_start']
        shan = d['status_handler']
        self.s_han = (default_status_handler, ()) if shan is None else shan  # coro
        self.crash_han = d['crash_handler']
        self.wifi_han = d['wifi_handler']
        self.init_str = buildinit(d)
        self.keepalive = d['keepalive']
        self._evtrun = asyncio.Event()
        self.verbose = d['verbose']
        # Watchdog timeout for ESP8266 (ms).
        wdog = d['timeout'] * 1000
        # SynCom string mode
        self.channel = SynCom(False, d['sckin'], d['sckout'], d['srx'], d['stx'],
                              d['reset'], wdog, True, self.verbose)
        if 'led' in d:
            asyncio.create_task(heartbeat(d['led']))
        # Start the SynCom instance. This will run self.start(). If an error
        # occurs self.quit() is called which returns to Syncom's start() method.
        # This waits on ._die before forcing a failure on the ESP8266 and re-running
        # self.start() to perform a clean restart.
        asyncio.create_task(self.channel.start(self.start, self._die))
        self.subs = {}  # (callback, qos, args) indexed by topic
        self.publock = asyncio.Lock()
        self.puback = asyncio.Event()
        self.evttim = asyncio.Event()
        self.evtwifi = asyncio.Event()
        # Only specify network on first run
        self.first_run = True
        self._time = 0  # NTP time. Invalid.
        # ESP8266 returns seconds from 2000 because I believed the docs.
        # Maintainers change the epoch on a whim.
        # Calculate ofsets in CPython using datetime.date:
        # (date(2000, 1, 1) - date(1970, 1, 1)).days * 24*60*60
        epoch = {2000 : 0, 1970 : 946684800}  # Offset to add.
        self._epoch_fix = epoch[gmtime(0)[0]]  # Find offset on current platform

# API
    async def publish(self, topic, msg, retain=False, qos=0):
        qos_check(qos)
        validate(topic, 'topic')  # Raise ValueError if invalid.
        validate(msg, 'message')
        await self.ready()
        async with self.publock:
            self.channel.send(argformat(PUBLISH, topic, msg, int(retain), qos))
            # Wait for ESP8266 to complete. Quick if qos==0. May be very slow
            # in an outage
            await self.puback.wait()

    async def subscribe(self, topic, qos, callback, *args):
        qos_check(qos)
        # Save subscription to resubscribe after outage
        self.subs[topic] = (callback, qos, args)
        # Subscribe
        await self.ready()
        self.channel.send(argformat(SUBSCRIBE, topic, qos))

    # Command handled directly by mqtt.py on ESP8266 e.g. MEM
    async def command(self, *argsend):
        await self.ready()
        self.channel.send(argformat(*argsend))

    def running(self):
        return self._evtrun.is_set()

    async def ready(self):
        await self._evtrun.wait()
        await self.evtwifi.wait()

    def wifi(self):
        return self.evtwifi.is_set()

    # Attempt to retrieve NTP time in secs since 2000 or device epoch
    async def get_time(self, pause=120, y2k=False):
        delta = 0 if y2k else self._epoch_fix
        self.evttim.clear()  # Defensive
        self._time = 0  # Invalidate
        while True:
            await self.ready()
            self.channel.send(TIME)
            try:
                await asyncio.wait_for(self.evttim.wait(), pause // 2)
            except asyncio.TimeoutError:
                pass
            if self._time:
                return self._time + delta  # Fix epoch
            await asyncio.sleep(pause)
        
# API END
    def _do_time(self, action):  # TIME received from ESP8266
        try:
            self._time = int(action[0])
        except ValueError:  # Gibberish.
            self._time = 0  # May be 0 if ESP has signalled failure
        self.evttim.set()
        self.evttim.clear()

    async def _die(self):  # ESP has crashed. Run user callback if provided.
        cb, args = self.crash_han
        cb(self, *args)
        await asyncio.sleep_ms(0)

# Convenience method allows this error code:
# return self.quit(message)
# This method is called from self.start. Note that return is to SynCom's
# .start method which will launch ._die
    def quit(self, *args):
        if args is not ():
            self.verbose and print(*args)
        self._evtrun.clear()

    def get_cmd(self, istr):
        ilst = istr.split(SEP)
        return ilst[0], ilst[1:]

    def do_status(self, action, last_status):
        try:
            iact = int(action[0])
        except ValueError:  # Gibberish from ESP8266: caller reboots
            return UNKNOWN
        if iact == PUBOK:
            # Occurs after all publications. If qos==0 immediately, otherwise
            # when PUBACK received from broker. May take a long time in outage.
            # If a crash occurs, puback is cleared by start()
            self.puback.set()
            self.puback.clear()
        elif iact == RUNNING:
            self._evtrun.set()
        if iact == WIFI_UP:
            self.evtwifi.set()
        elif iact == WIFI_DOWN:
            self.evtwifi.clear()
        # Detect WiFi status changes. Ignore initialisation and repeats
        if last_status != -1 and iact != last_status and iact in (WIFI_UP, WIFI_DOWN):
            cb, args = self.wifi_han
            cb(self.evtwifi.is_set(), self, *args)
        if self.verbose:
            if iact != UNKNOWN:
                if iact != last_status:  # Ignore repeats
                    printtime()
                    print('Status: ', self.status_msgs[iact])
            else:
                printtime()
                print('Unknown status: {} {}'.format(action[1], action[2]))
        return iact

# start() is run each time the channel acquires sync i.e. on startup and also
# after an ESP8266 crash and reset.
# Behaviour after fatal error with ESP8266:
# It clears ._evtrun to cause local tasks to terminate, then returns.
# A return causes the local channel instance to launch .die then issues
# a hardware reset to the ESP8266 (See SynCom.start() and .run() methods).
# After acquiring sync, start() gets rescheduled.
    async def start(self, channel):
        self.verbose and print('Starting...')
        self.puback.set()  # If a crash occurred while a pub was pending
        self.puback.clear()  # let it terminate and release the lock
        self.evttim.set()  # Likewise for get_time: let it return fail status.
        self.evttim.clear()
        await asyncio.sleep_ms(0)
        self._evtrun.clear()  # Set by .do_status
        s_task, s_args = self.s_han
        if self.lw_topic is not None:
            channel.send(argformat(WILL, self.lw_topic, self.lw_msg, self.lw_retain, self.lw_qos))
            res = await channel.await_obj()
            if res is None:  # SynCom timeout
                await s_task(self, ESP_FAIL, *s_args)
                return self.quit('ESP8266 fail 1. Resetting.')
            command, action = self.get_cmd(res)
            if command == STATUS:
                iact = self.do_status(action, -1)
                await s_task(self, iact, *s_args)
                if iact in _BAD_STATUS:
                    return self.quit('Bad status: {}. Resetting.'.format(iact))
            else:
                self.verbose and print('Expected will OK got: ', command, action)
        channel.send(self.init_str)
        while not self._evtrun.is_set():  # Until RUNNING status received
            res = await channel.await_obj()
            if res is None:
                await s_task(self, ESP_FAIL, *s_args)
                return self.quit('ESP8266 fail 2. Resetting.')
            command, action = self.get_cmd(res)
            if command == STATUS:
                iact = self.do_status(action, -1)
                result = await s_task(self, iact, *s_args)
                if iact == SPECNET:
                    if result == 1:
                        channel.send('1')  # Any string will do
                    else:
                        return self.quit()
                if iact in _BAD_STATUS:
                    return self.quit('Bad status. Resetting.')
            else:
                self.verbose and print('Init got: ', command, action)
        # On power up this will do nothing because user awaits .ready
        # before subscribing, so self.subs will be empty.
        for topic in self.subs:
            qos = self.subs[topic][1]
            self.channel.send(argformat(SUBSCRIBE, topic, qos))

        self.verbose and print('About to run user program.')
        if self.user_start[0] is not None:
            self.user_start[0](self, *self.user_start[1])  # User start function
        cb, args = self.wifi_han
        cb(True, self, *args)

# Initialisation is complete. Process messages from ESP8266.
        iact = -1  # Invalidate last status for change detection
        while True:  # print incoming messages, handle subscriptions
            chan_state = channel.any()
            if chan_state is None:  # SynCom Timeout
                self._evtrun.clear()
            elif chan_state > 0:
                res = await channel.await_obj()
                command, action = self.get_cmd(res)
                if command == SUBSCRIPTION:
                    if action[0] in self.subs: # topic found
                        cb, qos, args = self.subs[action[0]]
                        action += args
                        # Callback gets topic, message, retained, plus any user args
                        cb(*action)  # Run the callback
                elif command == STATUS:  # 1st arg of status is an integer
                    iact = self.do_status(action, iact) # Update pub q and wifi status
                    await s_task(self, iact, *s_args)
                    if iact in _DIRE_STATUS:
                        return self.quit('Fatal status. Resetting.')
                elif command == TIME:
                    self._do_time(action)
                elif command == MEM:  # Wouldn't ask for this if we didn't want a printout
                    print('ESP8266 RAM free: {} allocated: {}'.format(action[0], action[1]))
                else:
                    await s_task(self, UNKNOWN, *s_args)
                    return self.quit('Got unhandled command, resetting ESP8266:', command, action)  # ESP8266 has failed

            await asyncio.sleep_ms(20)
            if not self._evtrun.is_set():  # self.quit() has been called.
                await s_task(self, NO_NET, *s_args)
                return self.quit('Not running, resetting ESP8266')
Esempio n. 6
0
class MQTTlink(object):
    lw_topic = None
    lw_msg = None
    lw_retain = False
    lw_qos = 0
    @classmethod
    def will(cls, topic, msg, retain=False, qos=0):
        cls.lw_topic = topic
        cls.lw_msg = msg
        cls.lw_retain = retain
        cls.lw_qos = qos
    status_msgs = ('connected to broker', 'awaiting broker',
                'awaiting default network', 'awaiting specified network',
                'publish OK', 'running', 'unk', 'Will registered', 'Fail to connect to broker',
                'WiFi up', 'WiFi down')

    def __init__(self, d):
        self.user_start = (d['user_start'], d['args'])
        self.init_str = buildinit(d)
        # Synchroniser idle until started.
        self.rtc_synchroniser = RTCsynchroniser(self, d['rtc_resync'], d['local_time_offset'])
        self.keepalive = d['keepalive']
        self._running = False
        self.verbose = d['verbose']
        # Watchdog timeout for ESP8266 (ms).
        wdog = d['timeout'] * 1000
        # SynCom string mode
        self.channel = SynCom(False, d['sckin'], d['sckout'], d['srx'], d['stx'],
                              d['reset'], wdog, True, self.verbose)
        loop = asyncio.get_event_loop()
        loop.create_task(heartbeat())
        # Start the SynCom instance. This will run self.start(). If an error
        # occurs self.quit() is called which cancels all NamedTask instances
        # and returns to Syncom's start() method. This waits on Cancellable.cancel_all()
        # before forcing a failure on the ESP8266 and re-running self.start()
        # to perform a clean restart.
        loop.create_task(self.channel.start(self.start, Killer()))
        self.subs = {} # Callbacks indexed by topic
        self.pubs = []
        self._pub_free = True  # No publication in progress
        self.s_han = default_status_handler
        self.wifi_han = (lambda *_ : None, ())
        self._wifi_up = False
        # Only specify network on first run
        self.first_run = True


# API
    def publish(self, topic, msg, retain=False, qos=0):
        qos_check(qos)
        validate(topic, 'topic')  # Raise ValueError if invalid.
        validate(msg, 'message')
        self.pubs.append((topic, msg, 1 if retain else 0, qos))

    def pubq_len(self):
        return len(self.pubs)

    def subscribe(self, topic, qos, callback, *args):
        qos_check(qos)
        self.subs[topic] = (callback, args)
        self.channel.send(argformat(SUBSCRIBE, topic, qos))

    # Command handled directly by mqtt.py on ESP8266 e.g. MEM
    def command(self, *argsend):
        self.channel.send(argformat(*argsend))

    # Is Pyboard RTC synchronised?
    def rtc_syn(self):
        return self.rtc_synchroniser._rtc_syn()

    def status_handler(self, coro):
        self.s_han = coro

    def wifi_handler(self, cb, *args):
        self.wifi_han = (cb, args)

    def running(self):
        return self._running

    def wifi(self):
        return self._wifi_up
# API END

# Convenience method allows return self.quit() on error
    def quit(self, *args):
        if args is not ():
            self.vbprint(*args)
        self._running = False
        # Note that if this method is called from self.start return is to
        # SynCom's start method which will wait on the Killer instance to
        # cancel all Cancellable tasks

# On publication of a qos 1 message this is called with False. This prevents
# further pubs until the response is received, ensuring we wait for the PUBACK
# that corresponds to the last publication.
# This delay can be arbitrarily long if WiFi connectivity has been lost.
    def pub_free(self, val = None):
        if val is not None:
            self._pub_free = val
        return self._pub_free


    def vbprint(self, *args):
        if self.verbose:
            print(*args)

    def get_cmd(self, istr):
        ilst = istr.split(SEP)
        return ilst[0], ilst[1:]

    def do_status(self, action, last_status):
        try:
            iact = int(action[0])
        except ValueError:  # Gibberish from ESP8266: caller reboots
            return UNKNOWN
        if iact == PUBOK:
            self.pub_free(True)  # Unlock so we can do another pub.
        elif iact == RUNNING:
            self._running = True
        # Detect WiFi status changes. Ignore initialisation and repeats
        if last_status != -1 and iact != last_status and iact in (WIFI_UP, WIFI_DOWN):
            self._wifi_up = iact == WIFI_UP
            cb, args = self.wifi_han
            cb(self._wifi_up, *args)
        if self.verbose:
            if iact != UNKNOWN:
                if iact != last_status:  # Ignore repeats
                    printtime()
                    print('Status: ', self.status_msgs[iact])
            else:
                printtime()
                print('Unknown status: {} {}'.format(action[1], action[2]))
        return iact


# PUBLISH
    @asyn.cancellable
    async def _publish(self):
        while True:
            if len(self.pubs): 
                args = self.pubs.pop(0)
                secs = 0
                # pub free can be a long time coming if WiFi is down
                while not self._pub_free:
                    # If pubs are queued, wait 1 sec to respect ESP8266 buffers
                    await asyncio.sleep(1)
                    if self._wifi_up:
                        secs += 1
                    if secs > 60:
                        self.quit('No response from ESP8266')
                        break
                else:
                    self.channel.send(argformat(PUBLISH, *args))
                    # All pubs send PUBOK.
                    # No more pubs allowed until PUBOK received.
                    self.pub_free(False)
            else:
                await asyncio.sleep_ms(20)

# start() is run each time the channel acquires sync i.e. on startup and also
# after an ESP8266 crash and reset.
# Behaviour after fatal error with ESP8266:
# It sets _running False to cause local tasks to terminate, then returns.
# A return causes the local channel instance to wait on the exit_gate to cause
# tasks in this program terminate, then issues a hardware reset to the ESP8266.
# (See SynCom.start() method). After acquiring sync, start() gets rescheduled.
    async def start(self, channel):
        self.vbprint('Starting...')
        self.subs = {} # Callbacks indexed by topic
        self._pub_free = True  # No publication in progress
        self._running = False
        if self.lw_topic is not None:
            channel.send(argformat(WILL, self.lw_topic, self.lw_msg, self.lw_retain, self.lw_qos))
            res = await channel.await_obj()
            if res is None:  # SynCom timeout
                await self.s_han(self, ESP_FAIL)
                return self.quit('ESP8266 fail 1. Resetting.')
            command, action = self.get_cmd(res)
            if command == STATUS:
                iact = self.do_status(action, -1)
                await self.s_han(self, iact)
                if iact in _BAD_STATUS:
                    return self.quit('Bad status: {}. Resetting.'.format(iact))
            else:
                self.vbprint('Expected will OK got: ', command, action)
        channel.send(self.init_str)
        while not self._running:  # Until RUNNING status received
            res = await channel.await_obj()
            if res is None:
                await self.s_han(self, ESP_FAIL)
                return self.quit('ESP8266 fail 2. Resetting.')
            command, action = self.get_cmd(res)
            if command == STATUS:
                iact = self.do_status(action, -1)
                result = await self.s_han(self, iact)
                if iact == SPECNET:
                    if result == 1:
                        channel.send('1')  # Any string will do
                    else:
                        return self.quit()
                if iact in _BAD_STATUS:
                    return self.quit('Bad status. Resetting.')
            else:
                self.vbprint('Init got: ', command, action)

        self.vbprint('About to run user program.')
        if self.user_start is not None:
            self.user_start[0](self, *self.user_start[1])  # User start function
        loop = asyncio.get_event_loop()
        loop.create_task(asyn.Cancellable(self._publish)())
        self.rtc_synchroniser._start()  # Run coro if synchronisation is required.
        cb, args = self.wifi_han
        cb(True, *args)

# Initialisation is complete. Process messages from ESP8266.
        iact = -1                   # Invalidate last status for change detection
        while True:                 # print incoming messages, handle subscriptions
            chan_state = channel.any()
            if chan_state is None:  # SynCom Timeout
                self._running = False
            elif chan_state > 0:
                res = await channel.await_obj()
                command, action = self.get_cmd(res)
                if command == SUBSCRIPTION:
                    if action[0] in self.subs: # topic found
                        cb, args = self.subs[action[0]]
                        action += args
                        cb(*action)  # Run the callback
                elif command == STATUS:  # 1st arg of status is an integer
                    iact = self.do_status(action, iact) # Update pub q and wifi status
                    await self.s_han(self, iact)
                    if iact in _DIRE_STATUS:
                        return self.quit('Fatal status. Resetting.')
                elif command == TIME:
                    self.rtc_synchroniser._do_time(action)
                elif command == MEM:  # Wouldn't ask for this if we didn't want a printout
                    print('ESP8266 RAM free: {} allocated: {}'.format(action[0], action[1]))
                else:
                    await self.s_han(self, UNKNOWN)
                    return self.quit('Got unhandled command, resetting ESP8266:', command, action)  # ESP8266 has failed

            await asyncio.sleep_ms(20)
            if not self._running:  # self.quit() has been called.
                await self.s_han(self, NO_NET)
                return self.quit('Not running, resetting ESP8266')