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
  • TilCreator/firmware-master
  • blinkisync-as-preload
  • ch3/api-speed-eval2
  • ch3/dual-core
  • ch3/genapi-refactor
  • ch3/leds-api
  • ch3/splashscreen
  • dualcore
  • freertos-btle
  • genofire/ble-follow-py
  • genofire/haule-ble-fs-deactive
  • genofire/leds_rgb_get_state
  • genofire/rockets-state
  • hauke/ble-cleanups
  • ios-workarounds
  • koalo/bhi160-works-but-dirty
  • koalo/factory-reset
  • koalo/wip/i2c-for-python
  • master
  • msgctl/faultscreen
  • msgctl/gfx_rle
  • msgctl/textbuffer_api
  • plaetzchen/ios-workaround
  • rahix/bhi
  • rahix/bma
  • rahix/simple_menu
  • renze/hatchery_apps
  • renze/safe_mode
  • schleicher-test
  • schneider/ble-buffers
  • schneider/bonding
  • schneider/bootloader-update-9a0d158
  • schneider/bsec
  • schneider/fundamental-test
  • schneider/max30001
  • schneider/max30001-epicaridum
  • schneider/max30001-pycardium
  • schneider/maxim-sdk-update
  • schneider/mp-for-old-bl
  • schneider/schleicher-test
  • schneider/stream-locks
  • sonopard/display-pixels-drawimage
  • bootloader-v1
  • release-1
  • v0.0
  • v1.0
  • v1.1
  • v1.2
  • v1.3
  • v1.4
  • v1.5
  • v1.6
  • v1.7
  • v1.8
54 results
Show changes
1000 files
+ 373506
3360
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 -Ihw-tests/bmatest -Ihw-tests/bmetest -Ihw-tests/ecgtest -Ihw-tests/hello-freertos -Ihw-tests/hello-freertos/./ -Ihw-tests/hello-world -Ihw-tests/imutest -Ihw-tests/ips -Ilib/./card10/./ -Ilib/card10 -Ilib/./gfx/./ -Ilib/gfx -Ilib/./gfx/./Fonts/ -Ilib/./gfx/./GUI_DEV/ -Ilib/./gfx/./LCD/ -Ilib/sdk/Libraries/Boards/card10 -Ilib/./sdk/./Libraries/Boards/card10/../Include/ -Ilib/./sdk/./Libraries/Boards/card10/./Include/ -Ilib/sdk/Libraries/CMSIS/Device/Maxim/MAX32665 -Ilib/./sdk/./Libraries/FreeRTOS/../FreeRTOS-Plus/Source/FreeRTOS-Plus-CLI/ -Ilib/./sdk/./Libraries/FreeRTOS/./Source/include/ -Ilib/./sdk/./Libraries/FreeRTOS/./Source/portable/GCC/ARM_CM4F/ -Ilib/sdk/Libraries/MAX32665PeriphDriver -Ilib/./sdk/./Libraries/MAX32665PeriphDriver/../CMSIS/Device/Maxim/MAX32665/Include/ -Ilib/./sdk/./Libraries/MAX32665PeriphDriver/../CMSIS/Include/ -Ilib/./sdk/./Libraries/MAX32665PeriphDriver/Include/ -Ilib/vendor/Bosch/BHy1 -Ilib/./vendor/Bosch/BHy1/../../../card10/ -Ilib/./vendor/Bosch/BHy1/./driver/inc/ -Ilib/./vendor/Bosch/BHy1/./examples/firmware/ -Ilib/./vendor/Bosch/BMA400/./ -Ilib/vendor/Bosch/BMA400 -Ilib/./vendor/Bosch/BME680/./ -Ilib/vendor/Bosch/BME680 -Ilib/./vendor/Maxim/MAX77650/./ -Ilib/vendor/Maxim/MAX77650
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

.clang-format

0 → 100644
+121 −0
Original line number Diff line number Diff line
# SPDX-License-Identifier: GPL-2.0
#
# (Adapted from the Linux kernel sources)
#
# clang-format configuration file. Intended for clang-format >= 4.
#
# For more information, see:
#
#   Documentation/process/clang-format.rst
#   https://clang.llvm.org/docs/ClangFormat.html
#   https://clang.llvm.org/docs/ClangFormatStyleOptions.html
#
---
AccessModifierOffset: -4
AlignAfterOpenBracket: AlwaysBreak
AlignConsecutiveAssignments: true
AlignConsecutiveDeclarations: false
#AlignEscapedNewlines: Left # Unknown to clang-format-4.0
AlignOperands: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
# AllowAllArgumentsOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: false
BinPackArguments: false
BinPackParameters: false
BraceWrapping:
  AfterClass: false
  AfterControlStatement: false
  AfterEnum: false
  AfterFunction: true
  AfterNamespace: true
  AfterObjCDeclaration: false
  AfterStruct: false
  AfterUnion: false
  #AfterExternBlock: false # Unknown to clang-format-5.0
  BeforeCatch: false
  BeforeElse: false
  IndentBraces: false
  #SplitEmptyFunction: true # Unknown to clang-format-4.0
  #SplitEmptyRecord: true # Unknown to clang-format-4.0
  #SplitEmptyNamespace: true # Unknown to clang-format-4.0
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Custom
#BreakBeforeInheritanceComma: false # Unknown to clang-format-4.0
BreakBeforeTernaryOperators: false
BreakConstructorInitializersBeforeComma: false
#BreakConstructorInitializers: BeforeComma # Unknown to clang-format-4.0
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: false
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
#CompactNamespaces: false # Unknown to clang-format-4.0
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 8
ContinuationIndentWidth: 8
Cpp11BracedListStyle: false
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
#FixNamespaceComments: false # Unknown to clang-format-4.0

#IncludeBlocks: Preserve # Unknown to clang-format-5.0
IncludeCategories:
  - Regex: '.*'
    Priority: 1
IncludeIsMainRegex: '(Test)?$'
IndentCaseLabels: false
#IndentPPDirectives: None # Unknown to clang-format-5.0
IndentWidth: 8
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: Inner
#ObjCBinPackProtocolList: Auto # Unknown to clang-format-5.0
ObjCBlockIndentWidth: 8
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: true

# Taken from git's rules
#PenaltyBreakAssignment: 10 # Unknown to clang-format-4.0
PenaltyBreakBeforeFirstCallParameter: 30
PenaltyBreakComment: 10
PenaltyBreakFirstLessLess: 0
PenaltyBreakString: 10
PenaltyExcessCharacter: 100
PenaltyReturnTypeOnItsOwnLine: 60

PointerAlignment: Right
ReflowComments: false
SortIncludes: false
#SortUsingDeclarations: false # Unknown to clang-format-4.0
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
#SpaceBeforeCtorInitializerColon: true # Unknown to clang-format-5.0
#SpaceBeforeInheritanceColon: true # Unknown to clang-format-5.0
SpaceBeforeParens: ControlStatements
#SpaceBeforeRangeBasedForLoopColon: true # Unknown to clang-format-5.0
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp03
TabWidth: 8
UseTab: ForContinuationAndIndentation
...

.clang-tidy

0 → 100644
+34 −0
Original line number Diff line number Diff line
---
Checks:          'clang-diagnostic-*,clang-analyzer-*,bugprone-*'
WarningsAsErrors: ''
HeaderFilterRegex: ''
AnalyzeTemporaryDtors: false
FormatStyle:     none
User:            swym
CheckOptions:    
  - key:             cert-dcl16-c.NewSuffixes
    value:           'L;LL;LU;LLU'
  - key:             cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic
    value:           '1'
  - key:             google-readability-braces-around-statements.ShortStatementLines
    value:           '1'
  - key:             google-readability-function-size.StatementThreshold
    value:           '800'
  - key:             google-readability-namespace-comments.ShortNamespaceLines
    value:           '10'
  - key:             google-readability-namespace-comments.SpacesBeforeComments
    value:           '2'
  - key:             modernize-loop-convert.MaxCopySize
    value:           '16'
  - key:             modernize-loop-convert.MinConfidence
    value:           reasonable
  - key:             modernize-loop-convert.NamingStyle
    value:           CamelCase
  - key:             modernize-pass-by-value.IncludeStyle
    value:           llvm
  - key:             modernize-replace-auto-ptr.IncludeStyle
    value:           llvm
  - key:             modernize-use-nullptr.NullMacros
    value:           'NULL'
...

.gdbinit

deleted100644 → 0
+0 −5
Original line number Diff line number Diff line
target remote localhost:3333

define reset
    mon mww 0x40000004 0x80000000
end
+9 −2
Original line number Diff line number Diff line
build/
card10.bin
/build/
/Documentation/output/
__pycache__/
*.pyc
.*.swp
*~
compile_commands.json
/tags
/release-*/
+66 −9
Original line number Diff line number Diff line
image: "debian"
# maintained by q3k, built using docker/build-env
image: "derq3k/card10-build-env:20190806-195837Z-f95b541-dirty"

build:
    stage: build
    before_script:
        - echo "deb http://deb.debian.org/debian stretch-backports main" >> /etc/apt/sources.list
        - apt update -qq
        - apt install -y -qq gcc-arm-none-eabi python3-pip git
        - apt install -y -qq -t stretch-backports meson
        - pip3 install crc16
    script:
        - ./bootstrap.sh
    script:
        - 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:
        - 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
            - build/pycardium/pycardium.elf
            - build/pycardium/pycardium_epicardium.bin

lint:
    stage: test
    image: "derq3k/card10-lint-env:20190806-201106Z-f95b541-dirty"
    script:
        # Annoyatron is maintained by q3k. Its job is to serve MR comments that are friendlier than just a failing pipeline.
        #  source code: https://git.card10.badge.events.ccc.de/q3k/annoyatron/
        #  prod deployment: https://gerrit.hackerspace.pl/plugins/gitiles/hscloud/+/refs/heads/master/personal/q3k/annoyatron/
        # 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 -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
    only:
        - merge_requests

pages:
    stage: deploy
    # maintaned by q3k, build using docker/deploy-env
    image: "registry.k0.hswaw.net/q3k/card10-deploy-env:20210403-110003Z-4d929ee0"
    script:
        - export LD_LIBRARY_PATH=$(llvm-config --libdir)
        - echo $LD_LIBRARY_PATH
        - sphinx-build -b html Documentation/ Documentation/output/
        - mv Documentation/output/ public/
    artifacts:
        paths:
            - public/
    only:
        - master

.gitmodules

0 → 100644
+15 −0
Original line number Diff line number Diff line
[submodule "lib/micropython/micropython"]
	path = lib/micropython/micropython
	url = https://github.com/micropython/micropython.git
