Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • add_menu_vibration
  • blinkisync-as-preload
  • ch3/api-speed-eval2
  • ch3/dual-core
  • ch3/genapi-refactor
  • ch3/leds-api
  • ch3/splashscreen
  • dualcore
  • dx/flatten-config-module
  • dx/meh-bdf-to-stm
  • freertos-btle
  • genofire/ble-follow-py
  • koalo/bhi160-works-but-dirty
  • koalo/factory-reset
  • koalo/wip/i2c-for-python
  • master
  • msgctl/faultscreen
  • msgctl/textbuffer_api
  • plaetzchen/ios-workaround
  • rahix/bhi
  • rahix/bluetooth-app-favorite
  • rahix/bma
  • rahix/user-space-ctx
  • renze/hatchery_apps
  • renze/safe_mode
  • schleicher-test
  • schneider/212-reset-hardware-when-entering-repl
  • schneider/ancs
  • schneider/ble-buffers
  • schneider/ble-central
  • schneider/ble-ecg-stream-visu
  • schneider/ble-fixes-2020-3
  • schneider/ble-mini-demo
  • schneider/ble-stability
  • schneider/ble-stability-new-phy
  • schneider/bonding
  • schneider/bonding-fail-if-full
  • schneider/bootloader-update-9a0d158
  • schneider/deepsleep
  • schneider/deepsleep2
  • schneider/deepsleep4
  • schneider/default-main
  • schneider/freertos-list-debug
  • schneider/fundamental-test
  • schneider/iaq-python
  • schneider/ir
  • schneider/max30001
  • schneider/max30001-epicaridum
  • schneider/max30001-pycardium
  • schneider/maxim-sdk-update
  • schneider/mp-exception-print
  • schneider/mp-for-old-bl
  • schneider/png
  • schneider/schleicher-test
  • schneider/sdk-0.2.1-11
  • schneider/sdk-0.2.1-7
  • schneider/sleep-display
  • schneider/spo2-playground
  • schneider/stream-locks
  • schneider/v1.17-changelog
  • bootloader-v1
  • release-1
  • v0.0
  • v1.0
  • v1.1
  • v1.10
  • v1.11
  • v1.12
  • v1.13
  • v1.14
  • v1.15
  • v1.16
  • v1.17
  • v1.18
  • v1.2
  • v1.3
  • v1.4
  • v1.5
  • v1.6
  • v1.7
  • v1.8
  • v1.9
82 results

Target

Select target project
No results found
Select Git revision
  • ch3/api-speed-eval2
  • ch3/dual-core
  • ch3/genapi-refactor
  • ch3/leds-api
  • ch3/splashscreen
  • dualcore
  • filenames-blacklist
  • freertos-btle
  • genofire/ble-rewrite
  • ios-workarounds
  • koalo/bhi160-works-but-dirty
  • koalo/factory-reset
  • koalo/wip/i2c-for-python
  • m
  • master
  • mh/blecentral
  • msgctl/faultscreen
  • msgctl/gfx_rle
  • msgctl/textbuffer_api
  • patch-1
  • patch-2
  • rahix/bhi
  • rahix/ble-fix
  • rahix/bma
  • rahix/simple_menu
  • renze/hatchery_apps
  • renze/safe_mode
  • schleicher-test
  • schneider/bonding
  • schneider/bootloader-update-9a0d158
  • schneider/bsec
  • schneider/fundamental-test
  • schneider/mp-for-old-bl
  • schneider/schleicher-test
  • bootloader-v1
  • release-1
  • v0.0
  • v1.0
  • v1.1
  • v1.2
40 results
Show changes
1000 files
+ 165640
38534
Compare changes
  • Side-by-side
  • Inline

Files

+1 −1
Original line number Diff line number Diff line
flags = -DBOARD_CARD10=1 -D_FILE_OFFSET_BITS=64 -DTARGET=32665 -DTARGET_REV=0x4131 -target thumbv7m-none-eabi -Ibuild/epicardium -Ibuild/epicardium/366573f@@api-caller@sta -Ibuild/epicardium/366573f@@api-dispatcher@sta -Ibuild/epicardium/366573f@@epicardium.elf@exe -Ibuild/epicardium/366573f@@freertos@sta -Ibuild/lib/card10 -Ibuild/lib/card10/7eaaaa5@@card10@sta -Ibuild/lib/ff13 -Ibuild/lib/ff13/a277df3@@ff13@sta -Ibuild/lib/gfx -Ibuild/lib/gfx/2308dff@@gfx@sta -Ibuild/lib/micropython -Ibuild/lib/micropython/a57cd11@@mpy-cross-wrapper@exe -Ibuild/lib/mx25lba -Ibuild/lib/mx25lba/c7b864b@@mx25lba@sta -Ibuild/lib/sdk/Libraries/Boards/card10 -Ibuild/lib/sdk/Libraries/Boards/card10/9eeeac4@@board-card10@sta -Ibuild/lib/sdk/Libraries/CMSIS/Device/Maxim/MAX32665 -Ibuild/lib/sdk/Libraries/CMSIS/Device/Maxim/MAX32665/a500f70@@max32665-startup-core0@sta -Ibuild/lib/sdk/Libraries/CMSIS/Device/Maxim/MAX32665/a500f70@@max32665-startup-core1@sta -Ibuild/lib/sdk/Libraries/CMSIS/Device/Maxim/MAX32665/a500f70@@max32665-startup@sta -Ibuild/lib/sdk/Libraries/MAX32665PeriphDriver -Ibuild/lib/sdk/Libraries/MAX32665PeriphDriver/0d96707@@PeriphDriver@sta -Ibuild/lib/sdk/Libraries/MAXUSB -Ibuild/lib/sdk/Libraries/MAXUSB/9a51a91@@maxusb@sta -Ibuild/lib/vendor/Bosch/BHy1 -Ibuild/lib/vendor/Bosch/BHy1/6298ab9@@bhy1@sta -Ibuild/lib/vendor/Bosch/BMA400 -Ibuild/lib/vendor/Bosch/BMA400/b6b0216@@bma400@sta -Ibuild/lib/vendor/Bosch/BME680 -Ibuild/lib/vendor/Bosch/BME680/ef6f079@@bme680@sta -Ibuild/lib/vendor/Maxim/MAX77650 -Ibuild/lib/vendor/Maxim/MAX77650/cc369b8@@max77650@sta -Ibuild/lib/vendor/Maxim/MAX86150 -Ibuild/lib/vendor/Maxim/MAX86150/21e3a66@@max86150@sta -Ibuild/pycardium -Ibuild/pycardium/1f90fd2@@micropython@sta -Ibuild/pycardium/1f90fd2@@pycardium.elf@exe -Iepicardium -Ilib/card10 -Ilib/ff13 -Ilib/ff13/Source -Ilib/ff13/util -Ilib/FreeRTOS/Source/include -Ilib/FreeRTOS/Source/portable/GCC/ARM_CM4F -Ilib/gfx -Ilib/gfx/Fonts -Ilib/gfx/GUI_DEV -Ilib/gfx/LCD -Ilib/micropython -Ilib/micropython/micropython -Ilib/micropython/micropython/extmod -Ilib/micropython/micropython/lib/utils -Ilib/mx25lba -Ilib/sdk/Libraries/Boards/card10 -Ilib/sdk/Libraries/Boards/card10/Include -Ilib/sdk/Libraries/Boards/Include -Ilib/sdk/Libraries/CMSIS/Device/Maxim/MAX32665 -Ilib/sdk/Libraries/CMSIS/Device/Maxim/MAX32665/Include -Ilib/sdk/Libraries/CMSIS/Include -Ilib/sdk/Libraries/MAX32665PeriphDriver -Ilib/sdk/Libraries/MAX32665PeriphDriver/Include -Ilib/sdk/Libraries/MAXUSB -Ilib/sdk/Libraries/MAXUSB/include/core -Ilib/sdk/Libraries/MAXUSB/include/core/musbhsfc -Ilib/sdk/Libraries/MAXUSB/include/dbg_log -Ilib/sdk/Libraries/MAXUSB/include/devclass -Ilib/sdk/Libraries/MAXUSB/include/enumerate -Ilib/sdk/Libraries/MAXUSB/include/util -Ilib/vendor/Bosch/BHy1 -Ilib/vendor/Bosch/BHy1/driver/inc -Ilib/vendor/Bosch/BHy1/examples/firmware -Ilib/vendor/Bosch/BMA400 -Ilib/vendor/Bosch/BME680 -Ilib/vendor/Maxim/MAX77650 -Ilib/vendor/Maxim/MAX86150 -Ipycardium
flags = -DBOARD_CARD10=1 -D_FILE_OFFSET_BITS=64 -DTARGET=32665 -DTARGET_REV=0x4131 -target thumbv7m-none-eabi -Ibuild/epicardium -Ibuild/epicardium/366573f@@api-caller@sta -Ibuild/epicardium/366573f@@api-dispatcher@sta -Ibuild/epicardium/366573f@@epicardium.elf@exe -Ibuild/epicardium/366573f@@freertos@sta -Ibuild/lib/card10 -Ibuild/lib/card10/7eaaaa5@@card10@sta -Ibuild/lib/ff13 -Ibuild/lib/ff13/a277df3@@ff13@sta -Ibuild/lib/gfx -Ibuild/lib/gfx/2308dff@@gfx@sta -Ibuild/lib/micropython -Ibuild/lib/micropython/a57cd11@@mpy-cross-wrapper@exe -Ibuild/lib/mx25lba -Ibuild/lib/mx25lba/c7b864b@@mx25lba@sta -Ibuild/lib/sdk/Libraries/Boards/card10 -Ibuild/lib/sdk/Libraries/Boards/card10/9eeeac4@@board-card10@sta -Ibuild/lib/sdk/Libraries/CMSIS/Device/Maxim/MAX32665 -Ibuild/lib/sdk/Libraries/CMSIS/Device/Maxim/MAX32665/a500f70@@max32665-startup-core0@sta -Ibuild/lib/sdk/Libraries/CMSIS/Device/Maxim/MAX32665/a500f70@@max32665-startup-core1@sta -Ibuild/lib/sdk/Libraries/CMSIS/Device/Maxim/MAX32665/a500f70@@max32665-startup@sta -Ibuild/lib/sdk/Libraries/MAX32665PeriphDriver -Ibuild/lib/sdk/Libraries/MAX32665PeriphDriver/0d96707@@PeriphDriver@sta -Ibuild/lib/sdk/Libraries/MAXUSB -Ibuild/lib/sdk/Libraries/MAXUSB/9a51a91@@maxusb@sta -Ibuild/lib/vendor/Bosch/BHy1 -Ibuild/lib/vendor/Bosch/BHy1/6298ab9@@bhy1@sta -Ibuild/lib/vendor/Bosch/BMA400 -Ibuild/lib/vendor/Bosch/BMA400/b6b0216@@bma400@sta -Ibuild/lib/vendor/Bosch/BME680 -Ibuild/lib/vendor/Bosch/BME680/ef6f079@@bme680@sta -Ibuild/lib/vendor/Maxim/MAX77650 -Ibuild/lib/vendor/Maxim/MAX77650/cc369b8@@max77650@sta -Ibuild/lib/vendor/Maxim/MAX86150 -Ibuild/lib/vendor/Maxim/MAX86150/21e3a66@@max86150@sta -Ibuild/pycardium -Ibuild/pycardium/1f90fd2@@micropython@sta -Ibuild/pycardium/1f90fd2@@pycardium.elf@exe -Iepicardium -Ilib/card10 -Ilib/ff13 -Ilib/ff13/Source -Ilib/ff13/util -Ilib/FreeRTOS/Source/include -Ilib/FreeRTOS/Source/portable/GCC/ARM_CM4F -Ilib/gfx -Ilib/gfx/Fonts -Ilib/gfx/GUI_DEV -Ilib/gfx/LCD -Ilib/micropython -Ilib/micropython/micropython -Ilib/micropython/micropython/extmod -Ilib/micropython/micropython/lib/utils -Ilib/mx25lba -Ilib/sdk/Libraries/Boards/card10 -Ilib/sdk/Libraries/Boards/card10/Include -Ilib/sdk/Libraries/Boards/Include -Ilib/sdk/Libraries/CMSIS/Device/Maxim/MAX32665 -Ilib/sdk/Libraries/CMSIS/Device/Maxim/MAX32665/Include -Ilib/sdk/Libraries/CMSIS/Include -Ilib/sdk/Libraries/MAX32665PeriphDriver -Ilib/sdk/Libraries/MAX32665PeriphDriver/Include -Ilib/sdk/Libraries/MAXUSB -Ilib/sdk/Libraries/MAXUSB/include/core -Ilib/sdk/Libraries/MAXUSB/include/core/musbhsfc -Ilib/sdk/Libraries/MAXUSB/include/dbg_log -Ilib/sdk/Libraries/MAXUSB/include/devclass -Ilib/sdk/Libraries/MAXUSB/include/enumerate -Ilib/sdk/Libraries/MAXUSB/include/util -Ilib/vendor/Bosch/BHy1 -Ilib/vendor/Bosch/BHy1/driver/inc -Ilib/vendor/Bosch/BHy1/examples/firmware -Ilib/vendor/Bosch/BMA400 -Ilib/vendor/Bosch/BME680 -Ilib/vendor/Maxim/MAX77650 -Ilib/vendor/Maxim/MAX86150 -Ipycardium -Ilib/ctx
+2 −1
Original line number Diff line number Diff line
build/
/build/
/Documentation/output/
__pycache__/
*.pyc
@@ -6,3 +6,4 @@ __pycache__/
*~
compile_commands.json
/tags
/release-*/
+15 −4
Original line number Diff line number Diff line
@@ -4,22 +4,33 @@ image: "derq3k/card10-build-env:20190806-195837Z-f95b541-dirty"
build:
    stage: build
    script:
        - ./bootstrap.sh
        - git submodule deinit --all -f
        - ./bootstrap.sh --werror
        - ninja -C build/
        - arm-none-eabi-size build/bootloader/bootloader.elf build/epicardium/epicardium.elf build/pycardium/pycardium.elf
        - cp build/pycardium/pycardium_epicardium.bin card10.bin
    only:
        - merge_requests
        - master
    artifacts:
        expose_as: Firmware Binaries
        expire_in: 1 week
        paths:
            - build/epicardium/epicardium.elf
            - build/pycardium/pycardium.elf
            - card10.bin

release:
    stage: build
    script:
        - ./bootstrap.sh
        - git submodule deinit --all -f
        - ./bootstrap.sh --werror
        - ninja -C build/
        - arm-none-eabi-size build/bootloader/bootloader.elf build/epicardium/epicardium.elf build/pycardium/pycardium.elf
    only:
        - tag
    artifacts:
        expire_in: never
        paths:
            - build/bootloader/bootloader.elf
            - build/epicardium/epicardium.elf
@@ -36,7 +47,7 @@ lint:
        # If this starts failing for any reason, just remove this curl ping.
        - curl --fail https://annoyatron-prod.q3k.org/ping/mr?mr=${CI_MERGE_REQUEST_IID}
        - git remote rm card10 || true # old gitlab runners might have this remote.
        - git fetch https://git.card10.badge.events.ccc.de/card10/firmware.git master:card10/master
        - git -c http.sslVerify=false fetch https://git.card10.badge.events.ccc.de/card10/firmware.git master:card10/master
        - git merge-base card10/master HEAD || ( echo "Your change needs to be rebased against current master."; exit 1; )
        - git diff --name-only --diff-filter=d card10/master...HEAD | xargs tools/code-style.sh
        - git diff --exit-code
@@ -46,7 +57,7 @@ lint:
pages:
    stage: deploy
    # maintaned by q3k, build using docker/deploy-env
    image: "derq3k/card10-deploy-env:20190806-200743Z-f95b541-dirty"
    image: "registry.k0.hswaw.net/q3k/card10-deploy-env:20210403-110003Z-4d929ee0"
    script:
        - export LD_LIBRARY_PATH=$(llvm-config --libdir)
        - echo $LD_LIBRARY_PATH
+9 −0
Original line number Diff line number Diff line
@@ -4,3 +4,12 @@
[submodule "lib/micropython/micropython-lib"]
	path = lib/micropython/micropython-lib
	url = https://github.com/micropython/micropython-lib.git
[submodule "lib/crypto/tiny-AES-c"]
	path = lib/crypto/tiny-AES-c
	url = https://github.com/kokke/tiny-AES-c
[submodule "lib/crypto/SHA256"]
	path = lib/crypto/SHA256
	url = https://github.com/ilvn/SHA256
[submodule "lib/lodepng/lodepng"]
	path = lib/lodepng/lodepng
	url = https://github.com/lvandeve/lodepng

CHANGELOG.md

0 → 100644
+570 −0

File added.

Preview size limit exceeded, changes collapsed.

Original line number Diff line number Diff line
.. _bluetooth_card10_service:

Bluetooth Card10 Service
========================

@@ -18,72 +20,77 @@ The current draft uses following service specification:
- Time update characteristic:

  UUID: ``42230201-2342-2342-2342-234223422342``
  write
  read and write no response

- Vibra characteristic:

  UUID: ``4223020f-2342-2342-2342-234223422342``
  write
  write no response

- Rockets characteristic:

  UUID: ``42230210-2342-2342-2342-234223422342``
  write
  read and write no response

- Background LED Bottom Left characteristic:

  UUID: ``42230211-2342-2342-2342-234223422342``
  write
  read and write no response

- Background LED Bottom Right characteristic:

  UUID: ``42230212-2342-2342-2342-234223422342``
  write
  read and write no response

- Background LED Top Right characteristic:

  UUID: ``42230213-2342-2342-2342-234223422342``
  write
  read and write no response

- Background LED Top Left characteristic:

  UUID: ``42230214-2342-2342-2342-234223422342``
  write
  read and write no reponse

- LEDS dim bottom characteristic:

  UUID: ``42230215-2342-2342-2342-234223422342``
  write
  write with response

- LEDs dim top characteristic:

  UUID: ``42230216-2342-2342-2342-234223422342``
  write
  write with response

- LEDs powersafe characteristic:

  UUID: ``42230217-2342-2342-2342-234223422342``
  write
  write no response

- Flashlight characteristic:

  UUID: ``42230218-2342-2342-2342-234223422342``
  write
  write no response

- Personal State characteristic:

  UUID: ``42230219-2342-2342-2342-234223422342``
  read and write with response

- LEDs above characteristic:

  UUID: ``42230220-2342-2342-2342-234223422342``
  write
  read and write no reponse

- Light sensor characteristic:

  UUID: ``422302f0-2342-2342-2342-234223422342``
  read
  read no response

Time update characteristic
---------------------------------

The time update characteristic makes it possible to set the current time given in milliseconds after 1.1.1970 in the UTC timezone. The value is represented as a big endian ``uint64``
The time update characteristic makes it possible to set and get the current time given in milliseconds after 1.1.1970 in the UTC timezone. The value is represented as a big endian ``uint64``

- Thu Aug 15 19:40:45 UTC 2019 : ``0x0 0x0 0x1 0x6c 0x96 0xcb 0xf8 0xcc``

@@ -99,6 +106,9 @@ Rockets characteristic

The Rockets characteristic makes it possible to address every three rockets.
Just write there three byte array, one for evey rocket.
On read you get the current value of all three rockets.
Range is between 0 and 31 (``0x1f``) if send higher value it will set to max of 31.


