Update Micropython. ILI9341/XPT2046 fixes
This commit is contained in:
parent
bd027c9f04
commit
2c6d171f25
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -30,3 +30,6 @@
|
||||
[submodule "lib/tinyusb"]
|
||||
path = lib/tinyusb
|
||||
url = https://github.com/hathach/tinyusb
|
||||
[submodule "lib/mynewt-nimble"]
|
||||
path = lib/mynewt-nimble
|
||||
url = https://github.com/apache/mynewt-nimble.git
|
||||
|
||||
46
.travis.yml
46
.travis.yml
@ -32,8 +32,8 @@ jobs:
|
||||
- sudo apt-get install libnewlib-arm-none-eabi
|
||||
- arm-none-eabi-gcc --version
|
||||
script:
|
||||
- git submodule update --init lib/lwip lib/mbedtls lib/stm32lib
|
||||
- make ${MAKEOPTS} -C mpy-cross
|
||||
- make ${MAKEOPTS} -C ports/stm32 submodules
|
||||
- make ${MAKEOPTS} -C ports/stm32 BOARD=NUCLEO_F091RC
|
||||
- make ${MAKEOPTS} -C ports/stm32 BOARD=PYBV11 MICROPY_PY_WIZNET5K=5200 MICROPY_PY_CC3K=1
|
||||
- make ${MAKEOPTS} -C ports/stm32 BOARD=PYBD_SF2
|
||||
@ -66,8 +66,8 @@ jobs:
|
||||
- gcc --version
|
||||
- python3 --version
|
||||
script:
|
||||
- git submodule update --init lib/axtls lib/berkeley-db-1.xx lib/libffi
|
||||
- make ${MAKEOPTS} -C mpy-cross
|
||||
- make ${MAKEOPTS} -C ports/unix submodules
|
||||
- make ${MAKEOPTS} -C ports/unix deplibs
|
||||
- make ${MAKEOPTS} -C ports/unix coverage
|
||||
# run the main test suite
|
||||
@ -87,23 +87,23 @@ jobs:
|
||||
- stage: test
|
||||
env: NAME="unix port build and tests"
|
||||
script:
|
||||
- git submodule update --init lib/axtls lib/berkeley-db-1.xx lib/libffi
|
||||
- make ${MAKEOPTS} -C mpy-cross
|
||||
- make ${MAKEOPTS} -C ports/unix submodules
|
||||
- make ${MAKEOPTS} -C ports/unix deplibs
|
||||
- make ${MAKEOPTS} -C ports/unix
|
||||
- make ${MAKEOPTS} -C ports/unix test
|
||||
- (cd tests && MICROPY_CPYTHON3=python3 MICROPY_MICROPYTHON=../ports/unix/micropython ./run-perfbench.py 1000 1000)
|
||||
|
||||
# unix nanbox
|
||||
# unix nanbox (and using Python 2 to check it can run the build scripts)
|
||||
- stage: test
|
||||
env: NAME="unix nanbox port build and tests"
|
||||
install:
|
||||
- sudo apt-get install gcc-multilib libffi-dev:i386
|
||||
script:
|
||||
- git submodule update --init lib/axtls lib/berkeley-db-1.xx lib/libffi
|
||||
- make ${MAKEOPTS} -C mpy-cross
|
||||
- make ${MAKEOPTS} -C ports/unix deplibs
|
||||
- make ${MAKEOPTS} -C ports/unix nanbox
|
||||
- make ${MAKEOPTS} -C mpy-cross PYTHON=python2
|
||||
- make ${MAKEOPTS} -C ports/unix submodules
|
||||
- make ${MAKEOPTS} -C ports/unix PYTHON=python2 deplibs
|
||||
- make ${MAKEOPTS} -C ports/unix PYTHON=python2 nanbox
|
||||
- (cd tests && MICROPY_CPYTHON3=python3 MICROPY_MICROPYTHON=../ports/unix/micropython_nanbox ./run-tests)
|
||||
|
||||
# unix stackless
|
||||
@ -112,8 +112,8 @@ jobs:
|
||||
install:
|
||||
- sudo apt-get install clang
|
||||
script:
|
||||
- git submodule update --init lib/axtls lib/berkeley-db-1.xx lib/libffi
|
||||
- make ${MAKEOPTS} -C mpy-cross CC=clang
|
||||
- make ${MAKEOPTS} -C ports/unix submodules
|
||||
- make ${MAKEOPTS} -C ports/unix CC=clang CFLAGS_EXTRA="-DMICROPY_STACKLESS=1 -DMICROPY_STACKLESS_STRICT=1"
|
||||
- make ${MAKEOPTS} -C ports/unix CC=clang test
|
||||
|
||||
@ -128,6 +128,13 @@ jobs:
|
||||
after_failure:
|
||||
- (cd tests && for exp in *.exp; do testbase=$(basename $exp .exp); echo -e "\nFAILURE $testbase"; diff -u $testbase.exp $testbase.out; done)
|
||||
|
||||
# minimal unix port with tests
|
||||
- stage: test
|
||||
env: NAME="minimal unix port build and tests"
|
||||
script:
|
||||
- make ${MAKEOPTS} -C ports/unix minimal
|
||||
- (cd tests && MICROPY_CPYTHON3=python3 MICROPY_MICROPYTHON=../ports/unix/micropython_minimal ./run-tests -e exception_chain -e self_type_check -e subclass_native_init -d basics)
|
||||
|
||||
# windows port via mingw
|
||||
- stage: test
|
||||
env: NAME="windows port build via mingw"
|
||||
@ -149,18 +156,19 @@ jobs:
|
||||
- git clone https://github.com/espressif/esp-idf.git
|
||||
- export IDF_PATH=$(pwd)/esp-idf
|
||||
script:
|
||||
- git submodule update --init lib/berkeley-db-1.xx
|
||||
- make ${MAKEOPTS} -C mpy-cross
|
||||
# IDF v3 build
|
||||
- git -C esp-idf checkout $(grep "ESPIDF_SUPHASH_V3 :=" ports/esp32/Makefile | cut -d " " -f 3)
|
||||
- git -C esp-idf submodule update --init components/json/cJSON components/esp32/lib components/esptool_py/esptool components/expat/expat components/lwip/lwip components/mbedtls/mbedtls components/micro-ecc/micro-ecc components/nghttp/nghttp2
|
||||
- make ${MAKEOPTS} -C ports/esp32 submodules
|
||||
- make ${MAKEOPTS} -C ports/esp32
|
||||
# clean
|
||||
- git -C esp-idf clean -f -f -d components/json/cJSON components/esp32/lib components/expat/expat components/micro-ecc/micro-ecc components/nghttp/nghttp2
|
||||
- make ${MAKEOPTS} -C ports/esp32 clean
|
||||
# IDF v4 build
|
||||
- git -C esp-idf checkout $(grep "ESPIDF_SUPHASH_V4 :=" ports/esp32/Makefile | cut -d " " -f 3)
|
||||
- git -C esp-idf submodule update --init components/esp_wifi/lib_esp32 components/esptool_py/esptool components/lwip/lwip components/mbedtls/mbedtls
|
||||
- git -C esp-idf submodule update --init components/bt/controller/lib components/bt/host/nimble/nimble components/esp_wifi/lib_esp32 components/esptool_py/esptool components/lwip/lwip components/mbedtls/mbedtls
|
||||
- make ${MAKEOPTS} -C ports/esp32 submodules
|
||||
- make ${MAKEOPTS} -C ports/esp32
|
||||
|
||||
# esp8266 port
|
||||
@ -171,9 +179,10 @@ jobs:
|
||||
- zcat xtensa-lx106-elf-standalone.tar.gz | tar x
|
||||
- export PATH=$(pwd)/xtensa-lx106-elf/bin:$PATH
|
||||
script:
|
||||
- git submodule update --init lib/axtls lib/berkeley-db-1.xx
|
||||
- make ${MAKEOPTS} -C mpy-cross
|
||||
- make ${MAKEOPTS} -C ports/esp8266 submodules
|
||||
- make ${MAKEOPTS} -C ports/esp8266
|
||||
- make ${MAKEOPTS} -C ports/esp8266 BOARD=GENERIC_512K
|
||||
|
||||
# nrf port
|
||||
- stage: test
|
||||
@ -183,7 +192,7 @@ jobs:
|
||||
- sudo apt-get install libnewlib-arm-none-eabi
|
||||
- arm-none-eabi-gcc --version
|
||||
script:
|
||||
- git submodule update --init lib/nrfx
|
||||
- make ${MAKEOPTS} -C ports/nrf submodules
|
||||
- make ${MAKEOPTS} -C ports/nrf
|
||||
|
||||
# bare-arm and minimal ports
|
||||
@ -219,7 +228,7 @@ jobs:
|
||||
- sudo apt-get install gcc-arm-none-eabi
|
||||
- sudo apt-get install libnewlib-arm-none-eabi
|
||||
script:
|
||||
- git submodule update --init lib/asf4 lib/tinyusb
|
||||
- make ${MAKEOPTS} -C ports/samd submodules
|
||||
- make ${MAKEOPTS} -C ports/samd
|
||||
|
||||
# teensy port
|
||||
@ -230,3 +239,12 @@ jobs:
|
||||
- sudo apt-get install libnewlib-arm-none-eabi
|
||||
script:
|
||||
- make ${MAKEOPTS} -C ports/teensy
|
||||
|
||||
# powerpc port
|
||||
- stage: test
|
||||
env: NAME="powerpc port build"
|
||||
install:
|
||||
- sudo apt-get install gcc-powerpc64le-linux-gnu
|
||||
- sudo apt-get install libc6-dev-ppc64el-cross
|
||||
script:
|
||||
- make ${MAKEOPTS} -C ports/powerpc CROSS_COMPILE=powerpc64le-linux-gnu-
|
||||
|
||||
53
CODEOFCONDUCT.md
Normal file
53
CODEOFCONDUCT.md
Normal file
@ -0,0 +1,53 @@
|
||||
MicroPython Code of Conduct
|
||||
===========================
|
||||
|
||||
The MicroPython community is made up of members from around the globe with a
|
||||
diverse set of skills, personalities, and experiences. It is through these
|
||||
differences that our community experiences great successes and continued growth.
|
||||
When you're working with members of the community, this Code of Conduct will
|
||||
help steer your interactions and keep MicroPython a positive, successful, and
|
||||
growing community.
|
||||
|
||||
Members of the MicroPython community are open, considerate, and respectful.
|
||||
Behaviours that reinforce these values contribute to a positive environment, and
|
||||
include: acknowledging time and effort, being respectful of differing viewpoints
|
||||
and experiences, gracefully accepting constructive criticism, and using
|
||||
welcoming and inclusive language.
|
||||
|
||||
Every member of our community has the right to have their identity respected.
|
||||
The MicroPython community is dedicated to providing a positive experience for
|
||||
everyone, regardless of age, gender identity and expression, sexual orientation,
|
||||
disability, physical appearance, body size, ethnicity, nationality, race, or
|
||||
religion (or lack thereof), education, or socio-economic status.
|
||||
|
||||
Unacceptable behaviour includes: harassment, trolling, deliberate intimidation,
|
||||
violent threats or language directed against another person; insults, put downs,
|
||||
or jokes that are based upon stereotypes, that are exclusionary, or that hold
|
||||
others up for ridicule; unwelcome sexual attention or advances; sustained
|
||||
disruption of community discussions; publishing others' private information
|
||||
without explicit permission; and other conduct that is inappropriate for a
|
||||
professional audience including people of many different backgrounds.
|
||||
|
||||
This code of conduct covers all online and offline presence related to the
|
||||
MicroPython project, including GitHub and the forum. If a participant engages
|
||||
in behaviour that violates this code of conduct, the MicroPython team may take
|
||||
action as they deem appropriate, including warning the offender or expulsion
|
||||
from the community. Community members asked to stop any inappropriate behaviour
|
||||
are expected to comply immediately.
|
||||
|
||||
Thank you for helping make this a welcoming, friendly community for everyone.
|
||||
|
||||
If you believe that someone is violating the code of conduct, or have any other
|
||||
concerns, please contact a member of the MicroPython team by emailing
|
||||
contact@micropython.org.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
This Code of Conduct is licensed under the Creative Commons
|
||||
Attribution-ShareAlike 3.0 Unported License.
|
||||
|
||||
Attributions
|
||||
------------
|
||||
|
||||
Based on the Python code of conduct found at https://www.python.org/psf/conduct/
|
||||
43
README.md
43
README.md
@ -9,8 +9,9 @@ See also [Micropython + LittlevGL](https://blog.littlevgl.com/2019-02-20/micropy
|
||||
1. `sudo apt-get install build-essential libreadline-dev libffi-dev git pkg-config libsdl2-2.0-0 libsdl2-dev python`
|
||||
2. `git clone --recurse-submodules https://github.com/littlevgl/lv_micropython.git`
|
||||
3. `cd lv_micropython`
|
||||
4. `make -C ports/unix/`
|
||||
5. `./ports/unix/micropython`
|
||||
4. `make -C mpy-cross`
|
||||
5. `make -C ports/unix/`
|
||||
6. `./ports/unix/micropython`
|
||||
|
||||
### For ESP32 port
|
||||
|
||||
@ -29,7 +30,7 @@ make -C ports/esp32 LV_CFLAGS="-DLV_COLOR_DEPTH=16 -DLV_COLOR_16_SWAP=1" BOARD=G
|
||||
Explanation about the paramters:
|
||||
- `LV_CFLAGS` are used to override color depth and swap mode, for ILI9341 compatibility.
|
||||
- `LV_COLOR_DEPTH=16` is needed if you plan to use the ILI9341 driver.
|
||||
- `LV_COLOR_16_SWAP=1` is **only** needed if you plan to use the [Pure Micropython Display Driver](https://blog.littlevgl.com/2019-08-05/micropython-pure-display-driver). Otherwise, remove it.
|
||||
- `LV_COLOR_16_SWAP=1` is needed if you plan to use the [Pure Micropython Display Driver](https://blog.littlevgl.com/2019-08-05/micropython-pure-display-driver).
|
||||
- `BOARD` - I use WROVER board with SPIRAM. You can choose other boards from `ports/esp32/boards/` directory.
|
||||
- `PYTHON=python2` - depending on your installed `pyparsing` version, you might or might not need this.
|
||||
- `deploy` - make command will create ESP32 port of Micropython, and will try to deploy it through USB-UART bridge.
|
||||
@ -56,8 +57,6 @@ Now you can build the JavaScript port.
|
||||
7. Run an HTTP server that serves files from the current directory.
|
||||
8. Browse to `/lvgl_editor.html` on the HTTP Server.
|
||||
|
||||
|
||||
|
||||
## Super Simple Example
|
||||
|
||||
First, LittlevGL needs to be imported and initialized
|
||||
@ -101,32 +100,28 @@ Here is an alternative example, for registering ILI9341 drivers on Micropython E
|
||||
|
||||
```python
|
||||
import lvgl as lv
|
||||
|
||||
# Import ESP32 and ILI9341 drivers (advnaces tick count and schedules tasks)
|
||||
|
||||
import ILI9341 as ili
|
||||
import lvesp32
|
||||
|
||||
# Initialize the driver and register it to LittlevGL
|
||||
# Import ILI9341 driver and initialized it
|
||||
|
||||
disp = ili.display(miso=5, mosi=18, clk=19, cs=13, dc=12, rst=4, backlight=2)
|
||||
disp.init()
|
||||
from ili9341 import ili9341
|
||||
disp = ili9341()
|
||||
|
||||
# Register display driver
|
||||
# Import XPT2046 driver and initalize it
|
||||
|
||||
disp_buf1 = lv.disp_buf_t()
|
||||
buf1_1 = bytearray(480*10)
|
||||
lv.disp_buf_init(disp_buf1,buf1_1, None, len(buf1_1)//4)
|
||||
disp_drv = lv.disp_drv_t()
|
||||
lv.disp_drv_init(disp_drv)
|
||||
disp_drv.buffer = disp_buf1
|
||||
disp_drv.flush_cb = disp.flush
|
||||
disp_drv.hor_res = 240
|
||||
disp_drv.ver_res = 320
|
||||
lv.disp_drv_register(disp_drv)
|
||||
from xpt2046 import xpt2046
|
||||
touch = xpt2046()
|
||||
```
|
||||
|
||||
Now you can create the GUI itself
|
||||
By default, both ILI9341 and XPT2046 are initialized on the same SPI bus with the following parameters:
|
||||
|
||||
- ILI9341: `miso=5, mosi=18, clk=19, cs=13, dc=12, rst=4, power=14, backlight=15, spihost=esp.HSPI_HOST, mhz=40, factor=4, hybrid=True`
|
||||
- XPT2046: `cs=25, spihost=esp.HSPI_HOST, mhz=5, max_cmds=16, cal_x0 = 3783, cal_y0 = 3948, cal_x1 = 242, cal_y1 = 423, transpose = True, samples = 3`
|
||||
|
||||
You can change any of these parameters on ili9341/xpt2046 constructor.
|
||||
You can also initalize them on different SPI buses if you want, by providing miso/mosi/clk parameters. Set them to -1 to use existing (initialized) spihost bus.
|
||||
|
||||
Now you can create the GUI itself:
|
||||
|
||||
```python
|
||||
|
||||
|
||||
@ -10,3 +10,4 @@ See the `getting started guide
|
||||
:maxdepth: 1
|
||||
|
||||
cmodules.rst
|
||||
qstr.rst
|
||||
|
||||
112
docs/develop/qstr.rst
Normal file
112
docs/develop/qstr.rst
Normal file
@ -0,0 +1,112 @@
|
||||
MicroPython string interning
|
||||
============================
|
||||
|
||||
MicroPython uses `string interning`_ to save both RAM and ROM. This avoids
|
||||
having to store duplicate copies of the same string. Primarily, this applies to
|
||||
identifiers in your code, as something like a function or variable name is very
|
||||
likely to appear in multiple places in the code. In MicroPython an interned
|
||||
string is called a QSTR (uniQue STRing).
|
||||
|
||||
A QSTR value (with type ``qstr``) is a index into a linked list of QSTR pools.
|
||||
QSTRs store their length and a hash of their contents for fast comparison during
|
||||
the de-duplication process. All bytecode operations that work with strings use
|
||||
a QSTR argument.
|
||||
|
||||
Compile-time QSTR generation
|
||||
----------------------------
|
||||
|
||||
In the MicroPython C code, any strings that should be interned in the final
|
||||
firmware are written as ``MP_QSTR_Foo``. At compile time this will evaluate to
|
||||
a ``qstr`` value that points to the index of ``"Foo"`` in the QSTR pool.
|
||||
|
||||
A multi-step process in the ``Makefile`` makes this work. In summary this
|
||||
process has three parts:
|
||||
|
||||
1. Find all ``MP_QSTR_Foo`` tokens in the code.
|
||||
|
||||
2. Generate a static QSTR pool containing all the string data (including lengths
|
||||
and hashes).
|
||||
|
||||
3. Replace all ``MP_QSTR_Foo`` (via the preprocessor) with their corresponding
|
||||
index.
|
||||
|
||||
``MP_QSTR_Foo`` tokens are searched for in two sources:
|
||||
|
||||
1. All files referenced in ``$(SRC_QSTR)``. This is all C code (i.e. ``py``,
|
||||
``extmod``, ``ports/stm32``) but not including third-party code such as
|
||||
``lib``.
|
||||
|
||||
2. Additional ``$(QSTR_GLOBAL_DEPENDENCIES)`` (which includes ``mpconfig*.h``).
|
||||
|
||||
*Note:* ``frozen_mpy.c`` (generated by mpy-tool.py) has its own QSTR generation
|
||||
and pool.
|
||||
|
||||
Some additional strings that can't be expressed using the ``MP_QSTR_Foo`` syntax
|
||||
(e.g. they contain non-alphanumeric characters) are explicitly provided in
|
||||
``qstrdefs.h`` and ``qstrdefsport.h`` via the ``$(QSTR_DEFS)`` variable.
|
||||
|
||||
Processing happens in the following stages:
|
||||
|
||||
1. ``qstr.i.last`` is the concatenation of putting every single input file
|
||||
through the C pre-processor. This means that any conditionally disabled code
|
||||
will be removed, and macros expanded. This means we don't add strings to the
|
||||
pool that won't be used in the final firmware. Because at this stage (thanks
|
||||
to the ``NO_QSTR`` macro added by ``QSTR_GEN_EXTRA_CFLAGS``) there is no
|
||||
definition for ``MP_QSTR_Foo`` it passes through this stage unaffected. This
|
||||
file also includes comments from the preprocessor that include line number
|
||||
information. Note that this step only uses files that have changed, which
|
||||
means that ``qstr.i.last`` will only contain data from files that have
|
||||
changed since the last compile.
|
||||
2. ``qstr.split`` is an empty file created after running ``makeqstrdefs.py split``
|
||||
on qstr.i.last. It's just used as a dependency to indicate that the step ran.
|
||||
This script outputs one file per input C file, ``genhdr/qstr/...file.c.qstr``,
|
||||
which contains only the matched QSTRs. Each QSTR is printed as ``Q(Foo)``.
|
||||
This step is necessary to combine the existing files with the new data
|
||||
generated from the incremental update in ``qstr.i.last``.
|
||||
|
||||
3. ``qstrdefs.collected.h`` is the output of concatenating ``genhdr/qstr/*``
|
||||
using ``makeqstrdefs.py cat``. This is now the full set of ``MP_QSTR_Foo``'s
|
||||
found in the code, now formatted as ``Q(Foo)``, one-per-line, with duplicates.
|
||||
This file is only updated if the set of qstrs has changed. A hash of the QSTR
|
||||
data is written to another file (``qstrdefs.collected.h.hash``) which allows
|
||||
it to track changes across builds.
|
||||
|
||||
4. ``qstrdefs.preprocessed.h`` adds in the QSTRs from qstrdefs*. It
|
||||
concatenates ``qstrdefs.collected.h`` with ``qstrdefs*.h``, then it transforms
|
||||
each line from ``Q(Foo)`` to ``"Q(Foo)"`` so they pass through the preprocessor
|
||||
unchanged. Then the preprocessor is used to deal with any conditional
|
||||
compilation in ``qstrdefs*.h``. Then the transformation is undone back to
|
||||
``Q(Foo)``, and saved as ``qstrdefs.preprocessed.h``.
|
||||
|
||||
5. ``qstrdefs.generated.h`` is the output of ``makeqstrdata.py``. For each
|
||||
``Q(Foo)`` in qstrdefs.preprocessed.h (plus some extra hard-coded ones), it outputs
|
||||
``QDEF(MP_QSTR_Foo, (const byte*)"hash" "Foo")``.
|
||||
|
||||
Then in the main compile, two things happen with ``qstrdefs.generated.h``:
|
||||
|
||||
1. In qstr.h, each QDEF becomes an entry in an enum, which makes ``MP_QSTR_Foo``
|
||||
available to code and equal to the index of that string in the QSTR table.
|
||||
|
||||
2. In qstr.c, the actual QSTR data table is generated as elements of the
|
||||
``mp_qstr_const_pool->qstrs``.
|
||||
|
||||
.. _`string interning`: https://en.wikipedia.org/wiki/String_interning
|
||||
|
||||
Run-time QSTR generation
|
||||
------------------------
|
||||
|
||||
Additional QSTR pools can be created at runtime so that strings can be added to
|
||||
them. For example, the code::
|
||||
|
||||
foo[x] = 3
|
||||
|
||||
Will need to create a QSTR for the value of ``x`` so it can be used by the
|
||||
"load attr" bytecode.
|
||||
|
||||
Also, when compiling Python code, identifiers and literals need to have QSTRs
|
||||
created. Note: only literals shorter than 10 characters become QSTRs. This is
|
||||
because a regular string on the heap always takes up a minimum of 16 bytes (one
|
||||
GC block), whereas QSTRs allow them to be packed more efficiently into the pool.
|
||||
|
||||
QSTR pools (and the underlying "chunks" that store the string data) are allocated
|
||||
on-demand on the heap with a minimum size.
|
||||
@ -77,7 +77,7 @@ The :mod:`network` module::
|
||||
wlan.scan() # scan for access points
|
||||
wlan.isconnected() # check if the station is connected to an AP
|
||||
wlan.connect('essid', 'password') # connect to an AP
|
||||
wlan.config('mac') # get the interface's MAC adddress
|
||||
wlan.config('mac') # get the interface's MAC address
|
||||
wlan.ifconfig() # get the interface's IP/netmask/gw/DNS addresses
|
||||
|
||||
ap = network.WLAN(network.AP_IF) # create access-point interface
|
||||
@ -203,7 +203,7 @@ Use the :ref:`machine.ADC <machine.ADC>` class::
|
||||
adc = ADC(Pin(32)) # create ADC object on ADC pin
|
||||
adc.read() # read value, 0-4095 across voltage range 0.0v - 1.0v
|
||||
|
||||
adc.atten(ADC.ATTN_11DB) # set 11dB input attentuation (voltage range roughly 0.0v - 3.6v)
|
||||
adc.atten(ADC.ATTN_11DB) # set 11dB input attenuation (voltage range roughly 0.0v - 3.6v)
|
||||
adc.width(ADC.WIDTH_9BIT) # set 9 bit return values (returned range 0-511)
|
||||
adc.read() # read value using the newly configured attenuation and width
|
||||
|
||||
@ -257,7 +257,7 @@ class::
|
||||
spi.init(baudrate=200000) # set the baudrate
|
||||
|
||||
spi.read(10) # read 10 bytes on MISO
|
||||
spi.read(10, 0xff) # read 10 bytes while outputing 0xff on MOSI
|
||||
spi.read(10, 0xff) # read 10 bytes while outputting 0xff on MOSI
|
||||
|
||||
buf = bytearray(50) # create a buffer
|
||||
spi.readinto(buf) # read into the given buffer (reads 50 bytes in this case)
|
||||
|
||||
@ -140,7 +140,7 @@ The above may also happen after an application terminates and quits to the REPL
|
||||
for any reason including an exception. Subsequent arrival of data provokes the
|
||||
failure with the above error message repeatedly issued. So, sockets should be
|
||||
closed in any case, regardless whether an application terminates successfully
|
||||
or by an exeption, for example using try/finally::
|
||||
or by an exception, for example using try/finally::
|
||||
|
||||
sock = socket(...)
|
||||
try:
|
||||
|
||||
@ -188,7 +188,7 @@ class::
|
||||
spi.init(baudrate=200000) # set the baudrate
|
||||
|
||||
spi.read(10) # read 10 bytes on MISO
|
||||
spi.read(10, 0xff) # read 10 bytes while outputing 0xff on MOSI
|
||||
spi.read(10, 0xff) # read 10 bytes while outputting 0xff on MOSI
|
||||
|
||||
buf = bytearray(50) # create a buffer
|
||||
spi.readinto(buf) # read into the given buffer (reads 50 bytes in this case)
|
||||
@ -243,6 +243,12 @@ See :ref:`machine.RTC <machine.RTC>` ::
|
||||
rtc.datetime((2017, 8, 23, 1, 12, 48, 0, 0)) # set a specific date and time
|
||||
rtc.datetime() # get date and time
|
||||
|
||||
# synchronize with ntp
|
||||
# need to be connected to wifi
|
||||
import ntptime
|
||||
ntptime.settime() # set the rtc datetime from the remote server
|
||||
rtc.datetime() # get the date and time in UTC
|
||||
|
||||
Deep-sleep mode
|
||||
---------------
|
||||
|
||||
|
||||
@ -61,6 +61,7 @@ of the request you need to specify the page to retrieve.
|
||||
Let's define a function that can download and print a URL::
|
||||
|
||||
def http_get(url):
|
||||
import socket
|
||||
_, _, host, path = url.split('/', 3)
|
||||
addr = socket.getaddrinfo(host, 80)[0][-1]
|
||||
s = socket.socket()
|
||||
@ -74,8 +75,7 @@ Let's define a function that can download and print a URL::
|
||||
break
|
||||
s.close()
|
||||
|
||||
Make sure that you import the socket module before running this function. Then
|
||||
you can try::
|
||||
Then you can try::
|
||||
|
||||
>>> http_get('http://micropython.org/ks/test.html')
|
||||
|
||||
|
||||
@ -74,11 +74,11 @@ it will fallback to loading the built-in ``ujson`` module.
|
||||
:maxdepth: 1
|
||||
|
||||
builtins.rst
|
||||
array.rst
|
||||
cmath.rst
|
||||
gc.rst
|
||||
math.rst
|
||||
sys.rst
|
||||
uarray.rst
|
||||
ubinascii.rst
|
||||
ucollections.rst
|
||||
uerrno.rst
|
||||
@ -111,10 +111,24 @@ the following libraries.
|
||||
machine.rst
|
||||
micropython.rst
|
||||
network.rst
|
||||
ubluetooth.rst
|
||||
ucryptolib.rst
|
||||
uctypes.rst
|
||||
|
||||
|
||||
Port-specific libraries
|
||||
-----------------------
|
||||
|
||||
In some cases the following port/board-specific libraries have functions or
|
||||
classes similar to those in the :mod:`machine` library. Where this occurs, the
|
||||
entry in the port specific library exposes hardware functionality unique to
|
||||
that platform.
|
||||
|
||||
To write portable code use functions and classes from the :mod:`machine` module.
|
||||
To access platform-specific hardware use the appropriate library, e.g.
|
||||
:mod:`pyb` in the case of the Pyboard.
|
||||
|
||||
|
||||
Libraries specific to the pyboard
|
||||
---------------------------------
|
||||
|
||||
|
||||
@ -68,7 +68,7 @@ parameter should be `id`.
|
||||
(password) required to access said service. There can be further
|
||||
arbitrary keyword-only parameters, depending on the networking medium
|
||||
type and/or particular device. Parameters can be used to: a)
|
||||
specify alternative service identifer types; b) provide additional
|
||||
specify alternative service identifier types; b) provide additional
|
||||
connection parameters. For various medium types, there are different
|
||||
sets of predefined/recommended parameters, among them:
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
:mod:`array` -- arrays of numeric data
|
||||
======================================
|
||||
:mod:`uarray` -- arrays of numeric data
|
||||
=======================================
|
||||
|
||||
.. module:: array
|
||||
.. module:: uarray
|
||||
:synopsis: efficient arrays of numeric data
|
||||
|
||||
|see_cpython_module| :mod:`python:array`.
|
||||
@ -13,7 +13,7 @@ floating-point support).
|
||||
Classes
|
||||
-------
|
||||
|
||||
.. class:: array.array(typecode, [iterable])
|
||||
.. class:: array(typecode, [iterable])
|
||||
|
||||
Create array with elements of given type. Initial contents of the
|
||||
array are given by *iterable*. If it is not provided, an empty
|
||||
337
docs/library/ubluetooth.rst
Normal file
337
docs/library/ubluetooth.rst
Normal file
@ -0,0 +1,337 @@
|
||||
:mod:`ubluetooth` --- low-level Bluetooth
|
||||
=========================================
|
||||
|
||||
.. module:: ubluetooth
|
||||
:synopsis: Low-level Bluetooth radio functionality
|
||||
|
||||
This module provides an interface to a Bluetooth controller on a board.
|
||||
Currently this supports Bluetooth Low Energy (BLE) in Central, Peripheral,
|
||||
Broadcaster, and Observer roles, and a device may operate in multiple
|
||||
roles concurrently.
|
||||
|
||||
This API is intended to match the low-level Bluetooth protocol and provide
|
||||
building-blocks for higher-level abstractions such as specific device types.
|
||||
|
||||
class BLE
|
||||
---------
|
||||
|
||||
Constructor
|
||||
-----------
|
||||
|
||||
.. class:: BLE()
|
||||
|
||||
Returns the singleton BLE object.
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
.. method:: BLE.active([active])
|
||||
|
||||
Optionally changes the active state of the BLE radio, and returns the
|
||||
current state.
|
||||
|
||||
The radio must be made active before using any other methods on this class.
|
||||
|
||||
.. method:: BLE.config(name)
|
||||
|
||||
Queries a configuration value by *name*. Currently supported values are:
|
||||
|
||||
- ``'mac'``: Returns the device MAC address. If a device has a fixed address
|
||||
(e.g. PYBD) then it will be returned. Otherwise (e.g. ESP32) a random
|
||||
address will be generated when the BLE interface is made active.
|
||||
|
||||
Event Handling
|
||||
--------------
|
||||
|
||||
.. method:: BLE.irq(handler, trigger=0xffff)
|
||||
|
||||
Registers a callback for events from the BLE stack. The *handler* takes two
|
||||
arguments, ``event`` (which will be one of the codes below) and ``data``
|
||||
(which is an event-specific tuple of values).
|
||||
|
||||
The optional *trigger* parameter allows you to set a mask of events that
|
||||
your program is interested in. The default is all events.
|
||||
|
||||
An event handler showing all possible events::
|
||||
|
||||
def bt_irq(event, data):
|
||||
if event == _IRQ_CENTRAL_CONNECT:
|
||||
# A central has connected to this peripheral.
|
||||
conn_handle, addr_type, addr = data
|
||||
elif event == _IRQ_CENTRAL_DISCONNECT:
|
||||
# A central has disconnected from this peripheral.
|
||||
conn_handle, addr_type, addr = data
|
||||
elif event == _IRQ_GATTS_WRITE:
|
||||
# A central has written to this characteristic or descriptor.
|
||||
conn_handle, attr_handle = data
|
||||
elif event == _IRQ_GATTS_READ_REQUEST:
|
||||
# A central has issued a read. Note: this is a hard IRQ.
|
||||
# Return None to deny the read.
|
||||
# Note: This event is not supported on ESP32.
|
||||
conn_handle, attr_handle = data
|
||||
elif event == _IRQ_SCAN_RESULT:
|
||||
# A single scan result.
|
||||
addr_type, addr, connectable, rssi, adv_data = data
|
||||
elif event == _IRQ_SCAN_COMPLETE:
|
||||
# Scan duration finished or manually stopped.
|
||||
pass
|
||||
elif event == _IRQ_PERIPHERAL_CONNECT:
|
||||
# A successful gap_connect().
|
||||
conn_handle, addr_type, addr = data
|
||||
elif event == _IRQ_PERIPHERAL_DISCONNECT:
|
||||
# Connected peripheral has disconnected.
|
||||
conn_handle, addr_type, addr = data
|
||||
elif event == _IRQ_GATTC_SERVICE_RESULT:
|
||||
# Called for each service found by gattc_discover_services().
|
||||
conn_handle, start_handle, end_handle, uuid = data
|
||||
elif event == _IRQ_GATTC_CHARACTERISTIC_RESULT:
|
||||
# Called for each characteristic found by gattc_discover_services().
|
||||
conn_handle, def_handle, value_handle, properties, uuid = data
|
||||
elif event == _IRQ_GATTC_DESCRIPTOR_RESULT:
|
||||
# Called for each descriptor found by gattc_discover_descriptors().
|
||||
conn_handle, dsc_handle, uuid = data
|
||||
elif event == _IRQ_GATTC_READ_RESULT:
|
||||
# A gattc_read() has completed.
|
||||
conn_handle, value_handle, char_data = data
|
||||
elif event == _IRQ_GATTC_WRITE_STATUS:
|
||||
# A gattc_write() has completed.
|
||||
conn_handle, value_handle, status = data
|
||||
elif event == _IRQ_GATTC_NOTIFY:
|
||||
# A peripheral has sent a notify request.
|
||||
conn_handle, value_handle, notify_data = data
|
||||
elif event == _IRQ_GATTC_INDICATE:
|
||||
# A peripheral has sent an indicate request.
|
||||
conn_handle, value_handle, notify_data = data
|
||||
|
||||
The event codes are::
|
||||
|
||||
from micropython import const
|
||||
_IRQ_CENTRAL_CONNECT = const(1 << 0)
|
||||
_IRQ_CENTRAL_DISCONNECT = const(1 << 1)
|
||||
_IRQ_GATTS_WRITE = const(1 << 2)
|
||||
_IRQ_GATTS_READ_REQUEST = const(1 << 3)
|
||||
_IRQ_SCAN_RESULT = const(1 << 4)
|
||||
_IRQ_SCAN_COMPLETE = const(1 << 5)
|
||||
_IRQ_PERIPHERAL_CONNECT = const(1 << 6)
|
||||
_IRQ_PERIPHERAL_DISCONNECT = const(1 << 7)
|
||||
_IRQ_GATTC_SERVICE_RESULT = const(1 << 8)
|
||||
_IRQ_GATTC_CHARACTERISTIC_RESULT = const(1 << 9)
|
||||
_IRQ_GATTC_DESCRIPTOR_RESULT = const(1 << 10)
|
||||
_IRQ_GATTC_READ_RESULT = const(1 << 11)
|
||||
_IRQ_GATTC_WRITE_STATUS = const(1 << 12)
|
||||
_IRQ_GATTC_NOTIFY = const(1 << 13)
|
||||
_IRQ_GATTC_INDICATE = const(1 << 14)
|
||||
|
||||
In order to save space in the firmware, these constants are not included on the
|
||||
:mod:`ubluetooth` module. Add the ones that you need from the list above to your
|
||||
program.
|
||||
|
||||
|
||||
Broadcaster Role (Advertiser)
|
||||
-----------------------------
|
||||
|
||||
.. method:: BLE.gap_advertise(interval_us, adv_data=None, resp_data=None, connectable=True)
|
||||
|
||||
Starts advertising at the specified interval (in **micro**\ seconds). This
|
||||
interval will be rounded down to the nearest 625us. To stop advertising, set
|
||||
*interval_us* to ``None``.
|
||||
|
||||
*adv_data* and *resp_data* can be any type that implements the buffer
|
||||
protocol (e.g. ``bytes``, ``bytearray``, ``str``). *adv_data* is included
|
||||
in all broadcasts, and *resp_data* is send in reply to an active scan.
|
||||
|
||||
Note: if *adv_data* (or *resp_data*) is ``None``, then the data passed
|
||||
to the previous call to ``gap_advertise`` will be re-used. This allows a
|
||||
broadcaster to resume advertising with just ``gap_advertise(interval_us)``.
|
||||
To clear the advertising payload pass an empty ``bytes``, i.e. ``b''``.
|
||||
|
||||
|
||||
Observer Role (Scanner)
|
||||
-----------------------
|
||||
|
||||
.. method:: BLE.gap_scan(duration_ms, [interval_us], [window_us])
|
||||
|
||||
Run a scan operation lasting for the specified duration (in **milli**\ seconds).
|
||||
|
||||
To scan indefinitely, set *duration_ms* to ``0``.
|
||||
|
||||
To stop scanning, set *duration_ms* to ``None``.
|
||||
|
||||
Use *interval_us* and *window_us* to optionally configure the duty cycle.
|
||||
The scanner will run for *window_us* **micro**\ seconds every *interval_us*
|
||||
**micro**\ seconds for a total of *duration_ms* **milli**\ seconds. The default
|
||||
interval and window are 1.28 seconds and 11.25 milliseconds respectively
|
||||
(background scanning).
|
||||
|
||||
For each scan result, the ``_IRQ_SCAN_RESULT`` event will be raised.
|
||||
|
||||
When scanning is stopped (either due to the duration finishing or when
|
||||
explicitly stopped), the ``_IRQ_SCAN_COMPLETE`` event will be raised.
|
||||
|
||||
|
||||
Peripheral Role (GATT Server)
|
||||
-----------------------------
|
||||
|
||||
A BLE peripheral has a set of registered services. Each service may contain
|
||||
characteristics, which each have a value. Characteristics can also contain
|
||||
descriptors, which themselves have values.
|
||||
|
||||
These values are stored locally, and are accessed by their "value handle" which
|
||||
is generated during service registration. They can also be read from or written
|
||||
to by a remote central device. Additionally, a peripheral can "notify" a
|
||||
characteristic to a connected central via a connection handle.
|
||||
|
||||
Characteristics and descriptors have a default maximum size of 20 bytes.
|
||||
Anything written to them by a central will be truncated to this length. However,
|
||||
any local write will increase the maximum size, so if you want to allow larger
|
||||
writes from a central to a given characteristic, use
|
||||
:meth:`gatts_write<BLE.gatts_write>` after registration. e.g.
|
||||
``gatts_write(char_handle, bytes(100))``.
|
||||
|
||||
.. method:: BLE.gatts_register_services(services_definition)
|
||||
|
||||
Configures the peripheral with the specified services, replacing any
|
||||
existing services.
|
||||
|
||||
*services_definition* is a list of **services**, where each **service** is a
|
||||
two-element tuple containing a UUID and a list of **characteristics**.
|
||||
|
||||
Each **characteristic** is a two-or-three-element tuple containing a UUID, a
|
||||
**flags** value, and optionally a list of *descriptors*.
|
||||
|
||||
Each **descriptor** is a two-element tuple containing a UUID and a **flags**
|
||||
value.
|
||||
|
||||
The **flags** are a bitwise-OR combination of the
|
||||
:data:`ubluetooth.FLAGS_READ`, :data:`bluetooth.FLAGS_WRITE` and
|
||||
:data:`ubluetooth.FLAGS_NOTIFY` values defined below.
|
||||
|
||||
The return value is a list (one element per service) of tuples (each element
|
||||
is a value handle). Characteristics and descriptor handles are flattened
|
||||
into the same tuple, in the order that they are defined.
|
||||
|
||||
The following example registers two services (Heart Rate, and Nordic UART)::
|
||||
|
||||
HR_UUID = bluetooth.UUID(0x180D)
|
||||
HR_CHAR = (bluetooth.UUID(0x2A37), bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY,)
|
||||
HR_SERVICE = (HR_UUID, (HR_CHAR,),)
|
||||
UART_UUID = bluetooth.UUID('6E400001-B5A3-F393-E0A9-E50E24DCCA9E')
|
||||
UART_TX = (bluetooth.UUID('6E400003-B5A3-F393-E0A9-E50E24DCCA9E'), bluetooth.FLAG_READ | bluetooth.FLAG_NOTIFY,)
|
||||
UART_RX = (bluetooth.UUID('6E400002-B5A3-F393-E0A9-E50E24DCCA9E'), bluetooth.FLAG_WRITE,)
|
||||
UART_SERVICE = (UART_UUID, (UART_TX, UART_RX,),)
|
||||
SERVICES = (HR_SERVICE, UART_SERVICE,)
|
||||
( (hr,), (tx, rx,), ) = bt.gatts_register_services(SERVICES)
|
||||
|
||||
The three value handles (``hr``, ``tx``, ``rx``) can be used with
|
||||
:meth:`gatts_read <BLE.gatts_read>`, :meth:`gatts_write <BLE.gatts_write>`,
|
||||
and :meth:`gatts_notify <BLE.gatts_notify>`.
|
||||
|
||||
**Note:** Advertising must be stopped before registering services.
|
||||
|
||||
.. method:: BLE.gatts_read(value_handle)
|
||||
|
||||
Reads the local value for this handle (which has either been written by
|
||||
:meth:`gatts_write <BLE.gatts_write>` or by a remote central).
|
||||
|
||||
.. method:: BLE.gatts_write(value_handle, data)
|
||||
|
||||
Writes the local value for this handle, which can be read by a central.
|
||||
|
||||
.. method:: BLE.gatts_notify(conn_handle, value_handle, [data])
|
||||
|
||||
Notifies a connected central that this value has changed and that it should
|
||||
issue a read of the current value from this peripheral.
|
||||
|
||||
If *data* is specified, then the that value is sent to the central as part
|
||||
of the notification, avoiding the need for a separate read request. Note
|
||||
that this will not update the local value stored.
|
||||
|
||||
.. method:: BLE.gatts_set_buffer(value_handle, len, append=False)
|
||||
|
||||
Sets the internal buffer size for a value in bytes. This will limit the
|
||||
largest possible write that can be received. The default is 20.
|
||||
|
||||
Setting *append* to ``True`` will make all remote writes append to, rather
|
||||
than replace, the current value. At most *len* bytes can be buffered in
|
||||
this way. When you use :meth:`gatts_read <BLE.gatts_read>`, the value will
|
||||
be cleared after reading. This feature is useful when implementing something
|
||||
like the Nordic UART Service.
|
||||
|
||||
|
||||
Central Role (GATT Client)
|
||||
--------------------------
|
||||
|
||||
.. method:: BLE.gap_connect(addr_type, addr, scan_duration_ms=2000)
|
||||
|
||||
Connect to a peripheral.
|
||||
|
||||
On success, the ``_IRQ_PERIPHERAL_CONNECT`` event will be raised.
|
||||
|
||||
.. method:: BLE.gap_disconnect(conn_handle)
|
||||
|
||||
Disconnect the specified connection handle.
|
||||
|
||||
On success, the ``_IRQ_PERIPHERAL_DISCONNECT`` event will be raised.
|
||||
|
||||
Returns ``False`` if the connection handle wasn't connected, and ``True``
|
||||
otherwise.
|
||||
|
||||
.. method:: BLE.gattc_discover_services(conn_handle)
|
||||
|
||||
Query a connected peripheral for its services.
|
||||
|
||||
For each service discovered, the ``_IRQ_GATTC_SERVICE_RESULT`` event will be
|
||||
raised.
|
||||
|
||||
.. method:: BLE.gattc_discover_characteristics(conn_handle, start_handle, end_handle)
|
||||
|
||||
Query a connected peripheral for characteristics in the specified range.
|
||||
|
||||
For each characteristic discovered, the ``_IRQ_GATTC_CHARACTERISTIC_RESULT``
|
||||
event will be raised.
|
||||
|
||||
.. method:: BLE.gattc_discover_descriptors(conn_handle, start_handle, end_handle)
|
||||
|
||||
Query a connected peripheral for descriptors in the specified range.
|
||||
|
||||
For each descriptor discovered, the ``_IRQ_GATTC_DESCRIPTOR_RESULT`` event
|
||||
will be raised.
|
||||
|
||||
.. method:: BLE.gattc_read(conn_handle, value_handle)
|
||||
|
||||
Issue a remote read to a connected peripheral for the specified
|
||||
characteristic or descriptor handle.
|
||||
|
||||
On success, the ``_IRQ_GATTC_READ_RESULT`` event will be raised.
|
||||
|
||||
.. method:: BLE.gattc_write(conn_handle, value_handle, data)
|
||||
|
||||
Issue a remote write to a connected peripheral for the specified
|
||||
characteristic or descriptor handle.
|
||||
|
||||
On success, the ``_IRQ_GATTC_WRITE_STATUS`` event will be raised.
|
||||
|
||||
|
||||
class UUID
|
||||
----------
|
||||
|
||||
|
||||
Constructor
|
||||
-----------
|
||||
|
||||
.. class:: UUID(value)
|
||||
|
||||
Creates a UUID instance with the specified **value**.
|
||||
|
||||
The **value** can be either:
|
||||
|
||||
- A 16-bit integer. e.g. ``0x2908``.
|
||||
- A 128-bit UUID string. e.g. ``'6E400001-B5A3-F393-E0A9-E50E24DCCA9E'``.
|
||||
|
||||
|
||||
Constants
|
||||
---------
|
||||
|
||||
.. data:: ubluetooth.FLAG_READ
|
||||
ubluetooth.FLAG_WRITE
|
||||
ubluetooth.FLAG_NOTIFY
|
||||
@ -18,10 +18,10 @@ be implemented:
|
||||
* SHA1 - A previous generation algorithm. Not recommended for new usages,
|
||||
but SHA1 is a part of number of Internet standards and existing
|
||||
applications, so boards targeting network connectivity and
|
||||
interoperatiability will try to provide this.
|
||||
interoperability will try to provide this.
|
||||
|
||||
* MD5 - A legacy algorithm, not considered cryptographically secure. Only
|
||||
selected boards, targeting interoperatibility with legacy applications,
|
||||
selected boards, targeting interoperability with legacy applications,
|
||||
will offer this.
|
||||
|
||||
Constructors
|
||||
|
||||
@ -94,10 +94,10 @@ Filesystem access
|
||||
* ``f_frsize`` -- fragment size
|
||||
* ``f_blocks`` -- size of fs in f_frsize units
|
||||
* ``f_bfree`` -- number of free blocks
|
||||
* ``f_bavail`` -- number of free blocks for unpriviliged users
|
||||
* ``f_bavail`` -- number of free blocks for unprivileged users
|
||||
* ``f_files`` -- number of inodes
|
||||
* ``f_ffree`` -- number of free inodes
|
||||
* ``f_favail`` -- number of free inodes for unpriviliged users
|
||||
* ``f_favail`` -- number of free inodes for unprivileged users
|
||||
* ``f_flag`` -- mount flags
|
||||
* ``f_namemax`` -- maximum filename length
|
||||
|
||||
@ -187,25 +187,51 @@ implementation of this class will usually allow access to the memory-like
|
||||
functionality a piece of hardware (like flash memory). A block device can be
|
||||
used by a particular filesystem driver to store the data for its filesystem.
|
||||
|
||||
There are two compatible signatures for the ``readblocks`` and ``writeblocks``
|
||||
methods (see below), in order to support a variety of use cases. A given block
|
||||
device may implement one form or the other, or both at the same time.
|
||||
|
||||
.. class:: AbstractBlockDev(...)
|
||||
|
||||
Construct a block device object. The parameters to the constructor are
|
||||
dependent on the specific block device.
|
||||
|
||||
.. method:: readblocks(block_num, buf)
|
||||
.. method:: readblocks(block_num, buf, offset)
|
||||
|
||||
The first form reads aligned, multiples of blocks.
|
||||
Starting at the block given by the index *block_num*, read blocks from
|
||||
the device into *buf* (an array of bytes).
|
||||
The number of blocks to read is given by the length of *buf*,
|
||||
which will be a multiple of the block size.
|
||||
|
||||
.. method:: writeblocks(block_num, buf)
|
||||
The second form allows reading at arbitrary locations within a block,
|
||||
and arbitrary lengths.
|
||||
Starting at block index *block_num*, and byte offset within that block
|
||||
of *offset*, read bytes from the device into *buf* (an array of bytes).
|
||||
The number of bytes to read is given by the length of *buf*.
|
||||
|
||||
.. method:: writeblocks(block_num, buf)
|
||||
.. method:: writeblocks(block_num, buf, offset)
|
||||
|
||||
The first form writes aligned, multiples of blocks, and requires that the
|
||||
blocks that are written to be first erased (if necessary) by this method.
|
||||
Starting at the block given by the index *block_num*, write blocks from
|
||||
*buf* (an array of bytes) to the device.
|
||||
The number of blocks to write is given by the length of *buf*,
|
||||
which will be a multiple of the block size.
|
||||
|
||||
The second form allows writing at arbitrary locations within a block,
|
||||
and arbitrary lengths. Only the bytes being written should be changed,
|
||||
and the caller of this method must ensure that the relevant blocks are
|
||||
erased via a prior ``ioctl`` call.
|
||||
Starting at block index *block_num*, and byte offset within that block
|
||||
of *offset*, write bytes from *buf* (an array of bytes) to the device.
|
||||
The number of bytes to write is given by the length of *buf*.
|
||||
|
||||
Note that implementations must never implicitly erase blocks if the offset
|
||||
argument is specified, even if it is zero.
|
||||
|
||||
.. method:: ioctl(op, arg)
|
||||
|
||||
Control the block device and query its parameters. The operation to
|
||||
@ -219,6 +245,7 @@ used by a particular filesystem driver to store the data for its filesystem.
|
||||
- 5 -- get the number of bytes in a block, should return an integer,
|
||||
or ``None`` in which case the default value of 512 is used
|
||||
(*arg* is unused)
|
||||
- 6 -- erase a block, *arg* is the block number to erase
|
||||
|
||||
By way of example, the following class will implement a block device that stores
|
||||
its data in RAM using a ``bytearray``::
|
||||
@ -250,3 +277,34 @@ It can be used as follows::
|
||||
uos.VfsFat.mkfs(bdev)
|
||||
vfs = uos.VfsFat(bdev)
|
||||
uos.mount(vfs, '/ramdisk')
|
||||
|
||||
An example of a block device that supports both signatures and behaviours of
|
||||
the :meth:`readblocks` and :meth:`writeblocks` methods is::
|
||||
|
||||
class RAMBlockDev:
|
||||
def __init__(self, block_size, num_blocks):
|
||||
self.block_size = block_size
|
||||
self.data = bytearray(block_size * num_blocks)
|
||||
|
||||
def readblocks(self, block, buf, offset=0):
|
||||
addr = block_num * self.block_size + offset
|
||||
for i in range(len(buf)):
|
||||
buf[i] = self.data[addr + i]
|
||||
|
||||
def writeblocks(self, block_num, buf, offset=None):
|
||||
if offset is None:
|
||||
# do erase, then write
|
||||
for i in range(len(buf) // self.block_size):
|
||||
self.ioctl(6, block_num + i)
|
||||
offset = 0
|
||||
addr = block_num * self.block_size + offset
|
||||
for i in range(len(buf)):
|
||||
self.data[addr + i] = buf[i]
|
||||
|
||||
def ioctl(self, op, arg):
|
||||
if op == 4: # block count
|
||||
return len(self.data) // self.block_size
|
||||
if op == 5: # block size
|
||||
return self.block_size
|
||||
if op == 6: # block erase
|
||||
return 0
|
||||
|
||||
@ -20,7 +20,7 @@ When you save, the red light on the pyboard should turn on for about a second. T
|
||||
|
||||
So what does this code do? First we need some terminology. Python is an object-oriented language, almost everything in python is a *class* and when you create an instance of a class you get an *object*. Classes have *methods* associated to them. A method (also called a member function) is used to interact with or control the object.
|
||||
|
||||
The first line of code creates an LED object which we have then called led. When we create the object, it takes a single parameter which must be between 1 and 4, corresponding to the 4 LEDs on the board. The pyb.LED class has three important member functions that we will use: on(), off() and toggle(). The other function that we use is pyb.delay() this simply waits for a given time in miliseconds. Once we have created the LED object, the statement while True: creates an infinite loop which toggles the led between on and off and waits for 1 second.
|
||||
The first line of code creates an LED object which we have then called led. When we create the object, it takes a single parameter which must be between 1 and 4, corresponding to the 4 LEDs on the board. The pyb.LED class has three important member functions that we will use: on(), off() and toggle(). The other function that we use is pyb.delay() this simply waits for a given time in milliseconds. Once we have created the LED object, the statement while True: creates an infinite loop which toggles the led between on and off and waits for 1 second.
|
||||
|
||||
**Exercise: Try changing the time between toggling the led and turning on a different LED.**
|
||||
|
||||
|
||||
@ -4,152 +4,197 @@ Glossary
|
||||
.. glossary::
|
||||
|
||||
baremetal
|
||||
A system without a (full-fledged) OS, for example an
|
||||
A system without a (full-fledged) operating system, for example an
|
||||
:term:`MCU`-based system. When running on a baremetal system,
|
||||
MicroPython effectively becomes its user-facing OS with a command
|
||||
interpreter (REPL).
|
||||
MicroPython effectively functions like a small operating system,
|
||||
running user programs and providing a command interpreter
|
||||
(:term:`REPL`).
|
||||
|
||||
buffer protocol
|
||||
Any Python object that can be automatically converted into bytes, such
|
||||
as ``bytes``, ``bytearray``, ``memoryview`` and ``str`` objects, which
|
||||
all implement the "buffer protocol".
|
||||
|
||||
board
|
||||
A PCB board. Oftentimes, the term is used to denote a particular
|
||||
model of an :term:`MCU` system. Sometimes, it is used to actually
|
||||
refer to :term:`MicroPython port` to a particular board (and then
|
||||
may also refer to "boardless" ports like
|
||||
:term:`Unix port <MicroPython Unix port>`).
|
||||
Typically this refers to a printed circuit board (PCB) containing a
|
||||
:term:`microcontroller <MCU>` and supporting components.
|
||||
MicroPython firmware is typically provided per-board, as the firmware
|
||||
contains both MCU-specific functionality but also board-level
|
||||
functionality such as drivers or pin names.
|
||||
|
||||
bytecode
|
||||
A compact representation of a Python program that generated by
|
||||
compiling the Python source code. This is what the VM actually
|
||||
executes. Bytecode is typically generated automatically at runtime and
|
||||
is invisible to the user. Note that while :term:`CPython` and
|
||||
MicroPython both use bytecode, the format is different. You can also
|
||||
pre-compile source code offline using the :term:`cross-compiler`.
|
||||
|
||||
callee-owned tuple
|
||||
A tuple returned by some builtin function/method, containing data
|
||||
which is valid for a limited time, usually until next call to the
|
||||
same function (or a group of related functions). After next call,
|
||||
data in the tuple may be changed. This leads to the following
|
||||
restriction on the usage of callee-owned tuples - references to
|
||||
them cannot be stored. The only valid operation is extracting
|
||||
values from them (including making a copy). Callee-owned tuples
|
||||
is a MicroPython-specific construct (not available in the general
|
||||
Python language), introduced for memory allocation optimization.
|
||||
The idea is that callee-owned tuple is allocated once and stored
|
||||
on the callee side. Subsequent calls don't require allocation,
|
||||
allowing to return multiple values when allocation is not possible
|
||||
(e.g. in interrupt context) or not desirable (because allocation
|
||||
inherently leads to memory fragmentation). Note that callee-owned
|
||||
tuples are effectively mutable tuples, making an exception to
|
||||
Python's rule that tuples are immutable. (It may be interesting
|
||||
why tuples were used for such a purpose then, instead of mutable
|
||||
lists - the reason for that is that lists are mutable from user
|
||||
application side too, so a user could do things to a callee-owned
|
||||
list which the callee doesn't expect and could lead to problems;
|
||||
a tuple is protected from this.)
|
||||
This is a MicroPython-specific construct where, for efficiency
|
||||
reasons, some built-in functions or methods may re-use the same
|
||||
underlying tuple object to return data. This avoids having to allocate
|
||||
a new tuple for every call, and reduces heap fragmentation. Programs
|
||||
should not hold references to callee-owned tuples and instead only
|
||||
extract data from them (or make a copy).
|
||||
|
||||
CircuitPython
|
||||
A variant of MicroPython developed by `Adafruit Industries
|
||||
<https://circuitpython.org>`_.
|
||||
|
||||
CPython
|
||||
CPython is the reference implementation of Python programming
|
||||
language, and the most well-known one, which most of the people
|
||||
run. It is however one of many implementations (among which
|
||||
Jython, IronPython, PyPy, and many more, including MicroPython).
|
||||
As there is no formal specification of the Python language, only
|
||||
CPython documentation, it is not always easy to draw a line
|
||||
between Python the language and CPython its particular
|
||||
implementation. This however leaves more freedom for other
|
||||
implementations. For example, MicroPython does a lot of things
|
||||
differently than CPython, while still aspiring to be a Python
|
||||
language implementation.
|
||||
CPython is the reference implementation of the Python programming
|
||||
language, and the most well-known one. It is, however, one of many
|
||||
implementations (including Jython, IronPython, PyPy, and MicroPython).
|
||||
While MicroPython's implementation differs substantially from CPython,
|
||||
it aims to maintain as much compatibility as possible.
|
||||
|
||||
cross-compiler
|
||||
Also known as ``mpy-cross``. This tool runs on your PC and converts a
|
||||
:term:`.py file` containing MicroPython code into a :term:`.mpy file`
|
||||
containing MicroPython bytecode. This means it loads faster (the board
|
||||
doesn't have to compile the code), and uses less space on flash (the
|
||||
bytecode is more space efficient).
|
||||
|
||||
driver
|
||||
A MicroPython library that implements support for a particular
|
||||
component, such as a sensor or display.
|
||||
|
||||
FFI
|
||||
Acronym for Foreign Function Interface. A mechanism used by the
|
||||
:term:`MicroPython Unix port` to access operating system functionality.
|
||||
This is not available on :term:`baremetal` ports.
|
||||
|
||||
filesystem
|
||||
Most MicroPython ports and boards provide a filesystem stored in flash
|
||||
that is available to user code via the standard Python file APIs such
|
||||
as ``open()``. Some boards also make this internal filesystem
|
||||
accessible to the host via USB mass-storage.
|
||||
|
||||
frozen module
|
||||
A Python module that has been cross compiled and bundled into the
|
||||
firmware image. This reduces RAM requirements as the code is executed
|
||||
directly from flash.
|
||||
|
||||
Garbage Collector
|
||||
A background process that runs in Python (and MicroPython) to reclaim
|
||||
unused memory in the :term:`heap`.
|
||||
|
||||
GPIO
|
||||
General-purpose input/output. The simplest means to control
|
||||
electrical signals. With GPIO, user can configure hardware
|
||||
signal pin to be either input or output, and set or get
|
||||
its digital signal value (logical "0" or "1"). MicroPython
|
||||
abstracts GPIO access using :class:`machine.Pin` and :class:`machine.Signal`
|
||||
General-purpose input/output. The simplest means to control electrical
|
||||
signals (commonly referred to as "pins") on a microcontroller. GPIO
|
||||
typically allows pins to be either input or output, and to set or get
|
||||
their digital value (logical "0" or "1"). MicroPython abstracts GPIO
|
||||
access using the :class:`machine.Pin` and :class:`machine.Signal`
|
||||
classes.
|
||||
|
||||
GPIO port
|
||||
A group of :term:`GPIO` pins, usually based on hardware
|
||||
properties of these pins (e.g. controllable by the same
|
||||
register).
|
||||
A group of :term:`GPIO` pins, usually based on hardware properties of
|
||||
these pins (e.g. controllable by the same register).
|
||||
|
||||
heap
|
||||
A region of RAM where MicroPython stores dynamic data. It is managed
|
||||
automatically by the :term:`Garbage Collector`. Different MCUs and
|
||||
boards have vastly different amounts of RAM available for the heap, so
|
||||
this will affect how complex your program can be.
|
||||
|
||||
interned string
|
||||
A string referenced by its (unique) identity rather than its
|
||||
address. Interned strings are thus can be quickly compared just
|
||||
by their identifiers, instead of comparing by content. The
|
||||
drawbacks of interned strings are that interning operation takes
|
||||
time (proportional to the number of existing interned strings,
|
||||
i.e. becoming slower and slower over time) and that the space
|
||||
used for interned strings is not reclaimable. String interning
|
||||
is done automatically by MicroPython compiler and runtimer when
|
||||
it's either required by the implementation (e.g. function keyword
|
||||
arguments are represented by interned string id's) or deemed
|
||||
beneficial (e.g. for short enough strings, which have a chance
|
||||
to be repeated, and thus interning them would save memory on
|
||||
copies). Most of string and I/O operations don't produce interned
|
||||
strings due to drawbacks described above.
|
||||
An optimisation used by MicroPython to improve the efficiency of
|
||||
working with strings. An interned string is referenced by its (unique)
|
||||
identity rather than its address and can therefore be quickly compared
|
||||
just by its identifier. It also means that identical strings can be
|
||||
de-duplicated in memory. String interning is almost always invisible to
|
||||
the user.
|
||||
|
||||
MCU
|
||||
Microcontroller. Microcontrollers usually have much less resources
|
||||
than a full-fledged computing system, but smaller, cheaper and
|
||||
than a desktop, laptop, or phone, but are smaller, cheaper and
|
||||
require much less power. MicroPython is designed to be small and
|
||||
optimized enough to run on an average modern microcontroller.
|
||||
|
||||
micropython-lib
|
||||
MicroPython is (usually) distributed as a single executable/binary
|
||||
file with just few builtin modules. There is no extensive standard
|
||||
library comparable with :term:`CPython`. Instead, there is a related, but
|
||||
separate project
|
||||
`micropython-lib <https://github.com/micropython/micropython-lib>`_
|
||||
which provides implementations for many modules from CPython's
|
||||
standard library. However, large subset of these modules require
|
||||
POSIX-like environment (Linux, FreeBSD, MacOS, etc.; Windows may be
|
||||
partially supported), and thus would work or make sense only with
|
||||
`MicroPython Unix port`. Some subset of modules is however usable
|
||||
for `baremetal` ports too.
|
||||
library comparable with :term:`CPython`'s. Instead, there is a related,
|
||||
but separate project `micropython-lib
|
||||
<https://github.com/micropython/micropython-lib>`_ which provides
|
||||
implementations for many modules from CPython's standard library.
|
||||
|
||||
Unlike monolithic :term:`CPython` stdlib, micropython-lib modules
|
||||
are intended to be installed individually - either using manual
|
||||
copying or using :term:`upip`.
|
||||
Some of the modules are are implemented in pure Python, and are able to
|
||||
be used on all ports. However, the majority of these modules use
|
||||
:term:`FFI` to access operating system functionality, and as such can
|
||||
only be used on the :term:`MicroPython Unix port` (with limited support
|
||||
for Windows).
|
||||
|
||||
Unlike the :term:`CPython` stdlib, micropython-lib modules are
|
||||
intended to be installed individually - either using manual copying or
|
||||
using :term:`upip`.
|
||||
|
||||
MicroPython port
|
||||
MicroPython supports different :term:`boards <board>`, RTOSes,
|
||||
and OSes, and can be relatively easily adapted to new systems.
|
||||
MicroPython with support for a particular system is called a
|
||||
"port" to that system. Different ports may have widely different
|
||||
functionality. This documentation is intended to be a reference
|
||||
of the generic APIs available across different ports ("MicroPython
|
||||
core"). Note that some ports may still omit some APIs described
|
||||
here (e.g. due to resource constraints). Any such differences,
|
||||
and port-specific extensions beyond MicroPython core functionality,
|
||||
would be described in the separate port-specific documentation.
|
||||
MicroPython supports different :term:`boards <board>`, RTOSes, and
|
||||
OSes, and can be relatively easily adapted to new systems. MicroPython
|
||||
with support for a particular system is called a "port" to that
|
||||
system. Different ports may have widely different functionality. This
|
||||
documentation is intended to be a reference of the generic APIs
|
||||
available across different ports ("MicroPython core"). Note that some
|
||||
ports may still omit some APIs described here (e.g. due to resource
|
||||
constraints). Any such differences, and port-specific extensions
|
||||
beyond the MicroPython core functionality, would be described in the
|
||||
separate port-specific documentation.
|
||||
|
||||
MicroPython Unix port
|
||||
Unix port is one of the major :term:`MicroPython ports <MicroPython port>`.
|
||||
It is intended to run on POSIX-compatible operating systems, like
|
||||
Linux, MacOS, FreeBSD, Solaris, etc. It also serves as the basis
|
||||
of Windows port. The importance of Unix port lies in the fact
|
||||
that while there are many different :term:`boards <board>`, so
|
||||
two random users unlikely have the same board, almost all modern
|
||||
OSes have some level of POSIX compatibility, so Unix port serves
|
||||
as a kind of "common ground" to which any user can have access.
|
||||
So, Unix port is used for initial prototyping, different kinds
|
||||
of testing, development of machine-independent features, etc.
|
||||
All users of MicroPython, even those which are interested only
|
||||
in running MicroPython on :term:`MCU` systems, are recommended
|
||||
to be familiar with Unix (or Windows) port, as it is important
|
||||
productivity helper and a part of normal MicroPython workflow.
|
||||
The unix port is one of the major :term:`MicroPython ports
|
||||
<MicroPython port>`. It is intended to run on POSIX-compatible
|
||||
operating systems, like Linux, MacOS, FreeBSD, Solaris, etc. It also
|
||||
serves as the basis of Windows port. The Unix port is very useful for
|
||||
quick development and testing of the MicroPython language and
|
||||
machine-independent features. It can also function in a similar way to
|
||||
:term:`CPython`'s ``python`` executable.
|
||||
|
||||
.mpy file
|
||||
The output of the :term:`cross-compiler`. A compiled form of a
|
||||
:term:`.py file` that contains MicroPython bytecode instead of Python
|
||||
source code.
|
||||
|
||||
native
|
||||
Usually refers to "native code", i.e. machine code for the target
|
||||
microcontroller (such as ARM Thumb, Xtensa, x86/x64). The ``@native``
|
||||
decorator can be applied to a MicroPython function to generate native
|
||||
code instead of bytecode for that function, which will likely be
|
||||
faster but use more RAM.
|
||||
|
||||
port
|
||||
Either :term:`MicroPython port` or :term:`GPIO port`. If not clear
|
||||
from context, it's recommended to use full specification like one
|
||||
of the above.
|
||||
Usually short for :term:`MicroPython port`, but could also refer to
|
||||
:term:`GPIO port`.
|
||||
|
||||
.py file
|
||||
A file containing Python source code.
|
||||
|
||||
REPL
|
||||
An acronym for "Read, Eval, Print, Loop". This is the interactive
|
||||
Python prompt, useful for debugging or testing short snippets of code.
|
||||
Most MicroPython boards make a REPL available over a UART, and this is
|
||||
typically accessible on a host PC via USB.
|
||||
|
||||
stream
|
||||
Also known as a "file-like object". An object which provides sequential
|
||||
read-write access to the underlying data. A stream object implements
|
||||
a corresponding interface, which consists of methods like ``read()``,
|
||||
``write()``, ``readinto()``, ``seek()``, ``flush()``, ``close()``, etc.
|
||||
A stream is an important concept in MicroPython, many I/O objects
|
||||
implement the stream interface, and thus can be used consistently and
|
||||
interchangeably in different contexts. For more information on
|
||||
streams in MicroPython, see `uio` module.
|
||||
Also known as a "file-like object". An Python object which provides
|
||||
sequential read-write access to the underlying data. A stream object
|
||||
implements a corresponding interface, which consists of methods like
|
||||
``read()``, ``write()``, ``readinto()``, ``seek()``, ``flush()``,
|
||||
``close()``, etc. A stream is an important concept in MicroPython;
|
||||
many I/O objects implement the stream interface, and thus can be used
|
||||
consistently and interchangeably in different contexts. For more
|
||||
information on streams in MicroPython, see the `uio` module.
|
||||
|
||||
UART
|
||||
Acronym for "Universal Asynchronous Receiver/Transmitter". This is a
|
||||
peripheral that sends data over a pair of pins (TX & RX). Many boards
|
||||
include a way to make at least one of the UARTs available to a host PC
|
||||
as a serial port over USB.
|
||||
|
||||
upip
|
||||
(Literally, "micro pip"). A package manage for MicroPython, inspired
|
||||
by :term:`CPython`'s pip, but much smaller and with reduced functionality.
|
||||
upip runs both on :term:`Unix port <MicroPython Unix port>` and on
|
||||
:term:`baremetal` ports (those which offer filesystem and networking
|
||||
support).
|
||||
(Literally, "micro pip"). A package manager for MicroPython, inspired
|
||||
by :term:`CPython`'s pip, but much smaller and with reduced
|
||||
functionality.
|
||||
upip runs both on the :term:`Unix port <MicroPython Unix port>` and on
|
||||
:term:`baremetal` ports which offer filesystem and networking support.
|
||||
|
||||
@ -39,7 +39,7 @@ The MicroPython distribution package format is a well-known tar.gz
|
||||
format, with some adaptations however. The Gzip compressor, used as
|
||||
an external wrapper for TAR archives, by default uses 32KB dictionary
|
||||
size, which means that to uncompress a compressed stream, 32KB of
|
||||
contguous memory needs to be allocated. This requirement may be not
|
||||
contiguous memory needs to be allocated. This requirement may be not
|
||||
satisfiable on low-memory devices, which may have total memory available
|
||||
less than that amount, and even if not, a contiguous block like that
|
||||
may be hard to allocate due to memory fragmentation. To accommodate
|
||||
@ -132,7 +132,7 @@ Installing to a directory image involves using ``-p`` switch to `upip`::
|
||||
|
||||
micropython -m upip install -p install_dir micropython-pystone_lowmem
|
||||
|
||||
After this command, the package content (and contents of every depenency
|
||||
After this command, the package content (and contents of every dependency
|
||||
packages) will be available in the ``install_dir/`` subdirectory. You
|
||||
would need to transfer contents of this directory (without the
|
||||
``install_dir/`` prefix) to the device, at the suitable location, where
|
||||
|
||||
@ -214,7 +214,7 @@ There are certain limitations in the current implementation of the native code e
|
||||
* Generators are not supported.
|
||||
* If ``raise`` is used an argument must be supplied.
|
||||
|
||||
The trade-off for the improved performance (roughly twices as fast as bytecode) is an
|
||||
The trade-off for the improved performance (roughly twice as fast as bytecode) is an
|
||||
increase in compiled code size.
|
||||
|
||||
The Viper code emitter
|
||||
|
||||
@ -1,19 +1,17 @@
|
||||
Getting started with Blynk and the WiPy
|
||||
---------------------------------------
|
||||
|
||||
Blynk is a platform with iOS and Android apps to control
|
||||
Arduino, Raspberry Pi and the likes over the Internet.
|
||||
You can easily build graphic interfaces for all your
|
||||
projects by simply dragging and dropping widgets.
|
||||
Blynk provides iOS and Android apps to control any hardware over the Internet
|
||||
or directly using Bluetooth. You can easily build graphic interfaces for all
|
||||
your projects by simply dragging and dropping widgets, right on your smartphone.
|
||||
|
||||
There are several examples available that work out-of-the-box with
|
||||
the WiPy. Before anything else, make sure that your WiPy is running
|
||||
Before anything else, make sure that your WiPy is running
|
||||
the latest software, check :ref:`OTA How-To <wipy_firmware_upgrade>` for instructions.
|
||||
|
||||
1. Get the `Blynk library <https://github.com/wipy/wipy/blob/master/lib/blynk/BlynkLib.py>`_ and put it in ``/flash/lib/`` via FTP.
|
||||
2. Get the `Blynk examples <https://github.com/wipy/wipy/tree/master/examples/blynk>`_, edit the network settings, and afterwards
|
||||
upload them to ``/flash/lib/`` via FTP as well.
|
||||
1. Get the `Blynk library <https://github.com/vshymanskyy/blynk-library-python/blob/master/BlynkLib.py>`_ and put it in ``/flash/lib/`` via FTP.
|
||||
2. Get the `Blynk example for WiPy <https://github.com/vshymanskyy/blynk-library-python/blob/master/examples/hardware/PyCom_WiPy.py>`_, edit the network settings, and afterwards
|
||||
upload it to ``/flash/`` via FTP as well.
|
||||
3. Follow the instructions on each example to setup the Blynk dashboard on your smartphone or tablet.
|
||||
4. Give it a try, for instance::
|
||||
|
||||
>>> execfile('01_simple.py')
|
||||
>>> execfile('sync_virtual.py')
|
||||
|
||||
222
drivers/cyw43/cywbt.c
Normal file
222
drivers/cyw43/cywbt.c
Normal file
@ -0,0 +1,222 @@
|
||||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Damien P. George
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "py/runtime.h"
|
||||
#include "py/mphal.h"
|
||||
#include "pin_static_af.h"
|
||||
#include "uart.h"
|
||||
#include "cywbt.h"
|
||||
#include "nimble/hci_uart.h"
|
||||
|
||||
#if MICROPY_PY_NETWORK_CYW43
|
||||
|
||||
extern const char fw_4343WA1_7_45_98_50_start;
|
||||
#define CYWBT_FW_ADDR (&fw_4343WA1_7_45_98_50_start + 749 * 512 + 29 * 256)
|
||||
|
||||
/******************************************************************************/
|
||||
// CYW BT HCI low-level driver
|
||||
|
||||
static void cywbt_wait_cts_low(void) {
|
||||
mp_hal_pin_config(pyb_pin_BT_CTS, MP_HAL_PIN_MODE_INPUT, MP_HAL_PIN_PULL_UP, 0);
|
||||
for (int i = 0; i < 200; ++i) {
|
||||
if (mp_hal_pin_read(pyb_pin_BT_CTS) == 0) {
|
||||
break;
|
||||
}
|
||||
mp_hal_delay_ms(1);
|
||||
}
|
||||
mp_hal_pin_config_alt_static(pyb_pin_BT_CTS, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_UP, STATIC_AF_USART6_CTS);
|
||||
}
|
||||
|
||||
static int cywbt_hci_cmd_raw(size_t len, uint8_t *buf) {
|
||||
uart_tx_strn(&bt_hci_uart_obj, (void*)buf, len);
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
while (!uart_rx_any(&bt_hci_uart_obj)) {
|
||||
MICROPY_EVENT_POLL_HOOK
|
||||
}
|
||||
buf[i] = uart_rx_char(&bt_hci_uart_obj);
|
||||
}
|
||||
|
||||
// expect a comand complete event (event 0x0e)
|
||||
if (buf[0] != 0x04 || buf[1] != 0x0e) {
|
||||
printf("unknown response: %02x %02x %02x %02x\n", buf[0], buf[1], buf[2], buf[3]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
if buf[3:6] != cmd[:3]:
|
||||
print('response doesn\'t match cmd:', cmd, ev)
|
||||
return b''
|
||||
*/
|
||||
|
||||
int sz = buf[2] - 3;
|
||||
for (int i = 0; i < sz; ++i) {
|
||||
while (!uart_rx_any(&bt_hci_uart_obj)) {
|
||||
MICROPY_EVENT_POLL_HOOK
|
||||
}
|
||||
buf[i] = uart_rx_char(&bt_hci_uart_obj);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cywbt_hci_cmd(int ogf, int ocf, size_t param_len, const uint8_t *param_buf) {
|
||||
uint8_t *buf = bt_hci_cmd_buf;
|
||||
buf[0] = 0x01;
|
||||
buf[1] = ocf;
|
||||
buf[2] = ogf << 2 | ocf >> 8;
|
||||
buf[3] = param_len;
|
||||
if (param_len) {
|
||||
memcpy(buf + 4, param_buf, param_len);
|
||||
}
|
||||
return cywbt_hci_cmd_raw(4 + param_len, buf);
|
||||
}
|
||||
|
||||
static void put_le16(uint8_t *buf, uint16_t val) {
|
||||
buf[0] = val;
|
||||
buf[1] = val >> 8;
|
||||
}
|
||||
|
||||
static void put_le32(uint8_t *buf, uint32_t val) {
|
||||
buf[0] = val;
|
||||
buf[1] = val >> 8;
|
||||
buf[2] = val >> 16;
|
||||
buf[3] = val >> 24;
|
||||
}
|
||||
|
||||
static int cywbt_set_baudrate(uint32_t baudrate) {
|
||||
uint8_t buf[6];
|
||||
put_le16(buf, 0);
|
||||
put_le32(buf + 2, baudrate);
|
||||
return cywbt_hci_cmd(0x3f, 0x18, 6, buf);
|
||||
}
|
||||
|
||||
// download firmware
|
||||
static int cywbt_download_firmware(const uint8_t *firmware) {
|
||||
cywbt_hci_cmd(0x3f, 0x2e, 0, NULL);
|
||||
|
||||
bool last_packet = false;
|
||||
while (!last_packet) {
|
||||
uint8_t *buf = bt_hci_cmd_buf;
|
||||
memcpy(buf + 1, firmware, 3);
|
||||
firmware += 3;
|
||||
last_packet = buf[1] == 0x4e;
|
||||
if (buf[2] != 0xfc) {
|
||||
printf("fail1 %02x\n", buf[2]);
|
||||
break;
|
||||
}
|
||||
uint8_t len = buf[3];
|
||||
|
||||
memcpy(buf + 4, firmware, len);
|
||||
firmware += len;
|
||||
|
||||
buf[0] = 1;
|
||||
cywbt_hci_cmd_raw(4 + len, buf);
|
||||
if (buf[0] != 0) {
|
||||
printf("fail3 %02x\n", buf[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// RF switch must select high path during BT patch boot
|
||||
mp_hal_pin_config(pyb_pin_WL_GPIO_1, MP_HAL_PIN_MODE_INPUT, MP_HAL_PIN_PULL_UP, 0);
|
||||
mp_hal_delay_ms(10); // give some time for CTS to go high
|
||||
cywbt_wait_cts_low();
|
||||
mp_hal_pin_config(pyb_pin_WL_GPIO_1, MP_HAL_PIN_MODE_INPUT, MP_HAL_PIN_PULL_DOWN, 0); // Select chip antenna (could also select external)
|
||||
|
||||
nimble_hci_uart_set_baudrate(115200);
|
||||
cywbt_set_baudrate(3000000);
|
||||
nimble_hci_uart_set_baudrate(3000000);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cywbt_init(void) {
|
||||
// This is called from Nimble via hal_uart_config which will have already initialized the UART.
|
||||
|
||||
mp_hal_pin_output(pyb_pin_BT_REG_ON);
|
||||
mp_hal_pin_low(pyb_pin_BT_REG_ON);
|
||||
mp_hal_pin_input(pyb_pin_BT_HOST_WAKE);
|
||||
mp_hal_pin_output(pyb_pin_BT_DEV_WAKE);
|
||||
mp_hal_pin_low(pyb_pin_BT_DEV_WAKE);
|
||||
|
||||
// TODO don't select antenna if wifi is enabled
|
||||
mp_hal_pin_config(pyb_pin_WL_GPIO_4, MP_HAL_PIN_MODE_OUTPUT, MP_HAL_PIN_PULL_NONE, 0); // RF-switch power
|
||||
mp_hal_pin_high(pyb_pin_WL_GPIO_4); // Turn the RF-switch on
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cywbt_activate(void) {
|
||||
uint8_t buf[256];
|
||||
|
||||
mp_hal_pin_low(pyb_pin_BT_REG_ON);
|
||||
nimble_hci_uart_set_baudrate(115200);
|
||||
mp_hal_delay_ms(100);
|
||||
mp_hal_pin_high(pyb_pin_BT_REG_ON);
|
||||
cywbt_wait_cts_low();
|
||||
|
||||
// Reset
|
||||
cywbt_hci_cmd(0x03, 0x0003, 0, NULL);
|
||||
|
||||
// Change baudrate
|
||||
cywbt_set_baudrate(3000000);
|
||||
nimble_hci_uart_set_baudrate(3000000);
|
||||
|
||||
cywbt_download_firmware((const uint8_t*)CYWBT_FW_ADDR);
|
||||
|
||||
// Reset
|
||||
cywbt_hci_cmd(0x03, 0x0003, 0, NULL);
|
||||
|
||||
// Set BD_ADDR (sent as little endian)
|
||||
uint8_t bdaddr[6];
|
||||
mp_hal_get_mac(MP_HAL_MAC_BDADDR, bdaddr);
|
||||
buf[0] = bdaddr[5];
|
||||
buf[1] = bdaddr[4];
|
||||
buf[2] = bdaddr[3];
|
||||
buf[3] = bdaddr[2];
|
||||
buf[4] = bdaddr[1];
|
||||
buf[5] = bdaddr[0];
|
||||
cywbt_hci_cmd(0x3f, 0x0001, 6, buf);
|
||||
|
||||
// Set local name
|
||||
// memset(buf, 0, 248);
|
||||
// memcpy(buf, "PYBD-BLE", 8);
|
||||
// cywbt_hci_cmd(0x03, 0x0013, 248, buf);
|
||||
|
||||
// Configure sleep mode
|
||||
cywbt_hci_cmd(0x3f, 0x27, 12, (const uint8_t*)"\x01\x02\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00");
|
||||
|
||||
// HCI_Write_LE_Host_Support
|
||||
cywbt_hci_cmd(3, 109, 2, (const uint8_t*)"\x01\x00");
|
||||
|
||||
mp_hal_pin_high(pyb_pin_BT_DEV_WAKE); // let sleep
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
35
drivers/cyw43/cywbt.h
Normal file
35
drivers/cyw43/cywbt.h
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Damien P. George
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#ifndef MICROPY_INCLUDED_DRIVERS_CYW43_CYWBT_H
|
||||
#define MICROPY_INCLUDED_DRIVERS_CYW43_CYWBT_H
|
||||
|
||||
extern uint8_t bt_hci_cmd_buf[4 + 256];
|
||||
extern pyb_uart_obj_t bt_hci_uart_obj;
|
||||
|
||||
int cywbt_init(void);
|
||||
int cywbt_activate(void);
|
||||
|
||||
#endif // MICROPY_INCLUDED_DRIVERS_CYW43_CYWBT_H
|
||||
@ -13,7 +13,7 @@ class DS18X20:
|
||||
self.buf = bytearray(9)
|
||||
|
||||
def scan(self):
|
||||
return [rom for rom in self.ow.scan() if rom[0] == 0x10 or rom[0] == 0x28]
|
||||
return [rom for rom in self.ow.scan() if rom[0] in (0x10, 0x22, 0x28)]
|
||||
|
||||
def convert_temp(self):
|
||||
self.ow.reset(True)
|
||||
|
||||
48
examples/bluetooth/ble_advertising.py
Normal file
48
examples/bluetooth/ble_advertising.py
Normal file
@ -0,0 +1,48 @@
|
||||
# Helpers for generating BLE advertising payloads.
|
||||
|
||||
from micropython import const
|
||||
import struct
|
||||
|
||||
# Advertising payloads are repeated packets of the following form:
|
||||
# 1 byte data length (N + 1)
|
||||
# 1 byte type (see constants below)
|
||||
# N bytes type-specific data
|
||||
|
||||
_ADV_TYPE_FLAGS = const(0x01)
|
||||
_ADV_TYPE_NAME = const(0x09)
|
||||
_ADV_TYPE_UUID16_COMPLETE = const(0x3)
|
||||
_ADV_TYPE_UUID32_COMPLETE = const(0x5)
|
||||
_ADV_TYPE_UUID128_COMPLETE = const(0x7)
|
||||
_ADV_TYPE_UUID16_MORE = const(0x2)
|
||||
_ADV_TYPE_UUID32_MORE = const(0x4)
|
||||
_ADV_TYPE_UUID128_MORE = const(0x6)
|
||||
_ADV_TYPE_APPEARANCE = const(0x19)
|
||||
|
||||
|
||||
# Generate a payload to be passed to gap_advertise(adv_data=...).
|
||||
def advertising_payload(limited_disc=False, br_edr=False, name=None, services=None, appearance=0):
|
||||
payload = bytearray()
|
||||
|
||||
def _append(adv_type, value):
|
||||
nonlocal payload
|
||||
payload += struct.pack('BB', len(value) + 1, adv_type) + value
|
||||
|
||||
_append(_ADV_TYPE_FLAGS, struct.pack('B', (0x01 if limited_disc else 0x02) + (0x00 if br_edr else 0x04)))
|
||||
|
||||
if name:
|
||||
_append(_ADV_TYPE_NAME, name)
|
||||
|
||||
if services:
|
||||
for uuid in services:
|
||||
b = bytes(uuid)
|
||||
if len(b) == 2:
|
||||
_append(_ADV_TYPE_UUID16_COMPLETE, b)
|
||||
elif len(b) == 4:
|
||||
_append(_ADV_TYPE_UUID32_COMPLETE, b)
|
||||
elif len(b) == 16:
|
||||
_append(_ADV_TYPE_UUID128_COMPLETE, b)
|
||||
|
||||
# See org.bluetooth.characteristic.gap.appearance.xml
|
||||
_append(_ADV_TYPE_APPEARANCE, struct.pack('<h', appearance))
|
||||
|
||||
return payload
|
||||
76
examples/bluetooth/ble_temperature.py
Normal file
76
examples/bluetooth/ble_temperature.py
Normal file
@ -0,0 +1,76 @@
|
||||
# This example demonstrates a simple temperature sensor peripheral.
|
||||
#
|
||||
# The sensor's local value updates every second, and it will notify
|
||||
# any connected central every 10 seconds.
|
||||
|
||||
import bluetooth
|
||||
import random
|
||||
import struct
|
||||
import time
|
||||
from ble_advertising import advertising_payload
|
||||
|
||||
from micropython import const
|
||||
_IRQ_CENTRAL_CONNECT = const(1 << 0)
|
||||
_IRQ_CENTRAL_DISCONNECT = const(1 << 1)
|
||||
|
||||
# org.bluetooth.service.environmental_sensing
|
||||
_ENV_SENSE_UUID = bluetooth.UUID(0x181A)
|
||||
# org.bluetooth.characteristic.temperature
|
||||
_TEMP_CHAR = (bluetooth.UUID(0x2A6E), bluetooth.FLAG_READ|bluetooth.FLAG_NOTIFY,)
|
||||
_ENV_SENSE_SERVICE = (_ENV_SENSE_UUID, (_TEMP_CHAR,),)
|
||||
|
||||
# org.bluetooth.characteristic.gap.appearance.xml
|
||||
_ADV_APPEARANCE_GENERIC_THERMOMETER = const(768)
|
||||
|
||||
class BLETemperature:
|
||||
def __init__(self, ble, name='mpy-temp'):
|
||||
self._ble = ble
|
||||
self._ble.active(True)
|
||||
self._ble.irq(handler=self._irq)
|
||||
((self._handle,),) = self._ble.gatts_register_services((_ENV_SENSE_SERVICE,))
|
||||
self._connections = set()
|
||||
self._payload = advertising_payload(name=name, services=[_ENV_SENSE_UUID], appearance=_ADV_APPEARANCE_GENERIC_THERMOMETER)
|
||||
self._advertise()
|
||||
|
||||
def _irq(self, event, data):
|
||||
# Track connections so we can send notifications.
|
||||
if event == _IRQ_CENTRAL_CONNECT:
|
||||
conn_handle, _, _, = data
|
||||
self._connections.add(conn_handle)
|
||||
elif event == _IRQ_CENTRAL_DISCONNECT:
|
||||
conn_handle, _, _, = data
|
||||
self._connections.remove(conn_handle)
|
||||
# Start advertising again to allow a new connection.
|
||||
self._advertise()
|
||||
|
||||
def set_temperature(self, temp_deg_c, notify=False):
|
||||
# Data is sint16 in degrees Celsius with a resolution of 0.01 degrees Celsius.
|
||||
# Write the local value, ready for a central to read.
|
||||
self._ble.gatts_write(self._handle, struct.pack('<h', int(temp_deg_c * 100)))
|
||||
if notify:
|
||||
for conn_handle in self._connections:
|
||||
# Notify connected centrals to issue a read.
|
||||
self._ble.gatts_notify(conn_handle, self._handle)
|
||||
|
||||
def _advertise(self, interval_us=500000):
|
||||
self._ble.gap_advertise(interval_us, adv_data=self._payload)
|
||||
|
||||
|
||||
def demo():
|
||||
ble = bluetooth.BLE()
|
||||
temp = BLETemperature(ble)
|
||||
|
||||
t = 25
|
||||
i = 0
|
||||
|
||||
while True:
|
||||
# Write every second, notify every 10 seconds.
|
||||
i = (i + 1) % 10
|
||||
temp.set_temperature(t, notify=i == 0)
|
||||
# Random walk the temperature.
|
||||
t += random.uniform(-0.5, 0.5)
|
||||
time.sleep_ms(1000)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
demo()
|
||||
103
examples/bluetooth/ble_uart_peripheral.py
Normal file
103
examples/bluetooth/ble_uart_peripheral.py
Normal file
@ -0,0 +1,103 @@
|
||||
# This example demonstrates a peripheral implementing the Nordic UART Service (NUS).
|
||||
|
||||
import bluetooth
|
||||
from ble_advertising import advertising_payload
|
||||
|
||||
from micropython import const
|
||||
_IRQ_CENTRAL_CONNECT = const(1 << 0)
|
||||
_IRQ_CENTRAL_DISCONNECT = const(1 << 1)
|
||||
_IRQ_GATTS_WRITE = const(1 << 2)
|
||||
|
||||
_UART_UUID = bluetooth.UUID('6E400001-B5A3-F393-E0A9-E50E24DCCA9E')
|
||||
_UART_TX = (bluetooth.UUID('6E400003-B5A3-F393-E0A9-E50E24DCCA9E'), bluetooth.FLAG_NOTIFY,)
|
||||
_UART_RX = (bluetooth.UUID('6E400002-B5A3-F393-E0A9-E50E24DCCA9E'), bluetooth.FLAG_WRITE,)
|
||||
_UART_SERVICE = (_UART_UUID, (_UART_TX, _UART_RX,),)
|
||||
|
||||
# org.bluetooth.characteristic.gap.appearance.xml
|
||||
_ADV_APPEARANCE_GENERIC_COMPUTER = const(128)
|
||||
|
||||
class BLEUART:
|
||||
def __init__(self, ble, name='mpy-uart', rxbuf=100):
|
||||
self._ble = ble
|
||||
self._ble.active(True)
|
||||
self._ble.irq(handler=self._irq)
|
||||
((self._tx_handle, self._rx_handle,),) = self._ble.gatts_register_services((_UART_SERVICE,))
|
||||
# Increase the size of the rx buffer and enable append mode.
|
||||
self._ble.gatts_set_buffer(self._rx_handle, rxbuf, True)
|
||||
self._connections = set()
|
||||
self._rx_buffer = bytearray()
|
||||
self._handler = None
|
||||
# Optionally add services=[_UART_UUID], but this is likely to make the payload too large.
|
||||
self._payload = advertising_payload(name=name, appearance=_ADV_APPEARANCE_GENERIC_COMPUTER)
|
||||
self._advertise()
|
||||
|
||||
def irq(self, handler):
|
||||
self._handler = handler
|
||||
|
||||
def _irq(self, event, data):
|
||||
# Track connections so we can send notifications.
|
||||
if event == _IRQ_CENTRAL_CONNECT:
|
||||
conn_handle, _, _, = data
|
||||
self._connections.add(conn_handle)
|
||||
elif event == _IRQ_CENTRAL_DISCONNECT:
|
||||
conn_handle, _, _, = data
|
||||
if conn_handle in self._connections:
|
||||
self._connections.remove(conn_handle)
|
||||
# Start advertising again to allow a new connection.
|
||||
self._advertise()
|
||||
elif event == _IRQ_GATTS_WRITE:
|
||||
conn_handle, value_handle, = data
|
||||
if conn_handle in self._connections and value_handle == self._rx_handle:
|
||||
self._rx_buffer += self._ble.gatts_read(self._rx_handle)
|
||||
if self._handler:
|
||||
self._handler()
|
||||
|
||||
def any(self):
|
||||
return len(self._rx_buffer)
|
||||
|
||||
def read(self, sz=None):
|
||||
if not sz:
|
||||
sz = len(self._rx_buffer)
|
||||
result = self._rx_buffer[0:sz]
|
||||
self._rx_buffer = self._rx_buffer[sz:]
|
||||
return result
|
||||
|
||||
def write(self, data):
|
||||
for conn_handle in self._connections:
|
||||
self._ble.gatts_notify(conn_handle, self._tx_handle, data)
|
||||
|
||||
def close(self):
|
||||
for conn_handle in self._connections:
|
||||
self._ble.gap_disconnect(conn_handle)
|
||||
self._connections.clear()
|
||||
|
||||
def _advertise(self, interval_us=500000):
|
||||
self._ble.gap_advertise(interval_us, adv_data=self._payload)
|
||||
|
||||
|
||||
def demo():
|
||||
import time
|
||||
|
||||
ble = bluetooth.BLE()
|
||||
uart = BLEUART(ble)
|
||||
|
||||
def on_rx():
|
||||
print('rx: ', uart.read().decode().strip())
|
||||
|
||||
uart.irq(handler=on_rx)
|
||||
nums = [4, 8, 15, 16, 23, 42]
|
||||
i = 0
|
||||
|
||||
try:
|
||||
while True:
|
||||
uart.write(str(nums[i]) + '\n')
|
||||
i = (i + 1) % len(nums)
|
||||
time.sleep_ms(1000)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
uart.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
demo()
|
||||
80
examples/bluetooth/ble_uart_repl.py
Normal file
80
examples/bluetooth/ble_uart_repl.py
Normal file
@ -0,0 +1,80 @@
|
||||
# Proof-of-concept of a REPL over BLE UART.
|
||||
#
|
||||
# Tested with the Adafruit Bluefruit app on Android.
|
||||
# Set the EoL characters to \r\n.
|
||||
|
||||
import bluetooth
|
||||
import io
|
||||
import os
|
||||
import micropython
|
||||
import machine
|
||||
|
||||
from ble_uart_peripheral import BLEUART
|
||||
|
||||
_MP_STREAM_POLL = const(3)
|
||||
_MP_STREAM_POLL_RD = const(0x0001)
|
||||
|
||||
# TODO: Remove this when STM32 gets machine.Timer.
|
||||
if hasattr(machine, 'Timer'):
|
||||
_timer = machine.Timer(-1)
|
||||
else:
|
||||
_timer = None
|
||||
|
||||
# Batch writes into 50ms intervals.
|
||||
def schedule_in(handler, delay_ms):
|
||||
def _wrap(_arg):
|
||||
handler()
|
||||
if _timer:
|
||||
_timer.init(mode=machine.Timer.ONE_SHOT, period=delay_ms, callback=_wrap)
|
||||
else:
|
||||
micropython.schedule(_wrap, None)
|
||||
|
||||
# Simple buffering stream to support the dupterm requirements.
|
||||
class BLEUARTStream(io.IOBase):
|
||||
def __init__(self, uart):
|
||||
self._uart = uart
|
||||
self._tx_buf = bytearray()
|
||||
self._uart.irq(self._on_rx)
|
||||
|
||||
def _on_rx(self):
|
||||
# Needed for ESP32.
|
||||
if hasattr(os, 'dupterm_notify'):
|
||||
os.dupterm_notify(None)
|
||||
|
||||
def read(self, sz=None):
|
||||
return self._uart.read(sz)
|
||||
|
||||
def readinto(self, buf):
|
||||
avail = self._uart.read(len(buf))
|
||||
if not avail:
|
||||
return None
|
||||
for i in range(len(avail)):
|
||||
buf[i] = avail[i]
|
||||
return len(avail)
|
||||
|
||||
def ioctl(self, op, arg):
|
||||
if op == _MP_STREAM_POLL:
|
||||
if self._uart.any():
|
||||
return _MP_STREAM_POLL_RD
|
||||
return 0
|
||||
|
||||
def _flush(self):
|
||||
data = self._tx_buf[0:100]
|
||||
self._tx_buf = self._tx_buf[100:]
|
||||
self._uart.write(data)
|
||||
if self._tx_buf:
|
||||
schedule_in(self._flush, 50)
|
||||
|
||||
def write(self, buf):
|
||||
empty = not self._tx_buf
|
||||
self._tx_buf += buf
|
||||
if empty:
|
||||
schedule_in(self._flush, 50)
|
||||
|
||||
|
||||
def start():
|
||||
ble = bluetooth.BLE()
|
||||
uart = BLEUART(ble, name='mpy-repl')
|
||||
stream = BLEUARTStream(uart)
|
||||
|
||||
os.dupterm(stream)
|
||||
@ -1 +0,0 @@
|
||||
mpconfigport_minimal.h
|
||||
1
examples/embedding/mpconfigport.h
Normal file
1
examples/embedding/mpconfigport.h
Normal file
@ -0,0 +1 @@
|
||||
#include "mpconfigport_minimal.h"
|
||||
@ -3,6 +3,29 @@
|
||||
# this sets the config file for FatFs
|
||||
CFLAGS_MOD += -DFFCONF_H=\"lib/oofatfs/ffconf.h\"
|
||||
|
||||
################################################################################
|
||||
# VFS littlefs
|
||||
|
||||
LITTLEFS_DIR = lib/littlefs
|
||||
|
||||
ifeq ($(MICROPY_VFS_LFS1),1)
|
||||
CFLAGS_MOD += -DMICROPY_VFS_LFS1=1
|
||||
CFLAGS_MOD += -DLFS1_NO_MALLOC -DLFS1_NO_DEBUG -DLFS1_NO_WARN -DLFS1_NO_ERROR -DLFS1_NO_ASSERT
|
||||
SRC_MOD += $(addprefix $(LITTLEFS_DIR)/,\
|
||||
lfs1.c \
|
||||
lfs1_util.c \
|
||||
)
|
||||
endif
|
||||
|
||||
ifeq ($(MICROPY_VFS_LFS2),1)
|
||||
CFLAGS_MOD += -DMICROPY_VFS_LFS2=1
|
||||
CFLAGS_MOD += -DLFS2_NO_MALLOC -DLFS2_NO_DEBUG -DLFS2_NO_WARN -DLFS2_NO_ERROR -DLFS2_NO_ASSERT
|
||||
SRC_MOD += $(addprefix $(LITTLEFS_DIR)/,\
|
||||
lfs2.c \
|
||||
lfs2_util.c \
|
||||
)
|
||||
endif
|
||||
|
||||
################################################################################
|
||||
# ussl
|
||||
|
||||
|
||||
965
extmod/modbluetooth.c
Normal file
965
extmod/modbluetooth.c
Normal file
@ -0,0 +1,965 @@
|
||||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2018 Ayke van Laethem
|
||||
* Copyright (c) 2019 Jim Mussared
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "py/binary.h"
|
||||
#include "py/misc.h"
|
||||
#include "py/mperrno.h"
|
||||
#include "py/obj.h"
|
||||
#include "py/objstr.h"
|
||||
#include "py/objarray.h"
|
||||
#include "py/qstr.h"
|
||||
#include "py/runtime.h"
|
||||
#include "extmod/modbluetooth.h"
|
||||
#include <string.h>
|
||||
|
||||
#if MICROPY_PY_BLUETOOTH
|
||||
|
||||
#if !MICROPY_ENABLE_SCHEDULER
|
||||
#error modbluetooth requires MICROPY_ENABLE_SCHEDULER
|
||||
#endif
|
||||
|
||||
// This is used to protect the ringbuffer.
|
||||
#ifndef MICROPY_PY_BLUETOOTH_ENTER
|
||||
#define MICROPY_PY_BLUETOOTH_ENTER mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION();
|
||||
#define MICROPY_PY_BLUETOOTH_EXIT MICROPY_END_ATOMIC_SECTION(atomic_state);
|
||||
#endif
|
||||
|
||||
#define MP_BLUETOOTH_CONNECT_DEFAULT_SCAN_DURATION_MS 2000
|
||||
|
||||
#define MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_TUPLE_LEN 5
|
||||
#define MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN (MICROPY_PY_BLUETOOTH_RINGBUF_SIZE / 2)
|
||||
|
||||
STATIC const mp_obj_type_t bluetooth_ble_type;
|
||||
STATIC const mp_obj_type_t bluetooth_uuid_type;
|
||||
|
||||
typedef struct {
|
||||
mp_obj_base_t base;
|
||||
mp_obj_t irq_handler;
|
||||
mp_obj_t irq_data_tuple;
|
||||
uint8_t irq_addr_bytes[6];
|
||||
uint8_t irq_data_bytes[MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN];
|
||||
mp_obj_t irq_data_uuid;
|
||||
uint16_t irq_trigger;
|
||||
ringbuf_t ringbuf;
|
||||
} mp_obj_bluetooth_ble_t;
|
||||
|
||||
// TODO: this seems like it could be generic?
|
||||
STATIC mp_obj_t bluetooth_handle_errno(int err) {
|
||||
if (err != 0) {
|
||||
mp_raise_OSError(err);
|
||||
}
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// UUID object
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Parse string UUIDs, which are expected to be 128-bit UUIDs.
|
||||
STATIC void mp_bluetooth_parse_uuid_128bit_str(mp_obj_t obj, uint8_t *uuid) {
|
||||
size_t str_len;
|
||||
const char *str_data = mp_obj_str_get_data(obj, &str_len);
|
||||
int uuid_i = 32;
|
||||
for (int i = 0; i < str_len; i++) {
|
||||
char c = str_data[i];
|
||||
if (c == '-') {
|
||||
continue;
|
||||
}
|
||||
if (!unichar_isxdigit(c)) {
|
||||
mp_raise_ValueError("invalid char in UUID");
|
||||
}
|
||||
c = unichar_xdigit_value(c);
|
||||
uuid_i--;
|
||||
if (uuid_i < 0) {
|
||||
mp_raise_ValueError("UUID too long");
|
||||
}
|
||||
if (uuid_i % 2 == 0) {
|
||||
// lower nibble
|
||||
uuid[uuid_i/2] |= c;
|
||||
} else {
|
||||
// upper nibble
|
||||
uuid[uuid_i/2] = c << 4;
|
||||
}
|
||||
}
|
||||
if (uuid_i > 0) {
|
||||
mp_raise_ValueError("UUID too short");
|
||||
}
|
||||
}
|
||||
|
||||
STATIC mp_obj_t bluetooth_uuid_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
|
||||
mp_arg_check_num(n_args, n_kw, 1, 1, false);
|
||||
|
||||
mp_obj_bluetooth_uuid_t *self = m_new_obj(mp_obj_bluetooth_uuid_t);
|
||||
self->base.type = &bluetooth_uuid_type;
|
||||
|
||||
if (mp_obj_is_int(all_args[0])) {
|
||||
self->type = MP_BLUETOOTH_UUID_TYPE_16;
|
||||
mp_int_t value = mp_obj_get_int(all_args[0]);
|
||||
if (value > 65535) {
|
||||
mp_raise_ValueError("invalid UUID");
|
||||
}
|
||||
self->data[0] = value & 0xff;
|
||||
self->data[1] = (value >> 8) & 0xff;
|
||||
} else {
|
||||
self->type = MP_BLUETOOTH_UUID_TYPE_128;
|
||||
mp_bluetooth_parse_uuid_128bit_str(all_args[0], self->data);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
STATIC mp_obj_t bluetooth_uuid_unary_op(mp_unary_op_t op, mp_obj_t self_in) {
|
||||
mp_obj_bluetooth_uuid_t *self = MP_OBJ_TO_PTR(self_in);
|
||||
switch (op) {
|
||||
case MP_UNARY_OP_HASH: {
|
||||
// Use the QSTR hash function.
|
||||
return MP_OBJ_NEW_SMALL_INT(qstr_compute_hash(self->data, self->type));
|
||||
}
|
||||
default: return MP_OBJ_NULL; // op not supported
|
||||
}
|
||||
}
|
||||
|
||||
STATIC void bluetooth_uuid_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
|
||||
mp_obj_bluetooth_uuid_t *self = MP_OBJ_TO_PTR(self_in);
|
||||
mp_printf(print, "UUID%u(%s", self->type * 8, self->type <= 4 ? "0x" : "'");
|
||||
for (int i = 0; i < self->type; ++i) {
|
||||
if (i == 4 || i == 6 || i == 8 || i == 10) {
|
||||
mp_printf(print, "-");
|
||||
}
|
||||
mp_printf(print, "%02x", self->data[self->type - 1 - i]);
|
||||
}
|
||||
if (self->type == MP_BLUETOOTH_UUID_TYPE_128) {
|
||||
mp_printf(print, "'");
|
||||
}
|
||||
mp_printf(print, ")");
|
||||
}
|
||||
|
||||
mp_int_t bluetooth_uuid_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) {
|
||||
mp_obj_bluetooth_uuid_t *self = MP_OBJ_TO_PTR(self_in);
|
||||
|
||||
if (flags != MP_BUFFER_READ) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
bufinfo->buf = self->data;
|
||||
bufinfo->len = self->type;
|
||||
bufinfo->typecode = 'B';
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
||||
|
||||
STATIC void ringbuf_put_uuid(ringbuf_t *ringbuf, mp_obj_bluetooth_uuid_t *uuid) {
|
||||
assert(ringbuf_free(ringbuf) >= uuid->type + 1);
|
||||
ringbuf_put(ringbuf, uuid->type);
|
||||
for (int i = 0; i < uuid->type; ++i) {
|
||||
ringbuf_put(ringbuf, uuid->data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
STATIC void ringbuf_get_uuid(ringbuf_t *ringbuf, mp_obj_bluetooth_uuid_t *uuid) {
|
||||
assert(ringbuf_avail(ringbuf) >= 1);
|
||||
uuid->type = ringbuf_get(ringbuf);
|
||||
assert(ringbuf_avail(ringbuf) >= uuid->type);
|
||||
for (int i = 0; i < uuid->type; ++i) {
|
||||
uuid->data[i] = ringbuf_get(ringbuf);
|
||||
}
|
||||
}
|
||||
#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
||||
|
||||
STATIC const mp_obj_type_t bluetooth_uuid_type = {
|
||||
{ &mp_type_type },
|
||||
.name = MP_QSTR_UUID,
|
||||
.make_new = bluetooth_uuid_make_new,
|
||||
.unary_op = bluetooth_uuid_unary_op,
|
||||
.locals_dict = NULL,
|
||||
.print = bluetooth_uuid_print,
|
||||
.buffer_p = { .get_buffer = bluetooth_uuid_get_buffer },
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Bluetooth object: General
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
STATIC mp_obj_t bluetooth_ble_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
|
||||
if (MP_STATE_VM(bluetooth) == MP_OBJ_NULL) {
|
||||
mp_obj_bluetooth_ble_t *o = m_new_obj(mp_obj_bluetooth_ble_t);
|
||||
o->base.type = &bluetooth_ble_type;
|
||||
o->irq_handler = mp_const_none;
|
||||
// Pre-allocate the event data tuple to prevent needing to allocate in the IRQ handler.
|
||||
o->irq_data_tuple = mp_obj_new_tuple(MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_TUPLE_LEN, NULL);
|
||||
mp_obj_bluetooth_uuid_t *uuid = m_new_obj(mp_obj_bluetooth_uuid_t);
|
||||
uuid->base.type = &bluetooth_uuid_type;
|
||||
o->irq_data_uuid = MP_OBJ_FROM_PTR(uuid);
|
||||
o->irq_trigger = 0;
|
||||
ringbuf_alloc(&o->ringbuf, MICROPY_PY_BLUETOOTH_RINGBUF_SIZE);
|
||||
MP_STATE_VM(bluetooth) = MP_OBJ_FROM_PTR(o);
|
||||
}
|
||||
return MP_STATE_VM(bluetooth);
|
||||
}
|
||||
|
||||
STATIC mp_obj_t bluetooth_ble_active(size_t n_args, const mp_obj_t *args) {
|
||||
if (n_args == 2) {
|
||||
// Boolean enable/disable argument supplied, set current state.
|
||||
if (mp_obj_is_true(args[1])) {
|
||||
int err = mp_bluetooth_init();
|
||||
if (err != 0) {
|
||||
mp_raise_OSError(err);
|
||||
}
|
||||
} else {
|
||||
mp_bluetooth_deinit();
|
||||
}
|
||||
}
|
||||
// Return current state.
|
||||
return mp_obj_new_bool(mp_bluetooth_is_enabled());
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_active_obj, 1, 2, bluetooth_ble_active);
|
||||
|
||||
STATIC mp_obj_t bluetooth_ble_config(mp_obj_t self_in, mp_obj_t param) {
|
||||
if (param == MP_OBJ_NEW_QSTR(MP_QSTR_mac)) {
|
||||
uint8_t addr[6];
|
||||
mp_bluetooth_get_device_addr(addr);
|
||||
return mp_obj_new_bytes(addr, MP_ARRAY_SIZE(addr));
|
||||
} else {
|
||||
mp_raise_ValueError("unknown config param");
|
||||
}
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_ble_config_obj, bluetooth_ble_config);
|
||||
|
||||
STATIC mp_obj_t bluetooth_ble_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
|
||||
enum { ARG_handler, ARG_trigger };
|
||||
static const mp_arg_t allowed_args[] = {
|
||||
{ MP_QSTR_handler, MP_ARG_OBJ|MP_ARG_REQUIRED, {.u_obj = mp_const_none} },
|
||||
{ MP_QSTR_trigger, MP_ARG_INT, {.u_int = MP_BLUETOOTH_IRQ_ALL} },
|
||||
};
|
||||
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
|
||||
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
|
||||
mp_obj_t callback = args[ARG_handler].u_obj;
|
||||
if (callback != mp_const_none && !mp_obj_is_callable(callback)) {
|
||||
mp_raise_ValueError("invalid callback");
|
||||
}
|
||||
|
||||
// Update the callback.
|
||||
MICROPY_PY_BLUETOOTH_ENTER
|
||||
mp_obj_bluetooth_ble_t* o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
|
||||
o->irq_handler = callback;
|
||||
o->irq_trigger = args[ARG_trigger].u_int;
|
||||
MICROPY_PY_BLUETOOTH_EXIT
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(bluetooth_ble_irq_obj, 1, bluetooth_ble_irq);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Bluetooth object: GAP
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
STATIC mp_obj_t bluetooth_ble_gap_advertise(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
|
||||
enum { ARG_interval_us, ARG_adv_data, ARG_resp_data, ARG_connectable };
|
||||
static const mp_arg_t allowed_args[] = {
|
||||
{ MP_QSTR_interval_us, MP_ARG_OBJ, {.u_obj = MP_OBJ_NEW_SMALL_INT(500000)} },
|
||||
{ MP_QSTR_adv_data, MP_ARG_OBJ, {.u_obj = mp_const_none } },
|
||||
{ MP_QSTR_resp_data, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_none } },
|
||||
{ MP_QSTR_connectable, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_obj = mp_const_true } },
|
||||
};
|
||||
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
|
||||
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
|
||||
|
||||
if (args[ARG_interval_us].u_obj == mp_const_none) {
|
||||
mp_bluetooth_gap_advertise_stop();
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
mp_int_t interval_us = mp_obj_get_int(args[ARG_interval_us].u_obj);
|
||||
bool connectable = mp_obj_is_true(args[ARG_connectable].u_obj);
|
||||
|
||||
mp_buffer_info_t adv_bufinfo = {0};
|
||||
if (args[ARG_adv_data].u_obj != mp_const_none) {
|
||||
mp_get_buffer_raise(args[ARG_adv_data].u_obj, &adv_bufinfo, MP_BUFFER_READ);
|
||||
}
|
||||
|
||||
mp_buffer_info_t resp_bufinfo = {0};
|
||||
if (args[ARG_resp_data].u_obj != mp_const_none) {
|
||||
mp_get_buffer_raise(args[ARG_resp_data].u_obj, &resp_bufinfo, MP_BUFFER_READ);
|
||||
}
|
||||
|
||||
return bluetooth_handle_errno(mp_bluetooth_gap_advertise_start(connectable, interval_us, adv_bufinfo.buf, adv_bufinfo.len, resp_bufinfo.buf, resp_bufinfo.len));
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(bluetooth_ble_gap_advertise_obj, 1, bluetooth_ble_gap_advertise);
|
||||
|
||||
STATIC int bluetooth_gatts_register_service(mp_obj_t uuid_in, mp_obj_t characteristics_in, uint16_t **handles, size_t *num_handles) {
|
||||
if (!mp_obj_is_type(uuid_in, &bluetooth_uuid_type)) {
|
||||
mp_raise_ValueError("invalid service UUID");
|
||||
}
|
||||
mp_obj_bluetooth_uuid_t *service_uuid = MP_OBJ_TO_PTR(uuid_in);
|
||||
|
||||
mp_obj_t len_in = mp_obj_len(characteristics_in);
|
||||
size_t len = mp_obj_get_int(len_in);
|
||||
mp_obj_iter_buf_t iter_buf;
|
||||
mp_obj_t iterable = mp_getiter(characteristics_in, &iter_buf);
|
||||
mp_obj_t characteristic_obj;
|
||||
|
||||
// Lists of characteristic uuids and flags.
|
||||
mp_obj_bluetooth_uuid_t **characteristic_uuids = m_new(mp_obj_bluetooth_uuid_t*, len);
|
||||
uint8_t *characteristic_flags = m_new(uint8_t, len);
|
||||
|
||||
// Flattened list of descriptor uuids and flags. Grows (realloc) as more descriptors are encountered.
|
||||
mp_obj_bluetooth_uuid_t **descriptor_uuids = NULL;
|
||||
uint8_t *descriptor_flags = NULL;
|
||||
// How many descriptors in the flattened list per characteristic.
|
||||
uint8_t *num_descriptors = m_new(uint8_t, len);
|
||||
|
||||
// Inititally allocate enough room for the number of characteristics.
|
||||
// Will be grown to accommodate descriptors as necessary.
|
||||
*num_handles = len;
|
||||
*handles = m_new(uint16_t, *num_handles);
|
||||
|
||||
// Extract out characteristic uuids & flags.
|
||||
|
||||
int characteristic_index = 0; // characteristic index.
|
||||
int handle_index = 0; // handle index.
|
||||
int descriptor_index = 0; // descriptor index.
|
||||
while ((characteristic_obj = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) {
|
||||
// (uuid, flags, (optional descriptors),)
|
||||
size_t characteristic_len;
|
||||
mp_obj_t *characteristic_items;
|
||||
mp_obj_get_array(characteristic_obj, &characteristic_len, &characteristic_items);
|
||||
|
||||
if (characteristic_len < 2 || characteristic_len > 3) {
|
||||
mp_raise_ValueError("invalid characteristic tuple");
|
||||
}
|
||||
mp_obj_t uuid_obj = characteristic_items[0];
|
||||
if (!mp_obj_is_type(uuid_obj, &bluetooth_uuid_type)) {
|
||||
mp_raise_ValueError("invalid characteristic UUID");
|
||||
}
|
||||
|
||||
(*handles)[handle_index++] = 0xffff;
|
||||
|
||||
// Optional third element, iterable of descriptors.
|
||||
if (characteristic_len >= 3) {
|
||||
mp_obj_t descriptors_len_in = mp_obj_len(characteristic_items[2]);
|
||||
num_descriptors[characteristic_index] = mp_obj_get_int(descriptors_len_in);
|
||||
|
||||
if (num_descriptors[characteristic_index] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Grow the flattened uuids and flags arrays with this many more descriptors.
|
||||
descriptor_uuids = m_renew(mp_obj_bluetooth_uuid_t*, descriptor_uuids, descriptor_index, descriptor_index + num_descriptors[characteristic_index]);
|
||||
descriptor_flags = m_renew(uint8_t, descriptor_flags, descriptor_index, descriptor_index + num_descriptors[characteristic_index]);
|
||||
|
||||
// Also grow the handles array.
|
||||
*handles = m_renew(uint16_t, *handles, *num_handles, *num_handles + num_descriptors[characteristic_index]);
|
||||
|
||||
mp_obj_iter_buf_t iter_buf_desc;
|
||||
mp_obj_t iterable_desc = mp_getiter(characteristic_items[2], &iter_buf_desc);
|
||||
mp_obj_t descriptor_obj;
|
||||
|
||||
// Extract out descriptors for this characteristic.
|
||||
while ((descriptor_obj = mp_iternext(iterable_desc)) != MP_OBJ_STOP_ITERATION) {
|
||||
// (uuid, flags,)
|
||||
mp_obj_t *descriptor_items;
|
||||
mp_obj_get_array_fixed_n(descriptor_obj, 2, &descriptor_items);
|
||||
mp_obj_t desc_uuid_obj = descriptor_items[0];
|
||||
if (!mp_obj_is_type(desc_uuid_obj, &bluetooth_uuid_type)) {
|
||||
mp_raise_ValueError("invalid descriptor UUID");
|
||||
}
|
||||
|
||||
descriptor_uuids[descriptor_index] = MP_OBJ_TO_PTR(desc_uuid_obj);
|
||||
descriptor_flags[descriptor_index] = mp_obj_get_int(descriptor_items[1]);
|
||||
++descriptor_index;
|
||||
|
||||
(*handles)[handle_index++] = 0xffff;
|
||||
}
|
||||
|
||||
// Reflect that we've grown the handles array.
|
||||
*num_handles += num_descriptors[characteristic_index];
|
||||
}
|
||||
|
||||
characteristic_uuids[characteristic_index] = MP_OBJ_TO_PTR(uuid_obj);
|
||||
characteristic_flags[characteristic_index] = mp_obj_get_int(characteristic_items[1]);
|
||||
++characteristic_index;
|
||||
}
|
||||
|
||||
// Add service.
|
||||
return mp_bluetooth_gatts_register_service(service_uuid, characteristic_uuids, characteristic_flags, descriptor_uuids, descriptor_flags, num_descriptors, *handles, len);
|
||||
}
|
||||
|
||||
STATIC mp_obj_t bluetooth_ble_gatts_register_services(mp_obj_t self_in, mp_obj_t services_in) {
|
||||
mp_obj_t len_in = mp_obj_len(services_in);
|
||||
size_t len = mp_obj_get_int(len_in);
|
||||
mp_obj_iter_buf_t iter_buf;
|
||||
mp_obj_t iterable = mp_getiter(services_in, &iter_buf);
|
||||
mp_obj_t service_tuple_obj;
|
||||
|
||||
mp_obj_tuple_t *result = mp_obj_new_tuple(len, NULL);
|
||||
|
||||
uint16_t **handles = m_new0(uint16_t*, len);
|
||||
size_t *num_handles = m_new0(size_t, len);
|
||||
|
||||
// TODO: Add a `append` kwarg (defaulting to False) to make this behavior optional.
|
||||
bool append = false;
|
||||
int err = mp_bluetooth_gatts_register_service_begin(append);
|
||||
if (err != 0) {
|
||||
return bluetooth_handle_errno(err);
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
while ((service_tuple_obj = mp_iternext(iterable)) != MP_OBJ_STOP_ITERATION) {
|
||||
// (uuid, chars)
|
||||
mp_obj_t *service_items;
|
||||
mp_obj_get_array_fixed_n(service_tuple_obj, 2, &service_items);
|
||||
err = bluetooth_gatts_register_service(service_items[0], service_items[1], &handles[i], &num_handles[i]);
|
||||
if (err != 0) {
|
||||
return bluetooth_handle_errno(err);
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
// On Nimble, this will actually perform the registration, making the handles valid.
|
||||
err = mp_bluetooth_gatts_register_service_end();
|
||||
if (err != 0) {
|
||||
return bluetooth_handle_errno(err);
|
||||
}
|
||||
|
||||
// Return tuple of tuple of value handles.
|
||||
// TODO: Also the Generic Access service characteristics?
|
||||
for (i = 0; i < len; ++i) {
|
||||
mp_obj_tuple_t *service_handles = mp_obj_new_tuple(num_handles[i], NULL);
|
||||
for (int j = 0; j < num_handles[i]; ++j) {
|
||||
service_handles->items[j] = MP_OBJ_NEW_SMALL_INT(handles[i][j]);
|
||||
}
|
||||
result->items[i] = MP_OBJ_FROM_PTR(service_handles);
|
||||
}
|
||||
return MP_OBJ_FROM_PTR(result);
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_ble_gatts_register_services_obj, bluetooth_ble_gatts_register_services);
|
||||
|
||||
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
||||
STATIC mp_obj_t bluetooth_ble_gap_connect(size_t n_args, const mp_obj_t *args) {
|
||||
uint8_t addr_type = mp_obj_get_int(args[1]);
|
||||
mp_buffer_info_t bufinfo = {0};
|
||||
mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_READ);
|
||||
if (bufinfo.len != 6) {
|
||||
mp_raise_ValueError("invalid addr");
|
||||
}
|
||||
mp_int_t scan_duration_ms = MP_BLUETOOTH_CONNECT_DEFAULT_SCAN_DURATION_MS;
|
||||
if (n_args == 4) {
|
||||
scan_duration_ms = mp_obj_get_int(args[3]);
|
||||
}
|
||||
|
||||
int err = mp_bluetooth_gap_peripheral_connect(addr_type, bufinfo.buf, scan_duration_ms);
|
||||
return bluetooth_handle_errno(err);
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gap_connect_obj, 3, 4, bluetooth_ble_gap_connect);
|
||||
|
||||
STATIC mp_obj_t bluetooth_ble_gap_scan(size_t n_args, const mp_obj_t *args) {
|
||||
// Default is indefinite scan, with the NimBLE "background scan" interval and window.
|
||||
mp_int_t duration_ms = 0;
|
||||
mp_int_t interval_us = 1280000;
|
||||
mp_int_t window_us = 11250;
|
||||
if (n_args > 1) {
|
||||
if (args[1] == mp_const_none) {
|
||||
// scan(None) --> stop scan.
|
||||
return bluetooth_handle_errno(mp_bluetooth_gap_scan_stop());
|
||||
}
|
||||
duration_ms = mp_obj_get_int(args[1]);
|
||||
if (n_args > 2) {
|
||||
interval_us = mp_obj_get_int(args[2]);
|
||||
if (n_args > 3) {
|
||||
window_us = mp_obj_get_int(args[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return bluetooth_handle_errno(mp_bluetooth_gap_scan_start(duration_ms, interval_us, window_us));
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gap_scan_obj, 1, 4, bluetooth_ble_gap_scan);
|
||||
#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
||||
|
||||
STATIC mp_obj_t bluetooth_ble_gap_disconnect(mp_obj_t self_in, mp_obj_t conn_handle_in) {
|
||||
uint16_t conn_handle = mp_obj_get_int(conn_handle_in);
|
||||
int err = mp_bluetooth_gap_disconnect(conn_handle);
|
||||
if (err == 0) {
|
||||
return mp_const_true;
|
||||
} else if (err == MP_ENOTCONN) {
|
||||
return mp_const_false;
|
||||
} else {
|
||||
return bluetooth_handle_errno(err);
|
||||
}
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_ble_gap_disconnect_obj, bluetooth_ble_gap_disconnect);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Bluetooth object: GATTS (Peripheral/Advertiser role)
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
STATIC mp_obj_t bluetooth_ble_gatts_read(mp_obj_t self_in, mp_obj_t value_handle_in) {
|
||||
size_t len = 0;
|
||||
uint8_t* buf;
|
||||
mp_bluetooth_gatts_read(mp_obj_get_int(value_handle_in), &buf, &len);
|
||||
return mp_obj_new_bytes(buf, len);
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_ble_gatts_read_obj, bluetooth_ble_gatts_read);
|
||||
|
||||
STATIC mp_obj_t bluetooth_ble_gatts_write(mp_obj_t self_in, mp_obj_t value_handle_in, mp_obj_t data) {
|
||||
mp_buffer_info_t bufinfo = {0};
|
||||
mp_get_buffer_raise(data, &bufinfo, MP_BUFFER_READ);
|
||||
int err = mp_bluetooth_gatts_write(mp_obj_get_int(value_handle_in), bufinfo.buf, bufinfo.len);
|
||||
if (err != 0) {
|
||||
mp_raise_OSError(err);
|
||||
}
|
||||
return MP_OBJ_NEW_SMALL_INT(bufinfo.len);
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_3(bluetooth_ble_gatts_write_obj, bluetooth_ble_gatts_write);
|
||||
|
||||
STATIC mp_obj_t bluetooth_ble_gatts_notify(size_t n_args, const mp_obj_t *args) {
|
||||
mp_int_t conn_handle = mp_obj_get_int(args[1]);
|
||||
mp_int_t value_handle = mp_obj_get_int(args[2]);
|
||||
|
||||
if (n_args == 4) {
|
||||
mp_buffer_info_t bufinfo = {0};
|
||||
mp_get_buffer_raise(args[3], &bufinfo, MP_BUFFER_READ);
|
||||
size_t len = bufinfo.len;
|
||||
int err = mp_bluetooth_gatts_notify_send(conn_handle, value_handle, bufinfo.buf, &len);
|
||||
if (err != 0) {
|
||||
mp_raise_OSError(err);
|
||||
}
|
||||
return MP_OBJ_NEW_SMALL_INT(len);
|
||||
} else {
|
||||
int err = mp_bluetooth_gatts_notify(conn_handle, value_handle);
|
||||
return bluetooth_handle_errno(err);
|
||||
}
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gatts_notify_obj, 3, 4, bluetooth_ble_gatts_notify);
|
||||
|
||||
STATIC mp_obj_t bluetooth_ble_gatts_set_buffer(size_t n_args, const mp_obj_t *args) {
|
||||
mp_int_t value_handle = mp_obj_get_int(args[1]);
|
||||
mp_int_t len = mp_obj_get_int(args[2]);
|
||||
bool append = n_args >= 4 && mp_obj_is_true(args[3]);
|
||||
return bluetooth_handle_errno(mp_bluetooth_gatts_set_buffer(value_handle, len, append));
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gatts_set_buffer_obj, 3, 4, bluetooth_ble_gatts_set_buffer);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Bluetooth object: GATTC (Central/Scanner role)
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
||||
|
||||
STATIC mp_obj_t bluetooth_ble_gattc_discover_services(mp_obj_t self_in, mp_obj_t conn_handle_in) {
|
||||
mp_int_t conn_handle = mp_obj_get_int(conn_handle_in);
|
||||
return bluetooth_handle_errno(mp_bluetooth_gattc_discover_primary_services(conn_handle));
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_2(bluetooth_ble_gattc_discover_services_obj, bluetooth_ble_gattc_discover_services);
|
||||
|
||||
STATIC mp_obj_t bluetooth_ble_gattc_discover_characteristics(size_t n_args, const mp_obj_t *args) {
|
||||
mp_int_t conn_handle = mp_obj_get_int(args[1]);
|
||||
mp_int_t start_handle = mp_obj_get_int(args[2]);
|
||||
mp_int_t end_handle = mp_obj_get_int(args[3]);
|
||||
return bluetooth_handle_errno(mp_bluetooth_gattc_discover_characteristics(conn_handle, start_handle, end_handle));
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gattc_discover_characteristics_obj, 4, 4, bluetooth_ble_gattc_discover_characteristics);
|
||||
|
||||
STATIC mp_obj_t bluetooth_ble_gattc_discover_descriptors(size_t n_args, const mp_obj_t *args) {
|
||||
mp_int_t conn_handle = mp_obj_get_int(args[1]);
|
||||
mp_int_t start_handle = mp_obj_get_int(args[2]);
|
||||
mp_int_t end_handle = mp_obj_get_int(args[3]);
|
||||
return bluetooth_handle_errno(mp_bluetooth_gattc_discover_descriptors(conn_handle, start_handle, end_handle));
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gattc_discover_descriptors_obj, 4, 4, bluetooth_ble_gattc_discover_descriptors);
|
||||
|
||||
STATIC mp_obj_t bluetooth_ble_gattc_read(mp_obj_t self_in, mp_obj_t conn_handle_in, mp_obj_t value_handle_in) {
|
||||
mp_int_t conn_handle = mp_obj_get_int(conn_handle_in);
|
||||
mp_int_t value_handle = mp_obj_get_int(value_handle_in);
|
||||
return bluetooth_handle_errno(mp_bluetooth_gattc_read(conn_handle, value_handle));
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_3(bluetooth_ble_gattc_read_obj, bluetooth_ble_gattc_read);
|
||||
|
||||
STATIC mp_obj_t bluetooth_ble_gattc_write(size_t n_args, const mp_obj_t *args) {
|
||||
mp_int_t conn_handle = mp_obj_get_int(args[1]);
|
||||
mp_int_t value_handle = mp_obj_get_int(args[2]);
|
||||
mp_obj_t data = args[3];
|
||||
mp_buffer_info_t bufinfo = {0};
|
||||
mp_get_buffer_raise(data, &bufinfo, MP_BUFFER_READ);
|
||||
size_t len = bufinfo.len;
|
||||
return bluetooth_handle_errno(mp_bluetooth_gattc_write(conn_handle, value_handle, bufinfo.buf, &len));
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(bluetooth_ble_gattc_write_obj, 4, 4, bluetooth_ble_gattc_write);
|
||||
|
||||
#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Bluetooth object: Definition
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
STATIC const mp_rom_map_elem_t bluetooth_ble_locals_dict_table[] = {
|
||||
// General
|
||||
{ MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&bluetooth_ble_active_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&bluetooth_ble_config_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&bluetooth_ble_irq_obj) },
|
||||
// GAP
|
||||
{ MP_ROM_QSTR(MP_QSTR_gap_advertise), MP_ROM_PTR(&bluetooth_ble_gap_advertise_obj) },
|
||||
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
||||
{ MP_ROM_QSTR(MP_QSTR_gap_connect), MP_ROM_PTR(&bluetooth_ble_gap_connect_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_gap_scan), MP_ROM_PTR(&bluetooth_ble_gap_scan_obj) },
|
||||
#endif
|
||||
{ MP_ROM_QSTR(MP_QSTR_gap_disconnect), MP_ROM_PTR(&bluetooth_ble_gap_disconnect_obj) },
|
||||
// GATT Server (i.e. peripheral/advertiser role)
|
||||
{ MP_ROM_QSTR(MP_QSTR_gatts_register_services), MP_ROM_PTR(&bluetooth_ble_gatts_register_services_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_gatts_read), MP_ROM_PTR(&bluetooth_ble_gatts_read_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_gatts_write), MP_ROM_PTR(&bluetooth_ble_gatts_write_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_gatts_notify), MP_ROM_PTR(&bluetooth_ble_gatts_notify_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_gatts_set_buffer), MP_ROM_PTR(&bluetooth_ble_gatts_set_buffer_obj) },
|
||||
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
||||
// GATT Client (i.e. central/scanner role)
|
||||
{ MP_ROM_QSTR(MP_QSTR_gattc_discover_services), MP_ROM_PTR(&bluetooth_ble_gattc_discover_services_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_gattc_discover_characteristics), MP_ROM_PTR(&bluetooth_ble_gattc_discover_characteristics_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_gattc_discover_descriptors), MP_ROM_PTR(&bluetooth_ble_gattc_discover_descriptors_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_gattc_read), MP_ROM_PTR(&bluetooth_ble_gattc_read_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_gattc_write), MP_ROM_PTR(&bluetooth_ble_gattc_write_obj) },
|
||||
#endif
|
||||
};
|
||||
STATIC MP_DEFINE_CONST_DICT(bluetooth_ble_locals_dict, bluetooth_ble_locals_dict_table);
|
||||
|
||||
STATIC const mp_obj_type_t bluetooth_ble_type = {
|
||||
{ &mp_type_type },
|
||||
.name = MP_QSTR_BLE,
|
||||
.make_new = bluetooth_ble_make_new,
|
||||
.locals_dict = (void*)&bluetooth_ble_locals_dict,
|
||||
};
|
||||
|
||||
STATIC const mp_rom_map_elem_t mp_module_bluetooth_globals_table[] = {
|
||||
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_ubluetooth) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_BLE), MP_ROM_PTR(&bluetooth_ble_type) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_UUID), MP_ROM_PTR(&bluetooth_uuid_type) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_FLAG_READ), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_READ) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_FLAG_WRITE), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_FLAG_NOTIFY), MP_ROM_INT(MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY) },
|
||||
};
|
||||
|
||||
STATIC MP_DEFINE_CONST_DICT(mp_module_bluetooth_globals, mp_module_bluetooth_globals_table);
|
||||
|
||||
const mp_obj_module_t mp_module_ubluetooth = {
|
||||
.base = { &mp_type_module },
|
||||
.globals = (mp_obj_dict_t*)&mp_module_bluetooth_globals,
|
||||
};
|
||||
|
||||
// Helpers
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
STATIC void ringbuf_extract(ringbuf_t* ringbuf, mp_obj_tuple_t *data_tuple, size_t n_u16, size_t n_u8, mp_obj_str_t *bytes_addr, size_t n_b, size_t n_i8, mp_obj_bluetooth_uuid_t *uuid, mp_obj_str_t *bytes_data) {
|
||||
assert(ringbuf_avail(ringbuf) >= n_u16 * 2 + n_u8 + (bytes_addr ? 6 : 0) + n_b + n_i8 + (uuid ? 1 : 0) + (bytes_data ? 1 : 0));
|
||||
int j = 0;
|
||||
|
||||
for (int i = 0; i < n_u16; ++i) {
|
||||
data_tuple->items[j++] = MP_OBJ_NEW_SMALL_INT(ringbuf_get16(ringbuf));
|
||||
}
|
||||
if (n_u8) {
|
||||
data_tuple->items[j++] = MP_OBJ_NEW_SMALL_INT(ringbuf_get(ringbuf));
|
||||
}
|
||||
if (bytes_addr) {
|
||||
bytes_addr->len = 6;
|
||||
for (int i = 0; i < bytes_addr->len; ++i) {
|
||||
// cast away const, this is actually bt->irq_addr_bytes.
|
||||
((uint8_t*)bytes_addr->data)[i] = ringbuf_get(ringbuf);
|
||||
}
|
||||
data_tuple->items[j++] = MP_OBJ_FROM_PTR(bytes_addr);
|
||||
}
|
||||
if (n_b) {
|
||||
data_tuple->items[j++] = mp_obj_new_bool(ringbuf_get(ringbuf));
|
||||
}
|
||||
if (n_i8) {
|
||||
// Note the int8_t got packed into the ringbuf as a uint8_t.
|
||||
data_tuple->items[j++] = MP_OBJ_NEW_SMALL_INT((int8_t)ringbuf_get(ringbuf));
|
||||
}
|
||||
if (uuid) {
|
||||
ringbuf_get_uuid(ringbuf, uuid);
|
||||
data_tuple->items[j++] = MP_OBJ_FROM_PTR(uuid);
|
||||
}
|
||||
// The code that enqueues into the ringbuf should ensure that it doesn't
|
||||
// put more than MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN bytes into
|
||||
// the ringbuf.
|
||||
if (bytes_data) {
|
||||
bytes_data->len = ringbuf_get(ringbuf);
|
||||
for (int i = 0; i < bytes_data->len; ++i) {
|
||||
// cast away const, this is actually bt->irq_data_bytes.
|
||||
((uint8_t*)bytes_data->data)[i] = ringbuf_get(ringbuf);
|
||||
}
|
||||
data_tuple->items[j++] = MP_OBJ_FROM_PTR(bytes_data);
|
||||
}
|
||||
|
||||
data_tuple->len = j;
|
||||
}
|
||||
|
||||
STATIC mp_obj_t bluetooth_ble_invoke_irq(mp_obj_t none_in) {
|
||||
// This is always executing in schedule context.
|
||||
for (;;) {
|
||||
MICROPY_PY_BLUETOOTH_ENTER
|
||||
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
|
||||
|
||||
mp_int_t event = event = ringbuf_get16(&o->ringbuf);
|
||||
if (event < 0) {
|
||||
// Nothing available in ringbuf.
|
||||
MICROPY_PY_BLUETOOTH_EXIT
|
||||
break;
|
||||
}
|
||||
|
||||
// Although we're in schedule context, this code still avoids using any allocations:
|
||||
// - IRQs are disabled (to protect the ringbuf), and we need to avoid triggering GC
|
||||
// - The user's handler might not alloc, so we shouldn't either.
|
||||
|
||||
mp_obj_t handler = handler = o->irq_handler;
|
||||
mp_obj_tuple_t *data_tuple = MP_OBJ_TO_PTR(o->irq_data_tuple);
|
||||
|
||||
// Some events need to pass bytes objects to their handler, using the
|
||||
// pre-allocated bytes array.
|
||||
mp_obj_str_t irq_data_bytes_addr = {{&mp_type_bytes}, 0, 6, o->irq_addr_bytes};
|
||||
mp_obj_str_t irq_data_bytes_data = {{&mp_type_bytes}, 0, 0, o->irq_data_bytes};
|
||||
|
||||
if (event == MP_BLUETOOTH_IRQ_CENTRAL_CONNECT || event == MP_BLUETOOTH_IRQ_PERIPHERAL_CONNECT || event == MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT || event == MP_BLUETOOTH_IRQ_PERIPHERAL_DISCONNECT) {
|
||||
// conn_handle, addr_type, addr
|
||||
ringbuf_extract(&o->ringbuf, data_tuple, 1, 1, &irq_data_bytes_addr, 0, 0, NULL, NULL);
|
||||
} else if (event == MP_BLUETOOTH_IRQ_GATTS_WRITE) {
|
||||
// conn_handle, value_handle
|
||||
ringbuf_extract(&o->ringbuf, data_tuple, 2, 0, NULL, 0, 0, NULL, NULL);
|
||||
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
||||
} else if (event == MP_BLUETOOTH_IRQ_SCAN_RESULT) {
|
||||
// addr_type, addr, connectable, rssi, adv_data
|
||||
ringbuf_extract(&o->ringbuf, data_tuple, 0, 1, &irq_data_bytes_addr, 1, 1, NULL, &irq_data_bytes_data);
|
||||
} else if (event == MP_BLUETOOTH_IRQ_SCAN_COMPLETE) {
|
||||
// No params required.
|
||||
data_tuple->len = 0;
|
||||
} else if (event == MP_BLUETOOTH_IRQ_GATTC_SERVICE_RESULT) {
|
||||
// conn_handle, start_handle, end_handle, uuid
|
||||
ringbuf_extract(&o->ringbuf, data_tuple, 3, 0, NULL, 0, 0, MP_OBJ_TO_PTR(o->irq_data_uuid), NULL);
|
||||
} else if (event == MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_RESULT) {
|
||||
// conn_handle, def_handle, value_handle, properties, uuid
|
||||
ringbuf_extract(&o->ringbuf, data_tuple, 3, 1, NULL, 0, 0, MP_OBJ_TO_PTR(o->irq_data_uuid), NULL);
|
||||
} else if (event == MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_RESULT) {
|
||||
// conn_handle, handle, uuid
|
||||
ringbuf_extract(&o->ringbuf, data_tuple, 2, 0, NULL, 0, 0, MP_OBJ_TO_PTR(o->irq_data_uuid), NULL);
|
||||
} else if (event == MP_BLUETOOTH_IRQ_GATTC_READ_RESULT || event == MP_BLUETOOTH_IRQ_GATTC_NOTIFY || event == MP_BLUETOOTH_IRQ_GATTC_INDICATE) {
|
||||
// conn_handle, value_handle, data
|
||||
ringbuf_extract(&o->ringbuf, data_tuple, 2, 0, NULL, 0, 0, NULL, &irq_data_bytes_data);
|
||||
} else if (event == MP_BLUETOOTH_IRQ_GATTC_WRITE_STATUS) {
|
||||
// conn_handle, value_handle, status
|
||||
ringbuf_extract(&o->ringbuf, data_tuple, 3, 0, NULL, 0, 0, NULL, NULL);
|
||||
#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
||||
}
|
||||
|
||||
MICROPY_PY_BLUETOOTH_EXIT
|
||||
|
||||
mp_call_function_2(handler, MP_OBJ_NEW_SMALL_INT(event), MP_OBJ_FROM_PTR(data_tuple));
|
||||
}
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_1(bluetooth_ble_invoke_irq_obj, bluetooth_ble_invoke_irq);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Port API
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Callbacks are called in interrupt context (i.e. can't allocate), so we need to push the data
|
||||
// into the ringbuf and schedule the callback via mp_sched_schedule.
|
||||
|
||||
STATIC bool enqueue_irq(mp_obj_bluetooth_ble_t *o, size_t len, uint16_t event, bool *sched) {
|
||||
*sched = false;
|
||||
|
||||
if (o && ringbuf_free(&o->ringbuf) >= len + 2 && (o->irq_trigger & event) && o->irq_handler != mp_const_none) {
|
||||
*sched = ringbuf_avail(&o->ringbuf) == 0;
|
||||
ringbuf_put16(&o->ringbuf, event);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
STATIC void schedule_ringbuf(bool sched) {
|
||||
if (sched) {
|
||||
mp_sched_schedule(MP_OBJ_FROM_PTR(MP_ROM_PTR(&bluetooth_ble_invoke_irq_obj)), mp_const_none);
|
||||
}
|
||||
}
|
||||
|
||||
void mp_bluetooth_gap_on_connected_disconnected(uint16_t event, uint16_t conn_handle, uint8_t addr_type, const uint8_t *addr) {
|
||||
MICROPY_PY_BLUETOOTH_ENTER
|
||||
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
|
||||
bool sched;
|
||||
if (enqueue_irq(o, 2 + 1 + 6, event, &sched)) {
|
||||
ringbuf_put16(&o->ringbuf, conn_handle);
|
||||
ringbuf_put(&o->ringbuf, addr_type);
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
ringbuf_put(&o->ringbuf, addr[i]);
|
||||
}
|
||||
}
|
||||
MICROPY_PY_BLUETOOTH_EXIT
|
||||
schedule_ringbuf(sched);
|
||||
}
|
||||
|
||||
void mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle) {
|
||||
MICROPY_PY_BLUETOOTH_ENTER
|
||||
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
|
||||
bool sched;
|
||||
if (enqueue_irq(o, 2 + 2, MP_BLUETOOTH_IRQ_GATTS_WRITE, &sched)) {
|
||||
ringbuf_put16(&o->ringbuf, conn_handle);
|
||||
ringbuf_put16(&o->ringbuf, value_handle);
|
||||
}
|
||||
MICROPY_PY_BLUETOOTH_EXIT
|
||||
schedule_ringbuf(sched);
|
||||
}
|
||||
|
||||
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
||||
void mp_bluetooth_gap_on_scan_complete(void) {
|
||||
MICROPY_PY_BLUETOOTH_ENTER
|
||||
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
|
||||
bool sched;
|
||||
if (enqueue_irq(o, 0, MP_BLUETOOTH_IRQ_SCAN_COMPLETE, &sched)) {
|
||||
}
|
||||
MICROPY_PY_BLUETOOTH_EXIT
|
||||
schedule_ringbuf(sched);
|
||||
}
|
||||
|
||||
void mp_bluetooth_gap_on_scan_result(uint8_t addr_type, const uint8_t *addr, bool connectable, const int8_t rssi, const uint8_t *data, size_t data_len) {
|
||||
MICROPY_PY_BLUETOOTH_ENTER
|
||||
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
|
||||
bool sched;
|
||||
data_len = MIN(MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN, data_len);
|
||||
if (enqueue_irq(o, 1 + 6 + 1 + 1 + 1 + data_len, MP_BLUETOOTH_IRQ_SCAN_RESULT, &sched)) {
|
||||
ringbuf_put(&o->ringbuf, addr_type);
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
ringbuf_put(&o->ringbuf, addr[i]);
|
||||
}
|
||||
ringbuf_put(&o->ringbuf, connectable ? 1 : 0);
|
||||
// Note conversion of int8_t rssi to uint8_t. Must un-convert on the way out.
|
||||
ringbuf_put(&o->ringbuf, (uint8_t)rssi);
|
||||
ringbuf_put(&o->ringbuf, data_len);
|
||||
for (int i = 0; i < data_len; ++i) {
|
||||
ringbuf_put(&o->ringbuf, data[i]);
|
||||
}
|
||||
}
|
||||
MICROPY_PY_BLUETOOTH_EXIT
|
||||
schedule_ringbuf(sched);
|
||||
}
|
||||
|
||||
void mp_bluetooth_gattc_on_primary_service_result(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle, mp_obj_bluetooth_uuid_t *service_uuid) {
|
||||
MICROPY_PY_BLUETOOTH_ENTER
|
||||
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
|
||||
bool sched;
|
||||
if (enqueue_irq(o, 2 + 2 + 2 + 1 + service_uuid->type, MP_BLUETOOTH_IRQ_GATTC_SERVICE_RESULT, &sched)) {
|
||||
ringbuf_put16(&o->ringbuf, conn_handle);
|
||||
ringbuf_put16(&o->ringbuf, start_handle);
|
||||
ringbuf_put16(&o->ringbuf, end_handle);
|
||||
ringbuf_put_uuid(&o->ringbuf, service_uuid);
|
||||
}
|
||||
MICROPY_PY_BLUETOOTH_EXIT
|
||||
schedule_ringbuf(sched);
|
||||
}
|
||||
|
||||
void mp_bluetooth_gattc_on_characteristic_result(uint16_t conn_handle, uint16_t def_handle, uint16_t value_handle, uint8_t properties, mp_obj_bluetooth_uuid_t *characteristic_uuid) {
|
||||
MICROPY_PY_BLUETOOTH_ENTER
|
||||
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
|
||||
bool sched;
|
||||
if (enqueue_irq(o, 2 + 2 + 2 + 1 + characteristic_uuid->type, MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_RESULT, &sched)) {
|
||||
ringbuf_put16(&o->ringbuf, conn_handle);
|
||||
ringbuf_put16(&o->ringbuf, def_handle);
|
||||
ringbuf_put16(&o->ringbuf, value_handle);
|
||||
ringbuf_put(&o->ringbuf, properties);
|
||||
ringbuf_put_uuid(&o->ringbuf, characteristic_uuid);
|
||||
}
|
||||
MICROPY_PY_BLUETOOTH_EXIT
|
||||
schedule_ringbuf(sched);
|
||||
}
|
||||
|
||||
void mp_bluetooth_gattc_on_descriptor_result(uint16_t conn_handle, uint16_t handle, mp_obj_bluetooth_uuid_t *descriptor_uuid) {
|
||||
MICROPY_PY_BLUETOOTH_ENTER
|
||||
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
|
||||
bool sched;
|
||||
if (enqueue_irq(o, 2 + 2 + 1 + descriptor_uuid->type, MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_RESULT, &sched)) {
|
||||
ringbuf_put16(&o->ringbuf, conn_handle);
|
||||
ringbuf_put16(&o->ringbuf, handle);
|
||||
ringbuf_put_uuid(&o->ringbuf, descriptor_uuid);
|
||||
}
|
||||
MICROPY_PY_BLUETOOTH_EXIT
|
||||
schedule_ringbuf(sched);
|
||||
}
|
||||
|
||||
void mp_bluetooth_gattc_on_data_available(uint16_t event, uint16_t conn_handle, uint16_t value_handle, const uint8_t *data, size_t data_len) {
|
||||
MICROPY_PY_BLUETOOTH_ENTER
|
||||
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
|
||||
bool sched;
|
||||
data_len = MIN(MICROPY_PY_BLUETOOTH_MAX_EVENT_DATA_BYTES_LEN, data_len);
|
||||
if (enqueue_irq(o, 2 + 2 + 1 + data_len, event, &sched)) {
|
||||
ringbuf_put16(&o->ringbuf, conn_handle);
|
||||
ringbuf_put16(&o->ringbuf, value_handle);
|
||||
ringbuf_put(&o->ringbuf, data_len);
|
||||
for (int i = 0; i < data_len; ++i) {
|
||||
ringbuf_put(&o->ringbuf, data[i]);
|
||||
}
|
||||
}
|
||||
MICROPY_PY_BLUETOOTH_EXIT
|
||||
schedule_ringbuf(sched);
|
||||
}
|
||||
|
||||
void mp_bluetooth_gattc_on_write_status(uint16_t conn_handle, uint16_t value_handle, uint16_t status) {
|
||||
MICROPY_PY_BLUETOOTH_ENTER
|
||||
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
|
||||
bool sched;
|
||||
if (enqueue_irq(o, 2 + 2 + 2, MP_BLUETOOTH_IRQ_GATTC_WRITE_STATUS, &sched)) {
|
||||
ringbuf_put16(&o->ringbuf, conn_handle);
|
||||
ringbuf_put16(&o->ringbuf, value_handle);
|
||||
ringbuf_put16(&o->ringbuf, status);
|
||||
}
|
||||
MICROPY_PY_BLUETOOTH_EXIT
|
||||
schedule_ringbuf(sched);
|
||||
}
|
||||
|
||||
#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
||||
|
||||
#if MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK
|
||||
// This can only be enabled when the thread invoking this is a MicroPython thread.
|
||||
// On ESP32, for example, this is not the case.
|
||||
bool mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_handle) {
|
||||
mp_obj_bluetooth_ble_t *o = MP_OBJ_TO_PTR(MP_STATE_VM(bluetooth));
|
||||
if ((o->irq_trigger & MP_BLUETOOTH_IRQ_GATTS_READ_REQUEST) && o->irq_handler != mp_const_none) {
|
||||
// Use pre-allocated tuple because this is a hard IRQ.
|
||||
mp_obj_tuple_t *data = MP_OBJ_FROM_PTR(o->irq_data_tuple);
|
||||
data->items[0] = MP_OBJ_NEW_SMALL_INT(conn_handle);
|
||||
data->items[1] = MP_OBJ_NEW_SMALL_INT(value_handle);
|
||||
data->len = 2;
|
||||
mp_obj_t irq_ret = mp_call_function_2_protected(o->irq_handler, MP_OBJ_NEW_SMALL_INT(MP_BLUETOOTH_IRQ_GATTS_READ_REQUEST), o->irq_data_tuple);
|
||||
// If the IRQ handler explicitly returned false, then deny the read. Otherwise if it returns None/True, allow it.
|
||||
return irq_ret != MP_OBJ_NULL && (irq_ret == mp_const_none || mp_obj_is_true(irq_ret));
|
||||
} else {
|
||||
// No IRQ handler, allow the read.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // MICROPY_PY_BLUETOOTH
|
||||
256
extmod/modbluetooth.h
Normal file
256
extmod/modbluetooth.h
Normal file
@ -0,0 +1,256 @@
|
||||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2018 Ayke van Laethem
|
||||
* Copyright (c) 2019 Jim Mussared
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef MICROPY_INCLUDED_EXTMOD_MODBLUETOOTH_H
|
||||
#define MICROPY_INCLUDED_EXTMOD_MODBLUETOOTH_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "py/obj.h"
|
||||
#include "py/objlist.h"
|
||||
#include "py/ringbuf.h"
|
||||
|
||||
// Port specific configuration.
|
||||
#ifndef MICROPY_PY_BLUETOOTH_RINGBUF_SIZE
|
||||
#define MICROPY_PY_BLUETOOTH_RINGBUF_SIZE (128)
|
||||
#endif
|
||||
|
||||
#ifndef MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
||||
#define MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE (0)
|
||||
#endif
|
||||
|
||||
#ifndef MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK
|
||||
#define MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK (0)
|
||||
#endif
|
||||
|
||||
// Common constants.
|
||||
#ifndef MP_BLUETOOTH_MAX_ATTR_SIZE
|
||||
#define MP_BLUETOOTH_MAX_ATTR_SIZE (20)
|
||||
#endif
|
||||
|
||||
// Advertisement packet lengths
|
||||
#define MP_BLUETOOTH_GAP_ADV_MAX_LEN (32)
|
||||
|
||||
#define MP_BLUETOOTH_CHARACTERISTIC_FLAG_READ (1 << 1)
|
||||
#define MP_BLUETOOTH_CHARACTERISTIC_FLAG_WRITE (1 << 3)
|
||||
#define MP_BLUETOOTH_CHARACTERISTIC_FLAG_NOTIFY (1 << 4)
|
||||
|
||||
// Type value also doubles as length.
|
||||
#define MP_BLUETOOTH_UUID_TYPE_16 (2)
|
||||
#define MP_BLUETOOTH_UUID_TYPE_32 (4)
|
||||
#define MP_BLUETOOTH_UUID_TYPE_128 (16)
|
||||
|
||||
// Address types (for the addr_type params).
|
||||
// Ports will need to map these to their own values.
|
||||
#define MP_BLUETOOTH_ADDR_PUBLIC (0x00) // Public (identity) address. (Same as NimBLE and NRF SD)
|
||||
#define MP_BLUETOOTH_ADDR_RANDOM_STATIC (0x01) // Random static (identity) address. (Same as NimBLE and NRF SD)
|
||||
#define MP_BLUETOOTH_ADDR_PUBLIC_ID (0x02) // (Same as NimBLE)
|
||||
#define MP_BLUETOOTH_ADDR_RANDOM_ID (0x03) // (Same as NimBLE)
|
||||
#define MP_BLUETOOTH_ADDR_RANDOM_PRIVATE_RESOLVABLE (0x12) // Random private resolvable address. (NRF SD 0x02)
|
||||
#define MP_BLUETOOTH_ADDR_RANDOM_PRIVATE_NON_RESOLVABLE (0x13) // Random private non-resolvable address. (NRF SD 0x03)
|
||||
|
||||
// Event codes for the IRQ handler.
|
||||
// Can also be combined to pass to the trigger param to select which events you
|
||||
// are interested in.
|
||||
// Note this is currently stored in a uint16_t (in irq_trigger, and the event
|
||||
// arg to the irq handler), so one spare value remaining.
|
||||
#define MP_BLUETOOTH_IRQ_CENTRAL_CONNECT (1 << 0)
|
||||
#define MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT (1 << 1)
|
||||
#define MP_BLUETOOTH_IRQ_GATTS_WRITE (1 << 2)
|
||||
#define MP_BLUETOOTH_IRQ_GATTS_READ_REQUEST (1 << 3)
|
||||
#define MP_BLUETOOTH_IRQ_SCAN_RESULT (1 << 4)
|
||||
#define MP_BLUETOOTH_IRQ_SCAN_COMPLETE (1 << 5)
|
||||
#define MP_BLUETOOTH_IRQ_PERIPHERAL_CONNECT (1 << 6)
|
||||
#define MP_BLUETOOTH_IRQ_PERIPHERAL_DISCONNECT (1 << 7)
|
||||
#define MP_BLUETOOTH_IRQ_GATTC_SERVICE_RESULT (1 << 8)
|
||||
#define MP_BLUETOOTH_IRQ_GATTC_CHARACTERISTIC_RESULT (1 << 9)
|
||||
#define MP_BLUETOOTH_IRQ_GATTC_DESCRIPTOR_RESULT (1 << 10)
|
||||
#define MP_BLUETOOTH_IRQ_GATTC_READ_RESULT (1 << 11)
|
||||
#define MP_BLUETOOTH_IRQ_GATTC_WRITE_STATUS (1 << 12)
|
||||
#define MP_BLUETOOTH_IRQ_GATTC_NOTIFY (1 << 13)
|
||||
#define MP_BLUETOOTH_IRQ_GATTC_INDICATE (1 << 14)
|
||||
#define MP_BLUETOOTH_IRQ_ALL (0xffff)
|
||||
|
||||
/*
|
||||
These aren't included in the module for space reasons, but can be used
|
||||
in your Python code if necessary.
|
||||
|
||||
from micropython import const
|
||||
_IRQ_CENTRAL_CONNECT = const(1 << 0)
|
||||
_IRQ_CENTRAL_DISCONNECT = const(1 << 1)
|
||||
_IRQ_GATTS_WRITE = const(1 << 2)
|
||||
_IRQ_GATTS_READ_REQUEST = const(1 << 3)
|
||||
_IRQ_SCAN_RESULT = const(1 << 4)
|
||||
_IRQ_SCAN_COMPLETE = const(1 << 5)
|
||||
_IRQ_PERIPHERAL_CONNECT = const(1 << 6)
|
||||
_IRQ_PERIPHERAL_DISCONNECT = const(1 << 7)
|
||||
_IRQ_GATTC_SERVICE_RESULT = const(1 << 8)
|
||||
_IRQ_GATTC_CHARACTERISTIC_RESULT = const(1 << 9)
|
||||
_IRQ_GATTC_DESCRIPTOR_RESULT = const(1 << 10)
|
||||
_IRQ_GATTC_READ_RESULT = const(1 << 11)
|
||||
_IRQ_GATTC_WRITE_STATUS = const(1 << 12)
|
||||
_IRQ_GATTC_NOTIFY = const(1 << 13)
|
||||
_IRQ_GATTC_INDICATE = const(1 << 14)
|
||||
_IRQ_ALL = const(0xffff)
|
||||
*/
|
||||
|
||||
// Common UUID type.
|
||||
// Ports are expected to map this to their own internal UUID types.
|
||||
// Internally the UUID data is little-endian, but the user should only
|
||||
// ever see this if they use the buffer protocol, e.g. in order to
|
||||
// construct an advertising payload (which needs to be in LE).
|
||||
// Both the constructor and the print function work in BE.
|
||||
typedef struct {
|
||||
mp_obj_base_t base;
|
||||
uint8_t type;
|
||||
uint8_t data[16];
|
||||
} mp_obj_bluetooth_uuid_t;
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// API implemented by ports (i.e. called from modbluetooth.c):
|
||||
|
||||
// TODO: At the moment this only allows for a single `Bluetooth` instance to be created.
|
||||
// Ideally in the future we'd be able to have multiple instances or to select a specific BT driver or HCI UART.
|
||||
// So these global methods should be replaced with a struct of function pointers (like the machine.I2C implementations).
|
||||
|
||||
// Any method returning an int returns errno on failure, otherwise zero.
|
||||
|
||||
// Note: All methods dealing with addresses (as 6-byte uint8 pointers) are in big-endian format.
|
||||
// (i.e. the same way they would be printed on a device sticker or in a UI), so the user sees
|
||||
// addresses in a way that looks like what they'd expect.
|
||||
// This means that the lower level implementation will likely need to reorder them (e.g. Nimble
|
||||
// works in little-endian, as does BLE itself).
|
||||
|
||||
// Enables the Bluetooth stack.
|
||||
int mp_bluetooth_init(void);
|
||||
|
||||
// Disables the Bluetooth stack. Is a no-op when not enabled.
|
||||
void mp_bluetooth_deinit(void);
|
||||
|
||||
// Returns true when the Bluetooth stack is enabled.
|
||||
bool mp_bluetooth_is_enabled(void);
|
||||
|
||||
// Gets the MAC addr of this device in big-endian format.
|
||||
void mp_bluetooth_get_device_addr(uint8_t *addr);
|
||||
|
||||
// Start advertisement. Will re-start advertisement when already enabled.
|
||||
// Returns errno on failure.
|
||||
int mp_bluetooth_gap_advertise_start(bool connectable, int32_t interval_us, const uint8_t *adv_data, size_t adv_data_len, const uint8_t *sr_data, size_t sr_data_len);
|
||||
|
||||
// Stop advertisement. No-op when already stopped.
|
||||
void mp_bluetooth_gap_advertise_stop(void);
|
||||
|
||||
// Start adding services. Must be called before mp_bluetooth_register_service.
|
||||
int mp_bluetooth_gatts_register_service_begin(bool append);
|
||||
// // Add a service with the given list of characteristics to the queue to be registered.
|
||||
// The value_handles won't be valid until after mp_bluetooth_register_service_end is called.
|
||||
int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, mp_obj_bluetooth_uuid_t **characteristic_uuids, uint8_t *characteristic_flags, mp_obj_bluetooth_uuid_t **descriptor_uuids, uint8_t *descriptor_flags, uint8_t *num_descriptors, uint16_t *handles, size_t num_characteristics);
|
||||
// Register any queued services.
|
||||
int mp_bluetooth_gatts_register_service_end();
|
||||
|
||||
// Read the value from the local gatts db (likely this has been written by a central).
|
||||
int mp_bluetooth_gatts_read(uint16_t value_handle, uint8_t **value, size_t *value_len);
|
||||
// Write a value to the local gatts db (ready to be queried by a central).
|
||||
int mp_bluetooth_gatts_write(uint16_t value_handle, const uint8_t *value, size_t value_len);
|
||||
// Notify the central that it should do a read.
|
||||
int mp_bluetooth_gatts_notify(uint16_t conn_handle, uint16_t value_handle);
|
||||
// Notify the central, including a data payload. (Note: does not set the gatts db value).
|
||||
int mp_bluetooth_gatts_notify_send(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t *value_len);
|
||||
// Indicate the central.
|
||||
int mp_bluetooth_gatts_indicate(uint16_t conn_handle, uint16_t value_handle);
|
||||
|
||||
// Resize and enable/disable append-mode on a value.
|
||||
// Append-mode means that remote writes will append and local reads will clear after reading.
|
||||
int mp_bluetooth_gatts_set_buffer(uint16_t value_handle, size_t len, bool append);
|
||||
|
||||
// Disconnect from a central or peripheral.
|
||||
int mp_bluetooth_gap_disconnect(uint16_t conn_handle);
|
||||
|
||||
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
||||
// Start a discovery (scan). Set duration to zero to run continuously.
|
||||
int mp_bluetooth_gap_scan_start(int32_t duration_ms, int32_t interval_us, int32_t window_us);
|
||||
|
||||
// Stop discovery (if currently active).
|
||||
int mp_bluetooth_gap_scan_stop(void);
|
||||
|
||||
// Connect to a found peripheral.
|
||||
int mp_bluetooth_gap_peripheral_connect(uint8_t addr_type, const uint8_t *addr, int32_t duration_ms);
|
||||
|
||||
// Find all primary services on the connected peripheral.
|
||||
int mp_bluetooth_gattc_discover_primary_services(uint16_t conn_handle);
|
||||
|
||||
// Find all characteristics on the specified service on a connected peripheral.
|
||||
int mp_bluetooth_gattc_discover_characteristics(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle);
|
||||
|
||||
// Find all descriptors on the specified characteristic on a connected peripheral.
|
||||
int mp_bluetooth_gattc_discover_descriptors(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle);
|
||||
|
||||
// Initiate read of a value from the remote peripheral.
|
||||
int mp_bluetooth_gattc_read(uint16_t conn_handle, uint16_t value_handle);
|
||||
|
||||
// Write the value to the remote peripheral.
|
||||
int mp_bluetooth_gattc_write(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t *value_len);
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// API implemented by modbluetooth (called by port-specific implementations):
|
||||
|
||||
// Notify modbluetooth that a connection/disconnection event has occurred.
|
||||
void mp_bluetooth_gap_on_connected_disconnected(uint16_t event, uint16_t conn_handle, uint8_t addr_type, const uint8_t *addr);
|
||||
|
||||
// Call this when a characteristic is written to.
|
||||
void mp_bluetooth_gatts_on_write(uint16_t conn_handle, uint16_t value_handle);
|
||||
|
||||
#if MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK
|
||||
// Call this when a characteristic is read from. Return false to deny the read.
|
||||
bool mp_bluetooth_gatts_on_read_request(uint16_t conn_handle, uint16_t value_handle);
|
||||
#endif
|
||||
|
||||
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
||||
// Notify modbluetooth that scan has finished, either timeout, manually, or by some other action (e.g. connecting).
|
||||
void mp_bluetooth_gap_on_scan_complete(void);
|
||||
|
||||
// Notify modbluetooth of a scan result.
|
||||
void mp_bluetooth_gap_on_scan_result(uint8_t addr_type, const uint8_t *addr, bool connectable, const int8_t rssi, const uint8_t *data, size_t data_len);
|
||||
|
||||
// Notify modbluetooth that a service was found (either by discover-all, or discover-by-uuid).
|
||||
void mp_bluetooth_gattc_on_primary_service_result(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle, mp_obj_bluetooth_uuid_t *service_uuid);
|
||||
|
||||
// Notify modbluetooth that a characteristic was found (either by discover-all-on-service, or discover-by-uuid-on-service).
|
||||
void mp_bluetooth_gattc_on_characteristic_result(uint16_t conn_handle, uint16_t def_handle, uint16_t value_handle, uint8_t properties, mp_obj_bluetooth_uuid_t *characteristic_uuid);
|
||||
|
||||
// Notify modbluetooth that a descriptor was found.
|
||||
void mp_bluetooth_gattc_on_descriptor_result(uint16_t conn_handle, uint16_t handle, mp_obj_bluetooth_uuid_t *descriptor_uuid);
|
||||
|
||||
// Notify modbluetooth that a read has completed with data (or notify/indicate data available, use `event` to disambiguate).
|
||||
void mp_bluetooth_gattc_on_data_available(uint16_t event, uint16_t conn_handle, uint16_t value_handle, const uint8_t *data, size_t data_len);
|
||||
|
||||
// Notify modbluetooth that a write has completed.
|
||||
void mp_bluetooth_gattc_on_write_status(uint16_t conn_handle, uint16_t value_handle, uint16_t status);
|
||||
#endif
|
||||
|
||||
#endif // MICROPY_INCLUDED_EXTMOD_MODBLUETOOTH_H
|
||||
818
extmod/modbluetooth_nimble.c
Normal file
818
extmod/modbluetooth_nimble.c
Normal file
@ -0,0 +1,818 @@
|
||||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Damien P. George
|
||||
* Copyright (c) 2019 Jim Mussared
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "py/runtime.h"
|
||||
#include "py/mperrno.h"
|
||||
#include "py/mphal.h"
|
||||
|
||||
#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE
|
||||
|
||||
#include "modbluetooth_nimble.h"
|
||||
#include "modbluetooth.h"
|
||||
|
||||
#include "host/ble_hs.h"
|
||||
#include "host/util/util.h"
|
||||
#include "nimble/ble.h"
|
||||
#include "nimble/nimble_port.h"
|
||||
#include "services/gap/ble_svc_gap.h"
|
||||
|
||||
#ifndef MICROPY_PY_BLUETOOTH_DEFAULT_NAME
|
||||
#define MICROPY_PY_BLUETOOTH_DEFAULT_NAME "PYBD"
|
||||
#endif
|
||||
|
||||
#define DEBUG_EVENT_printf(...) //printf(__VA_ARGS__)
|
||||
|
||||
STATIC int8_t ble_hs_err_to_errno_table[] = {
|
||||
[BLE_HS_EAGAIN] = MP_EAGAIN,
|
||||
[BLE_HS_EALREADY] = MP_EALREADY,
|
||||
[BLE_HS_EINVAL] = MP_EINVAL,
|
||||
[BLE_HS_EMSGSIZE] = MP_EIO,
|
||||
[BLE_HS_ENOENT] = MP_ENOENT,
|
||||
[BLE_HS_ENOMEM] = MP_ENOMEM,
|
||||
[BLE_HS_ENOTCONN] = MP_ENOTCONN,
|
||||
[BLE_HS_ENOTSUP] = MP_EOPNOTSUPP,
|
||||
[BLE_HS_EAPP] = MP_EIO,
|
||||
[BLE_HS_EBADDATA] = MP_EIO,
|
||||
[BLE_HS_EOS] = MP_EIO,
|
||||
[BLE_HS_ECONTROLLER] = MP_EIO,
|
||||
[BLE_HS_ETIMEOUT] = MP_ETIMEDOUT,
|
||||
[BLE_HS_EDONE] = MP_EIO, // TODO: Maybe should be MP_EISCONN (connect uses this for "already connected").
|
||||
[BLE_HS_EBUSY] = MP_EBUSY,
|
||||
[BLE_HS_EREJECT] = MP_EIO,
|
||||
[BLE_HS_EUNKNOWN] = MP_EIO,
|
||||
[BLE_HS_EROLE] = MP_EIO,
|
||||
[BLE_HS_ETIMEOUT_HCI] = MP_EIO,
|
||||
[BLE_HS_ENOMEM_EVT] = MP_EIO,
|
||||
[BLE_HS_ENOADDR] = MP_EIO,
|
||||
[BLE_HS_ENOTSYNCED] = MP_EIO,
|
||||
[BLE_HS_EAUTHEN] = MP_EIO,
|
||||
[BLE_HS_EAUTHOR] = MP_EIO,
|
||||
[BLE_HS_EENCRYPT] = MP_EIO,
|
||||
[BLE_HS_EENCRYPT_KEY_SZ] = MP_EIO,
|
||||
[BLE_HS_ESTORE_CAP] = MP_EIO,
|
||||
[BLE_HS_ESTORE_FAIL] = MP_EIO,
|
||||
[BLE_HS_EPREEMPTED] = MP_EIO,
|
||||
[BLE_HS_EDISABLED] = MP_EIO,
|
||||
};
|
||||
|
||||
STATIC int ble_hs_err_to_errno(int err) {
|
||||
if (0 <= err && err < MP_ARRAY_SIZE(ble_hs_err_to_errno_table)) {
|
||||
return ble_hs_err_to_errno_table[err];
|
||||
} else {
|
||||
return MP_EIO;
|
||||
}
|
||||
}
|
||||
|
||||
// Note: modbluetooth UUIDs store their data in LE.
|
||||
STATIC ble_uuid_t* create_nimble_uuid(const mp_obj_bluetooth_uuid_t *uuid) {
|
||||
if (uuid->type == MP_BLUETOOTH_UUID_TYPE_16) {
|
||||
ble_uuid16_t *result = m_new(ble_uuid16_t, 1);
|
||||
result->u.type = BLE_UUID_TYPE_16;
|
||||
result->value = (uuid->data[1] << 8) | uuid->data[0];
|
||||
return (ble_uuid_t*)result;
|
||||
} else if (uuid->type == MP_BLUETOOTH_UUID_TYPE_32) {
|
||||
ble_uuid32_t *result = m_new(ble_uuid32_t, 1);
|
||||
result->u.type = BLE_UUID_TYPE_32;
|
||||
result->value = (uuid->data[1] << 24) | (uuid->data[1] << 16) | (uuid->data[1] << 8) | uuid->data[0];
|
||||
return (ble_uuid_t*)result;
|
||||
} else if (uuid->type == MP_BLUETOOTH_UUID_TYPE_128) {
|
||||
ble_uuid128_t *result = m_new(ble_uuid128_t, 1);
|
||||
result->u.type = BLE_UUID_TYPE_128;
|
||||
memcpy(result->value, uuid->data, 16);
|
||||
return (ble_uuid_t*)result;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
||||
|
||||
STATIC mp_obj_bluetooth_uuid_t create_mp_uuid(const ble_uuid_any_t *uuid) {
|
||||
mp_obj_bluetooth_uuid_t result;
|
||||
switch (uuid->u.type) {
|
||||
case BLE_UUID_TYPE_16:
|
||||
result.type = MP_BLUETOOTH_UUID_TYPE_16;
|
||||
result.data[0] = uuid->u16.value & 0xff;
|
||||
result.data[1] = (uuid->u16.value << 8) & 0xff;
|
||||
break;
|
||||
case BLE_UUID_TYPE_32:
|
||||
result.type = MP_BLUETOOTH_UUID_TYPE_32;
|
||||
result.data[0] = uuid->u32.value & 0xff;
|
||||
result.data[1] = (uuid->u32.value << 8) & 0xff;
|
||||
result.data[2] = (uuid->u32.value << 16) & 0xff;
|
||||
result.data[3] = (uuid->u32.value << 24) & 0xff;
|
||||
break;
|
||||
case BLE_UUID_TYPE_128:
|
||||
result.type = MP_BLUETOOTH_UUID_TYPE_128;
|
||||
memcpy(result.data, uuid->u128.value, 16);
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// modbluetooth (and the layers above it) work in BE for addresses, Nimble works in LE.
|
||||
STATIC void reverse_addr_byte_order(uint8_t *addr_out, const uint8_t *addr_in) {
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
addr_out[i] = addr_in[5-i];
|
||||
}
|
||||
}
|
||||
|
||||
STATIC ble_addr_t create_nimble_addr(uint8_t addr_type, const uint8_t *addr) {
|
||||
ble_addr_t addr_nimble;
|
||||
addr_nimble.type = addr_type;
|
||||
// Incoming addr is from modbluetooth (BE), so copy and convert to LE for Nimble.
|
||||
reverse_addr_byte_order(addr_nimble.val, addr);
|
||||
return addr_nimble;
|
||||
}
|
||||
|
||||
#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
||||
|
||||
typedef struct {
|
||||
// Pointer to heap-allocated data.
|
||||
uint8_t *data;
|
||||
// Allocated size of data.
|
||||
size_t data_alloc;
|
||||
// Current bytes in use.
|
||||
size_t data_len;
|
||||
// Whether new writes append or replace existing data (default false).
|
||||
bool append;
|
||||
} gatts_db_entry_t;
|
||||
|
||||
volatile int mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF;
|
||||
|
||||
STATIC void reset_cb(int reason) {
|
||||
(void)reason;
|
||||
}
|
||||
|
||||
STATIC void sync_cb(void) {
|
||||
int rc;
|
||||
ble_addr_t addr;
|
||||
|
||||
rc = ble_hs_util_ensure_addr(0); // prefer public address
|
||||
if (rc != 0) {
|
||||
// https://mynewt.apache.org/latest/tutorials/ble/eddystone.html#configure-the-nimble-stack-with-an-address
|
||||
#if MICROPY_PY_BLUETOOTH_RANDOM_ADDR
|
||||
rc = ble_hs_id_gen_rnd(1, &addr);
|
||||
assert(rc == 0);
|
||||
rc = ble_hs_id_set_rnd(addr.val);
|
||||
assert(rc == 0);
|
||||
#else
|
||||
uint8_t addr_be[6];
|
||||
mp_hal_get_mac(MP_HAL_MAC_BDADDR, addr_be);
|
||||
reverse_addr_byte_order(addr.val, addr_be);
|
||||
// ble_hs_id_set_pub(addr.val);
|
||||
rc = ble_hs_id_set_rnd(addr.val);
|
||||
assert(rc == 0);
|
||||
#endif
|
||||
|
||||
rc = ble_hs_util_ensure_addr(0); // prefer public address
|
||||
assert(rc == 0);
|
||||
}
|
||||
|
||||
if (MP_BLUETOOTH_MAX_ATTR_SIZE > 20) {
|
||||
rc = ble_att_set_preferred_mtu(MP_BLUETOOTH_MAX_ATTR_SIZE+3);
|
||||
assert(rc == 0);
|
||||
}
|
||||
|
||||
ble_svc_gap_device_name_set(MICROPY_PY_BLUETOOTH_DEFAULT_NAME);
|
||||
|
||||
mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE;
|
||||
}
|
||||
|
||||
STATIC void create_gatts_db_entry(uint16_t handle) {
|
||||
mp_map_elem_t *elem = mp_map_lookup(MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db, MP_OBJ_NEW_SMALL_INT(handle), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND);
|
||||
gatts_db_entry_t *entry = m_new(gatts_db_entry_t, 1);
|
||||
entry->data = m_new(uint8_t, MP_BLUETOOTH_MAX_ATTR_SIZE);
|
||||
entry->data_alloc = MP_BLUETOOTH_MAX_ATTR_SIZE;
|
||||
entry->data_len = 0;
|
||||
entry->append = false;
|
||||
elem->value = MP_OBJ_FROM_PTR(entry);
|
||||
}
|
||||
|
||||
STATIC void gatts_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) {
|
||||
switch (ctxt->op) {
|
||||
case BLE_GATT_REGISTER_OP_SVC:
|
||||
// Called when a service is successfully registered.
|
||||
DEBUG_EVENT_printf("gatts_register_cb: svc uuid=%p handle=%d\n", &ctxt->svc.svc_def->uuid, ctxt->svc.handle);
|
||||
break;
|
||||
|
||||
case BLE_GATT_REGISTER_OP_CHR:
|
||||
// Called when a characteristic is successfully registered.
|
||||
DEBUG_EVENT_printf("gatts_register_cb: chr uuid=%p def_handle=%d val_handle=%d\n", &ctxt->chr.chr_def->uuid, ctxt->chr.def_handle, ctxt->chr.val_handle);
|
||||
|
||||
// Note: We will get this event for the default GAP Service, meaning that we allocate storage for the
|
||||
// "device name" and "appearance" characteristics, even though we never see the reads for them.
|
||||
// TODO: Possibly check if the service UUID is 0x1801 and ignore?
|
||||
|
||||
// Allocate the gatts_db storage for this characteristic.
|
||||
// Although this function is a callback, it's called synchronously from ble_hs_sched_start/ble_gatts_start, so safe to allocate.
|
||||
create_gatts_db_entry(ctxt->chr.val_handle);
|
||||
break;
|
||||
|
||||
case BLE_GATT_REGISTER_OP_DSC:
|
||||
// Called when a descriptor is successfully registered.
|
||||
// Note: This is event is not called for the CCCD.
|
||||
DEBUG_EVENT_printf("gatts_register_cb: dsc uuid=%p handle=%d\n", &ctxt->dsc.dsc_def->uuid, ctxt->dsc.handle);
|
||||
|
||||
// See above, safe to alloc.
|
||||
create_gatts_db_entry(ctxt->dsc.handle);
|
||||
|
||||
// Unlike characteristics, we have to manually provide a way to get the handle back to the register method.
|
||||
*((uint16_t*)ctxt->dsc.dsc_def->arg) = ctxt->dsc.handle;
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_EVENT_printf("gatts_register_cb: unknown op %d\n", ctxt->op);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
STATIC int gap_event_cb(struct ble_gap_event *event, void *arg) {
|
||||
DEBUG_EVENT_printf("gap_event_cb: type=%d\n", event->type);
|
||||
struct ble_gap_conn_desc desc;
|
||||
uint8_t addr[6] = {0};
|
||||
|
||||
switch (event->type) {
|
||||
case BLE_GAP_EVENT_CONNECT:
|
||||
if (event->connect.status == 0) {
|
||||
// Connection established.
|
||||
ble_gap_conn_find(event->connect.conn_handle, &desc);
|
||||
reverse_addr_byte_order(addr, desc.peer_id_addr.val);
|
||||
mp_bluetooth_gap_on_connected_disconnected(MP_BLUETOOTH_IRQ_CENTRAL_CONNECT, event->connect.conn_handle, desc.peer_id_addr.type, addr);
|
||||
} else {
|
||||
// Connection failed.
|
||||
mp_bluetooth_gap_on_connected_disconnected(MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT, event->connect.conn_handle, 0xff, addr);
|
||||
}
|
||||
break;
|
||||
|
||||
case BLE_GAP_EVENT_DISCONNECT:
|
||||
// Disconnect.
|
||||
reverse_addr_byte_order(addr, event->disconnect.conn.peer_id_addr.val);
|
||||
mp_bluetooth_gap_on_connected_disconnected(MP_BLUETOOTH_IRQ_CENTRAL_DISCONNECT, event->disconnect.conn.conn_handle, event->disconnect.conn.peer_id_addr.type, addr);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mp_bluetooth_init(void) {
|
||||
// Clean up if necessary.
|
||||
mp_bluetooth_deinit();
|
||||
|
||||
ble_hs_cfg.reset_cb = reset_cb;
|
||||
ble_hs_cfg.sync_cb = sync_cb;
|
||||
ble_hs_cfg.gatts_register_cb = gatts_register_cb;
|
||||
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
|
||||
|
||||
MP_STATE_PORT(bluetooth_nimble_root_pointers) = m_new0(mp_bluetooth_nimble_root_pointers_t, 1);
|
||||
MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db = m_new(mp_map_t, 1);
|
||||
|
||||
mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_STARTING;
|
||||
|
||||
mp_bluetooth_nimble_port_preinit();
|
||||
nimble_port_init();
|
||||
mp_bluetooth_nimble_port_postinit();
|
||||
|
||||
// By default, just register the default gap service.
|
||||
ble_svc_gap_init();
|
||||
|
||||
mp_bluetooth_nimble_port_start();
|
||||
|
||||
// Wait for sync callback
|
||||
while (mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE) {
|
||||
MICROPY_EVENT_POLL_HOOK
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Called when the host stop procedure has completed.
|
||||
STATIC void ble_hs_shutdown_stop_cb(int status, void *arg) {
|
||||
mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF;
|
||||
}
|
||||
|
||||
STATIC struct ble_hs_stop_listener ble_hs_shutdown_stop_listener;
|
||||
|
||||
void mp_bluetooth_deinit(void) {
|
||||
if (mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) {
|
||||
return;
|
||||
}
|
||||
|
||||
mp_bluetooth_gap_advertise_stop();
|
||||
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
||||
mp_bluetooth_gap_scan_stop();
|
||||
#endif
|
||||
|
||||
ble_hs_stop(&ble_hs_shutdown_stop_listener, ble_hs_shutdown_stop_cb, NULL);
|
||||
|
||||
while (mp_bluetooth_nimble_ble_state != MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF) {
|
||||
MICROPY_EVENT_POLL_HOOK
|
||||
}
|
||||
|
||||
mp_bluetooth_nimble_port_deinit();
|
||||
|
||||
MP_STATE_PORT(bluetooth_nimble_root_pointers) = NULL;
|
||||
}
|
||||
|
||||
bool mp_bluetooth_is_enabled(void) {
|
||||
return mp_bluetooth_nimble_ble_state == MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE;
|
||||
}
|
||||
|
||||
void mp_bluetooth_get_device_addr(uint8_t *addr) {
|
||||
#if MICROPY_PY_BLUETOOTH_RANDOM_ADDR
|
||||
ble_hs_id_copy_addr(BLE_ADDR_RANDOM, addr, NULL);
|
||||
#else
|
||||
mp_hal_get_mac(MP_HAL_MAC_BDADDR, addr);
|
||||
#endif
|
||||
}
|
||||
|
||||
int mp_bluetooth_gap_advertise_start(bool connectable, int32_t interval_us, const uint8_t *adv_data, size_t adv_data_len, const uint8_t *sr_data, size_t sr_data_len) {
|
||||
int ret;
|
||||
|
||||
mp_bluetooth_gap_advertise_stop();
|
||||
|
||||
if (adv_data) {
|
||||
ret = ble_gap_adv_set_data(adv_data, adv_data_len);
|
||||
if (ret != 0) {
|
||||
return ble_hs_err_to_errno(ret);
|
||||
}
|
||||
}
|
||||
|
||||
if (sr_data) {
|
||||
ret = ble_gap_adv_rsp_set_data(sr_data, sr_data_len);
|
||||
if (ret != 0) {
|
||||
return ble_hs_err_to_errno(ret);
|
||||
}
|
||||
}
|
||||
|
||||
struct ble_gap_adv_params adv_params = {
|
||||
.conn_mode = connectable ? BLE_GAP_CONN_MODE_UND : BLE_GAP_CONN_MODE_NON,
|
||||
.disc_mode = BLE_GAP_DISC_MODE_GEN,
|
||||
.itvl_min = interval_us / BLE_HCI_ADV_ITVL, // convert to 625us units.
|
||||
.itvl_max = interval_us / BLE_HCI_ADV_ITVL,
|
||||
.channel_map = 7, // all 3 channels.
|
||||
};
|
||||
|
||||
ret = ble_gap_adv_start(BLE_OWN_ADDR_PUBLIC, NULL, BLE_HS_FOREVER, &adv_params, gap_event_cb, NULL);
|
||||
if (ret == 0) {
|
||||
return 0;
|
||||
}
|
||||
ret = ble_gap_adv_start(BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT, NULL, BLE_HS_FOREVER, &adv_params, gap_event_cb, NULL);
|
||||
if (ret == 0) {
|
||||
return 0;
|
||||
}
|
||||
ret = ble_gap_adv_start(BLE_OWN_ADDR_RPA_RANDOM_DEFAULT, NULL, BLE_HS_FOREVER, &adv_params, gap_event_cb, NULL);
|
||||
if (ret == 0) {
|
||||
return 0;
|
||||
}
|
||||
ret = ble_gap_adv_start(BLE_OWN_ADDR_RANDOM, NULL, BLE_HS_FOREVER, &adv_params, gap_event_cb, NULL);
|
||||
if (ret == 0) {
|
||||
return 0;
|
||||
}
|
||||
DEBUG_EVENT_printf("ble_gap_adv_start: %d\n", ret);
|
||||
|
||||
return ble_hs_err_to_errno(ret);
|
||||
}
|
||||
|
||||
void mp_bluetooth_gap_advertise_stop(void) {
|
||||
if (ble_gap_adv_active()) {
|
||||
ble_gap_adv_stop();
|
||||
}
|
||||
}
|
||||
|
||||
static int characteristic_access_cb(uint16_t conn_handle, uint16_t value_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) {
|
||||
DEBUG_EVENT_printf("characteristic_access_cb: conn_handle=%u value_handle=%u op=%u\n", conn_handle, value_handle, ctxt->op);
|
||||
mp_map_elem_t *elem;
|
||||
gatts_db_entry_t *entry;
|
||||
switch (ctxt->op) {
|
||||
case BLE_GATT_ACCESS_OP_READ_CHR:
|
||||
case BLE_GATT_ACCESS_OP_READ_DSC:
|
||||
#if MICROPY_PY_BLUETOOTH_GATTS_ON_READ_CALLBACK
|
||||
// Allow Python code to override (by using gatts_write), or deny (by returning false) the read.
|
||||
if (!mp_bluetooth_gatts_on_read_request(conn_handle, value_handle)) {
|
||||
return BLE_ATT_ERR_READ_NOT_PERMITTED;
|
||||
}
|
||||
#endif
|
||||
|
||||
elem = mp_map_lookup(MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db, MP_OBJ_NEW_SMALL_INT(value_handle), MP_MAP_LOOKUP);
|
||||
if (!elem) {
|
||||
return BLE_ATT_ERR_ATTR_NOT_FOUND;
|
||||
}
|
||||
entry = MP_OBJ_TO_PTR(elem->value);
|
||||
|
||||
os_mbuf_append(ctxt->om, entry->data, entry->data_len);
|
||||
|
||||
return 0;
|
||||
case BLE_GATT_ACCESS_OP_WRITE_CHR:
|
||||
case BLE_GATT_ACCESS_OP_WRITE_DSC:
|
||||
elem = mp_map_lookup(MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db, MP_OBJ_NEW_SMALL_INT(value_handle), MP_MAP_LOOKUP);
|
||||
if (!elem) {
|
||||
return BLE_ATT_ERR_ATTR_NOT_FOUND;
|
||||
}
|
||||
entry = MP_OBJ_TO_PTR(elem->value);
|
||||
|
||||
size_t offset = 0;
|
||||
if (entry->append) {
|
||||
offset = entry->data_len;
|
||||
}
|
||||
entry->data_len = MIN(entry->data_alloc, OS_MBUF_PKTLEN(ctxt->om) + offset);
|
||||
os_mbuf_copydata(ctxt->om, 0, entry->data_len - offset, entry->data + offset);
|
||||
|
||||
mp_bluetooth_gatts_on_write(conn_handle, value_handle);
|
||||
|
||||
return 0;
|
||||
}
|
||||
return BLE_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
|
||||
int mp_bluetooth_gatts_register_service_begin(bool append) {
|
||||
int ret = ble_gatts_reset();
|
||||
if (ret != 0) {
|
||||
return ble_hs_err_to_errno(ret);
|
||||
}
|
||||
|
||||
// Reset the gatt characteristic value db.
|
||||
mp_map_init(MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db, 0);
|
||||
|
||||
// By default, just register the default gap service.
|
||||
ble_svc_gap_init();
|
||||
|
||||
if (!append) {
|
||||
// Unref any previous service definitions.
|
||||
for (int i = 0; i < MP_STATE_PORT(bluetooth_nimble_root_pointers)->n_services; ++i) {
|
||||
MP_STATE_PORT(bluetooth_nimble_root_pointers)->services[i] = NULL;
|
||||
}
|
||||
MP_STATE_PORT(bluetooth_nimble_root_pointers)->n_services = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mp_bluetooth_gatts_register_service_end() {
|
||||
int ret = ble_gatts_start();
|
||||
if (ret != 0) {
|
||||
return ble_hs_err_to_errno(ret);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mp_bluetooth_gatts_register_service(mp_obj_bluetooth_uuid_t *service_uuid, mp_obj_bluetooth_uuid_t **characteristic_uuids, uint8_t *characteristic_flags, mp_obj_bluetooth_uuid_t **descriptor_uuids, uint8_t *descriptor_flags, uint8_t *num_descriptors, uint16_t *handles, size_t num_characteristics) {
|
||||
if (MP_STATE_PORT(bluetooth_nimble_root_pointers)->n_services == MP_BLUETOOTH_NIMBLE_MAX_SERVICES) {
|
||||
return MP_E2BIG;
|
||||
}
|
||||
size_t handle_index = 0;
|
||||
size_t descriptor_index = 0;
|
||||
|
||||
struct ble_gatt_chr_def *characteristics = m_new(struct ble_gatt_chr_def, num_characteristics + 1);
|
||||
for (size_t i = 0; i < num_characteristics; ++i) {
|
||||
characteristics[i].uuid = create_nimble_uuid(characteristic_uuids[i]);
|
||||
characteristics[i].access_cb = characteristic_access_cb;
|
||||
characteristics[i].arg = NULL;
|
||||
characteristics[i].flags = characteristic_flags[i];
|
||||
characteristics[i].min_key_size = 0;
|
||||
characteristics[i].val_handle = &handles[handle_index];
|
||||
++handle_index;
|
||||
|
||||
if (num_descriptors[i] == 0) {
|
||||
characteristics[i].descriptors = NULL;
|
||||
} else {
|
||||
struct ble_gatt_dsc_def *descriptors = m_new(struct ble_gatt_dsc_def, num_descriptors[i] + 1);
|
||||
|
||||
for (size_t j = 0; j < num_descriptors[i]; ++j) {
|
||||
descriptors[j].uuid = create_nimble_uuid(descriptor_uuids[descriptor_index]);
|
||||
descriptors[j].access_cb = characteristic_access_cb;
|
||||
descriptors[j].att_flags = descriptor_flags[i];
|
||||
descriptors[j].min_key_size = 0;
|
||||
// Unlike characteristic, Nimble doesn't provide an automatic way to remember the handle, so use the arg.
|
||||
descriptors[j].arg = &handles[handle_index];
|
||||
++descriptor_index;
|
||||
++handle_index;
|
||||
}
|
||||
descriptors[num_descriptors[i]].uuid = NULL; // no more descriptors
|
||||
|
||||
characteristics[i].descriptors = descriptors;
|
||||
}
|
||||
}
|
||||
characteristics[num_characteristics].uuid = NULL; // no more characteristics
|
||||
|
||||
struct ble_gatt_svc_def *service = m_new(struct ble_gatt_svc_def, 2);
|
||||
service[0].type = BLE_GATT_SVC_TYPE_PRIMARY;
|
||||
service[0].uuid = create_nimble_uuid(service_uuid);
|
||||
service[0].includes = NULL;
|
||||
service[0].characteristics = characteristics;
|
||||
service[1].type = 0; // no more services
|
||||
|
||||
MP_STATE_PORT(bluetooth_nimble_root_pointers)->services[MP_STATE_PORT(bluetooth_nimble_root_pointers)->n_services++] = service;
|
||||
|
||||
// Note: advertising must be stopped for gatts registration to work
|
||||
|
||||
int ret = ble_gatts_count_cfg(service);
|
||||
if (ret != 0) {
|
||||
return ble_hs_err_to_errno(ret);
|
||||
}
|
||||
|
||||
ret = ble_gatts_add_svcs(service);
|
||||
if (ret != 0) {
|
||||
return ble_hs_err_to_errno(ret);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mp_bluetooth_gap_disconnect(uint16_t conn_handle) {
|
||||
return ble_hs_err_to_errno(ble_gap_terminate(conn_handle, BLE_ERR_REM_USER_CONN_TERM));
|
||||
}
|
||||
|
||||
int mp_bluetooth_gatts_read(uint16_t value_handle, uint8_t **value, size_t *value_len) {
|
||||
mp_map_elem_t *elem = mp_map_lookup(MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db, MP_OBJ_NEW_SMALL_INT(value_handle), MP_MAP_LOOKUP);
|
||||
if (!elem) {
|
||||
return MP_EINVAL;
|
||||
}
|
||||
gatts_db_entry_t *entry = MP_OBJ_TO_PTR(elem->value);
|
||||
*value = entry->data;
|
||||
*value_len = entry->data_len;
|
||||
if (entry->append) {
|
||||
entry->data_len = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mp_bluetooth_gatts_write(uint16_t value_handle, const uint8_t *value, size_t value_len) {
|
||||
mp_map_elem_t *elem = mp_map_lookup(MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db, MP_OBJ_NEW_SMALL_INT(value_handle), MP_MAP_LOOKUP);
|
||||
if (!elem) {
|
||||
return MP_EINVAL;
|
||||
}
|
||||
gatts_db_entry_t *entry = MP_OBJ_TO_PTR(elem->value);
|
||||
if (value_len > entry->data_alloc) {
|
||||
entry->data = m_new(uint8_t, value_len);
|
||||
entry->data_alloc = value_len;
|
||||
}
|
||||
|
||||
memcpy(entry->data, value, value_len);
|
||||
entry->data_len = value_len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO: Could use ble_gatts_chr_updated to send to all subscribed centrals.
|
||||
|
||||
int mp_bluetooth_gatts_notify(uint16_t conn_handle, uint16_t value_handle) {
|
||||
// Confusingly, notify/notify_custom/indicate are "gattc" function (even though they're used by peripherals (i.e. gatt servers)).
|
||||
// See https://www.mail-archive.com/dev@mynewt.apache.org/msg01293.html
|
||||
return ble_hs_err_to_errno(ble_gattc_notify(conn_handle, value_handle));
|
||||
}
|
||||
|
||||
int mp_bluetooth_gatts_notify_send(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t *value_len) {
|
||||
struct os_mbuf *om = ble_hs_mbuf_from_flat(value, *value_len);
|
||||
if (om == NULL) {
|
||||
return -1;
|
||||
}
|
||||
// TODO: check that notify_custom takes ownership of om, if not os_mbuf_free_chain(om).
|
||||
return ble_hs_err_to_errno(ble_gattc_notify_custom(conn_handle, value_handle, om));
|
||||
}
|
||||
|
||||
int mp_bluetooth_gatts_indicate(uint16_t conn_handle, uint16_t value_handle) {
|
||||
return ble_hs_err_to_errno(ble_gattc_indicate(conn_handle, value_handle));
|
||||
}
|
||||
|
||||
int mp_bluetooth_gatts_set_buffer(uint16_t value_handle, size_t len, bool append) {
|
||||
mp_map_elem_t *elem = mp_map_lookup(MP_STATE_PORT(bluetooth_nimble_root_pointers)->gatts_db, MP_OBJ_NEW_SMALL_INT(value_handle), MP_MAP_LOOKUP);
|
||||
if (!elem) {
|
||||
return MP_EINVAL;
|
||||
}
|
||||
gatts_db_entry_t *entry = MP_OBJ_TO_PTR(elem->value);
|
||||
entry->data = m_renew(uint8_t, entry->data, entry->data_alloc, len);
|
||||
entry->data_alloc = len;
|
||||
entry->data_len = 0;
|
||||
entry->append = append;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
||||
|
||||
STATIC int gap_scan_cb(struct ble_gap_event *event, void *arg) {
|
||||
DEBUG_EVENT_printf("gap_scan_cb: event=%d type=%d\n", event->type, event->type == BLE_GAP_EVENT_DISC ? event->disc.event_type : -1);
|
||||
|
||||
if (event->type == BLE_GAP_EVENT_DISC_COMPLETE) {
|
||||
mp_bluetooth_gap_on_scan_complete();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (event->type != BLE_GAP_EVENT_DISC) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (event->disc.event_type == BLE_HCI_ADV_RPT_EVTYPE_ADV_IND || event->disc.event_type == BLE_HCI_ADV_RPT_EVTYPE_NONCONN_IND) {
|
||||
bool connectable = event->disc.event_type == BLE_HCI_ADV_RPT_EVTYPE_ADV_IND;
|
||||
uint8_t addr[6];
|
||||
reverse_addr_byte_order(addr, event->disc.addr.val);
|
||||
mp_bluetooth_gap_on_scan_result(event->disc.addr.type, addr, connectable, event->disc.rssi, event->disc.data, event->disc.length_data);
|
||||
} else if (event->disc.event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP) {
|
||||
// TODO
|
||||
} else if (event->disc.event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_IND) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mp_bluetooth_gap_scan_start(int32_t duration_ms, int32_t interval_us, int32_t window_us) {
|
||||
if (duration_ms == 0) {
|
||||
duration_ms = BLE_HS_FOREVER;
|
||||
}
|
||||
struct ble_gap_disc_params discover_params = {
|
||||
.itvl = MAX(BLE_HCI_SCAN_ITVL_MIN, MIN(BLE_HCI_SCAN_ITVL_MAX, interval_us / BLE_HCI_SCAN_ITVL)),
|
||||
.window = MAX(BLE_HCI_SCAN_WINDOW_MIN, MIN(BLE_HCI_SCAN_WINDOW_MAX, window_us / BLE_HCI_SCAN_ITVL)),
|
||||
.filter_policy = BLE_HCI_CONN_FILT_NO_WL,
|
||||
.limited = 0,
|
||||
.passive = 1, // TODO: Handle BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP in gap_scan_cb above.
|
||||
.filter_duplicates = 0,
|
||||
};
|
||||
int err = ble_gap_disc(BLE_OWN_ADDR_PUBLIC, duration_ms, &discover_params, gap_scan_cb, NULL);
|
||||
return ble_hs_err_to_errno(err);
|
||||
}
|
||||
|
||||
int mp_bluetooth_gap_scan_stop(void) {
|
||||
int err = ble_gap_disc_cancel();
|
||||
if (err == 0) {
|
||||
mp_bluetooth_gap_on_scan_complete();
|
||||
return 0;
|
||||
}
|
||||
return ble_hs_err_to_errno(err);
|
||||
}
|
||||
|
||||
// Central role: GAP events for a connected peripheral.
|
||||
STATIC int peripheral_gap_event_cb(struct ble_gap_event *event, void *arg) {
|
||||
DEBUG_EVENT_printf("peripheral_gap_event_cb: event=%d\n", event->type);
|
||||
struct ble_gap_conn_desc desc;
|
||||
uint8_t buf[MP_BLUETOOTH_MAX_ATTR_SIZE];
|
||||
size_t len;
|
||||
uint8_t addr[6] = {0};
|
||||
|
||||
switch (event->type) {
|
||||
case BLE_GAP_EVENT_CONNECT:
|
||||
if (event->connect.status == 0) {
|
||||
// Connection established.
|
||||
ble_gap_conn_find(event->connect.conn_handle, &desc);
|
||||
reverse_addr_byte_order(addr, desc.peer_id_addr.val);
|
||||
mp_bluetooth_gap_on_connected_disconnected(MP_BLUETOOTH_IRQ_PERIPHERAL_CONNECT, event->connect.conn_handle, desc.peer_id_addr.type, addr);
|
||||
} else {
|
||||
// Connection failed.
|
||||
mp_bluetooth_gap_on_connected_disconnected(MP_BLUETOOTH_IRQ_PERIPHERAL_DISCONNECT, event->connect.conn_handle, 0xff, addr);
|
||||
}
|
||||
break;
|
||||
|
||||
case BLE_GAP_EVENT_DISCONNECT:
|
||||
// Disconnect.
|
||||
reverse_addr_byte_order(addr, event->disconnect.conn.peer_id_addr.val);
|
||||
mp_bluetooth_gap_on_connected_disconnected(MP_BLUETOOTH_IRQ_PERIPHERAL_DISCONNECT, event->disconnect.conn.conn_handle, event->disconnect.conn.peer_id_addr.type, addr);
|
||||
|
||||
break;
|
||||
|
||||
case BLE_GAP_EVENT_NOTIFY_RX:
|
||||
len = MIN(MP_BLUETOOTH_MAX_ATTR_SIZE, OS_MBUF_PKTLEN(event->notify_rx.om));
|
||||
os_mbuf_copydata(event->notify_rx.om, 0, len, buf);
|
||||
if (event->notify_rx.indication == 0) {
|
||||
mp_bluetooth_gattc_on_data_available(MP_BLUETOOTH_IRQ_GATTC_NOTIFY, event->notify_rx.conn_handle, event->notify_rx.attr_handle, buf, len);
|
||||
} else {
|
||||
mp_bluetooth_gattc_on_data_available(MP_BLUETOOTH_IRQ_GATTC_INDICATE, event->notify_rx.conn_handle, event->notify_rx.attr_handle, buf, len);
|
||||
}
|
||||
break;
|
||||
|
||||
case BLE_GAP_EVENT_CONN_UPDATE:
|
||||
// TODO
|
||||
break;
|
||||
|
||||
case BLE_GAP_EVENT_CONN_UPDATE_REQ:
|
||||
// TODO
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mp_bluetooth_gap_peripheral_connect(uint8_t addr_type, const uint8_t *addr, int32_t duration_ms) {
|
||||
if (ble_gap_disc_active()) {
|
||||
mp_bluetooth_gap_scan_stop();
|
||||
}
|
||||
|
||||
// TODO: This is the same as ble_gap_conn_params_dflt (i.e. passing NULL).
|
||||
STATIC const struct ble_gap_conn_params params = {
|
||||
.scan_itvl = 0x0010,
|
||||
.scan_window = 0x0010,
|
||||
.itvl_min = BLE_GAP_INITIAL_CONN_ITVL_MIN,
|
||||
.itvl_max = BLE_GAP_INITIAL_CONN_ITVL_MAX,
|
||||
.latency = BLE_GAP_INITIAL_CONN_LATENCY,
|
||||
.supervision_timeout = BLE_GAP_INITIAL_SUPERVISION_TIMEOUT,
|
||||
.min_ce_len = BLE_GAP_INITIAL_CONN_MIN_CE_LEN,
|
||||
.max_ce_len = BLE_GAP_INITIAL_CONN_MAX_CE_LEN,
|
||||
};
|
||||
|
||||
ble_addr_t addr_nimble = create_nimble_addr(addr_type, addr);
|
||||
int err = ble_gap_connect(BLE_OWN_ADDR_PUBLIC, &addr_nimble, duration_ms, ¶ms, &peripheral_gap_event_cb, NULL);
|
||||
return ble_hs_err_to_errno(err);
|
||||
}
|
||||
|
||||
STATIC int peripheral_discover_service_cb(uint16_t conn_handle, const struct ble_gatt_error *error, const struct ble_gatt_svc *service, void *arg) {
|
||||
DEBUG_EVENT_printf("peripheral_discover_service_cb: conn_handle=%d status=%d start_handle=%d\n", conn_handle, error->status, service ? service->start_handle : -1);
|
||||
if (error->status == 0) {
|
||||
mp_obj_bluetooth_uuid_t service_uuid = create_mp_uuid(&service->uuid);
|
||||
mp_bluetooth_gattc_on_primary_service_result(conn_handle, service->start_handle, service->end_handle, &service_uuid);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mp_bluetooth_gattc_discover_primary_services(uint16_t conn_handle) {
|
||||
int err = ble_gattc_disc_all_svcs(conn_handle, &peripheral_discover_service_cb, NULL);
|
||||
return ble_hs_err_to_errno(err);
|
||||
}
|
||||
|
||||
STATIC int ble_gatt_characteristic_cb(uint16_t conn_handle, const struct ble_gatt_error *error, const struct ble_gatt_chr *characteristic, void *arg) {
|
||||
DEBUG_EVENT_printf("ble_gatt_characteristic_cb: conn_handle=%d status=%d def_handle=%d val_handle=%d\n", conn_handle, error->status, characteristic ? characteristic->def_handle : -1, characteristic ? characteristic->val_handle : -1);
|
||||
if (error->status == 0) {
|
||||
mp_obj_bluetooth_uuid_t characteristic_uuid = create_mp_uuid(&characteristic->uuid);
|
||||
mp_bluetooth_gattc_on_characteristic_result(conn_handle, characteristic->def_handle, characteristic->val_handle, characteristic->properties, &characteristic_uuid);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mp_bluetooth_gattc_discover_characteristics(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle) {
|
||||
int err = ble_gattc_disc_all_chrs(conn_handle, start_handle, end_handle, &ble_gatt_characteristic_cb, NULL);
|
||||
return ble_hs_err_to_errno(err);
|
||||
}
|
||||
|
||||
STATIC int ble_gatt_descriptor_cb(uint16_t conn_handle, const struct ble_gatt_error *error, uint16_t characteristic_val_handle, const struct ble_gatt_dsc *descriptor, void *arg) {
|
||||
DEBUG_EVENT_printf("ble_gatt_descriptor_cb: conn_handle=%d status=%d chr_handle=%d dsc_handle=%d\n", conn_handle, error->status, characteristic_val_handle, descriptor ? descriptor->handle : -1);
|
||||
if (error->status == 0) {
|
||||
mp_obj_bluetooth_uuid_t descriptor_uuid = create_mp_uuid(&descriptor->uuid);
|
||||
mp_bluetooth_gattc_on_descriptor_result(conn_handle, descriptor->handle, &descriptor_uuid);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mp_bluetooth_gattc_discover_descriptors(uint16_t conn_handle, uint16_t start_handle, uint16_t end_handle) {
|
||||
int err = ble_gattc_disc_all_dscs(conn_handle, start_handle, end_handle, &ble_gatt_descriptor_cb, NULL);
|
||||
return ble_hs_err_to_errno(err);
|
||||
}
|
||||
|
||||
STATIC int ble_gatt_attr_read_cb(uint16_t conn_handle, const struct ble_gatt_error *error, struct ble_gatt_attr *attr, void *arg) {
|
||||
DEBUG_EVENT_printf("ble_gatt_attr_read_cb: conn_handle=%d status=%d handle=%d\n", conn_handle, error->status, attr ? attr->handle : -1);
|
||||
// TODO: Maybe send NULL if error->status non-zero.
|
||||
if (error->status == 0) {
|
||||
uint8_t buf[MP_BLUETOOTH_MAX_ATTR_SIZE];
|
||||
size_t len = MIN(MP_BLUETOOTH_MAX_ATTR_SIZE, OS_MBUF_PKTLEN(attr->om));
|
||||
os_mbuf_copydata(attr->om, 0, len, buf);
|
||||
mp_bluetooth_gattc_on_data_available(MP_BLUETOOTH_IRQ_GATTC_READ_RESULT, conn_handle, attr->handle, buf, len);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Initiate read of a value from the remote peripheral.
|
||||
int mp_bluetooth_gattc_read(uint16_t conn_handle, uint16_t value_handle) {
|
||||
int err = ble_gattc_read(conn_handle, value_handle, &ble_gatt_attr_read_cb, NULL);
|
||||
return ble_hs_err_to_errno(err);
|
||||
}
|
||||
|
||||
STATIC int ble_gatt_attr_write_cb(uint16_t conn_handle, const struct ble_gatt_error *error, struct ble_gatt_attr *attr, void *arg) {
|
||||
DEBUG_EVENT_printf("ble_gatt_attr_write_cb: conn_handle=%d status=%d handle=%d\n", conn_handle, error->status, attr ? attr->handle : -1);
|
||||
mp_bluetooth_gattc_on_write_status(conn_handle, attr->handle, error->status);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Write the value to the remote peripheral.
|
||||
int mp_bluetooth_gattc_write(uint16_t conn_handle, uint16_t value_handle, const uint8_t *value, size_t *value_len) {
|
||||
int err = ble_gattc_write_flat(conn_handle, value_handle, value, *value_len, &ble_gatt_attr_write_cb, NULL);
|
||||
return ble_hs_err_to_errno(err);
|
||||
}
|
||||
|
||||
#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
|
||||
|
||||
#endif // MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE
|
||||
54
extmod/modbluetooth_nimble.h
Normal file
54
extmod/modbluetooth_nimble.h
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Jim Mussared
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef MICROPY_INCLUDED_EXTMOD_MODBLUETOOTH_NIMBLE_H
|
||||
#define MICROPY_INCLUDED_EXTMOD_MODBLUETOOTH_NIMBLE_H
|
||||
|
||||
#define MP_BLUETOOTH_NIMBLE_MAX_SERVICES (8)
|
||||
|
||||
typedef struct _mp_bluetooth_nimble_root_pointers_t {
|
||||
// Characteristic (and descriptor) value storage.
|
||||
mp_map_t *gatts_db;
|
||||
|
||||
// Pending service definitions.
|
||||
size_t n_services;
|
||||
struct ble_gatt_svc_def *services[MP_BLUETOOTH_NIMBLE_MAX_SERVICES];
|
||||
} mp_bluetooth_nimble_root_pointers_t;
|
||||
|
||||
enum {
|
||||
MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF,
|
||||
MP_BLUETOOTH_NIMBLE_BLE_STATE_STARTING,
|
||||
MP_BLUETOOTH_NIMBLE_BLE_STATE_ACTIVE,
|
||||
};
|
||||
|
||||
extern volatile int mp_bluetooth_nimble_ble_state;
|
||||
|
||||
void mp_bluetooth_nimble_port_preinit(void);
|
||||
void mp_bluetooth_nimble_port_postinit(void);
|
||||
void mp_bluetooth_nimble_port_deinit(void);
|
||||
void mp_bluetooth_nimble_port_start(void);
|
||||
|
||||
#endif // MICROPY_INCLUDED_EXTMOD_MODBLUETOOTH_NIMBLE_H
|
||||
@ -320,7 +320,7 @@ STATIC const mp_obj_type_t btree_type = {
|
||||
.locals_dict = (void*)&btree_locals_dict,
|
||||
};
|
||||
|
||||
STATIC FILEVTABLE btree_stream_fvtable = {
|
||||
STATIC const FILEVTABLE btree_stream_fvtable = {
|
||||
mp_stream_posix_read,
|
||||
mp_stream_posix_write,
|
||||
mp_stream_posix_lseek,
|
||||
|
||||
@ -1468,7 +1468,7 @@ STATIC mp_uint_t lwip_socket_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_
|
||||
|
||||
if (socket->state == STATE_NEW) {
|
||||
// New sockets are not connected so set HUP
|
||||
ret |= flags & MP_STREAM_POLL_HUP;
|
||||
ret |= MP_STREAM_POLL_HUP;
|
||||
} else if (socket->state == STATE_PEER_CLOSED) {
|
||||
// Peer-closed socket is both readable and writable: read will
|
||||
// return EOF, write - error. Without this poll will hang on a
|
||||
@ -1476,11 +1476,14 @@ STATIC mp_uint_t lwip_socket_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_
|
||||
ret |= flags & (MP_STREAM_POLL_RD | MP_STREAM_POLL_WR);
|
||||
} else if (socket->state == ERR_RST) {
|
||||
// Socket was reset by peer, a write will return an error
|
||||
ret |= flags & (MP_STREAM_POLL_WR | MP_STREAM_POLL_HUP);
|
||||
ret |= flags & MP_STREAM_POLL_WR;
|
||||
ret |= MP_STREAM_POLL_HUP;
|
||||
} else if (socket->state == _ERR_BADF) {
|
||||
ret |= MP_STREAM_POLL_NVAL;
|
||||
} else if (socket->state < 0) {
|
||||
// Socket in some other error state, use catch-all ERR flag
|
||||
// TODO: may need to set other return flags here
|
||||
ret |= flags & MP_STREAM_POLL_ERR;
|
||||
ret |= MP_STREAM_POLL_ERR;
|
||||
}
|
||||
|
||||
} else if (request == MP_STREAM_CLOSE) {
|
||||
|
||||
@ -169,21 +169,29 @@ STATIC mp_obj_ssl_socket_t *socket_new(mp_obj_t sock, struct ssl_args *args) {
|
||||
|
||||
mbedtls_ssl_set_bio(&o->ssl, &o->sock, _mbedtls_ssl_send, _mbedtls_ssl_recv, NULL);
|
||||
|
||||
if (args->key.u_obj != MP_OBJ_NULL) {
|
||||
if (args->key.u_obj != mp_const_none) {
|
||||
size_t key_len;
|
||||
const byte *key = (const byte*)mp_obj_str_get_data(args->key.u_obj, &key_len);
|
||||
// len should include terminating null
|
||||
ret = mbedtls_pk_parse_key(&o->pkey, key, key_len + 1, NULL, 0);
|
||||
assert(ret == 0);
|
||||
if (ret != 0) {
|
||||
ret = MBEDTLS_ERR_PK_BAD_INPUT_DATA; // use general error for all key errors
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
size_t cert_len;
|
||||
const byte *cert = (const byte*)mp_obj_str_get_data(args->cert.u_obj, &cert_len);
|
||||
// len should include terminating null
|
||||
ret = mbedtls_x509_crt_parse(&o->cert, cert, cert_len + 1);
|
||||
assert(ret == 0);
|
||||
if (ret != 0) {
|
||||
ret = MBEDTLS_ERR_X509_BAD_INPUT_DATA; // use general error for all cert errors
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ret = mbedtls_ssl_conf_own_cert(&o->conf, &o->cert, &o->pkey);
|
||||
assert(ret == 0);
|
||||
if (ret != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
if (args->do_handshake.u_bool) {
|
||||
@ -208,6 +216,10 @@ cleanup:
|
||||
|
||||
if (ret == MBEDTLS_ERR_SSL_ALLOC_FAILED) {
|
||||
mp_raise_OSError(MP_ENOMEM);
|
||||
} else if (ret == MBEDTLS_ERR_PK_BAD_INPUT_DATA) {
|
||||
mp_raise_ValueError("invalid key");
|
||||
} else if (ret == MBEDTLS_ERR_X509_BAD_INPUT_DATA) {
|
||||
mp_raise_ValueError("invalid cert");
|
||||
} else {
|
||||
mp_raise_OSError(MP_EIO);
|
||||
}
|
||||
@ -219,6 +231,9 @@ STATIC mp_obj_t mod_ssl_getpeercert(mp_obj_t o_in, mp_obj_t binary_form) {
|
||||
mp_raise_NotImplementedError(NULL);
|
||||
}
|
||||
const mbedtls_x509_crt* peer_cert = mbedtls_ssl_get_peer_cert(&o->ssl);
|
||||
if (peer_cert == NULL) {
|
||||
return mp_const_none;
|
||||
}
|
||||
return mp_obj_new_bytes(peer_cert->raw.p, peer_cert->raw.len);
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_ssl_getpeercert_obj, mod_ssl_getpeercert);
|
||||
@ -331,8 +346,8 @@ STATIC const mp_obj_type_t ussl_socket_type = {
|
||||
STATIC mp_obj_t mod_ssl_wrap_socket(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
|
||||
// TODO: Implement more args
|
||||
static const mp_arg_t allowed_args[] = {
|
||||
{ MP_QSTR_key, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
|
||||
{ MP_QSTR_cert, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
|
||||
{ MP_QSTR_key, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj)} },
|
||||
{ MP_QSTR_cert, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj)} },
|
||||
{ MP_QSTR_server_side, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} },
|
||||
{ MP_QSTR_server_hostname, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_PTR(&mp_const_none_obj)} },
|
||||
{ MP_QSTR_do_handshake, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = true} },
|
||||
|
||||
1
extmod/nimble/bsp/bsp.h
Normal file
1
extmod/nimble/bsp/bsp.h
Normal file
@ -0,0 +1 @@
|
||||
// empty
|
||||
1
extmod/nimble/hal/hal_gpio.h
Normal file
1
extmod/nimble/hal/hal_gpio.h
Normal file
@ -0,0 +1 @@
|
||||
// empty
|
||||
18
extmod/nimble/hal/hal_uart.h
Normal file
18
extmod/nimble/hal/hal_uart.h
Normal file
@ -0,0 +1,18 @@
|
||||
#ifndef MICROPY_INCLUDED_EXTMOD_NIMBLE_HAL_HAL_UART_H
|
||||
#define MICROPY_INCLUDED_EXTMOD_NIMBLE_HAL_HAL_UART_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define SYSINIT_PANIC_ASSERT_MSG(cond, msg)
|
||||
|
||||
#define HAL_UART_PARITY_NONE (0)
|
||||
|
||||
typedef int (*hal_uart_tx_cb_t)(void *arg);
|
||||
typedef int (*hal_uart_rx_cb_t)(void *arg, uint8_t data);
|
||||
|
||||
int hal_uart_init_cbs(uint32_t port, hal_uart_tx_cb_t tx_cb, void *tx_arg, hal_uart_rx_cb_t rx_cb, void *rx_arg);
|
||||
int hal_uart_config(uint32_t port, uint32_t baud, uint32_t bits, uint32_t stop, uint32_t parity, uint32_t flow);
|
||||
void hal_uart_start_tx(uint32_t port);
|
||||
int hal_uart_close(uint32_t port);
|
||||
|
||||
#endif // MICROPY_INCLUDED_EXTMOD_NIMBLE_HAL_HAL_UART_H
|
||||
95
extmod/nimble/nimble.mk
Normal file
95
extmod/nimble/nimble.mk
Normal file
@ -0,0 +1,95 @@
|
||||
# Makefile directives for Apache mynewt nimble BLE component
|
||||
|
||||
ifeq ($(MICROPY_BLUETOOTH_NIMBLE),1)
|
||||
|
||||
NIMBLE_LIB_DIR = lib/mynewt-nimble
|
||||
NIMBLE_EXTMOD_DIR = extmod/nimble
|
||||
|
||||
SRC_LIB += $(addprefix $(NIMBLE_LIB_DIR)/, \
|
||||
$(addprefix ext/tinycrypt/src/, \
|
||||
aes_encrypt.c \
|
||||
cmac_mode.c \
|
||||
ecc.c \
|
||||
ecc_dh.c \
|
||||
utils.c \
|
||||
) \
|
||||
nimble/host/services/gap/src/ble_svc_gap.c \
|
||||
nimble/host/services/gatt/src/ble_svc_gatt.c \
|
||||
$(addprefix nimble/host/src/, \
|
||||
ble_att.c \
|
||||
ble_att_clt.c \
|
||||
ble_att_cmd.c \
|
||||
ble_att_svr.c \
|
||||
ble_eddystone.c \
|
||||
ble_gap.c \
|
||||
ble_gattc.c \
|
||||
ble_gatts.c \
|
||||
ble_hs_adv.c \
|
||||
ble_hs_atomic.c \
|
||||
ble_hs.c \
|
||||
ble_hs_cfg.c \
|
||||
ble_hs_conn.c \
|
||||
ble_hs_dbg.c \
|
||||
ble_hs_flow.c \
|
||||
ble_hs_hci.c \
|
||||
ble_hs_hci_cmd.c \
|
||||
ble_hs_hci_evt.c \
|
||||
ble_hs_hci_util.c \
|
||||
ble_hs_id.c \
|
||||
ble_hs_log.c \
|
||||
ble_hs_mbuf.c \
|
||||
ble_hs_misc.c \
|
||||
ble_hs_mqueue.c \
|
||||
ble_hs_pvcy.c \
|
||||
ble_hs_startup.c \
|
||||
ble_hs_stop.c \
|
||||
ble_ibeacon.c \
|
||||
ble_l2cap.c \
|
||||
ble_l2cap_coc.c \
|
||||
ble_l2cap_sig.c \
|
||||
ble_l2cap_sig_cmd.c \
|
||||
ble_monitor.c \
|
||||
ble_sm_alg.c \
|
||||
ble_sm.c \
|
||||
ble_sm_cmd.c \
|
||||
ble_sm_lgcy.c \
|
||||
ble_sm_sc.c \
|
||||
ble_store.c \
|
||||
ble_store_util.c \
|
||||
ble_uuid.c \
|
||||
) \
|
||||
nimble/host/store/ram/src/ble_store_ram.c \
|
||||
nimble/host/util/src/addr.c \
|
||||
nimble/transport/uart/src/ble_hci_uart.c \
|
||||
$(addprefix porting/nimble/src/, \
|
||||
endian.c \
|
||||
mem.c \
|
||||
nimble_port.c \
|
||||
os_mbuf.c \
|
||||
os_mempool.c \
|
||||
os_msys_init.c \
|
||||
) \
|
||||
)
|
||||
|
||||
EXTMOD_SRC_C += $(addprefix $(NIMBLE_EXTMOD_DIR)/, \
|
||||
nimble/npl_os.c \
|
||||
nimble/hci_uart.c \
|
||||
)
|
||||
|
||||
CFLAGS_MOD += -DMICROPY_BLUETOOTH_NIMBLE=1
|
||||
|
||||
INC += -I$(TOP)/$(NIMBLE_EXTMOD_DIR)
|
||||
INC += -I$(TOP)/$(NIMBLE_LIB_DIR)
|
||||
INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/ext/tinycrypt/include
|
||||
INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/nimble/host/include
|
||||
INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/nimble/host/services/gap/include
|
||||
INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/nimble/host/services/gatt/include
|
||||
INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/nimble/host/store/ram/include
|
||||
INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/nimble/host/util/include
|
||||
INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/nimble/include
|
||||
INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/nimble/transport/uart/include
|
||||
INC += -I$(TOP)/$(NIMBLE_LIB_DIR)/porting/nimble/include
|
||||
|
||||
$(BUILD)/$(NIMBLE_LIB_DIR)/%.o: CFLAGS += -Wno-maybe-uninitialized -Wno-pointer-arith -Wno-unused-but-set-variable -Wno-format
|
||||
|
||||
endif
|
||||
88
extmod/nimble/nimble/hci_uart.c
Normal file
88
extmod/nimble/nimble/hci_uart.c
Normal file
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2018-2019 Damien P. George
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "py/runtime.h"
|
||||
#include "py/mphal.h"
|
||||
#include "pin_static_af.h"
|
||||
#include "nimble/ble.h"
|
||||
#include "hal/hal_uart.h"
|
||||
#include "hci_uart.h"
|
||||
|
||||
#if MICROPY_BLUETOOTH_NIMBLE
|
||||
|
||||
/******************************************************************************/
|
||||
// Bindings Uart to Nimble
|
||||
uint8_t bt_hci_cmd_buf[4 + 256];
|
||||
|
||||
static hal_uart_tx_cb_t hal_uart_tx_cb;
|
||||
static void *hal_uart_tx_arg;
|
||||
static hal_uart_rx_cb_t hal_uart_rx_cb;
|
||||
static void *hal_uart_rx_arg;
|
||||
|
||||
int hal_uart_init_cbs(uint32_t port, hal_uart_tx_cb_t tx_cb, void *tx_arg, hal_uart_rx_cb_t rx_cb, void *rx_arg) {
|
||||
hal_uart_tx_cb = tx_cb;
|
||||
hal_uart_tx_arg = tx_arg;
|
||||
hal_uart_rx_cb = rx_cb;
|
||||
hal_uart_rx_arg = rx_arg;
|
||||
return 0; // success
|
||||
}
|
||||
|
||||
int hal_uart_config(uint32_t port, uint32_t baudrate, uint32_t bits, uint32_t stop, uint32_t parity, uint32_t flow) {
|
||||
nimble_hci_uart_configure(port);
|
||||
nimble_hci_uart_set_baudrate(baudrate);
|
||||
return nimble_hci_uart_activate();
|
||||
}
|
||||
|
||||
void hal_uart_start_tx(uint32_t port) {
|
||||
size_t len = 0;
|
||||
for (;;) {
|
||||
int data = hal_uart_tx_cb(hal_uart_tx_arg);
|
||||
if (data == -1) {
|
||||
break;
|
||||
}
|
||||
bt_hci_cmd_buf[len++] = data;
|
||||
}
|
||||
|
||||
#if 0
|
||||
printf("[% 8d] BTUTX: %02x", mp_hal_ticks_ms(), hci_cmd_buf[0]);
|
||||
for (int i = 1; i < len; ++i) {
|
||||
printf(":%02x", hci_cmd_buf[i]);
|
||||
}
|
||||
printf("\n");
|
||||
#endif
|
||||
|
||||
nimble_hci_uart_tx_strn((void*)bt_hci_cmd_buf, len);
|
||||
}
|
||||
|
||||
int hal_uart_close(uint32_t port) {
|
||||
return 0; // success
|
||||
}
|
||||
|
||||
void nimble_uart_process(void) {
|
||||
nimble_hci_uart_rx(hal_uart_rx_cb, hal_uart_rx_arg);
|
||||
}
|
||||
|
||||
#endif // MICROPY_BLUETOOTH_NIMBLE
|
||||
43
extmod/nimble/nimble/hci_uart.h
Normal file
43
extmod/nimble/nimble/hci_uart.h
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Damien P. George
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#ifndef MICROPY_INCLUDED_EXTMOD_NIMBLE_NIMBLE_HCI_UART_H
|
||||
#define MICROPY_INCLUDED_EXTMOD_NIMBLE_NIMBLE_HCI_UART_H
|
||||
|
||||
#include "extmod/nimble/hal/hal_uart.h"
|
||||
|
||||
// To be implemented by the port.
|
||||
|
||||
int nimble_hci_uart_configure(uint32_t port);
|
||||
|
||||
// This will default to MICROPY_HW_BLE_UART_BAUDRATE, but can be updated later.
|
||||
int nimble_hci_uart_set_baudrate(uint32_t baudrate);
|
||||
|
||||
int nimble_hci_uart_activate(void);
|
||||
|
||||
void nimble_hci_uart_rx(hal_uart_rx_cb_t rx_cb, void *rx_arg);
|
||||
void nimble_hci_uart_tx_strn(const char *str, uint len);
|
||||
|
||||
#endif // MICROPY_INCLUDED_EXTMOD_NIMBLE_NIMBLE_HCI_UART_H
|
||||
66
extmod/nimble/nimble/nimble_npl_os.h
Normal file
66
extmod/nimble/nimble/nimble_npl_os.h
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2018-2019 Damien P. George
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef MICROPY_INCLUDED_STM32_NIMBLE_NIMBLE_NPL_OS_H
|
||||
#define MICROPY_INCLUDED_STM32_NIMBLE_NIMBLE_NPL_OS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define BLE_NPL_OS_ALIGNMENT (4)
|
||||
#define BLE_NPL_TIME_FOREVER (0xffffffff)
|
||||
|
||||
typedef uint32_t ble_npl_time_t;
|
||||
typedef int32_t ble_npl_stime_t;
|
||||
|
||||
struct ble_npl_event {
|
||||
ble_npl_event_fn *fn;
|
||||
void *arg;
|
||||
struct ble_npl_event *prev;
|
||||
struct ble_npl_event *next;
|
||||
};
|
||||
|
||||
struct ble_npl_eventq {
|
||||
struct ble_npl_event *head;
|
||||
struct ble_npl_eventq *nextq;
|
||||
};
|
||||
|
||||
struct ble_npl_callout {
|
||||
bool active;
|
||||
uint32_t ticks;
|
||||
struct ble_npl_eventq *evq;
|
||||
struct ble_npl_event ev;
|
||||
struct ble_npl_callout *nextc;
|
||||
};
|
||||
|
||||
struct ble_npl_mutex {
|
||||
volatile uint8_t locked;
|
||||
};
|
||||
|
||||
struct ble_npl_sem {
|
||||
volatile uint16_t count;
|
||||
};
|
||||
|
||||
#endif // MICROPY_INCLUDED_STM32_NIMBLE_NIMBLE_NPL_OS_H
|
||||
387
extmod/nimble/nimble/npl_os.c
Normal file
387
extmod/nimble/nimble/npl_os.c
Normal file
@ -0,0 +1,387 @@
|
||||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2018-2019 Damien P. George
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "py/mphal.h"
|
||||
#include "py/runtime.h"
|
||||
#include "nimble/ble.h"
|
||||
#include "nimble/nimble_npl.h"
|
||||
|
||||
#define DEBUG_OS_printf(...) //printf(__VA_ARGS__)
|
||||
#define DEBUG_MALLOC_printf(...) //printf(__VA_ARGS__)
|
||||
#define DEBUG_EVENT_printf(...) //printf(__VA_ARGS__)
|
||||
#define DEBUG_MUTEX_printf(...) //printf(__VA_ARGS__)
|
||||
#define DEBUG_SEM_printf(...) //printf(__VA_ARGS__)
|
||||
#define DEBUG_CALLOUT_printf(...) //printf(__VA_ARGS__)
|
||||
#define DEBUG_TIME_printf(...) //printf(__VA_ARGS__)
|
||||
#define DEBUG_CRIT_printf(...) //printf(__VA_ARGS__)
|
||||
|
||||
bool ble_npl_os_started(void) {
|
||||
DEBUG_OS_printf("ble_npl_os_started\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
void *ble_npl_get_current_task_id(void) {
|
||||
DEBUG_OS_printf("ble_npl_get_current_task_id\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
// malloc
|
||||
|
||||
// Maintain a linked list of heap memory that we've passed to Nimble,
|
||||
// discoverable via the bluetooth_nimble_memory root pointer.
|
||||
|
||||
// MP_STATE_PORT(bluetooth_nimble_memory) is a pointer to [next, prev, data...].
|
||||
|
||||
// TODO: This is duplicated from mbedtls. Perhaps make this a generic feature?
|
||||
void *m_malloc_bluetooth(size_t size) {
|
||||
void **ptr = m_malloc0(size + 2 * sizeof(uintptr_t));
|
||||
if (MP_STATE_PORT(bluetooth_nimble_memory) != NULL) {
|
||||
MP_STATE_PORT(bluetooth_nimble_memory)[0] = ptr;
|
||||
}
|
||||
ptr[0] = NULL;
|
||||
ptr[1] = MP_STATE_PORT(bluetooth_nimble_memory);
|
||||
MP_STATE_PORT(bluetooth_nimble_memory) = ptr;
|
||||
return &ptr[2];
|
||||
}
|
||||
|
||||
void m_free_bluetooth(void *ptr_in) {
|
||||
void **ptr = &((void**)ptr_in)[-2];
|
||||
if (ptr[1] != NULL) {
|
||||
((void**)ptr[1])[0] = ptr[0];
|
||||
}
|
||||
if (ptr[0] != NULL) {
|
||||
((void**)ptr[0])[1] = ptr[1];
|
||||
} else {
|
||||
MP_STATE_PORT(bluetooth_nimble_memory) = ptr[1];
|
||||
}
|
||||
m_free(ptr);
|
||||
}
|
||||
|
||||
// Check if a nimble ptr is tracked.
|
||||
// If it isn't, that means that it's from a previous soft-reset cycle.
|
||||
STATIC bool is_valid_nimble_malloc(void *ptr) {
|
||||
DEBUG_MALLOC_printf("NIMBLE is_valid_nimble_malloc(%p)\n", ptr);
|
||||
void** search = MP_STATE_PORT(bluetooth_nimble_memory);
|
||||
while (search) {
|
||||
if (&search[2] == ptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
search = (void**)search[1];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void *nimble_malloc(size_t size) {
|
||||
DEBUG_MALLOC_printf("NIMBLE malloc(%u)\n", (uint)size);
|
||||
void* ptr = m_malloc_bluetooth(size);
|
||||
DEBUG_MALLOC_printf(" --> %p\n", ptr);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
// Only free if it's still a valid pointer.
|
||||
void nimble_free(void *ptr) {
|
||||
DEBUG_MALLOC_printf("NIMBLE free(%p)\n", ptr);
|
||||
if (ptr && is_valid_nimble_malloc(ptr)) {
|
||||
m_free_bluetooth(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Only realloc if it's still a valid pointer. Otherwise just malloc.
|
||||
void *nimble_realloc(void *ptr, size_t size) {
|
||||
// This is only used by ble_gatts.c to grow the queue of pending services to be registered.
|
||||
DEBUG_MALLOC_printf("NIMBLE realloc(%p, %u)\n", ptr, (uint)size);
|
||||
void *ptr2 = nimble_malloc(size);
|
||||
if (ptr && is_valid_nimble_malloc(ptr)) {
|
||||
// If it's a realloc and we still have the old data, then copy it.
|
||||
// This will happen as we add services.
|
||||
memcpy(ptr2, ptr, size);
|
||||
m_free_bluetooth(ptr);
|
||||
}
|
||||
return ptr2;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
// EVENTQ
|
||||
|
||||
struct ble_npl_eventq *global_eventq = NULL;
|
||||
|
||||
void os_eventq_run_all(void) {
|
||||
for (struct ble_npl_eventq *evq = global_eventq; evq != NULL; evq = evq->nextq) {
|
||||
while (evq->head != NULL) {
|
||||
struct ble_npl_event *ev = evq->head;
|
||||
evq->head = ev->next;
|
||||
if (ev->next) {
|
||||
ev->next->prev = NULL;
|
||||
ev->next = NULL;
|
||||
}
|
||||
ev->prev = NULL;
|
||||
DEBUG_EVENT_printf("event_run(%p)\n", ev);
|
||||
ev->fn(ev);
|
||||
DEBUG_EVENT_printf("event_run(%p) done\n", ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ble_npl_eventq_init(struct ble_npl_eventq *evq) {
|
||||
DEBUG_EVENT_printf("ble_npl_eventq_init(%p)\n", evq);
|
||||
evq->head = NULL;
|
||||
struct ble_npl_eventq **evq2;
|
||||
for (evq2 = &global_eventq; *evq2 != NULL; evq2 = &(*evq2)->nextq) {
|
||||
}
|
||||
*evq2 = evq;
|
||||
evq->nextq = NULL;
|
||||
}
|
||||
|
||||
void ble_npl_eventq_put(struct ble_npl_eventq *evq, struct ble_npl_event *ev) {
|
||||
DEBUG_EVENT_printf("ble_npl_eventq_put(%p, %p (%p, %p))\n", evq, ev, ev->fn, ev->arg);
|
||||
ev->next = NULL;
|
||||
if (evq->head == NULL) {
|
||||
evq->head = ev;
|
||||
ev->prev = NULL;
|
||||
} else {
|
||||
struct ble_npl_event *ev2 = evq->head;
|
||||
while (true) {
|
||||
if (ev2 == ev) {
|
||||
DEBUG_EVENT_printf(" --> already in queue\n");
|
||||
return;
|
||||
}
|
||||
if (ev2->next == NULL) {
|
||||
break;
|
||||
}
|
||||
DEBUG_EVENT_printf(" --> %p\n", ev2->next);
|
||||
ev2 = ev2->next;
|
||||
}
|
||||
ev2->next = ev;
|
||||
ev->prev = ev2;
|
||||
}
|
||||
}
|
||||
|
||||
void ble_npl_event_init(struct ble_npl_event *ev, ble_npl_event_fn *fn, void *arg) {
|
||||
DEBUG_EVENT_printf("ble_npl_event_init(%p, %p, %p)\n", ev, fn, arg);
|
||||
ev->fn = fn;
|
||||
ev->arg = arg;
|
||||
ev->next = NULL;
|
||||
}
|
||||
|
||||
void *ble_npl_event_get_arg(struct ble_npl_event *ev) {
|
||||
DEBUG_EVENT_printf("ble_npl_event_get_arg(%p) -> %p\n", ev, ev->arg);
|
||||
return ev->arg;
|
||||
}
|
||||
|
||||
void ble_npl_event_set_arg(struct ble_npl_event *ev, void *arg) {
|
||||
DEBUG_EVENT_printf("ble_npl_event_set_arg(%p, %p)\n", ev, arg);
|
||||
ev->arg = arg;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
// MUTEX
|
||||
|
||||
ble_npl_error_t ble_npl_mutex_init(struct ble_npl_mutex *mu) {
|
||||
DEBUG_MUTEX_printf("ble_npl_mutex_init(%p)\n", mu);
|
||||
mu->locked = 0;
|
||||
return BLE_NPL_OK;
|
||||
}
|
||||
|
||||
ble_npl_error_t ble_npl_mutex_pend(struct ble_npl_mutex *mu, ble_npl_time_t timeout) {
|
||||
DEBUG_MUTEX_printf("ble_npl_mutex_pend(%p, %u) locked=%u\n", mu, (uint)timeout, (uint)mu->locked);
|
||||
mu->locked = 1;
|
||||
return BLE_NPL_OK;
|
||||
}
|
||||
|
||||
ble_npl_error_t ble_npl_mutex_release(struct ble_npl_mutex *mu) {
|
||||
DEBUG_MUTEX_printf("ble_npl_mutex_release(%p) locked=%u\n", mu, (uint)mu->locked);
|
||||
mu->locked = 0;
|
||||
return BLE_NPL_OK;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
// SEM
|
||||
|
||||
ble_npl_error_t ble_npl_sem_init(struct ble_npl_sem *sem, uint16_t tokens) {
|
||||
DEBUG_SEM_printf("ble_npl_sem_init(%p, %u)\n", sem, (uint)tokens);
|
||||
sem->count = tokens;
|
||||
return BLE_NPL_OK;
|
||||
}
|
||||
|
||||
ble_npl_error_t ble_npl_sem_pend(struct ble_npl_sem *sem, ble_npl_time_t timeout) {
|
||||
DEBUG_SEM_printf("ble_npl_sem_pend(%p, %u) count=%u\n", sem, (uint)timeout, (uint)sem->count);
|
||||
if (sem->count == 0) {
|
||||
uint32_t t0 = mp_hal_ticks_ms();
|
||||
while (sem->count == 0 && mp_hal_ticks_ms() - t0 < timeout) {
|
||||
extern void nimble_uart_process(void);
|
||||
nimble_uart_process();
|
||||
if (sem->count != 0) {
|
||||
break;
|
||||
}
|
||||
__WFI();
|
||||
}
|
||||
if (sem->count == 0) {
|
||||
printf("timeout\n");
|
||||
return BLE_NPL_TIMEOUT;
|
||||
}
|
||||
DEBUG_SEM_printf("got response in %u ms\n", (int)(mp_hal_ticks_ms() - t0));
|
||||
}
|
||||
sem->count -= 1;
|
||||
return BLE_NPL_OK;
|
||||
}
|
||||
|
||||
ble_npl_error_t ble_npl_sem_release(struct ble_npl_sem *sem) {
|
||||
DEBUG_SEM_printf("ble_npl_sem_release(%p)\n", sem);
|
||||
sem->count += 1;
|
||||
return BLE_NPL_OK;
|
||||
}
|
||||
|
||||
uint16_t ble_npl_sem_get_count(struct ble_npl_sem *sem) {
|
||||
DEBUG_SEM_printf("ble_npl_sem_get_count(%p)\n", sem);
|
||||
return sem->count;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
// CALLOUT
|
||||
|
||||
static struct ble_npl_callout *global_callout = NULL;
|
||||
|
||||
void os_callout_process(void) {
|
||||
uint32_t tnow = mp_hal_ticks_ms();
|
||||
for (struct ble_npl_callout *c = global_callout; c != NULL; c = c->nextc) {
|
||||
if (!c->active) {
|
||||
continue;
|
||||
}
|
||||
if ((int32_t)(tnow - c->ticks) >= 0) {
|
||||
DEBUG_CALLOUT_printf("callout_run(%p) tnow=%u ticks=%u evq=%p\n", c, (uint)tnow, (uint)c->ticks, c->evq);
|
||||
c->active = false;
|
||||
if (c->evq) {
|
||||
ble_npl_eventq_put(c->evq, &c->ev);
|
||||
} else {
|
||||
c->ev.fn(&c->ev);
|
||||
}
|
||||
DEBUG_CALLOUT_printf("callout_run(%p) done\n", c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ble_npl_callout_init(struct ble_npl_callout *c, struct ble_npl_eventq *evq, ble_npl_event_fn *ev_cb, void *ev_arg) {
|
||||
DEBUG_CALLOUT_printf("ble_npl_callout_init(%p, %p, %p, %p)\n", c, evq, ev_cb, ev_arg);
|
||||
c->active = false;
|
||||
c->ticks = 0;
|
||||
c->evq = evq;
|
||||
ble_npl_event_init(&c->ev, ev_cb, ev_arg);
|
||||
|
||||
struct ble_npl_callout **c2;
|
||||
for (c2 = &global_callout; *c2 != NULL; c2 = &(*c2)->nextc) {
|
||||
if (c == *c2) {
|
||||
// callout already in linked list so don't link it in again
|
||||
return;
|
||||
}
|
||||
}
|
||||
*c2 = c;
|
||||
c->nextc = NULL;
|
||||
}
|
||||
|
||||
ble_npl_error_t ble_npl_callout_reset(struct ble_npl_callout *c, ble_npl_time_t ticks) {
|
||||
DEBUG_CALLOUT_printf("ble_npl_callout_reset(%p, %u) tnow=%u\n", c, (uint)ticks, (uint)mp_hal_ticks_ms());
|
||||
c->active = true;
|
||||
c->ticks = ble_npl_time_get() + ticks;
|
||||
return BLE_NPL_OK;
|
||||
}
|
||||
|
||||
void ble_npl_callout_stop(struct ble_npl_callout *c) {
|
||||
DEBUG_CALLOUT_printf("ble_npl_callout_stop(%p)\n", c);
|
||||
c->active = false;
|
||||
}
|
||||
|
||||
bool ble_npl_callout_is_active(struct ble_npl_callout *c) {
|
||||
DEBUG_CALLOUT_printf("ble_npl_callout_is_active(%p)\n", c);
|
||||
return c->active;
|
||||
}
|
||||
|
||||
ble_npl_time_t ble_npl_callout_get_ticks(struct ble_npl_callout *c) {
|
||||
DEBUG_CALLOUT_printf("ble_npl_callout_get_ticks(%p)\n", c);
|
||||
return c->ticks;
|
||||
}
|
||||
|
||||
ble_npl_time_t ble_npl_callout_remaining_ticks(struct ble_npl_callout *c, ble_npl_time_t now) {
|
||||
DEBUG_CALLOUT_printf("ble_npl_callout_remaining_ticks(%p, %u)\n", c, (uint)now);
|
||||
if (c->ticks > now) {
|
||||
return c->ticks - now;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void *ble_npl_callout_get_arg(struct ble_npl_callout *c) {
|
||||
DEBUG_CALLOUT_printf("ble_npl_callout_get_arg(%p)\n", c);
|
||||
return ble_npl_event_get_arg(&c->ev);
|
||||
}
|
||||
|
||||
void ble_npl_callout_set_arg(struct ble_npl_callout *c, void *arg) {
|
||||
DEBUG_CALLOUT_printf("ble_npl_callout_set_arg(%p, %p)\n", c, arg);
|
||||
ble_npl_event_set_arg(&c->ev, arg);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
// TIME
|
||||
|
||||
uint32_t ble_npl_time_get(void) {
|
||||
DEBUG_TIME_printf("ble_npl_time_get -> %u\n", (uint)mp_hal_ticks_ms());
|
||||
return mp_hal_ticks_ms();
|
||||
}
|
||||
|
||||
ble_npl_error_t ble_npl_time_ms_to_ticks(uint32_t ms, ble_npl_time_t *out_ticks) {
|
||||
DEBUG_TIME_printf("ble_npl_time_ms_to_ticks(%u)\n", (uint)ms);
|
||||
*out_ticks = ms;
|
||||
return BLE_NPL_OK;
|
||||
}
|
||||
|
||||
ble_npl_time_t ble_npl_time_ms_to_ticks32(uint32_t ms) {
|
||||
DEBUG_TIME_printf("ble_npl_time_ms_to_ticks32(%u)\n", (uint)ms);
|
||||
return ms;
|
||||
}
|
||||
|
||||
uint32_t ble_npl_time_ticks_to_ms32(ble_npl_time_t ticks) {
|
||||
DEBUG_TIME_printf("ble_npl_time_ticks_to_ms32(%u)\n", (uint)ticks);
|
||||
return ticks;
|
||||
}
|
||||
|
||||
void ble_npl_time_delay(ble_npl_time_t ticks) {
|
||||
mp_hal_delay_ms(ticks + 1);
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
// CRITICAL
|
||||
|
||||
uint32_t ble_npl_hw_enter_critical(void) {
|
||||
DEBUG_CRIT_printf("ble_npl_hw_enter_critical()\n");
|
||||
return raise_irq_pri(15);
|
||||
}
|
||||
|
||||
void ble_npl_hw_exit_critical(uint32_t ctx) {
|
||||
DEBUG_CRIT_printf("ble_npl_hw_exit_critical(%u)\n", (uint)ctx);
|
||||
restore_irq_pri(ctx);
|
||||
}
|
||||
160
extmod/nimble/syscfg/syscfg.h
Normal file
160
extmod/nimble/syscfg/syscfg.h
Normal file
@ -0,0 +1,160 @@
|
||||
#ifndef MICROPY_INCLUDED_EXTMOD_NIMBLE_SYSCFG_H
|
||||
#define MICROPY_INCLUDED_EXTMOD_NIMBLE_SYSCFG_H
|
||||
|
||||
#include "py/mphal.h"
|
||||
#include "uart.h"
|
||||
|
||||
void *nimble_malloc(size_t size);
|
||||
void nimble_free(void *ptr);
|
||||
void *nimble_realloc(void *ptr, size_t size);
|
||||
|
||||
#define malloc(size) nimble_malloc(size)
|
||||
#define free(ptr) nimble_free(ptr)
|
||||
#define realloc(ptr, size) nimble_realloc(ptr, size)
|
||||
|
||||
int nimble_sprintf(char *str, const char *fmt, ...);
|
||||
#define sprintf(str, fmt, ...) nimble_sprintf(str, fmt, __VA_ARGS__)
|
||||
|
||||
#define MYNEWT_VAL(x) MYNEWT_VAL_ ## x
|
||||
|
||||
#define MYNEWT_VAL_LOG_LEVEL (255)
|
||||
|
||||
/*** compiler/arm-none-eabi-m4 */
|
||||
#define MYNEWT_VAL_HARDFLOAT (1)
|
||||
|
||||
/*** kernel/os */
|
||||
#define MYNEWT_VAL_FLOAT_USER (0)
|
||||
#define MYNEWT_VAL_MSYS_1_BLOCK_COUNT (12)
|
||||
#define MYNEWT_VAL_MSYS_1_BLOCK_SIZE (292)
|
||||
#define MYNEWT_VAL_MSYS_2_BLOCK_COUNT (0)
|
||||
#define MYNEWT_VAL_MSYS_2_BLOCK_SIZE (0)
|
||||
#define MYNEWT_VAL_OS_CPUTIME_FREQ (1000000)
|
||||
#define MYNEWT_VAL_OS_CPUTIME_TIMER_NUM (0)
|
||||
#define MYNEWT_VAL_OS_CTX_SW_STACK_CHECK (0)
|
||||
#define MYNEWT_VAL_OS_CTX_SW_STACK_GUARD (4)
|
||||
#define MYNEWT_VAL_OS_MAIN_STACK_SIZE (1024)
|
||||
#define MYNEWT_VAL_OS_MAIN_TASK_PRIO (127)
|
||||
#define MYNEWT_VAL_OS_MEMPOOL_CHECK (0)
|
||||
#define MYNEWT_VAL_OS_MEMPOOL_POISON (0)
|
||||
|
||||
/*** nimble */
|
||||
#define MYNEWT_VAL_BLE_EXT_ADV (0)
|
||||
#define MYNEWT_VAL_BLE_EXT_ADV_MAX_SIZE (31)
|
||||
#define MYNEWT_VAL_BLE_MAX_CONNECTIONS (4)
|
||||
#define MYNEWT_VAL_BLE_MULTI_ADV_INSTANCES (0)
|
||||
#define MYNEWT_VAL_BLE_ROLE_BROADCASTER (1)
|
||||
#define MYNEWT_VAL_BLE_ROLE_CENTRAL (1)
|
||||
#define MYNEWT_VAL_BLE_ROLE_OBSERVER (1)
|
||||
#define MYNEWT_VAL_BLE_ROLE_PERIPHERAL (1)
|
||||
#define MYNEWT_VAL_BLE_WHITELIST (1)
|
||||
|
||||
/*** nimble/host */
|
||||
#define MYNEWT_VAL_BLE_ATT_PREFERRED_MTU (256)
|
||||
#define MYNEWT_VAL_BLE_ATT_SVR_FIND_INFO (1)
|
||||
#define MYNEWT_VAL_BLE_ATT_SVR_FIND_TYPE (1)
|
||||
#define MYNEWT_VAL_BLE_ATT_SVR_INDICATE (1)
|
||||
#define MYNEWT_VAL_BLE_ATT_SVR_MAX_PREP_ENTRIES (64)
|
||||
#define MYNEWT_VAL_BLE_ATT_SVR_NOTIFY (1)
|
||||
#define MYNEWT_VAL_BLE_ATT_SVR_QUEUED_WRITE (1)
|
||||
#define MYNEWT_VAL_BLE_ATT_SVR_QUEUED_WRITE_TMO (30000)
|
||||
#define MYNEWT_VAL_BLE_ATT_SVR_READ (1)
|
||||
#define MYNEWT_VAL_BLE_ATT_SVR_READ_BLOB (1)
|
||||
#define MYNEWT_VAL_BLE_ATT_SVR_READ_GROUP_TYPE (1)
|
||||
#define MYNEWT_VAL_BLE_ATT_SVR_READ_MULT (1)
|
||||
#define MYNEWT_VAL_BLE_ATT_SVR_READ_TYPE (1)
|
||||
#define MYNEWT_VAL_BLE_ATT_SVR_SIGNED_WRITE (1)
|
||||
#define MYNEWT_VAL_BLE_ATT_SVR_WRITE (1)
|
||||
#define MYNEWT_VAL_BLE_ATT_SVR_WRITE_NO_RSP (1)
|
||||
#define MYNEWT_VAL_BLE_GAP_MAX_PENDING_CONN_PARAM_UPDATE (1)
|
||||
#define MYNEWT_VAL_BLE_GATT_DISC_ALL_CHRS (MYNEWT_VAL_BLE_ROLE_CENTRAL)
|
||||
#define MYNEWT_VAL_BLE_GATT_DISC_ALL_DSCS (MYNEWT_VAL_BLE_ROLE_CENTRAL)
|
||||
#define MYNEWT_VAL_BLE_GATT_DISC_ALL_SVCS (MYNEWT_VAL_BLE_ROLE_CENTRAL)
|
||||
#define MYNEWT_VAL_BLE_GATT_DISC_CHR_UUID (MYNEWT_VAL_BLE_ROLE_CENTRAL)
|
||||
#define MYNEWT_VAL_BLE_GATT_DISC_SVC_UUID (MYNEWT_VAL_BLE_ROLE_CENTRAL)
|
||||
#define MYNEWT_VAL_BLE_GATT_FIND_INC_SVCS (MYNEWT_VAL_BLE_ROLE_CENTRAL)
|
||||
#define MYNEWT_VAL_BLE_GATT_INDICATE (1)
|
||||
#define MYNEWT_VAL_BLE_GATT_MAX_PROCS (4)
|
||||
#define MYNEWT_VAL_BLE_GATT_NOTIFY (1)
|
||||
#define MYNEWT_VAL_BLE_GATT_READ (MYNEWT_VAL_BLE_ROLE_CENTRAL)
|
||||
#define MYNEWT_VAL_BLE_GATT_READ_LONG (MYNEWT_VAL_BLE_ROLE_CENTRAL)
|
||||
#define MYNEWT_VAL_BLE_GATT_READ_MAX_ATTRS (8)
|
||||
#define MYNEWT_VAL_BLE_GATT_READ_MULT (MYNEWT_VAL_BLE_ROLE_CENTRAL)
|
||||
#define MYNEWT_VAL_BLE_GATT_READ_UUID (MYNEWT_VAL_BLE_ROLE_CENTRAL)
|
||||
#define MYNEWT_VAL_BLE_GATT_RESUME_RATE (1000)
|
||||
#define MYNEWT_VAL_BLE_GATT_SIGNED_WRITE (MYNEWT_VAL_BLE_ROLE_CENTRAL)
|
||||
#define MYNEWT_VAL_BLE_GATT_WRITE (MYNEWT_VAL_BLE_ROLE_CENTRAL)
|
||||
#define MYNEWT_VAL_BLE_GATT_WRITE_LONG (MYNEWT_VAL_BLE_ROLE_CENTRAL)
|
||||
#define MYNEWT_VAL_BLE_GATT_WRITE_MAX_ATTRS (4)
|
||||
#define MYNEWT_VAL_BLE_GATT_WRITE_NO_RSP (MYNEWT_VAL_BLE_ROLE_CENTRAL)
|
||||
#define MYNEWT_VAL_BLE_GATT_WRITE_RELIABLE (MYNEWT_VAL_BLE_ROLE_CENTRAL)
|
||||
#define MYNEWT_VAL_BLE_HOST (1)
|
||||
#define MYNEWT_VAL_BLE_HS_DEBUG (0)
|
||||
#define MYNEWT_VAL_BLE_HS_FLOW_CTRL (0)
|
||||
#define MYNEWT_VAL_BLE_HS_FLOW_CTRL_ITVL (1000)
|
||||
#define MYNEWT_VAL_BLE_HS_FLOW_CTRL_THRESH (2)
|
||||
#define MYNEWT_VAL_BLE_HS_FLOW_CTRL_TX_ON_DISCONNECT (0)
|
||||
#define MYNEWT_VAL_BLE_HS_PHONY_HCI_ACKS (0)
|
||||
#define MYNEWT_VAL_BLE_HS_REQUIRE_OS (1)
|
||||
#define MYNEWT_VAL_BLE_L2CAP_COC_MAX_NUM (0)
|
||||
#define MYNEWT_VAL_BLE_L2CAP_JOIN_RX_FRAGS (1)
|
||||
#define MYNEWT_VAL_BLE_L2CAP_MAX_CHANS (3*MYNEWT_VAL_BLE_MAX_CONNECTIONS)
|
||||
#define MYNEWT_VAL_BLE_L2CAP_RX_FRAG_TIMEOUT (30000)
|
||||
#define MYNEWT_VAL_BLE_L2CAP_SIG_MAX_PROCS (1)
|
||||
#define MYNEWT_VAL_BLE_MONITOR_CONSOLE_BUFFER_SIZE (128)
|
||||
#define MYNEWT_VAL_BLE_MONITOR_RTT (0)
|
||||
#define MYNEWT_VAL_BLE_MONITOR_RTT_BUFFERED (1)
|
||||
#define MYNEWT_VAL_BLE_MONITOR_RTT_BUFFER_NAME ("monitor")
|
||||
#define MYNEWT_VAL_BLE_MONITOR_RTT_BUFFER_SIZE (256)
|
||||
#define MYNEWT_VAL_BLE_MONITOR_UART (0)
|
||||
#define MYNEWT_VAL_BLE_MONITOR_UART_BAUDRATE (1000000)
|
||||
#define MYNEWT_VAL_BLE_MONITOR_UART_BUFFER_SIZE (64)
|
||||
#define MYNEWT_VAL_BLE_MONITOR_UART_DEV ("uart0")
|
||||
#define MYNEWT_VAL_BLE_RPA_TIMEOUT (300)
|
||||
#define MYNEWT_VAL_BLE_SM_BONDING (0)
|
||||
#define MYNEWT_VAL_BLE_SM_IO_CAP (BLE_HS_IO_NO_INPUT_OUTPUT)
|
||||
#define MYNEWT_VAL_BLE_SM_KEYPRESS (0)
|
||||
#define MYNEWT_VAL_BLE_SM_LEGACY (1)
|
||||
#define MYNEWT_VAL_BLE_SM_MAX_PROCS (1)
|
||||
#define MYNEWT_VAL_BLE_SM_MITM (0)
|
||||
#define MYNEWT_VAL_BLE_SM_OOB_DATA_FLAG (0)
|
||||
#define MYNEWT_VAL_BLE_SM_OUR_KEY_DIST (0)
|
||||
#define MYNEWT_VAL_BLE_SM_SC (1)
|
||||
#define MYNEWT_VAL_BLE_SM_THEIR_KEY_DIST (0)
|
||||
#define MYNEWT_VAL_BLE_STORE_MAX_BONDS (3)
|
||||
#define MYNEWT_VAL_BLE_STORE_MAX_CCCDS (8)
|
||||
|
||||
/*** nimble/host/services/gap */
|
||||
#define MYNEWT_VAL_BLE_SVC_GAP_APPEARANCE (0)
|
||||
#define MYNEWT_VAL_BLE_SVC_GAP_APPEARANCE_WRITE_PERM (-1)
|
||||
#define MYNEWT_VAL_BLE_SVC_GAP_CENTRAL_ADDRESS_RESOLUTION (-1)
|
||||
#define MYNEWT_VAL_BLE_SVC_GAP_DEVICE_NAME ("pybd")
|
||||
#define MYNEWT_VAL_BLE_SVC_GAP_DEVICE_NAME_MAX_LENGTH (31)
|
||||
#define MYNEWT_VAL_BLE_SVC_GAP_DEVICE_NAME_WRITE_PERM (-1)
|
||||
#define MYNEWT_VAL_BLE_SVC_GAP_PPCP_MAX_CONN_INTERVAL (0)
|
||||
#define MYNEWT_VAL_BLE_SVC_GAP_PPCP_MIN_CONN_INTERVAL (0)
|
||||
#define MYNEWT_VAL_BLE_SVC_GAP_PPCP_SLAVE_LATENCY (0)
|
||||
#define MYNEWT_VAL_BLE_SVC_GAP_PPCP_SUPERVISION_TMO (0)
|
||||
|
||||
/* Overridden by targets/porting-nimble (defined by nimble/transport) */
|
||||
#define MYNEWT_VAL_BLE_HCI_TRANSPORT_NIMBLE_BUILTIN (0)
|
||||
#define MYNEWT_VAL_BLE_HCI_TRANSPORT_RAM (0)
|
||||
#define MYNEWT_VAL_BLE_HCI_TRANSPORT_SOCKET (0)
|
||||
#define MYNEWT_VAL_BLE_HCI_TRANSPORT_UART (1)
|
||||
|
||||
/*** nimble/transport/uart */
|
||||
#define MYNEWT_VAL_BLE_ACL_BUF_COUNT (12)
|
||||
#define MYNEWT_VAL_BLE_ACL_BUF_SIZE (255)
|
||||
#define MYNEWT_VAL_BLE_HCI_ACL_OUT_COUNT (12)
|
||||
#define MYNEWT_VAL_BLE_HCI_EVT_BUF_SIZE (70)
|
||||
#define MYNEWT_VAL_BLE_HCI_EVT_HI_BUF_COUNT (8)
|
||||
#define MYNEWT_VAL_BLE_HCI_EVT_LO_BUF_COUNT (8)
|
||||
|
||||
/* Overridden by targets/porting-nimble (defined by nimble/transport/uart) */
|
||||
#define MYNEWT_VAL_BLE_HCI_UART_BAUD (MICROPY_HW_BLE_UART_BAUDRATE)
|
||||
#define MYNEWT_VAL_BLE_HCI_UART_DATA_BITS (8)
|
||||
#define MYNEWT_VAL_BLE_HCI_UART_FLOW_CTRL (1)
|
||||
#define MYNEWT_VAL_BLE_HCI_UART_PARITY (HAL_UART_PARITY_NONE)
|
||||
#define MYNEWT_VAL_BLE_HCI_UART_PORT (MICROPY_HW_BLE_UART_ID)
|
||||
#define MYNEWT_VAL_BLE_HCI_UART_STOP_BITS (1)
|
||||
|
||||
#endif // MICROPY_INCLUDED_EXTMOD_NIMBLE_SYSCFG_H
|
||||
@ -53,6 +53,9 @@ static const char *_compilecode(const char *re, ByteProg *prog, int sizecode)
|
||||
PC++; // Skip # of pair byte
|
||||
prog->len++;
|
||||
for (cnt = 0; *re != ']'; re++, cnt++) {
|
||||
if (*re == '\\') {
|
||||
++re;
|
||||
}
|
||||
if (!*re) return NULL;
|
||||
EMIT(PC++, *re);
|
||||
if (re[1] == '-' && re[2] != ']') {
|
||||
|
||||
39
extmod/vfs.h
39
extmod/vfs.h
@ -38,18 +38,40 @@
|
||||
#define MP_S_IFDIR (0x4000)
|
||||
#define MP_S_IFREG (0x8000)
|
||||
|
||||
// these are the values for mp_vfs_blockdev_t.flags
|
||||
#define MP_BLOCKDEV_FLAG_NATIVE (0x0001) // readblocks[2]/writeblocks[2] contain native func
|
||||
#define MP_BLOCKDEV_FLAG_FREE_OBJ (0x0002) // fs_user_mount_t obj should be freed on umount
|
||||
#define MP_BLOCKDEV_FLAG_HAVE_IOCTL (0x0004) // new protocol with ioctl
|
||||
#define MP_BLOCKDEV_FLAG_NO_FILESYSTEM (0x0008) // the block device has no filesystem on it
|
||||
|
||||
// constants for block protocol ioctl
|
||||
#define BP_IOCTL_INIT (1)
|
||||
#define BP_IOCTL_DEINIT (2)
|
||||
#define BP_IOCTL_SYNC (3)
|
||||
#define BP_IOCTL_SEC_COUNT (4)
|
||||
#define BP_IOCTL_SEC_SIZE (5)
|
||||
#define MP_BLOCKDEV_IOCTL_INIT (1)
|
||||
#define MP_BLOCKDEV_IOCTL_DEINIT (2)
|
||||
#define MP_BLOCKDEV_IOCTL_SYNC (3)
|
||||
#define MP_BLOCKDEV_IOCTL_BLOCK_COUNT (4)
|
||||
#define MP_BLOCKDEV_IOCTL_BLOCK_SIZE (5)
|
||||
#define MP_BLOCKDEV_IOCTL_BLOCK_ERASE (6)
|
||||
|
||||
// At the moment the VFS protocol just has import_stat, but could be extended to other methods
|
||||
typedef struct _mp_vfs_proto_t {
|
||||
mp_import_stat_t (*import_stat)(void *self, const char *path);
|
||||
} mp_vfs_proto_t;
|
||||
|
||||
typedef struct _mp_vfs_blockdev_t {
|
||||
uint16_t flags;
|
||||
size_t block_size;
|
||||
mp_obj_t readblocks[5];
|
||||
mp_obj_t writeblocks[5];
|
||||
// new protocol uses just ioctl, old uses sync (optional) and count
|
||||
union {
|
||||
mp_obj_t ioctl[4];
|
||||
struct {
|
||||
mp_obj_t sync[2];
|
||||
mp_obj_t count[2];
|
||||
} old;
|
||||
} u;
|
||||
} mp_vfs_blockdev_t;
|
||||
|
||||
typedef struct _mp_vfs_mount_t {
|
||||
const char *str; // mount point with leading /
|
||||
size_t len;
|
||||
@ -57,6 +79,13 @@ typedef struct _mp_vfs_mount_t {
|
||||
struct _mp_vfs_mount_t *next;
|
||||
} mp_vfs_mount_t;
|
||||
|
||||
void mp_vfs_blockdev_init(mp_vfs_blockdev_t *self, mp_obj_t bdev);
|
||||
int mp_vfs_blockdev_read(mp_vfs_blockdev_t *self, size_t block_num, size_t num_blocks, uint8_t *buf);
|
||||
int mp_vfs_blockdev_read_ext(mp_vfs_blockdev_t *self, size_t block_num, size_t block_off, size_t len, uint8_t *buf);
|
||||
int mp_vfs_blockdev_write(mp_vfs_blockdev_t *self, size_t block_num, size_t num_blocks, const uint8_t *buf);
|
||||
int mp_vfs_blockdev_write_ext(mp_vfs_blockdev_t *self, size_t block_num, size_t block_off, size_t len, const uint8_t *buf);
|
||||
mp_obj_t mp_vfs_blockdev_ioctl(mp_vfs_blockdev_t *self, uintptr_t cmd, uintptr_t arg);
|
||||
|
||||
mp_vfs_mount_t *mp_vfs_lookup_path(const char *path, const char **path_out);
|
||||
mp_import_stat_t mp_vfs_import_stat(const char *path);
|
||||
mp_obj_t mp_vfs_mount(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
|
||||
|
||||
143
extmod/vfs_blockdev.c
Normal file
143
extmod/vfs_blockdev.c
Normal file
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2013-2019 Damien P. George
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "py/runtime.h"
|
||||
#include "py/binary.h"
|
||||
#include "py/objarray.h"
|
||||
#include "py/mperrno.h"
|
||||
#include "extmod/vfs.h"
|
||||
|
||||
#if MICROPY_VFS
|
||||
|
||||
void mp_vfs_blockdev_init(mp_vfs_blockdev_t *self, mp_obj_t bdev) {
|
||||
mp_load_method(bdev, MP_QSTR_readblocks, self->readblocks);
|
||||
mp_load_method_maybe(bdev, MP_QSTR_writeblocks, self->writeblocks);
|
||||
mp_load_method_maybe(bdev, MP_QSTR_ioctl, self->u.ioctl);
|
||||
if (self->u.ioctl[0] != MP_OBJ_NULL) {
|
||||
// Device supports new block protocol, so indicate it
|
||||
self->flags |= MP_BLOCKDEV_FLAG_HAVE_IOCTL;
|
||||
} else {
|
||||
// No ioctl method, so assume the device uses the old block protocol
|
||||
mp_load_method_maybe(bdev, MP_QSTR_sync, self->u.old.sync);
|
||||
mp_load_method(bdev, MP_QSTR_count, self->u.old.count);
|
||||
}
|
||||
}
|
||||
|
||||
int mp_vfs_blockdev_read(mp_vfs_blockdev_t *self, size_t block_num, size_t num_blocks, uint8_t *buf) {
|
||||
if (self->flags & MP_BLOCKDEV_FLAG_NATIVE) {
|
||||
mp_uint_t (*f)(uint8_t*, uint32_t, uint32_t) = (void*)(uintptr_t)self->readblocks[2];
|
||||
return f(buf, block_num, num_blocks);
|
||||
} else {
|
||||
mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, num_blocks * self->block_size, buf};
|
||||
self->readblocks[2] = MP_OBJ_NEW_SMALL_INT(block_num);
|
||||
self->readblocks[3] = MP_OBJ_FROM_PTR(&ar);
|
||||
mp_call_method_n_kw(2, 0, self->readblocks);
|
||||
// TODO handle error return
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int mp_vfs_blockdev_read_ext(mp_vfs_blockdev_t *self, size_t block_num, size_t block_off, size_t len, uint8_t *buf) {
|
||||
mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, len, buf};
|
||||
self->readblocks[2] = MP_OBJ_NEW_SMALL_INT(block_num);
|
||||
self->readblocks[3] = MP_OBJ_FROM_PTR(&ar);
|
||||
self->readblocks[4] = MP_OBJ_NEW_SMALL_INT(block_off);
|
||||
mp_obj_t ret = mp_call_method_n_kw(3, 0, self->readblocks);
|
||||
if (ret == mp_const_none) {
|
||||
return 0;
|
||||
} else {
|
||||
return MP_OBJ_SMALL_INT_VALUE(ret);
|
||||
}
|
||||
}
|
||||
|
||||
int mp_vfs_blockdev_write(mp_vfs_blockdev_t *self, size_t block_num, size_t num_blocks, const uint8_t *buf) {
|
||||
if (self->writeblocks[0] == MP_OBJ_NULL) {
|
||||
// read-only block device
|
||||
return -MP_EROFS;
|
||||
}
|
||||
|
||||
if (self->flags & MP_BLOCKDEV_FLAG_NATIVE) {
|
||||
mp_uint_t (*f)(const uint8_t*, uint32_t, uint32_t) = (void*)(uintptr_t)self->writeblocks[2];
|
||||
return f(buf, block_num, num_blocks);
|
||||
} else {
|
||||
mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, num_blocks * self->block_size, (void*)buf};
|
||||
self->writeblocks[2] = MP_OBJ_NEW_SMALL_INT(block_num);
|
||||
self->writeblocks[3] = MP_OBJ_FROM_PTR(&ar);
|
||||
mp_call_method_n_kw(2, 0, self->writeblocks);
|
||||
// TODO handle error return
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int mp_vfs_blockdev_write_ext(mp_vfs_blockdev_t *self, size_t block_num, size_t block_off, size_t len, const uint8_t *buf) {
|
||||
if (self->writeblocks[0] == MP_OBJ_NULL) {
|
||||
// read-only block device
|
||||
return -MP_EROFS;
|
||||
}
|
||||
|
||||
mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, len, (void*)buf};
|
||||
self->writeblocks[2] = MP_OBJ_NEW_SMALL_INT(block_num);
|
||||
self->writeblocks[3] = MP_OBJ_FROM_PTR(&ar);
|
||||
self->writeblocks[4] = MP_OBJ_NEW_SMALL_INT(block_off);
|
||||
mp_obj_t ret = mp_call_method_n_kw(3, 0, self->writeblocks);
|
||||
if (ret == mp_const_none) {
|
||||
return 0;
|
||||
} else {
|
||||
return MP_OBJ_SMALL_INT_VALUE(ret);
|
||||
}
|
||||
}
|
||||
|
||||
mp_obj_t mp_vfs_blockdev_ioctl(mp_vfs_blockdev_t *self, uintptr_t cmd, uintptr_t arg) {
|
||||
if (self->flags & MP_BLOCKDEV_FLAG_HAVE_IOCTL) {
|
||||
// New protocol with ioctl
|
||||
self->u.ioctl[2] = MP_OBJ_NEW_SMALL_INT(cmd);
|
||||
self->u.ioctl[3] = MP_OBJ_NEW_SMALL_INT(arg);
|
||||
return mp_call_method_n_kw(2, 0, self->u.ioctl);
|
||||
} else {
|
||||
// Old protocol with sync and count
|
||||
switch (cmd) {
|
||||
case MP_BLOCKDEV_IOCTL_SYNC:
|
||||
if (self->u.old.sync[0] != MP_OBJ_NULL) {
|
||||
mp_call_method_n_kw(0, 0, self->u.old.sync);
|
||||
}
|
||||
break;
|
||||
|
||||
case MP_BLOCKDEV_IOCTL_BLOCK_COUNT:
|
||||
return mp_call_method_n_kw(0, 0, self->u.old.count);
|
||||
|
||||
case MP_BLOCKDEV_IOCTL_BLOCK_SIZE:
|
||||
// Old protocol has fixed sector size of 512 bytes
|
||||
break;
|
||||
|
||||
case MP_BLOCKDEV_IOCTL_INIT:
|
||||
// Old protocol doesn't have init
|
||||
break;
|
||||
}
|
||||
return mp_const_none;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // MICROPY_VFS
|
||||
@ -68,27 +68,18 @@ STATIC mp_obj_t fat_vfs_make_new(const mp_obj_type_t *type, size_t n_args, size_
|
||||
// create new object
|
||||
fs_user_mount_t *vfs = m_new_obj(fs_user_mount_t);
|
||||
vfs->base.type = type;
|
||||
vfs->flags = FSUSER_FREE_OBJ;
|
||||
vfs->fatfs.drv = vfs;
|
||||
|
||||
// load block protocol methods
|
||||
mp_load_method(args[0], MP_QSTR_readblocks, vfs->readblocks);
|
||||
mp_load_method_maybe(args[0], MP_QSTR_writeblocks, vfs->writeblocks);
|
||||
mp_load_method_maybe(args[0], MP_QSTR_ioctl, vfs->u.ioctl);
|
||||
if (vfs->u.ioctl[0] != MP_OBJ_NULL) {
|
||||
// device supports new block protocol, so indicate it
|
||||
vfs->flags |= FSUSER_HAVE_IOCTL;
|
||||
} else {
|
||||
// no ioctl method, so assume the device uses the old block protocol
|
||||
mp_load_method_maybe(args[0], MP_QSTR_sync, vfs->u.old.sync);
|
||||
mp_load_method(args[0], MP_QSTR_count, vfs->u.old.count);
|
||||
}
|
||||
// Initialise underlying block device
|
||||
vfs->blockdev.flags = MP_BLOCKDEV_FLAG_FREE_OBJ;
|
||||
vfs->blockdev.block_size = FF_MIN_SS; // default, will be populated by call to MP_BLOCKDEV_IOCTL_BLOCK_SIZE
|
||||
mp_vfs_blockdev_init(&vfs->blockdev, args[0]);
|
||||
|
||||
// mount the block device so the VFS methods can be used
|
||||
FRESULT res = f_mount(&vfs->fatfs);
|
||||
if (res == FR_NO_FILESYSTEM) {
|
||||
// don't error out if no filesystem, to let mkfs()/mount() create one if wanted
|
||||
vfs->flags |= FSUSER_NO_FILESYSTEM;
|
||||
vfs->blockdev.flags |= MP_BLOCKDEV_FLAG_NO_FILESYSTEM;
|
||||
} else if (res != FR_OK) {
|
||||
mp_raise_OSError(fresult_to_errno_table[res]);
|
||||
}
|
||||
@ -380,11 +371,11 @@ STATIC mp_obj_t vfs_fat_mount(mp_obj_t self_in, mp_obj_t readonly, mp_obj_t mkfs
|
||||
// 1. readonly=True keyword argument
|
||||
// 2. nonexistent writeblocks method (then writeblocks[0] == MP_OBJ_NULL already)
|
||||
if (mp_obj_is_true(readonly)) {
|
||||
self->writeblocks[0] = MP_OBJ_NULL;
|
||||
self->blockdev.writeblocks[0] = MP_OBJ_NULL;
|
||||
}
|
||||
|
||||
// check if we need to make the filesystem
|
||||
FRESULT res = (self->flags & FSUSER_NO_FILESYSTEM) ? FR_NO_FILESYSTEM : FR_OK;
|
||||
FRESULT res = (self->blockdev.flags & MP_BLOCKDEV_FLAG_NO_FILESYSTEM) ? FR_NO_FILESYSTEM : FR_OK;
|
||||
if (res == FR_NO_FILESYSTEM && mp_obj_is_true(mkfs)) {
|
||||
uint8_t working_buf[FF_MAX_SS];
|
||||
res = f_mkfs(&self->fatfs, FM_FAT | FM_SFD, 0, working_buf, sizeof(working_buf));
|
||||
@ -392,7 +383,7 @@ STATIC mp_obj_t vfs_fat_mount(mp_obj_t self_in, mp_obj_t readonly, mp_obj_t mkfs
|
||||
if (res != FR_OK) {
|
||||
mp_raise_OSError(fresult_to_errno_table[res]);
|
||||
}
|
||||
self->flags &= ~FSUSER_NO_FILESYSTEM;
|
||||
self->blockdev.flags &= ~MP_BLOCKDEV_FLAG_NO_FILESYSTEM;
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
|
||||
@ -26,30 +26,13 @@
|
||||
#ifndef MICROPY_INCLUDED_EXTMOD_VFS_FAT_H
|
||||
#define MICROPY_INCLUDED_EXTMOD_VFS_FAT_H
|
||||
|
||||
#include "py/lexer.h"
|
||||
#include "py/obj.h"
|
||||
#include "lib/oofatfs/ff.h"
|
||||
#include "extmod/vfs.h"
|
||||
|
||||
// these are the values for fs_user_mount_t.flags
|
||||
#define FSUSER_NATIVE (0x0001) // readblocks[2]/writeblocks[2] contain native func
|
||||
#define FSUSER_FREE_OBJ (0x0002) // fs_user_mount_t obj should be freed on umount
|
||||
#define FSUSER_HAVE_IOCTL (0x0004) // new protocol with ioctl
|
||||
#define FSUSER_NO_FILESYSTEM (0x0008) // the block device has no filesystem on it
|
||||
|
||||
typedef struct _fs_user_mount_t {
|
||||
mp_obj_base_t base;
|
||||
uint16_t flags;
|
||||
mp_obj_t readblocks[4];
|
||||
mp_obj_t writeblocks[4];
|
||||
// new protocol uses just ioctl, old uses sync (optional) and count
|
||||
union {
|
||||
mp_obj_t ioctl[4];
|
||||
struct {
|
||||
mp_obj_t sync[2];
|
||||
mp_obj_t count[2];
|
||||
} old;
|
||||
} u;
|
||||
mp_vfs_blockdev_t blockdev;
|
||||
FATFS fatfs;
|
||||
} fs_user_mount_t;
|
||||
|
||||
|
||||
@ -38,16 +38,11 @@
|
||||
#include "py/runtime.h"
|
||||
#include "py/binary.h"
|
||||
#include "py/objarray.h"
|
||||
#include "py/mperrno.h"
|
||||
#include "lib/oofatfs/ff.h"
|
||||
#include "lib/oofatfs/diskio.h"
|
||||
#include "extmod/vfs_fat.h"
|
||||
|
||||
#if FF_MAX_SS == FF_MIN_SS
|
||||
#define SECSIZE(fs) (FF_MIN_SS)
|
||||
#else
|
||||
#define SECSIZE(fs) ((fs)->ssize)
|
||||
#endif
|
||||
|
||||
typedef void *bdev_t;
|
||||
STATIC fs_user_mount_t *disk_get_device(void *bdev) {
|
||||
return (fs_user_mount_t*)bdev;
|
||||
@ -69,20 +64,9 @@ DRESULT disk_read (
|
||||
return RES_PARERR;
|
||||
}
|
||||
|
||||
if (vfs->flags & FSUSER_NATIVE) {
|
||||
mp_uint_t (*f)(uint8_t*, uint32_t, uint32_t) = (void*)(uintptr_t)vfs->readblocks[2];
|
||||
if (f(buff, sector, count) != 0) {
|
||||
return RES_ERROR;
|
||||
}
|
||||
} else {
|
||||
mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, count * SECSIZE(&vfs->fatfs), buff};
|
||||
vfs->readblocks[2] = MP_OBJ_NEW_SMALL_INT(sector);
|
||||
vfs->readblocks[3] = MP_OBJ_FROM_PTR(&ar);
|
||||
mp_call_method_n_kw(2, 0, vfs->readblocks);
|
||||
// TODO handle error return
|
||||
}
|
||||
int ret = mp_vfs_blockdev_read(&vfs->blockdev, sector, count, buff);
|
||||
|
||||
return RES_OK;
|
||||
return ret == 0 ? RES_OK : RES_ERROR;
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------*/
|
||||
@ -101,25 +85,14 @@ DRESULT disk_write (
|
||||
return RES_PARERR;
|
||||
}
|
||||
|
||||
if (vfs->writeblocks[0] == MP_OBJ_NULL) {
|
||||
int ret = mp_vfs_blockdev_write(&vfs->blockdev, sector, count, buff);
|
||||
|
||||
if (ret == -MP_EROFS) {
|
||||
// read-only block device
|
||||
return RES_WRPRT;
|
||||
}
|
||||
|
||||
if (vfs->flags & FSUSER_NATIVE) {
|
||||
mp_uint_t (*f)(const uint8_t*, uint32_t, uint32_t) = (void*)(uintptr_t)vfs->writeblocks[2];
|
||||
if (f(buff, sector, count) != 0) {
|
||||
return RES_ERROR;
|
||||
}
|
||||
} else {
|
||||
mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, count * SECSIZE(&vfs->fatfs), (void*)buff};
|
||||
vfs->writeblocks[2] = MP_OBJ_NEW_SMALL_INT(sector);
|
||||
vfs->writeblocks[3] = MP_OBJ_FROM_PTR(&ar);
|
||||
mp_call_method_n_kw(2, 0, vfs->writeblocks);
|
||||
// TODO handle error return
|
||||
}
|
||||
|
||||
return RES_OK;
|
||||
return ret == 0 ? RES_OK : RES_ERROR;
|
||||
}
|
||||
|
||||
|
||||
@ -139,42 +112,16 @@ DRESULT disk_ioctl (
|
||||
}
|
||||
|
||||
// First part: call the relevant method of the underlying block device
|
||||
static const uint8_t op_map[8] = {
|
||||
[CTRL_SYNC] = MP_BLOCKDEV_IOCTL_SYNC,
|
||||
[GET_SECTOR_COUNT] = MP_BLOCKDEV_IOCTL_BLOCK_COUNT,
|
||||
[GET_SECTOR_SIZE] = MP_BLOCKDEV_IOCTL_BLOCK_SIZE,
|
||||
[IOCTL_INIT] = MP_BLOCKDEV_IOCTL_INIT,
|
||||
};
|
||||
uint8_t bp_op = op_map[cmd & 7];
|
||||
mp_obj_t ret = mp_const_none;
|
||||
if (vfs->flags & FSUSER_HAVE_IOCTL) {
|
||||
// new protocol with ioctl
|
||||
static const uint8_t op_map[8] = {
|
||||
[CTRL_SYNC] = BP_IOCTL_SYNC,
|
||||
[GET_SECTOR_COUNT] = BP_IOCTL_SEC_COUNT,
|
||||
[GET_SECTOR_SIZE] = BP_IOCTL_SEC_SIZE,
|
||||
[IOCTL_INIT] = BP_IOCTL_INIT,
|
||||
};
|
||||
uint8_t bp_op = op_map[cmd & 7];
|
||||
if (bp_op != 0) {
|
||||
vfs->u.ioctl[2] = MP_OBJ_NEW_SMALL_INT(bp_op);
|
||||
vfs->u.ioctl[3] = MP_OBJ_NEW_SMALL_INT(0); // unused
|
||||
ret = mp_call_method_n_kw(2, 0, vfs->u.ioctl);
|
||||
}
|
||||
} else {
|
||||
// old protocol with sync and count
|
||||
switch (cmd) {
|
||||
case CTRL_SYNC:
|
||||
if (vfs->u.old.sync[0] != MP_OBJ_NULL) {
|
||||
mp_call_method_n_kw(0, 0, vfs->u.old.sync);
|
||||
}
|
||||
break;
|
||||
|
||||
case GET_SECTOR_COUNT:
|
||||
ret = mp_call_method_n_kw(0, 0, vfs->u.old.count);
|
||||
break;
|
||||
|
||||
case GET_SECTOR_SIZE:
|
||||
// old protocol has fixed sector size of 512 bytes
|
||||
break;
|
||||
|
||||
case IOCTL_INIT:
|
||||
// old protocol doesn't have init
|
||||
break;
|
||||
}
|
||||
if (bp_op != 0) {
|
||||
ret = mp_vfs_blockdev_ioctl(&vfs->blockdev, bp_op, 0);
|
||||
}
|
||||
|
||||
// Second part: convert the result for return
|
||||
@ -194,10 +141,8 @@ DRESULT disk_ioctl (
|
||||
} else {
|
||||
*((WORD*)buff) = mp_obj_get_int(ret);
|
||||
}
|
||||
#if FF_MAX_SS != FF_MIN_SS
|
||||
// need to store ssize because we use it in disk_read/disk_write
|
||||
vfs->fatfs.ssize = *((WORD*)buff);
|
||||
#endif
|
||||
vfs->blockdev.block_size = *((WORD*)buff);
|
||||
return RES_OK;
|
||||
}
|
||||
|
||||
@ -211,7 +156,7 @@ DRESULT disk_ioctl (
|
||||
if (ret != mp_const_none && MP_OBJ_SMALL_INT_VALUE(ret) != 0) {
|
||||
// error initialising
|
||||
stat = STA_NOINIT;
|
||||
} else if (vfs->writeblocks[0] == MP_OBJ_NULL) {
|
||||
} else if (vfs->blockdev.writeblocks[0] == MP_OBJ_NULL) {
|
||||
stat = STA_PROTECT;
|
||||
} else {
|
||||
stat = 0;
|
||||
|
||||
125
extmod/vfs_lfs.c
Normal file
125
extmod/vfs_lfs.c
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Damien P. George
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "py/runtime.h"
|
||||
#include "extmod/vfs.h"
|
||||
#include "extmod/vfs_lfs.h"
|
||||
|
||||
#if MICROPY_VFS && (MICROPY_VFS_LFS1 || MICROPY_VFS_LFS2)
|
||||
|
||||
enum { LFS_MAKE_ARG_bdev, LFS_MAKE_ARG_readsize, LFS_MAKE_ARG_progsize, LFS_MAKE_ARG_lookahead };
|
||||
|
||||
static const mp_arg_t lfs_make_allowed_args[] = {
|
||||
{ MP_QSTR_, MP_ARG_OBJ },
|
||||
{ MP_QSTR_readsize, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 32} },
|
||||
{ MP_QSTR_progsize, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 32} },
|
||||
{ MP_QSTR_lookahead, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 32} },
|
||||
};
|
||||
|
||||
#if MICROPY_VFS_LFS1
|
||||
|
||||
#include "lib/littlefs/lfs1.h"
|
||||
|
||||
#define LFS_BUILD_VERSION (1)
|
||||
#define LFSx_MACRO(s) LFS1 ## s
|
||||
#define LFSx_API(s) lfs1_ ## s
|
||||
#define MP_VFS_LFSx(s) mp_vfs_lfs1_ ## s
|
||||
#define MP_OBJ_VFS_LFSx mp_obj_vfs_lfs1_t
|
||||
#define MP_OBJ_VFS_LFSx_FILE mp_obj_vfs_lfs1_file_t
|
||||
#define MP_TYPE_VFS_LFSx mp_type_vfs_lfs1
|
||||
#define MP_TYPE_VFS_LFSx_(s) mp_type_vfs_lfs1 ## s
|
||||
|
||||
typedef struct _mp_obj_vfs_lfs1_t {
|
||||
mp_obj_base_t base;
|
||||
mp_vfs_blockdev_t blockdev;
|
||||
vstr_t cur_dir;
|
||||
struct lfs1_config config;
|
||||
lfs1_t lfs;
|
||||
} mp_obj_vfs_lfs1_t;
|
||||
|
||||
typedef struct _mp_obj_vfs_lfs1_file_t {
|
||||
mp_obj_base_t base;
|
||||
mp_obj_vfs_lfs1_t *vfs;
|
||||
lfs1_file_t file;
|
||||
struct lfs1_file_config cfg;
|
||||
uint8_t file_buffer[0];
|
||||
} mp_obj_vfs_lfs1_file_t;
|
||||
|
||||
const char *mp_vfs_lfs1_make_path(mp_obj_vfs_lfs1_t *self, mp_obj_t path_in);
|
||||
mp_obj_t mp_vfs_lfs1_file_open(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mode_in);
|
||||
|
||||
#include "extmod/vfs_lfsx.c"
|
||||
#include "extmod/vfs_lfsx_file.c"
|
||||
|
||||
#undef LFS_BUILD_VERSION
|
||||
#undef LFSx_MACRO
|
||||
#undef LFSx_API
|
||||
#undef MP_VFS_LFSx
|
||||
#undef MP_OBJ_VFS_LFSx
|
||||
#undef MP_OBJ_VFS_LFSx_FILE
|
||||
#undef MP_TYPE_VFS_LFSx
|
||||
#undef MP_TYPE_VFS_LFSx_
|
||||
|
||||
#endif // MICROPY_VFS_LFS1
|
||||
|
||||
#if MICROPY_VFS_LFS2
|
||||
|
||||
#include "lib/littlefs/lfs2.h"
|
||||
|
||||
#define LFS_BUILD_VERSION (2)
|
||||
#define LFSx_MACRO(s) LFS2 ## s
|
||||
#define LFSx_API(s) lfs2_ ## s
|
||||
#define MP_VFS_LFSx(s) mp_vfs_lfs2_ ## s
|
||||
#define MP_OBJ_VFS_LFSx mp_obj_vfs_lfs2_t
|
||||
#define MP_OBJ_VFS_LFSx_FILE mp_obj_vfs_lfs2_file_t
|
||||
#define MP_TYPE_VFS_LFSx mp_type_vfs_lfs2
|
||||
#define MP_TYPE_VFS_LFSx_(s) mp_type_vfs_lfs2 ## s
|
||||
|
||||
typedef struct _mp_obj_vfs_lfs2_t {
|
||||
mp_obj_base_t base;
|
||||
mp_vfs_blockdev_t blockdev;
|
||||
vstr_t cur_dir;
|
||||
struct lfs2_config config;
|
||||
lfs2_t lfs;
|
||||
} mp_obj_vfs_lfs2_t;
|
||||
|
||||
typedef struct _mp_obj_vfs_lfs2_file_t {
|
||||
mp_obj_base_t base;
|
||||
mp_obj_vfs_lfs2_t *vfs;
|
||||
lfs2_file_t file;
|
||||
struct lfs2_file_config cfg;
|
||||
uint8_t file_buffer[0];
|
||||
} mp_obj_vfs_lfs2_file_t;
|
||||
|
||||
const char *mp_vfs_lfs2_make_path(mp_obj_vfs_lfs2_t *self, mp_obj_t path_in);
|
||||
mp_obj_t mp_vfs_lfs2_file_open(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mode_in);
|
||||
|
||||
#include "extmod/vfs_lfsx.c"
|
||||
#include "extmod/vfs_lfsx_file.c"
|
||||
|
||||
#endif // MICROPY_VFS_LFS2
|
||||
|
||||
#endif // MICROPY_VFS && (MICROPY_VFS_LFS1 || MICROPY_VFS_LFS2)
|
||||
39
extmod/vfs_lfs.h
Normal file
39
extmod/vfs_lfs.h
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Damien P. George
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
#ifndef MICROPY_INCLUDED_EXTMOD_VFS_LFS_H
|
||||
#define MICROPY_INCLUDED_EXTMOD_VFS_LFS_H
|
||||
|
||||
#include "py/obj.h"
|
||||
|
||||
extern const mp_obj_type_t mp_type_vfs_lfs1;
|
||||
extern const mp_obj_type_t mp_type_vfs_lfs1_fileio;
|
||||
extern const mp_obj_type_t mp_type_vfs_lfs1_textio;
|
||||
|
||||
extern const mp_obj_type_t mp_type_vfs_lfs2;
|
||||
extern const mp_obj_type_t mp_type_vfs_lfs2_fileio;
|
||||
extern const mp_obj_type_t mp_type_vfs_lfs2_textio;
|
||||
|
||||
#endif // MICROPY_INCLUDED_EXTMOD_VFS_LFS_H
|
||||
428
extmod/vfs_lfsx.c
Normal file
428
extmod/vfs_lfsx.c
Normal file
@ -0,0 +1,428 @@
|
||||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Damien P. George
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "py/runtime.h"
|
||||
#include "py/stream.h"
|
||||
#include "py/binary.h"
|
||||
#include "py/objarray.h"
|
||||
#include "py/mperrno.h"
|
||||
#include "extmod/vfs.h"
|
||||
|
||||
STATIC int MP_VFS_LFSx(dev_ioctl)(const struct LFSx_API(config) *c, int cmd, int arg, bool must_return_int) {
|
||||
mp_obj_t ret = mp_vfs_blockdev_ioctl(c->context, cmd, arg);
|
||||
int ret_i = 0;
|
||||
if (must_return_int || ret != mp_const_none) {
|
||||
ret_i = mp_obj_get_int(ret);
|
||||
}
|
||||
return ret_i;
|
||||
}
|
||||
|
||||
STATIC int MP_VFS_LFSx(dev_read)(const struct LFSx_API(config) *c, LFSx_API(block_t) block, LFSx_API(off_t) off, void *buffer, LFSx_API(size_t) size) {
|
||||
return mp_vfs_blockdev_read_ext(c->context, block, off, size, buffer);
|
||||
}
|
||||
|
||||
STATIC int MP_VFS_LFSx(dev_prog)(const struct LFSx_API(config) *c, LFSx_API(block_t) block, LFSx_API(off_t) off, const void *buffer, LFSx_API(size_t) size) {
|
||||
return mp_vfs_blockdev_write_ext(c->context, block, off, size, buffer);
|
||||
}
|
||||
|
||||
STATIC int MP_VFS_LFSx(dev_erase)(const struct LFSx_API(config) *c, LFSx_API(block_t) block) {
|
||||
return MP_VFS_LFSx(dev_ioctl)(c, MP_BLOCKDEV_IOCTL_BLOCK_ERASE, block, true);
|
||||
}
|
||||
|
||||
STATIC int MP_VFS_LFSx(dev_sync)(const struct LFSx_API(config) *c) {
|
||||
return MP_VFS_LFSx(dev_ioctl)(c, MP_BLOCKDEV_IOCTL_SYNC, 0, false);
|
||||
}
|
||||
|
||||
STATIC void MP_VFS_LFSx(init_config)(MP_OBJ_VFS_LFSx *self, mp_obj_t bdev, size_t read_size, size_t prog_size, size_t lookahead) {
|
||||
self->blockdev.flags = MP_BLOCKDEV_FLAG_FREE_OBJ;
|
||||
mp_vfs_blockdev_init(&self->blockdev, bdev);
|
||||
|
||||
struct LFSx_API(config) *config = &self->config;
|
||||
memset(config, 0, sizeof(*config));
|
||||
|
||||
config->context = &self->blockdev;
|
||||
|
||||
config->read = MP_VFS_LFSx(dev_read);
|
||||
config->prog = MP_VFS_LFSx(dev_prog);
|
||||
config->erase = MP_VFS_LFSx(dev_erase);
|
||||
config->sync = MP_VFS_LFSx(dev_sync);
|
||||
|
||||
MP_VFS_LFSx(dev_ioctl)(config, MP_BLOCKDEV_IOCTL_INIT, 0, false); // initialise block device
|
||||
int bs = MP_VFS_LFSx(dev_ioctl)(config, MP_BLOCKDEV_IOCTL_BLOCK_SIZE, 0, true); // get block size
|
||||
int bc = MP_VFS_LFSx(dev_ioctl)(config, MP_BLOCKDEV_IOCTL_BLOCK_COUNT, 0, true); // get block count
|
||||
self->blockdev.block_size = bs;
|
||||
|
||||
config->read_size = read_size;
|
||||
config->prog_size = prog_size;
|
||||
config->block_size = bs;
|
||||
config->block_count = bc;
|
||||
|
||||
#if LFS_BUILD_VERSION == 1
|
||||
config->lookahead = lookahead;
|
||||
config->read_buffer = m_new(uint8_t, config->read_size);
|
||||
config->prog_buffer = m_new(uint8_t, config->prog_size);
|
||||
config->lookahead_buffer = m_new(uint8_t, config->lookahead / 8);
|
||||
#else
|
||||
config->block_cycles = 100;
|
||||
config->cache_size = 4 * MAX(read_size, prog_size);
|
||||
config->lookahead_size = lookahead;
|
||||
config->read_buffer = m_new(uint8_t, config->cache_size);
|
||||
config->prog_buffer = m_new(uint8_t, config->cache_size);
|
||||
config->lookahead_buffer = m_new(uint8_t, config->lookahead_size);
|
||||
#endif
|
||||
}
|
||||
|
||||
const char *MP_VFS_LFSx(make_path)(MP_OBJ_VFS_LFSx *self, mp_obj_t path_in) {
|
||||
const char *path = mp_obj_str_get_str(path_in);
|
||||
if (path[0] != '/') {
|
||||
size_t l = vstr_len(&self->cur_dir);
|
||||
if (l > 0) {
|
||||
vstr_add_str(&self->cur_dir, path);
|
||||
path = vstr_null_terminated_str(&self->cur_dir);
|
||||
self->cur_dir.len = l;
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
STATIC mp_obj_t MP_VFS_LFSx(make_new)(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) {
|
||||
mp_arg_val_t args[MP_ARRAY_SIZE(lfs_make_allowed_args)];
|
||||
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(lfs_make_allowed_args), lfs_make_allowed_args, args);
|
||||
|
||||
MP_OBJ_VFS_LFSx *self = m_new0(MP_OBJ_VFS_LFSx, 1);
|
||||
self->base.type = type;
|
||||
vstr_init(&self->cur_dir, 16);
|
||||
vstr_add_byte(&self->cur_dir, '/');
|
||||
MP_VFS_LFSx(init_config)(self, args[LFS_MAKE_ARG_bdev].u_obj,
|
||||
args[LFS_MAKE_ARG_readsize].u_int, args[LFS_MAKE_ARG_progsize].u_int, args[LFS_MAKE_ARG_lookahead].u_int);
|
||||
int ret = LFSx_API(mount)(&self->lfs, &self->config);
|
||||
if (ret < 0) {
|
||||
mp_raise_OSError(-ret);
|
||||
}
|
||||
return MP_OBJ_FROM_PTR(self);
|
||||
}
|
||||
|
||||
STATIC mp_obj_t MP_VFS_LFSx(mkfs)(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
|
||||
mp_arg_val_t args[MP_ARRAY_SIZE(lfs_make_allowed_args)];
|
||||
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(lfs_make_allowed_args), lfs_make_allowed_args, args);
|
||||
|
||||
MP_OBJ_VFS_LFSx self;
|
||||
MP_VFS_LFSx(init_config)(&self, args[LFS_MAKE_ARG_bdev].u_obj,
|
||||
args[LFS_MAKE_ARG_readsize].u_int, args[LFS_MAKE_ARG_progsize].u_int, args[LFS_MAKE_ARG_lookahead].u_int);
|
||||
int ret = LFSx_API(format)(&self.lfs, &self.config);
|
||||
if (ret < 0) {
|
||||
mp_raise_OSError(-ret);
|
||||
}
|
||||
return mp_const_none;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(MP_VFS_LFSx(mkfs_fun_obj), 0, MP_VFS_LFSx(mkfs));
|
||||
STATIC MP_DEFINE_CONST_STATICMETHOD_OBJ(MP_VFS_LFSx(mkfs_obj), MP_ROM_PTR(&MP_VFS_LFSx(mkfs_fun_obj)));
|
||||
|
||||
// Implementation of mp_vfs_lfs_file_open is provided in vfs_lfsx_file.c
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_3(MP_VFS_LFSx(open_obj), MP_VFS_LFSx(file_open));
|
||||
|
||||
typedef struct MP_VFS_LFSx(_ilistdir_it_t) {
|
||||
mp_obj_base_t base;
|
||||
mp_fun_1_t iternext;
|
||||
bool is_str;
|
||||
MP_OBJ_VFS_LFSx *vfs;
|
||||
LFSx_API(dir_t) dir;
|
||||
} MP_VFS_LFSx(ilistdir_it_t);
|
||||
|
||||
STATIC mp_obj_t MP_VFS_LFSx(ilistdir_it_iternext)(mp_obj_t self_in) {
|
||||
MP_VFS_LFSx(ilistdir_it_t) *self = MP_OBJ_TO_PTR(self_in);
|
||||
|
||||
struct LFSx_API(info) info;
|
||||
for (;;) {
|
||||
int ret = LFSx_API(dir_read)(&self->vfs->lfs, &self->dir, &info);
|
||||
if (ret == 0) {
|
||||
LFSx_API(dir_close)(&self->vfs->lfs, &self->dir);
|
||||
return MP_OBJ_STOP_ITERATION;
|
||||
}
|
||||
if (!(info.name[0] == '.' && (info.name[1] == '\0'
|
||||
|| (info.name[1] == '.' && info.name[2] == '\0')))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// make 4-tuple with info about this entry
|
||||
mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(4, NULL));
|
||||
if (self->is_str) {
|
||||
t->items[0] = mp_obj_new_str(info.name, strlen(info.name));
|
||||
} else {
|
||||
t->items[0] = mp_obj_new_bytes((const byte*)info.name, strlen(info.name));
|
||||
}
|
||||
t->items[1] = MP_OBJ_NEW_SMALL_INT(info.type == LFSx_MACRO(_TYPE_REG) ? MP_S_IFREG : MP_S_IFDIR);
|
||||
t->items[2] = MP_OBJ_NEW_SMALL_INT(0); // no inode number
|
||||
t->items[3] = MP_OBJ_NEW_SMALL_INT(info.size);
|
||||
|
||||
return MP_OBJ_FROM_PTR(t);
|
||||
}
|
||||
|
||||
STATIC mp_obj_t MP_VFS_LFSx(ilistdir_func)(size_t n_args, const mp_obj_t *args) {
|
||||
MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(args[0]);
|
||||
bool is_str_type = true;
|
||||
const char *path;
|
||||
if (n_args == 2) {
|
||||
if (mp_obj_get_type(args[1]) == &mp_type_bytes) {
|
||||
is_str_type = false;
|
||||
}
|
||||
path = MP_VFS_LFSx(make_path)(self, args[1]);
|
||||
} else {
|
||||
path = vstr_null_terminated_str(&self->cur_dir);
|
||||
}
|
||||
|
||||
MP_VFS_LFSx(ilistdir_it_t) *iter = m_new_obj(MP_VFS_LFSx(ilistdir_it_t));
|
||||
iter->base.type = &mp_type_polymorph_iter;
|
||||
iter->iternext = MP_VFS_LFSx(ilistdir_it_iternext);
|
||||
iter->is_str = is_str_type;
|
||||
iter->vfs = self;
|
||||
int ret = LFSx_API(dir_open)(&self->lfs, &iter->dir, path);
|
||||
if (ret < 0) {
|
||||
mp_raise_OSError(-ret);
|
||||
}
|
||||
return MP_OBJ_FROM_PTR(iter);
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(MP_VFS_LFSx(ilistdir_obj), 1, 2, MP_VFS_LFSx(ilistdir_func));
|
||||
|
||||
STATIC mp_obj_t MP_VFS_LFSx(remove)(mp_obj_t self_in, mp_obj_t path_in) {
|
||||
MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in);
|
||||
const char *path = MP_VFS_LFSx(make_path)(self, path_in);
|
||||
int ret = LFSx_API(remove)(&self->lfs, path);
|
||||
if (ret < 0) {
|
||||
mp_raise_OSError(-ret);
|
||||
}
|
||||
return mp_const_none;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_2(MP_VFS_LFSx(remove_obj), MP_VFS_LFSx(remove));
|
||||
|
||||
STATIC mp_obj_t MP_VFS_LFSx(rmdir)(mp_obj_t self_in, mp_obj_t path_in) {
|
||||
MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in);
|
||||
const char *path = MP_VFS_LFSx(make_path)(self, path_in);
|
||||
int ret = LFSx_API(remove)(&self->lfs, path);
|
||||
if (ret < 0) {
|
||||
mp_raise_OSError(-ret);
|
||||
}
|
||||
return mp_const_none;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_2(MP_VFS_LFSx(rmdir_obj), MP_VFS_LFSx(rmdir));
|
||||
|
||||
STATIC mp_obj_t MP_VFS_LFSx(rename)(mp_obj_t self_in, mp_obj_t path_old_in, mp_obj_t path_new_in) {
|
||||
MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in);
|
||||
const char *path_old = MP_VFS_LFSx(make_path)(self, path_old_in);
|
||||
vstr_t path_new;
|
||||
vstr_init(&path_new, vstr_len(&self->cur_dir));
|
||||
vstr_add_strn(&path_new, vstr_str(&self->cur_dir), vstr_len(&self->cur_dir));
|
||||
vstr_add_str(&path_new, mp_obj_str_get_str(path_new_in));
|
||||
int ret = LFSx_API(rename)(&self->lfs, path_old, vstr_null_terminated_str(&path_new));
|
||||
vstr_clear(&path_new);
|
||||
if (ret < 0) {
|
||||
mp_raise_OSError(-ret);
|
||||
}
|
||||
return mp_const_none;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_3(MP_VFS_LFSx(rename_obj), MP_VFS_LFSx(rename));
|
||||
|
||||
STATIC mp_obj_t MP_VFS_LFSx(mkdir)(mp_obj_t self_in, mp_obj_t path_o) {
|
||||
MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in);
|
||||
const char *path = MP_VFS_LFSx(make_path)(self, path_o);
|
||||
int ret = LFSx_API(mkdir)(&self->lfs, path);
|
||||
if (ret < 0) {
|
||||
mp_raise_OSError(-ret);
|
||||
}
|
||||
return mp_const_none;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_2(MP_VFS_LFSx(mkdir_obj), MP_VFS_LFSx(mkdir));
|
||||
|
||||
STATIC mp_obj_t MP_VFS_LFSx(chdir)(mp_obj_t self_in, mp_obj_t path_in) {
|
||||
MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in);
|
||||
|
||||
// Check path exists
|
||||
const char *path = MP_VFS_LFSx(make_path)(self, path_in);
|
||||
if (path[1] != '\0') {
|
||||
// Not at root, check it exists
|
||||
struct LFSx_API(info) info;
|
||||
int ret = LFSx_API(stat)(&self->lfs, path, &info);
|
||||
if (ret < 0 || info.type != LFSx_MACRO(_TYPE_DIR)) {
|
||||
mp_raise_OSError(-MP_ENOENT);
|
||||
}
|
||||
}
|
||||
|
||||
// Update cur_dir with new path
|
||||
if (path == vstr_str(&self->cur_dir)) {
|
||||
self->cur_dir.len = strlen(path);
|
||||
} else {
|
||||
vstr_reset(&self->cur_dir);
|
||||
vstr_add_str(&self->cur_dir, path);
|
||||
}
|
||||
|
||||
// If not at root add trailing / to make it easy to build paths
|
||||
if (vstr_len(&self->cur_dir) != 1) {
|
||||
vstr_add_byte(&self->cur_dir, '/');
|
||||
}
|
||||
|
||||
return mp_const_none;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_2(MP_VFS_LFSx(chdir_obj), MP_VFS_LFSx(chdir));
|
||||
|
||||
STATIC mp_obj_t MP_VFS_LFSx(getcwd)(mp_obj_t self_in) {
|
||||
MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in);
|
||||
if (vstr_len(&self->cur_dir) == 1) {
|
||||
return MP_OBJ_NEW_QSTR(MP_QSTR__slash_);
|
||||
} else {
|
||||
// don't include trailing /
|
||||
return mp_obj_new_str(self->cur_dir.buf, self->cur_dir.len - 1);
|
||||
}
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_1(MP_VFS_LFSx(getcwd_obj), MP_VFS_LFSx(getcwd));
|
||||
|
||||
STATIC mp_obj_t MP_VFS_LFSx(stat)(mp_obj_t self_in, mp_obj_t path_in) {
|
||||
MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in);
|
||||
const char *path = mp_obj_str_get_str(path_in);
|
||||
struct LFSx_API(info) info;
|
||||
int ret = LFSx_API(stat)(&self->lfs, path, &info);
|
||||
if (ret < 0) {
|
||||
mp_raise_OSError(-ret);
|
||||
}
|
||||
|
||||
mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL));
|
||||
t->items[0] = MP_OBJ_NEW_SMALL_INT(info.type == LFSx_MACRO(_TYPE_REG) ? MP_S_IFREG : MP_S_IFDIR); // st_mode
|
||||
t->items[1] = MP_OBJ_NEW_SMALL_INT(0); // st_ino
|
||||
t->items[2] = MP_OBJ_NEW_SMALL_INT(0); // st_dev
|
||||
t->items[3] = MP_OBJ_NEW_SMALL_INT(0); // st_nlink
|
||||
t->items[4] = MP_OBJ_NEW_SMALL_INT(0); // st_uid
|
||||
t->items[5] = MP_OBJ_NEW_SMALL_INT(0); // st_gid
|
||||
t->items[6] = mp_obj_new_int_from_uint(info.size); // st_size
|
||||
t->items[7] = MP_OBJ_NEW_SMALL_INT(0); // st_atime
|
||||
t->items[8] = MP_OBJ_NEW_SMALL_INT(0); // st_mtime
|
||||
t->items[9] = MP_OBJ_NEW_SMALL_INT(0); // st_ctime
|
||||
|
||||
return MP_OBJ_FROM_PTR(t);
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_2(MP_VFS_LFSx(stat_obj), MP_VFS_LFSx(stat));
|
||||
|
||||
STATIC int LFSx_API(traverse_cb)(void *data, LFSx_API(block_t) bl) {
|
||||
(void)bl;
|
||||
uint32_t *n = (uint32_t*)data;
|
||||
*n += 1;
|
||||
return LFSx_MACRO(_ERR_OK);
|
||||
}
|
||||
|
||||
STATIC mp_obj_t MP_VFS_LFSx(statvfs)(mp_obj_t self_in, mp_obj_t path_in) {
|
||||
(void)path_in;
|
||||
MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in);
|
||||
uint32_t n_used_blocks = 0;
|
||||
#if LFS_BUILD_VERSION == 1
|
||||
int ret = LFSx_API(traverse)(&self->lfs, LFSx_API(traverse_cb), &n_used_blocks);
|
||||
#else
|
||||
int ret = LFSx_API(fs_traverse)(&self->lfs, LFSx_API(traverse_cb), &n_used_blocks);
|
||||
#endif
|
||||
if (ret < 0) {
|
||||
mp_raise_OSError(-ret);
|
||||
}
|
||||
|
||||
mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL));
|
||||
t->items[0] = MP_OBJ_NEW_SMALL_INT(self->lfs.cfg->block_size); // f_bsize
|
||||
t->items[1] = t->items[0]; // f_frsize
|
||||
t->items[2] = MP_OBJ_NEW_SMALL_INT(self->lfs.cfg->block_count); // f_blocks
|
||||
t->items[3] = MP_OBJ_NEW_SMALL_INT(self->lfs.cfg->block_count - n_used_blocks); // f_bfree
|
||||
t->items[4] = t->items[3]; // f_bavail
|
||||
t->items[5] = MP_OBJ_NEW_SMALL_INT(0); // f_files
|
||||
t->items[6] = MP_OBJ_NEW_SMALL_INT(0); // f_ffree
|
||||
t->items[7] = MP_OBJ_NEW_SMALL_INT(0); // f_favail
|
||||
t->items[8] = MP_OBJ_NEW_SMALL_INT(0); // f_flags
|
||||
t->items[9] = MP_OBJ_NEW_SMALL_INT(LFSx_MACRO(_NAME_MAX)); // f_namemax
|
||||
|
||||
return MP_OBJ_FROM_PTR(t);
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_2(MP_VFS_LFSx(statvfs_obj), MP_VFS_LFSx(statvfs));
|
||||
|
||||
STATIC mp_obj_t MP_VFS_LFSx(mount)(mp_obj_t self_in, mp_obj_t readonly, mp_obj_t mkfs) {
|
||||
(void)self_in;
|
||||
(void)readonly;
|
||||
(void)mkfs;
|
||||
// already called LFSx_API(mount) in MP_VFS_LFSx(make_new)
|
||||
return mp_const_none;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_3(MP_VFS_LFSx(mount_obj), MP_VFS_LFSx(mount));
|
||||
|
||||
STATIC mp_obj_t MP_VFS_LFSx(umount)(mp_obj_t self_in) {
|
||||
MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in);
|
||||
// LFS unmount never fails
|
||||
LFSx_API(unmount)(&self->lfs);
|
||||
return mp_const_none;
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_1(MP_VFS_LFSx(umount_obj), MP_VFS_LFSx(umount));
|
||||
|
||||
STATIC const mp_rom_map_elem_t MP_VFS_LFSx(locals_dict_table)[] = {
|
||||
{ MP_ROM_QSTR(MP_QSTR_mkfs), MP_ROM_PTR(&MP_VFS_LFSx(mkfs_obj)) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&MP_VFS_LFSx(open_obj)) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_ilistdir), MP_ROM_PTR(&MP_VFS_LFSx(ilistdir_obj)) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_mkdir), MP_ROM_PTR(&MP_VFS_LFSx(mkdir_obj)) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_rmdir), MP_ROM_PTR(&MP_VFS_LFSx(rmdir_obj)) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_chdir), MP_ROM_PTR(&MP_VFS_LFSx(chdir_obj)) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_getcwd), MP_ROM_PTR(&MP_VFS_LFSx(getcwd_obj)) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_remove), MP_ROM_PTR(&MP_VFS_LFSx(remove_obj)) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_rename), MP_ROM_PTR(&MP_VFS_LFSx(rename_obj)) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_stat), MP_ROM_PTR(&MP_VFS_LFSx(stat_obj)) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_statvfs), MP_ROM_PTR(&MP_VFS_LFSx(statvfs_obj)) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_mount), MP_ROM_PTR(&MP_VFS_LFSx(mount_obj)) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_umount), MP_ROM_PTR(&MP_VFS_LFSx(umount_obj)) },
|
||||
};
|
||||
STATIC MP_DEFINE_CONST_DICT(MP_VFS_LFSx(locals_dict), MP_VFS_LFSx(locals_dict_table));
|
||||
|
||||
STATIC mp_import_stat_t MP_VFS_LFSx(import_stat)(void *self_in, const char *path) {
|
||||
MP_OBJ_VFS_LFSx *self = self_in;
|
||||
struct LFSx_API(info) info;
|
||||
int ret = LFSx_API(stat)(&self->lfs, path, &info);
|
||||
if (ret == 0) {
|
||||
if (info.type == LFSx_MACRO(_TYPE_REG)) {
|
||||
return MP_IMPORT_STAT_FILE;
|
||||
} else {
|
||||
return MP_IMPORT_STAT_DIR;
|
||||
}
|
||||
}
|
||||
return MP_IMPORT_STAT_NO_EXIST;
|
||||
}
|
||||
|
||||
STATIC const mp_vfs_proto_t MP_VFS_LFSx(proto) = {
|
||||
.import_stat = MP_VFS_LFSx(import_stat),
|
||||
};
|
||||
|
||||
const mp_obj_type_t MP_TYPE_VFS_LFSx = {
|
||||
{ &mp_type_type },
|
||||
#if LFS_BUILD_VERSION == 1
|
||||
.name = MP_QSTR_VfsLfs1,
|
||||
#else
|
||||
.name = MP_QSTR_VfsLfs2,
|
||||
#endif
|
||||
.make_new = MP_VFS_LFSx(make_new),
|
||||
.protocol = &MP_VFS_LFSx(proto),
|
||||
.locals_dict = (mp_obj_dict_t*)&MP_VFS_LFSx(locals_dict),
|
||||
};
|
||||
236
extmod/vfs_lfsx_file.c
Normal file
236
extmod/vfs_lfsx_file.c
Normal file
@ -0,0 +1,236 @@
|
||||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Damien P. George
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "py/runtime.h"
|
||||
#include "py/stream.h"
|
||||
#include "py/mperrno.h"
|
||||
#include "extmod/vfs.h"
|
||||
|
||||
STATIC void MP_VFS_LFSx(check_open)(MP_OBJ_VFS_LFSx_FILE *self) {
|
||||
if (self->vfs == NULL) {
|
||||
mp_raise_ValueError(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
STATIC void MP_VFS_LFSx(file_print)(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
|
||||
(void)self_in;
|
||||
(void)kind;
|
||||
mp_printf(print, "<io.%s>", mp_obj_get_type_str(self_in));
|
||||
}
|
||||
|
||||
mp_obj_t MP_VFS_LFSx(file_open)(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mode_in) {
|
||||
MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in);
|
||||
|
||||
int flags = 0;
|
||||
const mp_obj_type_t *type = &MP_TYPE_VFS_LFSx_(_textio);
|
||||
const char *mode_str = mp_obj_str_get_str(mode_in);
|
||||
for (; *mode_str; ++mode_str) {
|
||||
int new_flags = 0;
|
||||
switch (*mode_str) {
|
||||
case 'r':
|
||||
new_flags = LFSx_MACRO(_O_RDONLY);
|
||||
break;
|
||||
case 'w':
|
||||
new_flags = LFSx_MACRO(_O_WRONLY) | LFSx_MACRO(_O_CREAT) | LFSx_MACRO(_O_TRUNC);
|
||||
break;
|
||||
case 'x':
|
||||
new_flags = LFSx_MACRO(_O_WRONLY) | LFSx_MACRO(_O_CREAT) | LFSx_MACRO(_O_EXCL);
|
||||
break;
|
||||
case 'a':
|
||||
new_flags = LFSx_MACRO(_O_WRONLY) | LFSx_MACRO(_O_CREAT) | LFSx_MACRO(_O_APPEND);
|
||||
break;
|
||||
case '+':
|
||||
flags |= LFSx_MACRO(_O_RDWR);
|
||||
break;
|
||||
#if MICROPY_PY_IO_FILEIO
|
||||
case 'b':
|
||||
type = &MP_TYPE_VFS_LFSx_(_fileio);
|
||||
break;
|
||||
#endif
|
||||
case 't':
|
||||
type = &MP_TYPE_VFS_LFSx_(_textio);
|
||||
break;
|
||||
}
|
||||
if (new_flags) {
|
||||
if (flags) {
|
||||
mp_raise_ValueError(NULL);
|
||||
}
|
||||
flags = new_flags;
|
||||
}
|
||||
}
|
||||
if (flags == 0) {
|
||||
flags = LFSx_MACRO(_O_RDONLY);
|
||||
}
|
||||
|
||||
#if LFS_BUILD_VERSION == 1
|
||||
MP_OBJ_VFS_LFSx_FILE *o = m_new_obj_var_with_finaliser(MP_OBJ_VFS_LFSx_FILE, uint8_t, self->lfs.cfg->prog_size);
|
||||
#else
|
||||
MP_OBJ_VFS_LFSx_FILE *o = m_new_obj_var_with_finaliser(MP_OBJ_VFS_LFSx_FILE, uint8_t, self->lfs.cfg->cache_size);
|
||||
#endif
|
||||
o->base.type = type;
|
||||
o->vfs = self;
|
||||
#if !MICROPY_GC_CONSERVATIVE_CLEAR
|
||||
memset(&o->file, 0, sizeof(o->file));
|
||||
memset(&o->cfg, 0, sizeof(o->cfg));
|
||||
#endif
|
||||
o->cfg.buffer = &o->file_buffer[0];
|
||||
|
||||
const char *path = MP_VFS_LFSx(make_path)(self, path_in);
|
||||
int ret = LFSx_API(file_opencfg)(&self->lfs, &o->file, path, flags, &o->cfg);
|
||||
if (ret < 0) {
|
||||
o->vfs = NULL;
|
||||
mp_raise_OSError(-ret);
|
||||
}
|
||||
|
||||
return MP_OBJ_FROM_PTR(o);
|
||||
}
|
||||
|
||||
STATIC mp_obj_t MP_VFS_LFSx(file___exit__)(size_t n_args, const mp_obj_t *args) {
|
||||
(void)n_args;
|
||||
return mp_stream_close(args[0]);
|
||||
}
|
||||
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(MP_VFS_LFSx(file___exit___obj), 4, 4, MP_VFS_LFSx(file___exit__));
|
||||
|
||||
STATIC mp_uint_t MP_VFS_LFSx(file_read)(mp_obj_t self_in, void *buf, mp_uint_t size, int *errcode) {
|
||||
MP_OBJ_VFS_LFSx_FILE *self = MP_OBJ_TO_PTR(self_in);
|
||||
MP_VFS_LFSx(check_open)(self);
|
||||
LFSx_API(ssize_t) sz = LFSx_API(file_read)(&self->vfs->lfs, &self->file, buf, size);
|
||||
if (sz < 0) {
|
||||
*errcode = -sz;
|
||||
return MP_STREAM_ERROR;
|
||||
}
|
||||
return sz;
|
||||
}
|
||||
|
||||
STATIC mp_uint_t MP_VFS_LFSx(file_write)(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode) {
|
||||
MP_OBJ_VFS_LFSx_FILE *self = MP_OBJ_TO_PTR(self_in);
|
||||
MP_VFS_LFSx(check_open)(self);
|
||||
LFSx_API(ssize_t) sz = LFSx_API(file_write)(&self->vfs->lfs, &self->file, buf, size);
|
||||
if (sz < 0) {
|
||||
*errcode = -sz;
|
||||
return MP_STREAM_ERROR;
|
||||
}
|
||||
return sz;
|
||||
}
|
||||
|
||||
STATIC mp_uint_t MP_VFS_LFSx(file_ioctl)(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) {
|
||||
MP_OBJ_VFS_LFSx_FILE *self = MP_OBJ_TO_PTR(self_in);
|
||||
|
||||
if (request != MP_STREAM_CLOSE) {
|
||||
MP_VFS_LFSx(check_open)(self);
|
||||
}
|
||||
|
||||
if (request == MP_STREAM_SEEK) {
|
||||
struct mp_stream_seek_t *s = (struct mp_stream_seek_t*)(uintptr_t)arg;
|
||||
int res = LFSx_API(file_seek)(&self->vfs->lfs, &self->file, s->offset, s->whence);
|
||||
if (res < 0) {
|
||||
*errcode = -res;
|
||||
return MP_STREAM_ERROR;
|
||||
}
|
||||
res = LFSx_API(file_tell)(&self->vfs->lfs, &self->file);
|
||||
if (res < 0) {
|
||||
*errcode = -res;
|
||||
return MP_STREAM_ERROR;
|
||||
}
|
||||
s->offset = res;
|
||||
return 0;
|
||||
} else if (request == MP_STREAM_FLUSH) {
|
||||
int res = LFSx_API(file_sync)(&self->vfs->lfs, &self->file);
|
||||
if (res < 0) {
|
||||
*errcode = -res;
|
||||
return MP_STREAM_ERROR;
|
||||
}
|
||||
return 0;
|
||||
} else if (request == MP_STREAM_CLOSE) {
|
||||
if (self->vfs == NULL) {
|
||||
return 0;
|
||||
}
|
||||
int res = LFSx_API(file_close)(&self->vfs->lfs, &self->file);
|
||||
self->vfs = NULL; // indicate a closed file
|
||||
if (res < 0) {
|
||||
*errcode = -res;
|
||||
return MP_STREAM_ERROR;
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
*errcode = MP_EINVAL;
|
||||
return MP_STREAM_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
STATIC const mp_rom_map_elem_t MP_VFS_LFSx(file_locals_dict_table)[] = {
|
||||
{ MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_readlines), MP_ROM_PTR(&mp_stream_unbuffered_readlines_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&mp_stream_flush_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mp_stream_close_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_seek), MP_ROM_PTR(&mp_stream_seek_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_tell), MP_ROM_PTR(&mp_stream_tell_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mp_stream_close_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&mp_identity_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&MP_VFS_LFSx(file___exit___obj)) },
|
||||
};
|
||||
STATIC MP_DEFINE_CONST_DICT(MP_VFS_LFSx(file_locals_dict), MP_VFS_LFSx(file_locals_dict_table));
|
||||
|
||||
#if MICROPY_PY_IO_FILEIO
|
||||
STATIC const mp_stream_p_t MP_VFS_LFSx(fileio_stream_p) = {
|
||||
.read = MP_VFS_LFSx(file_read),
|
||||
.write = MP_VFS_LFSx(file_write),
|
||||
.ioctl = MP_VFS_LFSx(file_ioctl),
|
||||
};
|
||||
|
||||
const mp_obj_type_t MP_TYPE_VFS_LFSx_(_fileio) = {
|
||||
{ &mp_type_type },
|
||||
.name = MP_QSTR_FileIO,
|
||||
.print = MP_VFS_LFSx(file_print),
|
||||
.getiter = mp_identity_getiter,
|
||||
.iternext = mp_stream_unbuffered_iter,
|
||||
.protocol = &MP_VFS_LFSx(fileio_stream_p),
|
||||
.locals_dict = (mp_obj_dict_t*)&MP_VFS_LFSx(file_locals_dict),
|
||||
};
|
||||
#endif
|
||||
|
||||
STATIC const mp_stream_p_t MP_VFS_LFSx(textio_stream_p) = {
|
||||
.read = MP_VFS_LFSx(file_read),
|
||||
.write = MP_VFS_LFSx(file_write),
|
||||
.ioctl = MP_VFS_LFSx(file_ioctl),
|
||||
.is_text = true,
|
||||
};
|
||||
|
||||
const mp_obj_type_t MP_TYPE_VFS_LFSx_(_textio) = {
|
||||
{ &mp_type_type },
|
||||
.name = MP_QSTR_TextIOWrapper,
|
||||
.print = MP_VFS_LFSx(file_print),
|
||||
.getiter = mp_identity_getiter,
|
||||
.iternext = mp_stream_unbuffered_iter,
|
||||
.protocol = &MP_VFS_LFSx(textio_stream_p),
|
||||
.locals_dict = (mp_obj_dict_t*)&MP_VFS_LFSx(file_locals_dict),
|
||||
};
|
||||
@ -217,3 +217,19 @@ char *strstr(const char *haystack, const char *needle)
|
||||
return (char *) haystack;
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t strspn(const char *s, const char *accept) {
|
||||
const char *ss = s;
|
||||
while (*s && strchr(accept, *s) != NULL) {
|
||||
++s;
|
||||
}
|
||||
return s - ss;
|
||||
}
|
||||
|
||||
size_t strcspn(const char *s, const char *reject) {
|
||||
const char *ss = s;
|
||||
while (*s && strchr(reject, *s) == NULL) {
|
||||
++s;
|
||||
}
|
||||
return s - ss;
|
||||
}
|
||||
|
||||
10
lib/libm_dbl/thumb_vfp_sqrt.c
Normal file
10
lib/libm_dbl/thumb_vfp_sqrt.c
Normal file
@ -0,0 +1,10 @@
|
||||
// an implementation of sqrt for Thumb using hardware double-precision VFP instructions
|
||||
|
||||
double sqrt(double x) {
|
||||
double ret;
|
||||
asm volatile (
|
||||
"vsqrt.f64 %P0, %P1\n"
|
||||
: "=w" (ret)
|
||||
: "w" (x));
|
||||
return ret;
|
||||
}
|
||||
19
lib/littlefs/README.md
Normal file
19
lib/littlefs/README.md
Normal file
@ -0,0 +1,19 @@
|
||||
littlefs library
|
||||
================
|
||||
|
||||
The upstream source for the files in this directory is
|
||||
https://github.com/ARMmbed/littlefs
|
||||
|
||||
To generate the separate files with lfs1 and lfs2 prefixes run the following
|
||||
commands in the top-level directory of the littlefs repository (replace the
|
||||
version tags with the latest/desired ones, and set `$MPY_DIR`):
|
||||
|
||||
git checkout v1.7.2
|
||||
python2 ./scripts/prefix.py lfs1
|
||||
cp lfs1*.[ch] $MPY_DIR/lib/littlefs
|
||||
git reset --hard HEAD
|
||||
|
||||
git checkout v2.1.3
|
||||
python2 ./scripts/prefix.py lfs2
|
||||
cp lfs2*.[ch] $MPY_DIR/lib/littlefs
|
||||
git reset --hard HEAD
|
||||
2583
lib/littlefs/lfs1.c
Normal file
2583
lib/littlefs/lfs1.c
Normal file
File diff suppressed because it is too large
Load Diff
501
lib/littlefs/lfs1.h
Normal file
501
lib/littlefs/lfs1.h
Normal file
@ -0,0 +1,501 @@
|
||||
/*
|
||||
* The little filesystem
|
||||
*
|
||||
* Copyright (c) 2017, Arm Limited. All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
#ifndef LFS1_H
|
||||
#define LFS1_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
|
||||
/// Version info ///
|
||||
|
||||
// Software library version
|
||||
// Major (top-nibble), incremented on backwards incompatible changes
|
||||
// Minor (bottom-nibble), incremented on feature additions
|
||||
#define LFS1_VERSION 0x00010007
|
||||
#define LFS1_VERSION_MAJOR (0xffff & (LFS1_VERSION >> 16))
|
||||
#define LFS1_VERSION_MINOR (0xffff & (LFS1_VERSION >> 0))
|
||||
|
||||
// Version of On-disk data structures
|
||||
// Major (top-nibble), incremented on backwards incompatible changes
|
||||
// Minor (bottom-nibble), incremented on feature additions
|
||||
#define LFS1_DISK_VERSION 0x00010001
|
||||
#define LFS1_DISK_VERSION_MAJOR (0xffff & (LFS1_DISK_VERSION >> 16))
|
||||
#define LFS1_DISK_VERSION_MINOR (0xffff & (LFS1_DISK_VERSION >> 0))
|
||||
|
||||
|
||||
/// Definitions ///
|
||||
|
||||
// Type definitions
|
||||
typedef uint32_t lfs1_size_t;
|
||||
typedef uint32_t lfs1_off_t;
|
||||
|
||||
typedef int32_t lfs1_ssize_t;
|
||||
typedef int32_t lfs1_soff_t;
|
||||
|
||||
typedef uint32_t lfs1_block_t;
|
||||
|
||||
// Max name size in bytes
|
||||
#ifndef LFS1_NAME_MAX
|
||||
#define LFS1_NAME_MAX 255
|
||||
#endif
|
||||
|
||||
// Max file size in bytes
|
||||
#ifndef LFS1_FILE_MAX
|
||||
#define LFS1_FILE_MAX 2147483647
|
||||
#endif
|
||||
|
||||
// Possible error codes, these are negative to allow
|
||||
// valid positive return values
|
||||
enum lfs1_error {
|
||||
LFS1_ERR_OK = 0, // No error
|
||||
LFS1_ERR_IO = -5, // Error during device operation
|
||||
LFS1_ERR_CORRUPT = -52, // Corrupted
|
||||
LFS1_ERR_NOENT = -2, // No directory entry
|
||||
LFS1_ERR_EXIST = -17, // Entry already exists
|
||||
LFS1_ERR_NOTDIR = -20, // Entry is not a dir
|
||||
LFS1_ERR_ISDIR = -21, // Entry is a dir
|
||||
LFS1_ERR_NOTEMPTY = -39, // Dir is not empty
|
||||
LFS1_ERR_BADF = -9, // Bad file number
|
||||
LFS1_ERR_FBIG = -27, // File too large
|
||||
LFS1_ERR_INVAL = -22, // Invalid parameter
|
||||
LFS1_ERR_NOSPC = -28, // No space left on device
|
||||
LFS1_ERR_NOMEM = -12, // No more memory available
|
||||
};
|
||||
|
||||
// File types
|
||||
enum lfs1_type {
|
||||
LFS1_TYPE_REG = 0x11,
|
||||
LFS1_TYPE_DIR = 0x22,
|
||||
LFS1_TYPE_SUPERBLOCK = 0x2e,
|
||||
};
|
||||
|
||||
// File open flags
|
||||
enum lfs1_open_flags {
|
||||
// open flags
|
||||
LFS1_O_RDONLY = 1, // Open a file as read only
|
||||
LFS1_O_WRONLY = 2, // Open a file as write only
|
||||
LFS1_O_RDWR = 3, // Open a file as read and write
|
||||
LFS1_O_CREAT = 0x0100, // Create a file if it does not exist
|
||||
LFS1_O_EXCL = 0x0200, // Fail if a file already exists
|
||||
LFS1_O_TRUNC = 0x0400, // Truncate the existing file to zero size
|
||||
LFS1_O_APPEND = 0x0800, // Move to end of file on every write
|
||||
|
||||
// internally used flags
|
||||
LFS1_F_DIRTY = 0x10000, // File does not match storage
|
||||
LFS1_F_WRITING = 0x20000, // File has been written since last flush
|
||||
LFS1_F_READING = 0x40000, // File has been read since last flush
|
||||
LFS1_F_ERRED = 0x80000, // An error occured during write
|
||||
};
|
||||
|
||||
// File seek flags
|
||||
enum lfs1_whence_flags {
|
||||
LFS1_SEEK_SET = 0, // Seek relative to an absolute position
|
||||
LFS1_SEEK_CUR = 1, // Seek relative to the current file position
|
||||
LFS1_SEEK_END = 2, // Seek relative to the end of the file
|
||||
};
|
||||
|
||||
|
||||
// Configuration provided during initialization of the littlefs
|
||||
struct lfs1_config {
|
||||
// Opaque user provided context that can be used to pass
|
||||
// information to the block device operations
|
||||
void *context;
|
||||
|
||||
// Read a region in a block. Negative error codes are propogated
|
||||
// to the user.
|
||||
int (*read)(const struct lfs1_config *c, lfs1_block_t block,
|
||||
lfs1_off_t off, void *buffer, lfs1_size_t size);
|
||||
|
||||
// Program a region in a block. The block must have previously
|
||||
// been erased. Negative error codes are propogated to the user.
|
||||
// May return LFS1_ERR_CORRUPT if the block should be considered bad.
|
||||
int (*prog)(const struct lfs1_config *c, lfs1_block_t block,
|
||||
lfs1_off_t off, const void *buffer, lfs1_size_t size);
|
||||
|
||||
// Erase a block. A block must be erased before being programmed.
|
||||
// The state of an erased block is undefined. Negative error codes
|
||||
// are propogated to the user.
|
||||
// May return LFS1_ERR_CORRUPT if the block should be considered bad.
|
||||
int (*erase)(const struct lfs1_config *c, lfs1_block_t block);
|
||||
|
||||
// Sync the state of the underlying block device. Negative error codes
|
||||
// are propogated to the user.
|
||||
int (*sync)(const struct lfs1_config *c);
|
||||
|
||||
// Minimum size of a block read. This determines the size of read buffers.
|
||||
// This may be larger than the physical read size to improve performance
|
||||
// by caching more of the block device.
|
||||
lfs1_size_t read_size;
|
||||
|
||||
// Minimum size of a block program. This determines the size of program
|
||||
// buffers. This may be larger than the physical program size to improve
|
||||
// performance by caching more of the block device.
|
||||
// Must be a multiple of the read size.
|
||||
lfs1_size_t prog_size;
|
||||
|
||||
// Size of an erasable block. This does not impact ram consumption and
|
||||
// may be larger than the physical erase size. However, this should be
|
||||
// kept small as each file currently takes up an entire block.
|
||||
// Must be a multiple of the program size.
|
||||
lfs1_size_t block_size;
|
||||
|
||||
// Number of erasable blocks on the device.
|
||||
lfs1_size_t block_count;
|
||||
|
||||
// Number of blocks to lookahead during block allocation. A larger
|
||||
// lookahead reduces the number of passes required to allocate a block.
|
||||
// The lookahead buffer requires only 1 bit per block so it can be quite
|
||||
// large with little ram impact. Should be a multiple of 32.
|
||||
lfs1_size_t lookahead;
|
||||
|
||||
// Optional, statically allocated read buffer. Must be read sized.
|
||||
void *read_buffer;
|
||||
|
||||
// Optional, statically allocated program buffer. Must be program sized.
|
||||
void *prog_buffer;
|
||||
|
||||
// Optional, statically allocated lookahead buffer. Must be 1 bit per
|
||||
// lookahead block.
|
||||
void *lookahead_buffer;
|
||||
|
||||
// Optional, statically allocated buffer for files. Must be program sized.
|
||||
// If enabled, only one file may be opened at a time.
|
||||
void *file_buffer;
|
||||
};
|
||||
|
||||
// Optional configuration provided during lfs1_file_opencfg
|
||||
struct lfs1_file_config {
|
||||
// Optional, statically allocated buffer for files. Must be program sized.
|
||||
// If NULL, malloc will be used by default.
|
||||
void *buffer;
|
||||
};
|
||||
|
||||
// File info structure
|
||||
struct lfs1_info {
|
||||
// Type of the file, either LFS1_TYPE_REG or LFS1_TYPE_DIR
|
||||
uint8_t type;
|
||||
|
||||
// Size of the file, only valid for REG files
|
||||
lfs1_size_t size;
|
||||
|
||||
// Name of the file stored as a null-terminated string
|
||||
char name[LFS1_NAME_MAX+1];
|
||||
};
|
||||
|
||||
|
||||
/// littlefs data structures ///
|
||||
typedef struct lfs1_entry {
|
||||
lfs1_off_t off;
|
||||
|
||||
struct lfs1_disk_entry {
|
||||
uint8_t type;
|
||||
uint8_t elen;
|
||||
uint8_t alen;
|
||||
uint8_t nlen;
|
||||
union {
|
||||
struct {
|
||||
lfs1_block_t head;
|
||||
lfs1_size_t size;
|
||||
} file;
|
||||
lfs1_block_t dir[2];
|
||||
} u;
|
||||
} d;
|
||||
} lfs1_entry_t;
|
||||
|
||||
typedef struct lfs1_cache {
|
||||
lfs1_block_t block;
|
||||
lfs1_off_t off;
|
||||
uint8_t *buffer;
|
||||
} lfs1_cache_t;
|
||||
|
||||
typedef struct lfs1_file {
|
||||
struct lfs1_file *next;
|
||||
lfs1_block_t pair[2];
|
||||
lfs1_off_t poff;
|
||||
|
||||
lfs1_block_t head;
|
||||
lfs1_size_t size;
|
||||
|
||||
const struct lfs1_file_config *cfg;
|
||||
uint32_t flags;
|
||||
lfs1_off_t pos;
|
||||
lfs1_block_t block;
|
||||
lfs1_off_t off;
|
||||
lfs1_cache_t cache;
|
||||
} lfs1_file_t;
|
||||
|
||||
typedef struct lfs1_dir {
|
||||
struct lfs1_dir *next;
|
||||
lfs1_block_t pair[2];
|
||||
lfs1_off_t off;
|
||||
|
||||
lfs1_block_t head[2];
|
||||
lfs1_off_t pos;
|
||||
|
||||
struct lfs1_disk_dir {
|
||||
uint32_t rev;
|
||||
lfs1_size_t size;
|
||||
lfs1_block_t tail[2];
|
||||
} d;
|
||||
} lfs1_dir_t;
|
||||
|
||||
typedef struct lfs1_superblock {
|
||||
lfs1_off_t off;
|
||||
|
||||
struct lfs1_disk_superblock {
|
||||
uint8_t type;
|
||||
uint8_t elen;
|
||||
uint8_t alen;
|
||||
uint8_t nlen;
|
||||
lfs1_block_t root[2];
|
||||
uint32_t block_size;
|
||||
uint32_t block_count;
|
||||
uint32_t version;
|
||||
char magic[8];
|
||||
} d;
|
||||
} lfs1_superblock_t;
|
||||
|
||||
typedef struct lfs1_free {
|
||||
lfs1_block_t off;
|
||||
lfs1_block_t size;
|
||||
lfs1_block_t i;
|
||||
lfs1_block_t ack;
|
||||
uint32_t *buffer;
|
||||
} lfs1_free_t;
|
||||
|
||||
// The littlefs type
|
||||
typedef struct lfs1 {
|
||||
const struct lfs1_config *cfg;
|
||||
|
||||
lfs1_block_t root[2];
|
||||
lfs1_file_t *files;
|
||||
lfs1_dir_t *dirs;
|
||||
|
||||
lfs1_cache_t rcache;
|
||||
lfs1_cache_t pcache;
|
||||
|
||||
lfs1_free_t free;
|
||||
bool deorphaned;
|
||||
bool moving;
|
||||
} lfs1_t;
|
||||
|
||||
|
||||
/// Filesystem functions ///
|
||||
|
||||
// Format a block device with the littlefs
|
||||
//
|
||||
// Requires a littlefs object and config struct. This clobbers the littlefs
|
||||
// object, and does not leave the filesystem mounted. The config struct must
|
||||
// be zeroed for defaults and backwards compatibility.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs1_format(lfs1_t *lfs1, const struct lfs1_config *config);
|
||||
|
||||
// Mounts a littlefs
|
||||
//
|
||||
// Requires a littlefs object and config struct. Multiple filesystems
|
||||
// may be mounted simultaneously with multiple littlefs objects. Both
|
||||
// lfs1 and config must be allocated while mounted. The config struct must
|
||||
// be zeroed for defaults and backwards compatibility.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs1_mount(lfs1_t *lfs1, const struct lfs1_config *config);
|
||||
|
||||
// Unmounts a littlefs
|
||||
//
|
||||
// Does nothing besides releasing any allocated resources.
|
||||
// Returns a negative error code on failure.
|
||||
int lfs1_unmount(lfs1_t *lfs1);
|
||||
|
||||
/// General operations ///
|
||||
|
||||
// Removes a file or directory
|
||||
//
|
||||
// If removing a directory, the directory must be empty.
|
||||
// Returns a negative error code on failure.
|
||||
int lfs1_remove(lfs1_t *lfs1, const char *path);
|
||||
|
||||
// Rename or move a file or directory
|
||||
//
|
||||
// If the destination exists, it must match the source in type.
|
||||
// If the destination is a directory, the directory must be empty.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs1_rename(lfs1_t *lfs1, const char *oldpath, const char *newpath);
|
||||
|
||||
// Find info about a file or directory
|
||||
//
|
||||
// Fills out the info structure, based on the specified file or directory.
|
||||
// Returns a negative error code on failure.
|
||||
int lfs1_stat(lfs1_t *lfs1, const char *path, struct lfs1_info *info);
|
||||
|
||||
|
||||
/// File operations ///
|
||||
|
||||
// Open a file
|
||||
//
|
||||
// The mode that the file is opened in is determined by the flags, which
|
||||
// are values from the enum lfs1_open_flags that are bitwise-ored together.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs1_file_open(lfs1_t *lfs1, lfs1_file_t *file,
|
||||
const char *path, int flags);
|
||||
|
||||
// Open a file with extra configuration
|
||||
//
|
||||
// The mode that the file is opened in is determined by the flags, which
|
||||
// are values from the enum lfs1_open_flags that are bitwise-ored together.
|
||||
//
|
||||
// The config struct provides additional config options per file as described
|
||||
// above. The config struct must be allocated while the file is open, and the
|
||||
// config struct must be zeroed for defaults and backwards compatibility.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs1_file_opencfg(lfs1_t *lfs1, lfs1_file_t *file,
|
||||
const char *path, int flags,
|
||||
const struct lfs1_file_config *config);
|
||||
|
||||
// Close a file
|
||||
//
|
||||
// Any pending writes are written out to storage as though
|
||||
// sync had been called and releases any allocated resources.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs1_file_close(lfs1_t *lfs1, lfs1_file_t *file);
|
||||
|
||||
// Synchronize a file on storage
|
||||
//
|
||||
// Any pending writes are written out to storage.
|
||||
// Returns a negative error code on failure.
|
||||
int lfs1_file_sync(lfs1_t *lfs1, lfs1_file_t *file);
|
||||
|
||||
// Read data from file
|
||||
//
|
||||
// Takes a buffer and size indicating where to store the read data.
|
||||
// Returns the number of bytes read, or a negative error code on failure.
|
||||
lfs1_ssize_t lfs1_file_read(lfs1_t *lfs1, lfs1_file_t *file,
|
||||
void *buffer, lfs1_size_t size);
|
||||
|
||||
// Write data to file
|
||||
//
|
||||
// Takes a buffer and size indicating the data to write. The file will not
|
||||
// actually be updated on the storage until either sync or close is called.
|
||||
//
|
||||
// Returns the number of bytes written, or a negative error code on failure.
|
||||
lfs1_ssize_t lfs1_file_write(lfs1_t *lfs1, lfs1_file_t *file,
|
||||
const void *buffer, lfs1_size_t size);
|
||||
|
||||
// Change the position of the file
|
||||
//
|
||||
// The change in position is determined by the offset and whence flag.
|
||||
// Returns the old position of the file, or a negative error code on failure.
|
||||
lfs1_soff_t lfs1_file_seek(lfs1_t *lfs1, lfs1_file_t *file,
|
||||
lfs1_soff_t off, int whence);
|
||||
|
||||
// Truncates the size of the file to the specified size
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs1_file_truncate(lfs1_t *lfs1, lfs1_file_t *file, lfs1_off_t size);
|
||||
|
||||
// Return the position of the file
|
||||
//
|
||||
// Equivalent to lfs1_file_seek(lfs1, file, 0, LFS1_SEEK_CUR)
|
||||
// Returns the position of the file, or a negative error code on failure.
|
||||
lfs1_soff_t lfs1_file_tell(lfs1_t *lfs1, lfs1_file_t *file);
|
||||
|
||||
// Change the position of the file to the beginning of the file
|
||||
//
|
||||
// Equivalent to lfs1_file_seek(lfs1, file, 0, LFS1_SEEK_CUR)
|
||||
// Returns a negative error code on failure.
|
||||
int lfs1_file_rewind(lfs1_t *lfs1, lfs1_file_t *file);
|
||||
|
||||
// Return the size of the file
|
||||
//
|
||||
// Similar to lfs1_file_seek(lfs1, file, 0, LFS1_SEEK_END)
|
||||
// Returns the size of the file, or a negative error code on failure.
|
||||
lfs1_soff_t lfs1_file_size(lfs1_t *lfs1, lfs1_file_t *file);
|
||||
|
||||
|
||||
/// Directory operations ///
|
||||
|
||||
// Create a directory
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs1_mkdir(lfs1_t *lfs1, const char *path);
|
||||
|
||||
// Open a directory
|
||||
//
|
||||
// Once open a directory can be used with read to iterate over files.
|
||||
// Returns a negative error code on failure.
|
||||
int lfs1_dir_open(lfs1_t *lfs1, lfs1_dir_t *dir, const char *path);
|
||||
|
||||
// Close a directory
|
||||
//
|
||||
// Releases any allocated resources.
|
||||
// Returns a negative error code on failure.
|
||||
int lfs1_dir_close(lfs1_t *lfs1, lfs1_dir_t *dir);
|
||||
|
||||
// Read an entry in the directory
|
||||
//
|
||||
// Fills out the info structure, based on the specified file or directory.
|
||||
// Returns a negative error code on failure.
|
||||
int lfs1_dir_read(lfs1_t *lfs1, lfs1_dir_t *dir, struct lfs1_info *info);
|
||||
|
||||
// Change the position of the directory
|
||||
//
|
||||
// The new off must be a value previous returned from tell and specifies
|
||||
// an absolute offset in the directory seek.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs1_dir_seek(lfs1_t *lfs1, lfs1_dir_t *dir, lfs1_off_t off);
|
||||
|
||||
// Return the position of the directory
|
||||
//
|
||||
// The returned offset is only meant to be consumed by seek and may not make
|
||||
// sense, but does indicate the current position in the directory iteration.
|
||||
//
|
||||
// Returns the position of the directory, or a negative error code on failure.
|
||||
lfs1_soff_t lfs1_dir_tell(lfs1_t *lfs1, lfs1_dir_t *dir);
|
||||
|
||||
// Change the position of the directory to the beginning of the directory
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs1_dir_rewind(lfs1_t *lfs1, lfs1_dir_t *dir);
|
||||
|
||||
|
||||
/// Miscellaneous littlefs specific operations ///
|
||||
|
||||
// Traverse through all blocks in use by the filesystem
|
||||
//
|
||||
// The provided callback will be called with each block address that is
|
||||
// currently in use by the filesystem. This can be used to determine which
|
||||
// blocks are in use or how much of the storage is available.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs1_traverse(lfs1_t *lfs1, int (*cb)(void*, lfs1_block_t), void *data);
|
||||
|
||||
// Prunes any recoverable errors that may have occured in the filesystem
|
||||
//
|
||||
// Not needed to be called by user unless an operation is interrupted
|
||||
// but the filesystem is still mounted. This is already called on first
|
||||
// allocation.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs1_deorphan(lfs1_t *lfs1);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif
|
||||
31
lib/littlefs/lfs1_util.c
Normal file
31
lib/littlefs/lfs1_util.c
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* lfs1 util functions
|
||||
*
|
||||
* Copyright (c) 2017, Arm Limited. All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
#include "lfs1_util.h"
|
||||
|
||||
// Only compile if user does not provide custom config
|
||||
#ifndef LFS1_CONFIG
|
||||
|
||||
|
||||
// Software CRC implementation with small lookup table
|
||||
void lfs1_crc(uint32_t *restrict crc, const void *buffer, size_t size) {
|
||||
static const uint32_t rtable[16] = {
|
||||
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
|
||||
0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
|
||||
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
|
||||
0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c,
|
||||
};
|
||||
|
||||
const uint8_t *data = buffer;
|
||||
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
*crc = (*crc >> 4) ^ rtable[(*crc ^ (data[i] >> 0)) & 0xf];
|
||||
*crc = (*crc >> 4) ^ rtable[(*crc ^ (data[i] >> 4)) & 0xf];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
186
lib/littlefs/lfs1_util.h
Normal file
186
lib/littlefs/lfs1_util.h
Normal file
@ -0,0 +1,186 @@
|
||||
/*
|
||||
* lfs1 utility functions
|
||||
*
|
||||
* Copyright (c) 2017, Arm Limited. All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
#ifndef LFS1_UTIL_H
|
||||
#define LFS1_UTIL_H
|
||||
|
||||
// Users can override lfs1_util.h with their own configuration by defining
|
||||
// LFS1_CONFIG as a header file to include (-DLFS1_CONFIG=lfs1_config.h).
|
||||
//
|
||||
// If LFS1_CONFIG is used, none of the default utils will be emitted and must be
|
||||
// provided by the config file. To start I would suggest copying lfs1_util.h and
|
||||
// modifying as needed.
|
||||
#ifdef LFS1_CONFIG
|
||||
#define LFS1_STRINGIZE(x) LFS1_STRINGIZE2(x)
|
||||
#define LFS1_STRINGIZE2(x) #x
|
||||
#include LFS1_STRINGIZE(LFS1_CONFIG)
|
||||
#else
|
||||
|
||||
// System includes
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef LFS1_NO_MALLOC
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
#ifndef LFS1_NO_ASSERT
|
||||
#include <assert.h>
|
||||
#endif
|
||||
#if !defined(LFS1_NO_DEBUG) || !defined(LFS1_NO_WARN) || !defined(LFS1_NO_ERROR)
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
|
||||
// Macros, may be replaced by system specific wrappers. Arguments to these
|
||||
// macros must not have side-effects as the macros can be removed for a smaller
|
||||
// code footprint
|
||||
|
||||
// Logging functions
|
||||
#ifndef LFS1_NO_DEBUG
|
||||
#define LFS1_DEBUG(fmt, ...) \
|
||||
printf("lfs1 debug:%d: " fmt "\n", __LINE__, __VA_ARGS__)
|
||||
#else
|
||||
#define LFS1_DEBUG(fmt, ...)
|
||||
#endif
|
||||
|
||||
#ifndef LFS1_NO_WARN
|
||||
#define LFS1_WARN(fmt, ...) \
|
||||
printf("lfs1 warn:%d: " fmt "\n", __LINE__, __VA_ARGS__)
|
||||
#else
|
||||
#define LFS1_WARN(fmt, ...)
|
||||
#endif
|
||||
|
||||
#ifndef LFS1_NO_ERROR
|
||||
#define LFS1_ERROR(fmt, ...) \
|
||||
printf("lfs1 error:%d: " fmt "\n", __LINE__, __VA_ARGS__)
|
||||
#else
|
||||
#define LFS1_ERROR(fmt, ...)
|
||||
#endif
|
||||
|
||||
// Runtime assertions
|
||||
#ifndef LFS1_NO_ASSERT
|
||||
#define LFS1_ASSERT(test) assert(test)
|
||||
#else
|
||||
#define LFS1_ASSERT(test)
|
||||
#endif
|
||||
|
||||
|
||||
// Builtin functions, these may be replaced by more efficient
|
||||
// toolchain-specific implementations. LFS1_NO_INTRINSICS falls back to a more
|
||||
// expensive basic C implementation for debugging purposes
|
||||
|
||||
// Min/max functions for unsigned 32-bit numbers
|
||||
static inline uint32_t lfs1_max(uint32_t a, uint32_t b) {
|
||||
return (a > b) ? a : b;
|
||||
}
|
||||
|
||||
static inline uint32_t lfs1_min(uint32_t a, uint32_t b) {
|
||||
return (a < b) ? a : b;
|
||||
}
|
||||
|
||||
// Find the next smallest power of 2 less than or equal to a
|
||||
static inline uint32_t lfs1_npw2(uint32_t a) {
|
||||
#if !defined(LFS1_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM))
|
||||
return 32 - __builtin_clz(a-1);
|
||||
#else
|
||||
uint32_t r = 0;
|
||||
uint32_t s;
|
||||
a -= 1;
|
||||
s = (a > 0xffff) << 4; a >>= s; r |= s;
|
||||
s = (a > 0xff ) << 3; a >>= s; r |= s;
|
||||
s = (a > 0xf ) << 2; a >>= s; r |= s;
|
||||
s = (a > 0x3 ) << 1; a >>= s; r |= s;
|
||||
return (r | (a >> 1)) + 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Count the number of trailing binary zeros in a
|
||||
// lfs1_ctz(0) may be undefined
|
||||
static inline uint32_t lfs1_ctz(uint32_t a) {
|
||||
#if !defined(LFS1_NO_INTRINSICS) && defined(__GNUC__)
|
||||
return __builtin_ctz(a);
|
||||
#else
|
||||
return lfs1_npw2((a & -a) + 1) - 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Count the number of binary ones in a
|
||||
static inline uint32_t lfs1_popc(uint32_t a) {
|
||||
#if !defined(LFS1_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM))
|
||||
return __builtin_popcount(a);
|
||||
#else
|
||||
a = a - ((a >> 1) & 0x55555555);
|
||||
a = (a & 0x33333333) + ((a >> 2) & 0x33333333);
|
||||
return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Find the sequence comparison of a and b, this is the distance
|
||||
// between a and b ignoring overflow
|
||||
static inline int lfs1_scmp(uint32_t a, uint32_t b) {
|
||||
return (int)(unsigned)(a - b);
|
||||
}
|
||||
|
||||
// Convert from 32-bit little-endian to native order
|
||||
static inline uint32_t lfs1_fromle32(uint32_t a) {
|
||||
#if !defined(LFS1_NO_INTRINSICS) && ( \
|
||||
(defined( BYTE_ORDER ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \
|
||||
(defined(__BYTE_ORDER ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \
|
||||
(defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
|
||||
return a;
|
||||
#elif !defined(LFS1_NO_INTRINSICS) && ( \
|
||||
(defined( BYTE_ORDER ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \
|
||||
(defined(__BYTE_ORDER ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \
|
||||
(defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))
|
||||
return __builtin_bswap32(a);
|
||||
#else
|
||||
return (((uint8_t*)&a)[0] << 0) |
|
||||
(((uint8_t*)&a)[1] << 8) |
|
||||
(((uint8_t*)&a)[2] << 16) |
|
||||
(((uint8_t*)&a)[3] << 24);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Convert to 32-bit little-endian from native order
|
||||
static inline uint32_t lfs1_tole32(uint32_t a) {
|
||||
return lfs1_fromle32(a);
|
||||
}
|
||||
|
||||
// Calculate CRC-32 with polynomial = 0x04c11db7
|
||||
void lfs1_crc(uint32_t *crc, const void *buffer, size_t size);
|
||||
|
||||
// Allocate memory, only used if buffers are not provided to littlefs
|
||||
static inline void *lfs1_malloc(size_t size) {
|
||||
#ifndef LFS1_NO_MALLOC
|
||||
return malloc(size);
|
||||
#else
|
||||
(void)size;
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Deallocate memory, only used if buffers are not provided to littlefs
|
||||
static inline void lfs1_free(void *p) {
|
||||
#ifndef LFS1_NO_MALLOC
|
||||
free(p);
|
||||
#else
|
||||
(void)p;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#endif
|
||||
4742
lib/littlefs/lfs2.c
Normal file
4742
lib/littlefs/lfs2.c
Normal file
File diff suppressed because it is too large
Load Diff
651
lib/littlefs/lfs2.h
Normal file
651
lib/littlefs/lfs2.h
Normal file
@ -0,0 +1,651 @@
|
||||
/*
|
||||
* The little filesystem
|
||||
*
|
||||
* Copyright (c) 2017, Arm Limited. All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
#ifndef LFS2_H
|
||||
#define LFS2_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
|
||||
/// Version info ///
|
||||
|
||||
// Software library version
|
||||
// Major (top-nibble), incremented on backwards incompatible changes
|
||||
// Minor (bottom-nibble), incremented on feature additions
|
||||
#define LFS2_VERSION 0x00020001
|
||||
#define LFS2_VERSION_MAJOR (0xffff & (LFS2_VERSION >> 16))
|
||||
#define LFS2_VERSION_MINOR (0xffff & (LFS2_VERSION >> 0))
|
||||
|
||||
// Version of On-disk data structures
|
||||
// Major (top-nibble), incremented on backwards incompatible changes
|
||||
// Minor (bottom-nibble), incremented on feature additions
|
||||
#define LFS2_DISK_VERSION 0x00020000
|
||||
#define LFS2_DISK_VERSION_MAJOR (0xffff & (LFS2_DISK_VERSION >> 16))
|
||||
#define LFS2_DISK_VERSION_MINOR (0xffff & (LFS2_DISK_VERSION >> 0))
|
||||
|
||||
|
||||
/// Definitions ///
|
||||
|
||||
// Type definitions
|
||||
typedef uint32_t lfs2_size_t;
|
||||
typedef uint32_t lfs2_off_t;
|
||||
|
||||
typedef int32_t lfs2_ssize_t;
|
||||
typedef int32_t lfs2_soff_t;
|
||||
|
||||
typedef uint32_t lfs2_block_t;
|
||||
|
||||
// Maximum name size in bytes, may be redefined to reduce the size of the
|
||||
// info struct. Limited to <= 1022. Stored in superblock and must be
|
||||
// respected by other littlefs drivers.
|
||||
#ifndef LFS2_NAME_MAX
|
||||
#define LFS2_NAME_MAX 255
|
||||
#endif
|
||||
|
||||
// Maximum size of a file in bytes, may be redefined to limit to support other
|
||||
// drivers. Limited on disk to <= 4294967296. However, above 2147483647 the
|
||||
// functions lfs2_file_seek, lfs2_file_size, and lfs2_file_tell will return
|
||||
// incorrect values due to using signed integers. Stored in superblock and
|
||||
// must be respected by other littlefs drivers.
|
||||
#ifndef LFS2_FILE_MAX
|
||||
#define LFS2_FILE_MAX 2147483647
|
||||
#endif
|
||||
|
||||
// Maximum size of custom attributes in bytes, may be redefined, but there is
|
||||
// no real benefit to using a smaller LFS2_ATTR_MAX. Limited to <= 1022.
|
||||
#ifndef LFS2_ATTR_MAX
|
||||
#define LFS2_ATTR_MAX 1022
|
||||
#endif
|
||||
|
||||
// Possible error codes, these are negative to allow
|
||||
// valid positive return values
|
||||
enum lfs2_error {
|
||||
LFS2_ERR_OK = 0, // No error
|
||||
LFS2_ERR_IO = -5, // Error during device operation
|
||||
LFS2_ERR_CORRUPT = -84, // Corrupted
|
||||
LFS2_ERR_NOENT = -2, // No directory entry
|
||||
LFS2_ERR_EXIST = -17, // Entry already exists
|
||||
LFS2_ERR_NOTDIR = -20, // Entry is not a dir
|
||||
LFS2_ERR_ISDIR = -21, // Entry is a dir
|
||||
LFS2_ERR_NOTEMPTY = -39, // Dir is not empty
|
||||
LFS2_ERR_BADF = -9, // Bad file number
|
||||
LFS2_ERR_FBIG = -27, // File too large
|
||||
LFS2_ERR_INVAL = -22, // Invalid parameter
|
||||
LFS2_ERR_NOSPC = -28, // No space left on device
|
||||
LFS2_ERR_NOMEM = -12, // No more memory available
|
||||
LFS2_ERR_NOATTR = -61, // No data/attr available
|
||||
LFS2_ERR_NAMETOOLONG = -36, // File name too long
|
||||
};
|
||||
|
||||
// File types
|
||||
enum lfs2_type {
|
||||
// file types
|
||||
LFS2_TYPE_REG = 0x001,
|
||||
LFS2_TYPE_DIR = 0x002,
|
||||
|
||||
// internally used types
|
||||
LFS2_TYPE_SPLICE = 0x400,
|
||||
LFS2_TYPE_NAME = 0x000,
|
||||
LFS2_TYPE_STRUCT = 0x200,
|
||||
LFS2_TYPE_USERATTR = 0x300,
|
||||
LFS2_TYPE_FROM = 0x100,
|
||||
LFS2_TYPE_TAIL = 0x600,
|
||||
LFS2_TYPE_GLOBALS = 0x700,
|
||||
LFS2_TYPE_CRC = 0x500,
|
||||
|
||||
// internally used type specializations
|
||||
LFS2_TYPE_CREATE = 0x401,
|
||||
LFS2_TYPE_DELETE = 0x4ff,
|
||||
LFS2_TYPE_SUPERBLOCK = 0x0ff,
|
||||
LFS2_TYPE_DIRSTRUCT = 0x200,
|
||||
LFS2_TYPE_CTZSTRUCT = 0x202,
|
||||
LFS2_TYPE_INLINESTRUCT = 0x201,
|
||||
LFS2_TYPE_SOFTTAIL = 0x600,
|
||||
LFS2_TYPE_HARDTAIL = 0x601,
|
||||
LFS2_TYPE_MOVESTATE = 0x7ff,
|
||||
|
||||
// internal chip sources
|
||||
LFS2_FROM_NOOP = 0x000,
|
||||
LFS2_FROM_MOVE = 0x101,
|
||||
LFS2_FROM_USERATTRS = 0x102,
|
||||
};
|
||||
|
||||
// File open flags
|
||||
enum lfs2_open_flags {
|
||||
// open flags
|
||||
LFS2_O_RDONLY = 1, // Open a file as read only
|
||||
LFS2_O_WRONLY = 2, // Open a file as write only
|
||||
LFS2_O_RDWR = 3, // Open a file as read and write
|
||||
LFS2_O_CREAT = 0x0100, // Create a file if it does not exist
|
||||
LFS2_O_EXCL = 0x0200, // Fail if a file already exists
|
||||
LFS2_O_TRUNC = 0x0400, // Truncate the existing file to zero size
|
||||
LFS2_O_APPEND = 0x0800, // Move to end of file on every write
|
||||
|
||||
// internally used flags
|
||||
LFS2_F_DIRTY = 0x010000, // File does not match storage
|
||||
LFS2_F_WRITING = 0x020000, // File has been written since last flush
|
||||
LFS2_F_READING = 0x040000, // File has been read since last flush
|
||||
LFS2_F_ERRED = 0x080000, // An error occured during write
|
||||
LFS2_F_INLINE = 0x100000, // Currently inlined in directory entry
|
||||
LFS2_F_OPENED = 0x200000, // File has been opened
|
||||
};
|
||||
|
||||
// File seek flags
|
||||
enum lfs2_whence_flags {
|
||||
LFS2_SEEK_SET = 0, // Seek relative to an absolute position
|
||||
LFS2_SEEK_CUR = 1, // Seek relative to the current file position
|
||||
LFS2_SEEK_END = 2, // Seek relative to the end of the file
|
||||
};
|
||||
|
||||
|
||||
// Configuration provided during initialization of the littlefs
|
||||
struct lfs2_config {
|
||||
// Opaque user provided context that can be used to pass
|
||||
// information to the block device operations
|
||||
void *context;
|
||||
|
||||
// Read a region in a block. Negative error codes are propogated
|
||||
// to the user.
|
||||
int (*read)(const struct lfs2_config *c, lfs2_block_t block,
|
||||
lfs2_off_t off, void *buffer, lfs2_size_t size);
|
||||
|
||||
// Program a region in a block. The block must have previously
|
||||
// been erased. Negative error codes are propogated to the user.
|
||||
// May return LFS2_ERR_CORRUPT if the block should be considered bad.
|
||||
int (*prog)(const struct lfs2_config *c, lfs2_block_t block,
|
||||
lfs2_off_t off, const void *buffer, lfs2_size_t size);
|
||||
|
||||
// Erase a block. A block must be erased before being programmed.
|
||||
// The state of an erased block is undefined. Negative error codes
|
||||
// are propogated to the user.
|
||||
// May return LFS2_ERR_CORRUPT if the block should be considered bad.
|
||||
int (*erase)(const struct lfs2_config *c, lfs2_block_t block);
|
||||
|
||||
// Sync the state of the underlying block device. Negative error codes
|
||||
// are propogated to the user.
|
||||
int (*sync)(const struct lfs2_config *c);
|
||||
|
||||
// Minimum size of a block read. All read operations will be a
|
||||
// multiple of this value.
|
||||
lfs2_size_t read_size;
|
||||
|
||||
// Minimum size of a block program. All program operations will be a
|
||||
// multiple of this value.
|
||||
lfs2_size_t prog_size;
|
||||
|
||||
// Size of an erasable block. This does not impact ram consumption and
|
||||
// may be larger than the physical erase size. However, non-inlined files
|
||||
// take up at minimum one block. Must be a multiple of the read
|
||||
// and program sizes.
|
||||
lfs2_size_t block_size;
|
||||
|
||||
// Number of erasable blocks on the device.
|
||||
lfs2_size_t block_count;
|
||||
|
||||
// Number of erase cycles before littlefs evicts metadata logs and moves
|
||||
// the metadata to another block. Suggested values are in the
|
||||
// range 100-1000, with large values having better performance at the cost
|
||||
// of less consistent wear distribution.
|
||||
//
|
||||
// Set to -1 to disable block-level wear-leveling.
|
||||
int32_t block_cycles;
|
||||
|
||||
// Size of block caches. Each cache buffers a portion of a block in RAM.
|
||||
// The littlefs needs a read cache, a program cache, and one additional
|
||||
// cache per file. Larger caches can improve performance by storing more
|
||||
// data and reducing the number of disk accesses. Must be a multiple of
|
||||
// the read and program sizes, and a factor of the block size.
|
||||
lfs2_size_t cache_size;
|
||||
|
||||
// Size of the lookahead buffer in bytes. A larger lookahead buffer
|
||||
// increases the number of blocks found during an allocation pass. The
|
||||
// lookahead buffer is stored as a compact bitmap, so each byte of RAM
|
||||
// can track 8 blocks. Must be a multiple of 8.
|
||||
lfs2_size_t lookahead_size;
|
||||
|
||||
// Optional statically allocated read buffer. Must be cache_size.
|
||||
// By default lfs2_malloc is used to allocate this buffer.
|
||||
void *read_buffer;
|
||||
|
||||
// Optional statically allocated program buffer. Must be cache_size.
|
||||
// By default lfs2_malloc is used to allocate this buffer.
|
||||
void *prog_buffer;
|
||||
|
||||
// Optional statically allocated lookahead buffer. Must be lookahead_size
|
||||
// and aligned to a 32-bit boundary. By default lfs2_malloc is used to
|
||||
// allocate this buffer.
|
||||
void *lookahead_buffer;
|
||||
|
||||
// Optional upper limit on length of file names in bytes. No downside for
|
||||
// larger names except the size of the info struct which is controlled by
|
||||
// the LFS2_NAME_MAX define. Defaults to LFS2_NAME_MAX when zero. Stored in
|
||||
// superblock and must be respected by other littlefs drivers.
|
||||
lfs2_size_t name_max;
|
||||
|
||||
// Optional upper limit on files in bytes. No downside for larger files
|
||||
// but must be <= LFS2_FILE_MAX. Defaults to LFS2_FILE_MAX when zero. Stored
|
||||
// in superblock and must be respected by other littlefs drivers.
|
||||
lfs2_size_t file_max;
|
||||
|
||||
// Optional upper limit on custom attributes in bytes. No downside for
|
||||
// larger attributes size but must be <= LFS2_ATTR_MAX. Defaults to
|
||||
// LFS2_ATTR_MAX when zero.
|
||||
lfs2_size_t attr_max;
|
||||
};
|
||||
|
||||
// File info structure
|
||||
struct lfs2_info {
|
||||
// Type of the file, either LFS2_TYPE_REG or LFS2_TYPE_DIR
|
||||
uint8_t type;
|
||||
|
||||
// Size of the file, only valid for REG files. Limited to 32-bits.
|
||||
lfs2_size_t size;
|
||||
|
||||
// Name of the file stored as a null-terminated string. Limited to
|
||||
// LFS2_NAME_MAX+1, which can be changed by redefining LFS2_NAME_MAX to
|
||||
// reduce RAM. LFS2_NAME_MAX is stored in superblock and must be
|
||||
// respected by other littlefs drivers.
|
||||
char name[LFS2_NAME_MAX+1];
|
||||
};
|
||||
|
||||
// Custom attribute structure, used to describe custom attributes
|
||||
// committed atomically during file writes.
|
||||
struct lfs2_attr {
|
||||
// 8-bit type of attribute, provided by user and used to
|
||||
// identify the attribute
|
||||
uint8_t type;
|
||||
|
||||
// Pointer to buffer containing the attribute
|
||||
void *buffer;
|
||||
|
||||
// Size of attribute in bytes, limited to LFS2_ATTR_MAX
|
||||
lfs2_size_t size;
|
||||
};
|
||||
|
||||
// Optional configuration provided during lfs2_file_opencfg
|
||||
struct lfs2_file_config {
|
||||
// Optional statically allocated file buffer. Must be cache_size.
|
||||
// By default lfs2_malloc is used to allocate this buffer.
|
||||
void *buffer;
|
||||
|
||||
// Optional list of custom attributes related to the file. If the file
|
||||
// is opened with read access, these attributes will be read from disk
|
||||
// during the open call. If the file is opened with write access, the
|
||||
// attributes will be written to disk every file sync or close. This
|
||||
// write occurs atomically with update to the file's contents.
|
||||
//
|
||||
// Custom attributes are uniquely identified by an 8-bit type and limited
|
||||
// to LFS2_ATTR_MAX bytes. When read, if the stored attribute is smaller
|
||||
// than the buffer, it will be padded with zeros. If the stored attribute
|
||||
// is larger, then it will be silently truncated. If the attribute is not
|
||||
// found, it will be created implicitly.
|
||||
struct lfs2_attr *attrs;
|
||||
|
||||
// Number of custom attributes in the list
|
||||
lfs2_size_t attr_count;
|
||||
};
|
||||
|
||||
|
||||
/// internal littlefs data structures ///
|
||||
typedef struct lfs2_cache {
|
||||
lfs2_block_t block;
|
||||
lfs2_off_t off;
|
||||
lfs2_size_t size;
|
||||
uint8_t *buffer;
|
||||
} lfs2_cache_t;
|
||||
|
||||
typedef struct lfs2_mdir {
|
||||
lfs2_block_t pair[2];
|
||||
uint32_t rev;
|
||||
lfs2_off_t off;
|
||||
uint32_t etag;
|
||||
uint16_t count;
|
||||
bool erased;
|
||||
bool split;
|
||||
lfs2_block_t tail[2];
|
||||
} lfs2_mdir_t;
|
||||
|
||||
// littlefs directory type
|
||||
typedef struct lfs2_dir {
|
||||
struct lfs2_dir *next;
|
||||
uint16_t id;
|
||||
uint8_t type;
|
||||
lfs2_mdir_t m;
|
||||
|
||||
lfs2_off_t pos;
|
||||
lfs2_block_t head[2];
|
||||
} lfs2_dir_t;
|
||||
|
||||
// littlefs file type
|
||||
typedef struct lfs2_file {
|
||||
struct lfs2_file *next;
|
||||
uint16_t id;
|
||||
uint8_t type;
|
||||
lfs2_mdir_t m;
|
||||
|
||||
struct lfs2_ctz {
|
||||
lfs2_block_t head;
|
||||
lfs2_size_t size;
|
||||
} ctz;
|
||||
|
||||
uint32_t flags;
|
||||
lfs2_off_t pos;
|
||||
lfs2_block_t block;
|
||||
lfs2_off_t off;
|
||||
lfs2_cache_t cache;
|
||||
|
||||
const struct lfs2_file_config *cfg;
|
||||
} lfs2_file_t;
|
||||
|
||||
typedef struct lfs2_superblock {
|
||||
uint32_t version;
|
||||
lfs2_size_t block_size;
|
||||
lfs2_size_t block_count;
|
||||
lfs2_size_t name_max;
|
||||
lfs2_size_t file_max;
|
||||
lfs2_size_t attr_max;
|
||||
} lfs2_superblock_t;
|
||||
|
||||
// The littlefs filesystem type
|
||||
typedef struct lfs2 {
|
||||
lfs2_cache_t rcache;
|
||||
lfs2_cache_t pcache;
|
||||
|
||||
lfs2_block_t root[2];
|
||||
struct lfs2_mlist {
|
||||
struct lfs2_mlist *next;
|
||||
uint16_t id;
|
||||
uint8_t type;
|
||||
lfs2_mdir_t m;
|
||||
} *mlist;
|
||||
uint32_t seed;
|
||||
|
||||
struct lfs2_gstate {
|
||||
uint32_t tag;
|
||||
lfs2_block_t pair[2];
|
||||
} gstate, gpending, gdelta;
|
||||
|
||||
struct lfs2_free {
|
||||
lfs2_block_t off;
|
||||
lfs2_block_t size;
|
||||
lfs2_block_t i;
|
||||
lfs2_block_t ack;
|
||||
uint32_t *buffer;
|
||||
} free;
|
||||
|
||||
const struct lfs2_config *cfg;
|
||||
lfs2_size_t name_max;
|
||||
lfs2_size_t file_max;
|
||||
lfs2_size_t attr_max;
|
||||
|
||||
#ifdef LFS2_MIGRATE
|
||||
struct lfs21 *lfs21;
|
||||
#endif
|
||||
} lfs2_t;
|
||||
|
||||
|
||||
/// Filesystem functions ///
|
||||
|
||||
// Format a block device with the littlefs
|
||||
//
|
||||
// Requires a littlefs object and config struct. This clobbers the littlefs
|
||||
// object, and does not leave the filesystem mounted. The config struct must
|
||||
// be zeroed for defaults and backwards compatibility.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_format(lfs2_t *lfs2, const struct lfs2_config *config);
|
||||
|
||||
// Mounts a littlefs
|
||||
//
|
||||
// Requires a littlefs object and config struct. Multiple filesystems
|
||||
// may be mounted simultaneously with multiple littlefs objects. Both
|
||||
// lfs2 and config must be allocated while mounted. The config struct must
|
||||
// be zeroed for defaults and backwards compatibility.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_mount(lfs2_t *lfs2, const struct lfs2_config *config);
|
||||
|
||||
// Unmounts a littlefs
|
||||
//
|
||||
// Does nothing besides releasing any allocated resources.
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_unmount(lfs2_t *lfs2);
|
||||
|
||||
/// General operations ///
|
||||
|
||||
// Removes a file or directory
|
||||
//
|
||||
// If removing a directory, the directory must be empty.
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_remove(lfs2_t *lfs2, const char *path);
|
||||
|
||||
// Rename or move a file or directory
|
||||
//
|
||||
// If the destination exists, it must match the source in type.
|
||||
// If the destination is a directory, the directory must be empty.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_rename(lfs2_t *lfs2, const char *oldpath, const char *newpath);
|
||||
|
||||
// Find info about a file or directory
|
||||
//
|
||||
// Fills out the info structure, based on the specified file or directory.
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_stat(lfs2_t *lfs2, const char *path, struct lfs2_info *info);
|
||||
|
||||
// Get a custom attribute
|
||||
//
|
||||
// Custom attributes are uniquely identified by an 8-bit type and limited
|
||||
// to LFS2_ATTR_MAX bytes. When read, if the stored attribute is smaller than
|
||||
// the buffer, it will be padded with zeros. If the stored attribute is larger,
|
||||
// then it will be silently truncated. If no attribute is found, the error
|
||||
// LFS2_ERR_NOATTR is returned and the buffer is filled with zeros.
|
||||
//
|
||||
// Returns the size of the attribute, or a negative error code on failure.
|
||||
// Note, the returned size is the size of the attribute on disk, irrespective
|
||||
// of the size of the buffer. This can be used to dynamically allocate a buffer
|
||||
// or check for existance.
|
||||
lfs2_ssize_t lfs2_getattr(lfs2_t *lfs2, const char *path,
|
||||
uint8_t type, void *buffer, lfs2_size_t size);
|
||||
|
||||
// Set custom attributes
|
||||
//
|
||||
// Custom attributes are uniquely identified by an 8-bit type and limited
|
||||
// to LFS2_ATTR_MAX bytes. If an attribute is not found, it will be
|
||||
// implicitly created.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_setattr(lfs2_t *lfs2, const char *path,
|
||||
uint8_t type, const void *buffer, lfs2_size_t size);
|
||||
|
||||
// Removes a custom attribute
|
||||
//
|
||||
// If an attribute is not found, nothing happens.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_removeattr(lfs2_t *lfs2, const char *path, uint8_t type);
|
||||
|
||||
|
||||
/// File operations ///
|
||||
|
||||
// Open a file
|
||||
//
|
||||
// The mode that the file is opened in is determined by the flags, which
|
||||
// are values from the enum lfs2_open_flags that are bitwise-ored together.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_file_open(lfs2_t *lfs2, lfs2_file_t *file,
|
||||
const char *path, int flags);
|
||||
|
||||
// Open a file with extra configuration
|
||||
//
|
||||
// The mode that the file is opened in is determined by the flags, which
|
||||
// are values from the enum lfs2_open_flags that are bitwise-ored together.
|
||||
//
|
||||
// The config struct provides additional config options per file as described
|
||||
// above. The config struct must be allocated while the file is open, and the
|
||||
// config struct must be zeroed for defaults and backwards compatibility.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_file_opencfg(lfs2_t *lfs2, lfs2_file_t *file,
|
||||
const char *path, int flags,
|
||||
const struct lfs2_file_config *config);
|
||||
|
||||
// Close a file
|
||||
//
|
||||
// Any pending writes are written out to storage as though
|
||||
// sync had been called and releases any allocated resources.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_file_close(lfs2_t *lfs2, lfs2_file_t *file);
|
||||
|
||||
// Synchronize a file on storage
|
||||
//
|
||||
// Any pending writes are written out to storage.
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_file_sync(lfs2_t *lfs2, lfs2_file_t *file);
|
||||
|
||||
// Read data from file
|
||||
//
|
||||
// Takes a buffer and size indicating where to store the read data.
|
||||
// Returns the number of bytes read, or a negative error code on failure.
|
||||
lfs2_ssize_t lfs2_file_read(lfs2_t *lfs2, lfs2_file_t *file,
|
||||
void *buffer, lfs2_size_t size);
|
||||
|
||||
// Write data to file
|
||||
//
|
||||
// Takes a buffer and size indicating the data to write. The file will not
|
||||
// actually be updated on the storage until either sync or close is called.
|
||||
//
|
||||
// Returns the number of bytes written, or a negative error code on failure.
|
||||
lfs2_ssize_t lfs2_file_write(lfs2_t *lfs2, lfs2_file_t *file,
|
||||
const void *buffer, lfs2_size_t size);
|
||||
|
||||
// Change the position of the file
|
||||
//
|
||||
// The change in position is determined by the offset and whence flag.
|
||||
// Returns the new position of the file, or a negative error code on failure.
|
||||
lfs2_soff_t lfs2_file_seek(lfs2_t *lfs2, lfs2_file_t *file,
|
||||
lfs2_soff_t off, int whence);
|
||||
|
||||
// Truncates the size of the file to the specified size
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_file_truncate(lfs2_t *lfs2, lfs2_file_t *file, lfs2_off_t size);
|
||||
|
||||
// Return the position of the file
|
||||
//
|
||||
// Equivalent to lfs2_file_seek(lfs2, file, 0, LFS2_SEEK_CUR)
|
||||
// Returns the position of the file, or a negative error code on failure.
|
||||
lfs2_soff_t lfs2_file_tell(lfs2_t *lfs2, lfs2_file_t *file);
|
||||
|
||||
// Change the position of the file to the beginning of the file
|
||||
//
|
||||
// Equivalent to lfs2_file_seek(lfs2, file, 0, LFS2_SEEK_SET)
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_file_rewind(lfs2_t *lfs2, lfs2_file_t *file);
|
||||
|
||||
// Return the size of the file
|
||||
//
|
||||
// Similar to lfs2_file_seek(lfs2, file, 0, LFS2_SEEK_END)
|
||||
// Returns the size of the file, or a negative error code on failure.
|
||||
lfs2_soff_t lfs2_file_size(lfs2_t *lfs2, lfs2_file_t *file);
|
||||
|
||||
|
||||
/// Directory operations ///
|
||||
|
||||
// Create a directory
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_mkdir(lfs2_t *lfs2, const char *path);
|
||||
|
||||
// Open a directory
|
||||
//
|
||||
// Once open a directory can be used with read to iterate over files.
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_dir_open(lfs2_t *lfs2, lfs2_dir_t *dir, const char *path);
|
||||
|
||||
// Close a directory
|
||||
//
|
||||
// Releases any allocated resources.
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_dir_close(lfs2_t *lfs2, lfs2_dir_t *dir);
|
||||
|
||||
// Read an entry in the directory
|
||||
//
|
||||
// Fills out the info structure, based on the specified file or directory.
|
||||
// Returns a positive value on success, 0 at the end of directory,
|
||||
// or a negative error code on failure.
|
||||
int lfs2_dir_read(lfs2_t *lfs2, lfs2_dir_t *dir, struct lfs2_info *info);
|
||||
|
||||
// Change the position of the directory
|
||||
//
|
||||
// The new off must be a value previous returned from tell and specifies
|
||||
// an absolute offset in the directory seek.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_dir_seek(lfs2_t *lfs2, lfs2_dir_t *dir, lfs2_off_t off);
|
||||
|
||||
// Return the position of the directory
|
||||
//
|
||||
// The returned offset is only meant to be consumed by seek and may not make
|
||||
// sense, but does indicate the current position in the directory iteration.
|
||||
//
|
||||
// Returns the position of the directory, or a negative error code on failure.
|
||||
lfs2_soff_t lfs2_dir_tell(lfs2_t *lfs2, lfs2_dir_t *dir);
|
||||
|
||||
// Change the position of the directory to the beginning of the directory
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_dir_rewind(lfs2_t *lfs2, lfs2_dir_t *dir);
|
||||
|
||||
|
||||
/// Filesystem-level filesystem operations
|
||||
|
||||
// Finds the current size of the filesystem
|
||||
//
|
||||
// Note: Result is best effort. If files share COW structures, the returned
|
||||
// size may be larger than the filesystem actually is.
|
||||
//
|
||||
// Returns the number of allocated blocks, or a negative error code on failure.
|
||||
lfs2_ssize_t lfs2_fs_size(lfs2_t *lfs2);
|
||||
|
||||
// Traverse through all blocks in use by the filesystem
|
||||
//
|
||||
// The provided callback will be called with each block address that is
|
||||
// currently in use by the filesystem. This can be used to determine which
|
||||
// blocks are in use or how much of the storage is available.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_fs_traverse(lfs2_t *lfs2, int (*cb)(void*, lfs2_block_t), void *data);
|
||||
|
||||
#ifdef LFS2_MIGRATE
|
||||
// Attempts to migrate a previous version of littlefs
|
||||
//
|
||||
// Behaves similarly to the lfs2_format function. Attempts to mount
|
||||
// the previous version of littlefs and update the filesystem so it can be
|
||||
// mounted with the current version of littlefs.
|
||||
//
|
||||
// Requires a littlefs object and config struct. This clobbers the littlefs
|
||||
// object, and does not leave the filesystem mounted. The config struct must
|
||||
// be zeroed for defaults and backwards compatibility.
|
||||
//
|
||||
// Returns a negative error code on failure.
|
||||
int lfs2_migrate(lfs2_t *lfs2, const struct lfs2_config *cfg);
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif
|
||||
33
lib/littlefs/lfs2_util.c
Normal file
33
lib/littlefs/lfs2_util.c
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* lfs2 util functions
|
||||
*
|
||||
* Copyright (c) 2017, Arm Limited. All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
#include "lfs2_util.h"
|
||||
|
||||
// Only compile if user does not provide custom config
|
||||
#ifndef LFS2_CONFIG
|
||||
|
||||
|
||||
// Software CRC implementation with small lookup table
|
||||
uint32_t lfs2_crc(uint32_t crc, const void *buffer, size_t size) {
|
||||
static const uint32_t rtable[16] = {
|
||||
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
|
||||
0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
|
||||
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
|
||||
0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c,
|
||||
};
|
||||
|
||||
const uint8_t *data = buffer;
|
||||
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 0)) & 0xf];
|
||||
crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 4)) & 0xf];
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
230
lib/littlefs/lfs2_util.h
Normal file
230
lib/littlefs/lfs2_util.h
Normal file
@ -0,0 +1,230 @@
|
||||
/*
|
||||
* lfs2 utility functions
|
||||
*
|
||||
* Copyright (c) 2017, Arm Limited. All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
#ifndef LFS2_UTIL_H
|
||||
#define LFS2_UTIL_H
|
||||
|
||||
// Users can override lfs2_util.h with their own configuration by defining
|
||||
// LFS2_CONFIG as a header file to include (-DLFS2_CONFIG=lfs2_config.h).
|
||||
//
|
||||
// If LFS2_CONFIG is used, none of the default utils will be emitted and must be
|
||||
// provided by the config file. To start, I would suggest copying lfs2_util.h
|
||||
// and modifying as needed.
|
||||
#ifdef LFS2_CONFIG
|
||||
#define LFS2_STRINGIZE(x) LFS2_STRINGIZE2(x)
|
||||
#define LFS2_STRINGIZE2(x) #x
|
||||
#include LFS2_STRINGIZE(LFS2_CONFIG)
|
||||
#else
|
||||
|
||||
// System includes
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#ifndef LFS2_NO_MALLOC
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
#ifndef LFS2_NO_ASSERT
|
||||
#include <assert.h>
|
||||
#endif
|
||||
#if !defined(LFS2_NO_DEBUG) || \
|
||||
!defined(LFS2_NO_WARN) || \
|
||||
!defined(LFS2_NO_ERROR) || \
|
||||
defined(LFS2_YES_TRACE)
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
|
||||
// Macros, may be replaced by system specific wrappers. Arguments to these
|
||||
// macros must not have side-effects as the macros can be removed for a smaller
|
||||
// code footprint
|
||||
|
||||
// Logging functions
|
||||
#ifdef LFS2_YES_TRACE
|
||||
#define LFS2_TRACE(fmt, ...) \
|
||||
printf("lfs2_trace:%d: " fmt "\n", __LINE__, __VA_ARGS__)
|
||||
#else
|
||||
#define LFS2_TRACE(fmt, ...)
|
||||
#endif
|
||||
|
||||
#ifndef LFS2_NO_DEBUG
|
||||
#define LFS2_DEBUG(fmt, ...) \
|
||||
printf("lfs2_debug:%d: " fmt "\n", __LINE__, __VA_ARGS__)
|
||||
#else
|
||||
#define LFS2_DEBUG(fmt, ...)
|
||||
#endif
|
||||
|
||||
#ifndef LFS2_NO_WARN
|
||||
#define LFS2_WARN(fmt, ...) \
|
||||
printf("lfs2_warn:%d: " fmt "\n", __LINE__, __VA_ARGS__)
|
||||
#else
|
||||
#define LFS2_WARN(fmt, ...)
|
||||
#endif
|
||||
|
||||
#ifndef LFS2_NO_ERROR
|
||||
#define LFS2_ERROR(fmt, ...) \
|
||||
printf("lfs2_error:%d: " fmt "\n", __LINE__, __VA_ARGS__)
|
||||
#else
|
||||
#define LFS2_ERROR(fmt, ...)
|
||||
#endif
|
||||
|
||||
// Runtime assertions
|
||||
#ifndef LFS2_NO_ASSERT
|
||||
#define LFS2_ASSERT(test) assert(test)
|
||||
#else
|
||||
#define LFS2_ASSERT(test)
|
||||
#endif
|
||||
|
||||
|
||||
// Builtin functions, these may be replaced by more efficient
|
||||
// toolchain-specific implementations. LFS2_NO_INTRINSICS falls back to a more
|
||||
// expensive basic C implementation for debugging purposes
|
||||
|
||||
// Min/max functions for unsigned 32-bit numbers
|
||||
static inline uint32_t lfs2_max(uint32_t a, uint32_t b) {
|
||||
return (a > b) ? a : b;
|
||||
}
|
||||
|
||||
static inline uint32_t lfs2_min(uint32_t a, uint32_t b) {
|
||||
return (a < b) ? a : b;
|
||||
}
|
||||
|
||||
// Align to nearest multiple of a size
|
||||
static inline uint32_t lfs2_aligndown(uint32_t a, uint32_t alignment) {
|
||||
return a - (a % alignment);
|
||||
}
|
||||
|
||||
static inline uint32_t lfs2_alignup(uint32_t a, uint32_t alignment) {
|
||||
return lfs2_aligndown(a + alignment-1, alignment);
|
||||
}
|
||||
|
||||
// Find the next smallest power of 2 less than or equal to a
|
||||
static inline uint32_t lfs2_npw2(uint32_t a) {
|
||||
#if !defined(LFS2_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM))
|
||||
return 32 - __builtin_clz(a-1);
|
||||
#else
|
||||
uint32_t r = 0;
|
||||
uint32_t s;
|
||||
a -= 1;
|
||||
s = (a > 0xffff) << 4; a >>= s; r |= s;
|
||||
s = (a > 0xff ) << 3; a >>= s; r |= s;
|
||||
s = (a > 0xf ) << 2; a >>= s; r |= s;
|
||||
s = (a > 0x3 ) << 1; a >>= s; r |= s;
|
||||
return (r | (a >> 1)) + 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Count the number of trailing binary zeros in a
|
||||
// lfs2_ctz(0) may be undefined
|
||||
static inline uint32_t lfs2_ctz(uint32_t a) {
|
||||
#if !defined(LFS2_NO_INTRINSICS) && defined(__GNUC__)
|
||||
return __builtin_ctz(a);
|
||||
#else
|
||||
return lfs2_npw2((a & -a) + 1) - 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Count the number of binary ones in a
|
||||
static inline uint32_t lfs2_popc(uint32_t a) {
|
||||
#if !defined(LFS2_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM))
|
||||
return __builtin_popcount(a);
|
||||
#else
|
||||
a = a - ((a >> 1) & 0x55555555);
|
||||
a = (a & 0x33333333) + ((a >> 2) & 0x33333333);
|
||||
return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Find the sequence comparison of a and b, this is the distance
|
||||
// between a and b ignoring overflow
|
||||
static inline int lfs2_scmp(uint32_t a, uint32_t b) {
|
||||
return (int)(unsigned)(a - b);
|
||||
}
|
||||
|
||||
// Convert between 32-bit little-endian and native order
|
||||
static inline uint32_t lfs2_fromle32(uint32_t a) {
|
||||
#if !defined(LFS2_NO_INTRINSICS) && ( \
|
||||
(defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \
|
||||
(defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \
|
||||
(defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
|
||||
return a;
|
||||
#elif !defined(LFS2_NO_INTRINSICS) && ( \
|
||||
(defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \
|
||||
(defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \
|
||||
(defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))
|
||||
return __builtin_bswap32(a);
|
||||
#else
|
||||
return (((uint8_t*)&a)[0] << 0) |
|
||||
(((uint8_t*)&a)[1] << 8) |
|
||||
(((uint8_t*)&a)[2] << 16) |
|
||||
(((uint8_t*)&a)[3] << 24);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline uint32_t lfs2_tole32(uint32_t a) {
|
||||
return lfs2_fromle32(a);
|
||||
}
|
||||
|
||||
// Convert between 32-bit big-endian and native order
|
||||
static inline uint32_t lfs2_frombe32(uint32_t a) {
|
||||
#if !defined(LFS2_NO_INTRINSICS) && ( \
|
||||
(defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \
|
||||
(defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \
|
||||
(defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
|
||||
return __builtin_bswap32(a);
|
||||
#elif !defined(LFS2_NO_INTRINSICS) && ( \
|
||||
(defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \
|
||||
(defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \
|
||||
(defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))
|
||||
return a;
|
||||
#else
|
||||
return (((uint8_t*)&a)[0] << 24) |
|
||||
(((uint8_t*)&a)[1] << 16) |
|
||||
(((uint8_t*)&a)[2] << 8) |
|
||||
(((uint8_t*)&a)[3] << 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline uint32_t lfs2_tobe32(uint32_t a) {
|
||||
return lfs2_frombe32(a);
|
||||
}
|
||||
|
||||
// Calculate CRC-32 with polynomial = 0x04c11db7
|
||||
uint32_t lfs2_crc(uint32_t crc, const void *buffer, size_t size);
|
||||
|
||||
// Allocate memory, only used if buffers are not provided to littlefs
|
||||
// Note, memory must be 64-bit aligned
|
||||
static inline void *lfs2_malloc(size_t size) {
|
||||
#ifndef LFS2_NO_MALLOC
|
||||
return malloc(size);
|
||||
#else
|
||||
(void)size;
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Deallocate memory, only used if buffers are not provided to littlefs
|
||||
static inline void lfs2_free(void *p) {
|
||||
#ifndef LFS2_NO_MALLOC
|
||||
free(p);
|
||||
#else
|
||||
(void)p;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@ -1 +1 @@
|
||||
Subproject commit d633ad0882a0b17c70adb1680a793ace43da7368
|
||||
Subproject commit 2315fe625a4c2db133259be50b43f33f2a8eac68
|
||||
1
lib/mynewt-nimble
Submodule
1
lib/mynewt-nimble
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 223714cb16c255cfa701929c0de6d7579bfd2cdd
|
||||
@ -109,7 +109,7 @@ STATIC int usage(char **argv) {
|
||||
"-msmall-int-bits=number : set the maximum bits used to encode a small-int\n"
|
||||
"-mno-unicode : don't support unicode in compiled strings\n"
|
||||
"-mcache-lookup-bc : cache map lookups in the bytecode\n"
|
||||
"-march=<arch> : set architecture for native emitter; x86, x64, armv6, armv7m, xtensa\n"
|
||||
"-march=<arch> : set architecture for native emitter; x86, x64, armv6, armv7m, xtensa, xtensawin\n"
|
||||
"\n"
|
||||
"Implementation specific options:\n", argv[0]
|
||||
);
|
||||
@ -288,6 +288,9 @@ MP_NOINLINE int main_(int argc, char **argv) {
|
||||
} else if (strcmp(arch, "xtensa") == 0) {
|
||||
mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_XTENSA;
|
||||
mp_dynamic_compiler.nlr_buf_num_regs = MICROPY_NLR_NUM_REGS_XTENSA;
|
||||
} else if (strcmp(arch, "xtensawin") == 0) {
|
||||
mp_dynamic_compiler.native_arch = MP_NATIVE_ARCH_XTENSAWIN;
|
||||
mp_dynamic_compiler.nlr_buf_num_regs = MICROPY_NLR_NUM_REGS_XTENSAWIN;
|
||||
} else {
|
||||
return usage(argv);
|
||||
}
|
||||
|
||||
@ -39,6 +39,7 @@
|
||||
#define MICROPY_EMIT_ARM (1)
|
||||
#define MICROPY_EMIT_XTENSA (1)
|
||||
#define MICROPY_EMIT_INLINE_XTENSA (1)
|
||||
#define MICROPY_EMIT_XTENSAWIN (1)
|
||||
|
||||
#define MICROPY_DYNAMIC_COMPILER (1)
|
||||
#define MICROPY_COMP_CONST_FOLDING (1)
|
||||
|
||||
@ -69,11 +69,11 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_3(pyb_flash_writeblocks_obj, pyb_flash_writeblock
|
||||
STATIC mp_obj_t pyb_flash_ioctl(mp_obj_t self, mp_obj_t cmd_in, mp_obj_t arg_in) {
|
||||
mp_int_t cmd = mp_obj_get_int(cmd_in);
|
||||
switch (cmd) {
|
||||
case BP_IOCTL_INIT: return MP_OBJ_NEW_SMALL_INT(sflash_disk_init() != RES_OK);
|
||||
case BP_IOCTL_DEINIT: sflash_disk_flush(); return MP_OBJ_NEW_SMALL_INT(0);
|
||||
case BP_IOCTL_SYNC: sflash_disk_flush(); return MP_OBJ_NEW_SMALL_INT(0);
|
||||
case BP_IOCTL_SEC_COUNT: return MP_OBJ_NEW_SMALL_INT(SFLASH_SECTOR_COUNT);
|
||||
case BP_IOCTL_SEC_SIZE: return MP_OBJ_NEW_SMALL_INT(SFLASH_SECTOR_SIZE);
|
||||
case MP_BLOCKDEV_IOCTL_INIT: return MP_OBJ_NEW_SMALL_INT(sflash_disk_init() != RES_OK);
|
||||
case MP_BLOCKDEV_IOCTL_DEINIT: sflash_disk_flush(); return MP_OBJ_NEW_SMALL_INT(0);
|
||||
case MP_BLOCKDEV_IOCTL_SYNC: sflash_disk_flush(); return MP_OBJ_NEW_SMALL_INT(0);
|
||||
case MP_BLOCKDEV_IOCTL_BLOCK_COUNT: return MP_OBJ_NEW_SMALL_INT(SFLASH_SECTOR_COUNT);
|
||||
case MP_BLOCKDEV_IOCTL_BLOCK_SIZE: return MP_OBJ_NEW_SMALL_INT(SFLASH_SECTOR_SIZE);
|
||||
default: return mp_const_none;
|
||||
}
|
||||
}
|
||||
@ -96,14 +96,14 @@ const mp_obj_type_t pyb_flash_type = {
|
||||
|
||||
void pyb_flash_init_vfs(fs_user_mount_t *vfs) {
|
||||
vfs->base.type = &mp_fat_vfs_type;
|
||||
vfs->flags |= FSUSER_NATIVE | FSUSER_HAVE_IOCTL;
|
||||
vfs->blockdev.flags |= MP_BLOCKDEV_FLAG_NATIVE | MP_BLOCKDEV_FLAG_HAVE_IOCTL;
|
||||
vfs->fatfs.drv = vfs;
|
||||
vfs->readblocks[0] = (mp_obj_t)&pyb_flash_readblocks_obj;
|
||||
vfs->readblocks[1] = (mp_obj_t)&pyb_flash_obj;
|
||||
vfs->readblocks[2] = (mp_obj_t)sflash_disk_read; // native version
|
||||
vfs->writeblocks[0] = (mp_obj_t)&pyb_flash_writeblocks_obj;
|
||||
vfs->writeblocks[1] = (mp_obj_t)&pyb_flash_obj;
|
||||
vfs->writeblocks[2] = (mp_obj_t)sflash_disk_write; // native version
|
||||
vfs->u.ioctl[0] = (mp_obj_t)&pyb_flash_ioctl_obj;
|
||||
vfs->u.ioctl[1] = (mp_obj_t)&pyb_flash_obj;
|
||||
vfs->blockdev.readblocks[0] = (mp_obj_t)&pyb_flash_readblocks_obj;
|
||||
vfs->blockdev.readblocks[1] = (mp_obj_t)&pyb_flash_obj;
|
||||
vfs->blockdev.readblocks[2] = (mp_obj_t)sflash_disk_read; // native version
|
||||
vfs->blockdev.writeblocks[0] = (mp_obj_t)&pyb_flash_writeblocks_obj;
|
||||
vfs->blockdev.writeblocks[1] = (mp_obj_t)&pyb_flash_obj;
|
||||
vfs->blockdev.writeblocks[2] = (mp_obj_t)sflash_disk_write; // native version
|
||||
vfs->blockdev.u.ioctl[0] = (mp_obj_t)&pyb_flash_ioctl_obj;
|
||||
vfs->blockdev.u.ioctl[1] = (mp_obj_t)&pyb_flash_obj;
|
||||
}
|
||||
|
||||
@ -184,16 +184,16 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_3(pyb_sd_writeblocks_obj, pyb_sd_writeblocks);
|
||||
STATIC mp_obj_t pyb_sd_ioctl(mp_obj_t self, mp_obj_t cmd_in, mp_obj_t arg_in) {
|
||||
mp_int_t cmd = mp_obj_get_int(cmd_in);
|
||||
switch (cmd) {
|
||||
case BP_IOCTL_INIT:
|
||||
case BP_IOCTL_DEINIT:
|
||||
case BP_IOCTL_SYNC:
|
||||
case MP_BLOCKDEV_IOCTL_INIT:
|
||||
case MP_BLOCKDEV_IOCTL_DEINIT:
|
||||
case MP_BLOCKDEV_IOCTL_SYNC:
|
||||
// nothing to do
|
||||
return MP_OBJ_NEW_SMALL_INT(0); // success
|
||||
|
||||
case BP_IOCTL_SEC_COUNT:
|
||||
case MP_BLOCKDEV_IOCTL_BLOCK_COUNT:
|
||||
return MP_OBJ_NEW_SMALL_INT(sd_disk_info.ulNofBlock * (sd_disk_info.ulBlockSize / 512));
|
||||
|
||||
case BP_IOCTL_SEC_SIZE:
|
||||
case MP_BLOCKDEV_IOCTL_BLOCK_SIZE:
|
||||
return MP_OBJ_NEW_SMALL_INT(SD_SECTOR_SIZE);
|
||||
|
||||
default: // unknown command
|
||||
|
||||
@ -169,19 +169,6 @@ extern const struct _mp_obj_module_t mp_module_ussl;
|
||||
{ MP_ROM_QSTR(MP_QSTR_ubinascii), MP_ROM_PTR(&mp_module_ubinascii) }, \
|
||||
{ MP_ROM_QSTR(MP_QSTR_ussl), MP_ROM_PTR(&mp_module_ussl) }, \
|
||||
|
||||
#define MICROPY_PORT_BUILTIN_MODULE_WEAK_LINKS \
|
||||
{ MP_ROM_QSTR(MP_QSTR_errno), MP_ROM_PTR(&mp_module_uerrno) }, \
|
||||
{ MP_ROM_QSTR(MP_QSTR_struct), MP_ROM_PTR(&mp_module_ustruct) }, \
|
||||
{ MP_ROM_QSTR(MP_QSTR_re), MP_ROM_PTR(&mp_module_ure) }, \
|
||||
{ MP_ROM_QSTR(MP_QSTR_json), MP_ROM_PTR(&mp_module_ujson) }, \
|
||||
{ MP_ROM_QSTR(MP_QSTR_os), MP_ROM_PTR(&mp_module_uos) }, \
|
||||
{ MP_ROM_QSTR(MP_QSTR_time), MP_ROM_PTR(&mp_module_utime) }, \
|
||||
{ MP_ROM_QSTR(MP_QSTR_select), MP_ROM_PTR(&mp_module_uselect) }, \
|
||||
{ MP_ROM_QSTR(MP_QSTR_socket), MP_ROM_PTR(&mp_module_usocket) }, \
|
||||
{ MP_ROM_QSTR(MP_QSTR_binascii), MP_ROM_PTR(&mp_module_ubinascii) }, \
|
||||
{ MP_ROM_QSTR(MP_QSTR_ssl), MP_ROM_PTR(&mp_module_ussl) }, \
|
||||
{ MP_ROM_QSTR(MP_QSTR_machine), MP_ROM_PTR(&machine_module) }, \
|
||||
|
||||
// extra constants
|
||||
#define MICROPY_PORT_CONSTANTS \
|
||||
{ MP_ROM_QSTR(MP_QSTR_umachine), MP_ROM_PTR(&machine_module) }, \
|
||||
|
||||
@ -300,7 +300,7 @@ STATIC void mptask_init_sflash_filesystem (void) {
|
||||
// Initialise the local flash filesystem.
|
||||
// init the vfs object
|
||||
fs_user_mount_t *vfs_fat = sflash_vfs_fat;
|
||||
vfs_fat->flags = 0;
|
||||
vfs_fat->blockdev.flags = 0;
|
||||
pyb_flash_init_vfs(vfs_fat);
|
||||
|
||||
// Create it if needed, and mount in on /flash.
|
||||
|
||||
@ -33,12 +33,13 @@ MICROPY_FATFS = 1
|
||||
MICROPY_PY_BTREE = 1
|
||||
MICROPY_PY_ESPIFD = 1
|
||||
|
||||
#FROZEN_DIR = scripts
|
||||
FROZEN_MPY_DIR = modules
|
||||
FROZEN_MANIFEST ?= boards/manifest.py
|
||||
|
||||
# include py core make definitions
|
||||
include $(TOP)/py/py.mk
|
||||
|
||||
GIT_SUBMODULES = lib/berkeley-db-1.xx
|
||||
|
||||
PORT ?= /dev/ttyUSB0
|
||||
BAUD ?= 460800
|
||||
FLASH_MODE ?= dio
|
||||
@ -111,6 +112,16 @@ $(info Add the xtensa toolchain to your PATH. See README.md)
|
||||
$(error C compiler missing)
|
||||
endif
|
||||
|
||||
# Support BLE by default when building with IDF 4.x.
|
||||
# Can be explicitly disabled on the command line or board config.
|
||||
ifeq ($(ESPIDF_CURHASH),$(ESPIDF_SUPHASH_V4))
|
||||
MICROPY_PY_BLUETOOTH ?= 1
|
||||
ifeq ($(MICROPY_PY_BLUETOOTH),1)
|
||||
SDKCONFIG += boards/sdkconfig.ble
|
||||
MICROPY_BLUETOOTH_NIMBLE = 1
|
||||
endif
|
||||
endif
|
||||
|
||||
# include sdkconfig to get needed configuration values
|
||||
include $(SDKCONFIG)
|
||||
|
||||
@ -175,6 +186,29 @@ INC_ESPCOMP += -I$(ESPCOMP)/spi_flash/private_include
|
||||
INC_ESPCOMP += -I$(ESPCOMP)/wpa_supplicant/include/esp_supplicant
|
||||
INC_ESPCOMP += -I$(ESPCOMP)/xtensa/include
|
||||
INC_ESPCOMP += -I$(ESPCOMP)/xtensa/esp32/include
|
||||
ifeq ($(CONFIG_BT_NIMBLE_ENABLED),y)
|
||||
INC_ESPCOMP += -I$(ESPCOMP)/bt/include
|
||||
INC_ESPCOMP += -I$(ESPCOMP)/bt/common/osi/include
|
||||
INC_ESPCOMP += -I$(ESPCOMP)/bt/common/btc/include
|
||||
INC_ESPCOMP += -I$(ESPCOMP)/bt/common/include
|
||||
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/porting/nimble/include
|
||||
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/port/include
|
||||
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/include
|
||||
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/include
|
||||
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/ans/include
|
||||
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/bas/include
|
||||
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/gap/include
|
||||
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/gatt/include
|
||||
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/ias/include
|
||||
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/lls/include
|
||||
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/tps/include
|
||||
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/util/include
|
||||
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/store/ram/include
|
||||
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/nimble/host/store/config/include
|
||||
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/porting/npl/freertos/include
|
||||
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/nimble/ext/tinycrypt/include
|
||||
INC_ESPCOMP += -I$(ESPCOMP)/bt/host/nimble/esp-hci/include
|
||||
endif
|
||||
else
|
||||
INC_ESPCOMP += -I$(ESPCOMP)/ethernet/include
|
||||
INC_ESPCOMP += -I$(ESPCOMP)/expat/expat/expat/lib
|
||||
@ -186,6 +220,17 @@ INC_ESPCOMP += -I$(ESPCOMP)/nghttp/port/include
|
||||
INC_ESPCOMP += -I$(ESPCOMP)/nghttp/nghttp2/lib/includes
|
||||
endif
|
||||
|
||||
ifeq ($(ESPIDF_CURHASH),$(ESPIDF_SUPHASH_V4))
|
||||
ifeq ($(MICROPY_PY_BLUETOOTH),1)
|
||||
CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH=1
|
||||
CFLAGS_MOD += -DMICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE=1
|
||||
|
||||
ifeq ($(MICROPY_BLUETOOTH_NIMBLE),1)
|
||||
CFLAGS_MOD += -DMICROPY_BLUETOOTH_NIMBLE=1
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
# these flags are common to C and C++ compilation
|
||||
CFLAGS_COMMON = -Os -ffunction-sections -fdata-sections -fstrict-volatile-bitfields \
|
||||
-mlongcalls -nostdlib \
|
||||
@ -271,6 +316,7 @@ SRC_C = \
|
||||
modnetwork.c \
|
||||
network_lan.c \
|
||||
network_ppp.c \
|
||||
nimble.c \
|
||||
modsocket.c \
|
||||
modesp.c \
|
||||
esp32_partition.c \
|
||||
@ -287,6 +333,7 @@ SRC_C = \
|
||||
|
||||
EXTMOD_SRC_C = $(addprefix extmod/,\
|
||||
modonewire.c \
|
||||
modbluetooth_nimble.c \
|
||||
)
|
||||
|
||||
LIB_SRC_C = $(addprefix lib/,\
|
||||
@ -321,12 +368,11 @@ $(ESPIDFMOD_MODULE): $(ALL_ESPIDFMOD_SRC) $(LVGL_BINDING_DIR)/gen/gen_mpy.py
|
||||
|
||||
LIB_SRC_C += \
|
||||
lib/lv_bindings/driver/esp32/modlvesp32.c \
|
||||
lib/lv_bindings/driver/esp32/modILI9341.c \
|
||||
lib/lv_bindings/driver/esp32/modxpt2046.c \
|
||||
lib/lv_bindings/driver/esp32/modrtch.c \
|
||||
lib/lv_bindings/driver/esp32/espidf.c \
|
||||
$(ESPIDFMOD_MODULE) \
|
||||
lib/lv_bindings/driver/esp32/espidf.c
|
||||
#TODO lib/lv_bindings/driver/esp32/modxpt2046.c \
|
||||
# lib/lv_bindings/driver/esp32/modxpt2046.c \
|
||||
# lib/lv_bindings/driver/esp32/modILI9341.c \
|
||||
|
||||
endif
|
||||
|
||||
@ -488,6 +534,31 @@ ESPIDF_ESP_EVENT_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/esp_event/*.c))
|
||||
|
||||
ESPIDF_ESP_WIFI_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/esp_wifi/src/*.c))
|
||||
|
||||
ifeq ($(CONFIG_BT_NIMBLE_ENABLED),y)
|
||||
ESPIDF_BT_NIMBLE_O = $(patsubst %.c,%.o,\
|
||||
$(wildcard $(ESPCOMP)/bt/controller/*.c) \
|
||||
$(wildcard $(ESPCOMP)/bt/common/btc/core/*.c) \
|
||||
$(wildcard $(ESPCOMP)/bt/common/osi/*.c) \
|
||||
$(wildcard $(ESPCOMP)/bt/host/nimble/esp-hci/src/*.c) \
|
||||
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/ext/tinycrypt/src/*.c) \
|
||||
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/ans/src/*.c) \
|
||||
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/bas/src/*.c) \
|
||||
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/gap/src/*.c) \
|
||||
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/gatt/src/*.c) \
|
||||
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/ias/src/*.c) \
|
||||
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/lls/src/*.c) \
|
||||
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/services/tps/src/*.c) \
|
||||
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/src/*.c) \
|
||||
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/store/config/src/ble_store_config.c) \
|
||||
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/store/config/src/ble_store_nvs.c) \
|
||||
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/store/ram/src/*.c) \
|
||||
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/host/util/src/*.c) \
|
||||
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/nimble/src/*.c) \
|
||||
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/porting/nimble/src/*.c) \
|
||||
$(wildcard $(ESPCOMP)/bt/host/nimble/nimble/porting/npl/freertos/src/*.c) \
|
||||
)
|
||||
endif
|
||||
|
||||
$(BUILD)/$(ESPCOMP)/esp_eth/src/esp_eth_mac_dm9051.o: CFLAGS += -fno-strict-aliasing
|
||||
ESPIDF_ESP_ETH_O = $(patsubst %.c,%.o,$(wildcard $(ESPCOMP)/esp_eth/src/*.c))
|
||||
|
||||
@ -548,6 +619,9 @@ ifeq ($(ESPIDF_CURHASH),$(ESPIDF_SUPHASH_V4))
|
||||
$(eval $(call gen_espidf_lib_rule,esp_common,$(ESPIDF_ESP_COMMON_O)))
|
||||
$(eval $(call gen_espidf_lib_rule,esp_event,$(ESPIDF_ESP_EVENT_O)))
|
||||
$(eval $(call gen_espidf_lib_rule,esp_wifi,$(ESPIDF_ESP_WIFI_O)))
|
||||
ifeq ($(CONFIG_BT_NIMBLE_ENABLED),y)
|
||||
$(eval $(call gen_espidf_lib_rule,bt_nimble,$(ESPIDF_BT_NIMBLE_O)))
|
||||
endif
|
||||
$(eval $(call gen_espidf_lib_rule,esp_eth,$(ESPIDF_ESP_ETH_O)))
|
||||
$(eval $(call gen_espidf_lib_rule,xtensa,$(ESPIDF_XTENSA_O)))
|
||||
else
|
||||
@ -663,6 +737,7 @@ APP_LD_ARGS += -L$(dir $(LIBSTDCXX_FILE_NAME)) -lstdc++
|
||||
APP_LD_ARGS += $(LIBC_LIBM)
|
||||
ifeq ($(ESPIDF_CURHASH),$(ESPIDF_SUPHASH_V4))
|
||||
APP_LD_ARGS += -L$(ESPCOMP)/xtensa/esp32 -lhal
|
||||
APP_LD_ARGS += -L$(ESPCOMP)/bt/controller/lib -lbtdm_app
|
||||
APP_LD_ARGS += -L$(ESPCOMP)/esp_wifi/lib_esp32 -lcore -lmesh -lnet80211 -lphy -lrtc -lpp -lsmartconfig -lcoexist
|
||||
else
|
||||
APP_LD_ARGS += $(ESPCOMP)/esp32/libhal.a
|
||||
|
||||
@ -122,17 +122,10 @@ this repository):
|
||||
$ make -C mpy-cross
|
||||
```
|
||||
|
||||
The ESP32 port has a dependency on Berkeley DB, which is an external
|
||||
dependency (git submodule). You'll need to have git initialize that
|
||||
module using the commands:
|
||||
```bash
|
||||
$ git submodule init lib/berkeley-db-1.xx
|
||||
$ git submodule update
|
||||
```
|
||||
|
||||
Then to build MicroPython for the ESP32 run:
|
||||
```bash
|
||||
$ cd ports/esp32
|
||||
$ make submodules
|
||||
$ make
|
||||
```
|
||||
This will produce binary firmware images in the `build/` subdirectory
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
FLASH_FREQ = 80m
|
||||
|
||||
SDKCONFIG += boards/sdkconfig.base
|
||||
SDKCONFIG += boards/sdkconfig.240mhz
|
||||
SDKCONFIG += boards/sdkconfig.spiram
|
||||
SDKCONFIG += boards/TINYPICO/sdkconfig.board
|
||||
|
||||
6
ports/esp32/boards/manifest.py
Normal file
6
ports/esp32/boards/manifest.py
Normal file
@ -0,0 +1,6 @@
|
||||
freeze('$(PORT_DIR)/modules')
|
||||
freeze('$(MPY_DIR)/tools', ('upip.py', 'upip_utarfile.py'))
|
||||
freeze('$(MPY_DIR)/ports/esp8266/modules', 'ntptime.py')
|
||||
freeze('$(MPY_DIR)/ports/esp8266/modules', ('webrepl.py', 'webrepl_setup.py', 'websocket_helper.py',))
|
||||
freeze('$(MPY_DIR)/drivers/dht', 'dht.py')
|
||||
freeze('$(MPY_DIR)/drivers/onewire')
|
||||
6
ports/esp32/boards/manifest_release.py
Normal file
6
ports/esp32/boards/manifest_release.py
Normal file
@ -0,0 +1,6 @@
|
||||
include('manifest.py')
|
||||
|
||||
freeze('$(MPY_LIB_DIR)/upysh', 'upysh.py')
|
||||
freeze('$(MPY_LIB_DIR)/urequests', 'urequests.py')
|
||||
freeze('$(MPY_LIB_DIR)/umqtt.simple', 'umqtt/simple.py')
|
||||
freeze('$(MPY_LIB_DIR)/umqtt.robust', 'umqtt/robust.py')
|
||||
5
ports/esp32/boards/sdkconfig.240mhz
Normal file
5
ports/esp32/boards/sdkconfig.240mhz
Normal file
@ -0,0 +1,5 @@
|
||||
# MicroPython on ESP32, ESP IDF configuration with 240MHz CPU
|
||||
CONFIG_ESP32_DEFAULT_CPU_FREQ_80=
|
||||
CONFIG_ESP32_DEFAULT_CPU_FREQ_160=
|
||||
CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y
|
||||
CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=240
|
||||
@ -11,9 +11,8 @@ CONFIG_APP_EXCLUDE_PROJECT_NAME_VAR=y
|
||||
CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y
|
||||
|
||||
# ESP32-specific
|
||||
CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y
|
||||
CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=n
|
||||
CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1=n
|
||||
CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=n
|
||||
CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=n
|
||||
CONFIG_ESP32_XTAL_FREQ_AUTO=y
|
||||
CONFIG_FREERTOS_INTERRUPT_BACKTRACE=n
|
||||
CONFIG_IDLE_TASK_STACK_SIZE=4096
|
||||
@ -27,14 +26,19 @@ CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y
|
||||
CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP=y
|
||||
|
||||
# UDP
|
||||
CONFIG_LWIP_PPP_SUPPORT=y
|
||||
CONFIG_LWIP_PPP_PAP_SUPPORT=y
|
||||
CONFIG_LWIP_PPP_CHAP_SUPPORT=y
|
||||
|
||||
# v3.3-only (renamed in 4.0)
|
||||
CONFIG_LOG_BOOTLOADER_LEVEL_WARN=y
|
||||
CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=n
|
||||
CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1=n
|
||||
CONFIG_SUPPORT_STATIC_ALLOCATION=y
|
||||
CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK=y
|
||||
CONFIG_PPP_SUPPORT=y
|
||||
CONFIG_PPP_PAP_SUPPORT=y
|
||||
CONFIG_PPP_CHAP_SUPPORT=y
|
||||
|
||||
# v3.3-only (renamed in 4.0)
|
||||
CONFIG_LOG_BOOTLOADER_LEVEL_WARN=y
|
||||
CONFIG_SUPPORT_STATIC_ALLOCATION=y
|
||||
CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK=y
|
||||
|
||||
# Logs
|
||||
CONFIG_LOG_DEFAULT_LEVEL_WARN=y
|
||||
|
||||
14
ports/esp32/boards/sdkconfig.ble
Normal file
14
ports/esp32/boards/sdkconfig.ble
Normal file
@ -0,0 +1,14 @@
|
||||
# Note this requires building with IDF 4.x
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
|
||||
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=
|
||||
CONFIG_BTDM_CTRL_MODE_BTDM=
|
||||
|
||||
CONFIG_BT_NIMBLE_ENABLED=y
|
||||
|
||||
CONFIG_BT_NIMBLE_MAX_CONNECTIONS=4
|
||||
|
||||
# Pin to the same core as MP.
|
||||
CONFIG_BT_NIMBLE_PINNED_TO_CORE_0=n
|
||||
CONFIG_BT_NIMBLE_PINNED_TO_CORE_1=y
|
||||
CONFIG_BT_NIMBLE_PINNED_TO_CORE=1
|
||||
@ -173,11 +173,11 @@ STATIC mp_obj_t esp32_partition_ioctl(mp_obj_t self_in, mp_obj_t cmd_in, mp_obj_
|
||||
esp32_partition_obj_t *self = MP_OBJ_TO_PTR(self_in);
|
||||
mp_int_t cmd = mp_obj_get_int(cmd_in);
|
||||
switch (cmd) {
|
||||
case BP_IOCTL_INIT: return MP_OBJ_NEW_SMALL_INT(0);
|
||||
case BP_IOCTL_DEINIT: return MP_OBJ_NEW_SMALL_INT(0);
|
||||
case BP_IOCTL_SYNC: return MP_OBJ_NEW_SMALL_INT(0);
|
||||
case BP_IOCTL_SEC_COUNT: return MP_OBJ_NEW_SMALL_INT(self->part->size / BLOCK_SIZE_BYTES);
|
||||
case BP_IOCTL_SEC_SIZE: return MP_OBJ_NEW_SMALL_INT(BLOCK_SIZE_BYTES);
|
||||
case MP_BLOCKDEV_IOCTL_INIT: return MP_OBJ_NEW_SMALL_INT(0);
|
||||
case MP_BLOCKDEV_IOCTL_DEINIT: return MP_OBJ_NEW_SMALL_INT(0);
|
||||
case MP_BLOCKDEV_IOCTL_SYNC: return MP_OBJ_NEW_SMALL_INT(0);
|
||||
case MP_BLOCKDEV_IOCTL_BLOCK_COUNT: return MP_OBJ_NEW_SMALL_INT(self->part->size / BLOCK_SIZE_BYTES);
|
||||
case MP_BLOCKDEV_IOCTL_BLOCK_SIZE: return MP_OBJ_NEW_SMALL_INT(BLOCK_SIZE_BYTES);
|
||||
default: return mp_const_none;
|
||||
}
|
||||
}
|
||||
|
||||
@ -205,7 +205,7 @@ STATIC void machine_hw_spi_init_internal(
|
||||
return;
|
||||
|
||||
case ESP_ERR_INVALID_STATE:
|
||||
mp_raise_msg(&mp_type_OSError, "SPI device already in use");
|
||||
mp_raise_msg(&mp_type_OSError, "SPI host already in use");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -337,26 +337,26 @@ STATIC mp_obj_t machine_sdcard_ioctl(mp_obj_t self_in, mp_obj_t cmd_in, mp_obj_t
|
||||
mp_int_t cmd = mp_obj_get_int(cmd_in);
|
||||
|
||||
switch (cmd) {
|
||||
case BP_IOCTL_INIT:
|
||||
case MP_BLOCKDEV_IOCTL_INIT:
|
||||
err = sdcard_ensure_card_init(self, false);
|
||||
return MP_OBJ_NEW_SMALL_INT((err == ESP_OK) ? 0 : -1);
|
||||
|
||||
case BP_IOCTL_DEINIT:
|
||||
case MP_BLOCKDEV_IOCTL_DEINIT:
|
||||
// Ensure that future attempts to look at info re-read the card
|
||||
self->flags &= ~SDCARD_CARD_FLAGS_CARD_INIT_DONE;
|
||||
return MP_OBJ_NEW_SMALL_INT(0); // success
|
||||
|
||||
case BP_IOCTL_SYNC:
|
||||
case MP_BLOCKDEV_IOCTL_SYNC:
|
||||
// nothing to do
|
||||
return MP_OBJ_NEW_SMALL_INT(0); // success
|
||||
|
||||
case BP_IOCTL_SEC_COUNT:
|
||||
case MP_BLOCKDEV_IOCTL_BLOCK_COUNT:
|
||||
err = sdcard_ensure_card_init(self, false);
|
||||
if (err != ESP_OK)
|
||||
return MP_OBJ_NEW_SMALL_INT(-1);
|
||||
return MP_OBJ_NEW_SMALL_INT(self->card.csd.capacity);
|
||||
|
||||
case BP_IOCTL_SEC_SIZE:
|
||||
case MP_BLOCKDEV_IOCTL_BLOCK_SIZE:
|
||||
err = sdcard_ensure_card_init(self, false);
|
||||
if (err != ESP_OK)
|
||||
return MP_OBJ_NEW_SMALL_INT(-1);
|
||||
|
||||
@ -50,6 +50,7 @@ typedef struct _machine_uart_obj_t {
|
||||
uint16_t rxbuf;
|
||||
uint16_t timeout; // timeout waiting for first char (in ms)
|
||||
uint16_t timeout_char; // timeout waiting between chars (in ms)
|
||||
uint32_t invert; // lines to invert
|
||||
} machine_uart_obj_t;
|
||||
|
||||
STATIC const char *_parity_name[] = {"None", "1", "0"};
|
||||
@ -61,13 +62,42 @@ STATIC void machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_pri
|
||||
machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in);
|
||||
uint32_t baudrate;
|
||||
uart_get_baudrate(self->uart_num, &baudrate);
|
||||
mp_printf(print, "UART(%u, baudrate=%u, bits=%u, parity=%s, stop=%u, tx=%d, rx=%d, rts=%d, cts=%d, txbuf=%u, rxbuf=%u, timeout=%u, timeout_char=%u)",
|
||||
mp_printf(print, "UART(%u, baudrate=%u, bits=%u, parity=%s, stop=%u, tx=%d, rx=%d, rts=%d, cts=%d, txbuf=%u, rxbuf=%u, timeout=%u, timeout_char=%u",
|
||||
self->uart_num, baudrate, self->bits, _parity_name[self->parity],
|
||||
self->stop, self->tx, self->rx, self->rts, self->cts, self->txbuf, self->rxbuf, self->timeout, self->timeout_char);
|
||||
if (self->invert) {
|
||||
mp_printf(print, ", invert=");
|
||||
uint32_t invert_mask = self->invert;
|
||||
if (invert_mask & UART_INVERSE_TXD) {
|
||||
mp_printf(print, "INV_TX");
|
||||
invert_mask &= ~UART_INVERSE_TXD;
|
||||
if (invert_mask) {
|
||||
mp_printf(print, "|");
|
||||
}
|
||||
}
|
||||
if (invert_mask & UART_INVERSE_RXD) {
|
||||
mp_printf(print, "INV_RX");
|
||||
invert_mask &= ~UART_INVERSE_RXD;
|
||||
if (invert_mask) {
|
||||
mp_printf(print, "|");
|
||||
}
|
||||
}
|
||||
if (invert_mask & UART_INVERSE_RTS) {
|
||||
mp_printf(print, "INV_RTS");
|
||||
invert_mask &= ~UART_INVERSE_RTS;
|
||||
if (invert_mask) {
|
||||
mp_printf(print, "|");
|
||||
}
|
||||
}
|
||||
if (invert_mask & UART_INVERSE_CTS) {
|
||||
mp_printf(print, "INV_CTS");
|
||||
}
|
||||
}
|
||||
mp_printf(print, ")");
|
||||
}
|
||||
|
||||
STATIC void machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
|
||||
enum { ARG_baudrate, ARG_bits, ARG_parity, ARG_stop, ARG_tx, ARG_rx, ARG_rts, ARG_cts, ARG_txbuf, ARG_rxbuf, ARG_timeout, ARG_timeout_char };
|
||||
enum { ARG_baudrate, ARG_bits, ARG_parity, ARG_stop, ARG_tx, ARG_rx, ARG_rts, ARG_cts, ARG_txbuf, ARG_rxbuf, ARG_timeout, ARG_timeout_char, ARG_invert };
|
||||
static const mp_arg_t allowed_args[] = {
|
||||
{ MP_QSTR_baudrate, MP_ARG_INT, {.u_int = 0} },
|
||||
{ MP_QSTR_bits, MP_ARG_INT, {.u_int = 0} },
|
||||
@ -81,6 +111,7 @@ STATIC void machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, co
|
||||
{ MP_QSTR_rxbuf, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} },
|
||||
{ MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
|
||||
{ MP_QSTR_timeout_char, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
|
||||
{ MP_QSTR_invert, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
|
||||
};
|
||||
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
|
||||
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
|
||||
@ -205,6 +236,13 @@ STATIC void machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, co
|
||||
if (self->timeout_char < min_timeout_char) {
|
||||
self->timeout_char = min_timeout_char;
|
||||
}
|
||||
|
||||
// set line inversion
|
||||
if (args[ARG_invert].u_int & ~UART_LINE_INV_MASK) {
|
||||
mp_raise_ValueError("invalid inversion mask");
|
||||
}
|
||||
self->invert = args[ARG_invert].u_int;
|
||||
uart_set_line_inverse(self->uart_num, self->invert);
|
||||
}
|
||||
|
||||
STATIC mp_obj_t machine_uart_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
|
||||
@ -341,6 +379,11 @@ STATIC const mp_rom_map_elem_t machine_uart_locals_dict_table[] = {
|
||||
{ MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_sendbreak), MP_ROM_PTR(&machine_uart_sendbreak_obj) },
|
||||
|
||||
{ MP_ROM_QSTR(MP_QSTR_INV_TX), MP_ROM_INT(UART_INVERSE_TXD) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_INV_RX), MP_ROM_INT(UART_INVERSE_RXD) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_INV_RTS), MP_ROM_INT(UART_INVERSE_RTS) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_INV_CTS), MP_ROM_INT(UART_INVERSE_CTS) },
|
||||
};
|
||||
|
||||
STATIC MP_DEFINE_CONST_DICT(machine_uart_locals_dict, machine_uart_locals_dict_table);
|
||||
|
||||
@ -172,3 +172,13 @@ void nlr_jump_fail(void *val) {
|
||||
void mbedtls_debug_set_threshold(int threshold) {
|
||||
(void)threshold;
|
||||
}
|
||||
|
||||
void *esp_native_code_commit(void *buf, size_t len) {
|
||||
len = (len + 3) & ~3;
|
||||
uint32_t *p = heap_caps_malloc(len, MALLOC_CAP_EXEC);
|
||||
if (p == NULL) {
|
||||
m_malloc_fail(len);
|
||||
}
|
||||
memcpy(p, buf, len);
|
||||
return p;
|
||||
}
|
||||
|
||||
@ -1 +0,0 @@
|
||||
../../../drivers/dht/dht.py
|
||||
@ -1 +0,0 @@
|
||||
../../esp8266/modules/ds18x20.py
|
||||
1
ports/esp32/modules/ili9341.py
Symbolic link
1
ports/esp32/modules/ili9341.py
Symbolic link
@ -0,0 +1 @@
|
||||
../../../lib/lv_bindings/driver/esp32/ili9341.py
|
||||
@ -1 +0,0 @@
|
||||
../../esp8266/modules/ntptime.py
|
||||
@ -1 +0,0 @@
|
||||
../../esp8266/modules/onewire.py
|
||||
@ -1 +0,0 @@
|
||||
../../../../../micropython-lib/umqtt.robust/umqtt/robust.py
|
||||
@ -1 +0,0 @@
|
||||
../../../../../micropython-lib/umqtt.simple/umqtt/simple.py
|
||||
@ -1 +0,0 @@
|
||||
../../../tools/upip.py
|
||||
@ -1 +0,0 @@
|
||||
../../../tools/upip_utarfile.py
|
||||
@ -1 +0,0 @@
|
||||
../../../../micropython-lib/upysh/upysh.py
|
||||
@ -1 +0,0 @@
|
||||
../../../../micropython-lib/urequests/urequests.py
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user