[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.

COPYING

0 → 100644
+19 −0
Original line number Diff line number Diff line
Copyright 2019 card10-firmware contributors

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+247 −0
Original line number Diff line number Diff line
.. _bluetooth_card10_service:

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

.. warning::
    The specification is still work in progress

The Card10 Service implemented a direct Hardware access of the card10.

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

The current draft uses following service specification:

- Service

  UUID: ``42230200-2342-2342-2342-234223422342``

- Time update characteristic:

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

- Vibra characteristic:

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

- Rockets characteristic:

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

- Background LED Bottom Left characteristic:

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

- Background LED Bottom Right characteristic:

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

- Background LED Top Right characteristic:

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

- Background LED Top Left characteristic:

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

- LEDS dim bottom characteristic:

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

- LEDs dim top characteristic:

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

- LEDs powersafe characteristic:

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

- Flashlight characteristic:

  UUID: ``42230218-2342-2342-2342-234223422342``
  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``
  read and write no reponse

- Light sensor characteristic:

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

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

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``

Vibra characteristic
---------------------------------

The vibra characteristic makes it possible to let the card10 for given millisecound in a ``uint16`` vibrate.

- One secound: ``0xe803``

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:

======= ======= =======
   0       1       2
------- ------- -------
Rocket0 Rocket1 Rocket2
======= ======= =======

- 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 or read the current value.

Dataformat:

===== ======= =======
  0      1       2
----- ------- -------
 red   green   blue
===== ======= =======

- set led blue: ``0x0000ff``
- disabled:  ``0x000000``

LEDs dim <Position> characteristic
----------------------------------

The LEDs dim <Position> characteristic makes it possible to dim LEDs by position.
Just write a ``uint8`` between ``1`` and ``8``.

LEDs powersafe characteristic
---------------------------------

This characteristic makes it possible to set the LEDs in powersafe mode.
Even when set to zero, the RGB LEDs still individually consume ~1mA.
Powersave intelligently switches the supply power in groups.
This introduces delays in the magnitude of ~10µs, so it can be disabled for high speed applications such as POV

- enabled:   ``0x01``
- disabled:  ``0x00``

Flashlight characteristic
---------------------------------

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 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``

Light sensor characteristic
---------------------------------

The light sensor characteristic makes it possible to read the current value of the light sensor by receiving a ``uint16``.
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
=======================

.. warning::
    The file transfer specification is still work in progress

File transfer to the card10 is implemented using the custom Low Effort File
Transfer Protocol.

BLE Service
-----------
The service consists of two GATT characteristics that act as a bidrectional
link, similar to many BLE UART implementations. The two channels are seen from
the Central perspective and hence named Central TX and Central RX.

The current version uses following service specification:

- Service

  UUID: 42230100-2342-2342-2342-234223422342

- Central TX characteristic:

  UUID: 42230101-2342-2342-2342-234223422342
  write

- Central RX characteristic:

  UUID 42230102-2342-2342-2342-234223422342
  read, notify

Low Effort File Transfer Protocol
---------------------------------
(Version 1)

This protocol was designed to strike a balance between ease of implementation
and reasonable levels of functionality.

Features:

- File push from Central (e.g. Android) to Peripheral (card10)
- Path and file name support
- Chunked data transfer for variable MTUs
- CRC32 error-detection
- Basic error handling

All communication between Central and Peripheral is packet based. The first
byte specifies the packet type using a char followed by an optional CRC and/or
payload, depending on the packet type.

START:

===== ====
  0   1-N
----- ----
  s   path
===== ====

START_ACK:

===== ===
  0   1-4
----- ---
  S   CRC
===== ===

CHUNK:

===== ====== =======
  0     1-4    4-N
----- ------ -------
  c   offset payload
===== ====== =======

CHUNK_ACK:

===== ===
  0   1-4
----- ---
  C   CRC(*)
===== ===

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

FINISH:

=== ===
 0
--- ---
 f
=== ===

FINISH_ACK:

=== ===
 0
--- ---
 F
=== ===

ERROR:

=== ===
 0
--- ---
 e
=== ===


ERROR_ACK:

=== ===
 0
--- ---
 E
=== ===

Flow
----

The file transfer process can be described as a series of states from the view
of the Central role:


.. image:: ../static/ble-low-effort-flow.png


``IDLE`` state:

    - Send ``START`` to initiate transfer

``START_SENT`` state:

    - Wait for ``START_ACK``

``SEND_READY`` state:

    - Send first ``CHUNK``

``CHUNK_SENT`` state:

    - Wait for ``CHUNK_ACK``

``SEND_READY`` state:

    - Repeat previous two steps until all data is sent
    - If the last chunk was sent, send ``FINISH``

``FINISH_SENT`` state:

    - Wait for ``FINISH_ACK``

After ``FINISH_ACK`` was received, the transfer is complete and the process can
return to ``IDLE``.

Error Handling
--------------
Three types of errors are currently supported:

- CRC errors:

  If an ``ACK`` packet contains a CRC that fails the verification, then the
  original packet must be retransmitted. If three consecutive attempts to
  send a packet fail, then the transfer is aborted.


- ACK timeouts:

  If the Central does not receive a required ``ACK`` within 10 seconds, then
  the original packet must be retransmitted. If three consecutive attempts to
  send a packet fail, then the transfer is aborted.


- Unexpected response:

  All steps in the flow described above have exactly one expected response.
  If any other packet is received, then the transfer is aborted.

Aborting Transfer
-----------------
To abort the transfer, the Central role sends an ``ERROR`` packet and returns
to ``IDLE`` after receiving the ``ERROR_ACK``.
If the Peripheral role aborts the transfer, i.e. the Central receives an
``ERROR`` at any point, then it responds with ``ERROR_ACK`` and returns to
``IDLE``

.. warning::
    As this is a custom file transfer protocol developed under less than ideal
    circumstances, it does not provide any guarantees, especially not regarding
    reliability or security. The protocol assumes a secure link and a
    trustworthy peer, amongst many other things. Use with caution.
+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
+10 −0
Original line number Diff line number Diff line
#!/bin/bash
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.
================== ========== ===========

Documentation/conf.py

0 → 100644
+183 −0
Original line number Diff line number Diff line
import os
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
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
sys.path.insert(0, os.path.abspath("../pycardium/modules/py"))
sys.path.insert(0, os.path.abspath("./"))

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

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

project = "card10-firmware"
copyright = "2019"

# The full version, including alpha/beta/rc tags
release = (
    subprocess.check_output(["git", "describe", "--long", "--always"]).decode().strip()
)
release += "<br />"
release += time.strftime("%F %R")
version = release


# -- General configuration ---------------------------------------------------

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
    "sphinx.ext.autodoc",
    "sphinx.ext.viewcode",
    "sphinx.ext.ifconfig",
    "sphinx.ext.todo",
]

todo_include_todos = True

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# 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
# - https://github.com/snide/sphinx_rtd_theme
# - https://pypi.python.org/pypi/sphinx_rtd_theme
# - python-sphinx-rtd-theme package (on Debian)
try:
    import sphinx_rtd_theme

    html_theme = "sphinx_rtd_theme"
    html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
    pygments_style = "monokai"
except ImportError:
    logger.warning(
        'The Sphinx "sphinx_rtd_theme" HTML theme was not found. Make sure you have the theme installed to produce pretty HTML output. Falling back to the default theme.'
    )


# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["static"]

# Theme Options
html_theme_options = {"style_external_links": True}

# Show "Edit on GitLab" links
html_show_sourcelink = False
html_context = {
    "display_gitlab": True,
    "gitlab_host": "git.card10.badge.events.ccc.de",
    "gitlab_user": "card10",
    "gitlab_repo": "firmware",
    "gitlab_version": "master/",
    "conf_py_path": "Documentation/",
    "theme_vcs_pageview_mode": "edit",
}
# }}}

# -- Options for Auto-Doc ---------------------------------------------------- {{{
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"
# }}}

# -- Options for Hawkmoth ---------------------------------------------------- {{{
has_hawkmoth = False
try:
    # Attempt importing hawkmoth
    import hawkmoth  # noqa: F401

    extensions.append("hawkmoth")

    cautodoc_root = os.path.abspath("..")
    cautodoc_clang = "-D__SPHINX_DOC"
    has_hawkmoth = True
except ImportError as e:
    if e.name == "clang":
        logger.warning(
            "hawkmoth requires the clang python module.  Documentation for Epicardium API will not be generated."
        )
# }}}


# -- 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
+138 −0
Original line number Diff line number Diff line
.. _debugger:

Debugger
========
If you have one of our debuggers for card10, this page details how you can use
it.  The debugger looks like either one in the following pictures:

.. image:: static/debuggers.png

First of all, you need to connect your debugger and card10.  There are three
connections that you need (take a look at the above diagram for more info):

* ``HDK``: This connection provides debugging (SWD) and UART.
* ``DEV``: This connection provides power (battery charger) and the native USB
  connection (bootloader).
* ``USB-C``: Connect the proved USB-C cable with the side which has the blue
  dot, so the blue dots have the same side.

Console
-------
When using the debugger, you will usually have two /dev/ttyACM* devices (one
each for ``HDK`` and ``DEV``).  To avoid confusion, allow access without
``sudo`` and tell ModemManager to ignore them, you can use the following udev
rule file (saved under ``/etc/udev/rules.d/99-card10.rules``):

.. code-block:: text

  SUBSYSTEM=="tty", ATTRS{idVendor}=="0d28", ATTRS{idProduct}=="0204", MODE="0664", GROUP="plugdev", SYMLINK+="ttyACM-card10-hdk", ENV{ID_MM_DEVICE_IGNORE}="1"
  SUBSYSTEM=="tty", ATTRS{idVendor}=="0b6a", ATTRS{idProduct}=="003c", MODE="0664", GROUP="plugdev", SYMLINK+="ttyACM-card10-dev", ENV{ID_MM_DEVICE_IGNORE}="1"

After changing udev rules, you need to tell the udev daemon:

.. code-block:: shell-session

  $ sudo udevadm control --reload

Now, additional symlinks (``/dev/ttyACM-card10-hdk`` and
``/dev/ttyACM-card10-dev``) will be created when connecting the card10.

OpenOCD
-------
For debugging card10, you need our `own fork`_ of OpenOCD.  It contains a patch
which allows flashing both flash-banks instead of just one.  Install it using
the following commands:

.. _own fork: https://git.card10.badge.events.ccc.de/card10/openocd

.. code-block:: shell-session

   $ git clone --recursive https://git.card10.badge.events.ccc.de/card10/openocd.git
   $ cd openocd

   $ ./bootstrap
   $ ./configure --disable-werror

.. warning::
    Make sure ``CMSIS-DAP Compliant Debugger`` is set to **yes (auto)** after
    running ``./configure`` (if it is not, you might need to install ``libhidapi-dev``
    (Ubuntu)). If you get errors making the documentation you can
    ``touch doc/openocd.info`` to skip it and continue with ``make``.

.. code-block:: shell-session

   $ make -j8


Please run ``make install`` after removing any already installed OpenOCD
version. Otherwise please always specify the full path to OpenOCD (the binary
is under ``src/openocd``).

.. note::

   If you want to use OpenOCD as normal user, copy ``contrib/60-openocd.rules``
   into the ``/etc/udev/rules.d/`` directory and run ``udevadm control --reload``
   afterwards.

GDB (``arm-none-eabi-gdb``)
---------------------------
Apart from OpenOCD you also need ``arm-none-eabi-gdb``.  You should install
that package from your distros repositories:

