micropython.rst 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. .. _micropython_port:
  2. 7. ThingFlow-MicroPython Port
  3. =============================
  4. This section describes the port of ThingFlow to MicroPython_, a bare-metal implementation of
  5. Python 3 for small processors. This port has been tested on the ESP8266_
  6. using version 1.8.7 of MicroPython.
  7. MicroPython has only a subset of the libraries that come with the standard
  8. CPython implementation. For example, an event library, threading, and even
  9. logging are missing. This ThingFlow port currently only provides a subset of the
  10. ThingFlow functionality, due to the library limitation and memory limitations
  11. on the ESP8266.. The assumption is that processors like
  12. the ESP8266 are used primarily to sample sensor data and pass it on to
  13. a larger system (e.g. a Raspberry Pi or a server).
  14. The code for this port may
  15. be found in the main ThingFlow-Python repository, under the ``micropython``
  16. subdirectory.
  17. The core implementation is in ``miropython/thingflow.py``.
  18. The other files (``logger.py``,
  19. ``mqtt_writer.py``, and ``wifi.py``) provide some additional utilities.
  20. .. _MicroPython: http://www.micropython.org
  21. .. _ESP8266: https://en.wikipedia.org/wiki/ESP8266
  22. Installing
  23. -----------
  24. Just copy the python files in this directory to your MicroPython board.
  25. MicroPython's webrepl has experimental support for copying files. I
  26. instead used mpfshell_ to copy files to my ESP8266 board.
  27. To free up more memory, I disabled the starting if the webrepl in the
  28. ``boot.py`` script.
  29. .. _mpfshell: https://github.com/wedlers/mpfshell
  30. Bug Workarounds
  31. ---------------
  32. The thingflow code has a few workarounds for bugs in MicroPython (at least
  33. the ESP8266 port).
  34. Clock wrapping
  35. ~~~~~~~~~~~~~~
  36. The clock on the ESP8266 wraps once every few hours. This causes problems when
  37. we wish to measure sleep time. The ``utime.ticks_diff()`` function is
  38. supposed to handle this, bug apparently is buggy. This leads to cases where
  39. the calculation:::
  40. int(round(utime.ticks_diff(end_ts, now)/1000))
  41. yields 1,069,506 seconds instead of 59 seconds. Luckily, an assert in
  42. ``_advance_time`` caught the issue. The clock had clearly wrapped as
  43. ``end_ts`` (the earlier time) was 4266929 and the current timestamp was 30963.
  44. Long variable names for keyword arguments
  45. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  46. There is a bug in MicroPython where keyword argument names longer than 10
  47. characters can result in a incorrect exception saying that keyword arguments
  48. are not implemented. I think this is related to MicroPython issue #1998.
  49. Sensors
  50. -------
  51. Sensor code for the MicroPython port are in the ``sensors`` subdirectory.
  52. See the ``README.rst`` file in that directory for details.
  53. Design Notes
  54. ------------
  55. Scheduler Design
  56. ~~~~~~~~~~~~~~~~
  57. Since MicroPython does not provide an event scheduler, [#]_ we provide one directly
  58. in ``thingflow.py``. This scheduler is optimized for minimal power consumption (by
  59. reducing wake-ups) rather than for robustness in the face of tight deadlines.
  60. The scheduler has two layers: the *internal* layer (represented by the methods
  61. starting with an underscore) and the *public* layer. The public layer provides
  62. an API similar to the standard ThingFlow scheduler and is built on the internal
  63. layer.
  64. For ease of testing and flexibility, the internal layer is designed such that the
  65. sensor sampling and the sleeps between events happen outside it. The internal
  66. layer is responsible for determining sleep times and the set of tasks to
  67. sample at each wakeup.
  68. Time within the internal layer is measured in "ticks". Currently, one tick
  69. is 10 ms (1 "centisecond"). Fractional ticks are
  70. not allowed, to account for systems that cannot handle floating point sleep
  71. times. The scheduler tracks the current logical time in ticks and updates
  72. this based on elapsed time. To avoid issues with unexpected wrapping of the
  73. logical clock, it is automatically wrapped every 65535 ticks. The logical
  74. times for each for each scheduled task are then updated to reflect the wrapped
  75. clock.
  76. When a task (output_thing) is added to the scheduler, a sample interval is
  77. specified. To optimize for wakeups, the following approaches are used:
  78. 1. Tasks with the same interval are always scheduled together. If a new task is
  79. added that matches an older task's interval, it will not be scheduled until
  80. the existing one is run.
  81. 2. If there are no tasks with the same interval, we look for the smallest
  82. interval that is either a factor or multiple of the new interval. We
  83. schedule the new interval to be coordinated with this one. For example, if
  84. we have a new interval 60 seconds and old intervals 30/45 seconds, we will
  85. schedule the new 60 second interval to first run on the next execution
  86. of the 30 second tasks. Thus, they will run at the same time for each
  87. execution of the 60 second interval.
  88. 3. The next time for a task is maintained in logical ticks. If a task is run
  89. later than its interval, the next scheduled execution is kept as if the task
  90. had run at the correct time (by making the interval shorter). This avoids
  91. tasks getting out-of-sync when one misses a deadline.
  92. The public API layer is a subset of the standard ThingFlow scheduler API,
  93. except for the additional ``schedule_sensor`` convenience method.
  94. .. [#] I have heard rumors about a port of the ``async`` library to MicroPython.
  95. However, it still makes sense to use this custom scheduler, as it is
  96. likely to be more power efficent due to its strategy of scheduling
  97. events together.
  98. Logger
  99. ~~~~~~
  100. ``logger.py`` provides a very simple rotating file logger with an API that
  101. is a subset of Python's ``logging`` API. Given a log file ``outputfile``,
  102. it will log events to that file until the length would exceed ``max_len``.
  103. At that point, it renames the file to ``outputfile``.1 and then starts
  104. a new logfile. Thus, the maximum size of the logs at any given time should
  105. be 2*``max_len``.
  106. To use the logger, you need to first call ``initialize_logging()``. You can
  107. then call ``get_logger()`` to get the logger instance. Note that there is a
  108. single global log level. The whole mess of handlers, formatters, and filters
  109. is not provided.