Skip to content

Commit c88500b

Browse files
microbit-markmicrobit-carlos
authored andcommitted
docs: Add data logging API (bbcmicrobit#720)
Includes microbit.run_every function/decorator.
1 parent 93203e2 commit c88500b

File tree

6 files changed

+197
-0
lines changed

6 files changed

+197
-0
lines changed

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ Projects related to MicroPython on the BBC micro:bit include:
7979
ble.rst
8080
button.rst
8181
compass.rst
82+
log.rst
8283
display.rst
8384
filesystem.rst
8485
i2c.rst

docs/log-html-view.jpeg

26.2 KB
Loading

docs/log-my_data.png

40.2 KB
Loading

docs/log.rst

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
Data Logging **V2**
2+
*******************
3+
4+
.. py:module:: log
5+
6+
This module lets you log data to a ``MY_DATA`` file saved on a micro:bit
7+
**V2** ``MICROBIT`` USB drive.
8+
9+
.. image:: log-my_data.png
10+
11+
The data is structured in a table format and it can be viewed and plotted with
12+
a browser.
13+
14+
.. image:: log-html-view.jpeg
15+
16+
Further guidance on this feature can be found on the
17+
`data logging page of the microbit.org website
18+
<https://microbit.org/get-started/user-guide/data-logging/>`_.
19+
20+
Functions
21+
=========
22+
23+
.. py:function:: set_labels(*labels, timestamp=log.SECONDS)
24+
25+
Set up the log file header.
26+
27+
This function accepts any number of positional arguments, each creates
28+
a column header, e.g. ``log.set_labels("X", "Y", "Z")``.
29+
30+
Ideally this function should be called a single time, before any data is
31+
logged, to configure the data table header once.
32+
33+
If a log file already exists when the programme starts, or if this function
34+
is called multiple times, it will check the labels already defined in the
35+
log file.
36+
If this function call contains any new labels not already present, it will
37+
generate a new header row with the additional columns.
38+
39+
By default the first column contains a time stamp for each row. The time
40+
unit can be selected via the ``timestamp`` argument, e.g.
41+
``log.set_labels("temp", timestamp=log.MINUTES)``
42+
43+
:param \*labels: Any number of positional arguments, each corresponding to
44+
an entry in the log header.
45+
:param timestamp: Select the timestamp unit that will be automatically
46+
added as the first column in every row. Timestamp values can be one of
47+
``log.MILLISECONDS``, ``log.SECONDS``, ``log.MINUTES``, ``log.HOURS``,
48+
``log.DAYS`` or ``None`` to disable the timestamp. The default value
49+
is ``log.SECONDS``.
50+
51+
.. py:function:: set_mirroring(serial)
52+
53+
Configure mirroring of the data logging activity to the serial output.
54+
55+
Serial mirroring is disabled by default. When enabled, it will print to
56+
serial each row logged into the log file.
57+
58+
:param serial: ``True`` enables mirroring data to the serial output.
59+
60+
.. py:function:: delete(full=False)
61+
62+
Delete the contents of the log, including headers.
63+
64+
To add the log headers again the ``set_labels`` function should to be
65+
called after this function.
66+
67+
There are two erase modes; "full" completely removes the data from the
68+
physical storage, and "fast" invalidates the data without removing it.
69+
70+
:param full: ``True`` selects a "full" erase and ``False`` selects the
71+
"fast" erase method.
72+
73+
.. py:function:: add( data_dictionary, /, *, **kwargs)
74+
75+
Add a data row to the log.
76+
77+
There are two ways to log data with this function:
78+
79+
#. Via keyword arguments, each argument name representing a label.
80+
81+
* e.g. ``log.add(X=compass.get_x(), Y=compass.get_y())``
82+
83+
#. Via a dictionary, each dictionary key representing a label.
84+
85+
* e.g. ``log.add({ "X": compass.get_x(), "Y": compass.get_y() })``
86+
87+
The keyword argument option can be easier to use, and the dictionary option
88+
allows the use of spaces (and other special characters), that could not be
89+
used with the keyword arguments.
90+
91+
New labels not previously specified via the ``set_labels`` function, or by
92+
a previous call to this function, will trigger a new header entry to be
93+
added to the log with the extra labels.
94+
95+
Labels previously specified and not present in a call to this function will
96+
be skipped with an empty value in the log row.
97+
98+
:raise OSError: When the log is full this function raises an ``OSError``
99+
exception with error code 28 ``ENOSPC``, which indicates there is no
100+
space left in the device.
101+
102+
Examples
103+
========
104+
105+
A minimal example::
106+
107+
from microbit import *
108+
import log
109+
110+
# Set the timer to log data every 5 seconds
111+
@run_every(s=5)
112+
def log_temp():
113+
log.add(temp=temperature())
114+
115+
while True:
116+
# Needed so that the programme doesn't end
117+
sleep(100)
118+
119+
An example that runs through all of the functions of the log module API:
120+
121+
.. include:: ../examples/data-logging.py
122+
:code: python

docs/microbit.rst

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,38 @@ Functions
7676
:param n: An integer or floating point number indicating the number of
7777
milliseconds to wait.
7878

79+
.. py:function:: run_every(callback, h=None, min=None, s=None, ms=None)
80+
81+
Schedule to run a function at the interval specified by the time arguments.
82+
83+
``run_every`` can be used in two ways:
84+
85+
* As a **Decorator** - placed on top of the function to schedule.
86+
For example::
87+
88+
@run_every(h=1, min=20, s=30, ms=50)
89+
def my_function():
90+
# Do something here
91+
92+
* As a **Function** - passing the callback as a positional argument.
93+
For example::
94+
95+
def my_function():
96+
# Do something here
97+
run_every(my_function, s=30)
98+
99+
Each arguments corresponds to a different time unit and they are additive.
100+
So ``run_every(min=1, s=30)`` schedules the callback every minute and
101+
a half.
102+
103+
When an exception is thrown inside the callback function it deschedules
104+
the function. To avoid this you can catch exceptions with ``try/except``.
105+
106+
:param callback: Function to call at the provided interval.
107+
:param h: Sets the hour mark for the scheduling.
108+
:param min: Sets the minute mark for the scheduling.
109+
:param s: Sets the second mark for the scheduling.
110+
:param ms: Sets the millisecond mark for the scheduling.
79111

80112
.. py:function:: temperature()
81113

examples/data-logging.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from microbit import *
2+
import log
3+
4+
# Configure the labels and select a time unit for the timestamp
5+
log.set_labels('temp', 'brightness', timestamp=log.SECONDS)
6+
7+
# Send each data row to the serial output
8+
log.set_mirroring(True)
9+
10+
continue_logging = True
11+
12+
# This decorator schedules this function to run every 10s 50ms
13+
@run_every(s=10, ms=50)
14+
def log_data():
15+
"""Log the temperature and light level, and display an icon."""
16+
global continue_logging
17+
if continue_logging:
18+
display.show(Image.SURPRISED)
19+
try:
20+
log.add(temp=temperature(), brightness=display.read_light_level())
21+
except OSError:
22+
continue_logging = False
23+
display.scroll("Log full")
24+
sleep(500)
25+
26+
while True:
27+
if button_a.is_pressed() and button_b.is_pressed():
28+
display.show(Image.CONFUSED)
29+
# Delete the log file using the "full" options, which takes
30+
# longer but ensures the data is wiped from the device
31+
log.delete(full=True)
32+
continue_logging = True
33+
elif button_a.is_pressed():
34+
display.show(Image.HAPPY)
35+
# Log only the light level, the temp entry will be empty. If the log
36+
# is full this will throw an exception and the programme will stop
37+
log.add({
38+
"brightness": display.read_light_level()
39+
})
40+
else:
41+
display.show(Image.ASLEEP)
42+
sleep(500)

0 commit comments

Comments
 (0)