* Ubuntu (older): ``gdb-arm-none-eabi``
* Ubuntu (newer): ``gdb-multiarch``
* Arch: ``arm-none-eabi-gdb``

Debugging
---------
Run OpenOCD from the ``openocd/scripts`` directory in the firmware repository.
Call it as ``openocd -f interface/cmsis-dap.cfg -f target/max32665.cfg``.  If
the debugger and card10 are connected correctly, you should see the following
output:

.. code-block:: shell-session

   $ openocd -f interface/cmsis-dap.cfg -f target/max32665.cfg
   Info : CMSIS-DAP: SWD  Supported
   Info : CMSIS-DAP: FW Version = 1.0
   Info : CMSIS-DAP: Interface Initialised (SWD)
   Info : SWCLK/TCK = 0 SWDIO/TMS = 1 TDI = 0 TDO = 0 nTRST = 0 nRESET = 1
   Info : CMSIS-DAP: Interface ready
   Info : clock speed 2000 kHz
   Info : SWD DPIDR 0x2ba01477
   Info : max32xxx.cpu: hardware has 6 breakpoints, 4 watchpoints
   Info : Listening on port 3333 for gdb connections

Next, start *GDB* in parallel and connect it to OpenOCD.  You can do this easily
if you run GDB from the firmware repository root where we have provided an
``init.gdb`` file. Specify ``-x init.gdb`` to use this file.  Apart from
automatically connecting to OpenOCD, this script file also defines a ``reset``
command to soft-reset card10.

.. code-block:: shell-session

   $ arm-none-eabi-gdb -x init.gdb build/hw-tests/hello-world/hello-world.elf
   ...
   (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
   a soft-reset. This reset only resets the core, but not its peripherals.
   Our custom ``reset`` sets a special bit in the CPU which also resets the
   peripherals.

You are now connected to card10 and ready to start debugging!  If card10 is
still running, stop it using

.. code-block:: text

   (gdb) mon reset halt

Following that, you can debug as you would normally.
+104 −0
Original line number Diff line number Diff line
.. _epicardium_api_guide:

Epicardium API Development
==========================
If you are interested in augmenting the Epicardium API with new calls, this
page is the right place for you.

API Call Declaration
--------------------
All API calls are declared in ``epicardium/epicardium.h``.  We also generated
documentation for all API calls using sphinx.  Please document any new calls in
the same format as existing calls.  An example API call definition might look
like this:

.. code-block:: cpp

   /* At the top of epicardium.h, add a unique ID for your call */
   #define API_ROCKET           0xc0ffee

   /**
    * Turn on card10's rocket engines.
    *
    * :param force: Force in Newtons.
    * :param color: 24-bit exhaust color.
    * :return: ``0`` on success or ``-Exxx`` on error.  The following errors
    *     might occur:
    *
    *     - ``-ENODEV``: Sadly, card10 does not have rocket engines.
    */
   API(API_ROCKET, int epic_rocket_start(float force, uint32_t color));

There are a number of rules you should follow when defining new calls:

* Each API call need a unique call number (``API_ROCKET`` in this case).
  There are no special rules regarding call numbers, choose any number not yet
  in use.  Call numbers are ``uint32_t`` so you have plenty to choose from.
* API calls have the prefix ``epic_`` which of course is just an abbreviation
  for *Epicardium*.
* Only use types from the standard library or types defined (and documented!)
  in ``epicardium.h``.  **Never** include another header in ``epicardium.h``.
* When passing pointers, keep in mind that this will mean the other core will
  potentially modify stuff in your address space.  Please prefer passing pointers
  into core 1 (Pycardium) address space and refrain from returning pointers into
  Epicardium address space if possible.
* API calls follow the kernel convention of either returning a boolean if the
  name is a predicate or a success integer (with 0 denoting success and
  negative values denoting errors) if they are an imperative command or action
  (ref `Kernel Coding Style`_).  If you are reasonably sure your call cannot fail
  or an error is non-recoverable from core 1, return ``void``.

.. _Kernel Coding Style: https://www.kernel.org/doc/html/v5.2/process/coding-style.html#function-return-values-and-names

.. warning::

   After the 4th of August 00:00 UTC, **no** changes to existing IDs and the signature
   and behavior of existing calls are allowed!  This is necessary to ensure
   compatibility of future firmware versions with older payloads.  We call this
   date the **API Freeze Deadline**.  Addition of new calls will always be allowed.

   In return this also means payloads compiled against a version of *Epicardium
   API* released before that date are not guaranteed to work during and after
   camp.

API Call Definition
-------------------
API calls should be defined in a source file in ``epicardium/modules``.  If
there is not yet one where your call fits in, create a new one.  Don't forget
to also add it in ``epicardium/modules/meson.build`` to make the build-system
aware of it.

For the example above, the definition might look like this:

.. code-block:: cpp

   /* epicardium/modules/rocket.c */
   #include <stdio.h>
   #include "epicardium.h"

   int epic_rocket_start(float force, uint32_t color)
   {
           printf("Starting rocket engines with %fN in color 0x%x"
                  force,
                  color);

           /* Aw :( */
           return -ENODEV;
   }

To keep code-style uniform across the project, please format your code using
``./tools/code-style.sh <filename>`` (requires ``clang-format``).  Note that
this is not a definite style, however: If something looks better when manually
formatted, don't be afraid to do so.

.. warning::

   When writing your calls, **never** make assumptions about which FreeRTOS
   task you are running in.  While all calls from core 1 will end up in the
   "Dispatcher" task, other FreeRTOS tasks might might call your code at any
   time just as well.

   This is especially important if you use a `task semaphore`_.  Always call
   ``xTaskGetCurrentTaskHandle()``.

   .. _task semaphore: https://freertos.org/RTOS_Task_Notification_As_Binary_Semaphore.html
+23 −0
Original line number Diff line number Diff line
.. _epicardium_api:

Epicardium API
==============

.. ifconfig:: not has_hawkmoth

   .. warning::

      This version of the documentation was built without `Hawkmoth`_ working
      (most likely python3-clang was not installed).  This means, no
      documentation for the Epicardium API was generated.

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

This page details all available *Epicardium API* functions.  All these
functions are defined in

.. code-block:: c++

   #include "epicardium.h"

.. c:autodoc:: epicardium/epicardium.h
+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;
   }
+62 −0
Original line number Diff line number Diff line
.. _epicardium_api_overview:

Overview
========
Epicardium, the "main" firmware running on core 0 (more about our dual
core design can be found :ref:`here <firmware_overview>`), exposes a lot of
functionality to user-code via the so-called *Epicardium API*.  This API
consists of a number of calls that can be issued by core 1 using an
auto-generated library.

API Design
----------
.. note::

   This is the current design.  We might adjust it in the future to allow for
   more performance in cases where this approach is too slow.  This will most
   likely be the addition of some form of asynchroneous calls.

The API is strictly synchroneous.  This means, an API call looks exactly the
same as calling any other function.  Internally, the call will wake up
Epicardium and there the call will be dispatched.  It will then block until
Epicardium finished executing the call and return whatever the call has as a
return value.  In code:

.. code-block:: c++

   #include "epicardium.h"

   int main(void)
   {
   	/* ... */

   	/* Call the API to write to UART. */
   	epic_uart_write_str("Hello from core 1!\r\n", 20);

   	/*
   	 * Call the API to receive a byte from UART.
   	 * This will block until at least one character is received.
   	 */
   	char chr = epic_uart_read_chr();

   	/* ... */
   }

Internals
---------
In most cases, you won't need to care about the actual implementation of the
API as it is well hidden inside the auto-generated library.  If you want to
know anyway, here is a rough overview:

The current design is based around a shared memory region, the semaphore
peripherals provided by MAX32666, and the SEV/WFE mechanism.  When issuing a
call, this is what happens:

.. image:: ../static/synchroneous-api-call.svg

There is one important thing to note here:  Right now, the API is only ever
polled for new calls from Epicardium inside the idle task.  This means, that if
it is busy with other things, the API will have the least priority.  For now,
this has not been an issue as Epicardium is sleeping most of the time anyway.
If it becomes one in the future we will have to introduce another
synchronization point.
Original line number Diff line number Diff line
Sensor Streams
==============
Sensor drivers can make their data available to core 1 in a stream-like format.
This allows batch-reading many samples and shoud reduce pressure on the
Epicardium API this way.  Sensor streams are read on core 1 using
:c:func:`epic_stream_read`.

This page intends to document how to add this stream interface to a sensor driver.
It also serves as a reference of existing streams.  For that, take a look at the
definitions in the :c:type:`stream_descriptor` enum.

Adding a new Stream
-------------------
The list of possible sensor streams must be known at compile time.  Each stream
gets a unique ID in the :c:type:`stream_descriptor` enum.  Please do not assign
IDs manually but instead let the enum assign sequencial IDs.  :c:macro:`SD_MAX`
must always be the highest stream ID.  Additionally, please document what this
stream is for using a doc-comment so it shows up on this page.

When a sensor driver enables data collection, it should also register its
respective stream.  This is done using a :c:type:`stream_info` object.  Pass
this object to :c:func:`stream_register` to make your stream available.  Your
driver must guarantee the :c:member:`stream_info.queue` handle to be valid until
deregistration using :c:func:`stream_deregister`.

Definitions
-----------

.. c:autodoc:: epicardium/modules/stream.h
+25 −0
Original line number Diff line number Diff line
Copyright (c) 2016-2017, Jani Nikula <jani@nikula.org>
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

* Redistributions of source code must retain the above copyright
  notice, this list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright
  notice, this list of conditions and the following disclaimer in the
  documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+5 −0
Original line number Diff line number Diff line
# -*- makefile -*-

dir := hawkmoth

CLEAN := $(CLEAN) $(dir)/hawkmoth.pyc $(dir)/cautodoc.pyc $(dir)/__init__.pyc
+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
+122 −0
Original line number Diff line number Diff line
# Copyright (c) 2016-2017, Jani Nikula <jani@nikula.org>
# Licensed under the terms of BSD 2-Clause, see LICENSE for details.
"""
Hawkmoth
========

Sphinx C Domain autodoc directive extension.
"""

import glob
import os
import re
import stat
import subprocess
import sys

from docutils import nodes, statemachine
from docutils.parsers.rst import directives, Directive
from docutils.statemachine import ViewList
from sphinx.util.nodes import nested_parse_with_titles
from sphinx.util.docutils import switch_source_input
from sphinx.util import logging

from hawkmoth.parser import parse, ErrorLevel

with open(os.path.join(os.path.abspath(os.path.dirname(__file__)),
                       'VERSION')) as version_file:
    __version__ = version_file.read().strip()