Dataformat:

@@ -108,14 +118,14 @@ Dataformat:
Rocket0 Rocket1 Rocket2
======= ======= =======

- Enable only Rocket0:  ``0xff0000``
- Enable all rockets with 50% brightness: ``0x7f7f7f``
- Enable only Rocket0:  ``0x1f0000``
- Enable all rockets with 50% brightness: ``0x0f0f0f``

Background LED <Position> characteristic
----------------------------------------

The Background LEDs <Position> characteristic makes it possible to address the bottom LEDs by position.
Just write there three ``uint8`` for the rgb color.
Just write there three ``uint8`` for the rgb color or read the current value.

Dataformat:

@@ -153,9 +163,19 @@ This characteristic makes it possible to activate the flashlight.
- enabled:   ``0x01``
- disabled:  ``0x00``

Personal state characteristic
---------------------------------
This characteristic makes it possible to read and write the personal state.
It writes always as persistant and it gives feedback if the value is in range and your firmware support it.

- No State ``0x0000``
- No Contact ``0x0100``
- Chaos ``0x0200``
- ...

LEDs above characteristic
---------------------------------
This characteristic set every 11 leds on the top module at once.
This characteristic set or read the current value of every 11 leds on the top module at once.
By defining 11x rgb from left to right. You need also to set exchange a bigger MTU to use this feature.

- set a rainbow beginnig with red on the right edge: ``0xff0000ff8b00e8ff005dff0000ff2e00ffb900b9ff002eff5d00ffe800ffff008b``
@@ -167,3 +187,61 @@ The light sensor characteristic makes it possible to read the current value of t
The range of this sensor is between 0 (``0x0``) and 400 (``0x9001``).

- reading of ``0x0e00`` means **14**

Access via btgatt-client
---------------------------------

Accessing services from a linux system is possible via ``btgatt-client``. The inbuilt gatt client of ``bluetoothctl`` as well as ``libgatt`` were tested, but struggled with the card10's BLE stack.

**Example**:

.. code-block::

    # pairing the card10:

    $ bluetoothctl
    [bluetooth]# power on
    [bluetooth]# scan on
    [bluetooth]# pair CA:4D:10:xx:xx:xx     #replace xx:xx:xx with scan result
    # if this query doesn't appear, remove and re-pair:
    [agent] Confirm passkey ###### (yes/no): [CHG] Device CA:4D:10:xx:xx:xx Name: card10
    [card10-xxxxxx]# disconnect CA:4D:10:xx:xx:xx

    # using a service:

    $ btgatt-client -d CA:4D:10:xx:xx:xx
    # wait until services have been discovered, may take a minute
    [GATT client]# write-value 0x0926 31 31 31

    # if this error appears remove and re-pair:
    [GATT client]# Device disconnected: Software caused connection abort


ARM Cordio Sources
------------------

The BLE stack is based on the ARM Cordio stack. This stack has been developed by a 3rd party and was bought by ARM and open-sourced.

There are many copies of it floating around on the Internet and some are more up to date than others. To keep track here is a list:
 - mbed
    - mbed has a reasonably up to date version of the stack in their GitHub repository.
    - It is scattered below the CORDIO directories in https://github.com/ARMmbed/mbed-os/tree/master/features/FEATURE_BLE/targets
    - ble-profiles are not included. mbed has written their own adaptation layer to interface with the stack and implements profiles in C++
 - Ambiq Suite SDK
    - Can be found here: https://github.com/sparkfun/AmbiqSuiteSDK/tree/master/third_party/exactle
    - Patches on top might be here: https://support.ambiqmicro.com/hc/en-us/categories/115000239012-Software
    - Reasonably up to date
    - Intersting part: has their own FreeRTOS integration (but apparently on older WSF)
    - BLE 5.1 (?)
 - Packetcraft
    - Apparently tasked with maintaining the stack in general
    - Most up to date version
    - https://github.com/packetcraft-inc/cordio
    - Apparently developing (security) fixes, distributing them to customers, but not applying them to master: https://github.com/ARMmbed/mbed-os/commit/c92777311578eb003b4546c4e5e6f2c1f8ba3c84
    - BLE 5.1
 - Maxim
    - Distributed via Maxim Toolchain Installation tool, no repository available
    - Contains software implementation of the base band
    - ble-host and ble-profiles might be compatible with the one directly from Packetcraft
    - Developing their own low-power enhancements
    - BLE 5.0
+31 −0
Original line number Diff line number Diff line
.. _bluetooth_ecg_service:

Bluetooth ECG Service
========================

.. warning::
    The service is still work in progress and subject to change

The ECG service provides access to the ECG sensor of the card10

BLE Service
-----------

The current draft uses following service specification:

- Service:

  UUID: ``42230300-2342-2342-2342-234223422342``

- ECG samples characteristic:

  UUID: ``42230301-2342-2342-2342-234223422342``
  notify

ECG samples characteristic
--------------------------

List of 16 bit samples (big endian). Enable notifications to
receive a stream of samples while the ECG app is open.

The first 16 bit are a sample counter (big endian).
+99 −0
Original line number Diff line number Diff line
.. _ESS:

Environmental Sensing Service
=============================

The Environmental Sensing Service (ESS) implements access to
the BME680 environmental sensor of the card10.

It provides:

- Temperature
- Relative humidity
- Pressure

