README.rst 5.8 KB

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