class CAutoDocDirective(Directive):
    """Extract all documentation comments from the specified file"""
    required_argument = 1
    optional_arguments = 1
    logger = logging.getLogger(__name__)

    # Allow passing a variable number of file patterns as arguments
    final_argument_whitespace = True

    option_spec = {
        'compat': directives.unchanged_required,
        'clang': directives.unchanged_required,
    }
    has_content = False

    # Map verbosity levels to logger levels.
    _log_lvl = {ErrorLevel.ERROR: logging.LEVEL_NAMES['ERROR'],
                ErrorLevel.WARNING: logging.LEVEL_NAMES['WARNING'],
                ErrorLevel.INFO: logging.LEVEL_NAMES['INFO'],
                ErrorLevel.DEBUG: logging.LEVEL_NAMES['DEBUG']}

    def __display_parser_diagnostics(self, errors):
        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,
                                location=(env.docname, self.lineno))

    def __parse(self, viewlist, filename):
        env = self.state.document.settings.env

        compat = self.options.get('compat', env.config.cautodoc_compat)
        clang = self.options.get('clang', env.config.cautodoc_clang)

        comments, errors = parse(filename, compat=compat, clang=clang)

        self.__display_parser_diagnostics(errors)

        for (comment, meta) in comments:
            lineoffset = meta['line'] - 1
            lines = statemachine.string2lines(comment, 8,
                                              convert_whitespace=True)
            for line in lines:
                viewlist.append(line, filename, lineoffset)
                lineoffset += 1

    def run(self):
        env = self.state.document.settings.env

        result = ViewList()

        for pattern in self.arguments[0].split():
            filenames = glob.glob(env.config.cautodoc_root + '/' + pattern)
            if len(filenames) == 0:
                fmt = 'Pattern "{pat}" does not match any files.'
                self.logger.warning(fmt.format(pat=pattern),
                                    location=(env.docname, self.lineno))
                continue

            for filename in filenames:
                mode = os.stat(filename).st_mode
                if stat.S_ISDIR(mode):
                    fmt = 'Path "{name}" matching pattern "{pat}" is a directory.'
                    self.logger.warning(fmt.format(name=filename, pat=pattern),
                                        location=(env.docname, self.lineno))
                    continue

                # Tell Sphinx about the dependency and parse the file
                env.note_dependency(os.path.abspath(filename))
                self.__parse(result, filename)

        # Parse the extracted reST
        with switch_source_input(self.state, result):
            node = nodes.section()
            nested_parse_with_titles(self.state, result, node)

        return node.children

def setup(app):
    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')
    app.add_directive_to_domain('c', 'autodoc', CAutoDocDirective)

    return dict(version = __version__,
                parallel_read_safe = True, parallel_write_safe = True)
+48 −0
Original line number Diff line number Diff line
# Copyright (c) 2016-2019 Jani Nikula <jani@nikula.org>
# Licensed under the terms of BSD 2-Clause, see LICENSE for details.
"""
Hawkmoth parser debug tool
==========================

python3 -m hawkmoth
"""

import argparse
import sys

from hawkmoth.parser import parse

def main():
    parser = argparse.ArgumentParser(prog='hawkmoth', description="""
    Hawkmoth parser debug tool. Print the documentation comments extracted
    from FILE, along with the generated C Domain directives, to standard
    output. Include metadata with verbose output.""")
    parser.add_argument('file', metavar='FILE', type=str, action='store',
                        help='The C source or header file to parse.')
    parser.add_argument('--compat',
                        choices=['none',
                                 'javadoc-basic',
                                 'javadoc-liberal',
                                 'kernel-doc'],
                        help='Compatibility options. See cautodoc_compat.')
    parser.add_argument('--clang', metavar='PARAM[,PARAM,...]',
                        help='Arguments to pass to clang. See cautodoc_clang.')
    parser.add_argument('--verbose', dest='verbose', action='store_true',
                        help='Verbose output.')
    args = parser.parse_args()

    docs, errors = parse(args.file, compat=args.compat, clang=args.clang)

    for (doc, meta) in docs:
        if args.verbose:
            print('# {}'.format(meta))
        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()
+326 −0
Original line number Diff line number Diff line
# Copyright (c) 2016-2017 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 comment extractor
===============================

This module extracts relevant documentation comments, optionally reformatting
them in reST syntax.

This is the part that uses Clang Python Bindings to extract documentation
comments from C source code. This module does not depend on Sphinx.

There are two passes:

#. Pass over the tokens to find all the comments, including ones that aren't
   attached to cursors.

#. Pass over the cursors to document them.

There is minimal syntax parsing or input conversion:

* Identification of documentation comment blocks, and stripping the comment
  delimiters (``/**`` and ``*/``) and continuation line prefixes (e.g. ``␣*␣``).

* Identification of function-like macros.

* Indentation for reST C Domain directive blocks.

* An optional external filter may be invoked to support different syntaxes.
  These filters are expected to translate the comment into the reST format.

Otherwise, documentation comments are passed through verbatim.
"""

import enum
import re
import sys

from clang.cindex import CursorKind, TypeKind
from clang.cindex import Index, TranslationUnit
from clang.cindex import SourceLocation, SourceRange
from clang.cindex import TokenKind, TokenGroup

from hawkmoth.util import docstr, doccompat

class ErrorLevel(enum.Enum):
    """
    Supported error levels in inverse numerical order of severity. The values
    are chosen so that they map directly to a 'verbosity level'.
    """
    ERROR = 0
    WARNING = 1
    INFO = 2
    DEBUG = 3

def comment_extract(tu):

    # FIXME: How to handle top level comments above a cursor that it does *not*
    # describe? Parsing @file or @doc at this stage would not be a clean design.
    # One idea is to use '/***' to denote them, but that might throw off editor
    # highlighting. The workaround is to follow the top level comment with an
    # empty '/**/' comment that gets attached to the cursor.

    top_level_comments = []
    comments = {}
    cursor = None
    current_comment = None
    for token in tu.get_tokens(extent=tu.cursor.extent):
        # handle all comments we come across
        if token.kind == TokenKind.COMMENT:
            # if we already have a comment, it wasn't related to a cursor
            if current_comment and docstr.is_doc(current_comment.spelling):
                top_level_comments.append(current_comment)
            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):
            continue

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

        cursor = token_cursor

        # Note: current_comment may be None
        if current_comment != None and docstr.is_doc(current_comment.spelling):
            comments[cursor.hash] = current_comment
        current_comment = None

    # comment at the end of file
    if current_comment and docstr.is_doc(current_comment.spelling):
        top_level_comments.append(current_comment)

    return top_level_comments, comments

def _result(comment, cursor=None, fmt=docstr.Type.TEXT, nest=0,
            name=None, ttype=None, args=None, compat=None):

    # FIXME: docstr.generate changes the number of lines in output. This impacts
    # the error reporting via meta['line']. Adjust meta to take this into
    # account.

    doc = docstr.generate(text=comment.spelling, fmt=fmt,
                          name=name, ttype=ttype, args=args, transform=compat)

    doc = docstr.nest(doc, nest)

    meta = {'line': comment.extent.start.line}
    if cursor:
        meta['cursor.kind']        = cursor.kind,
        meta['cursor.displayname'] = cursor.displayname,
        meta['cursor.spelling']    = cursor.spelling

    return [(doc, meta)]

# Return None for simple macros, a potentially empty list of arguments for
# function-like macros
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('
    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 tokens:
        if token.spelling == ')':
            return args
        elif token.spelling == ',':
            continue
        elif token.kind == TokenKind.IDENTIFIER:
            args.append(token.spelling)
        elif token.spelling == '...':
            args.append(token.spelling)
        else:
            break

    return None

def _recursive_parse(comments, cursor, nest, compat):
    comment = comments[cursor.hash]
    name = cursor.spelling
    ttype = cursor.type.spelling

    if cursor.kind == CursorKind.MACRO_DEFINITION:
        # FIXME: check args against comment
        args = _get_macro_args(cursor)
        fmt = docstr.Type.MACRO if args is None else docstr.Type.MACRO_FUNC

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

    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)

    elif cursor.kind == CursorKind.TYPEDEF_DECL:
        # FIXME: function pointers typedefs.
        fmt = docstr.Type.TYPE

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

    elif cursor.kind in [CursorKind.STRUCT_DECL, CursorKind.UNION_DECL,
                         CursorKind.ENUM_DECL]:

        # FIXME:
        # Handle cases where variables are instantiated on type declaration,
        # including anonymous cases. Idea is that if there is a variable
        # instantiation, the documentation should be applied to the variable if
        # the structure is anonymous or to the type otherwise.
        #
        # Due to the new recursiveness of the parser, fixing this here, _should_
        # handle all cases (struct, union, enum).

        # FIXME: Handle anonymous enumerators.

        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=name if name else ttype, compat=compat)

        nest += 1
        for c in cursor.get_children():
            if c.hash in comments:
                result.extend(_recursive_parse(comments, c, nest, compat))

        return result

    elif cursor.kind == CursorKind.ENUM_CONSTANT_DECL:
        fmt = docstr.Type.ENUM_VAL

        return _result(comment, cursor=cursor, fmt=fmt,
                       nest=nest, name=name, 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
        # typedef, for example
        args = []

        # Only fully prototyped functions will have argument lists to process.
        if cursor.type.kind == TypeKind.FUNCTIONPROTO:
            for c in cursor.get_children():
                if c.kind == CursorKind.PARM_DECL:
                    args.append('{ttype} {arg}'.format(ttype=c.type.spelling,
                                                    arg=c.spelling))

            if cursor.type.is_function_variadic():
                args.append('...')

        fmt = docstr.Type.FUNC
        ttype = cursor.result_type.spelling

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

    # FIXME: If we reach here, nothing matched. This is a warning or even error
    # and it should be logged, but it should also return an empty list so that
    # it doesn't break. I.e. the parser needs to pass warnings and errors to the
    # Sphinx extension instead of polluting the generated output.
    fmt = docstr.Type.TEXT
    text = 'warning: unhandled cursor {kind} {name}\n'.format(
        kind=str(cursor.kind),
        name=cursor.spelling)

    doc = docstr.generate(text=text, fmt=fmt)

    meta = {
        'line':               comment.extent.start.line,
        'cursor.kind':        cursor.kind,
        'cursor.displayname': cursor.displayname,
        'cursor.spelling':    cursor.spelling
    }

    return [(doc, meta)]

def clang_diagnostics(errors, diagnostics):
    sev = {0: ErrorLevel.DEBUG,
           1: ErrorLevel.DEBUG,
           2: ErrorLevel.WARNING,
           3: ErrorLevel.ERROR,
           4: ErrorLevel.ERROR}

    for diag in diagnostics:
        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
# options - dictionary with directive options
def parse(filename, **options):

    errors = []
    args = options.get('clang')
    if args is not None:
        args = [s.strip() for s in args.split(',') if len(s.strip()) > 0]
        if len(args) == 0:
            args = None

    index = Index.create()

    tu = index.parse(filename, args=args, options=
                     TranslationUnit.PARSE_DETAILED_PROCESSING_RECORD |
                     TranslationUnit.PARSE_SKIP_FUNCTION_BODIES)

    clang_diagnostics(errors, tu.diagnostics)

    top_level_comments, comments = comment_extract(tu)

    result = []
    compat = lambda x: doccompat.convert(x, options.get('compat'))

    for comment in top_level_comments:
        result.extend(_result(comment, compat=compat))

    for cursor in tu.cursor.get_children():
        if cursor.hash in comments:
            result.extend(_recursive_parse(comments, cursor, 0, compat))

    # Sort all elements by order of appearance.
    result.sort(key=lambda r: r[1]['line'])

    return result, errors
Original line number Diff line number Diff line
# Copyright (c) 2016-2017, Jani Nikula <jani@nikula.org>
# Licensed under the terms of BSD 2-Clause, see LICENSE for details.
"""
Alternative docstring syntax
============================