If :ref:`bsec_api` is enabled the following additional estimates are available:

 - Indoor air quality (IAQ estimate
 - Equivalent CO2 (eCO2) estimate

Please refer to :py:mod:`bme680` for more information about BSEC.


If notifcations are enabled a measurement of all values is performed every 3 seconds. For each measurement a notification is sent for the characteristics which have notifications enabled.


A measurement can also be triggered by reading from a characteristic. A measurement takes roughly 200 ms. A notifciation will be sent to all characteristics which have notifications enabled except the one which was used to trigger the measurement.

.. note::
   If :ref:`bsec_api` is enabled, reading a characteristic will not trigger a new measurement.

.. note::
    This service will be available in version v1.17.


BLE Service
-----------

- Service

  UUID: ``181A``

- Temperature characteristic:

  UUID: ``2A6E``
  read and notify

- Humidity characteristic:

  UUID: ``2A6F``
  read and notify

- Pressure characteristic:

  UUID: ``2A6D``
  read and notify

- Indoor air quality (IAQ) characteristic:

  UUID: ``422302f1-2342-2342-2342-234223422342``
  read and notify

Temperature characteristic
--------------------------

- 16 bit little endian value representing the measured temperature.

- Unit: 0.01 deg C


Humidity characteristic
-----------------------

- 16 bit little endian value representing the measured relative humidity.

- Unit: 0.01%

Pressure characteristic
-----------------------

- 32 bit little endian value representing the measured pressure.

- Unit: 0.1 Pa (0.001 hPa)

Indoor air quality (IAQ) characteristic
---------------------------------------

Data format:

======== =========================== ===========================
Byte 0   Bytes 1-2                   Bytes 3-4
-------- --------------------------- ---------------------------
Accuracy IAQ (16-bit little endian)  eCO2 (16-bit little endian)
======== =========================== ===========================


Units:

- Accuracy and IAQ units: See :ref:`bsec_api` API description
- CO2 unit: [ppm]
Original line number Diff line number Diff line
.. _bluetooth_file_transfer:

Bluetooth File Transfer
=======================

@@ -77,9 +79,11 @@ CHUNK_ACK:
===== ===
  0   1-4
----- ---
  C   CRC
  C   CRC(*)
===== ===

CRC32 of the whole CHUNK packet including first byte, offset and payload.

FINISH:

=== ===
+115 −0
Original line number Diff line number Diff line
NimBLE
======

On the card10 the ARM Cordio-B50 stack is used, which is in a very early experimental state and has some incompatibilities with some smartphones.
Therefore some alternative stacks are evaluated, which meight be used as a replacement in the long term.


Here a stack called NimBLE is presented, which claims to be feature complete. Originally it has been developed for Mynewt, an open source embedded operating system by Apache (https://mynewt.apache.org/).


There is a working port for the ESP32 espressif ESP-IDF framework.
Like Epicardium, ESP-IDF is based on FreeRTOS. Therefore it can be used for evaluation purposes.

Getting NimBLE run on the ESP32
-------------------------------

Install required packages:

Ubuntu:

.. code-block:: shell-session

  sudo apt install git virtualenv python2.7 cmake

Arch:

.. code-block:: shell-session

  sudo pacman -S git python2 python2-virtualenv cmake

Download and extract xtensa ESP32 compiler:

.. code-block:: shell-session

  wget https://dl.espressif.com/dl/xtensa-esp32-elf-gcc8_2_0-esp32-2018r1-linux-amd64.tar.xz
  tar -xf xtensa-esp32-elf-gcc8_2_0-esp32-2018r1-linux-amd64.tar.xz


Clone esp-idf:

.. code-block:: shell-session

    git clone https://github.com/espressif/esp-idf.git

Add xtensa and ESP-IDF path to $PATH:

bash shell:

.. code-block:: shell-session

  export IDF_PATH=$PWD/esp-idf
  export PATH=${PATH}:$PWD/xtensa-esp32-elf/bin:$PWD/esp-idf/tools

fish shell:

.. code-block:: shell-session

  set -gx IDF_PATH $PWD/esp-idf
  set -gx PATH $PWD/xtensa-esp32-elf/bin/ $PWD/esp-idf/tools $PATH

Create a python2.7 virtualenv:

.. code-block:: shell-session

  cd esp-idf
  virtualenv -p /usr/bin/python2.7 venv

Enter the virtualenv:

bash shell:

.. code-block:: shell-session

  . venv/bin/activate

fish shell:

.. code-block:: shell-session

  . venv/bin/activate.fish

Init git submodules and install all required Python packages:

.. code-block:: shell-session

  git submodule update --init --recursive
  pip install -r requirements.txt


Now you are ready to build!

The following steps assume that your ESP32 is connected via USB and
is accessible via /dev/ttyUSB0. This meight be different on your system.

There are a few NimbLE examples which can be used for playing around:

Build a BLE server example (host mode):
---------------------------------------
.. code-block:: shell-session

  cd examples/bluetooth/nimble/bleprph
  idf.py -p /dev/ttyUSB0 flash monitor

This will build and flash the example to the ESP32 and instantly listens on /dev/ttyUSB0 serial port.
After the flashing process the ESP32 will anounce itself as **nimble-bleprph** device via BLE.

Build a BLE client example (central mode):
------------------------------------------
.. code-block:: shell-session

  cd examples/bluetooth/nimble/blecent
  idf.py -p /dev/ttyUSB0 flash monitor

This will build and flash the example to the ESP32 and instantly listens on /dev/ttyUSB0 serial port.
After the flashing process the ESP32 creates a GATT client and performs passive scan, it then connects to peripheral device if the device advertises connectability and the device advertises support for the Alert Notification service (0x1811) as primary service UUID.
+43 −0
Original line number Diff line number Diff line
.. _bluetooth_overview:

Overview
========

UUIDs
-----
Bluetooth uses UUIDs to identify almost everything. The Bluetooth SIG specifies
a number of "short" 16-bit UUIDs for general use. A device is free to define
other "long" 128-bit UUIDs if it wants to express non-standard functionality.

The card10 defines a few non-standard UUIDs. They are prefixed with ``4232``:


+--------------------------------------+-------------------------------------------------------+--------------------------------------------------------------------+
| UUID                                 | Usage                                                 | Comment                                                            |
+======================================+=======================================================+====================================================================+
| 422301XX-2342-2342-2342-234223422342 | :ref:`File Transfer Service<bluetooth_file_transfer>` | Used by the Companion App to install apps on the card10            |
+--------------------------------------+-------------------------------------------------------+--------------------------------------------------------------------+
| 422302XX-2342-2342-2342-234223422342 | :ref:`card10 Service<bluetooth_card10_service>`       | Controls general card10 functionality like LEDs and personal state |
+--------------------------------------+-------------------------------------------------------+--------------------------------------------------------------------+
| 422303XX-2342-2342-2342-234223422342 | :ref:`ECG Service<bluetooth_ecg_service>`             | Allows to stream ECG measurements via BLE                          |
+--------------------------------------+-------------------------------------------------------+--------------------------------------------------------------------+
| 422380XX-2342-2342-2342-234223422342 | Experimental usage                                    | To be used for development and experiments                         |
+--------------------------------------+-------------------------------------------------------+--------------------------------------------------------------------+

The first byte after the prefix identifies the service (e.g. ``03`` for ECG).
The next byte is reserved for UUIDs used by the service itself. The service
uses ``00`` at this position. The first attribute inside the service uses
``01`` at this position. See the documentation of any service for an example.

You can use the UUID range 422380XX-2342-2342-2342-234223422342 for your own
experiments / demos/ development. For example the demos under `demos/` in the
repository use this range of UUIDs.

BLE with MicroPython
--------------------

MicroPython docs: https://docs.micropython.org/en/latest/library/bluetooth.html

Basic example: https://git.card10.badge.events.ccc.de/card10/firmware/-/blob/master/demos/ble-ws2812-card10.py and https://git.card10.badge.events.ccc.de/card10/firmware/-/blob/master/demos/ble-ws2812-host.py

MicroPython examples: https://github.com/micropython/micropython/tree/7c54b6428058a236b8a48c93c255948ece7e718b/examples/bluetooth
Original line number Diff line number Diff line
@@ -4,3 +4,7 @@ set -e
cd "$(dirname "$0")"

sphinx-build -b html . ./output

if [ "$1" = "--open" ]; then
   xdg-open ./output/index.html
fi
+69 −0
Original line number Diff line number Diff line
.. _card10_cfg:

card10.cfg
==========

Certain high-level settings can be configured using a filed named ``card10.cfg``.  It is accessed from the :ref:`usb_file_transfer` of the bootloader.  Once you are in this mode and have mounted the badge's flash device, you can either create or update a file named ``card10.cfg``.

The file is in the well-known INI-style format, with one setting per line. For instance, if there were an option called ``answer_to_life``, you could set it by writing the following line in the ``card10.cfg`` file:

.. code-block:: text

   answer_to_life = 42

Don't forget to unmount the filesystem before rebooting your badge after changing any setting.

Syntax and Types
----------------

Lines that start with a ``#`` character are ignored.

Any other line will have the overall syntax of ``option_name = option_value``, with spaces around the ``=`` character optional.

Option names are internal to card10 and described below. Each option has a defined type.

========= ===========
Type name Description
========= ===========
Boolean   A true/false value. ``1`` or ``true`` is true, ``0`` or ``false`` is false. Example: ``foo = true``.
String    An unquoted string value of maximum 20 bytes. Values longer than 20 bytes are trimmed. Example: ``foo = bar``.
Integer   A signed 32-bit integer in base 10. Example: ``foo = 42`` or ``bar = -1337``.
Float     A single-precision (32-bit) floating-point number in base 10. Example: ``foo = 13.37``.
========= ===========

Supported options
-----------------

================== ========== ===========
Option name        Type       Description
================== ========== ===========
``execute_elf``    Boolean    Allow running of binary :ref:`l0dables`. These files can be nefarious, so this option is off by default.
------------------ ---------- -----------
``timezone``       String     Timezone for card10; must be of format ``[+-]HHMM``.  Examples: ``+0800``, ``-0200``
------------------ ---------- -----------
``default_app``    String     Full path to the exectutable file of the default application. If this option is not set,``apps/analog_clock/__init__.py`` is used.
------------------ ---------- -----------
``ble_enable``     Boolean    Activate the BLE interface. Turn off for more privacy or to conserve energy.
------------------ ---------- -----------
``ble_mac``        Boolean    MAC address used for BLE. Format: ``ca:4d:10:xx:xx:xx``.
------------------ ---------- -----------
``ble_hid_enable`` Boolean    Enable the Human Interface Device (HID) characteristics on BLE.
------------------ ---------- -----------
``ble_log_enable`` Boolean    Activate HCI level logging of BLE data. Creates a new btsnoop compatible log file named ``ble.log`` in the ``logs`` folder after each boot if BLE is activated. Keeps the last 10 files.
------------------ ---------- -----------
``right_scroll``   Boolean    Use both right buttons to scroll up and down. Lower left button is SELECT.
------------------ ---------- -----------
``long_press_ms``  Integer    Defines the timespan for a long key press in milliseconds.
------------------ ---------- -----------
``retrigger_ms``   Integer    Defines the timespan for repeating key presses when a key is hold in milliseconds.
------------------ ---------- -----------
``bsec_enable``    Boolean    Activate the Bosch :ref:`bsec_api` binary blob to compute an Indoor Air Quality indication.
------------------ ---------- -----------
``bsec_debug``     Boolean    Turn on debug output of the BSEC system. Prints each meaurement on the console.
------------------ ---------- -----------
``bsec_offset``    Integer    Temperature offset in .1 K. Example: Set to `-14` if temperature reads 1.4 "C" to high. Default: -2.2 K (appropriate for a card10 without a case, connected to USB and with BLE active in vertical orientation).
------------------ ---------- -----------
``battery_check``  Boolean    Whether the low battery check should be enabled (default ``true``).  **Warning**: Do not use this unless you know what you're doing.  This option is only meant to be used on devices with a broken PMIC ADMUX connection.
------------------ ---------- -----------
``has_flashlight`` Boolean    Whether the flashlight LED was soldered onto the harmonic board.  Can be used by apps to optionally enable flashlight features.
================== ========== ===========
Original line number Diff line number Diff line
@@ -3,6 +3,8 @@ import subprocess
import sys
import time
import sphinx.util.logging
from docutils import nodes
from docutils.parsers import rst

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
@@ -13,7 +15,6 @@ sys.path.insert(0, os.path.abspath("./"))

logger = sphinx.util.logging.getLogger("card10/conf.py")


# -- Project information -----------------------------------------------------

project = "card10-firmware"
@@ -47,6 +48,23 @@ todo_include_todos = True
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ["output", "Thumbs.db", ".DS_Store", "hawkmoth"]

# -- Extensions -------------------------------------------------------------- {{{


class ColorExample(rst.Directive):
    has_content = False
    required_arguments = 1
    optional_arguments = 0
    option_spec = {}

    def run(self):
        color = self.arguments[0]
        html_text = '<div style="width: 30px;height: 30px;background: {};border: black 1px solid;border-radius: 15px;"></div>'
        return [nodes.raw("", html_text.format(color), format="html")]


# }}}

# -- Options for HTML output ------------------------------------------------- {{{

# The Read the Docs theme is available from
@@ -87,7 +105,23 @@ html_context = {
# }}}

# -- Options for Auto-Doc ---------------------------------------------------- {{{
autodoc_mock_imports = ["sys_display", "sys_leds", "ucollections", "urandom", "utime"]
autodoc_mock_imports = [
    "buttons",
    "interrupt",
    "sys_ble_hid",
    "sys_bme680",
    "sys_bhi160",
    "sys_display",
    "sys_leds",
    "sys_max30001",
    "sys_max86150",
    "sys_png",
    "sys_config",
    "ucollections",
    "uerrno",
    "urandom",
    "utime",
]

autodoc_member_order = "bysource"
# }}}
@@ -114,3 +148,36 @@ except ImportError as e:
# -- Sphinx Setup ------------------------------------------------------------
def setup(app):
    app.add_config_value("has_hawkmoth", has_hawkmoth, "")
    app.add_directive("color-example", ColorExample)
    fix_issue_8945()


def fix_issue_8945():
    c_domain = __import__("sphinx.domains.c").domains.c

    for kw in [
        "char",
        "float",
        "int",
        "long",
        "short",
        "void",
        "_Bool",
        "bool",
        "_Complex",
        "complex",
    ]:
        if kw in c_domain._keywords:
            c_domain._keywords.remove(kw)
        if hasattr(c_domain, "_macroKeywords") and kw in c_domain._macroKeywords:
            c_domain._macroKeywords.remove(kw)

    def parse_xref_object(self):
        name = self._parse_nested_name()
        self.skip_ws()
        self.skip_string("()")
        # Removing this line as a hacky workaround:
        # self.assert_end()
        return name

    c_domain.DefinitionParser.parse_xref_object = parse_xref_object
Original line number Diff line number Diff line
@@ -115,6 +115,12 @@ command to soft-reset card10.
   ...
   (gdb)

.. note::
   You will also find the following self-describing gdb files in the firmware
   root directory, which do not require additional arguments:
   ``flash-all.gdb,  flash-bootloader.gdb,
   flash-both.gdb,  flash-epicardium.gdb,  flash-pycardium.gdb``

.. warning::
   If you are used to use ``mon reset halt``, be aware that the card10 prototypes
   do not connect the reset line to the debugger. OpenOCD is configured to only do
+8 −0
Original line number Diff line number Diff line
.. _epicardium_internal_apis:

Epicardium Internal APIs
========================

Core OS APIs
------------
.. c:autodoc:: epicardium/os/core.h
+136 −0
Original line number Diff line number Diff line
Mutex
=====
Ontop of FreeRTOS, we have our own mutex implementation.  **Never use the
FreeRTOS mutexes directly!  Always use this abstraction layer instead**.  This
mutex implementation tries to make reasoning about program flow and locking
behavior easier.  And most importantly tries to help with debugging possible
dead-locks.

Design
------
There are a few guiding design principles:

- Mutexes can only be used from tasks, **never** from interrupts!
- Timers can use mutexes, but only with :c:func:`mutex_trylock`, **never** with
  :c:func:`mutex_lock` (Because they are not allowed to block).
- Locking can *never* fail (if it does, we consider this a fatal error ⇒ panic).
- No recursive locking.
- An unlock can only occur from the task which previously acquired the mutex.
- An unlock is only allowed if the mutex was previously acquired.

For a more elaborate explanation of the rationale behind these rules take a
look at the :ref:`mutex-design-reasons`.

Definitions
-----------
.. c:autodoc:: epicardium/os/mutex.h

.. _mutex-design-reasons:

Reasons for this Design
-----------------------

Locking can *never* fail
^^^^^^^^^^^^^^^^^^^^^^^^
This might seem like a bold claim at first but in the end, it is just a matter
of definition and shifting responsibilities.  Instead of requiring all code to
be robust against a locking attempt failing, we require all code to properly
lock and unlock their mutexes and thus never producing a situation where
locking would fail.

Because all code using any of the mutexes is contained in the Epicardium
code-base, we can - *hopefully* - audit it properly behaving ahead of time and
thus don't need to add code to ensure correctness at runtime.  This makes
downstream code easier to read and easier to reason about.

History of this project has shown that most code does not properly deal with
locking failures anyway: There was code simply skipping the mutexed action on
failure, code blocking a module entirely until reboot, and worst of all: Code
exposing the locking failure to 'user-space' (Pycardium) instead of retrying.
This has lead to spurious errors where technically there would not need to be
any.

Only from tasks
^^^^^^^^^^^^^^^
Locking a mutex from an ISR, a FreeRTOS software timer or any other context
which does not allow blocking is complicated to do right.  The biggest
difficulty is that a task might be holding the mutex during execution of such a
context and there is no way to wait for it to release the mutex.  This requires
careful design of the program flow to choose an alternative option in such a
case.  A common approach is to 'outsource' the relevant parts of the code into
an 'IRQ worker' which is essentially just a task waiting for the IRQ to wake it
up and then attempts to lock the mutex.

If you absolutely do need it (and for legacy reasons), software timers *can*
lock a mutex using :c:func:`mutex_trylock` (which never blocks).  I strongly
recommend **not** doing that, though.  As shown above, you will have to deal
with the case of the mutex being held by another task and it is very well
possible that your timer will get starved of the mutex because the scheduler
has no knowledge of its intentions.  In most cases, it is a better idea to use
a task and attempt locking using :c:func:`mutex_lock`.

.. todo::

   We might introduce a generic IRQ worker queue system at some point.

No recursive locking
^^^^^^^^^^^^^^^^^^^^
Recursive locking refers to the ability to 'reacquire' a mutex already held by
the current task, deeper down in the call-chain.  Only the outermost unlock
will actually release the mutex.  This feature is sometimes implemented to
allow more elegant abstractions where downstream code does not need to know
about the mutexes upstream code uses and can still also create a larger region
where the same mutex is held.

But exactly by hiding the locking done by a function, these abstractions make
it hard to trace locking chains and in some cases even make it impossible to
create provably correct behavior.  As an alternative, I would suggest using
different mutexes for the different levels of abstraction.  This also helps
keeping each mutex separated and 'local' to its purpose.

Only unlock from the acquiring task
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Because of the above mentioned mutex locking semantics, there should never be a
need to force-unlock a forgein mutex.  Even in cases of failures, all code
should still properly release all mutexes it holds.  One notable exceptions is
``panic()``\s which will abort all ongoing operations anyway.

Only unlock once after acquisition
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Justified with an argument of robustness, sometimes the :c:func:`mutex_unlock`
call is written in a way that allows unlocking an already unlocked mutex.  But
robustness of downstream code will not really be improved by the upstream API
dealing with arguably invalid usage.  For example, this could encourage
practices like unlocking everything again at the end of a function "just to be
sure".

Instead, code should be written in a way where the lock/unlock pair is
immediately recognizable as belonging together and is thus easily auditable to
have correct locking behavior.  A common pattern to help with readability in
this regard is the *Single Function Exit* which looks like this:

.. code-block:: cpp

   int function()
   {
           int ret;
           mutex_lock(&some_mutex);

           ret = foo();
           if (ret) {
                   /* Return with an error code */
                   ret = -ENODEV;
                   goto out_unlock;
           }

           ret = bar();
           if (ret) {
                   /* Return the return value from foo */
                   goto out_unlock;
           }

           ret = 0;
   out_unlock:
           mutex_unlock(&some_mutex);
           return ret;
   }
+118 −0
Original line number Diff line number Diff line
Hawkmoth - Sphinx Autodoc for C
===============================

Hawkmoth is a minimalistic Sphinx_ `C Domain`_ autodoc directive extension to
incorporate formatted C source code comments written in reStructuredText_ into
Sphinx based documentation. It uses Clang Python Bindings for parsing, and
generates C Domain directives for C API documentation, and more. In short,
Hawkmoth is Sphinx Autodoc for C.

Hawkmoth aims to be a compelling alternative for documenting C projects using
Sphinx, mainly through its simplicity of design, implementation and use.

.. _Sphinx: http://www.sphinx-doc.org

.. _C Domain: http://www.sphinx-doc.org/en/stable/domains.html

.. _reStructuredText: http://docutils.sourceforge.net/rst.html

Example
-------

Given C source code with rather familiar looking documentation comments::

  /**
   * Get foo out of bar.
   */
  void foobar();

and a directive in the Sphinx project::

  .. c:autodoc:: filename.c

you can incorporate code documentation into Sphinx. It's as simple as that.

You can document functions, their parameters and return values, structs, unions,
their members, macros, function-like macros, enums, enumeration constants,
typedefs, variables, as well as have generic documentation comments not attached
to any symbols.

Documentation
-------------

Documentation on how to install and configure Hawkmoth, and write documentation
comments, with examples, is available in the ``doc`` directory in the source
tree, obviously in Sphinx format and using the directive extension. Pre-built
documentation `showcasing what Hawkmoth can do`_ is available at `Read the
Docs`_.

.. _showcasing what Hawkmoth can do: https://hawkmoth.readthedocs.io/en/latest/examples.html

.. _Read the Docs: https://hawkmoth.readthedocs.io/

Installation
------------

You can install Hawkmoth from PyPI_ with::

  pip install hawkmoth

You'll additionally need to install Clang and Python 3 bindings for it through
your distro's package manager; they are not available via PyPI. For further
details, see the documentation.

Alternatively, installation packages are available for:

* `Arch Linux`_

In Sphinx ``conf.py``, add ``hawkmoth`` to ``extensions``, and point
``cautodoc_root`` at the source tree. See the extension documentation for
details.

.. _PyPI: https://pypi.org/project/hawkmoth/

.. _Arch Linux: https://aur.archlinux.org/packages/?K=hawkmoth

Development and Contributing
----------------------------

Hawkmoth source code is available on GitHub_. The development version can be
checked out via ``git`` using this command::

  git clone https://github.com/jnikula/hawkmoth.git

Please file bugs and feature requests as GitHub issues. Contributions are
welcome both as emailed patches to the mailing list and as pull requests.

.. _GitHub: https://github.com/jnikula/hawkmoth

Dependencies
------------

- Python 3.4
- Sphinx 3
- Clang 6.0
- Python 3 Bindings for Clang 6.0
- sphinx-testing 1.0.0 (for development)

These are the versions Hawkmoth is currently being developed and tested
against. Other versions might work, but no guarantees.

License
-------

Hawkmoth is free software, released under the `2-Clause BSD License`_.

.. _2-Clause BSD License: https://opensource.org/licenses/BSD-2-Clause

Contact
-------

IRC channel ``#hawkmoth`` on freenode_.

Mailing list hawkmoth@freelists.org. Subscription information at the `list home
page`_.

.. _freenode: https://freenode.net/

.. _list home page: https://www.freelists.org/list/hawkmoth

Documentation/hawkmoth/README.txt

deleted100644 → 0
+0 −121
Original line number Diff line number Diff line
Hawkmoth - Sphinx Autodoc for C
===============================

Hawkmoth is a minimalistic Sphinx_ `C Domain`_ autodoc directive extension to
incorporate formatted C source code comments written in reStructuredText_ into
Sphinx based documentation. It uses Clang Python Bindings for parsing, and
generates C Domain directives for C API documentation, and more. In short,
Hawkmoth is Sphinx Autodoc for C.

Hawkmoth aims to be a compelling alternative for documenting C projects using
Sphinx, mainly through its simplicity of design, implementation and use.

.. _Sphinx: http://www.sphinx-doc.org

.. _C Domain: http://www.sphinx-doc.org/en/stable/domains.html

.. _reStructuredText: http://docutils.sourceforge.net/rst.html

Example
-------

Given C source code with rather familiar looking documentation comments::

  /**
   * Get foo out of bar.
   */
  void foobar();

and a directive in the Sphinx project::

  .. c:autodoc:: filename.c

you can incorporate code documentation into Sphinx. It's as simple as that.

You can document functions, their parameters and return values, structs, unions,
their members, macros, function-like macros, enums, enumeration constants,
typedefs, variables, as well as have generic documentation comments not attached
to any symbols.

Documentation
-------------

Documentation on how to configure Hawkmoth and write documentation comments,
with examples, is available in the ``doc`` directory in the source tree,
obviously in Sphinx format and using the directive extension. Pre-built
documentation `showcasing what Hawkmoth can do`_ is available at `Read the
Docs`_.

.. _showcasing what Hawkmoth can do: https://hawkmoth.readthedocs.io/en/latest/examples.html

.. _Read the Docs: https://hawkmoth.readthedocs.io/

Installation
------------

You can install Hawkmoth from PyPI_ with::

  pip install hawkmoth

You'll additionally need to install Clang and Python 3 bindings for it through
your distro's package manager; they are not available via PyPI. You may also
need to set ``LD_LIBRARY_PATH`` so that the Clang library can be found. For
example::

  export LD_LIBRARY_PATH=$(llvm-config --libdir)

Alternatively, installation packages are available for:

* `Arch Linux`_

In Sphinx ``conf.py``, add ``hawkmoth`` to ``extensions``, and point
``cautodoc_root`` at the source tree. See the extension documentation for
details.

.. _PyPI: https://pypi.org/project/hawkmoth/

.. _Arch Linux: https://aur.archlinux.org/packages/?K=hawkmoth

Development and Contributing
----------------------------

Hawkmoth source code is available on GitHub_. The development version can be
checked out via ``git`` using this command::

  git clone https://github.com/jnikula/hawkmoth.git

Please file bugs and feature requests as GitHub issues. Contributions are
welcome both as emailed patches to the mailing list and as pull requests.

.. _GitHub: https://github.com/jnikula/hawkmoth

Dependencies
------------

- Python 3.4
- Sphinx 1.8
- Clang 6.0
- Python 3 Bindings for Clang 6.0
- sphinx-testing 1.0.0 (for development)

These are the versions Hawkmoth is currently being developed and tested
against. Other versions might work, but no guarantees.

License
-------

Hawkmoth is free software, released under the `2-Clause BSD License`_.

.. _2-Clause BSD License: https://opensource.org/licenses/BSD-2-Clause

Contact
-------

IRC channel ``#hawkmoth`` on freenode_.

Mailing list hawkmoth@freelists.org. Subscription information at the `list home
page`_.

.. _freenode: https://freenode.net/

.. _list home page: https://www.freelists.org/list/hawkmoth
Original line number Diff line number Diff line
@@ -52,7 +52,10 @@ class CAutoDocDirective(Directive):
        env = self.state.document.settings.env

        for (severity, filename, lineno, msg) in errors:
            if filename:
                toprint = '{}:{}: {}'.format(filename, lineno, msg)
            else:
                toprint = '{}'.format(msg)

            if severity.value <= env.app.verbosity:
                self.logger.log(self._log_lvl[severity], toprint,
@@ -109,7 +112,7 @@ class CAutoDocDirective(Directive):
        return node.children

def setup(app):
    app.require_sphinx('1.8')
    app.require_sphinx('3.0')
    app.add_config_value('cautodoc_root', app.confdir, 'env')
    app.add_config_value('cautodoc_compat', None, 'env')
    app.add_config_value('cautodoc_clang', None, 'env')
Original line number Diff line number Diff line
@@ -39,7 +39,10 @@ def main():
        print(doc)

    for (severity, filename, lineno, msg) in errors:
        if filename:
            print('{}: {}:{}: {}'.format(severity.name,
                                         filename, lineno, msg), file=sys.stderr)
        else:
            print('{}: {}'.format(severity.name, msg), file=sys.stderr)

main()
Original line number Diff line number Diff line
# Copyright (c) 2016-2017 Jani Nikula <jani@nikula.org>
# Copyright (c) 2018-2019 Bruno Santos <brunomanuelsantos@tecnico.ulisboa.pt>
# Copyright (c) 2018-2020 Bruno Santos <brunomanuelsantos@tecnico.ulisboa.pt>
# Licensed under the terms of BSD 2-Clause, see LICENSE for details.
"""
Documentation comment extractor
@@ -34,7 +34,7 @@ Otherwise, documentation comments are passed through verbatim.
"""

import enum
import itertools
import re
import sys

from clang.cindex import CursorKind, TypeKind
@@ -75,18 +75,22 @@ def comment_extract(tu):
            current_comment = token
            continue

        # Store off the token's cursor for a slight performance improvement
        # instead of accessing the `cursor` property multiple times.
        token_cursor = token.cursor

        # cursors that are 1) never documented themselves, and 2) allowed
        # between comment and the actual cursor being documented
        if (token.cursor.kind == CursorKind.INVALID_FILE or
            token.cursor.kind == CursorKind.TYPE_REF or
            token.cursor.kind == CursorKind.PREPROCESSING_DIRECTIVE or
            token.cursor.kind == CursorKind.MACRO_INSTANTIATION):
        if (token_cursor.kind == CursorKind.INVALID_FILE or
            token_cursor.kind == CursorKind.TYPE_REF or
            token_cursor.kind == CursorKind.PREPROCESSING_DIRECTIVE or
            token_cursor.kind == CursorKind.MACRO_INSTANTIATION):
            continue

        if cursor is not None and token.cursor == cursor:
        if cursor is not None and token_cursor == cursor:
            continue

        cursor = token.cursor
        cursor = token_cursor

        # Note: current_comment may be None
        if current_comment != None and docstr.is_doc(current_comment.spelling):
@@ -125,16 +129,18 @@ def _get_macro_args(cursor):
    if cursor.kind != CursorKind.MACRO_DEFINITION:
        return None

    tokens = cursor.get_tokens()

    # Use the first two tokens to make sure this starts with 'IDENTIFIER('
    x = [token for token in itertools.islice(cursor.get_tokens(), 2)]
    if (len(x) != 2 or x[0].spelling != cursor.spelling or
        x[1].spelling != '(' or x[0].extent.end != x[1].extent.start):
    one = next(tokens)
    two = next(tokens, None)
    if two is None or one.extent.end != two.extent.start or two.spelling != '(':
        return None

    # Naïve parsing of macro arguments
    # FIXME: This doesn't handle GCC named vararg extension FOO(vararg...)
    args = []
    for token in itertools.islice(cursor.get_tokens(), 2, None):
    for token in tokens:
        if token.spelling == ')':
            return args
        elif token.spelling == ',':
@@ -161,8 +167,26 @@ def _recursive_parse(comments, cursor, nest, compat):
        return _result(comment, cursor=cursor, fmt=fmt,
                       nest=nest, name=name, args=args, compat=compat)

    elif cursor.kind == CursorKind.VAR_DECL:
    elif cursor.kind in [CursorKind.VAR_DECL, CursorKind.FIELD_DECL]:
        if cursor.kind == CursorKind.VAR_DECL:
            fmt = docstr.Type.VAR
        else:
            fmt = docstr.Type.MEMBER

        # If this is an array, the dimensions should be applied to the name, not
        # the type.
        dims = ttype.rsplit(' ', 1)[-1]
        if dims.startswith('[') and dims.endswith(']'):
            ttype = ttype.rsplit(' ', 1)[0]
            name = name + dims

        # If this is a function pointer, or an array of function pointers, the
        # name should be within the parenthesis as in (*name) or (*name[N]).
        fptr_type = re.sub(r'\((\*+)(\[[^]]*\])?\)', r'(\1{}\2)'.format(name),
                           ttype, count=1)
        if fptr_type != ttype:
            name = fptr_type
            ttype = ''

        return _result(comment, cursor=cursor, fmt=fmt,
                       nest=nest, name=name, ttype=ttype, compat=compat)
@@ -188,9 +212,15 @@ def _recursive_parse(comments, cursor, nest, compat):

        # FIXME: Handle anonymous enumerators.

        fmt = docstr.Type.TYPE
        fmts = {CursorKind.STRUCT_DECL: docstr.Type.STRUCT,
                CursorKind.UNION_DECL: docstr.Type.UNION,
                CursorKind.ENUM_DECL: docstr.Type.ENUM}

        fmt = fmts[cursor.kind]

        # name may be empty for typedefs
        result = _result(comment, cursor=cursor, fmt=fmt,
                         nest=nest, name=ttype, compat=compat)
                         nest=nest, name=name if name else ttype, compat=compat)

        nest += 1
        for c in cursor.get_children():
@@ -205,12 +235,6 @@ def _recursive_parse(comments, cursor, nest, compat):
        return _result(comment, cursor=cursor, fmt=fmt,
                       nest=nest, name=name, compat=compat)

    elif cursor.kind == CursorKind.FIELD_DECL:
        fmt = docstr.Type.MEMBER

        return _result(comment, cursor=cursor, fmt=fmt,
                       nest=nest, name=name, ttype=ttype, compat=compat)

    elif cursor.kind == CursorKind.FUNCTION_DECL:
        # FIXME: check args against comment
        # FIXME: children may contain extra stuff if the return type is a
@@ -261,7 +285,8 @@ def clang_diagnostics(errors, diagnostics):
           4: ErrorLevel.ERROR}

    for diag in diagnostics:
        errors.extend([(sev[diag.severity], diag.location.file.name,
        filename = diag.location.file.name if diag.location.file else None
        errors.extend([(sev[diag.severity], filename,
                        diag.location.line, diag.spelling)])

# return a list of (comment, metadata) tuples
Original line number Diff line number Diff line
# Copyright (c) 2016-2017 Jani Nikula <jani@nikula.org>
# Copyright (c) 2018-2019 Bruno Santos <brunomanuelsantos@tecnico.ulisboa.pt>
# Copyright (c) 2016-2020 Jani Nikula <jani@nikula.org>
# Copyright (c) 2018-2020 Bruno Santos <brunomanuelsantos@tecnico.ulisboa.pt>
# Licensed under the terms of BSD 2-Clause, see LICENSE for details.
"""
Documentation strings manipulation library
@@ -17,6 +17,9 @@ class Type(Enum):
    TEXT = auto()
    VAR = auto()
    TYPE = auto()
    STRUCT = auto()
    UNION = auto()
    ENUM = auto()
    ENUM_VAL = auto()
    MEMBER = auto()
    MACRO = auto()
@@ -31,10 +34,13 @@ _doc_fmt = {
    Type.TEXT:       (0, '\n{text}\n'),
    Type.VAR:        (1, '\n.. c:var:: {ttype} {name}\n\n{text}\n'),
    Type.TYPE:       (1, '\n.. c:type:: {name}\n\n{text}\n'),
    Type.ENUM_VAL:   (1, '\n.. c:macro:: {name}\n\n{text}\n'),
    Type.STRUCT:     (1, '\n.. c:struct:: {name}\n\n{text}\n'),
    Type.UNION:      (1, '\n.. c:union:: {name}\n\n{text}\n'),
    Type.ENUM:       (1, '\n.. c:enum:: {name}\n\n{text}\n'),
    Type.ENUM_VAL:   (1, '\n.. c:enumerator:: {name}\n\n{text}\n'),
    Type.MEMBER:     (1, '\n.. c:member:: {ttype} {name}\n\n{text}\n'),
    Type.MACRO:      (1, '\n.. c:macro:: {name}\n\n{text}\n'),
    Type.MACRO_FUNC: (1, '\n.. c:function:: {name}({args})\n\n{text}\n'),
    Type.MACRO_FUNC: (1, '\n.. c:macro:: {name}({args})\n\n{text}\n'),
    Type.FUNC:       (1, '\n.. c:function:: {ttype} {name}({args})\n\n{text}\n')
}

Original line number Diff line number Diff line
# Copyright (c) 2021, Jani Nikula <jani@nikula.org>
# Licensed under the terms of BSD 2-Clause, see LICENSE for details.
"""
Read the Docs Helpers
=====================

Helpers for setting up and using Hawkmoth on Read the Docs.
"""

import os
import subprocess

def clang_setup(force=False):
    """Try to find and configure libclang location on RTD."""
    if 'READTHEDOCS' in os.environ or force:
        try:
            result = subprocess.run(['llvm-config', '--libdir'],
                                    check=True, capture_output=True, encoding='utf-8')
            libdir = result.stdout.strip()

            # For some reason there is no plain libclang.so symlink on RTD.
            from clang.cindex import Config
            Config.set_library_file(os.path.join(libdir, 'libclang.so.1'))
        except Exception as e:
            print(e)
Original line number Diff line number Diff line
.. _how_to_build:

How To Build
============
If you just want to write MicroPython code for card10, you probably **won't**
@@ -29,8 +31,9 @@ Dependencies

  - macOS (Note: The card10 firmware team used Linux so far. macOS recommendations here are experimental.)

    You can use `Homebrew`_ to install the required tools.
    The version of the Arm crosscompiler tool chain is quite important; with the wrong version, e.g. strip and/or ld might throw strange errors.
    You can use `Homebrew`_ to install the required tools.  The version of the
    ARM crosscompiler tool chain is quite important; with the wrong version,
    e.g. strip and/or ld might throw strange errors.

    .. code-block:: shell-session

@@ -38,9 +41,11 @@ Dependencies
        brew install px4/px4/gcc-arm-none-eabi-63
        brew install coreutils

  - Alternative: Download `ARM's GNU toolchain`_.  **TODO**
    .. _Homebrew: https://brew.sh/

  - Alternative: Download `ARM's GNU toolchain`_.  **TODO**


* **python3**:  For meson and various scripts needed for building.
* **meson** (>0.43.0) & **ninja**:  Unfortunately most distros only have very old versions
  of meson in their repositories.  Instead, you'll probably save yourself a lot
@@ -66,14 +71,42 @@ Dependencies
        brew install ninja
        pip3 install --user meson  # see https://mesonbuild.com/Getting-meson.html - you will have to add ~/.local/bin to your PATH.

* **python3-crc16**: Install with ``pip3 install --user crc16``.
* **python3-pillow**: Python Image Library ``pip3 install --user pillow``.
* One of two CRC packages is required. Pick one:

  - Ubuntu / Debian / macOS

    .. code-block:: shell-session

        pip3 install --user crc16

or

    .. code-block:: shell-session

        apt install python3-crcmod

or

    .. code-block:: shell-session

        pip3 install --user crcmod

  - Arch

    .. code-block:: shell-session

       pacman -S python-crc16

* **python3-pillow**: Python Image Library
       .. code-block:: shell-session

            pip3 install --user pillow

  - Arch

    .. code-block:: shell-session

       pacman -S python-crc16 python-pillow
       pacman -S python-pillow

.. _ARM's GNU toolchain: https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads

@@ -137,3 +170,44 @@ In order to do a rebuild you can issue a clean command to ninja via
  $ ninja -C build/ -t clean

Otherwise, rerunning ``./bootstrap.sh`` will also clean the build-directory.

.. note::

   **macOS**: If ``strip`` fails to work on the freshly compiled ``mpy-cross``:
   "strip: object: (...)/lib/micropython/micropython/mpy-cross/mpy-cross
   malformed object (unknown load command 9)", you a likely not using the
   `strip` that matches to your ``clang``. Do ``which strip && which clang``,
   and if the paths don't match, clean up your PATHs, or as a quick hack,
   create a symlink for strip.

.. note::

   If you try to flash pycardium_epicardium.bin (renamed to card10.bin)
   and the bootloader does not finish updating, the file might be too large.
   ~700kB is the normal size, but problems were reported where the file size
   was >1MB. This was caused by the ``tr`` tool in the build process
   (it's supposed to create a large file with 0xff in it) - this requires the
   LC_ALL environment variable to be not set, or set to "C"
   (but not UTF8 or similar).

Docker
======
Alternatively, clone the ``master`` branch of the firmware repository and enter it:

.. code-block:: shell-session

   $ git clone https://git.card10.badge.events.ccc.de/card10/firmware.git
   $ cd firmware

Afterwards, build a docker-container which will build the firmware via:

.. code-block:: shell-session

   $ docker build -f docker/Dockerfile_fwbuild -t card10-firmware-builder .

Now, you can start the container with the firmware directory mounted, which will build the
firmware into the firmware/build directory:

.. code-block:: shell-session

   $ docker run -v $(pwd):/firmware card10-firmware-builder
Original line number Diff line number Diff line
@@ -5,27 +5,14 @@ method of flashing:

Flash Without Debugger
----------------------
If you do not have a debugger, you have to update the firmware using our
bootloader.  To do so, you need to reboot card10 while keeping the buttom on
the bottom right pressed.  Rebooting is done by either short pressing the power
button (top left) while you have a working firmware, or turning the card10 off
completely (by pressing the power button for 8 seconds) and then starting it again.

.. image:: static/bootloader-buttons.png

If you did everything correctly, the bootloader will display:

.. code-block:: text

   Bootloader
   Jul 28 2019
   USB activated.
   Ready.
If you do not have a debugger, you have to update the firmware using our
bootloader by going into :ref:`usb_file_transfer`.

On your host, you should now see an 8MB flash-device appear.  You can now drop
the firmware's ``.bin`` (from ``build/pycardium/pycardium_epicardium.bin`` in
most cases) into this flash-storage.  You **must** call the file ``card10.bin``
for the bootloader to use it.
After you get your badge into :ref:`usb_file_transfer`, you can drop the firmware's
``.bin`` (from ``build/pycardium/pycardium_epicardium.bin`` in most cases) into
this flash-storage.  You **must** call the file ``card10.bin`` for the
bootloader to use it.

The bootloader will then display ``Writing.`` in red while it is actually
writing the file to external flash.  Please wait until it displays ``Ready.``
Original line number Diff line number Diff line
@@ -22,28 +22,41 @@ Last but not least, if you want to start hacking the lower-level firmware, the

   pycardium/overview
   pycardium/stdlib
   pycardium/bhi160
   pycardium/ble_hid
   pycardium/bme680
   pycardium/max30001
   pycardium/max86150
   pycardium/buttons
   pycardium/color
   pycardium/config
   pycardium/display
   pycardium/gpio
   pycardium/leds
   pycardium/light-sensor
   pycardium/os
   pycardium/personal_state
   pycardium/png
   pycardium/power
   pycardium/pride
   pycardium/simple_menu
   pycardium/utime
   pycardium/vibra
   pycardium/pride
   pycardium/ws2812

.. toctree::
   :maxdepth: 1
   :caption: Firmware

   overview
   card10-cfg
   usb-file-transfer
   how-to-build
   how-to-flash
   debugger
   pycardium-guide
   memorymap
   epicardium/mutex
   epicardium/sensor-streams

.. toctree::
@@ -53,13 +66,18 @@ Last but not least, if you want to start hacking the lower-level firmware, the
   epicardium/overview
   epicardium/api
   epicardium-guide
   epicardium/internal

.. toctree::
   :maxdepth: 1
   :caption: Bluetooth

   bluetooth/overview
   bluetooth/ess
   bluetooth/file-transfer
   bluetooth/card10
   bluetooth/ecg
   bluetooth/nimble

Indices and tables
==================
Original line number Diff line number Diff line
@@ -14,47 +14,72 @@ Epicardium
Epicardium is based on `FreeRTOS <https://www.freertos.org/>`_.  There are a
number of tasks that will have been keeping card10 running.  These are:

* **Dispatcher**:  The dispatcher task handles API calls from core 1.
* **PMIC**:  The power manager task checks the battery level and other interesting
  statistics that can be gathered from our power manager IC (MAX77650).
* **Serial**:  Handles serial communication via *UART*, *CDC ACM* and possibly
  Bluetooth.
* **BHI160**: Housekeeping task for interaction with the `BHI160`_.

+--------------------+-------------------------------+----------+-------------------------------------------+
| Name               | ID Global                     | Priority | Description                               |
+====================+===============================+==========+===========================================+
| `vPmicTask`_       | ``pmic_task_id`` (static)     | +4       | Power Management (and Reset Button)       |
+--------------------+-------------------------------+----------+-------------------------------------------+
| `vLifecycleTask`_  | ``lifecycle_task`` (static)   | +3       | Control of the payload running on core 1. |
+--------------------+-------------------------------+----------+-------------------------------------------+
| `vBleTask`_        | ``ble_task_id`` (static)      | +3       | Bluetooth Low Energy Stack                |
+--------------------+-------------------------------+----------+-------------------------------------------+
| `vSerialTask`_     | ``serial_task_id``            | +3       | Serial Output via UART/CDC-ACM/BLE        |
+--------------------+-------------------------------+----------+-------------------------------------------+
| `vApiDispatcher`_  | ``dispatcher_task_id``        | +2       | Epicardium API dispatcher                 |
+--------------------+-------------------------------+----------+-------------------------------------------+
| `vInterruptsTask`_ | ``interrupts_task`` (static)  | +2       | Interrupt dispatcher worker               |
+--------------------+-------------------------------+----------+-------------------------------------------+
| `vMAX30001Task`_   | ``max30001_task_id`` (static) | +1       | `MAX30001`_ ECG driver                    |
+--------------------+-------------------------------+----------+-------------------------------------------+
| `vBhi160Task`_     | ``bhi160_task_id`` (static)   | +1       | `BHI160`_ sensor fusion driver            |
+--------------------+-------------------------------+----------+-------------------------------------------+

.. _vPmicTask: https://git.card10.badge.events.ccc.de/card10/firmware/blob/master/epicardium/modules/pmic.c#L281
.. _vLifecycleTask: https://git.card10.badge.events.ccc.de/card10/firmware/blob/master/epicardium/modules/lifecycle.c#L361
.. _vBleTask: https://git.card10.badge.events.ccc.de/card10/firmware/blob/master/epicardium/ble/ble.c#L237
.. _vSerialTask: https://git.card10.badge.events.ccc.de/card10/firmware/blob/master/epicardium/modules/serial.c#L289
.. _vApiDispatcher: https://git.card10.badge.events.ccc.de/card10/firmware/blob/master/epicardium/modules/dispatcher.c#L25
.. _vInterruptsTask: https://git.card10.badge.events.ccc.de/card10/firmware/blob/master/epicardium/modules/interrupts.c#L119
.. _vLedTask: https://git.card10.badge.events.ccc.de/card10/firmware/blob/master/epicardium/modules/personal_state.c#L58
.. _vMAX30001Task: https://git.card10.badge.events.ccc.de/card10/firmware/blob/master/epicardium/modules/max30001.c#L378
.. _vBhi160Task: https://git.card10.badge.events.ccc.de/card10/firmware/blob/master/epicardium/modules/bhi.c#L419

.. _MAX30001: https://www.maximintegrated.com/en/products/analog/data-converters/analog-front-end-ics/MAX30001.html
.. _BHI160: https://www.bosch-sensortec.com/bst/products/all_products/bhi160

.. todo::

   The following tasks have not yet been implemented/are currently in the works:

   - **Bluetooth**: The bluetooth stack (`#23`_)
   - **Payload Controller**: Control what is running on core 1

   .. _#23: https://git.card10.badge.events.ccc.de/card10/firmware/issues/23

Epicardium API
--------------
Epicardium exposes lots of functionality via the *Epicardium API*.  The
technical details if this API can be found in this :ref:`overview
<epicardium_api_overview>`.  If you are interesting in adding new API calls,
technical details of this API can be found in this :ref:`overview
<epicardium_api_overview>`.  If you are interested in adding new API calls,
you should probably read the :ref:`epicardium_api_guide` guide.

Pycardium
---------

Pycardium is our MicroPython fork.  Its purpose is to make it as easy as
possible to interact with card10.  If you are interested in working on
Pycardium, take a look at the :ref:`pycardium_guide` guide.

L0dables
.. _l0dables:

l0dables
--------

Next to Pycardium, other bare-metal code can also run on core 1.  For example,
a Rustcardium or C-cardium.  These l0dables must be compiled using our special
a `Rustcardium`_ or C-cardium.  These l0dables must be compiled using our special
linker script and should link against the api-caller library so they can
interface with the :ref:`epicardium_api`.

.. todo::
Note: this feature is disabled by default and has to be enabled in :ref:`card10_cfg`. A :ref:`card10_cfg` file dropped into the :ref:`usb_file_transfer` of the badge containing ``execute_elf = true`` is enough.

l0dables are currently built within the source tree of the main repository. See |l0dables_blinky|_ for an example of a hello-world-like program. Within those programs, you can access the :ref:`epicardium_api` to control the hardware and behaviour of the badge.

Once you have a built ELF file, you can drop it into the FAT filesystem of the flash (eg. via :ref:`usb_file_transfer`) and it will be available from the menu program of the badge.

   Provide more details how this works
.. _Rustcardium: https://git.card10.badge.events.ccc.de/astro/rust-card10
.. |l0dables_blinky| replace:: ``l0dables/blinky``
.. _l0dables_blinky: https://git.card10.badge.events.ccc.de/card10/firmware/-/tree/master/l0dables

Program Flow Diagram
--------------------
+67 −0
Original line number Diff line number Diff line
.. py:module:: bhi160

``bhi160`` - Sensor Fusion
==========================

.. versionadded:: 1.4

Supports the BHI160 sensor on the card10 for accelerometer, gyroscope,
magnetometer and orientation.

The coordinate system of the BHI160 sensor data looks like this:

.. image:: ../static/bhi160-coordinates.png

* The **accelerometer** axes are just the ones shown in the picture.
* The **gyroscope**'s angular velocities rotate counter clockwise around
  their respective axis.
* The **orientation** sensor uses the following mapping:

  +---------------------+----------------------+-------------------+
  | X                   | Y                    | Z                 |
  +=====================+======================+===================+
  | Azimuth (0° - 360°) | Pitch (-180° - 180°) | Roll (-90° - 90°) |
  +---------------------+----------------------+-------------------+

**Example**:

.. code-block:: python

   import bhi160
   import time

   bhi = bhi160.BHI160Orientation()

    while True:
        samples = bhi.read()
        if len(samples) == 0:
            time.sleep(0.25)
            continue
        # print the latest sample
        print(samples[-1])
        time.sleep(0.25)


Orientation
-----------
.. autoclass:: bhi160.BHI160Orientation
   :members:
   :inherited-members:

Accelerometer
-------------
.. autoclass:: bhi160.BHI160Accelerometer
  :members:
  :inherited-members:

Gyroscope
---------
.. autoclass:: bhi160.BHI160Gyroscope
  :members:
  :inherited-members:

Magnetometer
------------
.. autoclass:: bhi160.BHI160Magnetometer
  :members:
  :inherited-members:
+88 −0
Original line number Diff line number Diff line
``ble_hid`` - BLE HID
=====================
The ``ble_hid`` module provides access to the BLE Human Interface Device functionality.

.. note::
    Make sure to enable the BLE HID functionality in :ref:`card10_cfg` and reboot your card10
    if you want to use BLE HID.

    Also make sure that the ``adafruit_hid`` folder from the card10 release archive is placed
    on the file system of your card10.


.. warning::
    At least Ubuntu Linux will keep auto connecting to BLE HID devices once they are
    paired to the host computer. If you want to connect your card10 to a phone again,
    you might have to temporarily turn off Bluetooth on your computer.

An example application can be found in the preload directory (named ``HID Demo``). It provides
examples how to use the card10 as keyboard, mouse or volume control.

Please refer to the Adafruit CircuitPython HID library for examples how to use the HID service.
The card10 implements the same HID descriptors as the Adafruit CircuitPython BLE library and
should be compatible with all uses of the Adafruit CircuitPython HID library.

**Example emulating a keyboard**:

Adapted from https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/master/CPB_Keybutton_BLE/cpb_keybutton_ble.py

A more complete version of this example can be found in the HID Demo app on your card10.

.. code-block:: python

    import ble_hid
    from adafruit_hid.keyboard import Keyboard
    from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
    from adafruit_hid.keycode import Keycode

    k = Keyboard(ble_hid.devices)
    kl = KeyboardLayoutUS(k)

    k.send(Keycode.BACKSPACE)

    # use keyboard_layout for words
    kl.write("Demo with a long text to show how fast a card10 can type!")

    # add shift modifier
    k.send(Keycode.SHIFT, Keycode.F19)


**Example emulating a mouse**:

.. code-block:: python

    import ble_hid
    import bhi160
    import buttons
    from adafruit_hid.mouse import Mouse

    m = Mouse(ble_hid.devices)

    def send_report(samples):
        if len(samples) > 0:
            x = -int(samples[0].z)
            y = -int(samples[0].y)
            m.move(x, y)

    sensor = bhi160.BHI160Orientation(sample_rate=10, callback=send_report)

    b_old = buttons.read()
    while True:
        b_new = buttons.read()
        if not b_old == b_new:
            print(b_new)
            b_old = b_new
            if b_new == buttons.TOP_RIGHT:
                m.click(Mouse.MIDDLE_BUTTON)
            elif b_new == buttons.BOTTOM_RIGHT:
                m.click(Mouse.RIGHT_BUTTON)
            elif b_new == buttons.BOTTOM_LEFT:
                m.click(Mouse.LEFT_BUTTON)

.. note::
    Make sure to catch ``OSError`` exceptions in real applications. The exception will be thrown if
    there is connection to the host (or if it is lost) and you want to send an event.


.. automodule:: ble_hid
   :members:
+111 −0
Original line number Diff line number Diff line
.. py:module:: bme680

``bme680`` - Environmental Sensor
=================================
Allows access to environmental data of card10's surroundings.

If ``bsec_enable`` is set in :ref:`card10_cfg`, the proprietary Bosch BSEC
library will be activated which offers the following extra functionality:

 - Manual temperature offset compensation
    The ``bsec_offset`` configuration allows to configure a static temperature
    offset. Please use a reference thermometer to determine the offset of your
    card10. If no offset is provided a default offset for a card10 which is
    connected to USB, has BLE active and is without a case is used.
 - A fixed measurement interval of 3 seconds
    This helps to stabilize the temperature of the card10.
 - An indoor air quality (IAQ)  and equivalent CO2 estimation algorithm
    Please refer to the :ref:`bsec_api` API documentation to get more information
    about how to interpret these estimates.

.. note::
   Please keep in mind that the BME680 can not directly measure CO2. It measures
   Volatile Organic Compounds (VOCs). The BSEC library uses this measurement
   to compute an Indoor Air Quality (IAQ) indication. It also assumes that all VOCs
   in the air are from human breath and computes an equivalent CO2 (eCO2)
   value from this. Please be aware of these facts when judging the accuracy
   of the IAQ and eCO2 values. Some more information can be found in the
   :ref:`bsec_api` API documentation.

.. warning::
   For the BSEC library to properly work the card10 should be kept running
   for at least 10 hours at least once. This is needed as the BSEC library
   periodically writes calibration information about the sensor to the
   card10's file system.

   Please make sure to observe the IAQ accuracy field. It will tell you if the
   IAQ and eCO2 measurements are deemed "accurate" by the BSEC library. Your
   application should either inform the user about the current accuracy (e.g.
   by color coding) or simply not show any values if the accuracy is below 2.


.. note::
   See also the BLE :ref:`ESS`.


**Example**:

.. code-block:: python

   import bme680, time

   with bme680.Bme680() as environment:
       while True:
           d = environment.get_data()

           print("Temperature:    {:10.2f} °C".format(d.temperature))
           print("Humidity:       {:10.2f} % r.h.".format(d.humidity))
           print("Pressure:       {:10.2f} hPa".format(d.pressure))
           print("Gas Resistance: {:10.2f} Ω".format(d.resistance))

           time.sleep(1)

You can use the return type of :py:meth:`~bme680.Bme680.get_data` to decide
if you want to use/display the additonal fields returned if BSEC is enabled.

.. code-block:: python

    if isinstance(d, bme680.BSECData):
        print("Air quality:    {:7d}".format(d.iaq))

Sensor Class
------------

.. autoclass:: bme680.Bme680
   :members:

Deprecated Interface
--------------------
The following functions should no longer be used directly.  The only exist for
compatibility as they were the old BME680 interface in previous firmware
versions.

.. py:function:: init()

   Initialize the sensor.

   Before being able to read data from the sensor, you have to call
   :py:func:`bme680.init`.

   .. versionadded:: 1.4
   .. deprecated:: 1.10
      Use the :py:class:`bme680.Bme680` class instead.

.. py:function:: get_data()

   Perform a single measurement of environmental data.

   :return: Tuple containing ``temperature`` (°C), ``humidity`` (% r.h.),
      ``pressure`` (hPa) and ``gas resistance`` (Ohm).

   .. versionadded:: 1.4
   .. deprecated:: 1.10
      Use the :py:class:`bme680.Bme680` class instead.

.. py:function:: deinit()

   Deinitialize the sensor.

   .. versionadded:: 1.4
   .. deprecated:: 1.10
      Use the :py:class:`bme680.Bme680` class instead.
Original line number Diff line number Diff line
@@ -14,23 +14,65 @@ The color module also contains a few constanst for commonly used colors:
Camp Colors
~~~~~~~~~~~
.. py:data:: CHAOSBLUE

   .. color-example:: #0076BA

.. py:data:: CHAOSBLUE_DARK

   .. color-example:: #005383

.. py:data:: COMMYELLOW

   .. color-example:: #FFC600

.. py:data:: COMMYELLOW_DARK

   .. color-example:: #D39A00

.. py:data:: CAMPGREEN

   .. color-example:: #99BA00

.. py:data:: CAMPGREEN_DARK

   .. color-example:: #6F8700


General
~~~~~~~
.. py:data:: BLACK

   .. color-example:: #000

.. py:data:: WHITE

   .. color-example:: #fff

.. py:data:: RED

   .. color-example:: #f00

.. py:data:: GREEN

   .. color-example:: #0f0

.. py:data:: YELLOW

   .. color-example:: #ff0

.. py:data:: BLUE

   .. color-example:: #00f

.. py:data:: MAGENTA

   .. color-example:: #f0f

.. py:data:: CYAN

   .. color-example:: #0ff


.. py:module:: htmlcolor

``htmlcolor`` - Color Constants
@@ -39,142 +81,561 @@ The ``htmlcolor`` module contains even more color constants. Note
that loading the ``htmlcolor`` module will require ~12K of RAM.

.. py:data:: ALICEBLUE

   .. color-example:: aliceblue

.. py:data:: ANTIQUEWHITE

   .. color-example:: antiquewhite

.. py:data:: AQUA

   .. color-example:: aqua

.. py:data:: AQUAMARINE

   .. color-example:: aquamarine

.. py:data:: AZURE

   .. color-example:: azure

.. py:data:: BEIGE

   .. color-example:: beige

.. py:data:: BISQUE

   .. color-example:: bisque

.. py:data:: BLACK

   .. color-example:: black

.. py:data:: BLANCHEDALMOND

   .. color-example:: blanchedalmond

.. py:data:: BLUE

   .. color-example:: blue

.. py:data:: BLUEVIOLET

   .. color-example:: blueviolet

.. py:data:: BROWN

   .. color-example:: brown

.. py:data:: BURLYWOOD

   .. color-example:: burlywood

.. py:data:: CADETBLUE

   .. color-example:: cadetblue

.. py:data:: CHARTREUSE

   .. color-example:: chartreuse

.. py:data:: CHOCOLATE

   .. color-example:: chocolate

.. py:data:: CORAL

   .. color-example:: coral

.. py:data:: CORNFLOWERBLUE

   .. color-example:: cornflowerblue

.. py:data:: CORNSILK

   .. color-example:: cornsilk

.. py:data:: CRIMSON

   .. color-example:: crimson

.. py:data:: CYAN

   .. color-example:: cyan

.. py:data:: DARKBLUE

   .. color-example:: darkblue

.. py:data:: DARKCYAN

   .. color-example:: darkcyan

.. py:data:: DARKGOLDENROD

   .. color-example:: darkgoldenrod

.. py:data:: DARKGRAY

   .. color-example:: darkgray

.. py:data:: DARKGREEN

   .. color-example:: darkgreen

.. py:data:: DARKKHAKI

   .. color-example:: darkkhaki

.. py:data:: DARKMAGENTA

   .. color-example:: darkmagenta

.. py:data:: DARKOLIVEGREEN

   .. color-example:: darkolivegreen

.. py:data:: DARKORANGE

   .. color-example:: darkorange

.. py:data:: DARKORCHID

   .. color-example:: darkorchid

.. py:data:: DARKRED

   .. color-example:: darkred

.. py:data:: DARKSALMON

   .. color-example:: darksalmon

.. py:data:: DARKSEAGREEN

   .. color-example:: darkseagreen

.. py:data:: DARKSLATEBLUE

   .. color-example:: darkslateblue

.. py:data:: DARKSLATEGRAY

   .. color-example:: darkslategray

.. py:data:: DARKTURQUOISE

   .. color-example:: darkturquoise

.. py:data:: DARKVIOLET

   .. color-example:: darkviolet

.. py:data:: DEEPPINK

   .. color-example:: deeppink

.. py:data:: DEEPSKYBLUE

   .. color-example:: deepskyblue

.. py:data:: DIMGRAY

   .. color-example:: dimgray

.. py:data:: DODGERBLUE

   .. color-example:: dodgerblue

.. py:data:: FIREBRICK

   .. color-example:: firebrick

.. py:data:: FLORALWHITE

   .. color-example:: floralwhite

.. py:data:: FORESTGREEN

   .. color-example:: forestgreen

.. py:data:: FUCHSIA

   .. color-example:: fuchsia

.. py:data:: GAINSBORO

   .. color-example:: gainsboro

.. py:data:: GHOSTWHITE

   .. color-example:: ghostwhite

.. py:data:: GOLD

   .. color-example:: gold

.. py:data:: GOLDENROD

   .. color-example:: goldenrod

.. py:data:: GRAY

   .. color-example:: gray

.. py:data:: GREEN

   .. color-example:: green

.. py:data:: GREENYELLOW

   .. color-example:: greenyellow

.. py:data:: HONEYDEW

   .. color-example:: honeydew

.. py:data:: HOTPINK

   .. color-example:: hotpink

.. py:data:: INDIANRED

   .. color-example:: indianred

.. py:data:: INDIGO

   .. color-example:: indigo

.. py:data:: IVORY

   .. color-example:: ivory

.. py:data:: KHAKI

   .. color-example:: khaki

.. py:data:: LAVENDER

   .. color-example:: lavender

.. py:data:: LAVENDERBLUSH

   .. color-example:: lavenderblush

.. py:data:: LAWNGREEN

   .. color-example:: lawngreen

.. py:data:: LEMONCHIFFON

   .. color-example:: lemonchiffon

.. py:data:: LIGHTBLUE

   .. color-example:: lightblue

.. py:data:: LIGHTCORAL

   .. color-example:: lightcoral

.. py:data:: LIGHTCYAN

   .. color-example:: lightcyan

.. py:data:: LIGHTGOLDENRODYELLOW

   .. color-example:: lightgoldenrodyellow

.. py:data:: LIGHTGRAY

   .. color-example:: lightgray

.. py:data:: LIGHTGREEN

   .. color-example:: lightgreen

.. py:data:: LIGHTPINK

   .. color-example:: lightpink

.. py:data:: LIGHTSALMON

   .. color-example:: lightsalmon

.. py:data:: LIGHTSEAGREEN

   .. color-example:: lightseagreen

.. py:data:: LIGHTSKYBLUE

   .. color-example:: lightskyblue

.. py:data:: LIGHTSLATEGRAY

   .. color-example:: lightslategray

.. py:data:: LIGHTSTEELBLUE

   .. color-example:: lightsteelblue

.. py:data:: LIGHTYELLOW

   .. color-example:: lightyellow

.. py:data:: LIME

   .. color-example:: lime

.. py:data:: LIMEGREEN

   .. color-example:: limegreen

.. py:data:: LINEN

   .. color-example:: linen

.. py:data:: MAGENTA

   .. color-example:: magenta

.. py:data:: MAROON

   .. color-example:: maroon

.. py:data:: MEDIUMAQUAMARINE

   .. color-example:: mediumaquamarine

.. py:data:: MEDIUMBLUE

   .. color-example:: mediumblue

.. py:data:: MEDIUMORCHID

   .. color-example:: mediumorchid

.. py:data:: MEDIUMPURPLE

   .. color-example:: mediumpurple

.. py:data:: MEDIUMSEAGREEN

   .. color-example:: mediumseagreen

.. py:data:: MEDIUMSLATEBLUE

   .. color-example:: mediumslateblue

.. py:data:: MEDIUMSPRINGGREEN

   .. color-example:: mediumspringgreen

.. py:data:: MEDIUMTURQUOISE

   .. color-example:: mediumturquoise

.. py:data:: MEDIUMVIOLETRED

   .. color-example:: mediumvioletred

.. py:data:: MIDNIGHTBLUE

   .. color-example:: midnightblue

.. py:data:: MINTCREAM

   .. color-example:: mintcream

.. py:data:: MISTYROSE

   .. color-example:: mistyrose

.. py:data:: MOCCASIN

   .. color-example:: moccasin

.. py:data:: NAVAJOWHITE

   .. color-example:: navajowhite

.. py:data:: NAVY

   .. color-example:: navy

.. py:data:: OLDLACE

   .. color-example:: oldlace

.. py:data:: OLIVE

   .. color-example:: olive

.. py:data:: OLIVEDRAB

   .. color-example:: olivedrab

.. py:data:: ORANGE

   .. color-example:: orange

.. py:data:: ORANGERED

   .. color-example:: orangered

.. py:data:: ORCHID

   .. color-example:: orchid

.. py:data:: PALEGOLDENROD

   .. color-example:: palegoldenrod

.. py:data:: PALEGREEN

   .. color-example:: palegreen

.. py:data:: PALETURQUOISE

   .. color-example:: paleturquoise

.. py:data:: PALEVIOLETRED

   .. color-example:: palevioletred

.. py:data:: PAPAYAWHIP

   .. color-example:: papayawhip

.. py:data:: PEACHPUFF

   .. color-example:: peachpuff

.. py:data:: PERU

   .. color-example:: peru

.. py:data:: PINK

   .. color-example:: pink

.. py:data:: PLUM

   .. color-example:: plum

.. py:data:: POWDERBLUE

   .. color-example:: powderblue

.. py:data:: PURPLE

   .. color-example:: purple

.. py:data:: RED

   .. color-example:: red

.. py:data:: ROSYBROWN

   .. color-example:: rosybrown

.. py:data:: ROYALBLUE

   .. color-example:: royalblue

.. py:data:: SADDLEBROWN

   .. color-example:: saddlebrown

.. py:data:: SALMON

   .. color-example:: salmon

.. py:data:: SANDYBROWN

   .. color-example:: sandybrown

.. py:data:: SEAGREEN

   .. color-example:: seagreen

.. py:data:: SEASHELL

   .. color-example:: seashell

.. py:data:: SIENNA

   .. color-example:: sienna

.. py:data:: SILVER

   .. color-example:: silver

.. py:data:: SKYBLUE

   .. color-example:: skyblue

.. py:data:: SLATEBLUE

   .. color-example:: slateblue

.. py:data:: SLATEGRAY

   .. color-example:: slategray

.. py:data:: SNOW

   .. color-example:: snow

.. py:data:: SPRINGGREEN

   .. color-example:: springgreen

.. py:data:: STEELBLUE

   .. color-example:: steelblue

.. py:data:: TAN

   .. color-example:: tan

.. py:data:: TEAL

   .. color-example:: teal

.. py:data:: THISTLE

   .. color-example:: thistle

.. py:data:: TOMATO

   .. color-example:: tomato

.. py:data:: TURQUOISE

   .. color-example:: turquoise

.. py:data:: VIOLET

   .. color-example:: violet

.. py:data:: WHEAT

   .. color-example:: wheat

.. py:data:: WHITE

   .. color-example:: white

.. py:data:: WHITESMOKE

   .. color-example:: whitesmoke

.. py:data:: YELLOW

   .. color-example:: yellow

.. py:data:: YELLOWGREEN

   .. color-example:: yellowgreen
+9 −0
Original line number Diff line number Diff line
``config`` - Configuration
==========================
The ``config`` module provides functions to interact with card10's
configuration file (:ref:`card10_cfg`).

.. automodule:: config
   :members:

Original line number Diff line number Diff line
``display`` - Display
=====================

The display module let's you draw on the card10's display.
Pixels are addressed from top left to bottom right with a range of x: 0 to 159 and y: 0 to 79.

.. code-block:: text

   0,0
      +---------------------+
      |                     |
      |                     |
      |                     |
      |                     |
      +---------------------+
                           159,79

Drawing operations are clipped, pixels outside of the screen will be ignored.

.. automodule:: display
   :members:
Original line number Diff line number Diff line
@@ -25,7 +25,10 @@ output in your scripts.
   :param int pin: ID of the pin to be configured.
   :param int mode: An integer with the bits for the wanted mode set. Create your
      integer by ORing :py:data:`gpio.mode.OUTPUT`, :py:data:`gpio.mode.INPUT`,
      :py:data:`gpio.mode.PULL_UP`, :py:data:`gpio.mode.PULL_DOWN`.
      :py:data:`gpio.mode.ADC`, :py:data:`gpio.mode.PULL_UP`,
      :py:data:`gpio.mode.PULL_DOWN`.

   .. note:: On WRISTBAND_3, there is no ADC functionality available

.. py:function:: get_mode(pin)

@@ -47,6 +50,9 @@ output in your scripts.

   :param int pin: ID of the pin of to get the mode of.
   :returns: Current value of the GPIO pin.
      If the pin is configured as ADC, the value returned
      will be between 0 and 1000, representing voltages from
      0V to 3.3V (:py:data:`gpio.ADC` is only available in 1.9+).

.. py:data:: WRISTBAND_1

@@ -75,6 +81,12 @@ output in your scripts.

   Configures a pin as input.

.. py:data:: ADC

   Configure pin as ADC input.

   .. versionadded: 1.9

.. py:data:: PULL_UP

   Enables the internal pull-up resistor of a pin.
Original line number Diff line number Diff line
@@ -25,3 +25,13 @@ be fairly stable.
.. py:function:: stop()

   Stop the ADC.

.. py:function:: read()

   Direct readout of the light-sensor.

   Use this function for low latency readout.  The time between polls can have
   an effect on the values measures.  If you do not need low latency, prefer
   :py:func:`light_sensor.get_reading`.

   .. versionadded:: 1.8
+5 −0
Original line number Diff line number Diff line
``max30001`` - MAX30001
=======================

.. automodule:: max30001
   :members:
+5 −0
Original line number Diff line number Diff line
``max86150`` - MAX86150
=======================

.. automodule:: max86150
   :members:
Original line number Diff line number Diff line
@@ -8,12 +8,6 @@ functions found in CPythons ``os`` module.
CPython-Like
------------

.. py:function:: unlink(path)

   Unlink (remove) a file.

   :param str path: The file to remove.

.. py:function:: listdir(dir)

   List contents of a directory.
@@ -22,10 +16,34 @@ CPython-Like
   :returns: A list of entities (files or subdirectories) in the directory
      ``dir``.

.. py:function:: mkdir(path)

   Create a directory named *path*.

   :param str path: Path to the directory to create.  Only the last component
      of this path will be created.

.. py:function:: rename(src, dst)

   Rename the file or directory *src* to *dst*. If *dst* exists, the operation
   will fail.

   :param str src: Path to source file to rename.
   :param str dst: Destination path to rename to.  Must not exist before
      calling :py:func:`os.rename`.

.. py:function:: unlink(path)

   Unlink (remove) a file.

   :param str path: The file to remove.

.. py:function:: urandom(n)

   Return ``n`` random bytes.

   .. versionadded:: 1.3

   :param int n: Number of random bytes to retrieve.
   :returns: ``bytes()`` object with ``n`` random bytes.

@@ -70,3 +88,44 @@ Card10-Specific

      Please only call this function if absolutely necessary.  In most cases
      you'll want to just :py:func:`os.exit` instead.

.. py:function:: usbconfig(config_type)

   Change active USB configuration. By default, card10 boots with
   :py:data:`os.USB_SERIAL` active.

   This will deactivate the currently active USB configuration. This means
   that, if you activate :py:data:`os.USB_FLASH` while :py:data:`os.USB_SERIAL`
   was active, the USB serial will be disconnected.

   :param config_type: Selects which config to activate. Possible
      values are :py:data:`os.USB_SERIAL`, :py:data:`os.USB_FLASH`,
      or :py:data:`os.USB_NONE`.

   .. versionadded:: 1.11

.. py:data:: USB_NONE

   No USB device active.

.. py:data:: USB_SERIAL

   CDC-ACM serial device active.

.. py:data:: USB_FLASH

   Mass-Storage device active.

.. py:function:: fs_is_attached()

   Check whether the filesystem is currently attached to card10 (or whether a connected
   USB host is currently holding control over it and possibly writing to it).

   :returns:

      - ``True`` if the filesystem is attached to card10 and an app can read and
        write files.
      - ``False`` if the filesystem is not available to card10 because a USB
        host is currently controlling it.

   .. versionadded: 1.18
Original line number Diff line number Diff line
@@ -33,11 +33,56 @@ systems this device will be called ``/dev/ttyACM0`` or ``/dev/ttyACM1``.
Choose a terminal-emulator of your liking and open the above mentioned device.
Baud-rate is 115200.  Some options are:

* **screen**: ``screen /dev/ttyACM0 115200``
* **picocom**: ``picocom -b 115200 /dev/ttyACM0``
* **screen**: ``sudo screen /dev/ttyACM0 115200``
* **picocom**: ``sudo picocom -b 115200 /dev/ttyACM0``

After connecting, reboot card10 and you should see the MicroPython REPL pop up.
After connecting, reboot reset the card10 via the power button (left upper
corner) and you should see the output of **menu.py** script (it's located in
*preload/menu.py*). You can press CTRL-C to interrupt the script and jump into
the MicroPython prompt.

.. todo::
To switch on the blue fairy dust you must import the led python module::

   Getting Started Guide for people interested in writing Python code.
   import leds

and power it on::

   leds.set_rocket(0, 31)

.. note::

   If you're using iOS/Mac then you can connect to your serial console using:

   .. code-block:: shell-session

      screen /dev/tty.usbmodem* 115200

   You can now see in your console what buttons you have pressed and your
   console outputs/logs.  With ``CTRL+C`` you exit the console.

REPL modes
^^^^^^^^^^
MicroPython supports a different REPL modes over the serial console. The modes
can be changed on every new line.

Normal mode
"""""""""""
This is the mode you will first see. You can switch to it by pressing CTRL-B.
If you are in a other mode you can return to this mode by pressing CTRL-B too.

Paste mode
""""""""""
You can enter the paste mode by pressing CTRL-E and you can simple copy 'n'
paste your source code into the console and it will be interpreted and executed
line by line. Every new line will be reply by the prompt with **===**.

RAW mode
""""""""
The RAW mode to be intendend for the usage with tools. By pressing CTRL-A you
will enter the RAW REPL mode. The type in code will not printed. By pressing
CTRL-D the whole entered code will be evaluated and executed. The board will
reply with **OK** and print after that the output (print commands) of the code
or give you tracebacks if an error occured.

You can use **pycard10** (tools/pycard10.py) to execute python files from your
PC directly on the card10.
+7 −0
Original line number Diff line number Diff line
``png`` - PNG Decoder
=====================
The ``png`` module provides functions to decode PNG files into raw pixel data
which can be displayed using the card10's display or its LEDs.

.. automodule:: png
   :members:
+61 −0
Original line number Diff line number Diff line
.. py:module:: power

``power`` - PMIC power module handling
======================================
.. versionadded:: 1.4

The :py:mod:`power` module allows you to read the card10's power status
in your scripts.

**Example**:

.. code-block:: python

   import power

   print(power.read_battery_voltage())

.. py:function:: read_battery_voltage()

   Read the battery voltage in V.  Please keep in mind that battery
   voltage behaves exponentially when interpreting this value.

   .. warning::

      Card10 will hard-shutdown once the voltage drops below 3.4 V

   .. versionadded:: 1.4

.. py:function:: read_battery_current()

   Read the battery-side current flow in A.

   .. versionadded:: 1.4

.. py:function:: read_chargein_voltage()

   Read the charge voltage in V.

   .. versionadded:: 1.4

.. py:function:: read_chargein_current()

   Read the charge current in A.

   .. versionadded:: 1.4

.. py:function:: read_system_voltage()

   Read the system-side voltate in V.

   .. versionadded:: 1.4

.. py:function:: read_thermistor_voltage()

   Read the thermistor voltage in V.

   There is a resistor network from GND over a thermistor
   (10K at room temperature) over 10K to the Thermistor Bias voltage.
   This reads the voltage between thermistor and resistor.

   .. versionadded:: 1.4
+30 −0
Original line number Diff line number Diff line
``simple_menu`` - Draw a Menu
=============================
.. versionadded:: 1.4

To allow quickly hacking some scripts, Pycardium has a small library for
displaying menus.  You can use it like this:

.. code-block:: python

   import color
   import simple_menu


   class MyMenu(simple_menu.Menu):
       color_1 = color.CAMPGREEN
       color_2 = color.CAMPGREEN_DARK

       def on_select(self, name, index):
           print("{!r} was selected!".format(name))


   if __name__ == "__main__":
       MyMenu(["foo", "bar", "baz"]).run()

.. autoclass:: simple_menu.Menu
   :members:

.. autodata:: simple_menu.TIMEOUT

.. autofunction:: simple_menu.button_events
Original line number Diff line number Diff line
MicroPython Standard Library
============================
Pycardium contains some modules from the MicroPython standard library.  These
are:
Pycardium contains some modules from the MicroPython standard library.

Some modules below use a standard Python name, but prefixed with “u”,
e.g. ujson instead of json. This is to signify that such a module is a
micro-library, i.e. implements only a subset of CPython module
functionality. Please refer to the official `MicroPython docs`_ for an
explanation why.

All u-name modules can also be imported using their non-u-name. E.g.
``import utime`` and import ``import time`` will both work.

.. _MicroPython docs: http://docs.micropython.org/en/latest/library/index.html#python-standard-libraries-and-micro-libraries

.. py:module:: framebuf

``framebuf``
------------
Refer to the official `MicroPython docs for framebuf`_.

.. _MicroPython docs for framebuf: https://docs.micropython.org/en/latest/library/framebuf.html

.. py:module:: ubinascii

@@ -201,6 +219,8 @@ Struct module.
      UUID(bytes='\x12\x34\x56\x78' * 4)
      UUID(int=0x12345678123456781234567812345678)

   .. versionadded:: 1.10

   .. py:attribute:: bytes

      UUID as ``bytes()`` object
@@ -217,11 +237,8 @@ Struct module.

      UUID version accordiung to RFC 4122

.. py:function:: uuid4():
.. py:function:: uuid4()

   Generate a new UUID version 4 (random UUID).

   .. todo::

      This function is not yet usable because we don't have
      :py:func:`os.urandom` yet.
   .. versionadded:: 1.10
Original line number Diff line number Diff line
@@ -8,6 +8,9 @@ CPython but wouldn't fit anywhere else in our implementation. Most
prominently,  this is the :py:func:`utime.alarm` function for setting an RTC
alarm.

Like all other u-name modules, ``utime`` can also imported using the standard
``import time`` statement.

.. |time| replace:: ``time``
.. _time: https://docs.python.org/3/library/time.html

@@ -28,11 +31,69 @@ alarm.
   Return the current timestamp in seconds since 2000-01-01 00:00 in
   the local timezone.

.. py:function:: time_ms()

   Return the current timestamp in milliseconds since 2000-01-01 00:00 in
   the local timezone.

.. py:function:: monotonic()

   Return a monotonically increasing timestamp.

   .. versionadded:: 1.11

.. py:function:: monotonic_ms()

   Return a monotonically increasing timestamp in milliseconds.

   .. versionadded:: 1.11

.. py:function:: ticks_ms()

   Return processor ticks (converted to milliseconds) since Pycardium startup.

   This function should be the preferred method for timing and profiling
   because it does not need an API call and thus is very fast.

   .. versionadded:: 1.13

.. py:function:: ticks_us()

   Return processor ticks (converted to microseconds) since Pycardium startup.

   This function should be the preferred method for timing and profiling
   because it does not need an API call and thus is very fast.

   .. versionadded:: 1.13

.. py:function:: unix_time()

   Return the current unix time as seconds since the epoch.

   .. versionadded:: 1.12

.. py:function:: unix_time_ms()

   Return the current unix time as milliseconds since the epoch.

   .. versionadded:: 1.12

.. py:function:: set_time(secs)

   Sets the time to ``secs`` seconds since 2000-01-01 00:00 in the local
   timezone.

   .. versionchanged:: 1.4
      :py:func:`utime.set_time` previously applied a wrong timezone offset,
      thus leading to wrong results.

.. py:function:: set_time_ms(msecs)

   Set the time to ``msecs`` seconds since 2000-01-01 00:00 in the local
   timezone.

   .. versionadded:: 1.12

.. py:function:: set_unix_time(secs)

   Sets the time to ``secs`` seconds since 1970-01-01 00:00 UTC.
@@ -40,6 +101,12 @@ alarm.
   by running ``date +%s`` in a command line or ``int(time.time())``
   in Python.

.. py:function:: set_unix_time_ms(msecs)

   Set the time to ``msecs`` milliseconds since the unix epoch.

   .. versionadded:: 1.12

.. py:function:: localtime([secs])

   Return the current time as a timestruct tuple.  If ``secs`` is given, return
@@ -71,13 +138,13 @@ alarm.

   .. code-block:: python

      import utime
      import time

      def minute_timer(x):
         current = utime.time()
         current = time.time()
         print("Current: " + str(current))
         alarm = (current // 60 + 1) * 60
         utime.alarm(alarm, minute_timer)
         time.alarm(alarm, minute_timer)

      minute_timer(None)

@@ -86,13 +153,13 @@ alarm.

   .. code-block:: python

      import interrupt, utime
      import interrupt, time

      def 5_second_timer(x):
         current = utime.time()
         current = time.time()
         print("Current: " + str(current))
         alarm = (current // 10) * 10 + 5
         utime.alarm(alarm)
         time.alarm(alarm)

      # This time, we need to register and enable the callback manually
      interrupt.set_callback(interrupt.RTC_ALARM, 5_second_timer)
+35 −0
Original line number Diff line number Diff line
.. py:module:: ws2812

``ws2812`` - Neopixel LEDs
==========================
The ``ws2812`` module controls LEDs of the WS2812 type. Just as the ``leds`` module, it exposes a function :py:func:`ws2812.set_all`, which works a similar fashion.

.. versionadded:: 1.10

.. py:function:: set_all(pin, colors)

   Set multiple of the LEDs to RGB values.

   Filling starts at the LED connected to the specified gpio pin.

   :param int pin: ID of the pin to use for sending the data.
   :param colors: List of RGB triplets.

   **Example**

   .. code-block:: python

      import color, time, ws2812, gpio

      gpio.set_mode(gpio.WRISTBAND_2, gpio.mode.OUTPUT)

      i = 0
      while True:
          col1 = color.from_hsv(i % 360, 1.0, 0.1)
          col2 = color.from_hsv((i + 20) % 360, 1.0, 0.1)
          col3 = color.from_hsv((i + 40) % 360, 1.0, 0.1)
          ws2812.set_all(gpio.WRISTBAND_2, [col1, col2, col3])
          i += 1
          time.sleep_ms(10)

   .. versionadded:: 1.10
+40 −0
Original line number Diff line number Diff line
.. _usb_file_transfer:

USB File Transfer
=================

The card10 badge bootloader offers a USB Mass Storage mode to access its 8MB external flash.

This flash contains a FAT32 filesystem and files on it will be visible to software running on the badge.

Getting to the USB mode
-----------------------

To get to the USB mode, you will need to boot the badge while keeping the bottom-right button pressed.

.. image:: static/bootloader-buttons.png

1. If the badge is on, hold the top-left (power) button until the sleep screen appears, then release.
2. Start holding the bottom-right button.
3. Quickly press and release the top-left (power) button to turn the badge on.
4. Your badge should now be in file transfer mode, and you can then release the bottom-right button.

If you succesfully got into USB File Transfer mode, the screen will display its version and notify about the USB mode by displaying ``USB activated. Ready.``. If you connect the badge via USB to your computer, it should now detect 8MB of flash storage that you can mount in the same way as a pendrive.

This pendrive can contain multiple files with different functions, some of them outlined here:

============== ========
File name      Function
============== ========
``card10.bin`` Firmware update file for the badge. If this file is present, the bootloader will perform an internal update to boot from this firmware file, then it will be deleted.
``main.py``    This file contains the default `application` to be run on power on.
``menu.py``    This file contains the default `menu` to be run when the power button is short-pressed.
``*.py``       These are application files written in (Micro)Python that can be run from the menu.
``*.elf``      These are :ref:`l0dables` that can be run from the menu.
============== ========

Updating files and rebooting
----------------------------

No matter which file you are writing, the bootloader will display a red ``Writing`` status message while write operations are pending. Please wait until it displays ``Ready`` again before resetting the badge by pressing the power button again.
Original line number Diff line number Diff line
@@ -6,31 +6,17 @@
#include "gfx.h"
#include "display.h"

/*
 * "Decompress" splash-screen image.  The algorithm works as follows:
 *
 * Each byte encodes up to 127 pixels in either white or black.  The most
 * significant bit determines the color, the remaining 7 bits determine the
 * amount.
 */
static void bootloader_display_splash(void)
{
	int idx = 0;

	Color white = gfx_color(&display_screen, WHITE);
	Color black = gfx_color(&display_screen, BLACK);
	for (int i = 0; i < sizeof(splash); i++) {
		Color color    = (splash[i] & 0x80) ? white : black;
		uint8_t length = splash[i] & 0x7f;

		for (int j = 0; j < length; j++) {
			uint16_t x = idx % 160;
			uint16_t y = idx / 160;
			gfx_setpixel(&display_screen, x, y, color);
			idx++;
		}
	}

	gfx_copy_region_rle_mono(
		&display_screen,
		0,
		0,
		160,
		80,
		sizeof(splash),
		(const void *)(splash)
	);
	gfx_update(&display_screen);
}

@@ -55,6 +41,21 @@ void bootloader_display_header(void)
	bootloader_display_line(2, CARD10_VERSION, white);
}

void bootloader_display_error(char *errtype, char *line1, char *line2)
{
	gfx_clear(&display_screen);

	Color red    = gfx_color(&display_screen, RED);
	Color yellow = gfx_color(&display_screen, YELLOW);
	Color white  = gfx_color(&display_screen, WHITE);

	bootloader_display_line(0, "[FATAL ERROR]", red);
	bootloader_display_line(1, errtype, yellow);
	bootloader_display_line(2, CARD10_VERSION, white);
	bootloader_display_line(3, line1, white);
	bootloader_display_line(4, line2, white);
}

/*
 * Display a line of text on the display.
 */
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@
/* Display */
void bootloader_display_init(void);
void bootloader_display_header(void);
void bootloader_display_error(char *errtype, char *line1, char *line2);
void bootloader_display_line(int line, char *string, uint16_t color);

/* USB */
Original line number Diff line number Diff line
@@ -6,8 +6,12 @@ BIN1="$2"
BIN2="$3"
BINOUT="$4"

dd if=/dev/zero ibs=1k count=448 2>/dev/null | LANG=C LC_CTYPE=C tr "\000" "\377" > "$BINOUT"
dd if="$BIN1" of="$BINOUT" conv=notrunc 2>/dev/null
dd if="$BIN2" >> "$BINOUT" 2>/dev/null
if [ "$(stat -c "%s" "${BIN1}")" -gt 589824 ]; then
    echo "$0: ${BIN1} is too big to fit!" >&2
    exit 1
fi

objcopy -I binary -O binary --pad-to=589824 --gap-fill=255 "${BIN1}" "$BINOUT"
cat "$BIN2" >>"$BINOUT"

"$PYTHON" "$(dirname "$0")/crc_patch.py" "$BINOUT"
Original line number Diff line number Diff line
#!/usr/bin/env python3
import sys
import warnings

warnings.simplefilter("ignore")

try:
    import crc16

    crcfun = crc16.crc16xmodem
except ImportError:
    try:
        import crcmod

        crcfun = crcmod.predefined.mkCrcFun("xmodem")
    except ImportError:
        try:
            import crcelk

            crcfun = crcelk.CRC_XMODEM.calc_bytes
        except ImportError:
            raise Exception(
                "Could not find a CRC implementation. Tried: crc16, crcmod, crcelk."
            )


def main():
    data = open(sys.argv[1], 'rb').read()
    crc = crc16.crc16xmodem(data)
    data = open(sys.argv[1], "rb").read()
    crc = crcfun(data)
    # print(crc)

    padded = data + bytes([crc >> 8, crc & 0xFF])

    crc = crc16.crc16xmodem(padded)
    crc = crcfun(padded)
    # print(crc)

    open(sys.argv[1], 'wb').write(padded)
    open(sys.argv[1], "wb").write(padded)


if __name__ == "__main__":
Original line number Diff line number Diff line
@@ -179,6 +179,9 @@ void flash_partition(void)
				data); /* wild cast. not sure if this works */
		if (ret != E_NO_ERROR) {
			printf("FLC_Write failed with %d\n", ret);
			bootloader_display_error(
				"Firmware Write", "Firmware not", "updated."
			);
			while (1)
				;
		}
+3 −1
Original line number Diff line number Diff line
@@ -4,7 +4,9 @@ set -xe
cd "$(dirname "$0")"
test -d build/ && rm -r build/

git submodule update --init ./lib/micropython
# Get external libs (MicroPython, tiny-AES-c, SHA256)
git submodule deinit --all
git submodule update --init ./lib
meson --cross-file card10-cross.ini build/ "$@"

set +x
Original line number Diff line number Diff line
@@ -6,7 +6,7 @@ strip = 'arm-none-eabi-strip'
[properties]
c_args      = ['-mthumb', '-mcpu=cortex-m4', '-mfloat-abi=softfp', '-mfpu=fpv4-sp-d16', '-Wa,-mimplicit-it=thumb', '-ffunction-sections', '-fdata-sections', '-fsingle-precision-constant', '-fno-isolate-erroneous-paths-dereference']

c_link_args = ['-mthumb', '-mcpu=cortex-m4', '-mfloat-abi=softfp', '-mfpu=fpv4-sp-d16', '-Wl,--start-group', '-lc', '-lnosys', '-Wl,--end-group', '--specs=nano.specs']
c_link_args = ['-mthumb', '-mcpu=cortex-m4', '-mfloat-abi=softfp', '-mfpu=fpv4-sp-d16', '-Wl,--start-group', '-lc', '-lnosys', '-Wl,--end-group', '--specs=nano.specs', '-Wl,-wrap,BbBleDrvRand']

target_defs = ['-DTARGET=32665', '-DTARGET_REV=0x4131', '-DBOARD_CARD10=1']

+9 −5
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ in stdenv.mkDerivation rec {
    bash
    crc16
    gcc-arm-embedded
    git
    meson
    ninja
    py
@@ -42,10 +43,13 @@ in stdenv.mkDerivation rec {
    meson --cross-file card10-cross.ini "$build"
    ninja -C "$build" -j $NIX_BUILD_CORES

    # Copy artifacts to derivation outputs.
    install -D -m 444 "$build/bootloader/bootloader.elf" -t "$out/bootloader"
    install -D -m 444 "$build/pycardium/pycardium_epicardium.bin" -t "$out/pycardium"
    install -D -m 444 "$build/epicardium/epicardium.elf" -t "$out/epicardium"
    install -D -m 444 "$build/pycardium/pycardium.elf" -t "$out/pycardium"
    # Copy ELFs for debugging
    install -D -m 444 "$build/bootloader/bootloader.elf" -t "$out/lib/bootloader.elf"
    install -D -m 444 "$build/epicardium/epicardium.elf" -t "$out/lib/epicardium.elf"
    install -D -m 444 "$build/pycardium/pycardium.elf" -t "$out/lib/pycardium.elf"
    # Create new flash contents
    install -D -m 444 "$build/pycardium/pycardium_epicardium.bin" "$out/card10/card10.bin"
    install -m 444 preload/*.py -t $out/card10/
    cp -ar preload/apps $out/card10/
  '';
}
+35 −0
Original line number Diff line number Diff line
import ws2812, gpio, bluetooth, time, display
from micropython import const

_IRQ_GATTS_WRITE = const(3)

WS2812_SERVICE_UUID = \
    bluetooth.UUID("23238000-2342-2342-2342-234223422342")
SET_ALL = (
    bluetooth.UUID("23238001-2342-2342-2342-234223422342"),
    bluetooth.FLAG_WRITE
)
WS2812_SERVICE = (
    WS2812_SERVICE_UUID,
    (SET_ALL,)
)

def irq(event, data):
    if event == _IRQ_GATTS_WRITE:
        conn_handle, value_handle = data
        value = ble.gatts_read(value_handle)
        ws2812.set_all(gpio.WRISTBAND_3, [value] * 3)

if __name__ == "__main__":
    display.open().backlight(0)
    gpio.set_mode(gpio.WRISTBAND_3, gpio.mode.OUTPUT)

    ble = bluetooth.BLE()
    ble.active(True)
    ble.irq(irq)
    ble.gatts_register_services((WS2812_SERVICE,))

    print("Waiting for connection!")

    while True:
        time.sleep(1)
+19 −0
Original line number Diff line number Diff line
#!/usr/bin/env python3
import bluepy
import time
import colorsys


# Change this to the MAC of your card10
p = bluepy.btle.Peripheral("CA:4D:10:01:ff:64")
c = p.getCharacteristics(
    uuid='23238001-2342-2342-2342-234223422342')[0]

hue = 0
while 1:
    r,g,b = colorsys.hsv_to_rgb(hue, 1, 0.1)
    c.write(b"%c%c%c" %
        (int(r*255), int(g*255), int(b*255)), True)
    time.sleep(.1)
    hue += 0.1
+10 −0
Original line number Diff line number Diff line
FROM ubuntu

RUN apt-get update && apt-get -y install gcc-arm-none-eabi binutils-arm-none-eabi libnewlib-arm-none-eabi python3 python3-pip ninja-build git

RUN pip3 install meson crc16 pillow

VOLUME /firmware
WORKDIR /firmware

CMD ./bootstrap.sh && ninja -C build && chown -R --reference=/firmware build
Original line number Diff line number Diff line
FROM ubuntu:bionic
FROM ubuntu:focal

RUN set -e -x ;\
    export DEBIAN_FRONTEND=noninteractive ;\
@@ -10,7 +10,7 @@ RUN set -e -x ;\
        llvm \
        python3-pip ;\
    pip3 install \
        clang \
        clang==10.0.1 \
        sphinx \
        sphinx_rtd_theme ;\
    rm -rf /var/lib/apt/lists
Original line number Diff line number Diff line
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H

#define  MXC_ASSERT_ENABLE
#include "mxc_assert.h"

#include "max32665.h"

#include <assert.h>

/* CMSIS keeps a global updated with current system clock in Hz */
#define configCPU_CLOCK_HZ          ((unsigned long)96000000)

@@ -51,7 +50,10 @@

#define INCLUDE_vTaskSuspend        1
#define INCLUDE_vTaskDelay          1
#define INCLUDE_vTaskDelete         1
#define INCLUDE_uxTaskGetStackHighWaterMark 1
#define INCLUDE_xTimerPendFunctionCall 1
#define INCLUDE_xSemaphoreGetMutexHolder 1

/* Allow static allocation of data structures */
#define configSUPPORT_STATIC_ALLOCATION 1
@@ -69,7 +71,7 @@
#define xPortSysTickHandler   SysTick_Handler

/* Assert */
#define configASSERT(x)       MXC_ASSERT(x)
#define configASSERT(x)       assert(x)

/* Tickless idle hooks */
typedef uint32_t TickType_t;
Original line number Diff line number Diff line
@@ -5,8 +5,17 @@
#define MXC_ASSERT_ENABLE
#include "mxc_assert.h"

static uint32_t irq_save = 0;

void *_api_call_start(api_id_t id, uintptr_t size)
{
	/*
	 * Disable all maskable interrupts here, to be turned on again at the
	 * end of _api_call_transact().
	 */
	irq_save = __get_PRIMASK();
	__set_PRIMASK(1);

	while (SEMA_GetSema(_API_SEMAPHORE) == E_BUSY) {
	}

@@ -51,6 +60,12 @@ void *_api_call_transact(void *buffer)
	API_CALL_MEM->call_flag = _API_FLAG_IDLE;
	SEMA_FreeSema(_API_SEMAPHORE);

	/*
	 * Re-enable interrupts (if previously enabled) after completing the API
	 * call.
	 */
	__set_PRIMASK(irq_save);

	return API_CALL_MEM->buffer;
}

@@ -109,7 +124,7 @@ int api_fetch_args(char *buf, size_t cnt)
		return 0;
	}

	int i;
	size_t i;
	for (i = 0; i < cnt && API_CALL_MEM->buffer[i + 0x20] != '\0'; i++) {
		buf[i] = API_CALL_MEM->buffer[i + 0x20];
	}
Original line number Diff line number Diff line
@@ -38,7 +38,7 @@ struct api_call_mem {
	api_id_t id;

	/* ID of the current interrupt */
	api_int_id_t int_id;
	volatile api_int_id_t int_id;

	/*
	 * Buffer for arguments/return value.  This buffer will be
Original line number Diff line number Diff line
#include "epicardium.h"
#include "api/dispatcher.h"
#include "api/interrupt-sender.h"
#include "modules/log.h"

#include "card10.h"

@@ -10,6 +8,7 @@
#include "tmr.h"

static void __core1_init(void);
extern void interrupt_trigger_sync(api_int_id_t id);

struct core1_info {
	/* Location of core1's interrupt vector table */
@@ -76,6 +75,11 @@ void __core1_init(void)
	 */
	TMR_IntClear(MXC_TMR5);

	/*
	 * Disable the SysTick
	 */
	SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk;

	/*
	 * Reset Interrupts
	 *
@@ -201,14 +205,19 @@ void core1_boot(void)

void core1_trigger_reset(void)
{
	/* Signal core 1 that we intend to load a new payload. */
	api_interrupt_trigger(EPIC_INT_RESET);
	/*
	 * Signal core 1 that we intend to load a new payload.
	 *
	 * This needs to be synchroneous because otherwise we will deadlock
	 * (Lifecycle task busy-spins and interrupt can never get dispatched).
	 */
	interrupt_trigger_sync(EPIC_INT_RESET);
}

void core1_wait_ready(void)
bool core1_is_ready(void)
{
	/* Wait for the core to accept */
	while (1) {
	bool ready;

	while (SEMA_GetSema(_CONTROL_SEMAPHORE) == E_BUSY) {
	}

@@ -216,12 +225,21 @@ void core1_wait_ready(void)
	 * core 1 will set the ready flag once it is spinning in the
	 * above loop, waiting for a new IVT.
	 */
		if (core1_info.ready) {
			break;
		}
	ready = core1_info.ready;

	SEMA_FreeSema(_CONTROL_SEMAPHORE);

	return ready;
}

void core1_wait_ready(void)
{
	/* Wait for the core to accept */
	while (1) {
		if (core1_is_ready()) {
			break;
		}

		for (int i = 0; i < 10000; i++) {
		}
	}
@@ -235,6 +253,9 @@ void core1_wait_ready(void)

void core1_load(void *ivt, char *args)
{
	while (SEMA_GetSema(_CONTROL_SEMAPHORE) == E_BUSY) {
	}

	/* If the core is currently in an API call, reset it. */
	API_CALL_MEM->call_flag = _API_FLAG_IDLE;
	API_CALL_MEM->id        = 0;
Original line number Diff line number Diff line
@@ -9,7 +9,7 @@
/* This function is defined by the generated dispatcher code */
void __api_dispatch_call(api_id_t id, void *buffer);

static volatile bool event_ready = false;
static volatile bool call_pending = false;

int api_dispatcher_init()
{
@@ -34,7 +34,7 @@ int api_dispatcher_init()

bool api_dispatcher_poll_once()
{
	if (event_ready) {
	if (call_pending) {
		return false;
	}

@@ -46,22 +46,27 @@ bool api_dispatcher_poll_once()
		return false;
	}

	event_ready = true;
	call_pending = true;
	return true;
}

bool api_dispatcher_poll()
{
	if (event_ready) {
	if (call_pending) {
		return true;
	}

	return api_dispatcher_poll_once();
}

bool api_dispatcher_call_pending()
{
	return call_pending;
}

api_id_t api_dispatcher_exec()
{
	if (!event_ready) {
	if (!call_pending) {
		return 0;
	}

@@ -69,7 +74,7 @@ api_id_t api_dispatcher_exec()
	__api_dispatch_call(id, API_CALL_MEM->buffer);
	API_CALL_MEM->call_flag = _API_FLAG_RETURNED;

	event_ready = false;
	call_pending = false;
	SEMA_FreeSema(_API_SEMAPHORE);

	/* Notify the caller that we returned */
@@ -86,7 +91,7 @@ void api_prepare_args(char *args)
	 * collide with any integer return value of API calls like epic_exec().
	 */
	API_CALL_MEM->id = 0;
	for (int i = 0; i <= strlen(args); i++) {
	for (size_t i = 0; i <= strlen(args); i++) {
		API_CALL_MEM->buffer[i + 0x20] = args[i];
	}
}
Original line number Diff line number Diff line
@@ -15,6 +15,12 @@ int api_dispatcher_init();
bool api_dispatcher_poll_once();
bool api_dispatcher_poll();

/*
 * Check if the other core requested a call or if we are already excuting it.
 * Only returns a cached version, without acquiring any locks.
 */
bool api_dispatcher_call_pending();

/*
 * Attempt to dispatch a call, if one had been polled using
 * api_dispatcher_poll().  Will return 0 if no call was dispatched or the ID of
@@ -39,6 +45,9 @@ void core1_boot(void);
/* Reset core 1 into a state where it can accept a new payload */
void core1_trigger_reset(void);

/* Check if core 1 is ready for a new payload */
bool core1_is_ready(void);

/* Wait for core 1 to respond that it is ready for a new payload */
void core1_wait_ready(void);

Original line number Diff line number Diff line
@@ -246,7 +246,7 @@ void __dispatch_isr(api_int_id_t id)
    # Generate Dispatcher {{{
    with open(args.server, "w") as f_dispatcher:
        tmp = """\
#include "modules/log.h"
#include "os/core.h"
#include "{header}"

void __api_dispatch_call(uint32_t id, void*epc__apistub_buffer)
Original line number Diff line number Diff line
#include "api/interrupt-sender.h"
#include "api/common.h"
#include "tmr_utils.h"

static bool int_enabled[EPIC_INT_NUM];

int api_interrupt_trigger(api_int_id_t id)
{
	if (id >= EPIC_INT_NUM) {
		return -EINVAL;
	}

	if (int_enabled[id]) {
		while (API_CALL_MEM->int_id != (-1))
			;

		API_CALL_MEM->int_id = id;
		TMR_TO_Start(MXC_TMR5, 1, 0);
	}
	return 0;
}
#include <assert.h>

void api_interrupt_init(void)
{
	API_CALL_MEM->int_id = (-1);

	for (int i = 0; i < EPIC_INT_NUM; i++) {
		int_enabled[i] = false;
}

	/* Reset interrupt is always enabled */
	int_enabled[EPIC_INT_RESET] = true;
}

int epic_interrupt_enable(api_int_id_t int_id)
bool api_interrupt_is_ready(void)
{
	if (int_id >= EPIC_INT_NUM) {
		return -EINVAL;
	return API_CALL_MEM->int_id == (api_int_id_t)(-1);
}

	int_enabled[int_id] = true;
	return 0;
}

int epic_interrupt_disable(api_int_id_t int_id)
void api_interrupt_trigger(api_int_id_t id)
{
	if (int_id >= EPIC_INT_NUM || int_id == EPIC_INT_RESET) {
		return -EINVAL;
	}
	assert(API_CALL_MEM->int_id == (api_int_id_t)(-1));

	int_enabled[int_id] = false;
	return 0;
	API_CALL_MEM->int_id = id;
	TMR_TO_Start(MXC_TMR5, 1, 0);
}
Original line number Diff line number Diff line
@@ -2,4 +2,5 @@
#include "api/common.h"

void api_interrupt_init(void);
int api_interrupt_trigger(api_int_id_t id);
bool api_interrupt_is_ready(void);
void api_interrupt_trigger(api_int_id_t id);

epicardium/ble/app/app_main.c

deleted100644 → 0
+0 −423
Original line number Diff line number Diff line
/*************************************************************************************************/
/*!
 *  \file
 *
 *  \brief  Application framework main module.
 *
 *  Copyright (c) 2011-2018 Arm Ltd. All Rights Reserved.
 *  ARM Ltd. confidential and proprietary.
 *
 *  IMPORTANT.  Your use of this file is governed by a Software License Agreement
 *  ("Agreement") that must be accepted in order to download or otherwise receive a
 *  copy of this file.  You may not use or copy this file for any purpose other than
 *  as described in the Agreement.  If you do not agree to all of the terms of the
 *  Agreement do not use this file and delete all copies in your possession or control;
 *  if you do not have a copy of the Agreement, you must contact ARM Ltd. prior
 *  to any use, copying or further distribution of this software.
 */
/*************************************************************************************************/

/* card10:
 * copied from: lib/sdk/Libraries/BTLE/stack/ble-profiles/sources/apps/app/app_main.c
 *
 * Reason: we need to correctly implement AppHandleNumericComparison
 */
/* clang-format off */
/* clang-formet turned off for easier diffing against orginal file */
#include <string.h>
#include "wsf_types.h"
#include "wsf_msg.h"
#include "sec_api.h"
#include "wsf_trace.h"
#include "wsf_timer.h"
#include "wsf_assert.h"
#include "util/bstream.h"
#include "dm_api.h"
#include "app_api.h"
#include "app_main.h"
#include "app_ui.h"

/**************************************************************************************************
  Global Variables
**************************************************************************************************/

/*! Configuration pointer for advertising */
appAdvCfg_t *pAppAdvCfg;

/*! Configuration pointer for extended and periodic advertising */
appExtAdvCfg_t *pAppExtAdvCfg;

/*! Configuration pointer for slave */
appSlaveCfg_t *pAppSlaveCfg;

/*! Configuration pointer for master */
appMasterCfg_t *pAppMasterCfg;

/*! Configuration pointer for extended master */
appExtMasterCfg_t *pAppExtMasterCfg;

/*! Configuration pointer for security */
appSecCfg_t *pAppSecCfg;

/*! Configuration pointer for connection parameter update */
appUpdateCfg_t *pAppUpdateCfg;

/*! Configuration pointer for discovery */
appDiscCfg_t *pAppDiscCfg;

/*! Configuration pointer for application */
appCfg_t *pAppCfg;

/*! Connection control block array */
appConnCb_t appConnCb[DM_CONN_MAX];

/*! WSF handler ID */
wsfHandlerId_t appHandlerId;

/*! Main control block */
appCb_t appCb;

/*! Configuration structure for incoming request actions */
const appReqActCfg_t appReqActCfg =
{
  APP_ACT_ACCEPT        /*! Action for the remote connection parameter request */
};

/*! Configuration pointer for incoming request actions on master */
appReqActCfg_t *pAppMasterReqActCfg = (appReqActCfg_t *) &appReqActCfg;

/*! Configurable pointer for incoming request actions on slave */
appReqActCfg_t *pAppSlaveReqActCfg = (appReqActCfg_t *) &appReqActCfg;

/*************************************************************************************************/
/*!
 *  \brief  Process messages from the event handler.
 *
 *  \param  pMsg    Pointer to message.
 *
 *  \return None.
 */
/*************************************************************************************************/
static void appProcMsg(wsfMsgHdr_t *pMsg)
{
  switch(pMsg->event)
  {
    case APP_BTN_POLL_IND:
      appUiBtnPoll();
      break;

    case APP_UI_TIMER_IND:
      appUiTimerExpired(pMsg);
      break;

    default:
      break;
  }
}

/*************************************************************************************************/
/*!
 *  \brief  Check the bonded state of a connection.
 *
 *  \param  connId      DM connection ID.
 *
 *  \return Bonded state.
 */
/*************************************************************************************************/
bool_t appCheckBonded(dmConnId_t connId)
{
  WSF_ASSERT((connId > 0) && (connId <= DM_CONN_MAX));

  return appConnCb[connId - 1].bonded;
}

/*************************************************************************************************/
/*!
 *  \brief  Check the bond-by-LTK state of a connection.
 *
 *  \param  connId      DM connection ID.
 *
 *  \return Bond-by-LTK state.
 */
/*************************************************************************************************/
bool_t appCheckBondByLtk(dmConnId_t connId)
{
  WSF_ASSERT((connId > 0) && (connId <= DM_CONN_MAX));

  return appConnCb[connId - 1].bondByLtk;
}

/*************************************************************************************************/
/*!
 *  \brief  Return the number of existing connections of the given role.
 *
 *  \param  role      Connection role
 *
 *  \return Number of connections.
 */
/*************************************************************************************************/
uint8_t appNumConns(uint8_t role)
{
  appConnCb_t   *pCcb = appConnCb;
  uint8_t       i, j;

  for (i = DM_CONN_MAX, j = 0; i > 0; i--, pCcb++)
  {
    if ((pCcb->connId != DM_CONN_ID_NONE) && (DmConnRole(pCcb->connId) == role))
    {
      j++;
    }
  }

  return j;
}

/*************************************************************************************************/
/*!
 *  \brief  App framework handler init function called during system initialization.
 *
 *  \param  handlerID  WSF handler ID for App.
 *
 *  \return None.
 */
/*************************************************************************************************/
void AppInit(void)
{
  appHandlerId = WsfOsSetNextHandler(AppHandler);

  AppDbInit();
}

/*************************************************************************************************/
/*!
 *  \brief  WSF event handler for app framework.
 *
 *  \param  event   WSF event mask.
 *  \param  pMsg    WSF message.
 *
 *  \return None.
 */
/*************************************************************************************************/
void AppHandler(wsfEventMask_t event, wsfMsgHdr_t *pMsg)
{
  if (pMsg != NULL)
  {
    APP_TRACE_INFO1("App got evt %d", pMsg->event);

    if (pMsg->event >= APP_MASTER_MSG_START)
    {
      /* pass event to master handler */
      (*appCb.masterCback)(pMsg);
    }
    else if (pMsg->event >= APP_SLAVE_MSG_START)
    {
      /* pass event to slave handler */
      (*appCb.slaveCback)(pMsg);
    }
    else
    {
      appProcMsg(pMsg);
    }
  }
  else
  {
    if (event & APP_BTN_DOWN_EVT)
    {
      AppUiBtnPressed();
    }
  }
}

/*************************************************************************************************/
/*!
 *  \brief  Handle a passkey request during pairing.  If the passkey is to displayed, a
 *          random passkey is generated and displayed.  If the passkey is to be entered
 *          the user is prompted to enter the passkey.
 *
 *  \param  pAuthReq  DM authentication requested event structure.
 *
 *  \return None.
 */
/*************************************************************************************************/
void AppHandlePasskey(dmSecAuthReqIndEvt_t *pAuthReq)
{
  uint32_t passkey;
  uint8_t  buf[SMP_PIN_LEN];

  if (pAuthReq->display)
  {
    /* generate random passkey, limit to 6 digit max */
    SecRand((uint8_t *) &passkey, sizeof(uint32_t));
    passkey %= 1000000;

    /* convert to byte buffer */
    buf[0] = UINT32_TO_BYTE0(passkey);
    buf[1] = UINT32_TO_BYTE1(passkey);
    buf[2] = UINT32_TO_BYTE2(passkey);

    /* send authentication response to DM */
    DmSecAuthRsp((dmConnId_t) pAuthReq->hdr.param, SMP_PIN_LEN, buf);

    /* display passkey */
    AppUiDisplayPasskey(passkey);
  }
  else
  {
    /* prompt user to enter passkey */
    AppUiAction(APP_UI_PASSKEY_PROMPT);
  }
}

/*************************************************************************************************/
/*!
*  \brief  Handle a numeric comparison indication during pairing.  The confirmation value is
*          displayed and the user is prompted to verify that the local and peer confirmation
*          values match.
*
*  \param  pCnfInd  DM confirmation indication event structure.
*
*  \return None.
*/
/*************************************************************************************************/
void AppHandleNumericComparison(dmSecCnfIndEvt_t *pCnfInd)
{
  uint32_t confirm = DmSecGetCompareValue(pCnfInd->confirm);

  /* display confirmation value */
  AppUiDisplayConfirmValue(confirm);

  /* TODO: Verify that local and peer confirmation values match */
  DmSecCompareRsp((dmConnId_t)pCnfInd->hdr.param, TRUE);
}

/*************************************************************************************************/
/*!
 *  \brief  Close the connection with the give connection identifier.
 *
 *  \param  connId    Connection identifier.
 *
 *  \return None.
 */
/*************************************************************************************************/
void AppConnClose(dmConnId_t connId)
{
  DmConnClose(DM_CLIENT_ID_APP, connId, HCI_ERR_REMOTE_TERMINATED);
}

/*************************************************************************************************/
/*!
 *  \brief  Get a list of connection identifiers of open connections.
 *
 *  \param  pConnIdList    Buffer to hold connection IDs (must be DM_CONN_MAX bytes).
 *
 *  \return Number of open connections.
 *
 */
/*************************************************************************************************/
uint8_t AppConnOpenList(dmConnId_t *pConnIdList)
{
  appConnCb_t   *pCcb = appConnCb;
  uint8_t       i;
  uint8_t       pos = 0;

  memset(pConnIdList, DM_CONN_ID_NONE, DM_CONN_MAX);

  for (i = DM_CONN_MAX; i > 0; i--, pCcb++)
  {
    if (pCcb->connId != DM_CONN_ID_NONE)
    {
      pConnIdList[pos++] = pCcb->connId;
    }
  }

  return pos;
}

/*************************************************************************************************/
/*!
 *  \brief  Check if a connection is open.
 *
 *  \return Connection ID of open connection or DM_CONN_ID_NONE if no open connections.
 */
/*************************************************************************************************/
dmConnId_t AppConnIsOpen(void)
{
  appConnCb_t   *pCcb = appConnCb;
  uint8_t       i;

  for (i = DM_CONN_MAX; i > 0; i--, pCcb++)
  {
    if (pCcb->connId != DM_CONN_ID_NONE)
    {
      return pCcb->connId;
    }
  }

  return DM_CONN_ID_NONE;
}

/*************************************************************************************************/
/*!
 *  \brief  Get the device database record handle associated with an open connection.
 *
 *  \param  connId    Connection identifier.
 *
 *  \return Database record handle or APP_DB_HDL_NONE.
 */
/*************************************************************************************************/
appDbHdl_t AppDbGetHdl(dmConnId_t connId)
{
  return appConnCb[connId-1].dbHdl;
}

/*************************************************************************************************/
/*!
 *  \brief  Add device to resolving list.
 *
 *  \param  pMsg    Pointer to DM callback event message.
 *  \param  connId  Connection identifier.
 *
 *  \return None.
 */
/*************************************************************************************************/
void AppAddDevToResList(dmEvt_t *pMsg, dmConnId_t connId)
{
  dmSecKey_t *pPeerKey;
  appDbHdl_t hdl = appConnCb[connId - 1].dbHdl;

  /* if LL Privacy is supported and the peer device has distributed its IRK */
  if (HciLlPrivacySupported() && ((pPeerKey = AppDbGetKey(hdl, DM_KEY_IRK, NULL))!= NULL))
  {
    /* add peer device to resolving list. If all-zero local or peer IRK is used then
       LL will only use or accept local or peer identity address respectively. */
    DmPrivAddDevToResList(pPeerKey->irk.addrType, pPeerKey->irk.bdAddr, pPeerKey->irk.key,
                          DmSecGetLocalIrk(), TRUE, pMsg->hdr.param);
  }
}

/*************************************************************************************************/
/*!
 *  \brief  Update privacy mode for a given peer device.
 *
 *  \param  hdl     Database record handle.
 *
 *  \return None.
 */
/*************************************************************************************************/
void AppUpdatePrivacyMode(appDbHdl_t hdl)
{
  /* if peer device's been added to resolving list but RPA Only attribute not found on peer device */
  if ((hdl != APP_DB_HDL_NONE) && AppDbGetPeerAddedToRl(hdl) && !AppDbGetPeerRpao(hdl))
  {
    dmSecKey_t *pPeerKey = AppDbGetKey(hdl, DM_KEY_IRK, NULL);
    if (pPeerKey != NULL)
    {
      /* set device privacy mode for this peer device */
      DmPrivSetPrivacyMode(pPeerKey->irk.addrType, pPeerKey->irk.bdAddr, DM_PRIV_MODE_DEVICE);

      /* make sure resolving list flag cleared */
      AppDbSetPeerAddedToRl(hdl, FALSE);
    }
  }
}
/* clang-format on */

File changed.

Preview size limit exceeded, changes collapsed.

+264 −0
Original line number Diff line number Diff line
#include "ble_api.h"
#include "epicardium.h"
#include "os/core.h"
#include "os/config.h"

#include "wsf_types.h"
#include "util/bstream.h"
#include "wsf_msg.h"
#include "wsf_trace.h"
#include "app_api.h"
#include "app_db.h"
#include "svc_ch.h"
#include "profiles/gap_api.h"

#include <stdio.h>
#include <string.h>

#define DEFAULT_ADV_INTERVAL_US 500000

/*! configurable parameters for advertising */
static appAdvCfg_t bleAdvCfg = {
	{ 0, 0 }, /*! Advertising durations in ms */
	{ DEFAULT_ADV_INTERVAL_US / 625,
	  0 } /*! Advertising intervals in 0.625 ms units */
};

static bool tainted;

/**************************************************************************************************
  Advertising Data
**************************************************************************************************/

/* clang-format off */
/*! advertising data, discoverable mode */
static const uint8_t bleAdvDataDisc[] = {
	/*! flags */
	2,                        /*! length */
	DM_ADV_TYPE_FLAGS,        /*! AD type */
	DM_FLAG_LE_LIMITED_DISC | /*! flags */
	DM_FLAG_LE_BREDR_NOT_SUP,

	3,
	DM_ADV_TYPE_APPEARANCE,
	UINT16_TO_BYTES(CH_APPEAR_WATCH),

	/*! service UUID list */
	17,
	DM_ADV_TYPE_128_UUID_PART,
	CARD10_UUID_SUFFIX,
	0x0,
	CARD10_UUID_PREFIX,

	2,                    /*! length */
	DM_ADV_TYPE_TX_POWER, /*! AD type */
	0,                    /*! tx power */
};

/*! advertising data, discoverable mode with HID service*/
static const uint8_t bleAdvDataDiscHID[] = {
	/*! flags */
	2,                        /*! length */
	DM_ADV_TYPE_FLAGS,        /*! AD type */
	DM_FLAG_LE_LIMITED_DISC | /*! flags */
	DM_FLAG_LE_BREDR_NOT_SUP,

	3,
	DM_ADV_TYPE_APPEARANCE,
	UINT16_TO_BYTES(CH_APPEAR_WATCH),

	/*! service UUID list */
	17,
	DM_ADV_TYPE_128_UUID_PART,
	CARD10_UUID_SUFFIX,
	0x0,
	CARD10_UUID_PREFIX,

	3,                        /*! length */
	DM_ADV_TYPE_16_UUID_PART, /*! AD type */
	UINT16_TO_BYTES(ATT_UUID_HID_SERVICE)
};

/*! scan data, discoverable mode */
uint8_t bleScanDataDisc[] = {
	/*! device name */
	14,                     /*! length */
	DM_ADV_TYPE_LOCAL_NAME, /*! AD type */
	'c','a','r','d','1','0','-','0','0','0','0','0','0',

	3,                      /*! length */
	DM_ADV_TYPE_16_SOLICIT, /*! AD type */
	UINT16_TO_BYTES(ATT_UUID_CURRENT_TIME_SERVICE),
};
/* clang-format on */

/*! advertising data, connectable mode */
static const uint8_t bleAdvDataConn[] = {
	/*! flags */
	2,                 /*! length */
	DM_ADV_TYPE_FLAGS, /*! AD type */
	DM_FLAG_LE_BREDR_NOT_SUP,
};

static uint8_t advertising_mode        = APP_MODE_NONE;
static uint8_t advertising_mode_target = APP_MODE_NONE;

void ble_adv_proc_msg(bleMsg_t *pMsg)
{
	switch (pMsg->hdr.event) {
	case DM_ADV_START_IND:
		LOG_INFO(
			"ble",
			"Advertisement started %u %u",
			advertising_mode,
			advertising_mode_target
		);
		if (advertising_mode != advertising_mode_target ||
		    advertising_mode_target == APP_MODE_NONE) {
			AppAdvStop();
		}
		break;

	case DM_ADV_STOP_IND:
		LOG_INFO(
			"ble",
			"Advertisement stopped %u %u",
			advertising_mode,
			advertising_mode_target
		);
		if (advertising_mode != advertising_mode_target) {
			advertising_mode = advertising_mode_target;
			AppAdvStart(advertising_mode);
		}
		break;
	case DM_CONN_CLOSE_IND:
		/* Stack overwrites advertising mode after connection close.
		 * Force our desired mode.
		 */
		advertising_mode = APP_MODE_NONE;
		AppAdvStop();
		break;
	};
}

void ble_adv_init(void)
{
	char buf[32];
	char a, b, c, d, e, f, K;

	/* clang-format off */
	int result = epic_config_get_string("ble_mac", buf, sizeof(buf));
	if (result == 0) {
		if (sscanf(buf,
			   "%c%c:%c%c:%c%c:%c%c:%c%c:%c%c",
			   &K, &K, &K, &K, &K, &K,
			   &a, &b, &c, &d, &e, &f) == 12) {
			bleScanDataDisc[9]  = a;
			bleScanDataDisc[10] = b;
			bleScanDataDisc[11] = c;
			bleScanDataDisc[12] = d;
			bleScanDataDisc[13] = e;
			bleScanDataDisc[14] = f;
		}
	}
	/* clang-format on */

	pAppAdvCfg = (appAdvCfg_t *)&bleAdvCfg;
}

void ble_adv_setup(void)
{
	/* set advertising and scan response data for discoverable mode */
	if (config_get_boolean_with_default("ble_hid_enable", false)) {
		AppAdvSetData(
			APP_ADV_DATA_DISCOVERABLE,
			sizeof(bleAdvDataDiscHID),
			(uint8_t *)bleAdvDataDiscHID
		);
	} else {
		AppAdvSetData(
			APP_ADV_DATA_DISCOVERABLE,
			sizeof(bleAdvDataDisc),
			(uint8_t *)bleAdvDataDisc
		);
	}

	AppAdvSetData(
		APP_SCAN_DATA_DISCOVERABLE,
		sizeof(bleScanDataDisc),
		(uint8_t *)bleScanDataDisc
	);

	/* set advertising and scan response data for connectable mode */
	AppAdvSetData(
		APP_ADV_DATA_CONNECTABLE,
		sizeof(bleAdvDataConn),
		(uint8_t *)bleAdvDataConn
	);
	AppAdvSetData(APP_SCAN_DATA_CONNECTABLE, 0, NULL);

	bleAdvCfg.advInterval[0] = DEFAULT_ADV_INTERVAL_US / 625;
}

void ble_adv_set_interval(uint32_t interval_us)
{
	bleAdvCfg.advInterval[0] = interval_us / 625;
	tainted                  = true;
}

void ble_adv_stop(void)
{
	if (advertising_mode != APP_MODE_NONE) {
		advertising_mode_target = APP_MODE_NONE;
		advertising_mode        = APP_MODE_NONE;
		AppAdvStop();
	}
}

static void adv_start(uint8_t mode)
{
	if (advertising_mode != APP_MODE_NONE) {
		/* We need to stop advertising in between or the
		* adv set will not be changed.
		* Also need to wait for the stop operation to finish
		* before we can start again
		* Also need to set the variables first as we don't
		* have a lock on the stack.*/
		advertising_mode_target = mode;
		advertising_mode        = APP_MODE_NONE;
		AppAdvStop();
	} else {
		advertising_mode        = mode;
		advertising_mode_target = mode;
		AppAdvStart(advertising_mode);
	}
	tainted = false;
}

void ble_adv_start(uint8_t mode)
{
	adv_start(mode);
	tainted = true;
}

void ble_adv_discoverable(bool discoverable)
{
	if (discoverable) {
		if (advertising_mode != APP_MODE_DISCOVERABLE || tainted) {
			LOG_INFO("ble", "Making bondable and discoverable");
			adv_start(APP_MODE_DISCOVERABLE);
		}
	} else {
		/* TODO: This does way more than the function name indicates */
		if (AppDbCheckBonded()) {
			if (advertising_mode != APP_MODE_CONNECTABLE ||
			    tainted) {
				LOG_INFO("ble", "Bonded. Making connectable");
				adv_start(APP_MODE_CONNECTABLE);
			}
		} else {
			LOG_INFO("ble", "Not bonded. Stop advertising");
			ble_adv_stop();
		}
	}
}

epicardium/ble/cccd.h

0 → 100644
+19 −0
Original line number Diff line number Diff line
#pragma once
/*! enumeration of client characteristic configuration descriptors */
enum
{
  BLE_GATT_SC_CCC_IDX,                    /*! GATT service, service changed characteristic */
  BLE_BATT_LVL_CCC_IDX,                   /*! Battery service, battery level characteristic */
  BLE_ESS_TEMP_CCC_IDX,                   /*! Environmental sensing service, temperature characteristic */
  BLE_ESS_HUMI_CCC_IDX,                   /*! Environmental sensing service, humidity characteristic */
  BLE_ESS_PRES_CCC_IDX,                   /*! Environmental sensing service, pressure characteristic */
  HIDAPP_MBI_CCC_HDL,                   /*! HID Boot Mouse Input characteristic */
  HIDAPP_KBI_CCC_HDL,                   /*! HID Boot Keyboard Input characteristic */
  HIDAPP_IN_KEYBOARD_CCC_HDL,           /*! HID Input Report characteristic for keyboard inputs */
  HIDAPP_IN_MOUSE_CCC_HDL,              /*! HID Input Report characteristic for mouse inputs */
  HIDAPP_IN_CONSUMER_CCC_HDL,             /*! HID Input Report characteristic for consumer control inputs */
  BLE_ESS_IAQ_CCC_IDX,                    /*! Environmental sensing service, IAQ characteristic */
  UART_TX_CH_CCC_IDX,
  BLE_NUM_CCC_IDX
};

epicardium/ble/ess.c

0 → 100644
+440 −0

File added.

Preview size limit exceeded, changes collapsed.

epicardium/ble/ess.h

0 → 100644
+36 −0

File added.

Preview size limit exceeded, changes collapsed.

epicardium/ble/hid.c

0 → 100644
+221 −0

File added.

Preview size limit exceeded, changes collapsed.

epicardium/ble/hid.h

0 → 100644
+20 −0

File added.

Preview size limit exceeded, changes collapsed.

+155 −112

File changed.

Preview size limit exceeded, changes collapsed.

epicardium/ble/uart.h

0 → 100644
+19 −0

File added.

Preview size limit exceeded, changes collapsed.

epicardium/cdcacm.c

deleted100644 → 0
+0 −382

File deleted.

Preview size limit exceeded, changes collapsed.

epicardium/cdcacm.h

deleted100644 → 0
+0 −10
Original line number Diff line number Diff line
#ifndef CDCACM_H
#define CDCACM_H
#include <stdint.h>

int cdcacm_init(void);
int cdcacm_num_read_avail(void);
uint8_t cdcacm_read(void);
void cdcacm_write(uint8_t *data, int len);

#endif

epicardium/descriptors.h

deleted100644 → 0
+0 −227

File deleted.

Preview size limit exceeded, changes collapsed.

+566 −0

File added.

Preview size limit exceeded, changes collapsed.

+97 −0

File added.

Preview size limit exceeded, changes collapsed.

+129 −0

File added.

Preview size limit exceeded, changes collapsed.

+334 −0

File added.

Preview size limit exceeded, changes collapsed.

epicardium/epic_boot.c

0 → 100644
+53 −0

File added.

Preview size limit exceeded, changes collapsed.

+92 −0

File added.

Preview size limit exceeded, changes collapsed.