diff --git a/.checkpatch.conf b/.checkpatch.conf new file mode 100644 index 0000000..bd2a63b --- /dev/null +++ b/.checkpatch.conf @@ -0,0 +1,37 @@ +# Copyright (c) 2023 Zephyr Project members and individual contributors +# Copyright (c) 2023 Golioth, Inc. +# SPDX-License-Identifier: Apache-2.0 + +--emacs +--summary-file +--show-types +--max-line-length=100 +--min-conf-desc-length=1 +--typedefsfile=../deps/zephyr/scripts/checkpatch/typedefsfile + +--ignore BRACES +--ignore PRINTK_WITHOUT_KERN_LEVEL +--ignore SPLIT_STRING +--ignore VOLATILE +--ignore CONFIG_EXPERIMENTAL +--ignore PREFER_KERNEL_TYPES +--ignore PREFER_SECTION +--ignore AVOID_EXTERNS +--ignore NETWORKING_BLOCK_COMMENT_STYLE +--ignore DATE_TIME +--ignore MINMAX +--ignore CONST_STRUCT +--ignore FILE_PATH_CHANGES +--ignore SPDX_LICENSE_TAG +--ignore C99_COMMENT_TOLERANCE +--ignore REPEATED_WORD +--ignore UNDOCUMENTED_DT_STRING +--ignore DT_SPLIT_BINDING_PATCH +--ignore DT_SCHEMA_BINDING_PATCH +--ignore TRAILING_SEMICOLON +--ignore COMPLEX_MACRO +--ignore MULTISTATEMENT_MACRO_USE_DO_WHILE +--ignore ENOSYS + +--no-tree +--exclude patches diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..b785676 --- /dev/null +++ b/.clang-format @@ -0,0 +1,90 @@ +# Copyright (c) 2023 Zephyr Project members and individual contributors +# Copyright (c) 2023 Golioth, Inc. +# SPDX-License-Identifier: Apache-2.0 + +# Copied from Zephyr: https://github.com/zephyrproject-rtos/zephyr/blob/main/.clang-format + +# Note: The list of ForEachMacros can be obtained using: +# +# git grep -h '^#define [^[:space:]]*FOR_EACH[^[:space:]]*(' include/ \ +# | sed "s,^#define \([^[:space:]]*FOR_EACH[^[:space:]]*\)(.*$, - '\1'," \ +# | sort | uniq +# +# References: +# - https://clang.llvm.org/docs/ClangFormatStyleOptions.html + +--- +BasedOnStyle: LLVM +AlignConsecutiveMacros: AcrossComments +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AttributeMacros: + - __aligned + - __deprecated + - __packed + - __printf_like + - __syscall + - __subsystem +BitFieldColonSpacing: After +BreakBeforeBraces: Linux +ColumnLimit: 100 +ConstructorInitializerIndentWidth: 8 +ContinuationIndentWidth: 8 +ForEachMacros: + - 'FOR_EACH' + - 'FOR_EACH_FIXED_ARG' + - 'FOR_EACH_IDX' + - 'FOR_EACH_IDX_FIXED_ARG' + - 'FOR_EACH_NONEMPTY_TERM' + - 'RB_FOR_EACH' + - 'RB_FOR_EACH_CONTAINER' + - 'SYS_DLIST_FOR_EACH_CONTAINER' + - 'SYS_DLIST_FOR_EACH_CONTAINER_SAFE' + - 'SYS_DLIST_FOR_EACH_NODE' + - 'SYS_DLIST_FOR_EACH_NODE_SAFE' + - 'SYS_SFLIST_FOR_EACH_CONTAINER' + - 'SYS_SFLIST_FOR_EACH_CONTAINER_SAFE' + - 'SYS_SFLIST_FOR_EACH_NODE' + - 'SYS_SFLIST_FOR_EACH_NODE_SAFE' + - 'SYS_SLIST_FOR_EACH_CONTAINER' + - 'SYS_SLIST_FOR_EACH_CONTAINER_SAFE' + - 'SYS_SLIST_FOR_EACH_NODE' + - 'SYS_SLIST_FOR_EACH_NODE_SAFE' + - '_WAIT_Q_FOR_EACH' + - 'Z_FOR_EACH' + - 'Z_FOR_EACH_ENGINE' + - 'Z_FOR_EACH_EXEC' + - 'Z_FOR_EACH_FIXED_ARG' + - 'Z_FOR_EACH_FIXED_ARG_EXEC' + - 'Z_FOR_EACH_IDX' + - 'Z_FOR_EACH_IDX_EXEC' + - 'Z_FOR_EACH_IDX_FIXED_ARG' + - 'Z_FOR_EACH_IDX_FIXED_ARG_EXEC' + - 'Z_GENLIST_FOR_EACH_CONTAINER' + - 'Z_GENLIST_FOR_EACH_CONTAINER_SAFE' + - 'Z_GENLIST_FOR_EACH_NODE' + - 'Z_GENLIST_FOR_EACH_NODE_SAFE' +# Disabled for now, see bug https://github.com/zephyrproject-rtos/zephyr/issues/48520 +#IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^".*\.h"$' + Priority: 0 + - Regex: '^<(assert|complex|ctype|errno|fenv|float|inttypes|limits|locale|math|setjmp|signal|stdarg|stdbool|stddef|stdint|stdio|stdlib|string|tgmath|time|wchar|wctype)\.h>$' + Priority: 1 + - Regex: '^\<zephyr/.*\.h\>$' + Priority: 2 + - Regex: '.*' + Priority: 3 +IndentCaseLabels: false +IndentWidth: 8 +# SpaceBeforeParens: ControlStatementsExceptControlMacros # clang-format >= 13.0 +SortIncludes: false +UseTab: Always +WhitespaceSensitiveMacros: + - STRINGIFY + - Z_STRINGIFY + - IF_ENABLED diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c346a38 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,98 @@ +# Copyright (c) 2023 Zephyr Project members and individual contributors +# Copyright (c) 2023 Golioth, Inc. +# SPDX-License-Identifier: Apache-2.0 + +# Copied from Zephyr: https://github.com/zephyrproject-rtos/zephyr/blob/main/.editorconfig + +# EditorConfig: https://editorconfig.org/ + +# top-most EditorConfig file +root = true + +# All (Defaults) +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +max_line_length = 100 + +# Assembly +[*.S] +indent_style = tab +indent_size = 8 + +# C +[*.{c,h}] +indent_style = tab +indent_size = 8 + +# C++ +[*.{cpp,hpp}] +indent_style = tab +indent_size = 8 + +# Linker Script +[*.ld] +indent_style = tab +indent_size = 8 + +# Python +[*.py] +indent_style = space +indent_size = 4 + +# Perl +[*.pl] +indent_style = tab +indent_size = 8 + +# reStructuredText +[*.rst] +indent_style = space +indent_size = 3 + +# YAML +[*.{yml,yaml}] +indent_style = space +indent_size = 2 + +# Shell Script +[*.sh] +indent_style = space +indent_size = 4 + +# Windows Command Script +[*.cmd] +end_of_line = crlf +indent_style = tab +indent_size = 8 + +# Valgrind Suppression File +[*.supp] +indent_style = space +indent_size = 3 + +# CMake +[{CMakeLists.txt,*.cmake}] +indent_style = space +indent_size = 2 + +# Makefile +[Makefile] +indent_style = tab +indent_size = 8 + +# Device tree +[*.{dts,dtsi,overlay}] +indent_style = tab +indent_size = 8 + +# Git commit messages +[COMMIT_EDITMSG] +max_line_length = 75 + +# Kconfig +[Kconfig*] +indent_style = tab +indent_size = 8 diff --git a/.github/workflows/build_zephyr.yml b/.github/workflows/build_zephyr.yml new file mode 100644 index 0000000..ce14771 --- /dev/null +++ b/.github/workflows/build_zephyr.yml @@ -0,0 +1,82 @@ +# Copyright (c) 2023 Golioth, Inc. +# SPDX-License-Identifier: Apache-2.0 + +name: Build Zephyr binaries + +on: + workflow_dispatch: + inputs: + ZEPHYR_SDK: + required: true + type: string + default: 0.16.3 + BOARD: + required: true + type: string + default: nrf52840dk_nrf52840 + ARTIFACT: + required: true + type: boolean + default: false + TAG: + type: string + + workflow_call: + inputs: + ZEPHYR_SDK: + required: true + type: string + BOARD: + required: true + type: string + ARTIFACT: + required: true + type: boolean + TAG: + type: string + +jobs: + build: + runs-on: ubuntu-latest + + container: golioth/golioth-zephyr-base:${{ inputs.ZEPHYR_SDK }}-SDK-v0 + + env: + ZEPHYR_SDK_INSTALL_DIR: /opt/toolchains/zephyr-sdk-${{ inputs.ZEPHYR_SDK }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + path: app + - name: Setup West workspace + run: | + west init -l app + west update --narrow -o=--depth=1 + west zephyr-export + pip3 install -r deps/zephyr/scripts/requirements-base.txt + # Needed for TF-M + pip3 install cryptography pyasn1 pyyaml cbor>=1.0.0 imgtool>=1.9.0 jinja2 click + + - name: Build with West + run: | + west build -p -b ${{ inputs.BOARD }} app + + - name: Prepare artifacts + if: inputs.ARTIFACT == true && inputs.TAG != '' + + run: | + cd build/zephyr + mkdir -p artifacts + mv merged.hex ./artifacts/golioth-${{ github.event.repository.name }}_${{ inputs.TAG }}_${{ inputs.BOARD }}_full.hex + mv app_update.bin ./artifacts/golioth-${{ github.event.repository.name }}_${{ inputs.TAG }}_${{ inputs.BOARD }}_update.bin + mv zephyr.elf ./artifacts/golioth-${{ github.event.repository.name }}_${{ inputs.TAG }}_${{ inputs.BOARD }}.elf + + # Run IDs are unique per repo but are reused on re-runs + - name: Save artifact + if: inputs.ARTIFACT == true + uses: actions/upload-artifact@v3 + with: + name: build_artifacts_${{ github.run_id }} + path: | + build/zephyr/artifacts/* diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1433f8c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,52 @@ +# Copyright (c) 2023 Golioth, Inc. +# SPDX-License-Identifier: Apache-2.0 + +name: Create Release + +on: + workflow_dispatch: + inputs: + version: + description: 'Release Version.' + required: true + default: 'v0.0.0' + type: string + +jobs: + build-binaries: + strategy: + matrix: + ZEPHYR_SDK: [0.16.3] + BOARD: ["nrf52840dk_nrf52840","adafruit_feather_nrf52840"] + + uses: ./.github/workflows/build_zephyr.yml + with: + ZEPHYR_SDK: ${{ matrix.ZEPHYR_SDK }} + BOARD: ${{ matrix.BOARD }} + ARTIFACT: true + TAG: ${{ inputs.version }} + + upload-binaries: + needs: build-binaries + + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Download artifact + uses: actions/download-artifact@v3 + with: + name: build_artifacts_${{ github.run_id }} + path: ~/artifacts + + - name: Create Release manually with GH CLI + run: gh release create --title ${{ inputs.version }} --draft ${{ inputs.version }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload artifacts to release + run: gh release upload --clobber ${{ inputs.version }} ~/artifacts/*.* + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..2329f00 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,17 @@ +# Copyright (c) 2023 Golioth, Inc. +# SPDX-License-Identifier: Apache-2.0 + +name: Test firmware + +on: + pull_request: + + push: + +jobs: + test_build: + uses: ./.github/workflows/build_zephyr.yml + with: + ZEPHYR_SDK: 0.16.3 + BOARD: nrf52840dk_nrf52840 + ARTIFACT: false diff --git a/.gitignore b/.gitignore index fbd2cdd..3938b60 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ -deps/ -build/ -.vscode/ +# Copyright (c) 2023 Golioth, Inc. +# SPDX-License-Identifier: Apache-2.0 + +build*/ +.vscode +.cache credentials.conf +__pycache__/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..538d455 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,18 @@ +<!-- Copyright (c) 2023 Golioth, Inc. --> +<!-- SPDX-License-Identifier: Apache-2.0 --> + +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.0.0] - TBA + +### Added +- Use of comercialy available GL-S200 Thread Border Router +- Added support for Adafruit Feather nRF52840 Express board +- Based on the [Reference Design Template](https://github.com/golioth/reference-design-template) +- Added a CHANGELOG.md to track changes moving forward. + diff --git a/CMakeLists.txt b/CMakeLists.txt index 06b3172..e2d93bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,16 +1,16 @@ -# -# Copyright (c) 2020 Nordic Semiconductor ASA -# -# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause -# +# Copyright (c) 2022-2023 Golioth, Inc. +# SPDX-License-Identifier: Apache-2.0 + cmake_minimum_required(VERSION 3.20.0) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) project(openthread) -# NORDIC SDK APP START target_sources(app PRIVATE src/main.c) -# NORDIC SDK APP END +target_sources(app PRIVATE src/app_rpc.c) +target_sources(app PRIVATE src/app_settings.c) +target_sources(app PRIVATE src/app_state.c) +target_sources(app PRIVATE src/app_sensors.c) -target_sources_ifdef(CONFIG_BT_NUS app PRIVATE src/ble_utils.c) +add_subdirectory_ifdef(CONFIG_ALUDEL_BATTERY_MONITOR src/battery_monitor) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index dd41a2b..b325a7b 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,3 +1,6 @@ +<!-- Copyright (c) 2023 Golioth, Inc. --> +<!-- SPDX-License-Identifier: Apache-2.0 --> + # Contributor Covenant Code of Conduct ## Our Pledge diff --git a/Kconfig b/Kconfig index 27938be..da365e1 100644 --- a/Kconfig +++ b/Kconfig @@ -1,13 +1,16 @@ -# -# Copyright (c) 2020 Nordic Semiconductor ASA -# -# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause -# +# Copyright (c) 2022-2023 Golioth, Inc. +# SPDX-License-Identifier: Apache-2.0 -menu "Zephyr Kernel" -source "Kconfig.zephyr" -endmenu +mainmenu "Golioth application options" + +if DNS_RESOLVER + +config DNS_SERVER_IP_ADDRESSES + default y -module = GOLIOTH_THREAD -module-str = Golioth Thread -source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config" +config DNS_SERVER1 + default "1.1.1.1" + +endif # DNS_RESOLVER + +source "Kconfig.zephyr" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.rst b/README.rst index 901736d..c4ac4a5 100644 --- a/README.rst +++ b/README.rst @@ -1,58 +1,68 @@ -Golioth OpenThread Demo -######################### +.. + Copyright (c) 2022-2024 Golioth, Inc. + SPDX-License-Identifier: Apache-2.0 -Overview -******** +Golioth OpenThread Demo +####################### -A demonstration of a Zephyr device connecting to Golioth over IPv6 via Thread Protocol. +This repository contains the firmware source code and `pre-built release +firmware images <releases_>`_ for the Golioth OpenThread Demo. -This is a standalone repository that will download all required files and dependencies. This directory will take up a good amount of room, as it will contain the latest Nordic NCS in the ``deps`` repository.. +The full project details are available on the `Golioth Thread Demo Project Page`_. Requirements ************ - Golioth device PSK credentials -- A running Thread Border Router with NAT64 (we will be using an OpenThread Border Router - OTBR) -- Thread network name and PSK key -- `The Laird BT510 <https://www.lairdconnect.com/iot-devices/iot-sensors/bt510-bluetooth-5-long-range-ip67-multi-sensor>`_. - -This demo can also work on the `Nordic nRF52840-DK <https://www.nordicsemi.com/Products/Development-hardware/nrf52840-dk>`_, but will not have on-board sensors. All build commands will explicitly call out the BT510. - -There are additional instructions around setting up RCP and OTBR on `our documentation page <https://golioth.github.io/golioth-openthread-demo-docs>`_. +- A running Thread Border Router with NAT64 translation (we will be using the + commercially available off-the-shelf `GL-S200 Thread Border Router`_) +- Thread Network Name and Network Key -Usage -***** +Supported Hardware +****************** -The firmware will wait for a serial console to attach over USB. +This firmware can be built for a variety of supported hardware platforms. -Once USB is attached, it will proceed with connecting to the pre-configured -thread network. +.. pull-quote:: + [!IMPORTANT] -Upon successful connection, the green LED of the nRF52840 dongle will turn on. + In Zephyr, each of these different hardware variants is given a unique + "board" identifier, which is used by the build system to generate firmware + for that variant. -When you press a button, a new log event will be sent to the Golioth Log device service. + When building firmware using the instructions below, make sure to use the + correct Zephyr board identifier that corresponds to your hardware platform. -A full Zephyr shell is available on the USB serial console, along with openthread commands. +.. list-table:: **Nordic Semiconductor Hardware** + :header-rows: 1 -You can list available openthread commands by running ``ot help`` on the console. + * - Development Borad + - Zephyr Board + * - .. image:: images/nRF52840_DK.png + :width: 240 + - ``nrf52840dk_nrf52840`` -Download This Repository -************************ +.. list-table:: **Adafruit Hardware** + :header-rows: 1 -.. code-block:: console + * - Development Board + - Zephyr Board - west init -m https://github.com/golioth/golioth-openthread-demo.git golioth-openthread - cd golioth-openthread - west update - + * - .. image:: images/Adafurit_nRF52840_Feather.png + :width: 240 + - ``adafruit_feather_nrf52840`` +Firmware Overview +***************** +This is a Reference Design for a Thread Protocol enabled device using Zephyr +and connecting to Golioth over IPv6. Configure ``prj.conf`` -******************** +====================== -Configure the following Kconfig options +Configure the following Kconfig options: - OPENTHREAD_NETWORK_NAME - Name of your Thread network - OPENTHREAD_NETWORKKEY - Network Key of your Thread network @@ -62,45 +72,209 @@ by changing these lines in the ``prj.conf`` configuration file, e.g.: .. code-block:: cfg CONFIG_OPENTHREAD_NETWORKKEY="00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff" - CONFIG_OPENTHREAD_NETWORK_NAME="OpenThreadDemo" + CONFIG_OPENTHREAD_NETWORK_NAME="golioth-thread" -Build, Flash, Provision -*********************** +``CONFIG_OPENTHREAD_CHANNEL`` is set to ``26``. -Build and Flash -=============== +.. pull-quote:: + [!IMPORTANT] -.. code-block:: console - - west build -b bt510 app - west flash + Make sure the Thread Network Name, Thread Network Key and Thread Channel + match your Border Router configuration. -Note, this requires a board with a debugger, either on-board or on an external platform. +Supported Golioth Zephyr SDK Features +===================================== -For the Laird BT510, you will need the `Laird SWD USB programming kit <https://www.lairdconnect.com/wireless-modules/programming-kits/usb-swd-programming-kit>`_. +This firmware implements the following features from the Golioth Zephyr SDK: -Provision -========= +- `Device Settings Service <https://docs.golioth.io/firmware/zephyr-device-sdk/device-settings-service>`_ +- `LightDB State Client <https://docs.golioth.io/firmware/zephyr-device-sdk/light-db/>`_ +- `LightDB Stream Client <https://docs.golioth.io/firmware/zephyr-device-sdk/light-db-stream/>`_ +- `Logging Client <https://docs.golioth.io/firmware/zephyr-device-sdk/logging/>`_ +- `Over-the-Air (OTA) Firmware Upgrade <https://docs.golioth.io/firmware/device-sdk/firmware-upgrade>`_ +- `Remote Procedure Call (RPC) <https://docs.golioth.io/firmware/zephyr-device-sdk/remote-procedure-call>`_ -If your device is not connecting to your OpenThread network using the info in your ``prj.conf``, use the following commands on the shell (connect to the device using the programmer) +Device Settings Service +----------------------- -.. code-block:: console - - uart:~$ ot ifconfig down - uart:~$ ot dataset networkkey 00112233445566778899aabbccddeeff - uart:~$ ot dataset networkname OpenThreadDemo - uart:~$ ot dataset commit active - uart:~$ ot ifconfig up - uart:~$ ot thread start +The following settings should be set in the Device Settings menu of the +`Golioth Console`_. + +``LOOP_DELAY_S`` + Adjusts the delay between sensor readings. Set to an integer value (seconds). + + Default value is ``60`` seconds. + +LightDB Stream Service +---------------------- + +An up-counting timer is periodically sent to the ``sensor/counter`` endpoint of the +LightDB Stream service to simulate sensor data. + +LightDB State Service +--------------------- + +The concept of Digital Twin is demonstrated with the LightDB State +``example_int0`` and ``example_int1`` variables that are members of the ``desired`` +and ``state`` endpoints. + +* ``desired`` values may be changed from the cloud side. The device will recognize + these, validate them for [0..65535] bounding, and then reset these endpoints + to ``-1`` -Check your device is attempting to attach to the OTBR using the command ``ot state`` +* ``state`` values will be updated by the device whenever a valid value is + received from the ``desired`` endpoints. The cloud may read the ``state`` + endpoints to determine device status, but only the device should ever write to + the ``state`` endpoints. -Finally, add your Golioth credentials using the settings shell. Connect over serial (programmer) to your device and then apply your Golioth PSK-ID / PSK +Remote Procedure Call (RPC) Service +----------------------------------- + +The following RPCs can be initiated in the Remote Procedure Call menu of the +`Golioth Console`_. + +``reboot`` + Reboot the system. + +``set_log_level`` + Set the log level. + + The method takes a single parameter which can be one of the following integer + values: + + * ``0``: ``LOG_LEVEL_NONE`` + * ``1``: ``LOG_LEVEL_ERR`` + * ``2``: ``LOG_LEVEL_WRN`` + * ``3``: ``LOG_LEVEL_INF`` + * ``4``: ``LOG_LEVEL_DBG`` + +Local set up +************ + +Do not clone this repo using git. Zephyr's ``west`` meta tool should be used to +set up your local workspace. + +Install the Python virtual environment (recommended) +==================================================== + +.. code-block:: shell + + cd ~ + mkdir golioth-openthread-demo + python -m venv golioth-openthread-demo/.venv + source golioth-openthread-demo/.venv/bin/activate + pip install wheel west + +Use ``west`` to initialize the workspace and install dependencies +================================================================= .. code-block:: console - - uart:~$ settings set golioth/psk-id <my-psk-id@my-project> - uart:~$ settings set golioth/psk <my-psk> - uart:~$ kernel reboot cold -These will persist after updates to your firmware, so you should only need to add them once. \ No newline at end of file + cd ~/golioth-openthread-demo + west init -m git@github.com:golioth/golioth-openthread-demo.git . + west update + west zephyr-export + pip install -r deps/zephyr/scripts/requirements.txt + +Building the application +************************ + +Build the Zephyr sample application from the top-level workspace of your project. +After a successful build you will see a new ``build/`` directory. + +Note that this git repository was cloned into the ``app`` folder, so any changes +you make to the application itself should be committed inside this repository. +The ``build`` and ``deps`` directories in the root of the workspace are managed +outside of this git repository by the ``west`` meta-tool. + +Prior to building, update ``CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION`` in the ``prj.conf`` file to +reflect the firmware version number you want to assign to this build. Then run the following +commands to build and program the firmware onto the device. + +.. pull-quote:: + [!IMPORTANT] + + When running the commands below, make sure to replace the placeholder + ``<your_zephyr_board_id>`` with the actual Zephyr board from the table above + that matches your hardware. + + In addition, replace ``<your.semantic.version>`` with a `SemVer`_-compliant + version string (e.g. ``1.2.3``) that will be used by the DFU service when + checking for firmware updates. + +.. code-block:: text + + $ (.venv) west build -p -b <your_zephyr_board_id> app + +For example, to build firmware for the `Nordic nRF52840 DK`_-based follow-along hardware: + +.. code-block:: text + + $ (.venv) west build -p -b nrf52840dk_nrf52840 app + +Flash the firmware +================== + +.. code-block:: text + + $ (.venv) west flash + +Provision the device +==================== + +In order for the device to securely authenticate with the Golioth Cloud, we need +to provision the device with a pre-shared key (PSK). This key will persist +across reboots and only needs to be set once after the device firmware has been +programmed. In addition, flashing new firmware images with ``west flash`` should +not erase these stored settings unless the entire device flash is erased. + +Configure the PSK-ID and PSK using the device UART shell and reboot the device: + +.. code-block:: text + + uart:~$ settings set golioth/psk-id <my-psk-id@my-project> + uart:~$ settings set golioth/psk <my-psk> + uart:~$ kernel reboot cold + + +Pulling in updates from the Reference Design Template +***************************************************** + +This reference design was forked from the `Reference Design Template`_ repo. We +recommend the following workflow to pull in future changes: + +* Setup + + * Create a ``template`` remote based on the Reference Design Template + repository + +* Merge in template changes + + * Fetch template changes and tags + * Merge template release tag into your ``main`` (or other branch) + * Resolve merge conflicts (if any) and commit to your repository + +.. code-block:: shell + + # Setup + git remote add template https://github.com/golioth/reference-design-template.git + git fetch template --tags + + # Merge in template changes + git fetch template --tags + git checkout your_local_branch + git merge template_v1.0.0 + + # Resolve merge conflicts if necessary + git add resolved_files + git commit + +.. _Golioth Console: https://console.golioth.io +.. _GL-S200 Thread Border Router: https://www.gl-inet.com/products/gl-s200/ +.. _Nordic nRF52840 DK: https://www.nordicsemi.com/Products/Development-hardware/nRF52840-DK +.. _Golioth Thread Demo Project Page: https://projects.golioth.io/reference-designs/openthread-demo/ +.. _releases: https://github.com/golioth/ +.. _Zephyr Getting Started Guide: https://docs.zephyrproject.org/latest/develop/getting_started/ +.. _Developer Training: https://training.golioth.io +.. _SemVer: https://semver.org +.. _Reference Design Template: https://github.com/golioth/reference-design-template diff --git a/boards/adafruit_feather_nrf52840.conf b/boards/adafruit_feather_nrf52840.conf new file mode 100644 index 0000000..a8a564a --- /dev/null +++ b/boards/adafruit_feather_nrf52840.conf @@ -0,0 +1,48 @@ +# Copyright (c) 2024 Golioth, Inc. +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_LOG=y + +CONFIG_NETWORKING=y +CONFIG_NET_L2_OPENTHREAD=y +CONFIG_MPSL=y + +CONFIG_NET_SHELL=y +CONFIG_OPENTHREAD_SHELL=y +CONFIG_SHELL_ARGC_MAX=26 +CONFIG_SHELL_CMD_BUFF_SIZE=416 + +CONFIG_MBEDTLS_SHA1_C=n +CONFIG_FPU=y + +# TLS configuration +CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_BUILTIN=n +CONFIG_MBEDTLS_ENABLE_HEAP=y +CONFIG_MBEDTLS_HEAP_SIZE=10240 + +# PSK needs to be manually enabled to prevent ENOTSUP (-134) +CONFIG_MBEDTLS_KEY_EXCHANGE_SOME_PSK_ENABLED=y +CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED=y +CONFIG_NET_SOCKETS_SOCKOPT_TLS=y + +CONFIG_THREAD_NAME=y +CONFIG_SCHED_CPU_MASK=y +CONFIG_THREAD_ANALYZER=y + +# Shell setup +CONFIG_SHELL=y + +# Settings shell +CONFIG_SETTINGS=y +CONFIG_GOLIOTH_SETTINGS=y +CONFIG_SETTINGS_RUNTIME=y +CONFIG_GOLIOTH_SAMPLE_PSK_SETTINGS=y +CONFIG_GOLIOTH_SAMPLE_SETTINGS_AUTOLOAD=y +CONFIG_GOLIOTH_SAMPLE_SETTINGS_SHELL=y + +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_NVS=y + +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 diff --git a/boards/adafruit_feather_nrf52840.overlay b/boards/adafruit_feather_nrf52840.overlay new file mode 100644 index 0000000..bd60592 --- /dev/null +++ b/boards/adafruit_feather_nrf52840.overlay @@ -0,0 +1,21 @@ +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/ { + chosen { + zephyr,entropy = &rng; + nordic,pm-ext-flash = &gd25q16; + }; + + aliases { + golioth-led = &led1; + user-btn = &button0; + }; +}; + +&uart0 { + status = "okay"; +}; + diff --git a/boards/nrf21540dk_nrf52840.overlay b/boards/nrf21540dk_nrf52840.overlay deleted file mode 100644 index a1fb0de..0000000 --- a/boards/nrf21540dk_nrf52840.overlay +++ /dev/null @@ -1,48 +0,0 @@ -/* Copyright (c) 2020 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause - */ - -/* Default Zephyr configuration already provides GPIO support for FEM. */ - -/ { - /* - * In some default configurations within the nRF Connect SDK, - * e.g. on nRF52840, the chosen zephyr,entropy node is &cryptocell. - * This devicetree overlay ensures that default is overridden wherever it - * is set, as this application uses the RNG node for entropy exclusively. - */ - chosen { - zephyr,entropy = &rng; - }; -}; -&adc { - status = "disabled"; -}; -&uart1 { - status = "disabled"; -}; -&pwm0 { - status = "disabled"; -}; -&i2c0 { - status = "disabled"; -}; -&spi0 { - status = "disabled"; -}; -&spi1 { - status = "disabled"; -}; -&spi2 { - status = "disabled"; -}; -&spi3 { - status = "disabled"; -}; -&qspi { - status = "disabled"; -}; -&usbd { - status = "disabled"; -}; diff --git a/boards/nrf52833dk_nrf52833.overlay b/boards/nrf52833dk_nrf52833.overlay deleted file mode 100644 index 7a654d8..0000000 --- a/boards/nrf52833dk_nrf52833.overlay +++ /dev/null @@ -1,36 +0,0 @@ -/* Copyright (c) 2021 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause - */ - - -&adc { - status = "disabled"; -}; -&uart1 { - status = "disabled"; -}; -&pwm0 { - status = "disabled"; -}; -&i2c0 { - status = "disabled"; -}; -&spi0 { - status = "disabled"; -}; -&spi1 { - status = "disabled"; -}; -&spi2 { - status = "disabled"; -}; -&spi3 { - status = "disabled"; -}; -&usbd { - status = "disabled"; -}; -&gpio1 { - status = "disabled"; -}; diff --git a/boards/nrf52840dk_nrf52840.conf b/boards/nrf52840dk_nrf52840.conf new file mode 100644 index 0000000..a8a564a --- /dev/null +++ b/boards/nrf52840dk_nrf52840.conf @@ -0,0 +1,48 @@ +# Copyright (c) 2024 Golioth, Inc. +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_LOG=y + +CONFIG_NETWORKING=y +CONFIG_NET_L2_OPENTHREAD=y +CONFIG_MPSL=y + +CONFIG_NET_SHELL=y +CONFIG_OPENTHREAD_SHELL=y +CONFIG_SHELL_ARGC_MAX=26 +CONFIG_SHELL_CMD_BUFF_SIZE=416 + +CONFIG_MBEDTLS_SHA1_C=n +CONFIG_FPU=y + +# TLS configuration +CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_BUILTIN=n +CONFIG_MBEDTLS_ENABLE_HEAP=y +CONFIG_MBEDTLS_HEAP_SIZE=10240 + +# PSK needs to be manually enabled to prevent ENOTSUP (-134) +CONFIG_MBEDTLS_KEY_EXCHANGE_SOME_PSK_ENABLED=y +CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED=y +CONFIG_NET_SOCKETS_SOCKOPT_TLS=y + +CONFIG_THREAD_NAME=y +CONFIG_SCHED_CPU_MASK=y +CONFIG_THREAD_ANALYZER=y + +# Shell setup +CONFIG_SHELL=y + +# Settings shell +CONFIG_SETTINGS=y +CONFIG_GOLIOTH_SETTINGS=y +CONFIG_SETTINGS_RUNTIME=y +CONFIG_GOLIOTH_SAMPLE_PSK_SETTINGS=y +CONFIG_GOLIOTH_SAMPLE_SETTINGS_AUTOLOAD=y +CONFIG_GOLIOTH_SAMPLE_SETTINGS_SHELL=y + +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_NVS=y + +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 diff --git a/boards/nrf52840dk_nrf52840.overlay b/boards/nrf52840dk_nrf52840.overlay index 166cb29..8f74714 100644 --- a/boards/nrf52840dk_nrf52840.overlay +++ b/boards/nrf52840dk_nrf52840.overlay @@ -4,46 +4,19 @@ */ / { - /* - * In some default configurations within the nRF Connect SDK, - * e.g. on nRF52840, the chosen zephyr,entropy node is &cryptocell. - * This devicetree overlay ensures that default is overridden wherever it - * is set, as this application uses the RNG node for entropy exclusively. - */ chosen { zephyr,entropy = &rng; + nordic,pm-ext-flash = &mx25r64; + }; + + aliases { + golioth-led = &led1; + user-btn = &button0; }; }; -&adc { - status = "disabled"; -}; -&uart1 { - status = "disabled"; -}; -&pwm0 { - status = "disabled"; -}; -&i2c0 { - status = "disabled"; -}; -&spi0 { - status = "disabled"; -}; -&spi1 { - status = "disabled"; -}; -&spi2 { - status = "disabled"; -}; -&spi3 { - status = "disabled"; -}; -&qspi { - status = "disabled"; -}; -&usbd { - status = "disabled"; -}; -&gpio1 { - status = "disabled"; + +&uart0 { + status = "okay"; }; + + diff --git a/boards/nrf5340dk_nrf5340_cpuapp.conf b/boards/nrf5340dk_nrf5340_cpuapp.conf deleted file mode 100644 index bb2ff81..0000000 --- a/boards/nrf5340dk_nrf5340_cpuapp.conf +++ /dev/null @@ -1,10 +0,0 @@ -# -# Copyright (c) 2021 Nordic Semiconductor -# -# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause -# - -# Default PRNG entropy for nRF53 Series devices is CSPRNG CC312 -# which for that purpose is too slow yet -# Use Xoroshiro128+ as PRNG -CONFIG_XOROSHIRO_RANDOM_GENERATOR=y diff --git a/boards/nrf5340dk_nrf5340_cpuapp_ns.conf b/boards/nrf5340dk_nrf5340_cpuapp_ns.conf deleted file mode 100644 index 6c0ba83..0000000 --- a/boards/nrf5340dk_nrf5340_cpuapp_ns.conf +++ /dev/null @@ -1,9 +0,0 @@ -# -# Copyright (c) 2021 Nordic Semiconductor -# -# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause -# - -# Enable Trusted Firmware M -CONFIG_BUILD_WITH_TFM=y -CONFIG_OPENTHREAD_CRYPTO_PSA=y diff --git a/child_image/mcuboot/boards/adafruit_feather_nrf52840.conf b/child_image/mcuboot/boards/adafruit_feather_nrf52840.conf new file mode 100644 index 0000000..4a03a13 --- /dev/null +++ b/child_image/mcuboot/boards/adafruit_feather_nrf52840.conf @@ -0,0 +1,24 @@ +# +# Copyright (c) 2021 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# In order to provide board specific configurations to the MCUboot child image +# we also need to provide a base configuration for MCUboot. This file contains +# the basic configurations needed to successfully build and run MCUboot. + +# MCUboot requires a large stack size, otherwise an MPU fault will occur +CONFIG_MAIN_STACK_SIZE=10240 + +# Enable flash operations +CONFIG_FLASH=y + +# This must be increased to accommodate the bigger images. +CONFIG_BOOT_MAX_IMG_SECTORS=256 + +# Enable serial recovery +CONFIG_UART_CONSOLE=n +CONFIG_MCUBOOT_SERIAL=y +CONFIG_MCUBOOT_SERIAL_DIRECT_IMAGE_UPLOAD=y + diff --git a/child_image/mcuboot/boards/adafruit_feather_nrf52840.overlay b/child_image/mcuboot/boards/adafruit_feather_nrf52840.overlay new file mode 100644 index 0000000..c5f2f3a --- /dev/null +++ b/child_image/mcuboot/boards/adafruit_feather_nrf52840.overlay @@ -0,0 +1,17 @@ +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/ { + chosen { + nordic,pm-ext-flash = &gd25q16; + }; + + + aliases { + mcuboot-button0 = &button0; + }; +}; + + diff --git a/child_image/mcuboot/boards/nrf52840dk_nrf52840.conf b/child_image/mcuboot/boards/nrf52840dk_nrf52840.conf new file mode 100644 index 0000000..4a03a13 --- /dev/null +++ b/child_image/mcuboot/boards/nrf52840dk_nrf52840.conf @@ -0,0 +1,24 @@ +# +# Copyright (c) 2021 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# In order to provide board specific configurations to the MCUboot child image +# we also need to provide a base configuration for MCUboot. This file contains +# the basic configurations needed to successfully build and run MCUboot. + +# MCUboot requires a large stack size, otherwise an MPU fault will occur +CONFIG_MAIN_STACK_SIZE=10240 + +# Enable flash operations +CONFIG_FLASH=y + +# This must be increased to accommodate the bigger images. +CONFIG_BOOT_MAX_IMG_SECTORS=256 + +# Enable serial recovery +CONFIG_UART_CONSOLE=n +CONFIG_MCUBOOT_SERIAL=y +CONFIG_MCUBOOT_SERIAL_DIRECT_IMAGE_UPLOAD=y + diff --git a/child_image/mcuboot/boards/nrf52840dk_nrf52840.overlay b/child_image/mcuboot/boards/nrf52840dk_nrf52840.overlay new file mode 100644 index 0000000..82e11a6 --- /dev/null +++ b/child_image/mcuboot/boards/nrf52840dk_nrf52840.overlay @@ -0,0 +1,11 @@ +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/ { + chosen { + nordic,pm-ext-flash = &mx25r64; + }; +}; + diff --git a/images/Adafurit_nRF52840_Feather.png b/images/Adafurit_nRF52840_Feather.png new file mode 100644 index 0000000..a4f94d7 Binary files /dev/null and b/images/Adafurit_nRF52840_Feather.png differ diff --git a/images/nRF52840_DK.png b/images/nRF52840_DK.png new file mode 100644 index 0000000..6547dd0 Binary files /dev/null and b/images/nRF52840_DK.png differ diff --git a/overlay-logging.conf b/overlay-logging.conf deleted file mode 100644 index 4bb5ff2..0000000 --- a/overlay-logging.conf +++ /dev/null @@ -1,22 +0,0 @@ -# -# Copyright (c) 2022 Nordic Semiconductor -# -# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause - -# Enable net module logging -# CONFIG_NET_LOG=y -# Option for configuring log level in net config library -# CONFIG_NET_CONFIG_LOG_LEVEL_INF=y - -# Option for configuring log level in Zephyr L2 logging -# CONFIG_OPENTHREAD_L2_DEBUG=y -# CONFIG_OPENTHREAD_L2_LOG_LEVEL_DBG=y -# CONFIG_OPENTHREAD_L2_DEBUG_DUMP_15_4=y -# CONFIG_OPENTHREAD_L2_DEBUG_DUMP_IPV6=y - -# Option for configuring log level in OpenThread -CONFIG_OPENTHREAD_LOG_LEVEL_INFO=y - -# Adjust log strdup settings -CONFIG_LOG_STRDUP_BUF_COUNT=32 -CONFIG_LOG_STRDUP_MAX_STRING=128 diff --git a/overlay-mtd.conf b/overlay-mtd.conf deleted file mode 100644 index 8d78608..0000000 --- a/overlay-mtd.conf +++ /dev/null @@ -1,15 +0,0 @@ -# -# Copyright (c) 2020 Nordic Semiconductor -# -# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause -# - -# Enable MTD Sleepy End Device -CONFIG_OPENTHREAD_MTD=y -CONFIG_OPENTHREAD_MTD_SED=y -CONFIG_OPENTHREAD_POLL_PERIOD=3000 -CONFIG_RAM_POWER_DOWN_LIBRARY=y -CONFIG_PM_DEVICE=y - -# This variant requires increased system workqueue stack size -CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=1536 diff --git a/overlay-rtt.conf b/overlay-rtt.conf deleted file mode 100644 index c4b8f35..0000000 --- a/overlay-rtt.conf +++ /dev/null @@ -1,13 +0,0 @@ -# -# Copyright (c) 2022 Nordic Semiconductor -# -# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause -# - -# Enable RTT logging backend -CONFIG_USE_SEGGER_RTT=y -CONFIG_LOG_BACKEND_RTT=y - -# Disable UART logging backend -CONFIG_LOG_BACKEND_UART=n -CONFIG_SHELL_LOG_BACKEND=n diff --git a/overlay-usb.conf b/overlay-usb.conf deleted file mode 100644 index db09a1e..0000000 --- a/overlay-usb.conf +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright (c) 2021 Nordic Semiconductor -# -# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause -# - -CONFIG_SHELL_BACKEND_SERIAL_INIT_PRIORITY=51 - -CONFIG_UART_LINE_CTRL=y -CONFIG_SHELL_BACKEND_SERIAL_CHECK_DTR=y -CONFIG_USB_CDC_ACM_LOG_LEVEL_OFF=y - -CONFIG_USB_DEVICE_STACK=y -CONFIG_USB_DEVICE_MANUFACTURER="Nordic Semiconductor ASA" -CONFIG_USB_DEVICE_PRODUCT="Thread CLI" -CONFIG_USB_DEVICE_VID=0x1915 -CONFIG_USB_DEVICE_PID=0x0000 diff --git a/prj.conf b/prj.conf index 8c06b25..974d6d9 100644 --- a/prj.conf +++ b/prj.conf @@ -1,98 +1,67 @@ -# Nordic Dev Kit Library -CONFIG_DK_LIBRARY=y +# Copyright (c) 2022-2023 Golioth, Inc. +# SPDX-License-Identifier: Apache-2.0 -CONFIG_LOG=y -CONFIG_GOLIOTH_THREAD_LOG_LEVEL_DBG=y +# Enable Golioth Firmware SDK +CONFIG_GOLIOTH_FIRMWARE_SDK=y -CONFIG_NETWORKING=y -CONFIG_NET_L2_OPENTHREAD=y - -CONFIG_SHELL=y -CONFIG_NET_SHELL=y -CONFIG_OPENTHREAD_SHELL=y -CONFIG_SHELL_ARGC_MAX=26 -CONFIG_SHELL_CMD_BUFF_SIZE=416 - -# IMPORTANT: Change the Thread network credentials to match your Thread network setup -CONFIG_OPENTHREAD_NORDIC_LIBRARY_MASTER=y -CONFIG_OPENTHREAD_NETWORK_NAME="OpenThreadDemo" -CONFIG_OPENTHREAD_NETWORKKEY="00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff" +# Golioth services used in this app +CONFIG_GOLIOTH_FW_UPDATE=y +CONFIG_GOLIOTH_LIGHTDB_STATE=y +CONFIG_LOG_BACKEND_GOLIOTH=y +CONFIG_GOLIOTH_RPC=y +CONFIG_GOLIOTH_SETTINGS=y +CONFIG_GOLIOTH_STREAM=y -CONFIG_MBEDTLS_SHA1_C=n -CONFIG_FPU=y +# Enable common sample library +CONFIG_GOLIOTH_SAMPLE_COMMON=y -# TLS configuration -CONFIG_MBEDTLS=y -CONFIG_MBEDTLS_BUILTIN=n +# Configure Golioth SDK dependencies +CONFIG_EVENTFD_MAX=14 +CONFIG_LOG_PROCESS_THREAD_STACK_SIZE=2048 CONFIG_MBEDTLS_ENABLE_HEAP=y CONFIG_MBEDTLS_HEAP_SIZE=10240 -# PSK needs to be manually enabled to prevent ENOTSUP (-134) -CONFIG_MBEDTLS_KEY_EXCHANGE_SOME_PSK_ENABLED=y -CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED=y -CONFIG_NET_SOCKETS_SOCKOPT_TLS=y - -# Golioth-specific configuration -CONFIG_GOLIOTH=y -CONFIG_GOLIOTH_SYSTEM_CLIENT=y - - -CONFIG_LOG_BACKEND_GOLIOTH=y -CONFIG_LOG_PROCESS_THREAD_STACK_SIZE=2048 - -# NAT64-prefixed coap.golioth.io IPv4 -# TODO: use DNS to resolve an IPv6-specific hostname to IPv6 -CONFIG_GOLIOTH_SYSTEM_SERVER_HOST="64:ff9b::2287:5a70" - -CONFIG_BOOTLOADER_MCUBOOT=n - -#Increase TLS message size - -CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=1400 - -#Sensor config - -CONFIG_I2C=y -CONFIG_SENSOR=y -CONFIG_SI7055=y - -# Debug - -CONFIG_THREAD_NAME=y -CONFIG_SCHED_CPU_MASK=y -CONFIG_THREAD_ANALYZER=y - -CONFIG_THREAD_NAME=y -CONFIG_SEGGER_SYSTEMVIEW=y -CONFIG_USE_SEGGER_RTT=y -CONFIG_TRACING=y -CONFIG_TRACING_BACKEND_RAM=y - -# Shell setup - -CONFIG_GPIO=y -CONFIG_I2C=y - -CONFIG_SHELL=y -CONFIG_I2C_SHELL=y +CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=2048 +CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=2048 +CONFIG_NETWORKING=y +CONFIG_NET_IPV4=y +CONFIG_POSIX_MAX_FDS=23 -CONFIG_ADC=y -CONFIG_ADC_SHELL=y +# Application +CONFIG_MAIN_STACK_SIZE=2048 +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 +CONFIG_NET_LOG=y +CONFIG_NET_SHELL=y +CONFIG_REBOOT=y -# Settings shell +# Flash memory (etc.) for firmware upgrade +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_NVS=y +CONFIG_STREAM_FLASH=y +CONFIG_IMG_MANAGER=y +CONFIG_IMG_ERASE_PROGRESSIVELY=y +CONFIG_REBOOT=y +# The rest of the runtime credentials config CONFIG_SETTINGS=y CONFIG_SETTINGS_RUNTIME=y +CONFIG_GOLIOTH_SAMPLE_PSK_SETTINGS=y CONFIG_GOLIOTH_SAMPLE_SETTINGS_AUTOLOAD=y -CONFIG_GOLIOTH_SAMPLES_COMMON=y -CONFIG_GOLIOTH_SYSTEM_SETTINGS=y CONFIG_GOLIOTH_SAMPLE_SETTINGS_SHELL=y -CONFIG_FLASH=y -CONFIG_FLASH_MAP=y -CONFIG_NVS=y +# Misc. +CONFIG_JSON_LIBRARY=y +# Longer response length needed for network info +CONFIG_GOLIOTH_RPC_MAX_RESPONSE_LEN=512 -CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=8192 +# Generate MCUboot compatible images +CONFIG_BOOTLOADER_MCUBOOT=y -CONFIG_GOLIOTH_SYSTEM_CLIENT_LOG_LEVEL_DBG=n +# Firmware version used in DFU process +CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION="1.0.0" -CONFIG_OPENTHREAD_DEBUG=n \ No newline at end of file +# IMPORTANT: Change the Thread network credentials to match your Thread network setup +CONFIG_OPENTHREAD_NORDIC_LIBRARY_MASTER=y +CONFIG_OPENTHREAD_CHANNEL=26 +CONFIG_OPENTHREAD_NETWORKKEY="00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff" +CONFIG_OPENTHREAD_NETWORK_NAME="golioth-thread" diff --git a/sample.yaml b/sample.yaml index aef1e9c..f1692b5 100644 --- a/sample.yaml +++ b/sample.yaml @@ -1,16 +1,16 @@ +# Copyright (c) 2022-2024 Golioth, Inc. +# SPDX-License-Identifier: Apache-2.0 + sample: description: Golioth Thread sample with OpenThread and IPv6 name: openthread common: - platform_allow: nrf5340dk_nrf5340_cpuapp nrf5340dk_nrf5340_cpuapp_ns nrf52840dk_nrf52840 nrf52833dk_nrf52833 nrf21540dk_nrf52840 + platform_allow: nrf52840dk_nrf52840 adafruit_feather_nrf52840 tags: golioth net socket thread integration_platforms: - - nrf5340dk_nrf5340_cpuapp - - nrf5340dk_nrf5340_cpuapp_ns - nrf52840dk_nrf52840 - - nrf52833dk_nrf52833 - - nrf21540dk_nrf52840 + - adafruit_feather_nrf52840 tests: sample.golioth.openthread diff --git a/src/app_rpc.c b/src/app_rpc.c new file mode 100644 index 0000000..c949cb0 --- /dev/null +++ b/src/app_rpc.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2022-2023 Golioth, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include <zephyr/logging/log.h> +#include <zephyr/logging/log_ctrl.h> +LOG_MODULE_REGISTER(app_rpc, LOG_LEVEL_DBG); + +#include <golioth/client.h> +#include <golioth/rpc.h> +#include <zephyr/logging/log_ctrl.h> +#include <zephyr/sys/reboot.h> + +#include "app_rpc.h" + +static void reboot_work_handler(struct k_work *work) +{ + for (int8_t i = 5; i >= 0; i--) { + if (i) { + LOG_INF("Rebooting in %d seconds...", i); + } + k_sleep(K_SECONDS(1)); + } + + /* Sync logs before reboot */ + LOG_PANIC(); + + sys_reboot(SYS_REBOOT_COLD); +} +K_WORK_DEFINE(reboot_work, reboot_work_handler); + +static enum golioth_rpc_status on_set_log_level(zcbor_state_t *request_params_array, + zcbor_state_t *response_detail_map, + void *callback_arg) +{ + double param_0; + uint8_t log_level; + bool ok; + + LOG_WRN("on_set_log_level"); + + ok = zcbor_float_decode(request_params_array, ¶m_0); + if (!ok) { + LOG_ERR("Failed to decode array item"); + return GOLIOTH_RPC_INVALID_ARGUMENT; + } + + log_level = (uint8_t)param_0; + + if ((log_level < 0) || (log_level > LOG_LEVEL_DBG)) { + + LOG_ERR("Requested log level is out of bounds: %d", log_level); + return GOLIOTH_RPC_INVALID_ARGUMENT; + } + + int source_id = 0; + char *source_name; + + while (1) { + source_name = (char *)log_source_name_get(0, source_id); + if (source_name == NULL) { + break; + } + + log_filter_set(NULL, 0, source_id, log_level); + ++source_id; + } + + LOG_WRN("Log levels for %d modules set to: %d", source_id, log_level); + + ok = zcbor_tstr_put_lit(response_detail_map, "log_modules") && + zcbor_float64_put(response_detail_map, (double)source_id); + + return GOLIOTH_RPC_OK; +} + +static enum golioth_rpc_status on_reboot(zcbor_state_t *request_params_array, + zcbor_state_t *response_detail_map, + void *callback_arg) +{ + /* Use work queue so this RPC can return confirmation to Golioth */ + k_work_submit(&reboot_work); + + return GOLIOTH_RPC_OK; +} + +static void rpc_log_if_register_failure(int err) +{ + if (err) { + LOG_ERR("Failed to register RPC: %d", err); + } +} + +void app_rpc_register(struct golioth_client *client) +{ + struct golioth_rpc *rpc = golioth_rpc_init(client); + + int err; + + err = golioth_rpc_register(rpc, "reboot", on_reboot, NULL); + rpc_log_if_register_failure(err); + + err = golioth_rpc_register(rpc, "set_log_level", on_set_log_level, NULL); + rpc_log_if_register_failure(err); +} diff --git a/src/app_rpc.h b/src/app_rpc.h new file mode 100644 index 0000000..4793235 --- /dev/null +++ b/src/app_rpc.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2022-2023 Golioth, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Handle remote procedure calls received from Golioth, returning a status code + * indicating the success or failure of the call. + * + * This demonstration implements the following RPCs: + * - `get_network_info`: Query and return network information. + * - `reboot`: reboot the device (no arguments) + * - `set_log_level`: adjust the logging level for all registered modules (valid + * argument values: 0..4) + * + * https://docs.golioth.io/firmware/zephyr-device-sdk/remote-procedure-call + */ + +#ifndef __APP_RPC_H__ +#define __APP_RPC_H__ + +#include <golioth/client.h> + +void app_rpc_register(struct golioth_client *client); + +#endif /* __APP_RPC_H__ */ diff --git a/src/app_sensors.c b/src/app_sensors.c new file mode 100644 index 0000000..a68f86c --- /dev/null +++ b/src/app_sensors.c @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2022-2023 Golioth, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include <zephyr/logging/log.h> +LOG_MODULE_REGISTER(app_sensors, LOG_LEVEL_DBG); + +#include <golioth/client.h> +#include <golioth/stream.h> +#include <zephyr/drivers/gpio.h> +#include <zephyr/kernel.h> + +#include "app_sensors.h" + +static struct golioth_client *client; +/* Add Sensor structs here */ + +/* Formatting string for sending sensor JSON to Golioth */ +#define JSON_FMT "{\"counter\":%d}" + +/* Callback for LightDB Stream */ + +static void async_error_handler(struct golioth_client *client, + const struct golioth_response *response, + const char *path, + void *arg) +{ + if (response->status != GOLIOTH_OK) { + LOG_ERR("Async task failed: %d", response->status); + return; + } +} + +/* This will be called by the main() loop */ +/* Do all of your work here! */ +void app_sensors_read_and_stream(void) +{ + int err; + char json_buf[256]; + + static uint8_t counter; + + /* Send sensor data to Golioth */ + /* For this demo we just fake it */ + snprintk(json_buf, sizeof(json_buf), JSON_FMT, counter); + LOG_DBG("%s", json_buf); + + err = golioth_stream_set_async(client, + "sensor", + GOLIOTH_CONTENT_TYPE_JSON, + json_buf, + strlen(json_buf), + async_error_handler, + NULL); + if (err) { + LOG_ERR("Failed to send sensor data to Golioth: %d", err); + } + + ++counter; +} + +void app_sensors_set_client(struct golioth_client *sensors_client) +{ + client = sensors_client; +} diff --git a/src/app_sensors.h b/src/app_sensors.h new file mode 100644 index 0000000..03e906b --- /dev/null +++ b/src/app_sensors.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022-2023 Golioth, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __APP_SENSORS_H__ +#define __APP_SENSORS_H__ + +/** The `app_sensors.c` file performs the important work of this application + * which is to read sensor values and report them to the Golioth LightDB Stream + * as time-series data. + * + * For this demonstration, a `counter` value is periodically logged and pushed + * to the Golioth time-series database. This simulated sensor reading occurs + * when the loop in `main.c` calls `app_sensors_read_and_stream()`. The + * frequency of this loop is determined by values received from the Golioth + * Settings Service (see app_settings.h). + * + * https://docs.golioth.io/firmware/zephyr-device-sdk/light-db-stream/ + */ + +#include <golioth/client.h> + +void app_sensors_set_client(struct golioth_client *sensors_client); +void app_sensors_read_and_stream(void); + +#endif /* __APP_SENSORS_H__ */ diff --git a/src/app_settings.c b/src/app_settings.c new file mode 100644 index 0000000..0dbba5b --- /dev/null +++ b/src/app_settings.c @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2022-2023 Golioth, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include <zephyr/logging/log.h> +LOG_MODULE_REGISTER(app_settings, LOG_LEVEL_DBG); + +#include <golioth/client.h> +#include <golioth/settings.h> +#include "main.h" +#include "app_settings.h" + +static int32_t _loop_delay_s = 60; +#define LOOP_DELAY_S_MAX 43200 +#define LOOP_DELAY_S_MIN 0 + +int32_t get_loop_delay_s(void) +{ + return _loop_delay_s; +} + +static enum golioth_settings_status on_loop_delay_setting(int32_t new_value, void *arg) +{ + _loop_delay_s = new_value; + LOG_INF("Set loop delay to %i seconds", new_value); + wake_system_thread(); + return GOLIOTH_SETTINGS_SUCCESS; +} + +int app_settings_register(struct golioth_client *client) +{ + struct golioth_settings *settings = golioth_settings_init(client); + + int err = golioth_settings_register_int_with_range(settings, + "LOOP_DELAY_S", + LOOP_DELAY_S_MIN, + LOOP_DELAY_S_MAX, + on_loop_delay_setting, + NULL); + + if (err) { + LOG_ERR("Failed to register settings callback: %d", err); + } + + return err; +} diff --git a/src/app_settings.h b/src/app_settings.h new file mode 100644 index 0000000..381d144 --- /dev/null +++ b/src/app_settings.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2022-2023 Golioth, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Process changes received from the Golioth Settings Service and return a code + * to Golioth to indicate the success or failure of the update. + * + * In this demonstration, the device looks for the `LOOP_DELAY_S` key from the + * Settings Service and uses this value to determine the delay between sensor + * reads (the period of sleep in the loop of `main.c`. + * + * https://docs.golioth.io/firmware/zephyr-device-sdk/device-settings-service + */ + +#ifndef __APP_SETTINGS_H__ +#define __APP_SETTINGS_H__ + +#include <stdint.h> +#include <golioth/client.h> + +int32_t get_loop_delay_s(void); +int app_settings_register(struct golioth_client *client); + +#endif /* __APP_SETTINGS_H__ */ diff --git a/src/app_state.c b/src/app_state.c new file mode 100644 index 0000000..961aceb --- /dev/null +++ b/src/app_state.c @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2022-2023 Golioth, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include <zephyr/logging/log.h> +LOG_MODULE_REGISTER(app_state, LOG_LEVEL_DBG); + +#include <golioth/client.h> +#include <golioth/lightdb_state.h> +#include <zephyr/data/json.h> +#include <zephyr/kernel.h> +#include "json_helper.h" + +#include "app_state.h" +#include "app_sensors.h" + +#define DEVICE_STATE_FMT "{\"example_int0\":%d,\"example_int1\":%d}" + +uint32_t _example_int0; +uint32_t _example_int1 = 1; + +static struct golioth_client *client; + +static void async_handler(struct golioth_client *client, + const struct golioth_response *response, + const char *path, + void *arg) +{ + if (response->status != GOLIOTH_OK) { + LOG_WRN("Failed to set state: %d", response->status); + return; + } + + LOG_DBG("State successfully set"); +} + +int app_state_reset_desired(void) +{ + LOG_INF("Resetting \"%s\" LightDB State endpoint to defaults.", APP_STATE_DESIRED_ENDP); + + char sbuf[sizeof(DEVICE_STATE_FMT) + 4]; /* space for two "-1" values */ + + snprintk(sbuf, sizeof(sbuf), DEVICE_STATE_FMT, -1, -1); + + int err; + err = golioth_lightdb_set_async(client, + APP_STATE_DESIRED_ENDP, + GOLIOTH_CONTENT_TYPE_JSON, + sbuf, + strlen(sbuf), + async_handler, + NULL); + if (err) { + LOG_ERR("Unable to write to LightDB State: %d", err); + } + return err; +} + +int app_state_update_actual(void) +{ + + char sbuf[sizeof(DEVICE_STATE_FMT) + 10]; /* space for uint16 values */ + + snprintk(sbuf, sizeof(sbuf), DEVICE_STATE_FMT, _example_int0, _example_int1); + + int err; + + err = golioth_lightdb_set_async(client, + APP_STATE_ACTUAL_ENDP, + GOLIOTH_CONTENT_TYPE_JSON, + sbuf, + strlen(sbuf), + async_handler, + NULL); + + if (err) { + LOG_ERR("Unable to write to LightDB State: %d", err); + } + return err; +} + +static void app_state_desired_handler(struct golioth_client *client, + const struct golioth_response *response, + const char *path, + const uint8_t *payload, + size_t payload_size, + void *arg) +{ + int err = 0; + int ret; + + if (response->status != GOLIOTH_OK) { + LOG_ERR("Failed to receive '%s' endpoint: %d", + APP_STATE_DESIRED_ENDP, + response->status); + return; + } + + LOG_HEXDUMP_DBG(payload, payload_size, APP_STATE_DESIRED_ENDP); + + struct app_state parsed_state; + + ret = json_obj_parse((char *)payload, payload_size, app_state_descr, + ARRAY_SIZE(app_state_descr), &parsed_state); + + if (ret < 0) { + LOG_ERR("Error parsing desired values: %d", ret); + app_state_reset_desired(); + return; + } + + uint8_t desired_processed_count = 0; + uint8_t state_change_count = 0; + + if (ret & 1 << 0) { + /* Process example_int0 */ + if ((parsed_state.example_int0 >= 0) && (parsed_state.example_int0 < 65536)) { + LOG_DBG("Validated desired example_int0 value: %d", + parsed_state.example_int0); + if (_example_int0 != parsed_state.example_int0) { + _example_int0 = parsed_state.example_int0; + ++state_change_count; + } + ++desired_processed_count; + } else if (parsed_state.example_int0 == -1) { + LOG_DBG("No change requested for example_int0"); + } else { + LOG_ERR("Invalid desired example_int0 value: %d", + parsed_state.example_int0); + ++desired_processed_count; + } + } + if (ret & 1 << 1) { + /* Process example_int1 */ + if ((parsed_state.example_int1 >= 0) && (parsed_state.example_int1 < 65536)) { + LOG_DBG("Validated desired example_int1 value: %d", + parsed_state.example_int1); + if (_example_int1 != parsed_state.example_int1) { + _example_int1 = parsed_state.example_int1; + ++state_change_count; + } + ++desired_processed_count; + } else if (parsed_state.example_int1 == -1) { + LOG_DBG("No change requested for example_int1"); + } else { + LOG_ERR("Invalid desired example_int1 value: %d", + parsed_state.example_int1); + ++desired_processed_count; + } + } + + if (state_change_count) { + /* The state was changed, so update the state on the Golioth servers */ + err = app_state_update_actual(); + } + if (desired_processed_count) { + /* We processed some desired changes to return these to -1 on the server + * to indicate the desired values were received. + */ + err = app_state_reset_desired(); + } + + if (err) { + LOG_ERR("Failed to update cloud state: %d", err); + } +} + +int app_state_observe(struct golioth_client *state_client) +{ + int err; + + client = state_client; + + err = golioth_lightdb_observe_async(client, + APP_STATE_DESIRED_ENDP, + GOLIOTH_CONTENT_TYPE_JSON, + app_state_desired_handler, + NULL); + if (err) { + LOG_WRN("failed to observe lightdb path: %d", err); + return err; + } + + /* This will only run once. It updates the actual state of the device + * with the Golioth servers. Future updates will be sent whenever + * changes occur. + */ + err = app_state_update_actual(); + + return err; +} diff --git a/src/app_state.h b/src/app_state.h new file mode 100644 index 0000000..a00222a --- /dev/null +++ b/src/app_state.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022-2023 Golioth, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** Observe and write to example endpoints for stateful data on the Golioth + * LightDB State Service. + * + * This demonstration exhibits (the concept of Digital + * Twin)[https://blog.golioth.io/better-iot-design-patterns-desired-state-vs-actual-state/]. + * It implements a _desired_ state which the cloud can set to request the device + * change its state, and an _actual_ state where the device reports its state. + * + * After receiving and processing a desired state, the device will reset the + * desired state (`APP_STATE_DESIRED_ENDP`) to `-1` indicating the data has been + * processed, and update the actual state (`APP_STATE_ACTUAL_ENDP`) to report + * the new state of the device. + * + * The device should write to the _actual state_ endpoint, the cloud should not. + * By convention the cloud should consider the _actual state_ values read-only. + * + * https://docs.golioth.io/firmware/zephyr-device-sdk/light-db/ + */ + +#ifndef __APP_STATE_H__ +#define __APP_STATE_H__ + +#include <golioth/client.h> + +#define APP_STATE_DESIRED_ENDP "desired" +#define APP_STATE_ACTUAL_ENDP "state" + +int app_state_observe(struct golioth_client *state_client); +int app_state_update_actual(void); + +#endif /* __APP_STATE_H__ */ diff --git a/src/battery_monitor/battery.c b/src/battery_monitor/battery.c new file mode 100644 index 0000000..5e95a5f --- /dev/null +++ b/src/battery_monitor/battery.c @@ -0,0 +1,377 @@ +/* + * Copyright (c) 2018-2019 Peter Bigot Consulting, LLC + * Copyright (c) 2019-2020 Nordic Semiconductor ASA + * Copyright (c) 2023 Golioth, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include <math.h> +#include <stdio.h> +#include <stdlib.h> + +#include <zephyr/kernel.h> +#include <zephyr/init.h> +#include <zephyr/drivers/gpio.h> +#include <zephyr/drivers/adc.h> +#include <zephyr/logging/log.h> +#include <golioth/client.h> +#include <golioth/stream.h> + +#include "battery_monitor/battery.h" +#include "../app_sensors.h" + +LOG_MODULE_REGISTER(battery, LOG_LEVEL_DBG); + +#define VBATT DT_PATH(vbatt) +#define ZEPHYR_USER DT_PATH(zephyr_user) + +/* Formatting string for sending battery JSON to Golioth */ +#define JSON_FMT "{\"batt_v\":%d.%03d,\"batt_lvl\":%d.%02d}" + +#define LABEL_BATTERY "Battery" + +#ifdef CONFIG_BOARD_THINGY52_NRF52832 +/* This board uses a divider that reduces max voltage to + * reference voltage (600 mV). + */ +#define BATTERY_ADC_GAIN ADC_GAIN_1 +#else +/* Other boards may use dividers that only reduce battery voltage to + * the maximum supported by the hardware (3.6 V) + */ +#define BATTERY_ADC_GAIN ADC_GAIN_1_6 +#endif + +char stream_endpoint[] = "battery"; + +char _batt_v_str[8] = "0.0 V"; +char _batt_lvl_str[5] = "none"; + +/* Battery values specific to the Aludel-mini */ +static const struct battery_level_point batt_levels[] = { + /* "Curve" here eyeballed from captured data for the [Adafruit + * 3.7v 2000 mAh](https://www.adafruit.com/product/2011) LIPO + * under full load that started with a charge of 3.96 V and + * dropped about linearly to 3.58 V over 15 hours. It then + * dropped rapidly to 3.10 V over one hour, at which point it + * stopped transmitting. + * + * Based on eyeball comparisons we'll say that 15/16 of life + * goes between 3.95 and 3.55 V, and 1/16 goes between 3.55 V + * and 3.1 V. + */ + + {10000, 3950}, + {625, 3550}, + {0, 3100}, +}; + +struct io_channel_config { + uint8_t channel; +}; + +struct divider_config { + struct io_channel_config io_channel; + struct gpio_dt_spec power_gpios; + /* output_ohm is used as a flag value: if it is nonzero then + * the battery is measured through a voltage divider; + * otherwise it is assumed to be directly connected to Vdd. + */ + uint32_t output_ohm; + uint32_t full_ohm; +}; + +static const struct divider_config divider_config = { +#if DT_NODE_HAS_STATUS(VBATT, okay) + /* clang-format off */ + .io_channel = { + DT_IO_CHANNELS_INPUT(VBATT), + }, /* clang-format on */ + .power_gpios = GPIO_DT_SPEC_GET_OR(VBATT, power_gpios, {}), + .output_ohm = DT_PROP(VBATT, output_ohms), + .full_ohm = DT_PROP(VBATT, full_ohms), +#else /* /vbatt exists */ + /* clang-format off */ + .io_channel = { + DT_IO_CHANNELS_INPUT(ZEPHYR_USER), + }, /* clang-format on */ +#endif /* /vbatt exists */ +}; + +struct divider_data { + const struct device *adc; + struct adc_channel_cfg adc_cfg; + struct adc_sequence adc_seq; + int16_t raw; +}; +static struct divider_data divider_data = { +#if DT_NODE_HAS_STATUS(VBATT, okay) + .adc = DEVICE_DT_GET(DT_IO_CHANNELS_CTLR(VBATT)), +#else + .adc = DEVICE_DT_GET(DT_IO_CHANNELS_CTLR(ZEPHYR_USER)), +#endif +}; + +static int divider_setup(void) +{ + const struct divider_config *cfg = ÷r_config; + const struct io_channel_config *iocp = &cfg->io_channel; + const struct gpio_dt_spec *gcp = &cfg->power_gpios; + struct divider_data *ddp = ÷r_data; + struct adc_sequence *asp = &ddp->adc_seq; + struct adc_channel_cfg *accp = &ddp->adc_cfg; + int rc; + + if (!device_is_ready(ddp->adc)) { + LOG_ERR("ADC device is not ready %s", ddp->adc->name); + return -ENOENT; + } + + if (gcp->port) { + if (!device_is_ready(gcp->port)) { + LOG_ERR("%s: device not ready", gcp->port->name); + return -ENOENT; + } + rc = gpio_pin_configure_dt(gcp, GPIO_OUTPUT_INACTIVE); + if (rc != 0) { + LOG_ERR("Failed to control feed %s.%u: %d", gcp->port->name, gcp->pin, rc); + return rc; + } + } + + *asp = (struct adc_sequence){ + .channels = BIT(0), + .buffer = &ddp->raw, + .buffer_size = sizeof(ddp->raw), + .oversampling = 4, + .calibrate = true, + }; + +#ifdef CONFIG_ADC_NRFX_SAADC + *accp = (struct adc_channel_cfg){ + .gain = BATTERY_ADC_GAIN, + .reference = ADC_REF_INTERNAL, + .acquisition_time = ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 40), + }; + + if (cfg->output_ohm != 0) { + accp->input_positive = SAADC_CH_PSELP_PSELP_AnalogInput0 + iocp->channel; + } else { + accp->input_positive = SAADC_CH_PSELP_PSELP_VDD; + } + + asp->resolution = 14; +#else /* CONFIG_ADC_var */ +#error Unsupported ADC +#endif /* CONFIG_ADC_var */ + + rc = adc_channel_setup(ddp->adc, accp); + if (rc) { + LOG_ERR("Failed to setup ADC for AIN%u: %d", iocp->channel, rc); + } else { + LOG_DBG("ADC setup for AIN%u complete", iocp->channel); + } + + return rc; +} + +static bool battery_ok; + +static int battery_setup(void) +{ + LOG_INF("Initializing battery measurement"); + + int rc = divider_setup(); + + battery_ok = (rc == 0); + if (rc) { + LOG_ERR("Battery measurement setup failed: %d", rc); + } + + return rc; +} + +SYS_INIT(battery_setup, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); + +int battery_measure_enable(bool enable) +{ + int rc = -ENOENT; + + if (battery_ok) { + const struct gpio_dt_spec *gcp = ÷r_config.power_gpios; + + rc = 0; + if (gcp->port) { + rc = gpio_pin_set_dt(gcp, enable); + } + } + return rc; +} + +int battery_sample(void) +{ + int rc = -ENOENT; + + if (battery_ok) { + struct divider_data *ddp = ÷r_data; + const struct divider_config *dcp = ÷r_config; + struct adc_sequence *sp = &ddp->adc_seq; + + rc = adc_read(ddp->adc, sp); + sp->calibrate = false; + if (rc == 0) { + int32_t val = ddp->raw; + + adc_raw_to_millivolts(adc_ref_internal(ddp->adc), ddp->adc_cfg.gain, + sp->resolution, &val); + + if (dcp->output_ohm != 0) { + rc = val * (uint64_t)dcp->full_ohm / dcp->output_ohm; + LOG_DBG("raw %u ~ %u mV => %d mV", ddp->raw, val, rc); + } else { + rc = val; + LOG_DBG("raw %u ~ %u mV", ddp->raw, val); + } + } + } + + return rc; +} + +unsigned int battery_level_pptt(unsigned int batt_mV, const struct battery_level_point *curve) +{ + const struct battery_level_point *pb = curve; + + if (batt_mV >= pb->lvl_mV) { + /* Measured voltage above highest point, cap at maximum. */ + return pb->lvl_pptt; + } + /* Go down to the last point at or below the measured voltage. */ + while ((pb->lvl_pptt > 0) && (batt_mV < pb->lvl_mV)) { + ++pb; + } + if (batt_mV < pb->lvl_mV) { + /* Below lowest point, cap at minimum */ + return pb->lvl_pptt; + } + + /* Linear interpolation between below and above points. */ + const struct battery_level_point *pa = pb - 1; + + return pb->lvl_pptt + + ((pa->lvl_pptt - pb->lvl_pptt) * (batt_mV - pb->lvl_mV) / (pa->lvl_mV - pb->lvl_mV)); +} + +int read_battery_data(struct battery_data *batt_data) +{ + + /* Turn on the voltage divider circuit */ + int err = battery_measure_enable(true); + + if (err) { + LOG_ERR("Failed to enable battery measurement power: %d", err); + return err; + } + + /* Read the battery voltage */ + int batt_mv = battery_sample(); + + if (batt_mv < 0) { + LOG_ERR("Failed to read battery voltage: %d", batt_mv); + return batt_mv; + } + + /* Turn off the voltage divider circuit */ + err = battery_measure_enable(false); + if (err) { + LOG_ERR("Failed to disable battery measurement power: %d", err); + return err; + } + + batt_data->battery_voltage_mv = batt_mv; + batt_data->battery_level_pptt = battery_level_pptt(batt_mv, batt_levels); + + return 0; +} + +char *get_batt_v_str(void) +{ + return _batt_v_str; +} + +char *get_batt_lvl_str(void) +{ + return _batt_lvl_str; +} + +void log_battery_data(void) +{ + LOG_INF("Battery measurement: voltage=%s, level=%s", get_batt_v_str(), get_batt_lvl_str()); +} + +static void async_error_handler(struct golioth_client *client, + const struct golioth_response *response, + const char *path, + void *arg) +{ + if (response->status != GOLIOTH_OK) { + LOG_ERR("Failed to stream battery data: %d", response->status); + return; + } +} + +int stream_battery_data(struct golioth_client *client, struct battery_data *batt_data) +{ + int err; + /* {"batt_v":X.XXX,"batt_lvl":XXX.XX} */ + char json_buf[35]; + + /* Send battery data to Golioth */ + snprintk(json_buf, sizeof(json_buf), JSON_FMT, batt_data->battery_voltage_mv / 1000, + batt_data->battery_voltage_mv % 1000, batt_data->battery_level_pptt / 100, + batt_data->battery_level_pptt % 100); + LOG_DBG("%s", json_buf); + + err = golioth_stream_set_async(client, + stream_endpoint, + GOLIOTH_CONTENT_TYPE_JSON, + json_buf, + strlen(json_buf), + async_error_handler, + NULL); + if (err) { + LOG_ERR("Failed to send battery data to Golioth: %d", err); + } + + return 0; +} + +int read_and_report_battery(struct golioth_client *client) +{ + int err; + struct battery_data batt_data; + + err = read_battery_data(&batt_data); + if (err) { + LOG_ERR("Error reading battery data"); + return err; + } + + /* Format as global string for easy access */ + snprintk(_batt_v_str, sizeof(_batt_v_str), "%d.%03d V", batt_data.battery_voltage_mv / 1000, + batt_data.battery_voltage_mv % 1000); + snprintk(_batt_lvl_str, sizeof(_batt_lvl_str), "%d%%", batt_data.battery_level_pptt / 100); + + log_battery_data(); + + if (golioth_client_is_connected(client)) { + err = stream_battery_data(client, &batt_data); + if (err) { + LOG_ERR("Error streaming battery info"); + return err; + } + } + + return 0; +} diff --git a/src/battery_monitor/include/battery_monitor/battery.h b/src/battery_monitor/include/battery_monitor/battery.h new file mode 100644 index 0000000..35680cb --- /dev/null +++ b/src/battery_monitor/include/battery_monitor/battery.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2018-2019 Peter Bigot Consulting, LLC + * Copyright (c) 2023 Golioth, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef APPLICATION_BATTERY_H_ +#define APPLICATION_BATTERY_H_ + +#include <stdint.h> +#include <stdbool.h> +#include <golioth/client.h> + +/** Enable or disable measurement of the battery voltage. + * + * @param enable true to enable, false to disable + * + * @return zero on success, or a negative error code. + */ +int battery_measure_enable(bool enable); + +/** Measure the battery voltage. + * + * @return the battery voltage in millivolts, or a negative error + * code. + */ +int battery_sample(void); + +/** A point in a battery discharge curve sequence. + * + * A discharge curve is defined as a sequence of these points, where + * the first point has #lvl_pptt set to 10000 and the last point has + * #lvl_pptt set to zero. Both #lvl_pptt and #lvl_mV should be + * monotonic decreasing within the sequence. + */ +struct battery_level_point { + /** Remaining life at #lvl_mV. */ + uint16_t lvl_pptt; + + /** Battery voltage at #lvl_pptt remaining life. */ + uint16_t lvl_mV; +}; + +/** Calculate the estimated battery level based on a measured voltage. + * + * @param batt_mV a measured battery voltage level. + * + * @param curve the discharge curve for the type of battery installed + * on the system. + * + * @return the estimated remaining capacity in parts per ten + * thousand. + */ +unsigned int battery_level_pptt(unsigned int batt_mV, const struct battery_level_point *curve); + +/** A battery voltage and level measurement. + * + * Battery voltage is in mV. + * Battery level is in parts per ten thousand. + */ +struct battery_data { + int battery_voltage_mv; + unsigned int battery_level_pptt; +}; + +/** + * @brief Get pointer to a string representation of the last read battery + * voltage. + * + * This string is generated each time read_and_report_battery() is called. + * + * @return Pointer to character array + */ +char *get_batt_v_str(void); + +/** + * @brief Get pointer to a string representation of the last read percentage + * level. If a level has not yet been read, this value will be `none`. + * + * This string is generated each time read_and_report_battery() is called. + * + * @return Pointer to character array + */ +char *get_batt_lvl_str(void); + +/** + * @brief Read the battery voltage and estimated level. + * + * @param battery_data pointer to a struct to read the battery data into. + * + * @return Error number or zero if successful. + */ +int read_battery_data(struct battery_data *batt_data); + +/** + * @brief Log the battery voltage and estimated level. + * + * @param battery_data battery data to log. + * + */ +void log_battery_data(void); + +/** + * @brief Stream battery data to Golioth. + * + * @param client Golioth client to use for the Stream API call + * @param battery_data battery data to stream to Golioth. + * + * @return Error number or zero if successful + */ +int stream_battery_data(struct golioth_client *client, struct battery_data *batt_data); + +/** + * @brief Read, log, stream, and display a battery measurement. + * + * @param client Golioth client to use for the Stream API call + * + * @return Error number or zero if successful + */ +int read_and_report_battery(struct golioth_client *client); + +#endif /* APPLICATION_BATTERY_H_ */ diff --git a/src/json_helper.h b/src/json_helper.h new file mode 100644 index 0000000..1de4379 --- /dev/null +++ b/src/json_helper.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 Golioth, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __JSON_HELPER_H_ +#define __JSON_HELPER_H_ + +#include <zephyr/data/json.h> + +struct app_state { + int32_t example_int0; + int32_t example_int1; +}; + +static const struct json_obj_descr app_state_descr[] = { + JSON_OBJ_DESCR_PRIM(struct app_state, example_int0, JSON_TOK_NUMBER), + JSON_OBJ_DESCR_PRIM(struct app_state, example_int1, JSON_TOK_NUMBER)}; + +#endif diff --git a/src/main.c b/src/main.c index 1107036..90d5986 100644 --- a/src/main.c +++ b/src/main.c @@ -1,205 +1,115 @@ -#include <dk_buttons_and_leds.h> -#include <zephyr/logging/log.h> - -#include <zephyr/drivers/uart.h> -#include <usb/usb_device.h> - -#include <net/golioth/system_client.h> +/* + * Copyright (c) 2022-2023 Golioth, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ -#include <net/openthread.h> +#include <zephyr/logging/log.h> +LOG_MODULE_REGISTER(golioth_openthread_demo, LOG_LEVEL_DBG); + +#include "app_rpc.h" +#include "app_settings.h" +#include "app_state.h" +#include "app_sensors.h" +#include <golioth/client.h> +#include <golioth/fw_update.h> #include <openthread/thread.h> +#include <samples/common/net_connect.h> +#include <samples/common/sample_credentials.h> +#include <zephyr/drivers/gpio.h> +#include "zephyr/kernel.h" +#include <zephyr/net/coap.h> +#include <zephyr/net/openthread.h> +#include <zephyr/net/socket.h> +#include "zephyr/sys/util_macro.h" -#include <zephyr/drivers/sensor.h> -#include <device.h> - -#include <init.h> +/* Current firmware version; update in prj.conf or via build argument */ +static const char *_current_version = CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION; +static struct golioth_client *client; +K_SEM_DEFINE(connected, 0, 1); +static k_tid_t _system_thread = 0; -LOG_MODULE_REGISTER(red_demo_main, LOG_LEVEL_DBG); +#if DT_NODE_EXISTS(DT_ALIAS(golioth_led)) +static const struct gpio_dt_spec golioth_led = GPIO_DT_SPEC_GET(DT_ALIAS(golioth_led), gpios); +static const struct gpio_dt_spec user_btn = GPIO_DT_SPEC_GET(DT_ALIAS(user_btn), gpios); +#endif /* DT_NODE_EXISTS(DT_ALIAS(golioth_led)) */ -#define CONSOLE_LABEL DT_LABEL(DT_CHOSEN(zephyr_console)) -#define OT_CONNECTION_LED DK_LED1 - -int sensor_interval = 60; -int counter = 0; - -struct device *temp_sensor; -struct device *imu_sensor; -struct device *mag_sensor; +static struct gpio_callback button_cb_data; static struct k_work on_connect_work; static struct k_work on_disconnect_work; -static struct golioth_client *client = GOLIOTH_SYSTEM_CLIENT_GET(); - -static K_SEM_DEFINE(connected, 0, 1); - - -static int sensor_push_handler(struct golioth_req_rsp *rsp) -{ - if (rsp->err) { - LOG_WRN("Failed to push sensor: %d", rsp->err); - return rsp->err; - } - dk_set_led_off(DK_LED2); - LOG_DBG("Sensor successfully pushed"); - return 0; -} - -// This work function will submit a LightDB Stream output -// It should be called every time the sensor takes a reading - -void my_sensorstream_work_handler(struct k_work *work) -{ - int err; - struct sensor_value temp; - struct sensor_value accel_x; - struct sensor_value accel_y; - struct sensor_value accel_z; - struct sensor_value mag; - char sbuf[100]; - - // kick off a temp sensor reading! - sensor_sample_fetch(temp_sensor); - sensor_channel_get(temp_sensor, SENSOR_CHAN_AMBIENT_TEMP, &temp); - LOG_DBG("Temp is %d.%06d", temp.val1, abs(temp.val2)); - - // kick off an IMU sensor reading! - sensor_sample_fetch(imu_sensor); - sensor_channel_get(imu_sensor, SENSOR_CHAN_ACCEL_X, &accel_x); - LOG_DBG("Accel X is %d.%06d", accel_x.val1, abs(accel_x.val2)); - sensor_channel_get(imu_sensor, SENSOR_CHAN_ACCEL_Y, &accel_y); - LOG_DBG("Accel Y is %d.%06d", accel_y.val1, abs(accel_y.val2)); - sensor_channel_get(imu_sensor, SENSOR_CHAN_ACCEL_Z, &accel_z); - LOG_DBG("Accel Z is %d.%06d", accel_z.val1, abs(accel_z.val2)); - - - // kick off a mag sensor reading! - sensor_sample_fetch(mag_sensor); - sensor_channel_get(mag_sensor, SENSOR_CHAN_PROX, &mag); - LOG_DBG("Mag is %d.%06d", mag.val1, abs(mag.val2)); - - - snprintk(sbuf, sizeof(sbuf) - 1, - "{\"accel_x\":%f,\"accel_y\":%f,\"accel_z\":%f,\"mag\":%f,\"temp\":%f}", - sensor_value_to_double(&accel_x), - sensor_value_to_double(&accel_y), - sensor_value_to_double(&accel_z), - sensor_value_to_double(&mag), - sensor_value_to_double(&temp) - ); - - // Async send data to the cloud, get confirmation and call the push handler - err = golioth_stream_push_cb(client, "redSensor", - GOLIOTH_CONTENT_FORMAT_APP_JSON, - sbuf, strlen(sbuf), - sensor_push_handler, NULL); - if (err) { - LOG_WRN("Failed to send sensor: %d", err); - printk("Failed to send sensor: %d\n", err); - } - -} - -K_WORK_DEFINE(my_sensorstream_work, my_sensorstream_work_handler); - - -// This work function initiates a sensor reading -// And kick off a LightDB stream event -// It should be called every time the timer fires +/* forward declarations */ +void golioth_connection_led_set(uint8_t state); -void my_sensor_work_handler(struct k_work *work) +void wake_system_thread(void) { - - LOG_DBG("LED on, taking sensor readings"); - dk_set_led_on(DK_LED2); - k_work_submit(&my_sensorstream_work); - + k_wakeup(_system_thread); } -K_WORK_DEFINE(my_sensor_work, my_sensor_work_handler); - -static int counter_set_handler(struct golioth_req_rsp *rsp) +static void on_client_event(struct golioth_client *client, + enum golioth_client_event event, + void *arg) { - if (rsp->err) { - LOG_WRN("Failed to set counter: %d", rsp->err); - return rsp->err; - } - - LOG_DBG("Counter successfully set"); - - return 0; -} - + bool is_connected = (event == GOLIOTH_CLIENT_EVENT_CONNECTED); -void my_timer_handler(struct k_timer *dummy) { - - char sbuf[sizeof("4294967295")]; - int err; - - snprintk(sbuf, sizeof(sbuf) - 1, "%d", counter); - - LOG_INF("Interval of %d seconds is up, taking a reading", sensor_interval); - - err = golioth_lightdb_set_cb(client, "counter", - GOLIOTH_CONTENT_FORMAT_APP_JSON, - sbuf, strlen(sbuf), - counter_set_handler, NULL); - if (err) { - LOG_WRN("Failed to set counter: %d", err); - return; + if (is_connected) { + k_sem_give(&connected); + golioth_connection_led_set(1); + } else { + golioth_connection_led_set(0); } - counter++; - - - k_work_submit(&my_sensor_work); - -} - -K_TIMER_DEFINE(my_timer, my_timer_handler, NULL); - - -static void golioth_on_connect(struct golioth_client *client) -{ - k_sem_give(&connected); - - LOG_INF("Connected to Golioth!"); + LOG_INF("Golioth client %s", is_connected ? "connected" : "disconnected"); } static void on_ot_connect(struct k_work *item) { ARG_UNUSED(item); - dk_set_led_off(OT_CONNECTION_LED); // Turn LED off when connected - + LOG_INF("OpenThread on connect"); } static void on_ot_disconnect(struct k_work *item) { ARG_UNUSED(item); - dk_set_led_on(OT_CONNECTION_LED); // Turn LED on when NOT connected + LOG_INF("OpenThread on disconnect"); } - -static void on_button_changed(uint32_t button_state, uint32_t has_changed) +static void start_golioth_client(void) { - uint32_t buttons = button_state & has_changed; + /* Get the client configuration from auto-loaded settings */ + const struct golioth_client_config *client_config = golioth_sample_credentials_get(); - if ((buttons & DK_BTN1_MSK) && button_state == 1) { - golioth_send_hello(client); - LOG_DBG("Button %d pressed, taking a reading", has_changed); - k_work_submit(&my_sensor_work); - } + /* Create and start a Golioth Client */ + client = golioth_client_create(client_config); + + /* Register Golioth on_connect callback */ + golioth_client_register_event_callback(client, on_client_event, NULL); + + /* Initialize DFU components */ + golioth_fw_update_init(client, _current_version); + + /*** Call Golioth APIs for other services in dedicated app files ***/ + /* Observe State service data */ + app_state_observe(client); + + /* Set Golioth Client for streaming sensor data */ + app_sensors_set_client(client); + + /* Register Settings service */ + app_settings_register(client); + /* Register RPC service */ + app_rpc_register(client); } -static void on_thread_state_changed(uint32_t flags, void *context) +static void on_thread_state_changed(otChangedFlags flags, struct openthread_context *ot_context, + void *user_data) { - struct openthread_context *ot_context = context; - if (flags & OT_CHANGED_THREAD_ROLE) { switch (otThreadGetDeviceRole(ot_context->instance)) { case OT_DEVICE_ROLE_CHILD: @@ -210,103 +120,92 @@ static void on_thread_state_changed(uint32_t flags, void *context) case OT_DEVICE_ROLE_DISABLED: case OT_DEVICE_ROLE_DETACHED: + default: k_work_submit(&on_disconnect_work); break; } } -} - -void main(void) -{ - int ret; - -#if DT_NODE_HAS_COMPAT(DT_CHOSEN(zephyr_shell_uart), zephyr_cdc_acm_uart) - const struct device *dev; - uint32_t dtr = 0U; - - ret = usb_enable(NULL); - if (ret != 0) { - LOG_ERR("Failed to enable USB"); - return; - } - - dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_shell_uart)); - if (dev == NULL) { - LOG_ERR("Failed to find specific UART device"); - return; - } - LOG_INF("Waiting for host to be ready to communicate"); - - /* Data Terminal Ready - check if host is ready to communicate */ - while (!dtr) { - ret = uart_line_ctrl_get(dev, UART_LINE_CTRL_DTR, &dtr); - if (ret) { - LOG_ERR("Failed to get Data Terminal Ready line state: %d", - ret); - continue; - } - k_msleep(100); + if (flags == OT_CHANGED_IP6_ADDRESS_ADDED) { + start_golioth_client(); } +} - /* Data Carrier Detect Modem - mark connection as established */ - (void)uart_line_ctrl_set(dev, UART_LINE_CTRL_DCD, 1); - /* Data Set Ready - the NCP SoC is ready to communicate */ - (void)uart_line_ctrl_set(dev, UART_LINE_CTRL_DSR, 1); -#endif - - LOG_INF("Start Golioth Thread sample"); - - ret = dk_buttons_init(on_button_changed); - if (ret) { - LOG_ERR("Cannot init buttons (error: %d)", ret); - return; - } +static struct openthread_state_changed_cb ot_state_chaged_cb = { + .state_changed_cb = on_thread_state_changed +}; - ret = dk_leds_init(); - if (ret) { - LOG_ERR("Cannot init leds, (error: %d)", ret); - return; - } +void button_pressed(const struct device *dev, struct gpio_callback *cb, uint32_t pins) +{ + LOG_DBG("Button pressed at %d", k_cycle_get_32()); + /* This function is an Interrupt Service Routine. Do not call functions that + * use other threads, or perform long-running operations here + */ + k_wakeup(_system_thread); +} - temp_sensor = (void *)DEVICE_DT_GET_ANY(silabs_si7055); +/* Set (unset) LED indicators for active Golioth connection */ +void golioth_connection_led_set(uint8_t state) +{ + uint8_t pin_state = state ? 1 : 0; +#if DT_NODE_EXISTS(DT_ALIAS(golioth_led)) + /* Turn on Golioth logo LED once connected */ + gpio_pin_set_dt(&golioth_led, pin_state); +#endif /* #if DT_NODE_EXISTS(DT_ALIAS(golioth_led)) */ + /* Change the state of the Golioth LED on Ostentus */ + IF_ENABLED(CONFIG_LIB_OSTENTUS, (led_golioth_set(pin_state);)); +} - if (temp_sensor == NULL) { - printk("Could not get si7055 device\n"); - return; - } - imu_sensor = (void *)DEVICE_DT_GET_ANY(st_lis2dh12); +int main(void) +{ + int err = 0; - if (imu_sensor == NULL) { - printk("Could not get lis2dh12 device\n"); - return; - } + LOG_DBG("Start OpenThread demo"); - mag_sensor = (void *)DEVICE_DT_GET_ANY(honeywell_sm351lt); + IF_ENABLED(CONFIG_NET_L2_OPENTHREAD, ( + k_work_init(&on_connect_work, on_ot_connect); + k_work_init(&on_disconnect_work, on_ot_disconnect); - if (mag_sensor == NULL) { - printk("Could not get sm351lt device\n"); - return; - } + openthread_state_changed_cb_register(openthread_get_default_context(), &ot_state_chaged_cb); + openthread_start(openthread_get_default_context()); + )); - //Turn on LED 1 while connecting - dk_set_led_on(OT_CONNECTION_LED); + LOG_INF("Firmware version: %s", CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION); - k_work_init(&on_connect_work, on_ot_connect); - k_work_init(&on_disconnect_work, on_ot_disconnect); + /* Get system thread id so loop delay change event can wake main */ + _system_thread = k_current_get(); - openthread_set_state_changed_cb(on_thread_state_changed); - openthread_start(openthread_get_default_context()); +#if DT_NODE_EXISTS(DT_ALIAS(golioth_led)) + /* Initialize Golioth logo LED */ + err = gpio_pin_configure_dt(&golioth_led, GPIO_OUTPUT_INACTIVE); + if (err) { + LOG_ERR("Unable to configure LED for Golioth Logo"); + } +#endif /* #if DT_NODE_EXISTS(DT_ALIAS(golioth_led)) */ - client->on_connect = golioth_on_connect; - golioth_system_client_start(); + /* Set up user button */ + err = gpio_pin_configure_dt(&user_btn, GPIO_INPUT); + if (err) { + LOG_ERR("Error %d: failed to configure %s pin %d", err, user_btn.port->name, + user_btn.pin); + return err; + } - k_sem_take(&connected, K_FOREVER); + err = gpio_pin_interrupt_configure_dt(&user_btn, GPIO_INT_EDGE_TO_ACTIVE); + if (err) { + LOG_ERR("Error %d: failed to configure interrupt on %s pin %d", err, + user_btn.port->name, user_btn.pin); + return err; + } - dk_set_led_off(DK_LED2); + gpio_init_callback(&button_cb_data, button_pressed, BIT(user_btn.pin)); + gpio_add_callback(user_btn.port, &button_cb_data); - k_timer_start(&my_timer, K_SECONDS(sensor_interval), K_SECONDS(sensor_interval)); + while (true) { + app_sensors_read_and_stream(); -} \ No newline at end of file + k_sleep(K_SECONDS(get_loop_delay_s())); + } +} diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..0737d46 --- /dev/null +++ b/src/main.h @@ -0,0 +1,7 @@ +/* + * Copyright (c) 2023 Golioth, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +void wake_system_thread(void); diff --git a/usb.overlay b/usb.overlay deleted file mode 100644 index 50a8193..0000000 --- a/usb.overlay +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) 2021 Nordic Semiconductor ASA - * - * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause - */ - -/ { - chosen { - zephyr,shell-uart = &cdc_acm_uart0; - }; -}; - -&zephyr_udc0 { - cdc_acm_uart0: cdc_acm_uart0 { - compatible = "zephyr,cdc-acm-uart"; - label = "CDC_ACM_0"; - }; -}; diff --git a/west.yml b/west.yml index a13a36d..0039389 100644 --- a/west.yml +++ b/west.yml @@ -1,49 +1,37 @@ +# Copyright (c) 2022-2023 Golioth, Inc. +# SPDX-License-Identifier: Apache-2.0 + manifest: - version: 0.7 - - defaults: - remote: nrfconnect + version: 0.8 - remotes: - - name: nrfconnect - url-base: https://github.com/nrfconnect projects: - - name: nrf - repo-path: sdk-nrf - remote: nrfconnect - revision: v2.1.0 + - name: golioth + path: modules/lib/golioth-firmware-sdk + revision: v0.13.1 + url: https://github.com/golioth/golioth-firmware-sdk.git + west-commands: scripts/west-commands.yml + submodules: true import: + file: west-ncs.yml path-prefix: deps name-allowlist: + - nrf - zephyr - cmsis - hal_nordic - mbedtls + - mbedtls-nrf + - mcuboot - net-tools - - nrf_hw_models - - segger - - tinycrypt - - tf-m-tests - nrfxlib - - mcuboot - - mcumgr - - tinycbor - - mbedtls-nrf - - memfault-firmware-sdk - openthread - - # Golioth repository. - - name: golioth - path: deps/modules/lib/golioth - revision: v0.5.0 - url: https://github.com/golioth/zephyr-sdk.git - - # QCBOR - - name: qcbor - path: deps/modules/lib/qcbor - revision: 17b5607b8c49b835d22dec3effa97b25c89267b3 - url: https://github.com/golioth/QCBOR.git + - qcbor + - segger + - tfm-mcuboot + - tinycrypt + - trusted-firmware-m + - zcbor self: path: app - west-commands: scripts/west-commands.yml +