This module abstracts different compatibility options converting different
syntaxes into 'native' reST ones.
"""

import re

# Basic Javadoc/Doxygen/kernel-doc import
#
# FIXME: One of the design goals of Hawkmoth is to keep things simple. There's a
# fine balance between sticking to that goal and adding compat code to
# facilitate any kind of migration to Hawkmoth. The compat code could be turned
# into a fairly simple plugin architecture, with some basic compat builtins, and
# the users could still extend the compat features to fit their specific needs.
def convert(comment, mode):
    """Convert documentation from a supported syntax into reST."""
    # FIXME: try to preserve whitespace better

    if mode == 'javadoc-basic' or mode == 'javadoc-liberal':
        # @param
        comment = re.sub(r"(?m)^([ \t]*)@param([ \t]+)([a-zA-Z0-9_]+|\.\.\.)([ \t]+)",
                         "\n\\1:param\\2\\3:\\4", comment)
        # @param[direction]
        comment = re.sub(r"(?m)^([ \t]*)@param\[([^]]*)\]([ \t]+)([a-zA-Z0-9_]+|\.\.\.)([ \t]+)",
                         "\n\\1:param\\3\\4: *(\\2)* \\5", comment)
        # @return
        comment = re.sub(r"(?m)^([ \t]*)@returns?([ \t]+|$)",
                         "\n\\1:return:\\2", comment)
        # @code/@endcode blocks. Works if the code is indented.
        comment = re.sub(r"(?m)^([ \t]*)@code([ \t]+|$)",
                         "\n::\n", comment)
        comment = re.sub(r"(?m)^([ \t]*)@endcode([ \t]+|$)",
                         "\n", comment)
        # Ignore @brief.
        comment = re.sub(r"(?m)^([ \t]*)@brief[ \t]+", "\n\\1", comment)

        # Ignore groups
        comment = re.sub(r"(?m)^([ \t]*)@(defgroup|addtogroup)[ \t]+[a-zA-Z0-9_]+[ \t]*",
                         "\n\\1", comment)
        comment = re.sub(r"(?m)^([ \t]*)@(ingroup|{|}).*", "\n", comment)

    if mode == 'javadoc-liberal':
        # Liberal conversion of any @tags, will fail for @code etc. but don't
        # care.
        comment = re.sub(r"(?m)^([ \t]*)@([a-zA-Z0-9_]+)([ \t]+)",
                         "\n\\1:\\2:\\3", comment)

    if mode == 'kernel-doc':
        # Basic kernel-doc convert, will document struct members as params, etc.
        comment = re.sub(r"(?m)^([ \t]*)@(returns?|RETURNS?):([ \t]+|$)",
                         "\n\\1:return:\\3", comment)
        comment = re.sub(r"(?m)^([ \t]*)@([a-zA-Z0-9_]+|\.\.\.):([ \t]+)",
                         "\n\\1:param \\2:\\3", comment)

    return comment
+107 −0
Original line number Diff line number Diff line
# 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
==========================================

This module allows a generic way of generating reST documentation for each C
construct.
"""

import re
from enum import Enum, auto

class Type(Enum):
    """Enumeration of supported formats."""
    TEXT = auto()
    VAR = auto()
    TYPE = auto()
    STRUCT = auto()
    UNION = auto()
    ENUM = auto()
    ENUM_VAL = auto()
    MEMBER = auto()
    MACRO = auto()
    MACRO_FUNC = auto()
    FUNC = auto()

# Dictionary of tuples (text indentation level, format string).
#
# Text indentation is required for indenting the documentation body relative to
# directive lines.
_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.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:macro:: {name}({args})\n\n{text}\n'),
    Type.FUNC:       (1, '\n.. c:function:: {ttype} {name}({args})\n\n{text}\n')
}

def _strip(comment):
    """Strip comment from comment markers."""
    comment = re.sub(r'^/\*\*[ \t]?', '', comment)
    comment = re.sub(r'\*/$', '', comment)
    # Could look at first line of comment, and remove the leading stuff there
    # from the rest.
    comment = re.sub(r'(?m)^[ \t]*\*?[ \t]?', '', comment)
    # Strip leading blank lines.
    comment = re.sub(r'^[\n]*', '', comment)
    return comment.strip()

def is_doc(comment):
    """Test if comment is a C documentation comment."""
    return comment.startswith('/**') and comment != '/**/'

def nest(text, nest):
    """
    Indent documentation block for nesting.

    Args:
        text (str): Documentation body.
        nest (int): Nesting level. For each level, the final block is indented
            one level. Useful for (e.g.) declaring structure members.

    Returns:
        str: Indented reST documentation string.
    """
    return re.sub('(?m)^(?!$)', '   ' * nest, text)

def generate(text, fmt=Type.TEXT, name=None,
             ttype=None, args=None, transform=None):
    """
    Generate reST documentation string.

    Args:
        text (str): Documentation body.
        fmt (enum :py:class:`Type`): Format type to use. Different formats
            require different arguments and ignores others if given.
        name (str): Name of the documented token.
        ttype (str): Type of the documented token.
        args (list): List of arguments (str).
        transform (func): Transformation function to be applied to the
            documentation body. This is useful (e.g.) to extend the generator
            with different syntaxes by converting them to reST. This is applied
            on the documentation body after removing comment markers.

    Returns:
        str: reST documentation string.
    """

    text = _strip(text)

    if transform:
        text = transform(text)

    if args is not None:
        args = ', '.join(args)

    (text_indent, fmt) = _doc_fmt[fmt]
    text = nest(text, text_indent)
    return fmt.format(text=text, name=name, ttype=ttype, args=args)
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)
+213 −0
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**
need to build the firmware yourself.  This page is for people **who want to work
on the underlying firmware itself**.

Dependencies
------------
* **gcc**, **binutils** & **newlib** for ``arm-none-eabi``:  The packages have
  slightly different names on different distros.

  - Ubuntu / Debian:

    .. code-block:: shell-session

       apt install gcc-arm-none-eabi binutils-arm-none-eabi libnewlib-arm-none-eabi

  - Arch:

    .. code-block:: shell-session

       pacman -S arm-none-eabi-gcc arm-none-eabi-binutils arm-none-eabi-newlib

  - Fedora

    .. code-block:: shell-session

        dnf install arm-none-eabi-gcc arm-none-eabi-binutils arm-none-eabi-newlib

  - 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.

    .. code-block:: shell-session

        brew tap px4/px4
        brew install px4/px4/gcc-arm-none-eabi-63
        brew install coreutils

    .. _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
  of headaches by installing meson from *pip*.

  - Ubuntu / Debian:

    .. code-block:: shell-session

       apt install ninja-build
       pip3 install --user meson

  - Arch (has latest *meson* in the repos):

    .. code-block:: shell-session

       pacman -S meson

  - macOS

    .. code-block:: shell-session

        brew install ninja
        pip3 install --user meson  # see https://mesonbuild.com/Getting-meson.html - you will have to add ~/.local/bin to your PATH.

* 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-pillow

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

Cloning
-------
Clone the ``master`` branch of the firmware repository:

.. code-block:: shell-session

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

Build Configuration
-------------------
Initialize the build-system using

.. code-block:: shell-session

   $ ./bootstrap.sh

Additional arguments to ``bootstrap.sh`` will be passed to *meson*.  You can
use this to for example, to enable one or more of the following optional
firmware features:

- ``-Ddebug_prints=true``: Print more verbose debugging log messages
- ``-Dble_trace=true``: Enable BLE tracing.  This will output lots of status
  info related to BLE.
- ``-Ddebug_core1=true``: Enable the core 1 SWD lines which are exposed on the
  SAO connector.  Only use this if you have a debugger which is modified for core 1.

.. warning::

   Our build-system contains a few workarounds around short-comings in meson.
   These workarounds might break on some setups which we did not yet test.  If
   this is the case for you, please open an issue in our `issue tracker`_!

.. _issue tracker: https://git.card10.badge.events.ccc.de/card10/firmware/issues

Building
--------
Build using *ninja*:

.. code-block:: shell-session

   $ ninja -C build/

If ninja succeeds, the resulting binaries are in ``build/``.  They are
available in two formats:  As an ``.elf`` which can be flashed using a debugger
and as a ``.bin`` which can be loaded using the provided bootloader.  Here is a
list of the binaries:

- ``build/bootloader/bootloader.elf``: Our bootloader.  It should already be on
  your card10.  The bootloader can only be flashed using a debugger.
- ``build/pycardium/pycardium_epicardium.bin``: The entire firmware in one ``.bin``.
- ``build/epicardium/epicardium.elf``: The core 0 part of the firmware, called Epicardium.
- ``build/pycardium/pycardium.elf``: Our MicroPython port, the core 1 part of the firmware.

In order to do a rebuild you can issue a clean command to ninja via

.. code-block:: shell-session

  $ 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
+58 −0
Original line number Diff line number Diff line
How To Flash
============
Depending on whether you have a debugger or not, you have to use a different
method of flashing:

Flash Without Debugger
----------------------

If you do not have a debugger, you have to update the firmware using our
bootloader by going into :ref:`usb_file_transfer`.

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.``
again before resetting card10 by pressing the power button again.

Flash Using Debugger
--------------------

First, setup everything as explained on the :ref:`debugger` page.  Following
that and after connecting to card10, you can flash your binary using the
``load`` command.  After loading, you need to use ``reset`` to reboot card10
using your new firmware.

.. warning::

   With the current version of the bootloader, before attempting to flash using
   the debugger, make sure there is no ``card10.bin`` stored on the device.
   If there is, the bootloader will overwrite whatever you just flashed after
   reboot every time.

.. code-block:: text

   (gdb) load
   Loading section .text, size 0x12514 lma 0x10010000
   Loading section .ARM.exidx, size 0x8 lma 0x10022514
   Loading section .data, size 0x8d8 lma 0x1002251c
   Start address 0x10012160, load size 77300
   Transfer rate: 19 KB/sec, 11042 bytes/write.
   (gdb)

.. note::

   If OpenOCD was able to connect, but GDB gives you an

   .. code-block:: text

      Error erasing flash with vFlashErase packet

   error, issue a ``reset`` command, quickly followed by a ``load`` command.

   Reason: The Epicardium puts parts of the CPU to sleep and the debugging
   interface is part of that. After a reset the bootloader starts up
   and lets OpenOCD/GDB take control again.
+87 −0
Original line number Diff line number Diff line
card10 firmware docs
====================

**Dear traveller,**

these transcripts describe how you can write code for your card10.  This
includes the Python modules that are available but also documentation of the
lower level firmware components.

If you want to write Python code for card10, you will want to take a look at
the :ref:`Pycardium <pycardium_overview>` docs.  If you are interested in writing applications
in other languages, you'll probably want to interface with
:ref:`Epicardium API <epicardium_api_overview>` directly.

Last but not least, if you want to start hacking the lower-level firmware, the
:ref:`Firmware <firmware_overview>` section of these docs is a good starting place.


.. toctree::
   :maxdepth: 1
   :caption: Pycardium

   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/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::
   :maxdepth: 1
   :caption: Epicardium API

   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
==================

* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
+47 −0
Original line number Diff line number Diff line
Memory Map
==========

Flash::

  | Address    | Usage              |
  |------------|--------------------|
  |0x100F FFFF |                    |
  |            | Core 1: 512 kB     |
  |0x1008 0000 |                    |
  |------------|--------------------|
  |0x1007 FFFF |                    |
  |            | Core 0: 448 kB     |
  |0x1001 0000 |                    |
  |------------|--------------------|
  |0x1000 FFFF |                    |
  |            | Bootloader: 64 kB  |
  |0x1000 0000 |                    |
  |------------|--------------------|


Ram::

  | Address    | Usage                 |
  |------------|-----------------------|
  |0x2008 BFFF |                       |
  |            | API Parameters: 48 kB |
  |0x2008 0000 |                       |
  |------------|-----------------------|
  |0x2007 FFFF |                       |
  |            | Core 1: 256 kB        |
  |0x2004 0000 |                       |
  |------------|-----------------------|
  |0x2003 FFFF |                       |
  |            | Core 0: 256 kB        |
  |0x2000 0000 |                       |
  |------------|-----------------------|

External Flash::

  | Address    | Usage                 |
  |------------|-----------------------|
  |0x0080 0000 |                       |
  |            | FAT file system: 8 MB |
  |0x0000 0000 |                       |
  |------------|-----------------------|
+88 −0
Original line number Diff line number Diff line
.. _firmware_overview:

Overview
========
To make the most of card10's dual-core processor, its firmware will have been
divided into two parts: The "main" firmware running on core 0 which will have
been called *Epicardium* and the "user-code" running on core 1.  In most cases
this will have been *Pycardium*, our MicroPython port.

.. image:: ./static/overview.svg

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:

+--------------------+-------------------------------+----------+-------------------------------------------+
| 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

Epicardium API
--------------
Epicardium exposes lots of functionality via the *Epicardium API*.  The
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
--------

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
linker script and should link against the api-caller library so they can
interface with the :ref:`epicardium_api`.

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.

.. _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
--------------------
The following diagram is a rough overview of the program flow in this fimware:

.. image:: ./static/firmware-flow.svg
+361 −0
Original line number Diff line number Diff line
.. _pycardium_guide:

Pycardium Module Development
============================
This is a step-by-step guide on augmenting Pycardium with new modules.  These
module are either written in Python or in C:

* **Python Modules**: Python modules live in ``pycardium/modules/py`` and are
  pre-compiled and packed into Pycardium at build-time.  Python modules are
  meant to be used for creating abstractions and helpers based on lower level
  functionality.  The rationale is that it is easier to write pythonic modules
  in python instead of having to wrap all that in C code.  Though care has to
  be taken as these modules can eat quite a lot of space.

* **C Modules**: C modules allow MicroPython code to interface with the rest of
  the system or allow a much faster implementation of performance-critical
  code. For interfacing with card10, these modules are mostly meant to make use
  of the :ref:`Epicardium API <epicardium_api_overview>`.

Python Modules
--------------
Adding a new Python module
~~~~~~~~~~~~~~~~~~~~~~~~~~
Add a file in ``pycardium/modules/py``.  The name of the file dictates the name
of the module.  Next, add you file to the list in
``pycardium/modules/py/meson.build`` to make the build-system aware of it.
Rebuild, and you should see your module pop up in the list shown by

.. code-block:: python

   help("modules")

Size Consideration
~~~~~~~~~~~~~~~~~~
Python modules can get quite big.  If you want to know exact sizes, take a look at
the output of

.. code-block:: shell-session

   ls -lh build/pycardium/*@@frozen.c@*/

If your module contains some kind of big lookup-table or data-block, consider
pushing that into external flash.  You can then read the data using standard
Python ``open()`` and if you need to decode it, use the ``ustruct`` or ``ujson``
module.

Creating a new C module
-----------------------
Module Source
~~~~~~~~~~~~~
Create a new file in ``pycardium/modules``.  The basic structure of a C module
looks like this:

.. code-block:: c++

   #include "py/obj.h"

   /* Define a function in this module */
   static mp_obj_t mp_example_hello(mp_obj_t number)
   {
           int num = mp_obj_get_int(number);
           printf("Hello from example: %d\r\n", num);
           return mp_const_none;
   }
   /* Create an object for this function */
   static MP_DEFINE_CONST_FUN_OBJ_1(example_hello_obj, mp_example_hello);

   /* The globals table for this module */
   static const mp_rom_map_elem_t example_module_globals_table[] = {
           {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_example)},
           {MP_ROM_QSTR(MP_QSTR_hello), MP_ROM_PTR(&example_hello_obj)},
   };
   static MP_DEFINE_CONST_DICT(example_module_globals, example_module_globals_table);

   const mp_obj_module_t example_module = {
           .base    = {&mp_type_module},
           .globals = (mp_obj_dict_t*)&example_module_globals,
   };

   /* This is a special macro that will make MicroPython aware of this module */
   /* clang-format off */
   MP_REGISTER_MODULE(MP_QSTR_example, example_module, MODULE_EXAMPLE_ENABLED);

.. note::

   Please keep the ``/* clang-format off */``.  It ensures that *clang-format*
   won't touch the following line.  This is necessary because MicroPython's
   codegen scripts can'd deal with newlines in the ``MP_REGISTER_MODULE`` macro.

Build Integration
~~~~~~~~~~~~~~~~~
Next, you need to add your module to ``pycardium/meson.build``. There is a list
of module sources at the very top called ``modsrc`` where you need to add your
file (e.g. ``modules/example.c``).

QSTR Definitions
~~~~~~~~~~~~~~~~
If you now run ``ninja -C build/``, you will hit a few errors regarding missing
QSTR definitions.  With the example module above, they will look similar to
this:

.. code-block:: text

   ../pycardium/modules/example.c:15:46: error: 'MP_QSTR_example' undeclared here (not in a function)
      15 |  {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_example)},

To fix these errors, you need to add all QSTRs your module needs to
``pycardium/modules/qstrdefs.h``.  Add a section for your module where you
define all QSTRs you need:

.. code-block:: cpp

   /* example */
   Q(example)
   Q(hello)

Each ``Q(...)`` will define into a corresponding ``MP_QSTR_...``.  So
``Q(example)`` corresponds to ``MP_QSTR_example``.

Enable Module
~~~~~~~~~~~~~
The last step is to actually enable inclusion of your module into the firmware.
Do this by adding a define in ``pycardium/mpconfigport.h``:

.. code-block:: cpp

   #define MODULE_EXAMPLE_ENABLED              (1)

The name of the define is the one from the last line in the module source above.

Wrapping Epicardium API
-----------------------
Most modules will probably make use of the :ref:`Epicardium API
<epicardium_api_overview>`. Doing so does not require any extra work, you can
just call the API from your module code.  You should check the input that your
module got from MicroPython before sending data off to Epicardium.  For
example, raise a ``ValueError`` if an integer is too big to fit into the type
specified by the API.  You should also gracefully handle errors returned by API
calls.  As most API calls use *errno* codes, you can just wrap them in an
``OSError``:

.. code-block:: cpp

   int ret = epic_bma_get_accel(&values);

   if (ret < 0) {
          mp_raise_OSError(-ret);
   }

QSTRs
-----
QSTRs are so called “interned strings”. This means they are not allocated like
normal python objects but instead live in flash and are indexed. This allow
MicroPython to very efficiently use them as identifiers. According to them,
comparing two QSTR is as fast as comparing integers.

Unfortunately, the way these QSTRs are collected from the source files is quite
weird.  MicroPython comes with a few python scripts (namely `makeqstrdefs.py`_
and `makeqstrdata.py`_) that parse the C source files and search for uses of
``MP_QSTR_*``.  These are then sorted and indexed into a header file called
``qstrdefs.collected.h``. This is closely tied in with their Makefiles.

As we use our own build system, we had to somehow wrap this generation to work
for us as well. This is done using a few scripts in `lib/micropython`_.
Currently, our build system does **not** parse the module sources to search for
QSTRs.  Instead all QSTRs needed by modules need to be defined in the header
``pycardium/modules/qstrdefs.h``.

.. _makeqstrdefs.py: https://github.com/micropython/micropython/blob/master/py/makeqstrdefs.py
.. _makeqstrdata.py: https://github.com/micropython/micropython/blob/master/py/makeqstrdata.py
.. _lib/micropython: https://git.card10.badge.events.ccc.de/card10/firmware/tree/master/lib/micropython

Functions for MicroPython
-------------------------
As shown in the example above, you can create functions that can be called from
MicroPython code.  These functions always have one of the following signatures.
To create a MicroPython object for a function, you need the macro call shown
after each signature.  Please place these calls directly after the function
body as shown above.

.. code-block:: cpp

   /* Function with 0 arguments */
   mp_obj_t mp_example_fun0(void);
   static MP_DEFINE_CONST_FUN_OBJ_0(example_fun0_obj, mp_example_fun0);

   /* Function with 1 argument */
   mp_obj_t mp_example_fun1(mp_obj_t arg0_in);
   static MP_DEFINE_CONST_FUN_OBJ_1(example_fun1_obj, mp_example_fun0);

   /* Function with 2 arguments */
   mp_obj_t mp_example_fun2(mp_obj_t arg0_in, mp_obj_t arg1_in);
   static MP_DEFINE_CONST_FUN_OBJ_2(example_fun2_obj, mp_example_fun0);

   /* Function with 3 arguments */
   mp_obj_t mp_example_fun3(mp_obj_t arg0_in, mp_obj_t arg1_in, mp_obj_t arg2_in);
   static MP_DEFINE_CONST_FUN_OBJ_3(example_fun3_obj, mp_example_fun0);

   /* Function with 4 or more arguments */
   mp_obj_t mp_example_fun4(size_t n_args, mp_obj_t *args);
   static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(example_fun4_obj, 4, 4, mp_example_fun4);

For functions with 4 or more arguments, you need to use the
``MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN`` macro and instead of directly accessing
the arguments, you get an array.  The macro gets two numbers (they are the same
in the example above):  The minimum and maximum number of arguments.

MicroPython Objects
-------------------
**TL;DR**: Look at |obj.h|_.  It contains most functions needed to create,
access, and modify MicroPython objects.

.. |obj.h| replace:: ``lib/micropython/micropython/py/obj.h``
.. _obj.h: https://github.com/micropython/micropython/blob/master/py/obj.h

For C modules to interface with MicroPython, you need to be able to interface
with MicroPython objects.  The generic type of an object is ``mp_obj_t``.  As
you can see in the example above, this is also what a function gets as
arguments and returns back to MicroPython.  You can cast ``mp_obj_t`` to
concrete object types if you made sure it is actually the correct type.

Booleans
~~~~~~~~
``True`` and ``False`` are *const* objects, called ``mp_const_true`` and
``mp_const_false`` respectively.  A usual equality check can be used:

.. code-block:: cpp

   #include "py/obj.h"
   #include "py/runtime.h"

   if (bool_obj == mp_const_true) {
       /* is a boolean true */
   } else if (bool_obj == mp_const_false) {
       /* is a boolean false */
   } else {
       mp_raise_TypeError("arg 0 is not a boolean");
   }

Integers
~~~~~~~~
As long as your integers stay within **31**-bit limits, integers are stored very
efficiently and can be accessed and created like this:

.. code-block:: cpp

   #include "py/obj.h"

   /* Create a new integer which is < 2^31 */
   mp_obj_t int_obj = MP_OBJ_NEW_SMALL_INT(0xc0ffee);

   /* Check if an integer is small and if so, extract it */
   if (mp_obj_is_small_int(int_obj)) {
           int int_value = MP_OBJ_SMALL_INT_VALUE(int_obj);
   }

For bigger integers or if you are uncertain about the limits, use the following
functions:

.. code-block:: cpp

   #include "py/obj.h"

   /* Create new integer objects in various sizes and signedness */
   mp_obj_t int0_obj = mp_obj_new_int((mp_int_t)value);
   mp_obj_t int1_obj = mp_obj_new_int_from_uint((mp_uint_t)value);
   mp_obj_t int2_obj = mp_obj_new_int_from_ll((long long)value);
   mp_obj_t int3_obj = mp_obj_new_int_from_ull((unsigned long long)value);

   /* Check if a value is an integer */
   if (mp_obj_is_integer(int_obj)) {

           /* Get an integer */
           int int0 = mp_obj_get_int(int_obj);
           int int0 = mp_obj_get_int_truncated(int_obj);
   }

   int value;
   if (!mp_obj_get_int_maybe(int_obj, &value)) {
      /* Not an integer! */
   }

To get really big integers you have to use

.. code-block:: cpp

   #include "py/obj.h"
   #include "py/objint.h"

   long long value;
   mp_obj_int_to_bytes_impl(int_obj, 8, (byte*)&value);

Strings
~~~~~~~
As in CPython, MicroPython also has multiple string representations:  There is
``str``, ``bytes``, and ``bytearray`` but also the above mentions ``QSTR``\ s.
Ideally, code should work with as many of these as possible and to ensure this,
please use these generic functions:

.. code-block:: cpp

   #include "py/obj.h"

   /* Create a new string object */
   char buf[] = "Hello MicroPython!";
   mp_obj_t str_obj = mp_obj_new_str(buf, sizeof(buf));

   /* Check if an object is a string */
   if (mp_obj_is_str(str_obj)) {
           /* Either str or QSTR */
   }
   if (mp_obj_is_str_or_bytes) {
           /* Either str, QSTR, or bytes */
   }

   /*
    * Get a char array from a string object.
    * CAUTION! This string is not necessarily null terminated!
    */
   char *str_data;
   size_t str_len;
   str_data = mp_obj_str_get_data(str_obj, &str_len);

Lists & Tuples
~~~~~~~~~~~~~~
While lists and tuples can be accessed using type specific functions, you
should refrain from doing so:  Use generic indexing functions instead, as they
also allow types derived from lists or tuples or types with custom indexing
implementations (i.e. duck-typing).

.. code-block:: cpp

   #include "py/obj.h"
   #include "py/runtime.h"

   /* Get the length of a list-like object */
   mp_int_t len = mp_obj_get_int(mp_obj_len(list_obj));

   /* Get element at a specific index */
   mp_obj_t elem5 = mp_obj_subscr(
           list_obj,
           MP_OBJ_NEW_SMALL_INT(5),
           MP_OBJ_SENTINEL
   );

To create a list or tuple:

.. code-block:: cpp

   mp_obj_t values[] = {
           MP_OBJ_NEW_SMALL_INT(0xde),
           MP_OBJ_NEW_SMALL_INT(0xad),
           MP_OBJ_NEW_SMALL_INT(0xbe),
           MP_OBJ_NEW_SMALL_INT(0xef),
   };

   /* Create a tuple */
   mp_obj_t tuple = mp_obj_new_tuple(4, values);

   /* Create a list */
   mp_obj_t list = mp_obj_new_list(4, values);
+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.
+59 −0
Original line number Diff line number Diff line
.. py:module:: buttons

``buttons`` - Push Buttons
==========================
The :py:mod:`buttons` module allows you to use card10's push buttons as input
in your scripts.

**Example**:

.. code-block:: python

   import buttons

   print("Press bottom left or right button:")

   while True:
      pressed = buttons.read(
          buttons.BOTTOM_LEFT | buttons.BOTTOM_RIGHT
      )

      if pressed != 0:
         break

   if pressed & buttons.BOTTOM_LEFT != 0:
      print("Left button pressed!")

   if pressed & buttons.BOTTOM_RIGHT != 0:
      print("Right button pressed!")

.. py:function:: read(mask)

   Read button status.

   :param int mask: Mask of buttons to check.  Create the mask by ORing
      :py:data:`buttons.BOTTOM_LEFT`, :py:data:`buttons.BOTTOM_RIGHT`,
      :py:data:`buttons.TOP_RIGHT`, and :py:data:`buttons.TOP_LEFT` (=
      :py:data:`buttons.RESET`).
   :returns: An integer with the bits for pressed buttons set.  Use the same
      costants as for the mask to check which buttons were pressed.

.. py:data:: BOTTOM_LEFT

   Bottom left button.

.. py:data:: BOTTOM_RIGHT

   Bottom right button.

.. py:data:: TOP_RIGHT

   Top right button.

.. py:data:: TOP_LEFT

   Top left button (Reset button).

.. py:data:: RESET

   Top left button (Reset button).
+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:

+21 −0
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:
+96 −0
Original line number Diff line number Diff line
.. py:module:: gpio

``gpio`` - GPIO Pins
==========================
The :py:mod:`gpio` module allows you to use card10's GPIO pins as input and
output in your scripts.

**Example**:

.. code-block:: python

   import gpio

   gpio.set_mode(gpio.WRISTBAND_1, gpio.mode.OUTPUT)
   gpio.write(gpio.WRISTBAND_1, True)

   gpio.set_mode(gpio.WRISTBAND_2, gpio.mode.INPUT | gpio.mode.PULL_UP)
   state = gpio.read(gpio.WRISTBAND_2)
   print("State of Wristband pin 2:", state)

.. py:function:: set_mode(pin, mode)

   Configure GPIO pin state.

   :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.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)

   Get GPIO pin state.

   :param int pin: ID of the pin of to get the mode of.
   :returns: An integer with the configure mode bits set.

.. py:function:: write(pin, value)

   Write a value to a GPIO pin.

   :param int pin: ID of the pin of to get the mode of.
   :param bool value: New pin value.

.. py:function:: read(pin)

   Read GPIO pin value.

   :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

   Pin ID for Wristband GPIO 1.

.. py:data:: WRISTBAND_2

   Pin ID for Wristband GPIO 2.

.. py:data:: WRISTBAND_3

   Pin ID for Wristband GPIO 3.

.. py:data:: WRISTBAND_4

   Pin ID for Wristband GPIO 4.


.. py:module:: gpio.mode

.. py:data:: OUTPUT

   Configures a pin as output.

.. py:data:: INPUT

   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.

.. py:data:: PULL_DOWN

   Enables the internal pull-down resistor of a pin.
+14 −0
Original line number Diff line number Diff line
``leds`` - LEDs
===============
The ``leds`` module provides functions to interact with card10's RGB LEDs.
This is the 11 LEDs above the display and 4 LEDs on the underside of the
top-board, in the four corners.

.. automodule:: leds
   :members:

``ledfx`` - LED Effects
=======================

.. automodule:: ledfx
   :members:
Original line number Diff line number Diff line
.. py:module:: light_sensor

``light_sensor`` - Ambient Brightness
=====================================
On the harmonic board, there is an IR-LED which can be used in reverse as a
crude brightness sensor.  Values returned are in no particular unit but seem to
be fairly stable.

.. py:function:: start()

   Turn on the ADC and start reading brightness values.  (In past this function must be
   called before any measurements can be taken.)

.. py:function:: get_reading()

   Get an ambient brightness reading.  The returned value is in no particular
   unit, though it seems to be fairly stable. The value could be between 0 and 400.  Common values:

   - ~8: Very dark are
   - ~17: Typical hackerspace brightness
   - >200: Direct sunlight

   :returns: A brightness reading in no particular unit

.. 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:
+131 −0
Original line number Diff line number Diff line
.. py:module:: os

``os`` - OS Functions
=====================
The ``os`` module allows access to a few core functionalities of Epicardium and
functions found in CPythons ``os`` module.

CPython-Like
------------

.. py:function:: listdir(dir)

   List contents of a directory.

   :param str dir: Path to the directory to list.
   :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.


Card10-Specific
---------------

.. py:function:: exit(ret = None)

   Exit from the current app and return to the menu.


   :param int ret: Optional return code, same semantics as Posix (``0`` means
      success).
   :return: This function will never return.

.. py:function:: exec(name)

   Try executing a new app, stopping the currently running one.

   ``name`` is the path to either a l0dable (ending in ``.elf``) or a python
   script (ending in ``.py``).  If the path does not lead to an executable file,
   ``os.exec()`` will raise an exception.

   :param str name: Path to new app/script/l0dable.
   :return: This function never returns.  It can, however raise an exception.

.. py:function:: read_battery()

   Read the current 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

.. py:function:: reset()

   Reboot card10.

   .. warning::

      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
+88 −0
Original line number Diff line number Diff line
.. _pycardium_overview:

Overview
========
Pycardium is what we call our MicroPython port for card10.  For the most part,
it will behave like a normal MicroPython, enhanced by a few modules to
interface with card10's hardware.  We have also included modules from Python's
standard library so you have lots of familiar functionality at hand to start
hacking!

.. note::

   Right now, MicroPython is only available via the serial console.  card10
   makes its console available as a CDC ACM device via USB or, if you have a
   debugger, on the debugging serial port.

   This means, you can only interact with Python through the REPL console for
   now. However, this will soon change, once `#10`_ and `#11`_ are implemented.

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

   In other words: It is currently not possible to execute Python code
   from .py files on the filesystem. You have to test new modules using the
   serial console.

Serial Console
--------------
If you have a debugger, you can just use the debuggers serial console.
Otherwise, card10 will open a serial usb-device on its usb-port. On most Linux
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**: ``sudo screen /dev/ttyACM0 115200``
* **picocom**: ``sudo picocom -b 115200 /dev/ttyACM0``

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.

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

   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.
Original line number Diff line number Diff line
.. py:module:: personal_state

``personal_state`` - Personal State
===================================
The :py:mod:`personal_state` module allows you to set and get the card10 users
`personal state`_ from your script. The personal state is displayed on the
top-left LED on the bottom of the harmonics board. While the personal state is
set the LED can't be controlled by the :py:mod:`leds` module.

.. _personal state: https://card10.badge.events.ccc.de/ps/

**Example**:

.. code-block:: python

   import personal_state

   # Enable the "camp" state only while the app is running.
   personal_state.set(personal_state.CAMP, False)

   # Enable the "chaos" state and keep it after the app exits.
   personal_state.set(personal_state.CHAOS, True)

   # Query the currently configured state and if it's persistent.
   state, persistent = personal_state.get()

   # Clear the currently configured state
   personal_state.clear()

.. py:function:: set(state, persistent)

   Set the users personal state.

   :param int state: ID of the personal state to set. Must be one of
      :py:data:`personal_state.NO_CONTACT`, :py:data:`personal_state.CHAOS`,
      :py:data:`personal_state.COMMUNICATION`, :py:data:`personal_state.CAMP`.
   :param int persistent: Controls whether the personal state is persistent. A
      persistent state is not reset when the pycardium application is changed
      or restarted. In persistent mode the personal state LED is not
      controllable by the pycardium application.

.. py:function:: clear()

   Clears a previously set personal state.

   If no personal state was set this function does nothing. It does not matter
   if a set state is marked as persistent or not.

.. py:function:: get()

   Get the users personal state.

   :returns: A tuple containing the currently set state and a boolean
      indicating if it's persistent or not.

.. py:data:: NO_STATE

   State ID reported when no personal state is set.

.. py:data:: NO_CONTACT

   State ID for the "No Contact" personal state.

.. py:data:: CHAOS

   State ID for the "Chaos" personal state.

.. py:data:: COMMUNICATION

   State ID for the "Communicatoin" personal state.

.. py:data:: CAMP

   State ID for the "Camp" personal state.
+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
+7 −0
Original line number Diff line number Diff line
``pride`` - Pride flags
=======================
The ``pride`` module provides an easy interface to print pride flags to the top LEDs and the display.

.. automodule:: pride
   :members:
+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
+32 −0
Original line number Diff line number Diff line
.. py:module:: vibra

``vibra`` - Vibration Motor
===========================

.. py:function:: vibra.vibrate(millis)

   Turn on the vibration motor for a certain duration.

   **Example**:

   .. code-block:: python

      import vibra

      # Turn on vibration motor for 60 ms
      vibra.vibrate(60)

   :param int millis:  Duration for the vibration motor to be on.


.. py:function:: vibra.set(state)

   Permanently set the state of the vibration motor to either *on* or *off*.

   .. warning::

      If your code for some reason crashes between turning the motor on and
      back off again, the motor will continue running.  Consider using
      :py:func:`vibra.vibrate` instead.

   :param bool state: ``True`` to turn on, ``False`` to turn the motor off.
+29 −111

File changed.

Preview size limit exceeded, changes collapsed.

ble/.cproject

deleted100644 → 0
+0 −115

File deleted.

Preview size limit exceeded, changes collapsed.

ble/.gdbinit

deleted100644 → 0
+0 −3
Original line number Diff line number Diff line
file build/max32665.elf
target remote localhost:3333

ble/.project

deleted100644 → 0
+0 −26

File deleted.

Preview size limit exceeded, changes collapsed.

ble/BLE_fit.launch

deleted100644 → 0
+0 −62

File deleted.

Preview size limit exceeded, changes collapsed.

ble/Makefile

deleted100644 → 0
+0 −138

File deleted.

Preview size limit exceeded, changes collapsed.

ble/build_image

deleted100755 → 0
+0 −5
Original line number Diff line number Diff line
make clean
make
make build/max32665.bin
cp build/max32665.bin card10.bin
../bootloader/crc_patch.py card10.bin

ble/fit.launch

deleted100644 → 0
+0 −65

File deleted.

Preview size limit exceeded, changes collapsed.

ble/fit_main.c

deleted100644 → 0
+0 −750

File deleted.

Preview size limit exceeded, changes collapsed.

ble/main.c

deleted100644 → 0
+0 −246

File deleted.

Preview size limit exceeded, changes collapsed.

ble/stack_fit.c

deleted100644 → 0
+0 −94

File deleted.

Preview size limit exceeded, changes collapsed.

ble/svc_dis.c

deleted100644 → 0
+0 −297

File deleted.

Preview size limit exceeded, changes collapsed.

bootloader/.gdbinit

deleted100644 → 0
+0 −3

File deleted.

Preview size limit exceeded, changes collapsed.

+15 −0

File added.

Preview size limit exceeded, changes collapsed.

bootloader/init.gdb

0 → 100644
+3 −0

File added.

Preview size limit exceeded, changes collapsed.

+277 −181

File changed.

Preview size limit exceeded, changes collapsed.

File changed.

Preview size limit exceeded, changes collapsed.

+5 −3

File changed.

Preview size limit exceeded, changes collapsed.

File changed.

Preview size limit exceeded, changes collapsed.

default.nix

0 → 100644
+55 −0

File added.

Preview size limit exceeded, changes collapsed.

+19 −0

File added.

Preview size limit exceeded, changes collapsed.

+17 −0

File added.

Preview size limit exceeded, changes collapsed.

+17 −0

File added.

Preview size limit exceeded, changes collapsed.

+133 −0

File added.

Preview size limit exceeded, changes collapsed.

+38 −0

File added.

Preview size limit exceeded, changes collapsed.

+54 −0

File added.

Preview size limit exceeded, changes collapsed.

+276 −0

File added.

Preview size limit exceeded, changes collapsed.

+295 −0

File added.

Preview size limit exceeded, changes collapsed.

epicardium/ble/ble.c

0 → 100644
+471 −0

File added.

Preview size limit exceeded, changes collapsed.

+264 −0

File added.

Preview size limit exceeded, changes collapsed.

+59 −0

File added.

Preview size limit exceeded, changes collapsed.

+891 −0

File added.

Preview size limit exceeded, changes collapsed.

epicardium/ble/cccd.h

0 → 100644
+19 −0

File added.

Preview size limit exceeded, changes collapsed.

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.

epicardium/ble/stack.c

0 → 100644
+201 −0

File added.

Preview size limit exceeded, changes collapsed.

+341 −0

File added.

Preview size limit exceeded, changes collapsed.

epicardium/ble/uart.c

0 → 100644
+218 −0

File added.

Preview size limit exceeded, changes collapsed.

epicardium/ble/uart.h

0 → 100644
+19 −0

File added.

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.

+2766 −0

File added.

Preview size limit exceeded, changes collapsed.

+173 −0

File added.

Preview size limit exceeded, changes collapsed.

+92 −0

File added.

Preview size limit exceeded, changes collapsed.

+15 −0

File added.

Preview size limit exceeded, changes collapsed.

+46 −0

File added.

Preview size limit exceeded, changes collapsed.

epicardium/l0der/elf.h

0 → 100644
+111 −0

File added.

Preview size limit exceeded, changes collapsed.

+728 −0

File added.

Preview size limit exceeded, changes collapsed.

+32 −0

File added.

Preview size limit exceeded, changes collapsed.

epicardium/main.c

0 → 100644
+244 −0

File added.

Preview size limit exceeded, changes collapsed.

epicardium/meson.build

0 → 100644
+129 −0

File added.

Preview size limit exceeded, changes collapsed.

epicardium/os/config.c

0 → 100644
+666 −0

File added.

Preview size limit exceeded, changes collapsed.

epicardium/os/config.h

0 → 100644
+17 −0

File added.

Preview size limit exceeded, changes collapsed.

epicardium/os/core.h

0 → 100644
+101 −0

File added.

Preview size limit exceeded, changes collapsed.

epicardium/os/log.c

0 → 100644
+55 −0

File added.

Preview size limit exceeded, changes collapsed.

epicardium/os/mutex.c

0 → 100644
+70 −0

File added.

Preview size limit exceeded, changes collapsed.

epicardium/os/mutex.h

0 → 100644
+91 −0

File added.

Preview size limit exceeded, changes collapsed.

epicardium/os/panic.c

0 → 100644
+158 −0

File added.

Preview size limit exceeded, changes collapsed.

epicardium/support.c

0 → 100644
+235 −0

File added.

Preview size limit exceeded, changes collapsed.

+141 −0

File added.

Preview size limit exceeded, changes collapsed.

+15 −0

File added.

Preview size limit exceeded, changes collapsed.

+459 −0

File added.

Preview size limit exceeded, changes collapsed.

+32 −0

File added.

Preview size limit exceeded, changes collapsed.

flash-all.gdb

0 → 100644
+15 −0

File added.

Preview size limit exceeded, changes collapsed.

flash-bootloader.gdb

0 → 100644
+8 −0

File added.

Preview size limit exceeded, changes collapsed.

flash-both.gdb

0 → 100644
+10 −0

File added.

Preview size limit exceeded, changes collapsed.

flash-epicardium.gdb

0 → 100644
+8 −0

File added.

Preview size limit exceeded, changes collapsed.

flash-pycardium.gdb

0 → 100644
+8 −0

File added.

Preview size limit exceeded, changes collapsed.

hw-tests/bmatest/.gdbinit

deleted100644 → 0
+0 −2

File deleted.

Preview size limit exceeded, changes collapsed.

hw-tests/bmetest/.gdbinit

deleted100644 → 0
+0 −2

File deleted.

Preview size limit exceeded, changes collapsed.

hw-tests/ecgtest/.gdbinit

deleted100644 → 0
+0 −2

File deleted.

Preview size limit exceeded, changes collapsed.

hw-tests/imutest/.gdbinit

deleted100644 → 0
+0 −2

File deleted.

Preview size limit exceeded, changes collapsed.

hw-tests/ips/.gdbinit

deleted100644 → 0
+0 −2

File deleted.

Preview size limit exceeded, changes collapsed.

hw-tests/ips/init.gdb

0 → 100644
+3 −0

File added.

Preview size limit exceeded, changes collapsed.

File changed.

Preview size limit exceeded, changes collapsed.

File changed.

Preview size limit exceeded, changes collapsed.

init.gdb

0 → 100644
+35 −0

File added.

Preview size limit exceeded, changes collapsed.

l0dables/blinky/main.c

0 → 100644
+56 −0

File added.

Preview size limit exceeded, changes collapsed.

l0dables/lib/crt.s

0 → 100644
+283 −0

File added.

Preview size limit exceeded, changes collapsed.

+152 −0

File added.

Preview size limit exceeded, changes collapsed.

+94 −0

File added.

Preview size limit exceeded, changes collapsed.

+18 −0

File added.

Preview size limit exceeded, changes collapsed.

l0dables/meson.build

0 → 100644
+3 −0

File added.

Preview size limit exceeded, changes collapsed.

+24 −0

File added.

Preview size limit exceeded, changes collapsed.

+21 −0

File added.

Preview size limit exceeded, changes collapsed.