diff --git a/.github/workflows/workflow-validate.yaml b/.github/workflows/workflow-validate.yaml index 7fb4cb4d..cc10c4e9 100644 --- a/.github/workflows/workflow-validate.yaml +++ b/.github/workflows/workflow-validate.yaml @@ -23,7 +23,7 @@ jobs: - name: Validate run: | - cd tests + cd scripts/validation npm install - cd .. - node tests/validate.js + cd ../../ + ./content-lint diff --git a/.github/workflows/workflow.yaml b/.github/workflows/workflow.yaml index 503ad0b6..bdbae3ec 100644 --- a/.github/workflows/workflow.yaml +++ b/.github/workflows/workflow.yaml @@ -5,7 +5,7 @@ on: types: deploy push: branches: - - main + - main env: AWS_REGION: 'us-west-1' @@ -55,4 +55,4 @@ jobs: run: | aws cloudfront create-invalidation \ --distribution-id "${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }}" \ - --paths '/content/tutorials/*' + --paths '/content/tutorials/*' '/content/datasheets/*' diff --git a/.gitignore b/.gitignore index c06d41ac..56997d77 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .DS_Store -tests/node_modules +node_modules/ +build diff --git a/content-lint b/content-lint index 50e7cde4..d081c41e 100755 --- a/content-lint +++ b/content-lint @@ -1,2 +1,9 @@ #!/bin/bash -node tests/validate.js $@ \ No newline at end of file + +if ! command -v node &> /dev/null +then + echo "Please install Node.js from here https://nodejs.org/en/download/" + exit +fi + +node scripts/validation/validate.js $@ \ No newline at end of file diff --git a/content/datasheets/portenta-breakout-board/assets/featured.jpg b/content/datasheets/portenta-breakout-board/assets/featured.jpg new file mode 100644 index 00000000..3a96e46a Binary files /dev/null and b/content/datasheets/portenta-breakout-board/assets/featured.jpg differ diff --git a/content/datasheets/portenta-breakout-board/assets/test-2.png b/content/datasheets/portenta-breakout-board/assets/test-2.png new file mode 100644 index 00000000..46acbbf3 Binary files /dev/null and b/content/datasheets/portenta-breakout-board/assets/test-2.png differ diff --git a/content/datasheets/portenta-breakout-board/assets/test-2.svg b/content/datasheets/portenta-breakout-board/assets/test-2.svg new file mode 100644 index 00000000..3f6b9cbe --- /dev/null +++ b/content/datasheets/portenta-breakout-board/assets/test-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/content/datasheets/portenta-breakout-board/assets/test.png b/content/datasheets/portenta-breakout-board/assets/test.png new file mode 100644 index 00000000..da40cb06 Binary files /dev/null and b/content/datasheets/portenta-breakout-board/assets/test.png differ diff --git a/content/datasheets/portenta-breakout-board/assets/test.svg b/content/datasheets/portenta-breakout-board/assets/test.svg new file mode 100644 index 00000000..055c6717 --- /dev/null +++ b/content/datasheets/portenta-breakout-board/assets/test.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/content/datasheets/portenta-breakout-board/datasheet.md b/content/datasheets/portenta-breakout-board/datasheet.md new file mode 100644 index 00000000..ab84c0cd --- /dev/null +++ b/content/datasheets/portenta-breakout-board/datasheet.md @@ -0,0 +1,96 @@ +--- +identifier: ASX00031 +title: Arduino® Portenta Breakout Board +revision: Rev. 01 +type: pro +--- + +![](assets/featured.jpg) + +# Description +The Arduino Portenta Breakout board is designed to assist developers with their prototypes by exposing the high-density connectors of the Portenta family on both sides of the breakout carrier, providing total flexibility for measuring and controlling signals - developing your own hardware, testing the design and measuring the input and output signals out of the high-density connectors. + +# Target areas +Industrial applications, prototyping, robotics, data logging + +# Features + +- Power ON Button +- Boot mode DIP switch +- **Connectors** + - USBA  + - RJ45 GBit Ethernet  + - Micro SD card + - OpenMV shutter module + - MIPI 20T  JTAG with trace capability +- **Power** + - CR2032 RTC Lithium Battery backup + - External power terminal block +- **I/O** + - Break out all Portenta High Density connector signals (see pinout table below) + - Male/female HD connectors allow interposing breakout between Portenta and shield to debug signals +- **Compatibility**\ + Standard Portenta High Density connector pinout +- **Safety information**\ + Class A + +# Contents + +## The Board +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + +### Configuration +Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? + +### Hardware +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + +#### Microcontroller +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. + +#### Sensors +![Cool Sensor](assets/test.png) + + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + +![Cool Sensor SVG](assets/test.svg) + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + +![](assets/test-2.svg) + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + +## Data + +| Name | Description | A | B | C | D | +|------|-------------------------------------------|--------|---|---|---| +| Name | Lorem ipsum dolor sit amet | a | b | c | d | +| Name | Lorem ipsum dolor sit amet | a | b | c | d | +| Name | Lorem ipsum dolor sit amet fgdggdgdg | a | b | c | d | +| Name | Lorem ipsum dolor sit amet test 222222222 | a | b | c | d | +| Name | Lorem ipsum dolor sit amet | a | b | c | d | +| Name | Lorem ipsum dolor sit amet | a | b | c | d | +| Name | Lorem ipsum dolor sit amet | a | b | c | d | +| Cool | test 123 | halloo | 1 | 2 | 3 | + + +| Name | Description | A | B | C | D | +|------|----------------------------|---|---|---|---| +| Name | Lorem ipsum dolor sit amet | a | b | c | d | +| Name | Lorem ipsum dolor sit amet | a | b | c | d | +| Name | Lorem ipsum dolor sit amet | a | b | c | d | +| Name | Lorem ipsum dolor sit amet | a | b | c | d | +| Name | Lorem ipsum dolor sit amet | a | b | c | d | +| Name | Lorem ipsum dolor sit amet | a | b | c | d | +| Name | Lorem ipsum dolor sit amet | a | b | c | d | + + +### Test +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + +![Cool test](assets/test-2.png) + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. \ No newline at end of file diff --git a/content/tutorials/portenta-h7/ec-ard-3wirevalve/assets/ec_ard_3wirevalve_cover.svg b/content/tutorials/portenta-h7/ec-ard-3wirevalve/assets/ec_ard_3wirevalve_cover.svg new file mode 100644 index 00000000..d93642d2 --- /dev/null +++ b/content/tutorials/portenta-h7/ec-ard-3wirevalve/assets/ec_ard_3wirevalve_cover.svg @@ -0,0 +1,672 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/content/tutorials/portenta-h7/ec-ard-3wirevalve/assets/ec_ard_connect_power_source.svg b/content/tutorials/portenta-h7/ec-ard-3wirevalve/assets/ec_ard_connect_power_source.svg new file mode 100644 index 00000000..050eea1a --- /dev/null +++ b/content/tutorials/portenta-h7/ec-ard-3wirevalve/assets/ec_ard_connect_power_source.svg @@ -0,0 +1,491 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/content/tutorials/portenta-h7/ec-ard-3wirevalve/assets/ec_ard_connect_valve.svg b/content/tutorials/portenta-h7/ec-ard-3wirevalve/assets/ec_ard_connect_valve.svg new file mode 100644 index 00000000..389960bf --- /dev/null +++ b/content/tutorials/portenta-h7/ec-ard-3wirevalve/assets/ec_ard_connect_valve.svg @@ -0,0 +1,854 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/content/tutorials/portenta-h7/ec-ard-3wirevalve/assets/ec_ard_valve_wires.svg b/content/tutorials/portenta-h7/ec-ard-3wirevalve/assets/ec_ard_valve_wires.svg new file mode 100644 index 00000000..968a64cb --- /dev/null +++ b/content/tutorials/portenta-h7/ec-ard-3wirevalve/assets/ec_ard_valve_wires.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/content/tutorials/portenta-h7/ec-ard-3wirevalve/content.md b/content/tutorials/portenta-h7/ec-ard-3wirevalve/content.md new file mode 100644 index 00000000..0a96ac22 --- /dev/null +++ b/content/tutorials/portenta-h7/ec-ard-3wirevalve/content.md @@ -0,0 +1,160 @@ +--- +title: Connecting and Controlling a Motorized Ball Valve +coverImage: assets/ec_ard_3wirevalve_cover.svg +tags: [Edge Control, Motorised Valve, Irrigation] +description: This tutorial will give you an overview of the core features of the board, setup the development environment and introduce the APIs required to program the board. +--- + +# Connecting and Controlling a Motorized Ball Valve + +## Overview + +A ball valve is a form of quarter-turn [valve](https://en.wikipedia.org/wiki/Valve) which uses a hollow, perforated and pivoting ball to control flow of liquids and gasses through it. This tutorial will guide you through connecting the board to a 3 Wire-Valve and writing a sketch that controls the basic operations such as the opening and closing of the valves. + +***Tip : If this is for your first Edge Control project, we recommend you to take a look at the [Getting Started Tutorial](https://www.arduino.cc/pro/tutorials/portenta-h7/ec-ard-gs) to setup the development environment before you proceed.*** + +### You Will Learn + +- How to connect a motorized valve to the Edge Control board +- How to control the valve through basic commands provided by the `Arduino_EdgeControl` library +- How to power the board with an external power supply + +### Required Hardware and Software + +- 1 x [Arduino Edge control board](https://store.arduino.cc/edge-control) +- 1 x [US Solid Motorised Ball Valve (9 - 24 V)](https://ussolid.com/u-s-solid-motorized-ball-valve-1-2-brass-electrical-ball-valve-with-full-port-9-24-v-ac-dc-3-wire-setup.html) (or compatible) +- External power source: 12V battery (LiPo / SLA) or power supply +- 1 x Micro USB cable +- Arduino IDE 1.8.10+ +- 2 x Phoenix connectors 1844646 +- 2 x Jumper cables + +## Instructions + +### 1. Connecting the Valves + +The motorized valve comes with three wires primarily marked as blue, yellow and red. The red and blue cables are for the positive and negative signals and the yellow is for the ground. + +![Schematics of the 3 wire motor](assets/ec_ard_valve_wires.svg) + +You need to ensure that the Phoenix connectors are in place before plugging in the wires to the respective pins. If you havent link + +Connect the red and the blue wire to any of the 8 pairs of `LATCHING OUT` pins. In this example we will use `1N` and `1P` of your Edge Control board. Latches allow you to store the state of the pins based on the previous output. As the valve doesn't come with internal drivers to store the state of the motor, we will use the `Latching_out` pins (instead of `Latching_out_cmd`) that are the ones that include drivers on the Edge Control. + +![Connecting the valves to the Phoenix](assets/ec_ard_connect_valve.svg) + +Connect the yellow wire to the nearby `GND` pin. Ensure that the wires are fastened securely and tightly to the Phoenix connectors so that they make contact with the pins. + +### 2. Opening And Closing the Valves + +Open a new sketch file on the Arduino IDE and name it `ValveControl.ino`. Add the header file `Arduino_EdgeControl.h` to your sketch + +```cpp +#include +``` + +Inside `void setup()` , after enabeling the serial communication, run the initialization routine `EdgeControl.begin()` . This routine is in charge of enabling the default power areas of the board. Then use `Latching.begin()` to configure the expander pins as outputs. + +```cpp +void setup() +{ + Serial.begin(9600); + while(!Serial); + + delay(1000); + + Serial.println("3-Wire Valve Demo"); + + EdgeControl.begin(); + Latching.begin(); + + Serial.println("Starting"); +} + +``` + +Inside the `loop()`you will add the instructions to open and close the valve. `Latching.channelDirection()` is used to control the signal to a particular pin using the parameter `LATCHING_OUT_1` and its direction using the parameters, `POSITIVE` or `NEGATIVE`. Hence, if you want the valve to open you will use the instruction, + +```cpp +Latching.channelDirection(LATCHING_OUT_1, POSITIVE) +``` + +and to close the valve, you need to send a signal in the opposite direction using the command, + +```cpp +Latching.channelDirection(LATCHING_OUT_1, NEGATIVE) +``` + +As it takes a few seconds for the valve to fully open or close, you need to maintain the signal for a set amount of time. Using the command, `Latching.strobe(4500)` you can adjust the duration (milliseconds) of signal passing through a particular pin. + +```cpp +void loop() +{ + Serial.println("Closing"); + Latching.channelDirection(LATCHING_OUT_1, POSITIVE); + Latching.strobe(4500); + delay(2500); + + Serial.println("Opening"); + Latching.channelDirection(LATCHING_OUT_1, NEGATIVE); + Latching.strobe(4500); + delay(2500); +} +``` + +### 3. Connecting To A Power Source + +The valves require a power supply of 9 - 12 V and you can either use a regular power supply or a 3 cell LiPo battery to provide the required voltage. Power sources can be connected to the onboard relay ports of the edge control board. Connect two jumper wires to the **GND** and **B** pins of the **Relay ports** + +![The power pins of the Edge Control](assets/ec_ard_connect_power_source.svg) + +Connect the jumper from the **B** pin to the positive terminal of the Battery and the jumper from the **GND** pin to the negative terminal of the battery + +### 4. Uploading the Sketch + +Connect the board to your computer, upload the `ValveControl.ino` sketch and open the **Serial Monitor**. If all the connections are done right, the valve opens and closes and you should be able to see the status as `Open` or `Close` on the serial monitor + +## Conclusion + +In this tutorial you learned how a 3 wire valve works and the basic operations that the Edge Control board uses to control the valves. With this knowledge you can build irrigation systems which periodically control the valves which can be installed in your fields. + +### Complete Sketch + +```cpp +#include + +void setup() +{ + Serial.begin(9600); + while(!Serial); + + delay(1000); + + Serial.println("3-Wire Valve Demo"); + + EdgeControl.begin(); + Latching.begin(); + + Serial.println("Starting"); +} + +void loop() +{ + Serial.println("Closing"); + Latching.channelDirection(LATCHING_OUT_1, POSITIVE); + Latching.strobe(4500); + delay(2500); + + Serial.println("Opening"); + Latching.channelDirection(LATCHING_OUT_1, NEGATIVE); + Latching.strobe(4500); + delay(2500); +} + +``` + +**Authors:** Ernesto E. Lopez, Lenard George Swamy + +**Reviewed by:** Ernesto E. Lopez [2021-03-18] + +**Last revision:** Lenard George Swamy [2021-04-06] diff --git a/content/tutorials/portenta-h7/ec-ard-gs/assets/ec_ard_connect_power_source.svg b/content/tutorials/portenta-h7/ec-ard-gs/assets/ec_ard_connect_power_source.svg new file mode 100644 index 00000000..7ec0cbdf --- /dev/null +++ b/content/tutorials/portenta-h7/ec-ard-gs/assets/ec_ard_connect_power_source.svg @@ -0,0 +1,491 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/content/tutorials/portenta-h7/ec-ard-gs/assets/ec_ard_gs_board_topology.svg b/content/tutorials/portenta-h7/ec-ard-gs/assets/ec_ard_gs_board_topology.svg new file mode 100644 index 00000000..c31b0957 --- /dev/null +++ b/content/tutorials/portenta-h7/ec-ard-gs/assets/ec_ard_gs_board_topology.svg @@ -0,0 +1,1271 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/content/tutorials/portenta-h7/ec-ard-gs/assets/ec_ard_gs_core.png b/content/tutorials/portenta-h7/ec-ard-gs/assets/ec_ard_gs_core.png new file mode 100644 index 00000000..835c7c74 Binary files /dev/null and b/content/tutorials/portenta-h7/ec-ard-gs/assets/ec_ard_gs_core.png differ diff --git a/content/tutorials/portenta-h7/ec-ard-gs/assets/ec_ard_gs_cover.svg b/content/tutorials/portenta-h7/ec-ard-gs/assets/ec_ard_gs_cover.svg new file mode 100644 index 00000000..837f2327 --- /dev/null +++ b/content/tutorials/portenta-h7/ec-ard-gs/assets/ec_ard_gs_cover.svg @@ -0,0 +1,1295 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/content/tutorials/portenta-h7/ec-ard-gs/assets/ec_ard_gs_power_rail.svg b/content/tutorials/portenta-h7/ec-ard-gs/assets/ec_ard_gs_power_rail.svg new file mode 100644 index 00000000..934b8042 --- /dev/null +++ b/content/tutorials/portenta-h7/ec-ard-gs/assets/ec_ard_gs_power_rail.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/content/tutorials/portenta-h7/ec-ard-gs/content.md b/content/tutorials/portenta-h7/ec-ard-gs/content.md new file mode 100644 index 00000000..8b2e0ac5 --- /dev/null +++ b/content/tutorials/portenta-h7/ec-ard-gs/content.md @@ -0,0 +1,173 @@ +--- +title: Getting Started With the Arduino Edge Control +coverImage: assets/ec_ard_gs_cover.svg +tags: [Getting Started, Setup, Blink] +description: This tutorial will give you an overview of the core features of the board, setup the development environment and introduce the APIs required to program the board. +--- + +# Getting Started With the Arduino Edge Control + +## Overview +The Edge Control board is a versatile tool that allows agriculturalists to develop creative and innovative solutions for agriculture by harnessing modern technology. In this tutorial you will set up the development environment for the board and learn to write a simple sketch that blinks the on-board LED. + +### You Will Learn +- About the basic board topology +- How to setup the development environment +- How to power up the board +- About the basic API provided by the Arduino_EdgeControl library + +### Required Hardware and Software +- Arduino Edge Control () +- Micro USB cable +- Arduino IDE 1.8.10+ +- External power source : a 12V SLA battery or 12V power supply +- 1x Phoenix connector +- 2x Jumper wires + +## Instructions + +In this getting started tutorial you will set up the Edge Control board and blink an LED. You will first learn to install the core from the Boards Manager. You will write a simple blink sketch using some fundamental APIs provided by the Arduino Edge Control Library. You will need to connect your board to an external power source and therefore have a SLA battery or a power source with you when running the sketch. + +### 1. Get to Know the Board + +The Arduino Edge Control board is designed to address the needs of **precision farming**. It provides a low power control system and modular connectivity allows you to adapt the board to your specific farming needs. The following image gives you an overview of some of the core features of the board that you need to be aware of before you can get started with the board. + +![The board topology](assets/ec_ard_gs_board_topology.svg) + +**Terminal Blocks** allows you to connect up to + +- **8** x **5V Analog Sensors** +- **4** x **4-20mA sensors** +- **16** x **Watermark Sensors** +- **16** x **Latching Devices** (e.g. Motorized Valves) + + and provides **4** x **configurable Solid State Relays**. + +***Connections to the Terminal blocks are made through the Phoenix connectors included in the kit.*** + +The **LCD Module Connector** is used to attach the LCD module to the Edge Control Board through a flat cable. + +The on-board **MKR slots 1 & 2** can be used to connect Arduino MKR boards to extend the capabilities such as connectivity through **LoRa, Wi-Fi, 2G/3G/CatM1/NBIoT**, and Sigfox. + +The board includes both a **microSD card socket** and an additional **2MB flash memory** for data storage. Both are directly connected to the main processor via a SPI interface. + +This board comes with the **Nina B306** processor which is the same processor used in other boards such as the **Nano 33 BLE**. + +### 2. The Basic Setup + +Before you start programming the Edge control board, you will have to download the [Mbed core](https://github.com/arduino/ArduinoCore-mbed) from the board manager. In the Arduino IDE open the **Board manager** search for the `Edge Control` core and install it. + +![Download the Core](assets/ec_ard_gs_core.png) + +### 3. The Blink Sketch + +Open a new sketch file, name it `hello_edge_control.ino` and add the **Edge Control library**. This library provides access to many of the different pins and functionalities of the board. + +```cpp +#include +``` + +For this example you need to ensure that the Serial communication has begun. The following code ensures that the board waits for at least 2.5 seconds before it times out and continues without an established serial connection. This guarantees that the communication has been established when connected to the Serial Monitor and also allows to work without being connected to it. Print a message on the Serial port with the text `Hello, Edge Control` + +```cpp + // Set the timeout + auto startNow = millis() + 2500; + while (!Serial && millis() < startNow); + Serial.println("Hello, Edge Control!"); +``` + +The board is designed to be very low power and for this reason some the electronics are powered off by default. Once the serial communication has been established, we need to enable the power on the power rails that we want to use. The power tree below will give you an idea of the different power rails in the board. + + +![Power rails of the Edge Control board](assets/ec_ard_gs_power_rail.svg) + +The Edge Control board uses an I/O expander in order to increase the number of digital control signals. If we want to blink the on-board LED we would need to enable the power of the I/O expander to which the LED is connected to and also enable the power to the 5V DCDC converter. 5V power line is powered by the **battery source** for which you can either use a power supply or a SLA battery to provide the required voltage. + +The `Power` class provides API access to enable the different voltages on the board. In this tutorial we need to enable the 3.3V and 5V power lines using the `enable3V3()` and `enable5V()` functions. + +```cpp +// Enable power lines +Power.enable3V3(); +Power.enable5V(); +``` + +Communication to the I/O Expander happens through the I2C port which we initialise with `Wire.begin()`. We also need to initialise the expander and configure the LED pin as OUTPUT. + +```cpp +// Start the I2C connection +Wire.begin(); + +// Initalise the expander pins +Expander.begin(); +Expander.pinMode(EXP_LED1, OUTPUT); +``` + +Inside the loop function, you can use the `Expander.digitalWrite(pin, value)` function to control the LED via the I/O expander as with a normal GPIO pin. + +```cpp +Serial.println("Blink"); +Expander.digitalWrite(EXP_LED1, LOW); +delay(500); +Expander.digitalWrite(EXP_LED1, HIGH); +delay(500); +``` + +**Hint: The Complete Sketch can be found in the Conclusions** + +### 4. Connect a Power Source + +Power sources can be connected to the on-board relay ports of the Edge Control board. Attach your **Phoenix connectors** to the **RELAY terminal** on the board. Connect two jumper wires to the **GND** and **B** pins of the **Relay ports**. + +![The power pins of the Edge Control](assets/ec_ard_connect_power_source.svg) + +Connect a jumper from the **B** pin to the positive terminal of the battery and a jumper from the **GND** pin to the negative terminal of the battery + +### 5. Upload the Sketch + +Connect the board to your computer, upload the `hello_edge_control.ino` sketch and open the **Serial Monitor**. If all the connections are done right, the LED blinks and you should be able to see the output `Hello Edge Control` and `Blink` in the serial monitor. + +## Conclusion +In this tutorial you learned how to set up your Edge Control Board, to power it, and how to use the core functions to control the GPIO pins. + +### Complete Sketch + +```cpp +#include + +void setup() { + Serial.begin(9600); + + // Set the timeout + auto startNow = millis() + 2500; + while (!Serial && millis() < startNow); + Serial.println("Hello, Edge Control Sketch!"); + + // Enable power lines + Power.enable3V3(); + Power.enable5V(); + + // Start the I2C connection + Wire.begin(); + + // Initalise the expander pins + Expander.begin(); + Expander.pinMode(EXP_LED1, OUTPUT); +} + +void loop() { + // put your main code here, to run repeatedly: + Serial.println("Blink"); + Expander.digitalWrite(EXP_LED1, LOW); + delay(500); + Expander.digitalWrite(EXP_LED1, HIGH); + delay(500); +} +``` + +### Next Steps + +We are developing new tutorials on how to connect valves, LCDs, watermark sensors and use many other functionalities of the board. In the mean time you can explore the Arduino Edge Control library to develop your own application. + +**Authors:** Lenard George +**Reviewed by:** Ernesto Lopez [2021-04-21] +**Last revision:** Sebastian Romero [2020-04-22] \ No newline at end of file diff --git a/content/tutorials/portenta-h7/metadata.json b/content/tutorials/portenta-h7/metadata.json index 1fb14693..81f93271 100644 --- a/content/tutorials/portenta-h7/metadata.json +++ b/content/tutorials/portenta-h7/metadata.json @@ -15,8 +15,13 @@ "por-ard-bl", "por-openmv-fd", "por-ard-trace32", + "por-ard-flash", "por-ard-kvs", "vs-ard-ttn", - "por-ard-lvgl" + "por-ard-lvgl", + "vs-openmv-ttn", + "por-openmv-gs", + "vs-openmv-ml", + "ec-ard-gs" ] } \ No newline at end of file diff --git a/content/tutorials/portenta-h7/por-ard-ap/content.md b/content/tutorials/portenta-h7/por-ard-ap/content.md index 146d8b3a..873a781b 100644 --- a/content/tutorials/portenta-h7/por-ard-ap/content.md +++ b/content/tutorials/portenta-h7/por-ard-ap/content.md @@ -1,41 +1,51 @@ +--- +title: Portenta H7 as a Wi-Fi Access Point +coverImage: assets/por_ard_ap_cover.svg +tags: [WiFi, Access Point, HTTP, Web Server] +description: In this tutorial you will configure the Portenta H7 as an access point and build a simple web server that will allow you to control the built-in RGB LEDs from your mobile device. +--- -# Portenta H7 as a WiFi Access Point -Portenta H7 comes with an on-board WiFi and a bluetooth Module that allows to develop IoT applications that require wireless connectivity and Internet access. Turning the board into an access point allows it to create a WiFi network on its own and allows other devices to connect to it. In this tutorial you will learn to set up your board as an access point web server and remotely control the red, green and blue LEDs on the built-in RGB LED by accessing an HTML page on your mobile device’s browser. +# Portenta H7 as a Wi-Fi Access Point -## What You Will Learn -- About the built-in WiFi + Bluetooth module. +## Overview +Portenta H7 comes with an on-board Wi-Fi and a Bluetooth® Module that allows to develop IoT applications that require wireless connectivity and Internet access. Turning the board into an access point allows it to create a Wi-Fi network on its own and allows other devices to connect to it. In this tutorial you will learn to set up your board as an access point web server and remotely control the red, green and blue LEDs on the built-in RGB LED by accessing an HTML page on your mobile device’s browser. + +### You Will Learn +- About the built-in Wi-Fi + Bluetooth® module. - How a client-server model works - How to create an HTTP communication channel between the board and an external device. -## Required Hardware and Software +### Required Hardware and Software - Portenta H7 board () - One USB C cable (either USB A to USB C or USB C to USB C) - Arduino IDE 1.8.10+ or Arduino Pro IDE 0.0.4 + - A smart phone -# Access Point Configuration -The Portenta H7 features a [Murata 1DX](https://wireless.murata.com/type-1dx.html), which is a high performance chipset which supports WiFi 802.11b/g/n + Bluetooth 5.1 BR/EDR/LE up to 65Mbps PHY data rate on WiFi and 3Mbps PHY data rate on Bluetooth. This module helps to configure the Portenta into three different modes of operation - an Access Point, a Station, or both. In this tutorial we will only focus on the access point configuration. +## Access Point Configuration +The Portenta H7 features a [Murata 1DX](https://wireless.murata.com/type-1dx.html), which is a high performance chipset which supports Wi-Fi 802.11b/g/n + Bluetooth® 5.1 BR/EDR/LE up to 65Mbps PHY data rate on Wi-Fi and 3Mbps PHY data rate on Bluetooth®. This module helps to configure the Portenta into three different modes of operation - an Access Point, a Station, or both. In this tutorial we will only focus on the access point configuration. -When the board is configured to operate as an access point, it can create its own wireless LAN ( WLAN ) network. In this mode, the board transmits and receives signals at 2.4 GHz allowing other electronic devices with WiFi capabilities using the same bandwidth to connect to the board. +When the board is configured to operate as an access point, it can create its own wireless LAN ( WLAN ) network. In this mode, the board transmits and receives signals at 2.4 GHz allowing other electronic devices with Wi-Fi capabilities using the same bandwidth to connect to the board. With the access point set up you create a client server architecture where the board provides a web server communicating with the client devices over HTTP. The connected devices can then make HTTP GET requests to the server to retrieve web pages served by the web server on the board. This makes the Portenta H7 an ideal board for developing IoT solutions where external client devices can send and receive information while more complex processing tasks take place on the server. -![A client device communicating with the Portenta H7 through HTTP ](assets/por_ard_ap_tutorial_core_topic.svg?sanitize=true) +![A client device communicating with the Portenta H7 through HTTP ](assets/por_ard_ap_tutorial_core_topic.svg) + +## Instructions -# Setting Up the Web Server +### Setting Up the Web Server In this tutorial you are going to convert the board into an access point and use it to set up a web server which provides a HTML webpage. This page contains buttons to toggle the red, green and blue colour of the built-in LED. You will then connect your mobile device to this access point and access this web page through the browser on your mobile phone. Once retrieved, you will be able to control the state of the red, green and blue LED on the built-in RGB LED from your mobile device. -![A mobile device controlling the different LEDs on the board ](assets/por_ard_ap_tutorial_overview.svg?sanitize=true) +![A mobile device controlling the different LEDs on the board ](assets/por_ard_ap_tutorial_overview.svg) -## 1. The Basic Setup +### 1. The Basic Setup Begin by plugging in your Portenta board to your computer using a USB-C cable and open the Arduino IDE or the Arduino Pro IDE. If this is your first time running Arduino sketch files on the board, we suggest you check out how to [set up the Portenta H7 for Arduino](https://www.arduino.cc/pro/tutorials/portenta-h7/por-ard-gs) before you proceed. ![The Portenta H7 can be connected to the computer using an appropriate USB-C cable](assets/por_tut1_im1.png) -## 2. Create the Web Server Sketch -Next we need to create a web server sketch that will handle the HTTP GET requests and provide the client devices with the HTML web page. The [Wifi.h](https://www.arduino.cc/en/Reference/WiFi) library provides all necessary methods that allows Arduino boards to use their WiFi features provided by the on-board WiFi module. To set up the web server copy the following code, paste it into a new sketch file and name it **SimpleWebServer.ino**. +### 2. Create the Web Server Sketch +Next we need to create a web server sketch that will handle the HTTP GET requests and provide the client devices with the HTML web page. The [Wi-Fi](https://www.arduino.cc/en/Reference/WiFi) library provides all necessary methods that allows Arduino boards to use their Wi-Fi features provided by the on-board Wi-Fi module. To set up the web server copy the following code, paste it into a new sketch file and name it **SimpleWebServer.ino**. -**Note:** You can access the final sketch inside the library: **Examples -> Arduino_Pro_Tutorials -> Portenta H7 as a WiFi Access Point -> SimpleWebServer** +**Note:** You can access the final sketch inside the library: **Examples -> Arduino_Pro_Tutorials -> Portenta H7 as a Wi-Fi Access Point -> SimpleWebServer** ```cpp #include @@ -69,7 +79,7 @@ void setup() { if(strlen(pass) < 8){ Serial.println("Creating access point failed"); - Serial.println("The WiFi password must be at least 8 characters long"); + Serial.println("The Wi-Fi password must be at least 8 characters long"); // don't continue while(true); } @@ -196,7 +206,7 @@ void printWiFiStatus() { Serial.print("SSID: "); Serial.println(WiFi.SSID()); - // print your WiFi shield's IP address: + // print your Wi-Fi shield's IP address: IPAddress ip = WiFi.localIP(); Serial.print("IP Address: "); Serial.println(ip); @@ -211,21 +221,21 @@ This sketch describes how the server will handle an incoming HTTP GET request fr Here the web page is just a simple HTML page with buttons to toggle the LED states. The way in which the web page works is: Whenever a button on the web page is pressed, the client device (in this case your phone) sends a HTTP GET request to a URL denoted by a letter, in this case H or L (H stands for HIGH, L stands for LOW) followed by the LED color that should be turned on or off r, g or b. For example to turn on the red LED the URL is /Hr . Once the server receives this request it changes the corresponding LED state, closes the connection and continues to listen to next requests. -![The sequence of actions in the tutorial’s client-server model](assets/por_ard_ap_sketch_explanation.svg?sanitize=true) +![The sequence of actions in the tutorial’s client-server model](assets/por_ard_ap_sketch_explanation.svg) ***Remember that the built-in RGB LEDs on the Portenta H7 need to be pulled to ground to make them light up. This means that a voltage level of __LOW__ on each of their pins will turn the specific color of the LED on, a voltage level of __HIGH__ will turn them off.*** -## 3. Create the arduino_secrets.h Tab +### 3. Create the arduino_secrets.h Tab A good practice is to have sensitive data like the SSID and the password required to identify and connect to a certain network within a separate file. Click on the arrow icon below the Serial Monitor button and open a new tab in the Arduino IDE. This will create a new file. -![Open a new tab in the IDE](assets/por_ard_ap_new_tab.png?sanitize=true) +![Open a new tab in the IDE](assets/por_ard_ap_new_tab.png) Name the file **arduino_secrets.h** and click OK. -![Naming the new tab arduino_secrets.h in the IDE](assets/por_ard_ap_new_tab_name.png?sanitize=true) +![Naming the new tab arduino_secrets.h in the IDE](assets/por_ard_ap_new_tab_name.png) -Once you’ve created the new tab, you will see an empty page in the IDE. Define two constants `SECRET_SSID` and `SECRET_PASS` that will hold the name of the WiFi network and the corresponding password. Add the following lines to your **arduino_secrets.h** file: +Once you’ve created the new tab, you will see an empty page in the IDE. Define two constants `SECRET_SSID` and `SECRET_PASS` that will hold the name of the Wi-Fi network and the corresponding password. Add the following lines to your **arduino_secrets.h** file: ```cpp # define SECRET_SSID "PortentaAccessPoint" @@ -240,33 +250,33 @@ In order to access the `SECRET_SSID` and `SECRET_PASS` constants in the **simple # include “arduino_secrets.h” ``` -![Including the header file arduino_secrets.h in the sketch file](assets/por_ard_ap_add_headerfile.png?sanitize=true) +![Including the header file arduino_secrets.h in the sketch file](assets/por_ard_ap_add_headerfile.png) -## 4. Upload the Code +### 4. Upload the Code Select the **Arduino Portenta H7 (M7 core)** from the **Board** menu and the port the Portenta is connected to. Upload the **simpleWebServer.ino** sketch. Doing so will automatically compile the sketch beforehand. -![Uploading the SimpleWebServer.ino to the Portenta](assets/por_ard_ap_upload_code_m7.png?sanitize=true) +![Uploading the SimpleWebServer.ino to the Portenta](assets/por_ard_ap_upload_code_m7.png) Once you've uploaded the code, open the serial monitor. You will be able to see the IP address of the access point. You will also see the message, `Device disconnected from AP` which means there are no devices connected to the Access point yet. -![Serial monitor displaying the details of the Access point](assets/por_ard_ap_open_serial_monitor.png?sanitize=true) +![Serial monitor displaying the details of the Access point](assets/por_ard_ap_open_serial_monitor.png) -## 5. Connecting to the Portenta Access Point +### 5. Connecting to the Portenta Access Point Once the access point is active and ready to be connected with external devices, you will be able to find the **PortentaAccessPoint** on the list of networks on your mobile device. Once you have entered the password you have defined earlier, your smart phone will connect to access point. -![PortentaAccessPoint shown on the list of available network devices](assets/por_ard_ap_find_ap.png?sanitize=true) +![PortentaAccessPoint shown on the list of available network devices](assets/por_ard_ap_find_ap.png) Now open a browser window on your mobile device and copy & paste the URL containing Portenta’s IP address that is displayed on the serial monitor. -![The URL containing the IP address of the access point displayed in the serial monitor](assets/por_ard_ap_copy_ip_address.png?sanitize=true) +![The URL containing the IP address of the access point displayed in the serial monitor](assets/por_ard_ap_copy_ip_address.png) Once you’ve entered the URL, the client sends a GET request to the web server to fetch the HTML web page specified in the code. Once loaded you will see the web page in your mobile browser. -![The HTML web page accessed on your mobile browser](assets/por_ard_ap_access_webpage.png?sanitize=true) +![The HTML web page accessed on your mobile browser](assets/por_ard_ap_access_webpage.png) -## 6. Access the Board From your Mobile Device +### 6. Access the Board From Your Mobile Device If you take a look at the serial monitor, you can see the details of the HTTP GET request and other details of the device connected to the access point. The GET request is always in the following format: @@ -282,17 +292,17 @@ HTTP/1.1 200 OK Once the server has responded to this request, it closes the connection and continues listening to next GET requests. -![The client details displayed on the serial monitor](assets/por_ard_ap_client_details.png?sanitize=true) +![The client details displayed on the serial monitor](assets/por_ard_ap_client_details.png) You’re now be able to toggle the states of the red, green and blue LED through the buttons displayed on your mobile browser. Everytime you press a button, the client sends a GET request to a URL in the format /Hx or /Lx ,where x can be ‘r’, ‘g’ or ‘b’, depending on the button pressed on the HTML page. The web server then reads the URL requested by the client, changes the state of the LED corresponding to the URL and closes the connection. -![The GET request details displayed in the serial monitor](assets/por_ard_ap_toggle_LEDS.png?sanitize=true) +![The GET request details displayed in the serial monitor](assets/por_ard_ap_toggle_LEDS.png) -# Conclusion +## Conclusion -This tutorial shows one of the several capabilities of the on-board WiFi+Bluetooth module by configuring the board as an access point and setting up a web server. You have also learnt how a simple client-server model and the underlying HTTP requests and responses work. +This tutorial shows one of the several capabilities of the on-board WiFi+Bluetooth® module by configuring the board as an access point and setting up a web server. You have also learnt how a simple client-server model and the underlying HTTP requests and responses work. -# Next Steps +### Next Steps Now that you've learnt how to set up a board as an access point and understand the client-server model, start experimenting with the **simpleWebServer.ino** sketch. This sketch can be tweaked in a variety of ways based on your needs. For example, you can add a slider to the HTML page that changes the blink rate of the built-in RGB LED from your mobile device. diff --git a/content/tutorials/portenta-h7/por-ard-ap/metadata.json b/content/tutorials/portenta-h7/por-ard-ap/metadata.json deleted file mode 100644 index 5f72d121..00000000 --- a/content/tutorials/portenta-h7/por-ard-ap/metadata.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "id":"por-ard-ap", - "index": 3, - "title":"Portenta H7 as a WiFi Access Point", - "slug":"por-ard-ap", - "authors":[ - "Lenard George" - ], - "coverImage":{ - "src":"assets/por_ard_ap_cover.svg?sanitize=true" - }, - "tags":[ - "WiFi", - "Access Point", - "HTTP", - "Web Server" - ], - "abstract":"In this tutorial you will configure the Portenta H7 as an access point and build a simple web server that will allow you to control the built-in RGB LEDs from your mobile device.", - "previousArticle":"por-ard-dcp", - "nextArticle":"por-ard-usb" -} diff --git a/content/tutorials/portenta-h7/por-ard-bl/content.md b/content/tutorials/portenta-h7/por-ard-bl/content.md index 672270db..b7e4332c 100644 --- a/content/tutorials/portenta-h7/por-ard-bl/content.md +++ b/content/tutorials/portenta-h7/por-ard-bl/content.md @@ -1,23 +1,32 @@ +--- +title: Updating the Portenta Bootloader +coverImage: assets/por_ard_bl_cover.svg +tags: [Bootloader, Firmware, Core] +description: This tutorial will explain what a bootloader is, why you should consider keeping it updated and how you can update it. +--- + # Updating the Portenta Bootloader + +## Overview This tutorial will explain what a bootloader is, why you should consider keeping it updated and how you can update it. The Portenta H7 also features a second ST ROM bootloader which is a separate mechanism that we don't cover in this tutorial. For the remainder of this tutorial, when we reference a bootloader, the custom bootloader provided by Arduino is meant. -## What You Will Learn +### You Will Learn - The concept of a bootloader - How to use the Arduino IDE board manager to update the Portenta core - How to use the Arduino IDE to update the Portenta bootloader to the lastest version -## Required Hardware and Software +### Required Hardware and Software - Portenta H7 board () - USB C cable (either USB A to USB C or USB C to USB C) - Arduino IDE 1.8.10+ or Arduino Pro IDE 0.0.4+ -# What is a Firmware? +## What Is a Firmware? In order to understand how a bootloader works we first need to understand what a firmware is in the world of Arduino. A firmware consists of your sketch (.ino file) plus a couple of files and libraries that give you access to the hardware functions. Those files and libraries together make a bundle that is called a **core**. If you ever wondered what exactly `digitalWrite(pin, HIGH)` does under the hood, the core is the place where you need to look. That also explains why the different hardware architectures on the different Arduino boards need a separate core. Because the hardware level implementation of a function like `digitalWrite` is hardware specific. ![The firmware consists of your sketch plus the core for the chosen micro controller board](assets/por_ard_bl_firmware.svg) -# What is a Bootloader? +## What Is a Bootloader? A bootloader is a small application that gets started when an Arduino board gets powered. When you order an official Arduino board it comes pre-flashed with a bootloader. @@ -32,15 +41,15 @@ Both the bootloader and the firmware have predefined (but adjustable) locations ![There are predefinded, but adjustable locations in the memory where the firmwares and the bootloader get installed](assets/por_ard_bl_flash_memory.svg) +## Instructions - -# Flashing the Latest Bootloader +### Flashing the Latest Bootloader Even though the Arduino boards come pre-flashed with a bootloader there are sometimes improvements or bug fixes which get integrated into an updated bootloader version. They usually improve stability and performance. To benefit from that it makes sense to update it when there is a new version. The bootloader is stored in a location that doesn't get overwritten by a firmware being uploaded to the Portenta. If you upload for example the OpenMV firmware and then later decide to switch back to a regular Arduino firmware the bootloader won't be affected. -## 1. Updating the Core -New versions of the bootloader normally get shipped together with the core. That means you first have to update the core before you can update the bootloader. To do so open the board manager in the menu under *Tools->Board->Boards Manager...* +### 1. Updating the Core +New versions of the bootloader normally get shipped together with the core. That means you first have to update the core before you can update the bootloader. To do so open the board manager in the menu under **Tools->Board->Boards Manager...** ![Open the Boards Manager from the Tools menu](assets/por_ard_bl_boards_manager.png) @@ -48,7 +57,7 @@ In the board manager and search for "portenta". Find the Arduino mbed-enabled B ![A search for "portenta" reveals the core that needs to be updated to get the latest bootloader](assets/por_ard_bl_update_core.png) -## 2. Updating the Bootloader +### 2. Updating the Bootloader To update the bootloader you can use the **PortentaH7_updateBootloader** sketch. You can find the sketch file under **File > Examples > Portenta_System** ![Finding the bootloader updater sketch](assets/por_ard_bl_find_sketch_file.png) @@ -67,15 +76,15 @@ You will see the message "Bootloader update complete. You may now disconnect the **Expert tip:** Newer versions of the bootloader allow to fetch the version number via dfu-util. To see it, put the Portenta board into bootloader mode and invoke the dfu-util command: `dfu-util -l | grep "Bootloader"`. The dfu-util command can be found in the Arduino15/packages/arduino/tools/dfu-util directory. -# Conclusion +## Conclusion Having an updated bootloader is important to benefit from improved performance and resolved bugs. As mentioned earlier we recommend to keep an eye out for new releases on our [Github repository](https://github.com/arduino/ArduinoCore-mbed/tree/master/bootloaders) and update the bootloader whenever there is a new release available. -# Troubleshooting -## LIBUSB_ERROR_IO Error +## Troubleshooting +### LIBUSB_ERROR_IO Error There are times when your code compiles perfectly but is unable to upload. If you encounter the following error message, it means that the IDE has encountered issues communicating with the USB interface of your board. -``` +```cpp dfu-util: error get_status: LIBUSB_ERROR_IO Opening DFU capable USB device... ``` @@ -83,5 +92,5 @@ Opening DFU capable USB device... Unplug and plug in the board to re-initiate the USB interface and you should be able to upload the code without issues. **Authors:** Sebastian Romero, Lenard George -**Reviewed by:** Lenard George [7.10.2020] -**Last revision:** Sebastian Romero [8.10.2020] \ No newline at end of file +**Reviewed by:** Lenard George [2020-10-07] +**Last revision:** Sebastian Romero [2020-10-08] \ No newline at end of file diff --git a/content/tutorials/portenta-h7/por-ard-bl/metadata.json b/content/tutorials/portenta-h7/por-ard-bl/metadata.json deleted file mode 100644 index 0af07d90..00000000 --- a/content/tutorials/portenta-h7/por-ard-bl/metadata.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "id":"por-ard-bl", - "index": 6, - "title":"Updating the Portenta Bootloader", - "slug":"por-ard-bl", - "authors":[ - "Sebastian Hunkeler", - "Lenard George" - ], - "coverImage":{ - "src":"assets/por_ard_bl_cover.svg?sanitize=true" - }, - "tags":[ - "Bootloader", - "Firmware", - "Core" - ], - "abstract":"This tutorial will explain what a bootloader is, why you should consider keeping it updated and how you can update it.", - "previousArticle":"por-openmv-bt", - "nextArticle":"por-openmv-fd" -} diff --git a/content/tutorials/portenta-h7/por-ard-ble/content.md b/content/tutorials/portenta-h7/por-ard-ble/content.md index c905ed6b..0bb11e07 100644 --- a/content/tutorials/portenta-h7/por-ard-ble/content.md +++ b/content/tutorials/portenta-h7/por-ard-ble/content.md @@ -1,12 +1,21 @@ +--- +title: BLE Connectivity on Portenta H7 +coverImage: assets/por_ard_ble_cover.svg +tags: [BLE, LED, Connectivity, Bluetooth] +description: This tutorial explains how to use BLE connectivity on the Portenta H7 to control the built-in LED using an external Bluetooth application. +--- + # BLE Connectivity on Portenta H7 -In this tutorial we will enable low energy bluetooth (BLE) on the Portenta H7 to allow an external bluetooth device to control the built-in LED either by turning it on or off. -## What You Will Learn +## Overview +In this tutorial we will enable low energy Bluetooth® (BLE) on the Portenta H7 to allow an external Bluetooth® device to control the built-in LED either by turning it on or off. + +### You Will Learn - Enabling BLE connectivity on the Portenta H7. - Connecting the Portenta to an external BLE Mobile Application (In this case [nRF Connect](https://www.nordicsemi.com/Software-and-tools/Development-Tools/nRF-Connect-for-mobile) by Nordic Semiconductor). -## Required Hardware and Software +### Required Hardware and Software - Portenta H7 board () - USB C cable (either USB A to USB C or USB C to USB C) @@ -14,30 +23,31 @@ In this tutorial we will enable low energy bluetooth (BLE) on the Portenta H7 to - Mobile device, phone or tablet - [nRFconnect](https://www.nordicsemi.com/Software-and-tools/Development-Tools/nRF-Connect-for-mobile) or equivalent tool downloaded on your mobile device: [nRF Connect for iOS](https://itunes.apple.com/us/app/nrf-connect/id1054362403?ls=1&mt=8) or [nRF Connect for android](https://play.google.com/store/apps/details?id=no.nordicsemi.android.mcp) -# Portenta and Low Energy Bluetooth (BLE) -The onboard WiFi/Bluetooth module of the H7 offers low energy bluetooth functionality that gives the board the flexibility to be easily connected to devices which also support BLE such as the Arduino Nano 33 IoT or most modern smart phones. Compared to classic Bluetooth, Low Energy Bluetooth is intended to provide considerably reduced power consumption and cost while maintaining a similar communication range. +## Portenta and Low Energy Bluetooth® (BLE) +The onboard WiFi/Bluetooth® module of the H7 offers low energy Bluetooth® functionality that gives the board the flexibility to be easily connected to devices which also support BLE such as the Arduino Nano 33 IoT or most modern smart phones. Compared to classic Bluetooth®, Low Energy Bluetooth® is intended to provide considerably reduced power consumption and cost while maintaining a similar communication range. +## Instructions -# Configuring the Development Environment -To communicate with the Portenta H7 via Bluetooth, we are going to start by uploading a pre-built sketch that starts a Bluetooth network and allows your mobile device, which will be used to control the LEDs, to connect to it. The sketch uses the [ArduinoBLE](https://www.arduino.cc/en/Reference/ArduinoBLE) Library that enables the BLE module and handles important functions such as scanning, connecting and interacting with services provided by other devices. You will also be using a third party application (e.g. [nRF Connect](https://www.nordicsemi.com/Software-and-tools/Development-Tools/nRF-Connect-for-mobile)), running on your mobile device that will connect your device to the board and help you control the built-in LED. +### Configuring the Development Environment +To communicate with the Portenta H7 via Bluetooth®, we are going to start by uploading a pre-built sketch that starts a Bluetooth® network and allows your mobile device, which will be used to control the LEDs, to connect to it. The sketch uses the [ArduinoBLE](https://www.arduino.cc/en/Reference/ArduinoBLE) Library that enables the BLE module and handles important functions such as scanning, connecting and interacting with services provided by other devices. You will also be using a third party application (e.g. [nRF Connect](https://www.nordicsemi.com/Software-and-tools/Development-Tools/nRF-Connect-for-mobile)), running on your mobile device that will connect your device to the board and help you control the built-in LED. -![BLE Configuration Scheme](assets/por_ard_ble_configuration.svg?sanitize=true) +![BLE Configuration Scheme](assets/por_ard_ble_configuration.svg) -## 1. The Basic Setup +### 1. The Basic Setup -Begin by plugging in your Portenta board to the computer using a USB-C cable and open the Arduino IDE or the Arduino Pro IDE. If this is your first time running Arduino sketch files on the board, we suggest you check out how to [set up the Portenta H7 for Arduino](https://github.com/bcmi-labs/arduino-pro-content/blob/master/content/tutorials/portenta-h7/por-ard-usb/por-ard-gs) before you proceed. +Begin by plugging in your Portenta board to the computer using a USB-C cable and open the Arduino IDE or the Arduino Pro IDE. If this is your first time running Arduino sketch files on the board, we suggest you check out how to [set up the Portenta H7 for Arduino](https://www.arduino.cc/pro/tutorials/portenta-h7/por-ard-gs) before you proceed. -![The Portenta H7 can be connected to the computer using an appropriate USB-C cable](assets/por_ard_ble_basic_setup.svg?sanitize=true) +![The Portenta H7 can be connected to the computer using an appropriate USB-C cable](assets/por_ard_ble_basic_setup.svg) -## 2. Install the ArduinoBLE Library +### 2. Install the ArduinoBLE Library You will need to install the ArduinoBLE library in the Arduino IDE you are using. For this example we will use the classic Arduino IDE. To install the library go to : **Tools -> Manage Libararies...** type **ArduinoBLE** and click **Install**. Make sure you install ArduinoBLE version 1.1.3 or higher. -![Download the BLE library in the Library Manager.](assets/por_ard_ble_arduino_library.png?sanitize=true) +![Download the BLE library in the Library Manager.](assets/por_ard_ble_arduino_library.png) -## 3. Create the BLE Sketch +### 3. Create the BLE Sketch Let's program the Portenta with the following example sketch. If the BLE module can be initialized correctly, you will see the blue LED lighting up for one second after uploading the sketch. If it fails you will see the red LED lighting up instead. Copy and paste the following code into a new sketch in your IDE or by open it from: **Examples -> Arduino_Pro_Examples -> BLE Connectivity on Portenta H7 -> PortentaBLE** @@ -133,14 +143,14 @@ void loop() { } ``` -In our example we use a pre-defined bluetooth number code pre-setup for controlling a device's LEDs. This code can also be referred to as [GATT codes](https://www.bluetooth.com/specifications/gatt/services/), which define how two bluetooth low energy devices transfer data. Once a connection is established with a device, its respecitve GATT code, which is a 16 bit identifier, is stored in a lookup table for future reference. +In our example we use a pre-defined Bluetooth® number code pre-setup for controlling a device's LEDs. This code can also be referred to as [GATT codes](https://www.bluetooth.com/specifications/gatt/services/), which define how two Bluetooth® low energy devices transfer data. Once a connection is established with a device, its respecitve GATT code, which is a 16 bit identifier, is stored in a lookup table for future reference. These GATT codes are very long, but in our example it is always the same code: ```BLEService ledService("19b10000-e8f2-537e-4f6c-d104768a1214"); // BLE LED Service``` ***Remember that on the Portenta the built-in LED is turned on by setting it to LOW and turned off by setting it to HIGH, the opposite of most other Arduino boards.*** -## 4. Upload the Sketch +### 4. Upload the Sketch Double press the reset button so the built-in LED is slowly pulsing green. Then, select your board in the menu: **Tools** -> **Board** -> **Arduino Portenta H7 (M7 core)** ![Select the Arduino Portenta H7 (M7 core) in the board selector.](assets/por_ard_ble_select_board_h7.png) @@ -149,7 +159,7 @@ Then choose the **Port** where your Portenta is connected to and **Upload** the ![Select the Port to which the Portenta is connected to.](assets/por_ard_ble_select_port.png) -## 5. Connect an External Device +### 5. Connect an External Device On your mobile device install **nRF Connect** or an equivalent app that allows for BLE connections. We will refer to **nRF Connect** for the rest of this tutorial. @@ -163,18 +173,18 @@ Once you have downloaded the nRF application on your mobile device look for your ![In the nRF Connect app use a Bool toggle switch to toggle the built-in LED.](assets/por_ard_ble_nrf_connect.png) -# Conclusion +## Conclusion This tutorial shows how to connect and control the built-in LED using a BLE connection. You have learnt how a simple BLE connection between your Portenta and your cell phone which has basic communication abilities between the two devices works. -# Next Steps +### Next Steps Now that you learnt how to configure the Portenta as a BLE endpoint you can try with two Portentas (or other BLE capable Arduino devices) to facilitate bidirectional communication. More information on how to achieve that can be found on the [BLE library reference page](https://www.arduino.cc/en/Reference/ArduinoBLE) -# Troubleshooting -## Sketch Upload Troubleshooting +## Troubleshooting +### Sketch Upload Troubleshooting If you try to upload a sketch and receive an error message, saying that the upload has failed you can try to upload the sketch while the Portenta H7 is in bootloader mode. To do so you need to double click the reset button. The green LED will start fading in and out. Try to upload the sketch again. The green LED will stop fading when the upload completes. **Authors:** Jeremy Ellis, Lenard George, Sebastian Romero -**Reviewed by:** Lenard George, Sebastian Romero, José Garcia [02.09.2020] -**Last revision:** Sebastian Romero [16.09.2020] +**Reviewed by:** Lenard George, Sebastian Romero, José Garcia [2020-09-02] +**Last revision:** Sebastian Romero [2020-09-16] diff --git a/content/tutorials/portenta-h7/por-ard-ble/metadata.json b/content/tutorials/portenta-h7/por-ard-ble/metadata.json deleted file mode 100644 index 7cf73f4f..00000000 --- a/content/tutorials/portenta-h7/por-ard-ble/metadata.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "id":"por-ard-ble", - "index": 4, - "title":"BLE Connectivity on Portenta H7", - "slug":"por-ard-ble", - "authors":[ - "Jeremy Ellis", - "Lenard George", - "Sebastian Romero" - ], - "coverImage":{ - "src":"assets/por_ard_ble_cover.svg?sanitize=true" - }, - "tags":[ - "BLE", - "LED", - "Connectivity", - "Bluetooth" - ], - "abstract":"This tutorial explains how to use BLE connectivity on the Portenta H7 to control the built-in LED using an external Bluetooth application.", - "previousArticle":"por-ard-usb", - "nextArticle":"por-openmv-bt" -} diff --git a/content/tutorials/portenta-h7/por-ard-dcp/content.md b/content/tutorials/portenta-h7/por-ard-dcp/content.md index efc94563..2cbddad1 100644 --- a/content/tutorials/portenta-h7/por-ard-dcp/content.md +++ b/content/tutorials/portenta-h7/por-ard-dcp/content.md @@ -1,35 +1,46 @@ +--- +title: Dual Core Processing +coverImage: assets/por_ard_dcp_cover.svg +tags: [Multitasking, RGB, LED] +description: In this tutorial you will run two classic Arduino blink programs simultaneously on different cores of the Portenta board that blinks the RGB LED in two different colours. +--- + # Dual Core Processing + +## Overview The Portenta H7 is equipped with a processor that has two processing units called cores. Dual core processing is the ability of a processor to read and execute instructions in two different cores simultaneously. In other words, a dual core processor can execute two applications, in this case two Arduino sketches, at the same time. In this tutorial you will run two classic Arduino blink programs simultaneously on different cores of the Portenta board that blinks the RGB LED in two different colours. -## What You Will Learn +### You Will Learn - How to upload and run applications on Portenta's M7 and M4 cores. - About the characteristics of the M7 and the M4 cores. - How to force boot the M4 core through the M7 core and why that is necessary. - Controlling the colours of the built-in RGB LED.  -## Required Hardware and Software +### Required Hardware and Software - Portenta H7 board  - USB C cable (either USB A to USB C or USB C to USB C) - Arduino IDE 1.8.10+ or Arduino Pro IDE 0.0.4+  -# Cortex® M7 & M4 +## Cortex® M7 & M4 Processor cores are individual processing units within the board's main processing unit (don't confuse a processor core with an [Arduino core](https://www.arduino.cc/en/guide/cores)). These cores are responsible for the executing instructions at a particular clock speed.  The on-board  Arm Cortex processor comes with two cores (Cortex® M7 and M4), with slightly different architectures and clock speeds. The M7  runs at 480 MHz and the architecture is designed  to separate Instruction and Data buses to optimize CPU latency. The M4 runs at 240 MHz and the architecture supports the ART™ accelerator (a block that speeds up instruction fetching accesses of the Cortex-M4 core to the D1-domain internal memories). The higher clock rate of the M7 makes it suitable to handle complex processing tasks such as data storage, debugging or handling input/output peripherals at a higher efficiency compared to the M4. The dual core processor of the Portenta H7 sets it apart from other single core Arduino boards by allowing true multitasking, faster data processing capabilities, enhanced processing power and application partitioning.   -![The Architectures of Cortex® M7 and M4 cores.](assets/por_ard_dcp_m4_m7_architectures.svg?sanitize=true) +![The Architectures of Cortex® M7 and M4 cores.](assets/por_ard_dcp_m4_m7_architectures.svg) + +## Instructions -# Accessing the M7 and M4 Core +### Accessing the M7 and M4 Core To best illustrate the idea of dual core processing, you will be running two separate sketch files. One on each of the cores which blinks the RGB LED in a different colour. The **BlinkRedLed_M7.ino** sketch will set the built-in RGB LED on the board to red and blink it with a delay of 500 ms. The **BlinkGreenLed_M4.ino** sketch will access the green LED in the RGB led and blink it with a delay of 200 ms. Both the cores will be executing the corresponding sketch file simultaneously and as a result both the green and red LED blink, however, at different intervals. -![Running two different sketch files on the different cores.](assets/por_ard_dcp_tutorial_overview.svg?sanitize=true) +![Running two different sketch files on the different cores.](assets/por_ard_dcp_tutorial_overview.svg) -## 1. The Basic Setup +### 1. The Basic Setup Begin by plugging-in your Portenta board to your computer using an appropriate USB-C cable and have the  Arduino IDE or the Arduino Pro IDE  open. If this is your first time running Arduino sketch files on the board, we suggest you check out how to [Setting Up Portenta H7 For Arduino](https://www.arduino.cc/pro/tutorials/portenta-h7/por-ard-gs) before you proceed. -![A Basic setup of the board attached to your computer](../por-ard-gs/assets/por_ard_gs_basic_setup.svg?sanitize=true) +![A Basic setup of the board attached to your computer](../por-ard-gs/assets/por_ard_gs_basic_setup.svg) **Note:** You can access the examples from the tutorials library once it's installed: **Examples -> Arduino_Pro_Tutorials -> Dual Core Processing** -## 2. Setting the LED Color +### 2. Setting the LED Color In the previous tutorial you learned how to access the built-in RGB LED through the macro definition LED_BUILTIN. You can also control the distinct Red, Green and Blue LED separately through the LEDR, LEDG and LEDB macro definition respectively.  Please note that, opposed to other Arduino boards, on the Portenta H7 the built-in RGB led pins need to be pulled to ground to make the LED light up. This means that a voltage level of LOW will turn the LED on, a voltage level of HIGH will turn it off. @@ -52,12 +63,12 @@ void loop() { } ``` -## 3. Upload the Sketch to the M7 Core +### 3. Upload the Sketch to the M7 Core Select the **Arduino Portenta H7 (M7 core)** from the **Board** menu and the port the Portenta is connected to (e.g. /dev/cu.usbmodem141101). Upload the **BlinkRedLed_M7.ino** sketch. Doing so will automatically compile the sketch beforehand. When the sketch is uploaded the RGB LED on the board will start blinking red. ![Uploading the BlinkRedLed_M7 sketch to the M7 core](assets/por_ard_dcp_upload_code_m7.png) -## 4. Making the LED Blink Green +### 4. Making the LED Blink Green Let's write another sketch that makes the RGB LED on the board blink green. Open a new sketch file and call it **BlinkGreenLed_M4.ino**. Copy and paste the following program that blinks the LED green, denoted by the variable `LEDG`, with a delay of 500ms. This time the blinking is controlled by the M4 core. ```cpp @@ -78,10 +89,10 @@ void loop() { If you would upload the sketch to the M4 at this point nothing would change. The reason is that the M4 core doesn't start up by itself. There is one more step required to make the M4 core blink the LED green: Force booting the M4. -## 5. Force Booting the M4 core +### 5. Force Booting the M4 Core The bootloader of the H7 boards is configured in such a way that only M7 gets booted automatically. The reason is that for simple use cases the M4 may not be needed and hence be unprogrammed and doesn't need to get powered. One such instance is when the M7 doesn't have the appropriate firmware that automatically handles the initialization of the M4. As a result you need to force boot the M4 so that it can run a sketch. You can do so through the M7 using a special command, `bootM4()` that boots the M4 when the board is powered. -![The M7 and the M4 cores share the flash memory where the sketches are stored.](assets/por_ard_dcp_m4_m7_flash_memory.svg?sanitize=true) +![The M7 and the M4 cores share the flash memory where the sketches are stored.](assets/por_ard_dcp_m4_m7_flash_memory.svg) Before you can upload the code for the M4 core to the flash memory you need to add the `bootM4()` command in the **BlinkRedLed_M7.ino** sketch file that is uploaded and run by the M7 core. Copy and paste the following command `bootM4()` inside the `setup()` function of the **BlinkRedLed_M7.ino** sketch and upload the sketch to M7 once again. @@ -104,19 +115,19 @@ void loop() { Once this sketch runs on the M7 core, it boots the M4 core and allows it to run its corresponding sketch. -## 6. Uploading to the M4 Core +### 6. Uploading to the M4 Core The final step is to upload the sketch that we prepared for the M4. Now open **Tools> Boards** from the IDE menu and select **Arduino Portenta H7 (M4 core)** from the boards. Upload the **BlinkGreenLed_M4.ino** to the board. Note that there is no separate serial port listed for the M4 in the port menu as the M7 takes care of the serial communication. The RGB LED blinking in RED currently, starts blinking in green simultaneously at an interval of 500 ms. When the blinking overlaps the mix of red and green light is perceived as yellow. ![Uploading the BlinkGreenLed_M4 to the M4 core](assets/por_ard_dcp_upload_code_m4.png) -# Programming both Cores with just one sketch +### Programming Both Cores With Just One Sketch So far, we used separate sketch files to program the different cores. We can also combine these two sketch files into one by taking advantage the preprocessor directives '#ifdef'. This way you can program different behaviors for both cores by using the same program. ***Programming bigger applications by using this method may increase the difficulty of the program you will need to create.*** Let's now to create a new sketch to blink both of LEDs with random sequences, this will allow you to clearly see different behaviors for both of the LEDs using a very simple program. -## 1. Programming the M7 Core Set-up +### 1. Programming the M7 Core Set-Up Let's start by opening a new sketch and naming it **BlinkBothCores.ino**. Then let's add the following lines of code. ```cpp @@ -134,7 +145,7 @@ void setup() { The code between `#ifdef CORE_CM7` and `#endif` will only apply for the M7 Core to boot the M4 core allowing it to run its corresponding sketch, that portion of code will also allow the M7 core control the blue LED of the board. -## 2. Programming the M4 Core Set-up +### 2. Programming the M4 Core Setup Then, as well inside the `setup()` function, you will need to include the following lines to configure properly the green LED in the M4 core. ```cpp @@ -143,7 +154,7 @@ Then, as well inside the `setup()` function, you will need to include the follow #endif ``` -## 3. Finishing the setup() function and programming the loop() +### 3. Finishing the Setup() Function and Programming the Loop() To finish with the `setup()` you will need to initialise the LEDs as outputs. Then in the `loop()` function you will need to include the sequence that blink the LEDs. to do so, add the following portion of code right after the `#endif`. @@ -162,12 +173,12 @@ void loop() { Now can upload the sketch to both the cores of the Portenta H7 individually. With this sketch, you will be able to control the LED through both the cores (M4 and M7) and you should be able to see the Blue and Green LEDs of the Portenta board blinking with different sequences. -# Conclusion +## Conclusion This tutorial introduces the idea of dual core processing and illustrates the concept by using the M7 and M4 cores to control the different colors of the built-in RGB LED. This simple example only describes how to access the M7 and M4 cores. In the upcoming tutorials you will learn to create applications that leverage the potential of dual core processing to perform more complex tasks.  -# Next steps -- Proceed with the next tutorial "Setting Up a WiFi Access Point" to learn how to make use of the built-in WiFi module and configure your Portena H7 as a WiFi access point. +### Next Steps +- Proceed with the next tutorial "Setting Up a Wi-Fi Access Point" to learn how to make use of the built-in Wi-Fi module and configure your Portena H7 as a Wi-Fi access point. **Authors:** Lenard George, Sebastian Hunkeler -**Reviewed by:** José Garcia [20.03.2020] -**Last revision:** 23.4.2020 +**Reviewed by:** José Garcia [2020-03-20] +**Last revision:** Sebastian Romero [2021-02-03] diff --git a/content/tutorials/portenta-h7/por-ard-dcp/metadata.json b/content/tutorials/portenta-h7/por-ard-dcp/metadata.json deleted file mode 100644 index 540e6e5d..00000000 --- a/content/tutorials/portenta-h7/por-ard-dcp/metadata.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "id":"por-ard-dcp", - "index": 2, - "title":"Dual Core Processing", - "slug":"por-ard-dcp", - "authors":[ - "Lenard George", - "Sebastian Hunkeler" - ], - "coverImage":{ - "src":"assets/por_ard_dcp_cover.svg?sanitize=true" - }, - "tags":[ - "Multitasking", - "RGB", - "LED" - ], - "abstract":"In this tutorial you will run two classic Arduino blink programs simultaneously on different cores of the Portenta board that blinks the RGB LED in two different colours.", - "previousArticle":"por-ard-gs", - "nextArticle":"por-ard-ap" -} diff --git a/content/tutorials/portenta-h7/por-ard-flash/assets/por_ar_flash_memory.svg b/content/tutorials/portenta-h7/por-ard-flash/assets/por_ar_flash_memory.svg new file mode 100644 index 00000000..2c578353 --- /dev/null +++ b/content/tutorials/portenta-h7/por-ard-flash/assets/por_ar_flash_memory.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/content/tutorials/portenta-h7/por-ard-flash/assets/por_ard_block_device_cover.svg b/content/tutorials/portenta-h7/por-ard-flash/assets/por_ard_block_device_cover.svg new file mode 100644 index 00000000..1f958fcf --- /dev/null +++ b/content/tutorials/portenta-h7/por-ard-flash/assets/por_ard_block_device_cover.svg @@ -0,0 +1,543 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/content/tutorials/portenta-h7/por-ard-flash/content.md b/content/tutorials/portenta-h7/por-ard-flash/content.md new file mode 100644 index 00000000..cfed382c --- /dev/null +++ b/content/tutorials/portenta-h7/por-ard-flash/content.md @@ -0,0 +1,455 @@ +--- +title: Reading and Writing Flash Memory +coverImage: assets/por_ard_block_device_cover.svg +tags: [Storage, Flash, Block Device] +description: This tutorial demonstrates how to use the on-board flash memory of the Portenta H7 to read and write data using the BlockDevice API provided by Mbed OS. +--- + +# Using the Flash Storage To Read and Write Data +## Overview +This tutorial demonstrates how to use the on-board flash memory of the Portenta H7 to read and write data using the BlockDevice API provided by Mbed OS. As the internal memory is limited in size we will also take a look at saving data to the QSPI flash memory. + +### You Will Learn +- Accessing the Portenta's internal flash memory using Mbed's Flash In-Application Programming Interface +- Accessing the Portenta's QSPI flash memory using Mbed's Flash In-Application Programming Interface +- Reading the memory's characteristics + +### Required Hardware and Software +- Portenta H7 board () +- USB-C cable (either USB-A to USB-C or USB-C to USB-C) +- Arduino IDE 1.8.10+ or Arduino Pro IDE 0.0.4+ or Arduino CLI 0.13.0+ + +## Mbed OS APIs for Flash Storage +Portenta's core is based on the Mbed operating system, allowing for Arduino APIs to be integrated using APIs exposed directly by Mbed OS. + +Mbed OS has a rich API for managing storage on different mediums, ranging from the small internal flash memory of a microcontroller to external SecureDigital cards with large data storage space. + +In this tutorial, we are going to save a value persistently inside the flash memory. That allows to access that value even after rebooting the board. We will retrieve some information from a flash block by using the [FlashIAPBlockDevice](https://os.mbed.com/docs/mbed-os/v6.9/apis/flashiapblockdevice.html) API and create a block device object within the available space of the memory. In case of the internal memory it's the space which is left after uploading a sketch to the board. + +***Be aware of the flash r/w limits: flash memories have a limited amount of read/write cycles. Typical flash memories can perform about 10000 writes cycles to the same block before starting to "wear out" and begin to lose the ability to retain data. You can render your board useless with improper use of this example and described APIs.*** + +## Block Device Blocks + +Blocks of flash memory can be accessed through the block device APIs. They are byte addressable but operate in units of blocks. There are three types of blocks for the different block device operations: read blocks, erase blocks and program blocks. The recommended procedure for programming data is to first erase a block and then programming it in units of the program block size. The sizes of the erase, program and read blocks may not be the same but they must be multiples of each another. Keep in mind that the state of an erased block is undefined until you program it with data. + +![Conceptual view of how the block sizes relate to one another](assets/por_ar_flash_memory.svg) + +## Programming the Internal Flash + +### 1. Create the Structure of the Program +Before we start it's important to keep the above mentioned **flash r/w limits** in mind! Therefore this method should only be used for **once-in-a-while** read and write **operations** such as reading a user setting in the `setup()`. It is not a good idea to use it for constantly updated values such as sensor data. + +Having this in mind, it's time to create a sketch to program the Portenta. After creating new sketch and giving it a fitting name (in our case `FlashStorage.ino`) we need to create one more file to be used by the sketch, called `FlashIAPLimits.h`, that we will use to define some helper functions. This allows us to reuse the helper file later for other sketches. + +### 2. The Helper Functions +Within the `FlashIAPLimits.h` file we start by including necessary libraries and defining the namespace. + +```cpp +// Ensures that this file is only included once +#pragma once + +#include +#include +#include + +using namespace mbed; +``` + +After that we create a struct which will later be used to save the storage's properties. + +```cpp +// An helper struct for FlashIAP limits +struct FlashIAPLimits { + size_t flash_size; + uint32_t start_address; + uint32_t available_size; +}; +``` + +The last part of the helper file consists of the `getFlashIAPLimits()` function used to calculate both the size of the flash memory as well as the size and start address of the available memory. +This is done with Mbed's [FlashIAP](https://os.mbed.com/docs/mbed-os/v6.6/mbed-os-api-doxy/classmbed_1_1_flash_i_a_p.html) API. It finds the address of the first sector after the sketch stored in the microcontroller's ROM: `FLASHIAP_APP_ROM_END_ADDR` and uses the FlashIAP to calculate the flash memory's size with `flash.get_flash_size()`. The other parameters can be determined using the same API. + +```cpp +// Get the actual start address and available size for the FlashIAP Block Device +// considering the space already occupied by the sketch (firmware). +FlashIAPLimits getFlashIAPLimits() +{ + // Alignment lambdas + auto align_down = [](uint64_t val, uint64_t size) { + return (((val) / size)) * size; + }; + auto align_up = [](uint32_t val, uint32_t size) { + return (((val - 1) / size) + 1) * size; + }; + + size_t flash_size; + uint32_t flash_start_address; + uint32_t start_address; + FlashIAP flash; + + auto result = flash.init(); + if (result != 0) + return { }; + + // Find the start of first sector after text area + int sector_size = flash.get_sector_size(FLASHIAP_APP_ROM_END_ADDR); + start_address = align_up(FLASHIAP_APP_ROM_END_ADDR, sector_size); + flash_start_address = flash.get_flash_start(); + flash_size = flash.get_flash_size(); + + result = flash.deinit(); + + int available_size = flash_start_address + flash_size - start_address; + if (available_size % (sector_size * 2)) { + available_size = align_down(available_size, sector_size * 2); + } + + return { flash_size, start_address, available_size }; +} +``` + +### 3. Reading & Writing Data +Going back to the `FlashStorage.ino` file some more files need to be included in order to implement reading add writing to the flash. The `FlashIAPBlockDevice.h` API will be used to create a block device in the empty part of the memory. Additionally we include the helper file `FlashIAPLimits.h` to have access to the address and size calculation function that we just created. We also reference the `mbed` namespace for better readability. + +```cpp +#include +#include "FlashIAPLimits.h" + +using namespace mbed; +``` + +The `setup()` function will first wait until a serial connection is established and then feed the random number generator, which is used later in this tutorial to write a random number in the flash memory every time the device boots up. + +```cpp +void setup() { + Serial.begin(115200); + while (!Serial); + + Serial.println("FlashIAPBlockDevice Test"); + Serial.println("------------------------"); + + // Feed the random number generator for later content generation + randomSeed(analogRead(0)); +``` + +Next the helper function, defined in the `FlashIAPLimits.h` file, is called to calculate the memory properties that are then used to create a block device using the `FlashIAPBlockDevice.h` library. + +```cpp +// Get limits of the the internal flash of the microcontroller +auto [flashSize, startAddress, iapSize] = getFlashIAPLimits(); + +Serial.print("Flash Size: "); +Serial.print(flashSize / 1024.0 / 1024.0); +Serial.println(" MB"); +Serial.print("FlashIAP Start Address: 0x"); +Serial.println(startAddress, HEX); +Serial.print("FlashIAP Size: "); +Serial.print(iapSize / 1024.0 / 1024.0); +Serial.println(" MB"); + +// Create a block device on the available space of the flash +FlashIAPBlockDevice blockDevice(startAddress, iapSize); +``` + +Before using the block device the first step is to initialize it using `blockDevice.init()`. Once initialized it can provide the sizes of the blocks for programming the flash. In terms of reading and writing flash memory blocks there is a distinction between the size of a readable block in bytes, a programmable block, which size is always a multiple of the read size and an erasable block which is always a multiple of a programmable block. + +When reading and writing directly from and to the flash memory we need to always allocate a buffer with a multiple of the program block size. The amount of required program blocks can be determined by dividing the data size by the program block size. The final buffer size is equal to the amount of program blocks multiplied by the program block size. + +```cpp +// Initialize the Flash IAP block device and print the memory layout +blockDevice.init(); + +const auto eraseBlockSize = blockDevice.get_erase_size(); +const auto programBlockSize = blockDevice.get_program_size(); + +Serial.println("Block device size: " + String((unsigned int) blockDevice.size() / 1024.0 / 1024.0) + " MB"); +Serial.println("Readable block size: " + String((unsigned int) blockDevice.get_read_size()) + " bytes"); +Serial.println("Programmable block size: " + String((unsigned int) programBlockSize) + " bytes"); +Serial.println("Erasable block size: " + String((unsigned int) eraseBlockSize / 1024) + " KB"); + +String newMessage = "Random number: " + String(random(1024)); + +// Calculate the amount of bytes needed to store the message +// This has to be a multiple of the program block size +const auto messageSize = newMessage.length() + 1; // C String takes 1 byte for NULL termination +const unsigned int requiredEraseBlocks = ceil(messageSize / (float) eraseBlockSize); +const unsigned int requiredProgramBlocks = ceil(messageSize / (float) programBlockSize); +const auto dataSize = requiredProgramBlocks * programBlockSize; +char buffer[dataSize] {}; +``` + +In the last part of the `setup()` function we can now use the block device to read and write data. First the buffer is used to read what was stored within the previous execution, then the memory gets erased and reprogrammed with the new content. At the end of the reading and writing process the block device needs to be deinitalized again using `blockDevice.deinit()`. + +```cpp +// Read back what was stored at previous execution +Serial.println("Reading previous message..."); +blockDevice.read(buffer, 0, dataSize); +Serial.println(buffer); + +// Erase a block starting at the offset 0 relative +// to the block device start address +blockDevice.erase(0, requiredEraseBlocks * eraseBlockSize); + +// Write an updated message to the first block +Serial.println("Writing new message..."); +Serial.println(newMessage); +blockDevice.program(newMessage.c_str(), 0, dataSize); + +// Deinitialize the device +blockDevice.deinit(); +Serial.println("Done."); +``` + +Finally the `loop()` function of this sketch will be left empty, considering that the flash reading and writing process should be carried out as little as possible. + +### 4. Upload the Sketch +Below is the complete sketch of this tutorial consisting of the main sketch and the `FlashIAPLimits.h` helper file, upload both of them to your Portenta H7 to try it out. + +**FlashIAPLimits.h** +```cpp +/** +Helper functions for calculating FlashIAP block device limits +**/ + +// Ensures that this file is only included once +#pragma once + +#include +#include +#include + +using namespace mbed; + +// A helper struct for FlashIAP limits +struct FlashIAPLimits { + size_t flash_size; + uint32_t start_address; + uint32_t available_size; +}; + +// Get the actual start address and available size for the FlashIAP Block Device +// considering the space already occupied by the sketch (firmware). +FlashIAPLimits getFlashIAPLimits() +{ + // Alignment lambdas + auto align_down = [](uint64_t val, uint64_t size) { + return (((val) / size)) * size; + }; + auto align_up = [](uint32_t val, uint32_t size) { + return (((val - 1) / size) + 1) * size; + }; + + size_t flash_size; + uint32_t flash_start_address; + uint32_t start_address; + FlashIAP flash; + + auto result = flash.init(); + if (result != 0) + return { }; + + // Find the start of first sector after text area + int sector_size = flash.get_sector_size(FLASHIAP_APP_ROM_END_ADDR); + start_address = align_up(FLASHIAP_APP_ROM_END_ADDR, sector_size); + flash_start_address = flash.get_flash_start(); + flash_size = flash.get_flash_size(); + + result = flash.deinit(); + + int available_size = flash_start_address + flash_size - start_address; + if (available_size % (sector_size * 2)) { + available_size = align_down(available_size, sector_size * 2); + } + + return { flash_size, start_address, available_size }; +} +``` + +**FlashStorage.ino** +```cpp +#include +#include "FlashIAPLimits.h" + +using namespace mbed; + +void setup() { + Serial.begin(115200); + while (!Serial); + + Serial.println("FlashIAPBlockDevice Test"); + Serial.println("------------------------"); + + // Feed the random number generator for later content generation + randomSeed(analogRead(0)); + + // Get limits of the the internal flash of the microcontroller + auto [flashSize, startAddress, iapSize] = getFlashIAPLimits(); + + Serial.print("Flash Size: "); + Serial.print(flashSize / 1024.0 / 1024.0); + Serial.println(" MB"); + Serial.print("FlashIAP Start Address: 0x"); + Serial.println(startAddress, HEX); + Serial.print("FlashIAP Size: "); + Serial.print(iapSize / 1024.0 / 1024.0); + Serial.println(" MB"); + + // Create a block device on the available space of the flash + FlashIAPBlockDevice blockDevice(startAddress, iapSize); + + // Initialize the Flash IAP block device and print the memory layout + blockDevice.init(); + + const auto eraseBlockSize = blockDevice.get_erase_size(); + const auto programBlockSize = blockDevice.get_program_size(); + + Serial.println("Block device size: " + String((unsigned int) blockDevice.size() / 1024.0 / 1024.0) + " MB"); + Serial.println("Readable block size: " + String((unsigned int) blockDevice.get_read_size()) + " bytes"); + Serial.println("Programmable block size: " + String((unsigned int) programBlockSize) + " bytes"); + Serial.println("Erasable block size: " + String((unsigned int) eraseBlockSize / 1024) + " KB"); + + String newMessage = "Random number: " + String(random(1024)); + + // Calculate the amount of bytes needed to store the message + // This has to be a multiple of the program block size + const auto messageSize = newMessage.length() + 1; // C String takes 1 byte for NULL termination + const unsigned int requiredEraseBlocks = ceil(messageSize / (float) eraseBlockSize); + const unsigned int requiredProgramBlocks = ceil(messageSize / (float) programBlockSize); + const auto dataSize = requiredProgramBlocks * programBlockSize; + char buffer[dataSize] {}; + + // Read back what was stored at previous execution + Serial.println("Reading previous message..."); + blockDevice.read(buffer, 0, dataSize); + Serial.println(buffer); + + // Erase a block starting at the offset 0 relative + // to the block device start address + blockDevice.erase(0, requiredEraseBlocks * eraseBlockSize); + + // Write an updated message to the first block + Serial.println("Writing new message..."); + Serial.println(newMessage); + blockDevice.program(newMessage.c_str(), 0, dataSize); + + // Deinitialize the device + blockDevice.deinit(); + Serial.println("Done."); +} + +void loop() {} +``` + +### 5. Results +After uploading the sketch open the serial monitor to start the flash reading and writing process. The first time you start the script the block device will be filled randomly. Now try to reset or disconnect the Portenta and reconnect it. You should see a message with the random number written to the flash storage in the previous execution. + +***Note that the value written to the flash storage will persist if the board is reset or disconnected. However the flash storage will be reprogrammed once a new sketch is uploaded to the Portenta and may overwrite the data stored in the flash.*** + +## Programming the QSPI Flash + +One issue with the internal flash is that it's limited in size and the erase blocks are pretty large. This leaves very little space for your sketch and you may quickly run into issues with more complex applications. Therefore we can use the external QSPI flash which has plenty of space to store data. +For that the block device needs to be initialized differently but the rest of the sketch remains the same. To initialize the device we use the [QSPIFBlockDevice](https://os.mbed.com/docs/mbed-os/v6.9/apis/qspifblockdevice.html) class which is a block device driver for NOR-based QSPI Flash devices. + +```cpp +#define BLOCK_DEVICE_SIZE 1024 * 8 // 8 KB +#define PARTITION_TYPE 0x0B // FAT 32 + +// Create a block device on the available space of the flash +QSPIFBlockDevice root(PD_11, PD_12, PF_7, PD_13, PF_10, PG_6, QSPIF_POLARITY_MODE_1, 40000000); +MBRBlockDevice blockDevice(&root, 1); + +// Initialize the Flash IAP block device and print the memory layout +if(blockDevice.init() != 0 || blockDevice.size() != BLOCK_DEVICE_SIZE) { + Serial.println("Partitioning block device..."); + blockDevice.deinit(); + // Allocate a FAT 32 partition + MBRBlockDevice::partition(&root, 1, PARTITION_TYPE, 0, BLOCK_DEVICE_SIZE); + blockDevice.init(); +} +``` + +While the QSPI block device memory can be used directly, it's better to use a partition table as the QSPI storage is also filled with other data such as the Wi-Fi firmware. For that we use the [MBRBlockDevice](https://os.mbed.com/docs/mbed-os/v6.8/apis/mbrblockdevice.html) class and allocate a 8 KB partition which can then be used to read and write data. +The full QSPI version of the sketch is as follows: + +```cpp +#include "QSPIFBlockDevice.h" +#include "MBRBlockDevice.h" + +using namespace mbed; + +#define BLOCK_DEVICE_SIZE 1024 * 8 // 8 KB +#define PARTITION_TYPE 0x0B // FAT 32 + +void setup() { + Serial.begin(115200); + while (!Serial); + + Serial.println("QSPI Block Device Test"); + Serial.println("------------------------"); + + // Feed the random number generator for later content generation + randomSeed(analogRead(0)); + + // Create a block device on the available space of the flash + QSPIFBlockDevice root(PD_11, PD_12, PF_7, PD_13, PF_10, PG_6, QSPIF_POLARITY_MODE_1, 40000000); + MBRBlockDevice blockDevice(&root, 1); + + // Initialize the Flash IAP block device and print the memory layout + if(blockDevice.init() != 0 || blockDevice.size() != BLOCK_DEVICE_SIZE) { + Serial.println("Partitioning block device..."); + blockDevice.deinit(); + // Allocate a FAT 32 partition + MBRBlockDevice::partition(&root, 1, PARTITION_TYPE, 0, BLOCK_DEVICE_SIZE); + blockDevice.init(); + } + + const auto eraseBlockSize = blockDevice.get_erase_size(); + const auto programBlockSize = blockDevice.get_program_size(); + + Serial.println("Block device size: " + String((unsigned int) blockDevice.size() / 1024) + " KB"); + Serial.println("Readable block size: " + String((unsigned int) blockDevice.get_read_size()) + " bytes"); + Serial.println("Programmable block size: " + String((unsigned int) programBlockSize) + " bytes"); + Serial.println("Erasable block size: " + String((unsigned int) eraseBlockSize / 1024) + " KB"); + + String newMessage = "Random number: " + String(random(1024)); + + // Calculate the amount of bytes needed to store the message + // This has to be a multiple of the program block size + const auto messageSize = newMessage.length() + 1; // C String takes 1 byte for NULL termination + const unsigned int requiredEraseBlocks = ceil(messageSize / (float) eraseBlockSize); + const unsigned int requiredBlocks = ceil(messageSize / (float) programBlockSize); + const auto dataSize = requiredBlocks * programBlockSize; + char buffer[dataSize] {}; + + // Read back what was stored at previous execution + Serial.println("Reading previous message..."); + blockDevice.read(buffer, 0, dataSize); + Serial.println(buffer); + + // Erase a block starting at the offset 0 relative + // to the block device start address + blockDevice.erase(0, requiredEraseBlocks * eraseBlockSize); + + // Write an updated message to the first block + Serial.println("Writing new message..."); + Serial.println(newMessage); + blockDevice.program(newMessage.c_str(), 0, dataSize); + + // Deinitialize the device + blockDevice.deinit(); + Serial.println("Done."); +} + +void loop() {} +``` + +## Conclusion +We have learned how to use the available space in the flash memory of the microcontroller to read and save custom data. It's not recommended to use the flash of the microcontroller as the primary storage for data-intensive applications. It is better suited for read/write operations that are performed only once in a while such as storing and retrieving application configurations or persistent parameters. + +## Next Steps +Now that you know how to use block device to perform reading and writing flash memory you can look into the [next tutorial](https://www.arduino.cc/pro/tutorials/portenta-h7/por-ard-kvs) on how to use the [TDBStore API](https://os.mbed.com/docs/mbed-os/v6.9/apis/kvstore.html) to create a [key value store](https://en.wikipedia.org/wiki/Key%E2%80%93value_database) in the flash memory. + + +**Authors:** Giampaolo Mancini, Sebastian Romero +**Reviewed by:** Lenard George, Jose Garcia [2021-01-28] +**Last revision:** Sebastian Romero [2021-03-25] \ No newline at end of file diff --git a/content/tutorials/portenta-h7/por-ard-gs/assets/por_ard_gs_bm_url.png b/content/tutorials/portenta-h7/por-ard-gs/assets/por_ard_gs_bm_url.png deleted file mode 100644 index 8d0c1b1e..00000000 Binary files a/content/tutorials/portenta-h7/por-ard-gs/assets/por_ard_gs_bm_url.png and /dev/null differ diff --git a/content/tutorials/portenta-h7/por-ard-gs/content.md b/content/tutorials/portenta-h7/por-ard-gs/content.md index fbe8cc0a..1ead36b7 100644 --- a/content/tutorials/portenta-h7/por-ard-gs/content.md +++ b/content/tutorials/portenta-h7/por-ard-gs/content.md @@ -1,4 +1,13 @@ +--- +title: Setting Up Portenta H7 For Arduino +coverImage: assets/por_ard_gs_cover.svg +tags: [Getting Started, IDE, Setup, Blink] +description: This tutorial teaches you how to set up the board, how to configure your computer and how to run the classic Arduino blink example to verify if the configuration was successful. +--- + # Setting Up Portenta H7 For Arduino + +## Overview Congratulations on your purchase of one of our most powerful microcontroller boards to date! We know you are eager to try out your new board but before you can start using the Portenta H7 to run Arduino sketches you need to configure your computer and the Arduino IDE. This tutorial teaches you how to set up the board, how to configure your computer and how to run the classic Arduino blink example to verify if the configuration was successful. One of the benefits of the Portenta H7 is that it supports different types of software cores. A core is the software API for a particular set of processors. It is the API that provides functions such as digitalRead(), analogWrite(), millis() etc. which directly operate on the hardware. @@ -7,33 +16,34 @@ At the moment of writing the tutorial, there is an Arduino core and a MicroPytho This tutorial focuses on the Arduino core which allows you to benefit from the thousands of existing Arduino libraries and code examples written in C and C++ which are compatible with the Arduino core. A tutorial about setting the Portenta H7 up for development with the MicroPython core will be released soon. -## What You Will Learn +### You Will Learn - About the Arduino and Mbed operating system (Mbed OS) stack - Installing the Mbed library - Controlling the built in LED on the Portenta board -## Required Hardware and Software +### Required Hardware and Software - Portenta H7 board () - USB C cable (either USB A to USB C or USB C to USB C) - Arduino IDE 1.8.10+ or Arduino Pro IDE 0.0.4+ -# Portenta and The Arduino Core +## Portenta and The Arduino Core The Portenta H7 is equipped with two Arm Cortex ST processors (Cortex-M4 and Cortex-M7) which run the Mbed OS. Mbed OS is an embedded real time operating system (RTOS) designed specifically for microcontrollers to run IoT applications on low power. A real-time operating system is an operating system designed to run real-time applications that process data as it comes in, typically without buffer delays. [Here](https://www.ni.com/en-us/innovations/white-papers/07/what-is-a-real-time-operating-system--rtos--.html) you can read more about real time operating systems. The Arduino core for the Portenta H7 sits on top of the Mbed OS and allows to develop applications using Mbed OS APIs which handle for example storage, connectivity, security and other hardware interfacing. [Here](https://os.mbed.com/docs/mbed-os/v5.15/apis/index.html) you can read more about the Mbed OS APIs. However, taking advantage of the Arm® Mbed™ real time operating system's powerful features can be a complicated process. Therefore we simplified that process by allowing you to run Arduino sketches on top of it. -![The Arduino core is built on top of the Mbed stack](assets/por_gs_mbed_stack.svg?sanitize=true) +![The Arduino core is built on top of the Mbed stack](assets/por_gs_mbed_stack.svg) +## Instructions -# Configuring the Development Environment +### Configuring the Development Environment In this section, we will guide you through a step-by-step process of setting up your Portenta board for running an Arduino Sketch that blinks the built-in RGB LED. -## 1. The Basic Setup +### 1. The Basic Setup Let's begin by Plug-in your Portenta to your computer using the appropriate USB C cable. Next, open your IDE and make sure that you have the right version of the Arduino IDE or the PRO IDE downloaded on to your computer. -![The Portenta H7 can be connected to the computer using an appropriate USB-C cable](assets/por_ard_gs_basic_setup.svg?sanitize=true) +![The Portenta H7 can be connected to the computer using an appropriate USB-C cable](assets/por_ard_gs_basic_setup.svg) -## 2. Adding the Portenta to the list of available boards +### 2. Adding the Portenta to the List of Available Boards This step is the same for both the classic IDE and the Pro IDE. Open the board manager and search for "portenta". Find the Arduino mbed-enabled Boards library and click on "Install" to install the latest version of the mbed core (1.2.3 at the time of writing this tutorial). **Note:** If you have previously installed the Nano 33 BLE core it will be updated by following this step. @@ -42,15 +52,16 @@ This step is the same for both the classic IDE and the Pro IDE. Open the board m ![Also in the Pro IDE, a search for "portenta" reveals the core that needs to be installed to support Portenta H7.](assets/por_ard_gs_bm_core_pro_ide.png) -## 3. Verify the USB connection (Windows only) +### 3. Verify the USB Connection (Windows Only) In this step you will check if Windows is able to detect the Portenta H7. To do so open the Windows Device manager and if everything is set up correctly you will be able to see your device listed under USB devices. Otherwise, try unplugging it and plugging it back in. ![If the Portenta H7 is detected correctly, it will be listed in the device manager under USB devices.](assets/por_ard_gs_usb_driver_win.png) -## 4. Uploading the classic blink sketch -Let's program the Portenta with the classic blink example to check if the connection to the board works: +### 4. Uploading the Classic Blink Sketch +Let's program the Portenta with the classic blink example to check if the connection to the board works. There are two ways to do that: -- In the classic Arduino IDE open the blink example by clicking the menu entry File->Examples->01.Basics->Blink. +- In the classic Arduino IDE open the blink example by clicking the menu entry File->Examples->01.Basics->Blink. You need to swap LOW and HIGH pin values as the built-in LED on Portenta is turned on by pulling it LOW. +- By downloading the 'Arduino_Pro_Tutorials' library and opening the pre-made sketch under File->Examples->Arduino_Pro_Tutorials->Setting Up Portenta H7 For Arduino->Blink - In the Arduino Pro IDE Copy and paste the following code into a new sketch in your IDE. ```cpp @@ -76,7 +87,7 @@ For Portenta H7 LED_BUILTIN represents the built-in RGB LED on the board in gr **Note:** The individual colours of the built-in RGB LED can be accessed and controlled separately. In the tutorial "Dual Core Processing" you will learn how to control the LED to light it in different colors -## 5. Upload the blink sketch +### 5. Upload the Blink Sketch Now it's time to upload the sketch and see if the LED will start to blink. Make sure you select Arduino Portenta H7 (M7 core) as the board and the port to which the Portenta H7 is connected. If the Portenta H7 doesn't show up in the list of ports, go back to step 5 and make sure that the drivers are installed correctly. Once selected click Upload. Once uploaded the built-in LED should start blinking with an interval of 1 second. **Note:** The Portenta H7 has an M7 and an M4 processor which run separate cores. That's why you need to select the one to which you want to upload your sketch to (check out the tutorial "Dual Core Processing" to learn more about Portenta's processors). @@ -87,28 +98,28 @@ Now it's time to upload the sketch and see if the LED will start to blink. Make **Optional:** We collect all the sketches from the tutorials in a library which you can install from the Library Manager: **Tools -> Manage Libraries**. Search for 'Arduino_Pro_Tutorials' or download them from the [repository](https://github.com/arduino-libraries/Arduino_Pro_Tutorials/releases). -# Conclusion +## Conclusion You have now configured your Portenta board to run Arduino sketches. Along with that you gained an understanding of how the Arduino Core runs on top of Mbed OS. -# Next Steps +### Next Steps - Proceed with the next tutorial "Dual Core Processing" to learn how to make use of Portenta H7's two processors to do two separate tasks simultaneously. - Read more about why we chose Mbed as as the foundation [here](https://blog.arduino.cc/2019/07/31/why-we-chose-to-build-the-arduino-nano-33-ble-core-on-mbed-os/) -# Troubleshooting -## Sketch Upload Troubleshooting +## Troubleshooting +### Sketch Upload Troubleshooting If trying to upload a sketch but you receive an error message, saying that the upload has failed you can try to upload the sketch while the Portenta H7 is in bootloader mode. To do so you need to double click the reset button. The green LED will start fading in and out. Try to upload the sketch again. The green LED will stop fading when the upload completes. ![Double-clicking the reset button puts the board into bootloader mode.](assets/por_ard_gs_reset.png) -## USB Hub Issues Troubleshooting +### USB Hub Issues Troubleshooting If you would like to use a USB hub to connect other devices to the Portenta make sure it's an active USB hub that can provide power to these devices. Passive hubs won't work. If you're using an active USB hub but still don't get it to work it may be a model that is not supported. -## Ubuntu Issues Troubleshooting +### Ubuntu Issues Troubleshooting If you're having troubles getting your Portenta to work on Ubuntu you can try the following: - Make sure **modemmanager** is not installed. Otherwise remove it with `sudo apt-get remove modemmanager` - Add the following rules to you udev rules -``` +```cpp SUBSYSTEM=="usb", ATTRS{idVendor}=="2341", MODE="0666" SUBSYSTEM=="usb", ATTRS{idVendor}=="1fc9", MODE="0666" SUBSYSTEM=="usb", ATTRS{idVendor}=="0525", MODE="0666" @@ -120,5 +131,5 @@ SUBSYSTEM=="usb", ATTRS{idVendor}=="0525", MODE="0666" **Authors:** Lenard George, Sebastian Hunkeler -**Reviewed by:** Jose Garcia [18.03.2020] -**Last revision:** Sebastian Romero [15.09.2020] +**Reviewed by:** Jose Garcia [2020-03-18] +**Last revision:** Sebastian Romero [2020-09-15] diff --git a/content/tutorials/portenta-h7/por-ard-gs/metadata.json b/content/tutorials/portenta-h7/por-ard-gs/metadata.json deleted file mode 100644 index a369aed9..00000000 --- a/content/tutorials/portenta-h7/por-ard-gs/metadata.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "id":"por-ard-gs", - "index": 1, - "title":"Setting Up Portenta H7 For Arduino", - "slug":"por-ard-gs", - "authors":[ - "Lenard George", - "Sebastian Hunkeler" - ], - "coverImage":{ - "src":"assets/por_ard_gs_cover.svg?sanitize=true" - }, - "tags":[ - "Getting Started", - "IDE", - "Setup", - "Blink" - ], - "abstract":"This tutorial teaches you how to set up the board, how to configure your computer and how to run the classic Arduino blink example to verify if the configuration was successful.", - "previousArticle":null, - "nextArticle":"por-ard-dcp" -} diff --git a/content/tutorials/portenta-h7/por-ard-kvs/content.md b/content/tutorials/portenta-h7/por-ard-kvs/content.md index 3122a435..cbc47feb 100644 --- a/content/tutorials/portenta-h7/por-ard-kvs/content.md +++ b/content/tutorials/portenta-h7/por-ard-kvs/content.md @@ -1,27 +1,38 @@ +--- +title: Creating a Flash-Optimised Key-Value Store +coverImage: assets/por_ard_block_device_cover.svg +tags: [Storage, Key-Value store, Flash] +description: This tutorial explains how to create a flash-optimised key-value store using the flash memory of the Portenta H7. +--- + # Creating a Flash-Optimised Key-Value Store -This tutorial explains how to create a flash-optimised key-value store using the flash memory of the Portenta H7. It builds on top of the *Flash In-Application Programming* tutorial. -## What You Will Learn -In this tutorial you will learn how to use the Mbed OS [TDBStore API](https://os.mbed.com/docs/mbed-os/v6.4/apis/kvstore.html) to create a [Key value store](https://en.wikipedia.org/wiki/Key%E2%80%93value_database) in the free space of the microcontroller's internal flash. +## Overview +This tutorial explains how to create a flash-optimised key-value store using the flash memory of the Portenta H7. It builds on top of the **Flash In-Application Programming** tutorial. + +### You Will Learn +In this tutorial you will learn how to use the Mbed OS [TDBStore API](https://os.mbed.com/docs/mbed-os/v6.9/apis/kvstore.html) to create a [Key value store](https://en.wikipedia.org/wiki/Key%E2%80%93value_database) in the free space of the microcontroller's internal flash. -## Required Hardware and Software +### Required Hardware and Software - Portenta H7 board () - USB C cable (either USB A to USB C or USB C to USB C) - Arduino IDE 1.8.10+ or Arduino Pro IDE 0.0.4+ or Arduino CLI 0.13.0+ -# MbedOS APIs for Flash Storage +## Instructions + +### MbedOS APIs for Flash Storage The core software of Portenta H7 is based on the Mbed OS operating system, allowing developers to integrate the Arduino API with the APIs exposed by Mbed OS. Mbed OS has a rich API for managing storage on different mediums, ranging from the small internal flash memory of a microcontroller to external SecureDigital cards with large data storage space. -In this tutorial, we are going to save a value persistently inside the flash memory. That allows to access that value even after a reset of the microcontroller. We will retrieve some information from a flash block by using the [FlashIAPBlockDevice](https://os.mbed.com/docs/mbed-os/v6.4/apis/flashiapblockdevice.html) and the [TDBStore](https://os.mbed.com/docs/mbed-os/v6.4/apis/kvstore.html) APIs. We will use the `FlashIAPBlockDevice` class to create a block device on the free space of the flash and we will create a Key-Value Store in it using the `TDBStore` API. +In this tutorial, we are going to save a value persistently inside the flash memory. That allows to access that value even after a reset of the microcontroller. We will retrieve some information from a flash block by using the [FlashIAPBlockDevice](https://os.mbed.com/docs/mbed-os/v6.9/apis/flashiapblockdevice.html) and the [TDBStore](https://os.mbed.com/docs/mbed-os/v6.9/apis/kvstore.html) APIs. We will use the `FlashIAPBlockDevice` class to create a block device on the free space of the flash and we will create a Key-Value Store in it using the `TDBStore` API. -***Important: The TBStore API optimises for access speed, reduce [wearing of the flash](https://en.wikipedia.org/wiki/Flash_memory#Memory_wear) and minimise storage overhead. TBStore is also resilient to power failures. If you want to use the flash memory of the microcontroller, _always prefer the TDBStore approach over a direct access to the FlashIAP block device_.*** +***Important: The TBStore API optimises for access speed, reduce [wearing of the flash](https://en.wikipedia.org/wiki/Flash_memory#Memory_wear) and minimise storage overhead. TBStore is also resilient to power failures. If you want to use the flash memory of the microcontroller, always prefer the TDBStore approach over a direct access to the FlashIAP block device.*** -## 1. The Basic Setup -Begin by plugging in your Portenta board to the computer using a USB-C cable and open the Arduino IDE or the Arduino Pro IDE. If this is your first time running Arduino sketch files on the board, we suggest you check out how to [set up the Portenta H7 for Arduino](https://github.com/bcmi-labs/arduino-pro-content/blob/master/content/tutorials/portenta-h7/por-ard-usb/por-ard-gs) before you proceed. +### 1. The Basic Setup +Begin by plugging in your Portenta board to the computer using a USB-C cable and open the Arduino IDE or the Arduino Pro IDE. If this is your first time running Arduino sketch files on the board, we suggest you check out how to [set up the Portenta H7 for Arduino](https://www.arduino.cc/pro/tutorials/portenta-h7/por-ard-gs) before you proceed. -## 2. Create the Structure of the Program +### 2. Create the Structure of the Program Let's program the Portenta with a sketch. We will also define a few helper functions in a supporting header file. * Create a new sketch named `FlashKeyValue.ino` * Create a new file named `FlashIAPLimits.h` to store the helper functions in a reusable file. @@ -29,7 +40,7 @@ Let's program the Portenta with a sketch. We will also define a few helper funct **Note:** Finished sketch its inside the tutorials library wrapper at: **Examples -> Arduino_Pro_Tutorials -> Creating a Flash-Optimised Key-Value Store -> FlashKeyValueStore** -## 3. Populate the Helper Functions +### 3. Populate the Helper Functions First let's add the helper functions to the `FlashIAPLimits.h` header. This will determine the available Flash limits to allocate the custom data. ```cpp @@ -91,7 +102,7 @@ FlashIAPLimits getFlashIAPLimits() } ``` -## 4. Make the Key Store Program +### 4. Make the Key Store Program Go to `FlashKeyValue.ino` and include the libraries that we need from **MBED** and our header helper (`FlashIAPLimits.h`) . The `getFlashIAPLimits()` function which is defined in the `FlashIAPLimits.h` header takes care of not overwriting data already stored on the flash and aligns the start and stop addresses with the size of the flash sector. We use those calculated limits to create a block device and a `TDBStore` on top of them. ```cpp @@ -247,7 +258,7 @@ int setSketchStats(const char* key, SketchStats stats) } ``` -## 5. Results +### 5. Results Upload the sketch and the output should be similar to the following: ```text @@ -273,17 +284,17 @@ Current Stats Push the reset button to restart the sketch. The values of the stats have been updated. `Previous Stats` which is retrieved from the key-value store now contains values from the previous execution. -# Conclusion and Caveats +## Conclusion We have learned how to use the available space in the flash memory of the microcontroller to create a key-value store and use it to retrieve and store data. It's not recommended to use the flash of the microcontroller as the primary storage for data-intensive applications. It is best suited for read/write operations that are performed only once in a while such as storing and retrieving application configurations or persistent parameters. -# Next Steps -- Learn how to retrieve a collection of keys using TDBStore iterators via [`iterator_open`](https://os.mbed.com/docs/mbed-os/v6.4/mbed-os-api-doxy/classmbed_1_1_k_v_store.html#a77661adec54b9909816e7492a2c61a91) and [`iterator_next`](https://os.mbed.com/docs/mbed-os/v6.4/mbed-os-api-doxy/classmbed_1_1_k_v_store.html#a5116b40a3480462b88dc3f1bb8583ad4) -- Learn how to create an incremental TDBStore set sequence via [`set_start`](https://os.mbed.com/docs/mbed-os/v6.4/mbed-os-api-doxy/classmbed_1_1_k_v_store.html#a6e882a0d4e0cbadf6269142ac3c4e693), [`set_add_data`](https://os.mbed.com/docs/mbed-os/v6.4/mbed-os-api-doxy/classmbed_1_1_k_v_store.html#adbe636bf8c05834fe68b281fc638c348) and [`set_finalize`](https://os.mbed.com/docs/mbed-os/v6.4/mbed-os-api-doxy/classmbed_1_1_k_v_store.html#a346da66252added46d3b93902066b548) +### Next Steps +- Learn how to retrieve a collection of keys using TDBStore iterators via [`iterator_open`](https://os.mbed.com/docs/mbed-os/v6.9/mbed-os-api-doxy/classmbed_1_1_k_v_store.html#a77661adec54b9909816e7492a2c61a91) and [`iterator_next`](https://os.mbed.com/docs/mbed-os/v6.9/mbed-os-api-doxy/classmbed_1_1_k_v_store.html#a5116b40a3480462b88dc3f1bb8583ad4) +- Learn how to create an incremental TDBStore set sequence via [`set_start`](https://os.mbed.com/docs/mbed-os/v6.9/mbed-os-api-doxy/classmbed_1_1_k_v_store.html#a6e882a0d4e0cbadf6269142ac3c4e693), [`set_add_data`](https://os.mbed.com/docs/mbed-os/v6.9/mbed-os-api-doxy/classmbed_1_1_k_v_store.html#adbe636bf8c05834fe68b281fc638c348) and [`set_finalize`](https://os.mbed.com/docs/mbed-os/v6.9/mbed-os-api-doxy/classmbed_1_1_k_v_store.html#a346da66252added46d3b93902066b548) - Learn how to use the 16MB QSPI Flash on the Portenta H7 **Authors:** Giampaolo Mancini -**Reviewed by:** Pablo Marquínez [2.12.2020] -**Last revision:** Sebastian Romero [11.1.2021] +**Reviewed by:** Pablo Marquínez [2020-12-02] +**Last revision:** Sebastian Romero [2021-01-11] \ No newline at end of file diff --git a/content/tutorials/portenta-h7/por-ard-kvs/metadata.json b/content/tutorials/portenta-h7/por-ard-kvs/metadata.json deleted file mode 100644 index c5e718fc..00000000 --- a/content/tutorials/portenta-h7/por-ard-kvs/metadata.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id":"por-ard-kvs", - "index": 8, - "title":"Creating a Flash-Optimised Key-Value Store", - "slug":"por-ard-kvs", - "authors":[ - "Giampaolo Mancini" - ], - "coverImage":{ - "src":"assets/por_ard_block_device_cover.svg?sanitize=true" - }, - "tags":[ - "Storage", - "Key-Value store", - "Flash" - ], - "abstract":"This tutorial explains how to create a flash-optimised key-value store using the flash memory of the Portenta H7.", - "previousArticle":"por-ard-trace32", - "nextArticle":"por-ard-lvgl" -} diff --git a/content/tutorials/portenta-h7/por-ard-lvgl/content.md b/content/tutorials/portenta-h7/por-ard-lvgl/content.md index 9c49108c..9eedcf13 100644 --- a/content/tutorials/portenta-h7/por-ard-lvgl/content.md +++ b/content/tutorials/portenta-h7/por-ard-lvgl/content.md @@ -1,42 +1,53 @@ +--- +title: Creating GUIs with LVGL +coverImage: assets/por_ard_lvgl_cover.svg +tags: [USB Host, LVGL, GUI, HDMI] +description: In this tutorial you will learn to use LVGL (Light and Versatile Graphics Library) to create a simple graphical user interface that consists of a label that updates itself. +--- + # Creating GUIs With LVGL + +## Overview In this tutorial you will learn to use [LVGL](https://lvgl.io/) to create a simple graphical user interface that consists of a text label that updates itself. -## What You Will Learn +### You Will Learn - Understanding the structure to build LVGL interfaces. - Building a simple UI with a text label. - Configuring the setup to display the User-Interface. -## Required Hardware and Software +### Required Hardware and Software - Portenta H7 board () - USB-C cable (either USB-A to USB-C or USB-C to USB-C) - Arduino IDE 1.8.10+ or Arduino Pro IDE 0.0.4+ -- USB-C hub with HDMI ([The one we used](https://www.dustinhome.se/product/5011166993/travel-port-usb-c-total)) +- USB-C hub with HDMI - External monitor - HDMI cable -# The Light and Versatile Graphics Library +## The Light and Versatile Graphics Library Graphical User interfaces are necessary for visualising information and interacting with certain aspects of an application. The Light and Versatile Graphics Library, also known as [LVGL](https://lvgl.io/), is an open-sourced library used to create graphical user-interfaces for microcontrollers and high-end processors. The light weight embedded library provides all the necessary widgets and user interface elements that will allow you to easily create user interfaces for displays and touch screens. -# Building a Simple GUI +## Instructions + +### Building a Simple GUI This tutorial will guide you through building a basic user interface using the LVGL Library which you can download using the Arduino Library Manager. The setup for this tutorial requires you to first to upload the finished sketch file to the Portenta board. You will then connect the board to a USB-hub in order to connect it to an external monitor. Once the hub is powered externally, a graphical user interface with a label will be displayed on the screen. ![Overview of how to connect the Portenta to the peripherals.](assets/por_ard_lvgl_tutorial_steps.svg) -## 1. The Basic Setup +### 1. The Basic Setup -Begin by plugging your Portenta board into the computer using a USB-C cable and open the Arduino IDE or the Arduino Pro IDE. If this is your first time running Arduino sketch files on the board, we suggest you check out how to [set up the Portenta H7 for Arduino](https://github.com/bcmi-labs/arduino-pro-content/blob/master/content/tutorials/portenta-h7/por-ard-usb/por-ard-gs) before you proceed. +Begin by plugging your Portenta board into the computer using a USB-C cable and open the Arduino IDE or the Arduino Pro IDE. If this is your first time running Arduino sketch files on the board, we suggest you check out how to [set up the Portenta H7 for Arduino](https://www.arduino.cc/pro/tutorials/portenta-h7/por-ard-gs) before you proceed. ![The Portenta H7 can be connected to the computer using an appropriate USB-C cable](assets/por_ard_lvgl_basic_setup.svg) -## 2. Download the LVGL Library +### 2. Download the LVGL Library -Next, select *Portenta* in the **Tools -> Board** menu before installing [lvgl](https://github.com/lvgl/lvgl) from the Library Manager. Then go to **Sketch** **->** **Include Libraries** **-> Manage Libraries** and search for *LVGL*. Download **lvgl ** by [kisvegabor](https://github.com/kisvegabor). +Next, select 'Portenta' in the **Tools -> Board** menu before installing [lvgl](https://github.com/lvgl/lvgl) from the Library Manager. Then go to **Sketch** **->** **Include Libraries** **-> Manage Libraries** and search for LVGL. Download **lvgl ** by [kisvegabor](https://github.com/kisvegabor). ![Library Manager showing the installed lvgl library.](assets/por_ard_lvgl_select_library.png) -## 3. Adding a Label Widget +### 3. Adding a Label Widget Let's start by including the library that we are going to use. @@ -72,7 +83,7 @@ void setup() { This sketch creates a label that will be displayed in the center of the connected monitor. -## 4. Connect an External Monitor +### 4. Connect an External Monitor Compile and upload the sketch to your Portenta H7. At this point your board becomes the host. Unplug the board from your computer and connect it to the USB-hub along with a monitor that is connected to the HDMI port. Power up your hub by connecting it to an external power source and the monitor will display a label saying `Counter`. @@ -82,7 +93,7 @@ Compile and upload the sketch to your Portenta H7. At this point your board beco Our label object currently has LVGL's default style. If you want to customise the style you can have a look at LVGL's [documentation](https://docs.lvgl.io/latest/en/html/widgets/label.html). -## 5. Creating a Simple Counter +### 5. Creating a Simple Counter Let's create a counter that increases each second and update the label on the screen. To do so, we will create a label and an updating task that is going to updated the label periodically and change its value. This is possible using an LVGL concept called 'Task'. @@ -125,7 +136,7 @@ void loop() { -## 6. Upload the Sketch +### 6. Upload the Sketch Below is the complete sketch of the tutorial that updates the label's text with an incrementing counter value. Upload the sketch to your Portenta H7 and connect it to an external monitor as described in Step 4. @@ -172,24 +183,24 @@ void loop() { ``` -# Conclusion +## Conclusion This tutorial shows how to build a simple user interface with your Portenta. Starting with a static view in the monitor and then converting it into a simple dynamic application that features an updating variable on the screen. The tutorial also shows how to use the "task" feature of LVGL to run instructions recurrently. -# Next Steps +### Next Steps Now that you know how to build a simple UI for a screen, you can try to add more labels to the screen to show various information or try out different LVGL widgets. -# Troubleshooting -## Counter Label Doesn't Update -* Make sure that the label and task are declared on top of the sketch, outside the `setup()` and `loop()` like a normal variable. -* Check if the task has the same structure in the first declaration and the function creation. -* Look inside the `loop()` and see if `lv_task_handler()` is there. - -## Sketch Upload Troubleshooting -* If you have troubles uploading the sketch, try to first set the board in bootloader mode, by clicking the reset button twice, then you should see the built-in LED pulsating. -* If you uploaded the sketch and you don't have any output in the display, make sure you have `portenta_init_video()` in the `setup()`. -* Unplug and plug the HDMI cable in again. -* Reset the Portenta once it's connected to the USB-hub. +## Troubleshooting +### Counter Label Doesn't Update +- Make sure that the label and task are declared on top of the sketch, outside the `setup()` and `loop()` like a normal variable. +- Check if the task has the same structure in the first declaration and the function creation. +- Look inside the `loop()` and see if `lv_task_handler()` is there. + +### Sketch Upload Troubleshooting +- If you have troubles uploading the sketch, try to first set the board in bootloader mode, by clicking the reset button twice, then you should see the built-in LED pulsating. +- If you uploaded the sketch and you don't have any output in the display, make sure you have `portenta_init_video()` in the `setup()`. +- Unplug and plug the HDMI cable in again. +- Reset the Portenta once it's connected to the USB-hub. **Authors:** Pablo Marquínez, Lenard George, Sebastian Romero **Reviewed by:** Jose Garcia, Manuel Zomer [2021-01-27] diff --git a/content/tutorials/portenta-h7/por-ard-lvgl/metadata.json b/content/tutorials/portenta-h7/por-ard-lvgl/metadata.json deleted file mode 100644 index cb056d05..00000000 --- a/content/tutorials/portenta-h7/por-ard-lvgl/metadata.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "id":"por-ard-lvgl", - "index": 9, - "title":"Creating GUIs with LVGL", - "slug":"por-ard-lvgl", - "authors":[ - "Pablo Marquínez", - "Lenard George", - "Sebastian Romero" - ], - "coverImage":{ - "src":"assets/por_ard_lvgl_cover.svg?sanitize=true" - }, - "tags":[ - "USB Host", - "LVGL", - "GUI", - "HDMI" - ], - "abstract":"In this tutorial you will learn to use LVGL (Light and Versatile Graphics Library) to create a simple graphical user interface that consists of a label that updates itself.", - "previousArticle":"por-ard-kvs", - "nextArticle":null -} diff --git a/content/tutorials/portenta-h7/por-ard-trace32/content.md b/content/tutorials/portenta-h7/por-ard-trace32/content.md index 0e6a3ccf..d47b0f8d 100644 --- a/content/tutorials/portenta-h7/por-ard-trace32/content.md +++ b/content/tutorials/portenta-h7/por-ard-trace32/content.md @@ -1,30 +1,41 @@ +--- +title: Lauterbach TRACE32 GDB Front-End Debugger for Portenta H7 +coverImage: assets/por_ard_trace32_cover.svg +tags: [Debugging, Lauterbach, TRACE32] +description: This tutorial will show you how to use the Lauterbach TRACE32 GDB front-end debugger to debug your Portenta H7 application via GDB on a serial interface. +--- + # Lauterbach TRACE32 GDB Front-End Debugger for Portenta H7 + +## Overview This tutorial will show you how to use the Lauterbach TRACE32 GDB front-end debugger to debug your Portenta H7 application via GDB on a serial interface. It also explains how to obtain a free licence of a fully functional version of TRACE32 using your Portenta's serial number. -## What You Will Learn +### You Will Learn - How to get a free license key for TRACE32 GDB Front End debugger for Portenta H7 - M7 core - How to download and start the Lauterbach TRACE32 GDB Front End debugger - How to flash and debug some ready-to-run demos -## Required Hardware and Software +### Required Hardware and Software - Portenta H7 board (https://store.arduino.cc/portenta-h7) - USB C cable (either USB A to USB C or USB C to USB C) - Arduino IDE 1.8.13+ or Arduino Pro IDE 0.1.0+ - Lauterbach TRACE32 (https://www.lauterbach.com/download_demo.html) -# TRACE32 GDB Front End Debugger +## Instructions + +### TRACE32 GDB Front End Debugger In this tutorial you will load an application on the Portenta H7 board which includes the Monitor for Remote Inspection (MRI). This is a GDB compatible serial monitor which is included in the ThreadDebug sketch in the Arduino IDE Examples for Portenta H7 (M7 core) and in all examples in the TRACE32 demo directory of the TRACE32 installation. Throughout this document the **double-tilde (~~)** is used as a place holder for the directory where you unzipped the TRACE32 software. ***This tutorial assumes that you have already installed the Arduino IDE or Arduino Pro IDE and configured it to support the Portenta H7 board. Please refer to [Setting Up Portenta H7 For Arduino](https://www.arduino.cc/pro/tutorials/portenta-h7/por-ard-gs) before you proceed.*** -## 1. Downloading the TRACE32 Debugger +### 1. Downloading the TRACE32 Debugger In order download the TRACE32 debugger open the [Lauterbach download page](https://www.lauterbach.com/download_demo.html) in your browser. Download the zip file named **Debugger for GDB target (Arduino Pro)** which contains TRACE32 for Portenta-H7 and demo applications. Extract the zip file to a directory of your choice. On Windows systems, please avoid C:\T32, because this is the default installation directory for the full TRACE32 distribution. -## 2. Registration and License Key +### 2. Registration and License Key Without a valid license, the TRACE32 debugger only works for few minutes in demo mode. Lauterbach can generate a **free license** based on the serial number of your Portenta H7 board. The license will be valid for one year and can easily be renewed for free after this period using the same procedure. In order to obtain a license, please register here: @@ -57,7 +68,7 @@ When you receive the email containing your license key, follow the instructions - Restart TRACE32 after adding the license key. -## 3. Starting the TRACE32 Debugger +### 3. Starting the TRACE32 Debugger To use the debugger launch the appropriate executable for your host operating system. The executables can be found in the corresponding sub-directory for your operating system: @@ -83,7 +94,7 @@ On Linux systems, you will need to edit the system-settings.cmm file to manually ***The manual port setting is also useful for Windows systems where you have multiple Portenta H7 boards connected, and you want to select a specific board to be used by TRACE32 for debugging. The automatic port selection is disabled when a &GDBPORT definition is found in `system-settings.cmm`.*** -## 4. Running Your First Demo +### 4. Running Your First Demo A number of pre-built demo programs are available.They can be accessed from the "Portenta H7 Demos" menu. The following instructions relate to the T32ThreadDebug example. However, the other examples follow a similar pattern. @@ -119,7 +130,7 @@ In case of errors, please check the physical connection to the board, check if y Take a look at the readme.txt file inside the demo directory for further information about the demo. -## Compile and Debug Other Projects +### Compile and Debug Other Projects The provided demos or another project of your choice can be edited, compiled and flashed with the Arduino IDE. You can open for example the T32ThreadDebug.ino file with Arduino IDE, build and flash it. Flashing is also possible with TRACE32. @@ -133,7 +144,7 @@ When you're done with flashing your application to the board you can switch back You may also create a custom startup script for your own application. A minimal startup script is shown below. Copy it into a text file and save it with a file extension ".cmm". To execute it call the menu command "File-->Run Script..." from the TRACE32 GUI. -``` +```cpp SYStem.Down SYStem.CPU PortentaH7-CM7 SYStem.PORT ; e.g. COM8 (Windows) or /dev/ttyUSB0 (Linux) @@ -155,38 +166,45 @@ You can also copy the script start.cmm from the T32ThreadDebug demo directory to For each demo the corresponding start.cmm script comes with a predefined window layout. For your own layout, manually open and arrange the windows as you prefer, then save this window layout using the "Store Windows..." command in the Window menu. Save the file as win.cmm. It will be automatically found and used the next time you start a debugging session. -# Conclusion +## Conclusion In this tutorial you learned how to acquire a free version of the TRACE32 GDB Front End debugger, fully licensed for Portenta H7 for one year. You learned how to start the debugger and debug some ready-to-run demos. Furthermore you learned how to debug an application compiled with the classic Arduino IDE or the Arduino Pro IDE. -# Troubleshooting +### Next Steps +Lauterbach also provides hardware-based debug & trace tools. To learn more about them please visit: -## Portenta’s Serial Number Is Not 24 Digits Long +- [https://www.lauterbach.com](https://www.lauterbach.com) +- [https://www.lauterbach.com/microtrace_cortexm.html](https://www.lauterbach.com/microtrace_cortexm.html) + + +## Troubleshooting + +### Portenta’s Serial Number Is Not 24 Digits Long - Update Arduino IDE to the latest version available -- Update **Arduino mbed-enabled Boards** core from Arduino IDE menu: *Tools > Board > Boards Manager* +- Update **Arduino mbed-enabled Boards** core from Arduino IDE menu: **Tools > Board > Boards Manager** - Update the Portenta's bootloader using the instructions found [here](https://www.arduino.cc/pro/tutorials/portenta-h7/por-ard-bl). -## Error Message in AREA View: 'No more arguments expected' +### Error Message in AREA View: 'No More Arguments Expected' - This may be caused by unsupported characters in your Windows user name. Make sure your user name neither contains any spaces nor special characters. -## Debugger Connection Issues +### Debugger Connection Issues In case the debugger encounters any issues while connecting to the Portenta, try the following: - Reset the Portenta H7 board before running the startup script. - If the errors persist, please check the physical connection. Check if your host PC has detected the board's serial port and if this is the port configured in TRACE32 (check also the configuration in the startup file). Reset the board and retry. -## Flashing Issues +### Flashing Issues - Before flashing the Portenta H7 board from the Arduino IDE, please disconnect the TRACE32 debugger by typing the command: “SYStem.Down” on the command line interface. Alternatively open the menu: “CPU-> System Settings…” and press the radio button “Down” in the “Mode” section. -## Debugger Hanging Issues +### Debugger Hanging Issues - The TRACE32 GDB front-end debugger is a run-mode debugger: At a breakpoint only the user threads are stopped. The kernel and all other system threads continue to run. It may happen that the debugger hangs if a breakpoint is set in critical system areas. In this case reset the board, remove all breakpoints and attach again to the target (SYStem.Mode Attach command). -## Issues While Starting TRACE32 on Linux +### Issues While Starting TRACE32 on Linux - The TRACE32 executable for Linux requires the Qt libraries. Please verify that one of the following versions of Qt is installed: - Qt4 >= 4.6.2 (Linux 32 bit or 64 bit) @@ -194,7 +212,7 @@ In case the debugger encounters any issues while connecting to the Portenta, try On Ubuntu Linux for example you can install the Qt5 libraries using apt-get: `sudo apt-get install qt5-default` -## Issues With the GDB Serial Port on Host Linux +### Issues With the GDB Serial Port on Host Linux The user running the TRACE32 executable on Linux must have the permission to access serial devices. For example in Ubuntu a temporary permission can be set as follows: @@ -206,12 +224,8 @@ You can also set a permanent permission adding the user to the "dialout" group. Alternatively you can run the TRACE32 executable with root permissions. -# Next Steps -Lauterbach also provides hardware-based debug & trace tools. To learn more about them please visit: -- [https://www.lauterbach.com](https://www.lauterbach.com) -- [https://www.lauterbach.com/microtrace_cortexm.html](https://www.lauterbach.com/microtrace_cortexm.html) **Authors:** Marco Ferrario, Lauterbach Italy, Sebastian Romero -**Reviewed by:** Maurizio Menegotto, Richard Copeman -**Last revision:** Sebastian Romero [29.10.2020] +**Reviewed by:** Maurizio Menegotto, Richard Copeman [2020-11-06] +**Last revision:** Sebastian Romero [2020-12-04] diff --git a/content/tutorials/portenta-h7/por-ard-trace32/metadata.json b/content/tutorials/portenta-h7/por-ard-trace32/metadata.json deleted file mode 100644 index b88840ff..00000000 --- a/content/tutorials/portenta-h7/por-ard-trace32/metadata.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "id":"por-ard-trace32", - "index": 7, - "title":"Lauterbach TRACE32 GDB Front-End Debugger for Portenta H7", - "slug":"por-ard-trace32", - "authors":[ - "Marco Ferrario", - "Lauterbach Italy", - "Sebastian Romero" - ], - "coverImage":{ - "src":"assets/por_ard_trace32_cover.svg?sanitize=true" - }, - "tags":[ - "Debugging", - "Lauterbach", - "TRACE32" - ], - "abstract":"This tutorial will show you how to use the Lauterbach TRACE32 GDB front-end debugger to debug your Portenta H7 application via GDB on a serial interface.", - "previousArticle":"por-openmv-fd", - "nextArticle":"por-ard-kvs" -} diff --git a/content/tutorials/portenta-h7/por-ard-usb/content.md b/content/tutorials/portenta-h7/por-ard-usb/content.md index 7677e357..bde48cef 100644 --- a/content/tutorials/portenta-h7/por-ard-usb/content.md +++ b/content/tutorials/portenta-h7/por-ard-usb/content.md @@ -1,19 +1,28 @@ +--- +beta: true +title: Portenta H7 as a USB Host +coverImage: assets/por_ard_usbh_cover.svg +tags: [USB, HID, RGB LED] +description: This tutorial teaches you how to set up the Portenta H7 to act as a USB host in a way that allows to connect peripherals such as a keyboard or mouse to interact with it. +--- + # Portenta H7 as a USB Host +## Overview It is possible to configure the Portenta H7 to act as a USB host in a way that allows to connect peripherals such as a keyboard or mouse to interact with the board. This way you could connect a keyboard to your Portenta and type numbers or characters to trigger actions from your sketch. -## What You Will Learn +### You Will Learn - How to configure the Portenta H7 as a USB host -- How to use the *KeyboardController* library to establish a USB connection with the Portenta H7 +- How to use the **KeyboardController** library to establish a USB connection with the Portenta H7 - To write a small program that reads button presses from a keyboard - How to debug the data being sent from the peripheral to the Portenta H7 using an additional Arduino board. -## Required Hardware and Software +### Required Hardware and Software - Portenta H7 board () - USB-C cable (either USB-A to USB-C or USB-C to USB-C) -- USB-C hub (active) (you can find [here](https://www.dustin.se/product/5011166993/travel-port-usb-c-total) the one used for this tutorial) (Optional) +- Active USB-C hub (optional) - External keyboard - Arduino IDE 1.8.10+ or Arduino Pro IDE 0.0.4+ - Power supply for the USB hub (if a USB hub is used) @@ -24,7 +33,7 @@ It is possible to configure the Portenta H7 to act as a USB host in a way that a - USB cable compatible with the Arduino Arduino MKR WiFi 1010 (or the board you have selected) - 3 jumper wires -# USB Host and Client Mode +## USB Host and Client Mode When using the Portenta H7 as a USB host it can receive and manage the information provided by other USB peripherals connected to it through a hub. In this scenario, the Portenta H7 is referred to as "USB host" or "master" device, and the peripheral is called the "client" device, ( usually a mouse or a keyboard ). @@ -34,7 +43,9 @@ In contrast to being the host point for peripherals, the Portenta H7 can also be Furthermore, the Portenta H7 can also be set to be a mouse or keyboard itself. With the corresponding sketch, circuitry and components, we could send keyboard or mouse data to a PC (not covered in this tutorial). -# Setting Up the USB host +## Instructions + +### Setting Up the USB Host In this tutorial you are going to configure your Portenta H7 as a USB Host. This will allow you to toggle the RGB built-in LEDs of the board by pressing the corresponding keys (r, g, b) on a USB keyboard that will be plugged into a USB hub. Throughout the tutorial, you will learn how to establish a serial communication between the board and the keyboard and also, how to program the board so it acts as a USB Host device. @@ -42,22 +53,22 @@ To achieve this you will use the [USBHOST](https://os.mbed.com/handbook/USBHost) Thanks to USB OTG (On The Go) specification the Portenta H7 can switch between host and client modes automatically. The code that enables USB OTG in Portenta H7 is included in the **mbed** core and it is ready to function without additional configurations. Some related classes to make use of the USB port for different use cases are: -* `USBCDC_ECM` > Ethernet over USB (See [here](https://os.mbed.com/docs/mbed-os/v5.15/apis/usbcdc-ecm.html)) -* `USBMSD` > Mass storage device over USB (See [here](https://os.mbed.com/docs/mbed-os/v5.15/apis/usbmsd.html)) -* `USBCDC` > Serial port over USB (See [here](https://os.mbed.com/docs/mbed-os/v5.15/apis/usbcdc.html)) -* `USBHID` > USB human interface devices e.g. mouse or keyboard (See [here](https://os.mbed.com/docs/mbed-os/v5.15/apis/usbhid.html)) +- `USBCDC_ECM` > Ethernet over USB (See [here](https://os.mbed.com/docs/mbed-os/v5.15/apis/usbcdc-ecm.html)) +- `USBMSD` > Mass storage device over USB (See [here](https://os.mbed.com/docs/mbed-os/v5.15/apis/usbmsd.html)) +- `USBCDC` > Serial port over USB (See [here](https://os.mbed.com/docs/mbed-os/v5.15/apis/usbcdc.html)) +- `USBHID` > USB human interface devices e.g. mouse or keyboard (See [here](https://os.mbed.com/docs/mbed-os/v5.15/apis/usbhid.html)) -## 1. The Basic Setup +### 1. The Basic Setup -Begin by plugging in your Portenta board to the computer using a USB-C cable and open the Arduino IDE or the Arduino Pro IDE. If this is your first time running Arduino sketch files on the board, we suggest you check out how to [set up the Portenta H7 for Arduino](por-ard-gs) before you proceed. +Begin by plugging in your Portenta board to the computer using a USB-C cable and open the Arduino IDE or the Arduino Pro IDE. If this is your first time running Arduino sketch files on the board, we suggest you check out how to [set up the Portenta H7 for Arduino](https://www.arduino.cc/pro/tutorials/portenta-h7/por-ard-gs) before you proceed. ![The Portenta H7 can be connected to the computer using an appropriate USB-C cable](assets/por_ard_usbh_basic_setup.svg) -## 2. Creating the Keyboard Controller +### 2. Creating the Keyboard Controller Let's create a sketch that handles the USB connections and modifies the state of the LEDs with each press on the R, G or B keys. When you press the key first time the LEDs will switch on and to turn them off, you will need to press the corresponding key again. -As programming the USB protocol that allows the board to handle USB devices is an arduous task, you will use a pre-built example called *KeyboardController*. To find it make sure you have the board description for the Arduino Portenta H7 installed using the Boards Manager. +As programming the USB protocol that allows the board to handle USB devices is an arduous task, you will use a pre-built example called **KeyboardController**. To find it make sure you have the board description for the Arduino Portenta H7 installed using the Boards Manager. ![Select the Arduino Portenta H7 (M7 core) in the board selector](assets/por_ard_usbh_select_board.png) @@ -70,7 +81,7 @@ The **USBHost** library that is used in this example is a revamp of the classic **Note:** You can find the finished sketch in the examples of the Arduino_Pro_Tutorials library: **Examples -> Arduino_Pro_Tutorials -> Portenta H7 as a USB Host -> LEDKeyboardController** -## 3. Detecting the Keys From the Keyboard +### 3. Detecting the Keys From the Keyboard The example you opened describes how the board will handle the connection with a keyboard, addressing the functionality of each one of the keys of it. In order to detect which one of the keys from the keyboard is pressed, you will need to modify and add some lines of code to the example. @@ -115,7 +126,7 @@ Then, in order to modify the state of the LEDs of the board with the R, G or B k ![Code to toggle the LEDs with the R, G and B keys](assets/por_ard_usbh_detecting_keys.png) -## 4. Initializing the LEDs +### 4. Initializing the LEDs Once you have the code that detects if the correct keys are pressed and controls the LEDs accordingly, you need to initialise the LEDs. To do so, add the following portion of code inside the `setup()` function. ```cpp @@ -137,7 +148,7 @@ bool ledGstate = false; bool ledBstate = false; ``` -## 5. Upload the Code +### 5. Upload the Code Before uploading the sketch to the board, save it you sketchbook and name it **leds_keyController.ino**. Then select the **Arduino Portenta H7 (M7 core)** from the **Board** menu and the port the Portenta is connected to. Upload the **leds_keyController.ino** sketch. Doing so will automatically compile the sketch beforehand. @@ -145,7 +156,7 @@ Before uploading the sketch to the board, save it you sketchbook and name it **l ![Select the Arduino Portenta H7 (M7 core) in the board selector](assets/por_ard_usbh_select_board.png) -## 6. Connecting a Keyboard to Portenta +### 6. Connecting a Keyboard to Portenta When you connect the Portenta board to the computer to program it, the computer is the USB host and the Portenta board is the USB client. The same happens when you connect an external keyboard to your PC. For this tutorial the Portenta board will be the host. It won't be connected to the PC after programming it! Let's see how to make the connections. @@ -157,15 +168,15 @@ In the image above you can see that: + The USB Hub (USB-C adapter) needs to be powered externaly with a power supply. This is required to provide power to Portenta. + You should connect the keyboard to the USB Hub (USB-C adapter) in the same way you would connect it to your PC. -### Alternative Configuration (No USB hub required) +### Alternative Configuration (No USB Hub Required) If you don't have a USB-C type hub you may complete this tutorial with a USB-C type keyboard or with a USB A type keyboard and a USB A to C adapter. To do so proceed as follows: + Power the Portenta H7 through the VIN pin with 5V. (Check [pinout diagram](https://content.arduino.cc/assets/Pinout-PortentaH7_latest.pdf)) + Connect the keyboard directly to the Portenta's USB-C connector (use a USB-A to USB-C adapter if your keyboard's connector is USB type A) -+ Add the following line of code in your sketch to enable power supply through Portenta's USB connector: `mbed::DigitalOut otg(PJ_6, 0);` ++ Add the following line of code in your sketch to enable power supply through Portenta's USB connector: `usb.supplyPowerOnVBUS(true);` -## 7. Toggling the LEDs +### 7. Toggling the LEDs Once you have connected your portenta board, you should be able to toggle the LEDs by pressing the R, G and B keys. @@ -176,17 +187,17 @@ If it doesn't work as it should try the following: 1. Reset the portenta by pressing the Reset button. ![Reset Button](assets/por_ard_usbh_reset.png) 2. Disconnect the Portenta board from the USB Hub (USB C adapter), disconnect the power from the USB Hub, connect the Portenta to the USB Hub and then connect the power to the USB Hub. -# Conclusion +## Conclusion This tutorial shows how to connect and control a keyboard by configuring Portenta as a USB Host. You have also learnt how to modify one of the pre-built examples for Portenta to have a visual reference of the interactions between the keyboard and the Portenta board. -# Next Steps +### Next Steps Now that you've learnt how to set up the board as a USB Host and understood how the example works, start experimenting with the **KeyboardController.ino** sketch. This sketch can be tweaked in a variety of ways based on your needs. For example, you could add a mouse to the USB Hub and increase the brightness of the built-in LEDs when pressing the left button and decrease it when pressing the right button. -# Troubleshooting +## Troubleshooting -## The LEDs Are Not Toggling +### The LEDs Are Not Toggling This troubleshooting guide will help you to find out why the LEDs of your Portenta don't toggle when you press the keys on the keyboard. To do so, you will need some extra components: @@ -196,13 +207,13 @@ This troubleshooting guide will help you to find out why the LEDs of your Porten To detect what the problem is, we are going to send all the information about the USB peripheral (in this case the keyboard) through serial communication from Portenta to the Arduino MKR WiFi 1010 board. Once this info arrives to the Arduino MKR WiFi 1010 board, we will print it out through the Serial Monitor, which will allow us to debug what can be happening. -### 1. Connect the Portenta to the Arduino MKR WiFi 1010 +### 1. Connect the Portenta to the Arduino MKR WiFi 1010 To connect thePortenta to the Arduino MKR WiFi 1010 board you will need connect the pins with Serial1 functionality (13RX and 14TX on both boards) between them as shown in the image below. Don't forget to connect as well the ground pins (GND) of the boards. ![Serial connection Portenta - Arduino MKR WiFi 1010](assets/por_ard_usbh_portenta_to_mkr.svg) -### 2. Program the Arduino MKR WiFi 1010 board +### 2. Program the Arduino MKR WiFi 1010 Board To set up Arduino MKR WiFi 1010 board copy the following code, paste it into a new sketch file and name it **mkr1010_serial.ino**. Then upload it to the Arduino Arduino MKR WiFi 1010 board. Make sure you select **Arduino MKR WiFi 1010** as the board and the port to which the Arduino MKR WiFi 1010 is connected. @@ -234,7 +245,7 @@ After programming your Arduino MKR WiFi 1010 board and connecting the Portenta b -### 3. Open the Serial Monitor of the Arduino MKR WiFi 1010 Board +### 4. Open the Serial Monitor of the Arduino MKR WiFi 1010 Once you have everything connected, open the Serial Monitor and reset the Portenta. After resetting the Portenta board, your Serial Monitor should display something similar to the following: @@ -250,5 +261,5 @@ If in the messages received on the MKR WiFi 1010 board you see any "Disabled" me If, after repeating this process several times, the connection still isn't working, the USB Hub you are using may not be compatible with the Portenta board and you will need a different USB Hub to complete this tutorial. **Authors:** Jose Garcia -**Reviewed by:** Sebastian Hunkeler +**Reviewed by:** Sebastian Hunkeler [2020-05-28] **Last revision:** Sebastian Hunkeler [2020-06-18] diff --git a/content/tutorials/portenta-h7/por-ard-usb/metadata.json b/content/tutorials/portenta-h7/por-ard-usb/metadata.json deleted file mode 100644 index ebc85aa9..00000000 --- a/content/tutorials/portenta-h7/por-ard-usb/metadata.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "id":"por-ard-usb", - "index": 4, - "beta": true, - "title":"Portenta H7 as a USB Host", - "slug":"por-ard-usb", - "authors":[ - "Jose Garcia" - ], - "coverImage":{ - "src":"assets/por_ard_usbh_cover.svg?sanitize=true" - }, - "tags":[ - "USB", - "HID", - "RGB LED" - ], - "abstract":"This tutorial teaches you how to set up the Portenta H7 to act as a USB host in a way that allows to connect peripherals such as a keyboard or mouse to interact with it.", - "previousArticle":"por-ard-ap", - "nextArticle":"por-ard-ble" -} diff --git a/content/tutorials/portenta-h7/por-openmv-bt/content.md b/content/tutorials/portenta-h7/por-openmv-bt/content.md index cbd0066d..470c438d 100644 --- a/content/tutorials/portenta-h7/por-openmv-bt/content.md +++ b/content/tutorials/portenta-h7/por-openmv-bt/content.md @@ -1,12 +1,21 @@ +--- +title: Blob Detection with Portenta and OpenMV +coverImage: assets/por_openmv_bt_cover.svg +tags: [OpenMV, Blob Detection, Machine Vision, Machine Learning] +description: This tutorial will show you how to use the vision carrier board for Portenta to detect the presence and the position of objects in a camera image. +--- + # Blob Detection with Portenta and OpenMV + +## Overview In this tutorial you will use the vision carrier board for Portenta to detect the presence and the position of objects in a camera image. For that you will use a technique that is often referred to as blob detection. For this task you will write a MicroPython script and run it on the Portenta with the help of the OpenMV IDE. -## What You Will Learn +### You Will Learn - How to use the OpenMV IDE to run MicroPython on Portenta - How to use the built-in blob detection algorithm of OpenMV - How to use MicroPython to toggle the built-in LEDs -## Required Hardware and Software +### Required Hardware and Software - Portenta H7 board () - Arduino Portenta Vision Shield (https://store.arduino.cc/portenta-vision-shield) - USB C cable (either USB A to USB C or USB C to USB C) @@ -14,7 +23,7 @@ In this tutorial you will use the vision carrier board for Portenta to detect th - Portenta Bootloader Version 20+ - OpenMV IDE 2.6.4+ -# Portenta and the OpenMV IDE +## Portenta and the OpenMV IDE The OpenMV IDE was built for Machine Vision applications. It is meant to provide an Arduino like experience for simple computer vision tasks using a camera sensor. OpenMV comes with its own fimware that is built on MicroPython. Among other hardware it supports the Portenta board. A statement from the creators of OpenMV on why they built it: >Currently, doing anything serious involving computer vision requires a computer running an operating system running may layers of software and requiring much setup before you can get computer vision code working. This is all well and fine if you need to do many other things than just processing images, like connecting to the internet, running many different applications concurrently, etc. @@ -23,13 +32,14 @@ The OpenMV IDE was built for Machine Vision applications. It is meant to provide This is where OpenMV comes in. [Here](https://openmv.io/) you can read more about the OpenMV IDE. +## Instructions -# Configuring the Development Environment +### Configuring the Development Environment Before you can start programming OpenMV scripts for the Portenta you need to download and install the OpenMV IDE. ***IMPORTANT: Before you connect the Portenta to the OpenMV IDE make sure you update the bootloader as explained in the "Flashing the OpenMV Firmware" section!*** -## 1. Downloading the OpenMV IDE +### 1. Downloading the OpenMV IDE Open the [OpenMV download](https://openmv.io/pages/download) page in your browser and download the version that you need for your operating system. Alternatively you may use the following direct download links of the OpenMV IDE 2.6.5: - [For Windows Xp, Vista, 7, 8, 10 or Later](https://github.com/openmv/openmv-ide/releases/download/v2.6.5/openmv-ide-windows-2.6.5.exe) @@ -41,9 +51,9 @@ Open the [OpenMV download](https://openmv.io/pages/download) page in your browse Follow the instructions of the installer. -## 2. Flashing the OpenMV Firmware +### 2. Flashing the OpenMV Firmware -Connect the Portenta to your computer via the USB-C cable if you haven't done so yet. Make sure you first update the bootloader to the latest version using the *PortentaH7_updateBootloader* sketch in the examples menu in the Arduino IDE. +Connect the Portenta to your computer via the USB-C cable if you haven't done so yet. Make sure you first update the bootloader to the latest version using the **PortentaH7_updateBootloader** sketch in the examples menu in the Arduino IDE. Instructions on how to update the bootloader can be found in the ["Updating the Portenta Bootloader" tutorial](https://www.arduino.cc/pro/tutorials/portenta-h7/por-ard-bl). ***In bootloader versions 17 and older there was a bug that could put the Portenta in a boot loop when the transmission aborted while flashing a large firmware file. This was fixed in the bootloader version 18. We strongly advise to update the bootloader before you proceed with the next step of this tutorial!*** @@ -69,14 +79,13 @@ The Portenta will start flashing its blue LED when it's ready to be connected. A ![When the Portenta is successfully connected to the OpenMV IDE a green play button appears in the lower left](assets/por_openmv_board_connected.png) - -# Blob Detection +## Blob Detection In this section you will learn how to use the built-in blob detection algorithm to detect the location of objects in an image. That algorithm allows to detect areas in a digital image that differ in properties such as brightness or color compared to surrounding areas. These areas are called blobs. To do so you need to feed an image from the camera to the algorithm. It will then analyse it and output the coordinates of the found blobs. You will visualize these coordinates directly on the image and indicate whether a blob was found by using the red and green LED. -## 1. Prepare the Script +### 1. Prepare the Script Create a new script by clicking the "New File" button in the toolbar on the left side. Import the required modules: @@ -89,7 +98,7 @@ import time # Import module for tracking elapsed time A module in Python is a confined bundle of functionality. By importing it into the script it gets made available. -## 2. Preparing the Sensor +### 2. Preparing the Sensor In order to take a snapshot with the camera it has to be configured in the script. @@ -104,7 +113,7 @@ The most relevant functions in this snipped are `set_pixformat` and `set_framesi The resolution of the camera needs to be set to a supported format both by the sensor and the algorithm. Algorithms which use a neural network are usually trained on a specific image resolution. This makes them sensistive to the provided image snapshot resolution. The vision carrier supports `QVGA` which you will use in this tutorial. -## 3. Detecting Blobs +### 3. Detecting Blobs In order to feed the blob detection algorithm with an image you have to take a snapshot from the camera or load the image from memory (e.g. SD card or internal flash). In this case you will take a snapshot using the `snapshot()` function. The resulting image needs then to be fed to the algorithm using the `find_blobs` function. You will notice that a list of tuples gets passed to the algorithm. In this list you can specify the grey scale values (brightness) that are mostly contained in the object that you would like to track. If you were for example to detect white objects on a black background the resulting range of brightness would be very narrow (e.g. from 200 - 255). Remember that 255 denotes the maximum brightness / white and 0 corresponds to the minimum brightness / black. If we're interested in a wider range of grey scale values to detect various objects we can set the threshold range for example to (100, 255). @@ -130,7 +139,7 @@ for blob in blobs: The result of that will be visible in the Frame Buffer preview panel on the right side of the OpenMV IDE. -## 4. Toggling LEDs +### 4. Toggling LEDs What if you want some visual feedback from the blob detection without any computer connected to your Portenta? You could use for example the built-in LEDs to indicate whether or not a blob was found in the camera image. Let's initialise the red and the green LEDs with the following code: @@ -154,7 +163,7 @@ else: In this example the green LED will light up when there is at least one blob found in the image. The red LED will light up if no blob could be found. -## 5. Uploading the Script +### 5. Uploading the Script Let's program the Portenta with the complete script and test if the algorithm works. Copy the following script and paste it into the new script file that you created. ```py @@ -210,22 +219,22 @@ Click on the "Play" button at the bottom of the left toolbar. Place some objects ![An example of a blob detection running in the OpenMV IDE](assets/por_openmv_blob_detected.png) -# Conclusion +## Conclusion In this tutorial you learned how to use the OpenMV IDE to develop MicroPython scripts that then run on the Portenta board. You also learned how to configure the camera of the Vision Carrier board to be used for machine vision applications in OpenMV. Last but not least you learned how to interact with the built-in LEDs in MicroPython on the OpenMV firmware. -# Next Steps +### Next Steps - Familiarize yourself with the OpenMV IDE. There are many other features that didn't get mentioned in this tutorial (e.g. the Serial Terminal). - Try out other machine vision examples that come with the OpenMV IDE (e.g. Face Detection). You can find them in the "Examples" menu. -# Troubleshooting -## OpenMV Firmware Flashing Issues +## Troubleshooting +### OpenMV Firmware Flashing Issues - If the upload of the OpenMV firmware fails during the download, put the board back in boot loader mode and try again. Give it a few tries until the firmware gets successfully uploaded. -- If the upload of the OpenMV firmware fails without even starting, try uploading the latest firmware using the "Load Specific Firmware File" option. You can find the latest firmware on the [OpenMV Github repository](https://github.com/openmv/openmv/releases). Look for a file called *firmware.bin* in the PORTENTA folder. -- If you experience issues putting the board in bootloader mode, make sure you first update the bootloader to the latest version using the *PortentaH7_updateBootloader* sketch from the examples menu in the Arduino IDE. +- If the upload of the OpenMV firmware fails without even starting, try uploading the latest firmware using the "Load Specific Firmware File" option. You can find the latest firmware on the [OpenMV Github repository](https://github.com/openmv/openmv/releases). Look for a file called **firmware.bin** in the PORTENTA folder. +- If you experience issues putting the board in bootloader mode, make sure you first update the bootloader to the latest version using the **PortentaH7_updateBootloader** sketch from the examples menu in the Arduino IDE. - If the camera cannot get recognized by the OpenMV IDE or if you see a "No OpenMV Cams found!" message, press the reset button of Portenta once and wait until you see the blue LED flashing. Then try again connecting to the board. - If you see a "OSError: Reset Failed" message, reset the board by pressing the reset button. Wait until you see the blue LED flashing, connect the board to the OpenMV IDE and try running the script again. **Authors:** Sebastian Romero -**Reviewed by:** Lenard George [6.10.2020] -**Last revision:** Sebastian Romero [7.10.2020] \ No newline at end of file +**Reviewed by:** Lenard George [2020-10-06] +**Last revision:** Sebastian Romero [2020-10-07] \ No newline at end of file diff --git a/content/tutorials/portenta-h7/por-openmv-bt/metadata.json b/content/tutorials/portenta-h7/por-openmv-bt/metadata.json deleted file mode 100644 index 2d24871e..00000000 --- a/content/tutorials/portenta-h7/por-openmv-bt/metadata.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "id":"por-openmv-bt", - "index": 5, - "title":"Blob Detection with Portenta and OpenMV", - "slug":"por-openmv-bt", - "authors":[ - "Sebastian Romero" - ], - "coverImage":{ - "src":"assets/por_openmv_bt_cover.svg?sanitize=true" - }, - "tags":[ - "OpenMV", - "Blob Detection", - "Machine Vision", - "Machine Learning" - ], - "abstract":"This tutorial will show you how to use the vision carrier board for Portenta to detect the presence and the position of objects in a camera image.", - "previousArticle":"por-ard-ble", - "nextArticle":"por-ard-bl" -} diff --git a/content/tutorials/portenta-h7/por-openmv-fd/content.md b/content/tutorials/portenta-h7/por-openmv-fd/content.md index 1b478461..bb2fce92 100644 --- a/content/tutorials/portenta-h7/por-openmv-fd/content.md +++ b/content/tutorials/portenta-h7/por-openmv-fd/content.md @@ -1,13 +1,22 @@ +--- +title: Creating a Basic Face Filter With OpenMV +coverImage: assets/por_openmv_fd_cover.svg +tags: [OpenMV, Face Detection, Haar Cascade, Machine Vision, Machine Learning] +description: In this tutorial you will build a MicroPython application with OpenMV that uses the Portenta Vision Shield to detect faces and overlay them with a custom bitmap image. +--- + # Creating a Basic Face Filter With OpenMV + +## Overview In this tutorial you will build a MicroPython application with OpenMV that uses the Portenta Vision Shield to detect faces and overlay them with a custom bitmap image. Think of it as building your own camera filter that puts a smile on every face it detects. This tutorial is based on the face detection example that comes with the OpenMV IDE. -## What You Will Learn +### You Will Learn - How to use the OpenMV IDE to run MicroPython on Portenta - How to use the built-in face detection algorithm of OpenMV - Copying files to the internal flash of the Portenta - Using MicroPython to read files from the internal flash -## Required Hardware and Software +### Required Hardware and Software - Portenta H7 board () - Arduino Portenta Vision Shield (https://store.arduino.cc/portenta-vision-shield) - USB C cable (either USB A to USB C or USB C to USB C) @@ -15,7 +24,7 @@ In this tutorial you will build a MicroPython application with OpenMV that uses - Portenta Bootloader Version 20+ - OpenMV IDE 2.6.4+ -# The Haar Cascade Algorithm +## The Haar Cascade Algorithm By harnessing the power of machine vision algorithms objects can be detected in a camera stream. Those algorithms can be trained to detect the desired type of object. In this tutorial you will use a machine learning based approach called Haar Cascade to detect faces. @@ -25,15 +34,17 @@ This approach uses a cascade algorithm that has multiple stages where the output The built-in Haar Cascade model for faces was trained with hundreds of images containing faces that are labeled as such and images that don't contain faces labeled differently. That allows the algorithm to distinguish such images after it is being trained. -# Creating the Face Detection Script +## Instructions + +### Creating the Face Detection Script For this tutorial you will be using the OpenMV IDE along with the OpenMV firmare on your Portenta H7 to build the face detection script. If this is your first time using the Vision Carrier and OpenMV we recommend you to take a look at the "Configuring the Development Environment" section inside the [Blob Detection tutorial](https://www.arduino.cc/pro/tutorials/portenta-h7/por-openmv-bt) to configure the development environment. -## 1. The Basic Setup +### 1. The Basic Setup Attach your Vision Carrier to your Portenta H7 and open the **OpenMV** Editor. For this tutorial, you will create a new script that is based on the face detection example provided by OpenMV. Create a new script by clicking the "New File" button in the toolbar on the left side and save it as **face_detection.py**. -## 2. Importing the Modules +### 2. Importing the Modules The script starts by importing the `sensor`, `image` and `time` modules for handling the camera sensor, using machine vision algorithms and time tracking functions. @@ -43,7 +54,7 @@ import image # Import module containing machine vision algorithms import time # Import module for tracking elapsed time ``` -## 3. Preparing the Sensor +### 3. Preparing the Sensor The next step is to calibrate the camera sensor for achieving the best results using the `sensor` module. You can use the `set_contrast()` function to set the contrast of the sensor to its highest value (3). This can help the algorithm identifying lines and edges more easily. `set_gainceiling()` controls the amplification of the signal from the camera sensor including any associated background noise. For maximising the detection success rate it is recommended to set the camera frame size to `HQVGA`. @@ -55,7 +66,7 @@ sensor.set_framesize(sensor.HQVGA) sensor.set_pixformat(sensor.GRAYSCALE) ``` -## 4. Finding the Face Features +### 4. Finding the Face Features OpenMV provides a Haar Cascade class ready to be used with the Vision Shield's camera. The function [`image.HaarCascade(path, number of stages)`](https://docs.openmv.io/library/omv.image.html#class-Haarcascade-feature-descriptor) is used to load a Haar Cascade model into memory. The `path` parameter can be used to either specify the location of a custom Haar Cascade model or to use the built-in `frontalface` model. The `stages` parameter is use to specify the desired Haar Cascade stages. @@ -66,7 +77,7 @@ face_cascade = image.HaarCascade("frontalface", stages=25) print(face_cascade) # Prints the Haar Cascade configuration ``` -## 5. Displaying a Bitmap Image +### 5. Displaying a Bitmap Image Once you know the location of the faces in the camera image you can overlay them with an image of your choice. OpenMV currently supports bmp, pgm or ppm image formats. Image formats with an alpha layer such as PNG are not supported yet. @@ -104,7 +115,7 @@ You can then draw the scaled bitmap image on top of the camera image using the ` cameraImage.draw_image(faceImage, faceX, faceY, x_scale=scale_ratio, y_scale=scale_ratio) ``` -## 7. Uploading the Script +### 6. Uploading the Script Let's program the Portenta with the complete script and test if the algorithm works. Copy the following script and paste it into the new script file that you created. ```py @@ -158,27 +169,27 @@ Click on the "Play" button at the bottom of the left toolbar. Point the camera o ![Copy the bitmap image to flash drive](assets/por_openmv_fd_output.png) -# Conclusion +## Conclusion In this tutorial you learned how to use OpenMV's built-in face detection algorithm which is based on Haar Cascade. Furthermore you learned how to copy a file to the internal flash and how to load an image from the flash into the memory. You have also learned how to draw an image on top of a snapshot from the camera stream. -# Next Steps +### Next Steps The [HaarCascade](https://docs.openmv.io/library/omv.image.html#class-Haarcascade-feature-descriptor) class provided by OpenMV can also detect other facial features such as eyes. For example you could tweak your **face_detection.py** script to detect your eyes simply by changing the `path` parameter from `frontalface ` to `eye` which is also a built-in model. Go ahead and replace the following line in your script and try to figure out how to overlay your eyes with a bitmap image of an eye. ```py face_cascade = image.HaarCascade("eye", stages=25) ``` -# Troubleshooting +## Troubleshooting -## Face Detection Issues +### Face Detection Issues - If OpenMV can't detect your face try moving the camera further away or position yourself in front of a wall or another plain background. -## Bitmap loading issues +### Bitmap Loading Issues - If you have troubles loading a custom bitmap image, try with the pbm format and try scaling it down to a smaller size such as 128x128 pixels. **Authors:** Sebastian Romero, Lenard George -**Reviewed by:** Lenard George [15.10.2020] -**Last revision:** Sebastian Romero [16.10.2020] +**Reviewed by:** Lenard George [2020-10-15] +**Last revision:** Sebastian Romero [2020-10-16] diff --git a/content/tutorials/portenta-h7/por-openmv-fd/metadata.json b/content/tutorials/portenta-h7/por-openmv-fd/metadata.json deleted file mode 100644 index 48a43ca2..00000000 --- a/content/tutorials/portenta-h7/por-openmv-fd/metadata.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "id":"por-openmv-fd", - "index": 6, - "title":"Creating a Basic Face Filter With OpenMV", - "slug":"por-openmv-fd", - "authors":[ - "Sebastian Romero", - "Lenard George" - ], - "coverImage":{ - "src":"assets/por_openmv_fd_cover.svg?sanitize=true" - }, - "tags":[ - "OpenMV", - "Face Detection", - "Haar Cascade", - "Machine Vision", - "Machine Learning" - ], - "abstract":"In this tutorial you will build a MicroPython application with OpenMV that uses the Portenta Vision Shield to detect faces and overlay them with a custom bitmap image.", - "previousArticle":"por-ard-bl", - "nextArticle":"por-ard-trace32" -} diff --git a/content/tutorials/portenta-h7/por-openmv-gs/assets/por_openmv_board_connected.png b/content/tutorials/portenta-h7/por-openmv-gs/assets/por_openmv_board_connected.png new file mode 100644 index 00000000..5b55ac9c Binary files /dev/null and b/content/tutorials/portenta-h7/por-openmv-gs/assets/por_openmv_board_connected.png differ diff --git a/content/tutorials/portenta-h7/por-openmv-gs/assets/por_openmv_click_connect.png b/content/tutorials/portenta-h7/por-openmv-gs/assets/por_openmv_click_connect.png new file mode 100644 index 00000000..3aba99a9 Binary files /dev/null and b/content/tutorials/portenta-h7/por-openmv-gs/assets/por_openmv_click_connect.png differ diff --git a/content/tutorials/portenta-h7/por-openmv-gs/assets/por_openmv_firmware_updater.png b/content/tutorials/portenta-h7/por-openmv-gs/assets/por_openmv_firmware_updater.png new file mode 100644 index 00000000..0c388077 Binary files /dev/null and b/content/tutorials/portenta-h7/por-openmv-gs/assets/por_openmv_firmware_updater.png differ diff --git a/content/tutorials/portenta-h7/por-openmv-gs/assets/por_openmv_gs_cover.svg b/content/tutorials/portenta-h7/por-openmv-gs/assets/por_openmv_gs_cover.svg new file mode 100644 index 00000000..1904a263 --- /dev/null +++ b/content/tutorials/portenta-h7/por-openmv-gs/assets/por_openmv_gs_cover.svg @@ -0,0 +1,883 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/content/tutorials/portenta-h7/por-openmv-gs/assets/por_openmv_open_ide.png b/content/tutorials/portenta-h7/por-openmv-gs/assets/por_openmv_open_ide.png new file mode 100644 index 00000000..8bbb9a34 Binary files /dev/null and b/content/tutorials/portenta-h7/por-openmv-gs/assets/por_openmv_open_ide.png differ diff --git a/content/tutorials/portenta-h7/por-openmv-gs/assets/por_openmv_reset_firmware.png b/content/tutorials/portenta-h7/por-openmv-gs/assets/por_openmv_reset_firmware.png new file mode 100644 index 00000000..8038d067 Binary files /dev/null and b/content/tutorials/portenta-h7/por-openmv-gs/assets/por_openmv_reset_firmware.png differ diff --git a/content/tutorials/portenta-h7/por-openmv-gs/content.md b/content/tutorials/portenta-h7/por-openmv-gs/content.md new file mode 100644 index 00000000..018c9715 --- /dev/null +++ b/content/tutorials/portenta-h7/por-openmv-gs/content.md @@ -0,0 +1,160 @@ +--- +title: Getting Started with OpenMV and MicroPython +coverImage: assets/por_openmv_gs_cover.svg +tags: [Getting Started, OpenMV, Setup, Blink, MicroPython] +description: This tutorial teaches you how to set up the board, how to use the OpenMV IDE and how to run a MicroPython blink example with OpenMV. +--- + +# Getting Started with OpenMV and MicroPython +## Overview +The OpenMV IDE is meant to provide an Arduino like experience for simple machine vision tasks using a camera sensor. In this tutorial, you will learn about some of the basic features of the OpenMV IDE and how to create a simple MicroPython script. + +### You Will Learn +- The basic features of the OpenMV IDE +- How to create a simple MicroPython script +- How to use the OpenMV IDE to run MicroPython on Portenta H7 + + +### Required Hardware and Software +- Portenta H7 board () +- USB-C cable (either USB-A to USB-C or USB-C to USB-C) +- Portenta Bootloader Version 20+ +- OpenMV IDE 2.6.4+ + +## Instructions + +Using the OpenMV IDE you can run [MicroPython](http://docs.MicroPython.org/en/latest/) scripts on the Portenta H7 board. MicroPython provides a lot of classes and modules that make it easy to quickly explore the features of the Portenta H7. In this tutorial you will first download the OpenMV IDE and set up the development environment. [Here](https://openmv.io/) you can read more about the OpenMV IDE. OpenMV comes with its own firmware that is built on MicroPython. You will then learn to write a simple script that will blink the on-board RGB LED using some basic MicroPython commands. + +### 1. Downloading the OpenMV IDE + +Before you can start programming OpenMV scripts for the Portenta you need to download and install the OpenMV IDE. + +***IMPORTANT: Before you connect the Portenta to the OpenMV IDE make sure you update the bootloader as explained in the "Flashing the OpenMV Firmware" section!*** + +Open the [OpenMV download](https://openmv.io/pages/download) page in your browser, download the version that you need for your operating system and follow the instructions of the installer. + +### 2. Flashing the OpenMV Firmware + +Connect the Portenta to your computer via the USB-C cable if you haven't done so yet. Make sure you first update the bootloader to the latest version using the **PortentaH7_updateBootloader** sketch in the examples menu in the Arduino IDE. + +Instructions on how to update the bootloader can be found in the ["Updating the Portenta Bootloader" tutorial](https://www.arduino.cc/pro/tutorials/portenta-h7/por-ard-bl). + +After updating the bootloader put the Portenta in bootloader mode by double-pressing the reset button on the board. The built-in green LED will start fading in and out. Now open the OpenMV IDE. + +![The OpenMV IDE after starting it](assets/por_openmv_open_ide.png) + +Click on the "connect" symbol at the bottom of the left toolbar. + +![Click the connect button to attach the Portenta to the OpenMV IDE](assets/por_openmv_click_connect.png) + +A pop-up will ask you how you would like to proceed. Select "Reset Firmware to Release Version". This will install the latest OpenMV firmware on the Portenta H7. You can leave the option of erasing the internal file system unselected and click "OK". + +![Install the latest version of the OpenMV firmware](assets/por_openmv_reset_firmware.png) + +Portenta H7's green LED will start flashing while the OpenMV firmware is being uploaded to the board. A terminal window will open which shows you the flashing progress. Wait until the green LED stops flashing and fading. You will see a message saying "DFU firmware update complete!" when the process is done. + +![Installing firmware on portenta board in OpenMV](assets/por_openmv_firmware_updater.png) + +***Installing the OpenMV firmware will overwrite any existing sketches in the internal flash of Portenta H7. Also the M7 port won't be exposed in the Arduino IDE anymore. To re-flash the M7 with an Arduino firmware you need to put the board into bootloader mode. To do so double press the reset button on the Portenta H7 board. The built-in green LED will start fading in and out. In bootloader mode you will see the Portenta M7 port again in the Arduino IDE.*** + +The board will start flashing its blue LED when it's ready to be connected. After confirming the completion dialog the Portenta H7 should already be connected to the OpenMV IDE, otherwise click the "connect" button (plug symbol) once again. + +![When the Portenta H7 is successfully connected a green play button appears](assets/por_openmv_board_connected.png) + +### 3. Preparing the Script + +Create a new script by clicking the "New File" button in the toolbar on the left side. Import the required module `pyb`: + +```py +import pyb # Import module for board related functions +``` + +A module in Python is a confined bundle of functionality. By importing it into the script it gets made available. For this example we only need `pyb`, which is a module that contains board related functionality such as PIN handling. You can read more about its functions [here](https://docs.micropython.org/en/latest/library/pyb.html). + +Now we can create the variables that will control our built-in RGB LED. With `pyb` we can easily control each colour. + +```py +redLED = pyb.LED(1) # built-in red LED +greenLED = pyb.LED(2) # built-in green LED +blueLED = pyb.LED(3) # built-in blue LED +``` + +Now we can easily distinguish between which color we control in the script. + +### 4. Creating the Main Loop in the Script + +Putting our code inside a while loop will make the code run continuously. In the loop we turn on an LED with `on`, then we use the `delay` function to create a delay. This function will wait with execution of the next instruction in the script. The duration of the delay can be controlled by changing the value inside the parentheses. The number defines how many milliseconds the board will wait. After the specified time has passed, we turn off the LED with the `off` function. We repeat that for each colour. + +```py +while True: + # Turns on the red LED + redLED.on() + # Makes the script wait for 1 second (1000 miliseconds) + pyb.delay(1000) + # Turns off the red LED + redLED.off() + pyb.delay(1000) + greenLED.on() + pyb.delay(1000) + greenLED.off() + pyb.delay(1000) + blueLED.on() + pyb.delay(1000) + blueLED.off() + pyb.delay(1000) +``` + +### 5. Uploading the Script + +Here you can see the complete blink script: + +```py +import pyb # Import module for board related functions + +redLED = pyb.LED(1) # built-in red LED +greenLED = pyb.LED(2) # built-in green LED +blueLED = pyb.LED(3) # built-in blue LED + +while True: + + # Turns on the red LED + redLED.on() + # Makes the script wait for 1 second (1000 miliseconds) + pyb.delay(1000) + # Turns off the red LED + redLED.off() + pyb.delay(1000) + greenLED.on() + pyb.delay(1000) + greenLED.off() + pyb.delay(1000) + blueLED.on() + pyb.delay(1000) + blueLED.off() + pyb.delay(1000) +``` + +Connect your board to the OpenMV IDE and upload the above script by pressing the play button in the lower left corner. + +![Press the green play button to upload the script](assets/por_openmv_board_connected.png) + +Now the built-in LED on your Portenta board should be blinking red, green and then blue repeatedly. + +## Conclusion +In this tutorial you learned how to use the OpenMV IDE with your Portenta board. You also learned how to control the Portenta H7's RGB LED with MicroPython functions and to upload the script to your board using the OpenMV IDE. + +### Next Steps +- Experiment with MicroPythons capabilities. If you want some examples of what to do, take a look at the examples included in the OpenMV IDE. Go to: **File>Examples>Arduino>Portenta H7** in the OpenMV IDE. +- Take a look at our other Portenta H7 tutorials which showcase its many uses. You can find them [here](https://www.arduino.cc/pro/tutorials/portenta-h7) + +## Troubleshooting +### OpenMV Firmware Flashing Issues +- If the upload of the OpenMV firmware fails during the download, put the board back in bootloader mode and try again. Repeat until the firmware gets successfully uploaded. +- If the OpenMV IDE still can't connect after flashing the firmware, try uploading the latest firmware using the "Load Specific Firmware File" option. You can find the latest firmware in the [OpenMV Github repository](https://github.com/openmv/openmv/releases). Look for a file named **firmware.bin** in the PORTENTA folder. +- If you experience issues putting the board in bootloader mode, make sure you first update the bootloader to the latest version using the **PortentaH7_updateBootloader** sketch from the examples menu in the Arduino IDE. +- If you see a "OSError: Reset Failed" message, reset the board by pressing the reset button. Wait until you see the blue LED flashing, connect the board to the OpenMV IDE and try running the script again. +- In bootloader versions 17 and older there was a bug that could put the Portenta into a boot loop when the transmission aborted while flashing a large firmware file. This was fixed in the bootloader version 18. + +**Authors:** Sebastian Romero, Benjamin Dannegård +**Reviewed by:** Lenard George [2021-04-12] +**Last revision:** Sebastian Romero [2021-05-03] \ No newline at end of file diff --git a/content/tutorials/portenta-h7/template/content.md b/content/tutorials/portenta-h7/template/content.md index 32dd8681..62647fe4 100644 --- a/content/tutorials/portenta-h7/template/content.md +++ b/content/tutorials/portenta-h7/template/content.md @@ -1,29 +1,32 @@ +--- +title: Setting Up Portenta H7 For Arduino +coverImage: assets/por_ard_gs_cover.svg +tags: [Getting Started, IDE, Setup, Blink] +description: This tutorial teaches you how to set up the board, how to configure your computer and how to run the classic Arduino blink example to verify if the configuration was successful. +--- + # Setting Up Portenta H7 For Arduino +## Overview Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. -## What You Will Learn +### You Will Learn - A - B - C -## Required Hardware and Software +### Required Hardware and Software - Portenta H7 board () - USB C cable (either USB A to USB C or USB C to USB C) - Arduino IDE 1.8.10+ or Arduino Pro IDE 0.0.4+ -# Portenta and The ... -Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. [Here](https://www.ni.com/en-us/innovations/white-papers/07/what-is-a-real-time-operating-system--rtos--.html) you can read more about real time operating systems. - -![The Arduino core is built on top of the Mbed stack](assets/Arduino-Logo.svg?sanitize=true) +## Instructions - -# Configuring the Development Environment Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. -## 1. The Basic Setup +### 1. The Basic Setup Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. -## 2. Uploading the classic blink sketch +### 2. Uploading the classic blink sketch Let's program the Portenta with the classic blink example to check if the connection to the board works: - In the classic Arduino IDE open the blink example by clicking the menu entry File->Examples->01.Basics->Blink. @@ -45,15 +48,16 @@ void loop() { } ``` -# Conclusion +## Conclusion Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. -# Next Steps +### Next Steps - A - B -# Troubleshooting -## Sketch Upload Troubleshooting +## Troubleshooting + +### Sketch Upload Troubleshooting Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. **Authors:** XX, YY diff --git a/content/tutorials/portenta-h7/template/metadata.json b/content/tutorials/portenta-h7/template/metadata.json deleted file mode 100644 index a369aed9..00000000 --- a/content/tutorials/portenta-h7/template/metadata.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "id":"por-ard-gs", - "index": 1, - "title":"Setting Up Portenta H7 For Arduino", - "slug":"por-ard-gs", - "authors":[ - "Lenard George", - "Sebastian Hunkeler" - ], - "coverImage":{ - "src":"assets/por_ard_gs_cover.svg?sanitize=true" - }, - "tags":[ - "Getting Started", - "IDE", - "Setup", - "Blink" - ], - "abstract":"This tutorial teaches you how to set up the board, how to configure your computer and how to run the classic Arduino blink example to verify if the configuration was successful.", - "previousArticle":null, - "nextArticle":"por-ard-dcp" -} diff --git a/content/tutorials/portenta-h7/vs-ard-gs/assets/vs_ard_frames_captured.png b/content/tutorials/portenta-h7/vs-ard-gs/assets/vs_ard_frames_captured.png new file mode 100644 index 00000000..d4b3eff0 Binary files /dev/null and b/content/tutorials/portenta-h7/vs-ard-gs/assets/vs_ard_frames_captured.png differ diff --git a/content/tutorials/portenta-h7/vs-ard-gs/assets/vs_ard_gs_attach_boards.svg b/content/tutorials/portenta-h7/vs-ard-gs/assets/vs_ard_gs_attach_boards.svg new file mode 100644 index 00000000..6517a52e --- /dev/null +++ b/content/tutorials/portenta-h7/vs-ard-gs/assets/vs_ard_gs_attach_boards.svg @@ -0,0 +1,922 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/content/tutorials/portenta-h7/vs-ard-gs/assets/vs_ard_gs_core.png b/content/tutorials/portenta-h7/vs-ard-gs/assets/vs_ard_gs_core.png new file mode 100644 index 00000000..1fae5752 Binary files /dev/null and b/content/tutorials/portenta-h7/vs-ard-gs/assets/vs_ard_gs_core.png differ diff --git a/content/tutorials/portenta-h7/vs-ard-gs/assets/vs_ard_gs_cover.svg b/content/tutorials/portenta-h7/vs-ard-gs/assets/vs_ard_gs_cover.svg new file mode 100644 index 00000000..ce417d09 --- /dev/null +++ b/content/tutorials/portenta-h7/vs-ard-gs/assets/vs_ard_gs_cover.svg @@ -0,0 +1,610 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/content/tutorials/portenta-h7/vs-ard-gs/assets/vs_ard_open_pde_sketch.png b/content/tutorials/portenta-h7/vs-ard-gs/assets/vs_ard_open_pde_sketch.png new file mode 100644 index 00000000..4a8eef7c Binary files /dev/null and b/content/tutorials/portenta-h7/vs-ard-gs/assets/vs_ard_open_pde_sketch.png differ diff --git a/content/tutorials/portenta-h7/vs-ard-gs/content.md b/content/tutorials/portenta-h7/vs-ard-gs/content.md new file mode 100644 index 00000000..5d0d1b01 --- /dev/null +++ b/content/tutorials/portenta-h7/vs-ard-gs/content.md @@ -0,0 +1,382 @@ +--- +title: Getting Started With The Vision Shield Camera +coverImage: assets/vs_ard_gs_cover.svg +tags: [Getting Started, Camera, Processing, Serial] +description: This tutorial shows you how to capture frames from the Vision Shield Camera module and visualise the video output through a Processing sketch. +--- + +# Getting Started With The Vision Shield Camera +## Overview +This tutorial shows you how to capture frames from the Vision Shield Camera module and visualise the video output through a Processing sketch. + +### You Will Learn +- Capturing the frames from the camera. +- Sending the frames as a byte stream through a Serial connection. +- Visualising the frames in Processing. + +### Required Hardware and Software +- 1x [Portenta H7 board](https://store.arduino.cc/portenta-h7) +- 1x Portenta Vision Shield ( [LoRa](https://store.arduino.cc/portenta-vision-shield-lora) or [Ethernet](https://store.arduino.cc/portenta-vision-shield) ) +- 1x USB-C cable (either USB-A to USB-C or USB-C to USB-C) +- Arduino IDE 1.8.10+ +- Processing 3.5.4+ + +## Instructions +Accessing the Vision Shield's camera data is done with the help of both Arduino and the Processing IDE. The Arduino sketch handles the capture of image data by the on-board camera while the java applet created with Processing helps to visualise this data with the help of a serial connection. The following steps will run you through how to capture, package the data through the serial port and visualise the output in Processing. + +### 1. The Basic Setup +Connect the Vision Shield to your Portenta H7 as shown in the figure. The top and bottom high density connecters are connected to the corresponding ones on the underside of the H7 board. Plug in the H7 to your computer using the USB C cable. + +![Connecting the Vision Shield to Portenta](assets/vs_ard_gs_attach_boards.svg) + +Open the board manager in the Arduino IDE and install the latest version of the Portenta Core which is [v1.3.2](https://github.com/arduino/ArduinoCore-mbed/releases/tag/1.3.2) + +![Download the mbed core](assets/vs_ard_gs_core.png) + +### 2. Capturing the Frames + +Create a new Arduino sketch called `CameraCaptureRawBytes.ino`. + +To capture the frames you will need to use the functions contained in `camera.h` which comes with the Portenta core. This library contains all APIs related to frame capturing, motion detection and pattern recognition. Include the header file in your sketch. + +```cpp +#include "camera.h" +``` + +Next, let's intialise a camera object and a frame buffer of the size 320*240 (76'800 bytes). + +```cpp +CameraClass cam; +uint8_t fb[320*240]; +``` + +In the `setup()` function, let's start the Serial communication at `921600` baud rate and iniitialise the camera using `cam.begin()`. + +```cpp +void setup() { + Serial.begin(921600); + //Init the cam QVGA, 30FPS + cam.begin(CAMERA_R320x240, 30); +} +``` + +In the loop we need to capture each Frame and send it over a serial connection to the Processing sketch that will display the frames. We will use the `grab(uint8_t *buffer, uint32_t timeout=5000);` function to fetch the frame from the frame buffer and save it into our custom data buffer. + +```cpp +void loop() { + // put your main code here, to run repeatedly: + + // Wait until the receiver acknowledges + // that they are ready to receive new data + while(Serial.read() != 1){}; + + // Grab frame and write to serial + if (cam.grab(fb) == 0) { + Serial.write(fb, 320*240); + } + +} +``` + +### 3. Create the Processing Sketch +Open a new processing sketch file and name it `CameraCapture.pde`. + +![Create a processing sketch](assets/vs_ard_open_pde_sketch.png) + +Let's start by importing the libraries and initialising the variables you will need to process the captured data. To process the data sent by the Vision Shield you will need to import the following libraries: + +- `processing.serial.*` : a [Serial Library](https://processing.org/reference/libraries/serial/index.html) that is used to read and write data to external devices over the serial line. +- `java.nio.ByteBuffer` : a java class that provides access to operations on byte buffers + +```java +import processing.serial.*; +import java.nio.ByteBuffer; +``` + +Next we initialise the following variables to process the received pixels from the serial port. We set the dimensions, pixel count, and bytes required per frame. + +```java +// must match resolution used in the sketch +final int cameraWidth = 320; +final int cameraHeight = 240; +final int cameraBytesPerPixel = 1; +final int cameraPixelCount = cameraWidth * cameraHeight; +final int bytesPerFrame = cameraWidth * cameraHeight * cameraBytesPerPixel; +``` + +To recieve the frames you will need a Serial port, a PImage object and an array to store the pixel values of the frame. Add the following variables to the code. + +```java +Serial myPort; +PImage myImage; +byte[] frameBuffer = new byte[bytesPerFrame]; +int pixelPosition = 0; +int lastUpdate = 0; +boolean shouldRedraw = false; +``` + +Here we will establish a connection to the serial port and prepare the buffer to store the frame pixels. Additionally we send a byte to the Arduino sketch from Processing to let it know that it's ready to receive data. + +```java +void setup() { + size(640, 480); + + // if you know the serial port name + //myPort = new Serial(this, "COM5", 921600); // Windows + //myPort = new Serial(this, "/dev/ttyACM0", 921600); // Linux + myPort = new Serial(this, "/dev/cu.usbmodem14101", 921600); // Mac + + // Set the number of bytes to buffer + myPort.buffer(bytesPerFrame) + + // Create an image based on the camera's dimensions and format + myImage = createImage(cameraWidth, cameraHeight, ALPHA); + + // Let the Arduino sketch know we're ready to receive data + myPort.write(1); +} +``` + +The draw function checks if the connection is still alive and if there is any new data that can be drawn as an image. In that case the original image gets copied into a new image object so that it can be scaled up. + +```java +void draw() { + // Time out after 1.5 seconds and ask for new data + if(millis() - lastUpdate > 1500) { + println("Connection timed out."); + myPort.clear(); + myPort.write(1); + } + + if(shouldRedraw){ + PImage img = myImage.copy(); + img.resize(640, 480); + image(img, 0, 0); + shouldRedraw = false; + } +} +``` + +### 4. Visualing the Frames +For this step, you will use the `serialEvent()` callback function to update the `myImage` when a new data is received on the serial port. + +```java +void serialEvent(Serial myPort) { + lastUpdate = millis(); + + // read the received bytes + myPort.readBytes(frameBuffer); + + // Access raw bytes via byte buffer + ByteBuffer bb = ByteBuffer.wrap(frameBuffer); + + int i = 0; + + while (bb.hasRemaining()) { + // read 8-bit pixel + byte pixelValue = bb.get(); + + // set pixel color + myImage.pixels[i++] = color(Byte.toUnsignedInt(pixelValue)); + } + + myImage.updatePixels(); + + // Ensures that the new image data is drawn in the next draw loop + shouldRedraw = true; + + // Let the Arduino sketch know we received all pixels + // and are ready for the next frame + myPort.write(1); +} +``` + +The first thing we do inside this method is to update the timestamp for when the last data was read. This is to detect and recover from a connection timeout. Then read the bytes from the `frameBuffer` array which you can do with the help of the [`readBytes()`](https://processing.org/reference/libraries/serial/Serial_readBytes_.html) method that returns the number of bytes read. + +```java +lastUpdate = millis(); + +// read the received bytes +myPort.readBytes(frameBuffer); +``` + +Then the frame buffer is translated into a ByteBuffer that allows for easy and safe access to the underlying bytes without having to worry about the array indices. + +```cpp +// Access raw bytes via byte buffer +ByteBuffer bb = ByteBuffer.wrap(frameBuffer); +``` + +Next we read the frame buffer and convert the bytes into pixel color values. The image gets constructed by sequentially filling the pixels array of the image. The conversion of the raw data is done wih [`color()`](https://processing.org/reference/color_.html) and [`Byte.toUnsignedInt()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Byte.html). + +```java +int i = 0; + +while (bb.hasRemaining()) { + // read 8-bit pixel + byte pixelValue = bb.get(); + + // set pixel color + myImage.pixels[i++] = color(Byte.toUnsignedInt(pixelValue)); +} +``` + +Once all the pixels have been updated, you need to tell the sketch to redraw the image. Additionally we send an acknowledgement back to the arduino sketch to ask it to send the pixels for the next frame. We update the image with `updatePixels()` and write `1` to the serial port for the acknowledgement. + +``` cpp +myImage.updatePixels(); + +// Ensures that the new image data is drawn in the next draw loop +shouldRedraw = true; + +// Let the Arduino sketch know we received all pixels +// and are ready for the next frame +myPort.write(1); +``` + +### 5. Upload the Sketch + +Select the right serial port on your IDE and upload the Arduino sketch to your H7. After a successful upload, run the `CameraViewer.pde` sketch in Processing. You should be able to see the rendered camera output on the Processing canvas. + +![Camera output on Processing](assets/vs_ard_frames_captured.png) + +## Conclusion + +In this tutorial you learnt how to capture the frames from your Vision Shield's Camera and to visualise the frames throught Processing. This knowledge can be useful for you to build and experiment simple computer vision applications for both outdoor and indoor environments. + +### Complete Sketch +The `CaptureRawBytes.ino` Sketch. + +```cpp +#include "camera.h" + +CameraClass cam; +uint8_t fb[320*240]; + +void setup() { + Serial.begin(921600); + + // Init the cam QVGA, 30FPS + cam.begin(CAMERA_R320x240, 30); +} + +void loop() { + // put your main code here, to run repeatedly: + + // Wait until the receiver acknowledges + // that they are ready to receive new data + while(Serial.read() != 1){}; + + // Grab frame and write to serial + if (cam.grab(fb) == 0) { + Serial.write(fb, 320*240); + } + +} +``` + +The `CameraViewer.pde` Sketch. + +```java +/* + This sketch reads a raw Stream of RGB565 pixels + from the Serial port and displays the frame on + the window. + Use with the Examples -> CameraCaptureRawBytes Arduino sketch. + This example code is in the public domain. +*/ + +import processing.serial.*; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +Serial myPort; + +// must match resolution used in the sketch +final int cameraWidth = 320; +final int cameraHeight = 240; +final int cameraBytesPerPixel = 1; +final int cameraPixelCount = cameraWidth * cameraHeight; +final int bytesPerFrame = cameraPixelCount * cameraBytesPerPixel; + +PImage myImage; +byte[] frameBuffer = new byte[bytesPerFrame]; +int lastUpdate = 0; +boolean shouldRedraw = false; + +void setup() { + size(640, 480); + + // if you have only ONE serial port active + //myPort = new Serial(this, Serial.list()[0], 921600); // if you have only ONE serial port active + + // if you know the serial port name + //myPort = new Serial(this, "COM5", 921600); // Windows + //myPort = new Serial(this, "/dev/ttyACM0", 921600); // Linux + myPort = new Serial(this, "/dev/cu.usbmodem14401", 921600); // Mac + + // wait for full frame of bytes + myPort.buffer(bytesPerFrame); + + myImage = createImage(cameraWidth, cameraHeight, ALPHA); + + // Let the Arduino sketch know we're ready to receive data + myPort.write(1); +} + +void draw() { + // Time out after 1.5 seconds and ask for new data + if(millis() - lastUpdate > 1500) { + println("Connection timed out."); + myPort.clear(); + myPort.write(1); + } + + if(shouldRedraw){ + PImage img = myImage.copy(); + img.resize(640, 480); + image(img, 0, 0); + shouldRedraw = false; + } +} + +void serialEvent(Serial myPort) { + lastUpdate = millis(); + + // read the received bytes + myPort.readBytes(frameBuffer); + + // Access raw bytes via byte buffer + ByteBuffer bb = ByteBuffer.wrap(frameBuffer); + + /* + Ensure proper endianness of the data for > 8 bit values. + When using > 8bit values uncomment the following line and + adjust the translation to the pixel color. + */ + //bb.order(ByteOrder.BIG_ENDIAN); + + int i = 0; + + while (bb.hasRemaining()) { + // read 8-bit pixel + byte pixelValue = bb.get(); + + // set pixel color + myImage.pixels[i++] = color(Byte.toUnsignedInt(pixelValue)); + } + + myImage.updatePixels(); + + // Ensures that the new image data is drawn in the next draw loop + shouldRedraw = true; + + // Let the Arduino sketch know we received all pixels + // and are ready for the next frame + myPort.write(1); +} +``` + +**Authors:** Lenard George, Sebastian Romero +**Reviewed by:** Sebastian Romero [2021-04-26] +**Last revision:** Sebastian Romero [2021-04-26] diff --git a/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_add_app.png b/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_add_app.png index 6e321924..79407f45 100644 Binary files a/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_add_app.png and b/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_add_app.png differ diff --git a/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_app.png b/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_app.png index 8cb212ad..38edc6dd 100644 Binary files a/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_app.png and b/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_app.png differ diff --git a/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_app_param.png b/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_app_param.png index 9e735b2e..fdf258e3 100644 Binary files a/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_app_param.png and b/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_app_param.png differ diff --git a/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_click_register.png b/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_click_register.png index f773a3c2..d60b929e 100644 Binary files a/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_click_register.png and b/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_click_register.png differ diff --git a/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_device_overview.png b/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_device_overview.png index 34337e97..9da5d782 100644 Binary files a/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_device_overview.png and b/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_device_overview.png differ diff --git a/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_home.png b/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_home.png index 6689bb5d..671a6b49 100644 Binary files a/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_home.png and b/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_home.png differ diff --git a/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_new_app.png b/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_new_app.png deleted file mode 100644 index 74fb7a26..00000000 Binary files a/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_new_app.png and /dev/null differ diff --git a/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_register_device.png b/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_register_device.png deleted file mode 100644 index 25e506c4..00000000 Binary files a/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_register_device.png and /dev/null differ diff --git a/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_register_device_1.png b/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_register_device_1.png new file mode 100644 index 00000000..fa057cd7 Binary files /dev/null and b/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_register_device_1.png differ diff --git a/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_register_device_2.png b/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_register_device_2.png new file mode 100644 index 00000000..eb4fe571 Binary files /dev/null and b/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_register_device_2.png differ diff --git a/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_serial.png b/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_serial.png new file mode 100644 index 00000000..7a3f3491 Binary files /dev/null and b/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_serial.png differ diff --git a/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_standalone.png b/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_standalone.png new file mode 100644 index 00000000..f14271d2 Binary files /dev/null and b/content/tutorials/portenta-h7/vs-ard-ttn/assets/vs_ard_ttn_standalone.png differ diff --git a/content/tutorials/portenta-h7/vs-ard-ttn/content.md b/content/tutorials/portenta-h7/vs-ard-ttn/content.md index e8e730f4..69f97221 100644 --- a/content/tutorials/portenta-h7/vs-ard-ttn/content.md +++ b/content/tutorials/portenta-h7/vs-ard-ttn/content.md @@ -1,9 +1,16 @@ -# Connecting the Vision Shield to TTN using LoRa +--- +title: Connecting the Vision Shield to TTN using LoRa® +coverImage: assets/vs_ard_things_nw.svg +tags: [Things Network, LoRa, Vision Shield] +description: This tutorial explains how to connect your Portenta H7 to The Things Network (TTN) using the the Vision Shield's Lora Connectivity feature. +--- + +# Connecting the Vision Shield to TTN Using LoRa® ## Overview -This tutorial explains how to connect your Portenta H7 to The Things Network (TTN) using the the Vision Shield's LoRa Connectivity feature. A data communication channel will be enabled between the H7 and a TTN application that will be configured on your TTN console. +This tutorial explains how to connect your Portenta H7 to The Things Network (TTN) using the the Vision Shield's LoRa® Connectivity feature. A data communication channel will be enabled between the H7 and a TTN application that will be configured on your TTN console. -***In order to connect your Portenta to the TTN make sure you are within the range (max. 10 Km) from an available LoRa Gateway. Indoor gateways will have a much shorter range. It is recommended that you check LoRa Gateway availability on [The Things Network map](https://www.thethingsnetwork.org/map) before you try this tutorial.*** +***In order to connect your Portenta to the TTN make sure you are within the range (max. 10 Km) from an available LoRa® Gateway. Indoor gateways will have a much shorter range. It is recommended that you check LoRa® Gateway availability on [The Things Network map](https://www.thethingsnetwork.org/map) before you try this tutorial.*** ### You Will Learn @@ -14,62 +21,76 @@ This tutorial explains how to connect your Portenta H7 to The Things Network (TT ### Required Hardware and Software - [Portenta H7 board](https://store.arduino.cc/portenta-h7) -- [Portenta Vision Shield - LoRa](https://store.arduino.cc/portenta-vision-shield-lora) +- [Portenta Vision Shield - LoRa®](https://store.arduino.cc/portenta-vision-shield-lora) - [1x Dipole Pentaband antenna](https://store.arduino.cc/antenna) or a UFL Antenna of the H7 - Arduino [offline](https://www.arduino.cc/en/main/software) IDE or Arduino ([Web Editor](https://create.arduino.cc/) - USB C cable (either USB A to USB C or USB C to USB C) -- An [account](https://account.thethingsnetwork.org/users/login) with The Things Network +- An [account](https://console.cloud.thethings.network/) with The Things Network + +### Updating the LoRa® Module Firmware +To be able to use the LoRa® functionality, we need to first update the firmware on the LoRa® modem. This can be done through Arduino IDE by running a sketch included in the examples from the MKRWAN library. + +1. Connect the Portenta H7 and the Portenta Vision Shield to your computer and open the Arduino IDE. +2. Install/update the **MKRWAN** library from Arduino IDE menu **Tools > Manage Libraries**. Type "MKRWAN" to find the library and click 'Install' or 'Update' if necessary. This library provides all the APIs to communicate with LoRa® and LoRaWAN® networks. +3. Open the **MKRWANFWUpdate_standalone** sketch from the Arduino IDE menu: **File > Examples > MKRWAN**. + +4. Upload the sketch. + + +![Finding the sketch](assets/vs_ard_ttn_standalone.png) + +5. Open the serial monitor and wait for the update to be confirmed. + +![Serial Monitor](assets/vs_ard_ttn_serial.png) ## Connecting to the TTN -The Portenta Vision Shield - LoRa can be connected to the TTN and can transmit data to other devices connected to this network through a secure channel. This channel is nothing but an applicaiton on the TTN network dedicated for your board. In this tutorial, you will be guided through a step-by-step process of setting up your Portenta board and the Vision Shield Lora to communicate with a TTN application. As stated before, to be able to follow this guide, to be under coverage of one of the TTN gateways. You can check for [the coverage](https://www.thethingsnetwork.org/map) now if you have not done so yet. +The Portenta Vision Shield - LoRa® can be connected to the TTN and can transmit data to other devices connected to this network through a secure channel. This channel is nothing but an applicaiton on the TTN network dedicated for your board. In this tutorial, you will be guided through a step-by-step process of setting up your Portenta board and the Vision Shield LoRa® to communicate with a TTN application. As stated before, to be able to follow this guide, to be under coverage of one of the TTN gateways. You can check for [the coverage](https://www.thethingsnetwork.org/map) now if you have not done so yet. ### 1. Setting up the Environment -Start by pointing your browser to www.thethingsnetwork.org and use the Sign Up button to setup an account. Next, then fill all the required fields to complete a new registration (if you already have a TTN account, skip this step and continue by signing in). +Start by going [here](https://console.cloud.thethings.network/). First choose your region. Next, sign in with your The Things Network account. If you don't have an account, create a new one on the login page. Then fill all the required fields to complete a new registration. ![The Things Network homepage](assets/vs_ard_ttn_home.png) -### 2. Creating an app on TTN +### 2. Creating an App on TTN -Once you have created an account with TTN, you need to create a TTN [application](https://www.thethingsnetwork.org/docs/applications/). An application provides a way to aggregate data from different devices, and then use these data with other 3rd party integrations. Go to your [console](https://console.thethingsnetwork.org), and click on **Applications** +Once you have created an account with TTN, you need to create a TTN [application](https://www.thethingsnetwork.org/docs/applications/). An application provides a way to aggregate data from different devices, and then use these data with other 3rd party integrations. After signing in, click on **Create an application**, or **Go to applications** if you already have one created. ![Select Applications on the Console](assets/vs_ard_ttn_app.png) -Here you'll have a list of all your applications. Now create your first app by pressing the **add application** button. - -![Finding the add application button](assets/vs_ard_ttn_new_app.png) +Here you'll have a list of all your applications. Now create your first app by pressing the **Create an application** button. You have now to fill only the first two fields: -- The first one is the **ID** of your app: this must be lowercase and without spaces. -- The second one is a **Description** of your app, and there's no restrictions on formatting +- The first one is the **Owner** of your app, it will automatically have you as the owner. +- The second one is the **ID** of your app: this must be lowercase and without spaces. ![Adding a application](assets/vs_ard_ttn_app_param.png) -After completing these two fields, press on the "Add application" button located at the bottom right corner of the page. The dashboard will then show you an overview of the newly created app. +After completing these two fields, press the "Create application" button located at the bottom left corner of the page. The dashboard will then show you an overview of the newly created app. ![Adding the App Parameters](assets/vs_ard_ttn_add_app.png) Let's take a closer look at these sections: -- **Application Overview** and Application EUIS: in order to use this app, you'll need the Application ID and its EUIs. An EUI is a globally unique identifier for networks, gateways applications and devices. The EUIs are used to identify all parts of the LoRaWAN inside the backend server. -- **Devices**: here you can see and manage all the associated devices (e.g. your Portenta H7 with Vision Shield Lora, Arduino MKR WAN 1300 or MKR WAN 1310), or proceed with the registration of new one. +- **Application Overview**: in order to use this app, you'll need the Application ID and a device specific AppKey. An EUI is a globally unique identifier for networks, gateways applications and devices. The EUIs are used to identify all parts of the LoRaWAN inside the backend server. +- **End devices**: here you can see and manage all the associated devices (e.g. your Portenta H7 with Vision Shield LoRa, Arduino MKR WAN 1300 or MKR WAN 1310), or proceed with the registration of a new one. Registering a new device lets you generate an AppEUI and an AppKey. - **Collaborators**: here you can see and manage all the app collaborators. To integrate with other collaborative platforms or to manage access rights to the app with other TTN registered profiles. -- **Access keys**: it's the most sensible information. It is basically the key to gain access to your app, so keep it safe. +- **API keys**: here you can create an API key, it's the most sensible information. It is basically the key to gain access to your app, so keep it safe. ### 3. Configuring the Vision Shield -It's now time to connect your Portenta H7 and Lora Vision Shield to TTN. You'll need to upload code to the board, so as you probably already know, there are two options: +It's now time to connect your Portenta H7 and LoRa® Vision Shield to TTN. You'll need to upload code to the board, so as you probably already know, there are two options: - Use the [Arduino Web Editor](https://create.arduino.cc/editor) - Use the [Arduino IDE](https://www.arduino.cc/en/software), (this is the option this guide will follow) -Plug the Portenta Vision Shield - LoRa to the Portenta H7 and them to your PC through the USB port. Be sure to have selected the right board "Arduino Portenta H7 (M7 core)" and the right port. +Plug the Portenta Vision Shield - LoRa® to the Portenta H7 and them to your PC through the USB port. Be sure to have selected the right board "Arduino Portenta H7 (M7 core)" and the right port. ![Select port M7 Core](assets/vs_ard_select_port.png) -The LoRa module on the Vision Shield can be accessed by using the [MKRWAN library](https://github.com/arduino-libraries/MKRWAN)( if you can't find it in your examples list, you can go to **tools > library manager** and type "MKRWAN library" to install it). This library provides all the APIS to communicate with LoRa and LoRaWAN networks and can be Installed from the library Manager. The first code you need to upload and run is from the **MKRWAN** library, and its name is **FirstConfiguration**. +The LoRa® module on the Vision Shield can be accessed by using the [MKRWAN library](https://github.com/arduino-libraries/MKRWAN)( if you can't find it in your examples list, you can go to **tools > library manager** and type "MKRWAN library" to install it). This library provides all the APIS to communicate with LoRa® and LoRaWAN® networks and can be Installed from the library Manager. The first code you need to upload and run is from the **MKRWAN** library, and its name is **FirstConfiguration**. ![Upload code to IDE](assets/vs_ard_select_example.png) @@ -93,14 +114,19 @@ In order to select the way in which the board is going to connect with TTN (OTA ### 4. Registring the Portenta on TTN -Before your Portenta H7 can start communicating with the TTN you need to [register](https://www.thethingsnetwork.org/docs/devices/registration.html) the board with an application. Go back to the TTN portal and scroll to **Devices** section on your Application dashboard, then click **Register Device**. +Before your Portenta H7 can start communicating with the TTN you need to [register](https://www.thethingsnetwork.org/docs/devices/registration.html) the board with an application. Go back to the TTN portal and scroll to **End devices** section on your Application dashboard, then click **Add end device**. ![Registering a Device](assets/vs_ard_ttn_click_register.png) -On the registration page, fill in **Device ID** and **EUI**. -**Note**: The Device ID must be lowercase and without spaces. The **EUI** should be copied from the Serial Monitor. +On the registration page, first we have to fill in information about our board. Select brand Arduino SA, and Portenta Vision Shield LoRa as the model. Hardware and firmware versions will automatically be set to the newest ones. Then set your preferred region. + +![Entering the device EUI](assets/vs_ard_ttn_register_device_1.png) + +In the second step of registering the device, fill in **End device ID** and **DevEUI**. You can click the generate button next to the AppKey field to generate an app key for this device. Similarly, you can press the button next to the AppEUI field to make it all zeros, or enter your own AppEUI. + +**Note**: The Device ID must be lowercase and without spaces. The **DevEUI** should be copied from the Serial Monitor. -![Entering the device EUI](assets/vs_ard_ttn_register_device.png) +![Second step of registering device](assets/vs_ard_ttn_register_device_2.png) After pressing the Register button, your board will show up on the **Device Overview** page. You can now see all the information needed to complete the Arduino setup. @@ -110,7 +136,7 @@ After pressing the Register button, your board will show up on the **Device Over Once your board has been registered you can send information to TTN. Let's come back to the Serial Monitor and proceed. It will ask for: -- Activation mode (that, in this case, is OTAA as you can see in the screenshot above), +- Activation mode (that, in this case, is OTAA), - The Application EUI - The App Key. @@ -132,14 +158,14 @@ Message sent correctly! ## Conclusion -If you recieve this message, you have managed to configure the Portenta H7 and the Lora Vision Shield on the TTN. +If you recieve this message, you have managed to configure the Portenta H7 and the LoRa® Vision Shield on the TTN. We have retrieved the device EUI, used it to register the device in the TTN console, and programmed the board using the data provided by TTN. Now, we can send data over the LoRa® network which can be viewed from anywhere in the world (as long as we have an Internet connection and our device is in range from a TTN gateway). ### Next Steps - Try sending uplink and downlink messages between Portenta and your TTN application with **LoraSendAndReceive** sketch from the MKRWAN library. - Experiment your Vision Shield's capabilities with OpenMV and the examples from the dedicated library for Arduino. You can continue with [this tutorial](https://www.arduino.cc/pro/tutorials/portenta-h7/por-openmv-bt) from the Arduino Pro site. -- Combine LoRaWAN protocol with an OpenMV example to develop your own IoT application. Take advantage of the Vision Shield's camera to detect, filter, classify images, read QR codes or more. +- Combine LoRaWAN® protocol with an OpenMV example to develop your own IoT application. Take advantage of the Vision Shield's camera to detect, filter, classify images, read QR codes or more. ## Troubleshooting @@ -149,4 +175,4 @@ If we are within good range of a gateway, we should also try to move our device **Authors:** Lenard George, Ignacio Herrera **Reviewed by:** Jose Garcia, Linnea Åkerberg [2021-02-02] -**Last revision:** Sebastian Romero [2021-02-05] \ No newline at end of file +**Last revision:** Benjamin Dannegård [2021-05-21] diff --git a/content/tutorials/portenta-h7/vs-ard-ttn/metadata.json b/content/tutorials/portenta-h7/vs-ard-ttn/metadata.json deleted file mode 100644 index 8566ab34..00000000 --- a/content/tutorials/portenta-h7/vs-ard-ttn/metadata.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "id":"vs-ard-ttn", - "index": 6, - "title":"Connecting the Vision Shield to TTN using LoRa", - "slug":"vs-ard-ttn", - "authors":[ - "Ignacio Herrera", - "Lenard George" - ], - "coverImage":{ - "src":"assets/vs_ard_things_nw.svg?sanitize=true" - }, - "tags":[ - "Things Network", - "LoRa", - "Vision Shield" - ], - "abstract":"This tutorial explains how to connect your Portenta H7 to The Things Network (TTN) using the the Vision Shield's Lora Connectivity feature.", - "previousArticle":"", - "nextArticle":"" -} diff --git a/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_classes.png b/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_classes.png new file mode 100644 index 00000000..fdebe364 Binary files /dev/null and b/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_classes.png differ diff --git a/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_cover.svg b/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_cover.svg new file mode 100644 index 00000000..ffeadffe --- /dev/null +++ b/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_cover.svg @@ -0,0 +1,448 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_edge_impulse_build.png b/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_edge_impulse_build.png new file mode 100644 index 00000000..0442d09c Binary files /dev/null and b/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_edge_impulse_build.png differ diff --git a/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_edge_impulse_classification.png b/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_edge_impulse_classification.png new file mode 100644 index 00000000..f62df16b Binary files /dev/null and b/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_edge_impulse_classification.png differ diff --git a/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_edge_impulse_data.png b/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_edge_impulse_data.png new file mode 100644 index 00000000..e331a4cf Binary files /dev/null and b/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_edge_impulse_data.png differ diff --git a/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_edge_impulse_design.png b/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_edge_impulse_design.png new file mode 100644 index 00000000..7c04e786 Binary files /dev/null and b/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_edge_impulse_design.png differ diff --git a/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_edge_impulse_features.png b/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_edge_impulse_features.png new file mode 100644 index 00000000..615b41a1 Binary files /dev/null and b/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_edge_impulse_features.png differ diff --git a/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_edge_impulse_login.png b/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_edge_impulse_login.png new file mode 100644 index 00000000..7ce00259 Binary files /dev/null and b/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_edge_impulse_login.png differ diff --git a/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_edge_impulse_parameters.png b/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_edge_impulse_parameters.png new file mode 100644 index 00000000..d54877d3 Binary files /dev/null and b/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_edge_impulse_parameters.png differ diff --git a/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_edge_impulse_training.png b/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_edge_impulse_training.png new file mode 100644 index 00000000..3220a8a6 Binary files /dev/null and b/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_edge_impulse_training.png differ diff --git a/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_new_dataset.png b/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_new_dataset.png new file mode 100644 index 00000000..d3e8fc2c Binary files /dev/null and b/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_new_dataset.png differ diff --git a/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_supervised_learning.svg b/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_supervised_learning.svg new file mode 100644 index 00000000..c0b0919d --- /dev/null +++ b/content/tutorials/portenta-h7/vs-openmv-ml/assets/vs_openmv_ml_supervised_learning.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/content/tutorials/portenta-h7/vs-openmv-ml/content.md b/content/tutorials/portenta-h7/vs-openmv-ml/content.md new file mode 100644 index 00000000..e8165afc --- /dev/null +++ b/content/tutorials/portenta-h7/vs-openmv-ml/content.md @@ -0,0 +1,178 @@ +--- +title: Training a Custom Machine Learning Model for Portenta H7 +coverImage: assets/vs_openmv_ml_cover.svg +tags: [Machine Learning, Edge Impulse, TinyML, Tensorflow] +description: This tutorial teaches you how to train a custom machine learning model with Edge Impulse and to run it using the Portenta Vision Shield. +--- + +# Training a Custom Machine Learning Model for Portenta H7 +## Overview + +This tutorial teaches you how to train a custom machine learning model with Edge Impulse and to run it using the Portenta Vision Shield. The Machine Learning (ML) model will use the TensorFlow Lite format and the classification example will run on OpenMV. + +### You Will Learn + +- To create datasets to be used for classification +- How to train a ML model in Edge Impulse +- To use OpenMV to run a classification example + +### Required Hardware and Software + +- [Portenta H7 board](https://store.arduino.cc/portenta-h7) +- [Portenta Vision Shield - LoRa®](https://store.arduino.cc/portenta-vision-shield-lora) or [Portenta Vision Shield - Ethernet](https://store.arduino.cc/usa/portenta-vision-shield) +- USB-C cable (either USB-A to USB-C or USB-C to USB-C) +- An [Edge Impulse](https://studio.edgeimpulse.com/) account for training the ML model +- Fruits (or other objects) to create the classification model 🍏🍌🍐 + +## Machine Learning on the Edge + +Machine learning on powerful computers has been around for a while. On microcontrollers this is a rather new territory. Microcontrollers might not be able to run ML models to process high resolution images at high frame rates but there are some interesting aspects. On the one hand microcontrollers can run at very low power on batteries for a long time. You could even put the processor to sleep and only wake it up when the camera or one of the attached sensors registers activity. On the other hand ML models on a microcontroller can run without internet connection as they don't need to upload data to the cloud. This means that you can install distributed ML solutions in places where there is no Internet connection (Edge Computing). Additionally processing data locally means that the data stays on the device which ensures data privacy. + +## The Edge Impulse Platform + +Edge Impulse is a platform that simplifies the process of creating machine learning models by choosing reasonable defaults for the countless parameters you could set when creating a ML model. It provides a simple user interface that not only allows to train a ML model but also to inspect the data and test the model. + +## Training the ML Model + +To train a ML model to classify an image we need to feed it with image data of that object. During the training process the model will be trained using a concept called [supervised learning](https://en.wikipedia.org/wiki/Supervised_learning). This means that we train the model with known data and tell it while it's "practicing" its predictions if they are correct or not. This is similar to what happens when you tell a toddler who is pointing at a donkey saying "horse" and you tell them that it's actually a donkey. The next few times they see a donkey they may still get it wrong but over time under your supervision they will learn to correctly identify a donkey. Conceptually, that's also how our ML model learns. + +![For supervised learning objects are labeled beforehand with their names](assets/vs_openmv_ml_supervised_learning.svg) + +### 1. Creating a Data Set + +The first step is to create a representative dataset of the objects that the ML model is supposed to identify. The key is to have as much diversity in the models as possible. If we show it for example only one specific apple that has a certain size, shape and peel, then it won't be very good at recognising other apples that look different. This is referred to as a bias and should be avoided as much as possible. In addition you need to teach the model what an apple is not. For that purpose you feed it random image data of things that are not an apple. You could name that class of image data "unknown". If you don't have such a class and the model has only ever seen an apple, it won't know what to do if there is no apple in the image. + +Creating data sets in OpenMV is simple as there is a built-in function to create them. Before you proceed, connect your Portenta H7 board with the Vision Shield mounted. Click on the connect button in the OpenMV IDE. If you haven't set up your board for OpenMV please consult the [getting started tutorial](https://www.arduino.cc/pro/tutorials/portenta-h7/por-openmv-bt). +Create a new dataset by using the menu command **Tools->Dataset Editor->New Dataset** and name it `Dataset-Fruits`. + +![The Dataset Editor can be found in the Tools menu](assets/vs_openmv_ml_new_dataset.png) + +The next step is to create image classes. A class represents a unique type of object, in this case the type of fruit. +First, create a new image class and name it `apple` by clicking on "New Class Folder" in the toolbar. Now run the image capturing script that is already open by clicking the play button. Focus the apple with the camera and click on **Capture Data** to snap a picture of it. Capture it from different angles and with different backgrounds to make the recognition later on more robust. Repeat this for other fruits that you would like to classify (e.g. a pear and a banana). Add an `unknown` class and capture some images of different backgrounds that you would like to use during the classification later on. + +![The various image classes can be created directly in the dataset editor](assets/vs_openmv_ml_classes.png) + +You may have also noticed that there is a labels text file. This file is used to store a textual representation of the classes to later classify the objects and print the class names. The classes are added to that automatically. + +### 2. Uploading the Data to Edge Impulse +Now that all data is ready to be uploaded you need to create a new Edge Impulse project. If you haven't registered an Edge Impulse account yet, you may create one on [their website](https://studio.edgeimpulse.com/login). Log in to the Edge Impulse Studio and create a new project named `Fruit-Detector`. + +After that you can go back to the OpenMV IDE and select **Tools->Dataset Editor->Export->Log in to Edge Impulse Account and Upload to Project**. The OpenMV IDE will ask you for your Edge Impulse login credentials. Select the project that you just created and click OK. Leave the data set split setting at the default. This will keep 20% of the images aside for testing the model once it has been trained. That allows you to assess how well your model performs at detecting the objects with data that it hasn't seen yet. + +![You need to log in with your Edge Impulse account when uploading a dataset for the first time](assets/vs_openmv_ml_edge_impulse_login.png) + + +### 3. Acquire Data + +Open your project in the Edge Impulse studio and navigate to "Data Acquisition". You can see that the images have been uploaded and labeled according to the classes that you created. With this tool you can browse through the image samples and remove the ones which you don't deem valuable for the training (e.g. if one of the images is too blurry). + +![The Data Acquisition tool allows to inspect the uploaded assets](assets/vs_openmv_ml_edge_impulse_data.png) + +### 4. Create an Impulse + +If you're happy with the data samples you can move on to designing your impulse. An impulse is in a nutshell a recipe with which the model is being trained. It defines actions that are performed on your input data to make them better suited for machine learning and a learning block that defines the algorithm for the classification. In the menu navigate to "Create Impulse" under "Impulse Design" and add an **Image** processing block as well as a **Transfer Learning** learning block. +It's recommended to adjust the image size to 48x48 for improved performance. You can try with higher resolutions but you will notice that the frame rate during the classification will drop significantly. Click on Save Impulse to apply the adjusted settings. + +![An Impulse consists of the building blocks needed to train a ML model](assets/vs_openmv_ml_edge_impulse_design.png) + + +### 5. Generate Features + +In this step you will adjust the image settings and generate the features from the input data. Features are unique properties that will be used by the classification algorithm to detect the objects. A feature can be the round shape of an apple or the fact that an image of a banana has many bright pixels as bananas are mostly yellow. +In the menu navigate to "Image" under "Impulse Design". Set the color depth to "Grayscale" and save the parameters as the Portenta Vision Shield features a grayscale camera. + +![In the image inspection tool you can set the color depth according to the input data](assets/vs_openmv_ml_edge_impulse_parameters.png) + +Then click on "Generate Features". The analysis process will take a while to complete depending on the amount of images that you uploaded. When it's done you can inspect the results. On the right hand side you can see a visualisation of the features in a 3D space. You can see that apples (blue dots) and pears (green dots) are somewhat hard to tell apart due to their round shape and are therefore have some overlapping data points in that visualisation. A banana on the other hand is easier to distinguish as it looks quite different. + +![The feature explorer allows to visually inspect the clusters of images in regards to their properties](assets/vs_openmv_ml_edge_impulse_features.png) + +### 6. Train the Model + +Now that the features of your image data are ready to be used for the actual training you can navigate to "Transfer Learning" in the menu. In this example we leave the settings at their default value except of "Number of training cycles" which we increase to 60. This defines how many times the model is being trained. The model gets better with each cycle the same way you get better when learning how to ride a bike and you practice it the first couple of times. +Click on "Start Training" to train the machine learning model. A small amount of images, the **validation set**, are put aside before the training starts to validate the trained model. Not to be confused with the **test set** which can be used to evaluate the final model. Once the training finishes you will see some statistics on how well the model performed during validation. Ideally you get an accuracy of 100% for each object. If you get poor results you may have some images which are not representative of the objects you're trying to classify and should be removed from the data set. + +![The confusion matrix shows the accuracy of the ML model after the last training cycle](assets/vs_openmv_ml_edge_impulse_training.png) + +## Using the ML Model + +The ML model is trained and already optimised to be used with microcontrollers. This is done automatically in the background through quantisation. This is a process where the numbers in the machine learning models are constrained in their value range for improved performance while sacrificing a bit of accuracy. + +### Deploy + +Deploying the ML model to the Portenta H7 is very simple. The Edge Impulse Studio provides an export feature for OpenMV. Switch to the deployment section in the menu, select OpenMV under "Build firmware" and click "build". This will create an OpenMV compatible library and download it as a zip file. Unzip it and copy **trained.tflite** and **labels.txt** to Portenta's flash drive. + +![The Edge Impulse Studio has a built-in export function for OpenMV](assets/vs_openmv_ml_edge_impulse_build.png) + +### Run the Script + +The final step is to run the **ei_image_classification.py** script. Open it in the OpenMV. Replace the print statement in the innermost for loop with the following code: + +```py +confidence = predictions_list[i][1] +label = predictions_list[i][0] +print("%s = %f" % (label[2:], confidence)) + +if confidence > 0.9 and label != "unknown": + print("It's a ", label, "!") +``` + +This code will print a message saying e.g. "It's an apple!" in case the confidence is above 90%. In the following screenshot you can see that the apple was detected with a confidence level of 1.0 which corresponds to 100%. + +![In this example the apple is detected with a 100% certainty](assets/vs_openmv_ml_edge_impulse_classification.png) + +Try pointing the camera of the Portenta Vision Shield at any of your fruits or other objects that you used for the training and check if it can be recognised successfully. + +The complete script of the classification example is as follows: + +```py +import sensor, image, time, os, tf + +sensor.reset() # Reset and initialize the sensor. +sensor.set_pixformat(sensor.GRAYSCALE) # Set pixel format to RGB565 (or GRAYSCALE) +sensor.set_framesize(sensor.QVGA) # Set frame size to QVGA (320x240) +sensor.set_windowing((240, 240)) # Set 240x240 window. +sensor.skip_frames(time=2000) # Let the camera adjust. + +net = "trained.tflite" +labels = [line.rstrip('\n') for line in open("labels.txt")] + +clock = time.clock() +while(True): + clock.tick() + + img = sensor.snapshot() + + # default settings just do one detection... change them to search the image... + for obj in tf.classify(net, img, min_scale=1.0, scale_mul=0.8, x_overlap=0.5, y_overlap=0.5): + print("**********\nPredictions at [x=%d,y=%d,w=%d,h=%d]" % obj.rect()) + img.draw_rectangle(obj.rect()) + # This combines the labels and confidence values into a list of tuples + predictions_list = list(zip(labels, obj.output())) + + for i in range(len(predictions_list)): + confidence = predictions_list[i][1] + label = predictions_list[i][0] + print("%s = %f" % (label, confidence)) + + if confidence > 0.9 and label != "unknown": + print("It's a", label, "!") + + print(clock.fps(), "fps") +``` + +## Conclusion + +You have learned about classification as a machine learning concept which categorises a set of data into classes. You have also learned how supervised learning works and what quantisation of a model means. Furthermore you have learned to train a custom TFLite machine learning model and deploy it to your Portenta H7. + +### Next Steps + +One thing that we didn't have a look at is testing the final model. In Edge Impulse Studio there is a section for this called "Model Testing". It allows you to test the accuracy of your model with additional data that it hasn't seen yet. This gives you an opportunity to find out for which images the classification accuracy falls short. You can inspect individual images that are not correctly detected and decide whether additional data needs to be created to re-train the model for better accuracy on those images. You can also decide to move the images that were not recognised correctly to the training set and try again with that setup. + +## Troubleshooting + +If you see the error message `OSError: [Errno 2] ENOENT` in OpenMV when running the classification script, make sure that you copied both the ML model file and the labels file to Portenta H7's flash drive. + +**Authors:** Sebastian Romero +**Reviewed by:** Benjamin Dannegård [2021-03-31] +**Last revision:** Sebastian Romero [2021-03-31] diff --git a/content/tutorials/portenta-h7/vs-openmv-ttn/assets/por_ard_gs_upload_sketch.png b/content/tutorials/portenta-h7/vs-openmv-ttn/assets/por_ard_gs_upload_sketch.png new file mode 100644 index 00000000..605d9125 Binary files /dev/null and b/content/tutorials/portenta-h7/vs-openmv-ttn/assets/por_ard_gs_upload_sketch.png differ diff --git a/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_ard_ttn_add_app.png b/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_ard_ttn_add_app.png new file mode 100644 index 00000000..79407f45 Binary files /dev/null and b/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_ard_ttn_add_app.png differ diff --git a/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_ard_ttn_app.png b/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_ard_ttn_app.png new file mode 100644 index 00000000..38edc6dd Binary files /dev/null and b/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_ard_ttn_app.png differ diff --git a/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_ard_ttn_app_param.png b/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_ard_ttn_app_param.png new file mode 100644 index 00000000..fdf258e3 Binary files /dev/null and b/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_ard_ttn_app_param.png differ diff --git a/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_ard_ttn_click_register.png b/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_ard_ttn_click_register.png new file mode 100644 index 00000000..d60b929e Binary files /dev/null and b/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_ard_ttn_click_register.png differ diff --git a/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_ard_ttn_device_overview.png b/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_ard_ttn_device_overview.png new file mode 100644 index 00000000..9da5d782 Binary files /dev/null and b/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_ard_ttn_device_overview.png differ diff --git a/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_ard_ttn_home.png b/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_ard_ttn_home.png new file mode 100644 index 00000000..671a6b49 Binary files /dev/null and b/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_ard_ttn_home.png differ diff --git a/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_ard_ttn_register_device_1.png b/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_ard_ttn_register_device_1.png new file mode 100644 index 00000000..fa057cd7 Binary files /dev/null and b/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_ard_ttn_register_device_1.png differ diff --git a/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_ard_ttn_register_device_2.png b/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_ard_ttn_register_device_2.png new file mode 100644 index 00000000..eb4fe571 Binary files /dev/null and b/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_ard_ttn_register_device_2.png differ diff --git a/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_mp_select_example.png b/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_mp_select_example.png new file mode 100644 index 00000000..f14271d2 Binary files /dev/null and b/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_mp_select_example.png differ diff --git a/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_mp_ttn_cover.svg b/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_mp_ttn_cover.svg new file mode 100644 index 00000000..5303c0a0 --- /dev/null +++ b/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_mp_ttn_cover.svg @@ -0,0 +1,1289 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_mp_ttn_serialmonitor.png b/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_mp_ttn_serialmonitor.png new file mode 100644 index 00000000..7a3f3491 Binary files /dev/null and b/content/tutorials/portenta-h7/vs-openmv-ttn/assets/vs_mp_ttn_serialmonitor.png differ diff --git a/content/tutorials/portenta-h7/vs-openmv-ttn/content.md b/content/tutorials/portenta-h7/vs-openmv-ttn/content.md new file mode 100644 index 00000000..bc0c0630 --- /dev/null +++ b/content/tutorials/portenta-h7/vs-openmv-ttn/content.md @@ -0,0 +1,241 @@ +--- +title: Connecting to The Things Network Using OpenMV +coverImage: assets/vs_mp_ttn_cover.svg +tags: [Getting Started, OpenMV, IDE, Setup, TTN, LoRa] +description: This tutorial explains how to connect your Portenta H7 to The Things Network (TTN) using the Vision Shield's LoRa Connectivity feature. +--- + +# Connecting to The Things Network Using OpenMV +## Overview + +This tutorial explains how to connect your Portenta H7 to The Things Network (TTN) using the Vision Shield's LoRa Connectivity feature. A data communication channel will be enabled between the H7 and a TTN application that will be configured on your TTN console. + +***In order to connect your Portenta to the TTN make sure you are within the range (max. 10 Km) from an available LoRa Gateway. Indoor gateways will have a much shorter range. It is recommended that you check LoRa Gateway availability on [The Things Network map](https://www.thethingsnetwork.org/map) before you try this tutorial.*** + +### You Will Learn + +- About LoRaWAN® and The Things Network, +- About creating a TTN application, +- How to establish a connection between the H7 and the TTN, + +### Required Hardware and Software + +- 1 x [Portenta H7 board](https://store.arduino.cc/portenta-h7) +- 1 x [Portenta Vision Shield - LoRa](https://store.arduino.cc/portenta-vision-shield-lora) +- [1x Dipole Pentaband antenna](https://store.arduino.cc/antenna) or a UFL Antenna of the H7 +- [OpenMV IDE](https://openmv.io/pages/download) +- Arduino IDE 1.8.10+ or Arduino Pro IDE 0.0.4+ or Arduino CLI 0.13.0+ +- 1 x USB-C cable (either USB-A to USB-C or USB-C to USB-C) +- An [account on The Things Network](https://console.cloud.thethings.network/) + +## Instructions + +The Portenta Vision Shield - LoRa can be connected to the TTN and can transmit data to other devices connected to this network through a secure channel. This channel is nothing but an application on the TTN network dedicated for your board. In this tutorial, you will be guided through a step-by-step process of setting up your Portenta board and the Vision Shield LoRa to communicate with a TTN application using OpenMV and MicroPython. As stated before, to be able to follow this guide, to be under coverage of one of the TTN gateways. You can check for [the coverage](https://www.thethingsnetwork.org/map) now if you have not done so yet. + +### 1. Setting up the Environment + +Start by going [here](https://console.cloud.thethings.network/). First choose your region. Next, sign in with your The Things Network account, or create a new one on the login page. + +![The Things Network homepage](assets/vs_ard_ttn_home.png) + +### 2. Creating an App on TTN + +Once you have created an account with TTN, you need to create a TTN [application](https://www.thethingsnetwork.org/docs/applications/). An application provides a way to aggregate data from different devices, and then use these data with other 3rd party integrations. After signing in, click on **Create an application**, or **Go to applications** if you already have one created. + +![Select Create an application](assets/vs_ard_ttn_app.png) + +Here you'll have a list of all your applications. Now create your first app by pressing the **Create an application** button. + +You have now to fill only the first two fields: + +- The first one is the **Owner** of your app, it will automatically have you as the owner. +- The second one is the **ID** of your app: this must be lowercase and without spaces. + +![Adding a application](assets/vs_ard_ttn_app_param.png) + +After completing these two fields, press the "Create application" button located at the bottom left corner of the page. The dashboard will then show you an overview of the newly created app. + +![Adding the App Parameters](assets/vs_ard_ttn_add_app.png) + +Let's take a closer look at these sections: + +- **Application Overview**: in order to use this app, you'll need the Application ID and a device specific AppKey. An EUI is a globally unique identifier for networks, gateways applications and devices. The EUIs are used to identify all parts of the LoRaWAN inside the backend server. +- **End devices**: here you can see and manage all the associated devices (e.g. your Portenta H7 with Vision Shield LoRa, Arduino MKR WAN 1300 or MKR WAN 1310), or proceed with the registration of a new one. Registering a new device lets you generate an AppEUI and an AppKey. +- **Collaborators**: here you can see and manage all the app collaborators. To integrate with other collaborative platforms or to manage access rights to the app with other TTN registered profiles. +- **API keys**: here you can create an API key, it's the most sensible information. It is basically the key to gain access to your app, so keep it safe. + +### 3. Updating the Modems Firmware + +To be able to use the LoRa functionality, we need to first update the modems firmware through the Arduino IDE. Connect the Portenta and Vision shield to your computer and open the Arduino IDE. The LoRa module on the Vision Shield can be accessed by using the [MKRWAN library](https://github.com/arduino-libraries/MKRWAN)(if you can't find it in your examples list, you can go to **tools > library manager** and type "MKRWAN library" to install it). This library provides all the APIS to communicate with LoRa and LoRaWAN networks and can be installed from the library manager. Select the **Portenta H7 (M7 core)** board in the Arduino IDE, like shown below. + +![Select the Arduino Portenta H7 (M7 core) in the board selector.](assets/por_ard_gs_upload_sketch.png) + +The code you need to upload and run is from the **MKRWAN** library, and its name is **MKRWANFWUpdate_standalone**. With the Portenta M7 selected, upload the **MKRWANFWUpdate_standalone** sketch. + +![Upload code to IDE](assets/vs_mp_select_example.png) + +After uploading the sketch, open the serial monitor to confirm that the firmware has been updated. If the upload was successful it will print the progress in the serial monitor. + +![Arduino IDE serial monitor after firmware update](assets/vs_mp_ttn_serialmonitor.png) + +If it all went correctly, you should see the same text in your serial monitor as on the image above. + +### 4. Configuring the Vision Shield + +It's now time to connect your Portenta H7 and LoRa Vision Shield to TTN. You'll need to upload code to the board using [OpenMV](https://openmv.io/pages/download) + +Plug the Portenta Vision Shield - LoRa to the Portenta H7 and them to your PC through the USB port. If the Portenta board does not show up on OpenMV, try double-pressing the reset button on the Portenta. And now update to the latest firmware in OpenMV. + +The only line you may need to change before uploading the code is the one that sets the frequency. Set the frequency code according to your country if needed. You can find more information about frequency by country at [this TTN link](https://www.thethingsnetwork.org/docs/lorawan/frequency-plans.html). + +***Consider that in Australia the boards connect correctly to TTN gateways on AS923 frequencies; AU915 frequencies requires the selection of sub band 2 which is not yet implemented in the firmware.*** + +```py +// change this to your regional band (eg. US915, AS923, ...) +lora = Lora(band=BAND_EU868, poll_ms=60000, debug=False) +``` + +The `lora.join_OTAA()` or `lora.join_ABP()` functions connect your vision shield to the things network (TTN), using either Over-The-Air-Activation (OTAA) or Activating-By-Personalization (ABP) protocols. We just need to enter our `appEui` and `appKey`. The timeout decides how long the board will try and connect before stopping. + +```py +try: + lora.join_OTAA(appEui, appKey, timeout=20000) + # Or ABP: + #lora.join_ABP(devAddr, nwkSKey, appSKey, timeout=5000) +``` + +We can then send data to our TTN application with `lora.send_data()`, in here we can decide what data we want to send to our TTN application. + +```py +try: + if lora.send_data("HeLoRA world!", True): + print("Message confirmed.") + else: + print("Message wasn't confirmed") +``` + +Now we need to read the downlink message. Using the `lora.available()` function we check if there is data received on the board. If there is data on the board that has been received, we can use the `lora.receive_data()` function to take that data and put it into a local variable. Making it easier to print in the OpenMV IDE serial terminal. Using `lora.poll()` we can make sure that the LoRa module is ready before we run the loop again. + +```py +# Read downlink messages +while (True): + if (lora.available()): + data = lora.receive_data() + if data: + print("Port: " + data["port"]) + print("Data: " + data["data"]) + lora.poll() + sleep_ms(1000) +``` + +**Hint: The Complete Sketch can be found in the Conclusions** + +Then, once the upload is completed open the Serial Terminal where you can now see firmware info, device EUI, data rate and join status. + +In order to select the way in which the board is going to connect with TTN (OTAA or ABP) we need to configure it on the TTN portal. We will see which option we should select in the following steps. + +### 5. Registering the Portenta on TTN + +Before your Portenta H7 can start communicating with the TTN you need to [register](https://www.thethingsnetwork.org/docs/devices/registration.html) the board with an application. Go back to the TTN portal and scroll to **End devices** section on your Application dashboard, then click **Add end device**. + +![Registering a Device](assets/vs_ard_ttn_click_register.png) + +On the registration page, first we have to fill in information about our board. Select brand Arduino SA, and Portenta Vision Shield LoRa as the model. Hardware and firmware versions will automatically be set the newest ones. Then set your preferred region. + +![First step of registering device](assets/vs_ard_ttn_register_device_1.png) + +In the second step of registering the device, fill in **End device ID** and **EUI**. You can click the generate button next to the AppKey field to generate an app key for this device. Similarly, you can press the button next to the AppEUI field to make it all zeros, or enter your own AppEUI. + +**Note**: The Device ID must be lowercase and without spaces. The **DevEUI** can be copied from the terminal in OpenMV. You can run the following script to obtain it. + +```py +from lora import * +print("Device EUI:", Lora().get_device_eui()) +``` + +![Second step of registering device](assets/vs_ard_ttn_register_device_2.png) + +After pressing the Register button, your board will show up on the **Device Overview** page. You can now see all the information needed to complete the Arduino setup. + +![The Things Network Device Overview](assets/vs_ard_ttn_device_overview.png) + +### 6. Connecting to TTN + +Once your board has been registered you can send information to TTN. Let's go back to the sketch to fill in the appEui and appKey. The sketch we use here will use OTA connection. + +You can read more into OTA vs ABP activation mode at [this link](https://www.thethingsnetwork.org/docs/devices/registration.html) + +Once your board has been registered you can send information to TTN. Let's proceed in OpenMV. In the sketch the application EUI and the app key needs to be filled in. Find the EUI and the App key from TTN **Device Overview** page. + +If this process is done successfully, you will see this message: + +```text +Message confirmed. +``` + +## Conclusion +If you receive this message, you have managed to configure the Portenta H7 and the LoRa Vision Shield on the TTN. We have retrieved the device EUI, used it to register the device in the TTN console, and programmed the board using the data provided by TTN. Now, we can send data over the LoRa® network which can be viewed from anywhere in the world (as long as we have an Internet connection and our device is in range from a TTN gateway). + +### Complete Sketch + +```py +from lora import * + +lora = Lora(band=BAND_EU868, poll_ms=60000, debug=False) + +print("Firmware:", lora.get_fw_version()) +print("Device EUI:", lora.get_device_eui()) +print("Data Rate:", lora.get_datarate()) +print("Join Status:", lora.get_join_status()) + +appEui = "" # Add your App EUI here +appKey = "" # Add your App Key here + +try: + lora.join_OTAA(appEui, appKey, timeout=20000) + # Or ABP: + #lora.join_ABP(devAddr, nwkSKey, appSKey, timeout=5000) +# You can catch individual errors like timeout, rx etc... +except LoraErrorTimeout as e: + print("Something went wrong; are you indoor? Move near a window and retry") + print("ErrorTimeout:", e) +except LoraErrorParam as e: + print("ErrorParam:", e) + +print("Connected.") +lora.set_port(3) + +try: + if lora.send_data("HeLoRA world!", True): + print("Message confirmed.") + else: + print("Message wasn't confirmed") + +except LoraErrorTimeout as e: + print("ErrorTimeout:", e) + +# Read downlink messages +while (True): + if (lora.available()): + data = lora.receive_data() + if data: + print("Port: " + data["port"]) + print("Data: " + data["data"]) + lora.poll() + sleep_ms(1000) +``` + +### Next Steps + +- Experiment your Vision Shield's capabilities with OpenMV and the examples from the dedicated library for Arduino. You can continue with [this tutorial](https://www.arduino.cc/pro/tutorials/portenta-h7/por-openmv-bt) from the Arduino Pro site. +- Combine LoRaWAN protocol with an OpenMV example to develop your own IoT application. Take advantage of the Vision Shield's camera to detect, filter, classify images, read QR codes or more. + +## Troubleshooting + +The most common issue is that the device cannot connect to a TTN gateway. Again, it is a good idea to check if we have coverage in the area we are conducting this tutorial, by checking out [this map](https://www.thethingsnetwork.org/map). + +If we are within good range of a gateway, we should also try to move our device and antenna to a window, and even hold it out the window and move it around. This has proven successful on numerous accounts, as the signal can travel less obstructed. + +**Authors:** Lenard George, Ignacio Herrera, Benjamin Dannegård +**Reviewed by:** Lenard George [2021-05-07] +**Last revision:** Benjamin Dannegård [2021-05-07] diff --git a/pull_request_template.md b/pull_request_template.md index 219e7a64..92ae8371 100644 --- a/pull_request_template.md +++ b/pull_request_template.md @@ -2,7 +2,5 @@ For tutorials: - [ ] I double checked the wiring in the schematics/images - [ ] I exported the cover image without background - [ ] I used SVG format for pure vector images -- [ ] I updated the metadata.json file of the tutorial -- [ ] I updated the metadata.json file of the previous tutorial to contain the "next" link +- [ ] I updated the metadata of the tutorial - [ ] I did a spell checking before requesting a review -- [ ] I used title case for titles diff --git a/tests/matcher.js b/scripts/lib/file-helper.js similarity index 72% rename from tests/matcher.js rename to scripts/lib/file-helper.js index 8f17d295..17429993 100644 --- a/tests/matcher.js +++ b/scripts/lib/file-helper.js @@ -1,30 +1,31 @@ const path = require('path'); const fs = require('fs'); +const matcher = require('./matcher'); -function matchAny(text, patterns, callback = null){ - let result = false; - - for(let pattern of patterns){ - if(text.indexOf(pattern) != -1){ - if(callback) callback(pattern); - result = true; - } +function getSubdirectories(path, excludePatterns = []){ + if (!fs.existsSync(path)) { + console.log("❌ Directory doesn't exist:", path); + return; } - return result; -} -function matchAll(text, patterns, callback = null){ - let result = true; + var files = fs.readdirSync(path); + let directories = []; + files.forEach(file => { + var fullPath = path + file; + + if (matcher.matchAny(fullPath, excludePatterns)) { + return; + } - for(let pattern of patterns){ - if(text.indexOf(pattern) == -1){ - if(callback) callback(pattern); - result = false; + var stat = fs.lstatSync(fullPath); + if (stat.isDirectory()) { + directories.push(fullPath); } - } - return result; + }) + return directories; } + /** * * @param {The directory from which to start a recursive search} startPath @@ -33,7 +34,7 @@ function matchAll(text, patterns, callback = null){ * @param {The matching files as recursion parameter} matchingFiles */ function findAllFiles(startPath, searchPattern, excludePatterns = [], matchingFiles = []) { - if(matchAny(startPath, excludePatterns)){ + if(matcher.matchAny(startPath, excludePatterns)){ // console.log("Excluding directory " + startPath); return matchingFiles; } @@ -51,7 +52,7 @@ function findAllFiles(startPath, searchPattern, excludePatterns = [], matchingFi var stat = fs.lstatSync(filename); if (stat.isDirectory()) { findAllFiles(filename, searchPattern, excludePatterns, matchingFiles); - } else if (!matchAny(filename, excludePatterns)) { + } else if (!matcher.matchAny(filename, excludePatterns)) { if(!searchPattern) { matchingFiles.push(filename); continue; @@ -69,27 +70,16 @@ function findAllFiles(startPath, searchPattern, excludePatterns = [], matchingFi return matchingFiles; }; -function getSubdirectories(path, excludePatterns = []){ - if (!fs.existsSync(path)) { - console.log("❌ Directory doesn't exist:", path); - return; +function createDirectoryIfNecessary(path){ + if(!fs.existsSync(path)){ + fs.mkdirSync(path, { recursive: true }); } +} - var files = fs.readdirSync(path); - let directories = []; - files.forEach(file => { - var fullPath = path + file; - - if (matchAny(fullPath, excludePatterns)) { - return; - } - - var stat = fs.lstatSync(fullPath); - if (stat.isDirectory()) { - directories.push(fullPath); - } - }) - return directories; +function getLineNumberFromIndex(index, haystack){ + const tempString = haystack.substring(0, index); + const lineNumber = tempString.split('\n').length; + return lineNumber; } -module.exports = { findAllFiles, matchAll, matchAny, getSubdirectories}; \ No newline at end of file +module.exports = { findAllFiles, getSubdirectories, createDirectoryIfNecessary, getLineNumberFromIndex}; diff --git a/scripts/lib/matcher.js b/scripts/lib/matcher.js new file mode 100644 index 00000000..49ab7580 --- /dev/null +++ b/scripts/lib/matcher.js @@ -0,0 +1,25 @@ +function matchAny(text, patterns, callback = null){ + let result = false; + + for(let pattern of patterns){ + if(text.indexOf(pattern) != -1){ + if(callback) callback(pattern); + result = true; + } + } + return result; +} + +function matchAll(text, patterns, callback = null){ + let result = true; + + for(let pattern of patterns){ + if(text.indexOf(pattern) == -1){ + if(callback) callback(pattern); + result = false; + } + } + return result; +} + +module.exports = { matchAll, matchAny}; \ No newline at end of file diff --git a/scripts/validation/config/config-tutorials.yml b/scripts/validation/config/config-tutorials.yml new file mode 100644 index 00000000..9acb26f0 --- /dev/null +++ b/scripts/validation/config/config-tutorials.yml @@ -0,0 +1,20 @@ +--- +headingMaxLength: 60 +excludePatterns: +- ".git" +- "/template" + +basePath: content/tutorials/portenta-h7/ +allowedSyntaxSpecifiers: +- cpp +- py +- text +- java + +allowNestedLists: false +metadataSchema: scripts/validation/config/metadata-schema.json +checkForBrokenLinks: true +brokenLinkExcludePatterns: +- assets/ + +verboseOutput: true diff --git a/scripts/validation/config/metadata-schema.json b/scripts/validation/config/metadata-schema.json new file mode 100644 index 00000000..154636d1 --- /dev/null +++ b/scripts/validation/config/metadata-schema.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "beta": { + "type": "boolean" + }, + "title": { + "type": "string" + }, + "coverImage": { + "type": "string" + }, + "tags": { + "type": "array", + "items": [ + { + "type": "string" + } + ] + }, + "description": { + "type": "string" + } + }, + "required": [ + "title", + "coverImage", + "tags", + "description" + ] + } + \ No newline at end of file diff --git a/scripts/validation/config/rules-spelling.yml b/scripts/validation/config/rules-spelling.yml new file mode 100644 index 00000000..550e53db --- /dev/null +++ b/scripts/validation/config/rules-spelling.yml @@ -0,0 +1,14 @@ +--- +- regex: "\\d\\s?[Mm]ega\\s[Bb]yte|\\d\\s?Mega\\s?[Bb]yte|\\d\\s?mb[\\s\\.]|[Mm][Bb]ytes" + shouldMatch: false + type: warning + format: markdown + errorMessage: Megabytes should be either written as 'megabytes' or abbreviated as + 'MB' + +# Excludes Wi-Fi in URLs (prepended by dash or slash) and WiFi.XY and MKR WiFi +- regex: "(? image.attributes.src.split("?")[0]); + return images.map(image => image.attributes.src); } get linkPaths(){ @@ -67,24 +78,24 @@ var Tutorial = class Tutorial { } get assets(){ - let files = matcher.findAllFiles(this.basePath + "/assets/", null, [".DS_Store"]); - return files.map(file => file.split("?")[0]); + let files = fileHelper.findAllFiles(this.basePath + "/assets/", null, [".DS_Store"]); + return files ? files.map(file => file.split("?")[0]) : []; } get metadata(){ - try { - const metadataPath = this.basePath + "/metadata.json"; - let rawData = fs.readFileSync(metadataPath); - return JSON.parse(rawData); - } catch(error){ - console.log("❌ Parse error in " + metadataPath); + try { + let rawData = fs.readFileSync(this.contentFilePath).toString(); + const content = fm(rawData); + return content.attributes; + } catch (error) { + console.log("Error occurred while parsing " + this.contentFilePath); console.log(error); return null; } } get svgAssets(){ - return matcher.findAllFiles(this.basePath + "/assets/", '.svg'); + return fileHelper.findAllFiles(this.basePath + "/assets/", '.svg'); } } diff --git a/scripts/validation/domain/validation-error.js b/scripts/validation/domain/validation-error.js new file mode 100644 index 00000000..1d4684a7 --- /dev/null +++ b/scripts/validation/domain/validation-error.js @@ -0,0 +1,10 @@ +var ValidationError = class ValidationError { + constructor(message, file, type = "error", lineNumber = undefined){ + this.message = message; + this.file = file; + this.type = type; + this.lineNumber = lineNumber; + } +} + +module.exports = { ValidationError } \ No newline at end of file diff --git a/scripts/validation/domain/validator.js b/scripts/validation/domain/validator.js new file mode 100644 index 00000000..cc0a6f66 --- /dev/null +++ b/scripts/validation/domain/validator.js @@ -0,0 +1,30 @@ +const Tutorial = require('./tutorial').Tutorial; + +var Validator = class Validator { + validatonCallbacks = []; + + constructor(tutorialPaths){ + this.tutorials = tutorialPaths.map(tutorialPath => new Tutorial(tutorialPath) ); + } + + addValidation(func) { + this.validatonCallbacks.push(func); + } + + async validate(){ + return new Promise(resolve => { + + let promises = []; + + for(const validation of this.validatonCallbacks){ + promises.push(validation(this.tutorials)); + } + Promise.all(promises).then(results => { + resolve(results.flat(1)); + }); + + }); + } +} + +module.exports = { Validator } \ No newline at end of file diff --git a/scripts/validation/package-lock.json b/scripts/validation/package-lock.json new file mode 100644 index 00000000..12051ca4 --- /dev/null +++ b/scripts/validation/package-lock.json @@ -0,0 +1,1157 @@ +{ + "name": "validate", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "validate", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "html-entities": "^2.1.0", + "jsonschema": "^1.2.6", + "marked": "^2.0.0", + "node-html-parser": "^2.1.0", + "path": "^0.12.7", + "title-case": "^3.0.3" + + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.1.0.tgz", + "integrity": "sha512-u+OHVGMH5P1HlaTFp3M4HolRnWepgx5rAnYBo+7/TrBZahuJjgQ4TMv2GjQ4IouGDzkgXYeOI/NQuF95VOUOsQ==" + }, + + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + + "node_modules/is-absolute-url": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", + "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-relative-url": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-relative-url/-/is-relative-url-3.0.0.tgz", + "integrity": "sha512-U1iSYRlY2GIMGuZx7gezlB5dp1Kheaym7zKzO1PV06mOihiWTXejLwm4poEJysPyXF+HtK/BEd0DVlcCh30pEA==", + "dev": true, + "dependencies": { + "is-absolute-url": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "node_modules/isemail": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz", + "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==", + "dev": true, + "dependencies": { + "punycode": "2.x.x" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "node_modules/json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + + "node_modules/jsonschema": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.2.6.tgz", + "integrity": "sha512-SqhURKZG07JyKKeo/ir24QnS4/BV7a6gQy93bUSe4lUdNp0QNpIz2c9elWJQ9dpc5cQYY6cvCzgRwy0MQCLyqA==", + "engines": { + "node": "*" + } + }, + + "node_modules/jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "node_modules/link-check": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/link-check/-/link-check-4.5.4.tgz", + "integrity": "sha512-VdjiYrIBNHtqH7NEvIlF/4i0V9xQWkoBry+65DtmmyKyD5qBZ2U9fCJYx75SI5Ms4ILJzGlNNojPKbPMpg5Spg==", + "dev": true, + "dependencies": { + "is-relative-url": "^3.0.0", + "isemail": "^3.2.0", + "ms": "^2.1.2", + "request": "^2.88.2" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/markdown-link-check": { + "version": "3.8.6", + "resolved": "https://registry.npmjs.org/markdown-link-check/-/markdown-link-check-3.8.6.tgz", + "integrity": "sha512-GWzd54hNol2Zwf6fFEhUYqi9wbib1BMdcphvGCZqhinaAKhiILDaQkX7sn2u9GMoItmZ1jQK0f8wULOK/M+M4w==", + "dev": true, + "dependencies": { + "async": "^3.2.0", + "chalk": "^4.1.0", + "commander": "^6.2.0", + "link-check": "^4.5.4", + "lodash": "^4.17.20", + "markdown-link-extractor": "^1.2.6", + "progress": "^2.0.3", + "request": "^2.88.2" + }, + "bin": { + "markdown-link-check": "markdown-link-check" + } + }, + "node_modules/markdown-link-extractor": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/markdown-link-extractor/-/markdown-link-extractor-1.2.6.tgz", + "integrity": "sha512-WDiwWTzR/zk0n0As7q1KCB1Jd/T7nJ7IEr6E1QKZR1Agd/xRmB0FjM2IrtC7IZ1ZwxflBE0aLe4pkX8d+rzV8w==", + "dev": true, + "dependencies": { + "marked": "^1.1.1" + } + }, + "node_modules/markdown-link-extractor/node_modules/marked": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/marked/-/marked-1.2.9.tgz", + "integrity": "sha512-H8lIX2SvyitGX+TRdtS06m1jHMijKN/XjfH6Ooii9fvxMlh8QdqBfBDkGUpMWH2kQNrtixjzYUa3SH8ROTgRRw==", + "dev": true, + "bin": { + "marked": "bin/marked" + }, + "engines": { + "node": ">= 8.16.2" + } + }, + + "node_modules/marked": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-2.0.0.tgz", + "integrity": "sha512-NqRSh2+LlN2NInpqTQnS614Y/3NkVMFFU6sJlRFEpxJ/LHuK/qJECH7/fXZjk4VZstPW/Pevjil/VtSONsLc7Q==", + "bin": { + "marked": "bin/marked" + }, + "engines": { + "node": ">= 8.16.2" + } + }, + + "node_modules/mime-db": { + "version": "1.46.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.46.0.tgz", + "integrity": "sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.29", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.29.tgz", + "integrity": "sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ==", + "dev": true, + "dependencies": { + "mime-db": "1.46.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + + "node_modules/node-html-parser": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-2.1.0.tgz", + "integrity": "sha512-kbCNfqjrwHAbG+mevL8aqjwVtF0Qv66XurWHoGLOc5G9rPR1L3k602jfeczAUUBldLNnCrdsDmO5G5nqAoMW+g==", + "dependencies": { + "he": "1.2.0" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + + "node_modules/path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=", + "dependencies": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dev": true, + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + + "node_modules/title-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/title-case/-/title-case-3.0.3.tgz", + "integrity": "sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + + "node_modules/tslib": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", + "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" + }, + + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + + "node_modules/util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dependencies": { + "inherits": "2.0.3" + } + + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + + } + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "async": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "front-matter": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/front-matter/-/front-matter-4.0.2.tgz", + "integrity": "sha512-I8ZuJ/qG92NWX8i5x1Y8qyj3vizhXS31OxjKDu3LKP+7/qBgfIKValiZIEwoVoJKUHlhWtYrktkxV1XsX+pPlg==", + "requires": { + "js-yaml": "^3.13.1" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + } + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "dev": true, + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" + }, + "html-entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.1.0.tgz", + "integrity": "sha512-u+OHVGMH5P1HlaTFp3M4HolRnWepgx5rAnYBo+7/TrBZahuJjgQ4TMv2GjQ4IouGDzkgXYeOI/NQuF95VOUOsQ==" + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "is-absolute-url": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", + "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", + "dev": true + }, + "is-relative-url": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-relative-url/-/is-relative-url-3.0.0.tgz", + "integrity": "sha512-U1iSYRlY2GIMGuZx7gezlB5dp1Kheaym7zKzO1PV06mOihiWTXejLwm4poEJysPyXF+HtK/BEd0DVlcCh30pEA==", + "dev": true, + "requires": { + "is-absolute-url": "^3.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "isemail": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz", + "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==", + "dev": true, + "requires": { + "punycode": "2.x.x" + } + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "requires": { + "argparse": "^2.0.1" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "jsonschema": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.2.6.tgz", + "integrity": "sha512-SqhURKZG07JyKKeo/ir24QnS4/BV7a6gQy93bUSe4lUdNp0QNpIz2c9elWJQ9dpc5cQYY6cvCzgRwy0MQCLyqA==" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "link-check": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/link-check/-/link-check-4.5.4.tgz", + "integrity": "sha512-VdjiYrIBNHtqH7NEvIlF/4i0V9xQWkoBry+65DtmmyKyD5qBZ2U9fCJYx75SI5Ms4ILJzGlNNojPKbPMpg5Spg==", + "dev": true, + "requires": { + "is-relative-url": "^3.0.0", + "isemail": "^3.2.0", + "ms": "^2.1.2", + "request": "^2.88.2" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "markdown-link-check": { + "version": "3.8.6", + "resolved": "https://registry.npmjs.org/markdown-link-check/-/markdown-link-check-3.8.6.tgz", + "integrity": "sha512-GWzd54hNol2Zwf6fFEhUYqi9wbib1BMdcphvGCZqhinaAKhiILDaQkX7sn2u9GMoItmZ1jQK0f8wULOK/M+M4w==", + "dev": true, + "requires": { + "async": "^3.2.0", + "chalk": "^4.1.0", + "commander": "^6.2.0", + "link-check": "^4.5.4", + "lodash": "^4.17.20", + "markdown-link-extractor": "^1.2.6", + "progress": "^2.0.3", + "request": "^2.88.2" + } + }, + "markdown-link-extractor": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/markdown-link-extractor/-/markdown-link-extractor-1.2.6.tgz", + "integrity": "sha512-WDiwWTzR/zk0n0As7q1KCB1Jd/T7nJ7IEr6E1QKZR1Agd/xRmB0FjM2IrtC7IZ1ZwxflBE0aLe4pkX8d+rzV8w==", + "dev": true, + "requires": { + "marked": "^1.1.1" + }, + "dependencies": { + "marked": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/marked/-/marked-1.2.9.tgz", + "integrity": "sha512-H8lIX2SvyitGX+TRdtS06m1jHMijKN/XjfH6Ooii9fvxMlh8QdqBfBDkGUpMWH2kQNrtixjzYUa3SH8ROTgRRw==", + "dev": true + } + } + }, + "marked": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-2.0.0.tgz", + "integrity": "sha512-NqRSh2+LlN2NInpqTQnS614Y/3NkVMFFU6sJlRFEpxJ/LHuK/qJECH7/fXZjk4VZstPW/Pevjil/VtSONsLc7Q==" + }, + "mime-db": { + "version": "1.46.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.46.0.tgz", + "integrity": "sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ==", + "dev": true + }, + "mime-types": { + "version": "2.1.29", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.29.tgz", + "integrity": "sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ==", + "dev": true, + "requires": { + "mime-db": "1.46.0" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node-html-parser": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-2.1.0.tgz", + "integrity": "sha512-kbCNfqjrwHAbG+mevL8aqjwVtF0Qv66XurWHoGLOc5G9rPR1L3k602jfeczAUUBldLNnCrdsDmO5G5nqAoMW+g==", + "requires": { + "he": "1.2.0" + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=", + "requires": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "title-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/title-case/-/title-case-3.0.3.tgz", + "integrity": "sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==", + "requires": { + "tslib": "^2.0.3" + } + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tslib": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", + "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "requires": { + "inherits": "2.0.3" + } + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + } + } +} diff --git a/tests/package.json b/scripts/validation/package.json similarity index 77% rename from tests/package.json rename to scripts/validation/package.json index 5ff6d6ef..5674bc0b 100644 --- a/tests/package.json +++ b/scripts/validation/package.json @@ -9,11 +9,16 @@ "author": "", "license": "ISC", "dependencies": { + "front-matter": "^4.0.2", "html-entities": "^2.1.0", + "js-yaml": "^4.1.0", "jsonschema": "^1.2.6", "marked": "^2.0.0", "node-html-parser": "^2.1.0", "path": "^0.12.7", "title-case": "^3.0.3" + }, + "devDependencies": { + "markdown-link-check": "^3.8.6" } } diff --git a/scripts/validation/tests/spelling.md b/scripts/validation/tests/spelling.md new file mode 100644 index 00000000..e1b1e23c --- /dev/null +++ b/scripts/validation/tests/spelling.md @@ -0,0 +1,16 @@ +# Tests for Wi-Fi spelling +Correct: Wi-Fi +WiFi +Wifi +Ignored: WiFi.begin() +Ignored: WiFiServer +Ignored: www.arduino.cc/libraries/wifi +Ignored: www.arduino.cc/libraries/wifi-tutorial + +# Tests for megabytes / MB +Correct: megabytes +Correct: MB +Megabytes +Mega Bytes +Mbytes +MBytes \ No newline at end of file diff --git a/scripts/validation/tests/trademarks.md b/scripts/validation/tests/trademarks.md new file mode 100644 index 00000000..74948d87 --- /dev/null +++ b/scripts/validation/tests/trademarks.md @@ -0,0 +1,15 @@ +# Test for LoRa® trademark +- [Portenta Vision Shield - LoRa®](https://store.arduino.cc/portenta-vision-shield-lora) +Correct: LoRa® +Correct: About LoRaWAN® and The Things Network, +- Lora® +- LoRa +- lora® +- LoRa +Ignored: **LoraSendAndReceive** sketch from the MKRWAN library + +# Test for Bluetooth® +Bluetooth +bluetooth +Bluetooth® +Ignored: [GATT codes](https://www.bluetooth.com/specifications/gatt/services/) \ No newline at end of file diff --git a/scripts/validation/tests/tutorial/content.md b/scripts/validation/tests/tutorial/content.md new file mode 100644 index 00000000..b93e0538 --- /dev/null +++ b/scripts/validation/tests/tutorial/content.md @@ -0,0 +1,9 @@ +# Incorrect Author Section + +**Authors:** Sebastian Romero, Benjamin Dannegård +**Reviewed by:** Lenard George [2021-04-12] +**Last revision:** Sebastian Romero [2021-05-03] + +**Authors:** Sebastian Romero, Benjamin Dannegård +**Reviewed by:** Lenard George [12.4.2021] +**Last revision:** Sebastian Romero [2021.05.03] \ No newline at end of file diff --git a/scripts/validation/validate.js b/scripts/validation/validate.js new file mode 100644 index 00000000..5a134cc7 --- /dev/null +++ b/scripts/validation/validate.js @@ -0,0 +1,336 @@ +const parser = require('node-html-parser'); +const fileHelper = require('../lib/file-helper'); +const fs = require('fs'); +const yaml = require('js-yaml'); +const validate = require('jsonschema').validate; +const path = require('path'); +const tc = require('title-case'); +const Validator = require('./domain/validator').Validator; +const { ValidationError } = require('./domain/validation-error'); +const markdownLinkCheck = require('markdown-link-check'); + +const PARSER_SYNTAX_PREFIX = "language-"; // Prepended by marked +const CONFIG_PATH = "scripts/validation/config"; +const basePathFromCommandline = process.argv[2]; +const config = yaml.load(fs.readFileSync(`${CONFIG_PATH}/config-tutorials.yml`, 'utf8'));; +let tutorialPaths; +const debug = false; // Set this to true to debug the rule matching + + +if(basePathFromCommandline) { + tutorialPaths = [basePathFromCommandline]; +} else { + tutorialPaths = fileHelper.getSubdirectories(config.basePath, config.excludePatterns); +} +const validator = new Validator(tutorialPaths); + +debugPrint = (message) => { + if(debug) console.log(message) +} + +/** + * Verify that all meta data is valid JSON and contains the correct attributes + */ +validator.addValidation(async (tutorials) => { + let errorsOccurred = []; + tutorials.forEach(tutorial => { + let jsonData = tutorial.metadata; + if(!jsonData) { + const errorMessage = "No metadata found"; + errorsOccurred.push(new ValidationError(errorMessage, tutorial.contentFilePath)); + return; + } + + try { + if(!jsonData.coverImage){ + const errorMessage = "No cover image found"; + errorsOccurred.push(new ValidationError(errorMessage, tutorial.contentFilePath)); + } else if (jsonData.coverImage.indexOf(".svg") == -1) { + const errorMessage = "Cover image is not in SVG format."; + errorsOccurred.push(new ValidationError(errorMessage, tutorial.contentFilePath)); + } + + let jsonSchema = JSON.parse(fs.readFileSync(config.metadataSchema)); + let validationResult = validate(jsonData, jsonSchema); + if(validationResult.errors.length != 0){ + const errorMessage = `An error occurred while validating the metadata ${validationResult}`; + errorsOccurred.push(new ValidationError(errorMessage, tutorial.contentFilePath)); + } + + } catch (error) { + const errorMessage = "An error occurred while parsing the metadata"; + errorsOccurred.push(new ValidationError(errorMessage, tutorial.contentFilePath)); + } + }); + return errorsOccurred; +}); + +/** + * Verifies that the titles are in the correct format + */ +validator.addValidation(async (tutorials) => { + let errorsOccurred = []; + tutorials.forEach(tutorial => { + tutorial.headings.forEach(heading => { + if(tc.titleCase(heading) != heading){ + const errorMessage = heading + "' is not title case"; + errorsOccurred.push(new ValidationError(errorMessage, tutorial.contentFilePath)); + } + if(heading.length > config.headingMaxLength){ + const errorMessage = heading + "' (" + heading.length + ") exceeds the max length (" + config.headingMaxLength + ")"; + errorsOccurred.push(new ValidationError(errorMessage, tutorial.contentFilePath)); + } + }); + }); + return errorsOccurred; +}); + + +/** + * Verify that SVG images don't contain embedded images + */ +validator.addValidation(async (tutorials) => { + let errorsOccurred = []; + tutorials.forEach(tutorial => { + let svgFiles = tutorial.svgAssets; + if(svgFiles === undefined || svgFiles.length == 0) return; + svgFiles.forEach(path => { + const rawData = fs.readFileSync(path); + if(rawData.includes(" { + if(!config.checkForBrokenLinks) return []; + + let promises = tutorials.map(tutorial => { + return new Promise(function(resolve){ + const markdownContent = tutorial.markdown; + if(!markdownContent) return; + const ignorePatterns = config.brokenLinkExcludePatterns.map((ignorePattern) => { + return {pattern : new RegExp(ignorePattern)} + }); + const options = { ignorePatterns: ignorePatterns}; + markdownLinkCheck(markdownContent, options, function (err, results) { + if (err) { + console.error('Error', err); + return; + } + let errorsOccurred = []; + results.forEach(function (result) { + if(result.status == "alive" && config.verboseOutput){ + debugPrint(`👍 ${result.link} is alive`); + } else if(result.status == "dead" && result.statusCode !== 0){ + const errorMessage = `${result.link} is dead 💀 HTTP ${result.statusCode}`; + errorsOccurred.push(new ValidationError(errorMessage, tutorial.contentFilePath)); + } + }); + resolve(errorsOccurred); + }); + }); + }); + + return Promise.all(promises).then((results) => { + return results.flat(1); + }); +}); + + +/** + * Verify that all files in the assets folder are referenced + */ +validator.addValidation(async (tutorials) => { + let errorsOccurred = []; + tutorials.forEach(tutorial => { + let imageNames = tutorial.imagePaths.map(imagePath => path.basename(imagePath)); + let assetNames = tutorial.assets.map(asset => path.basename(asset)); + let linkNames = tutorial.linkPaths.map(link => path.basename(link)); + let coverImageName = tutorial.coverImagePath ? path.basename(tutorial.coverImagePath) : null; + + assetNames.forEach(asset => { + if(coverImageName == asset) return; + if(!imageNames.includes(asset) && !linkNames.includes(asset)){ + const errorMessage = asset + " is not used."; + errorsOccurred.push(new ValidationError(errorMessage, tutorial.path)); + } + }); + }); + return errorsOccurred; +}); + + +/** + * Verify that the images exist and don't have an absolute path + */ +validator.addValidation(async (tutorials) => { + let errorsOccurred = []; + tutorials.forEach(tutorial => { + tutorial.imagePaths.forEach(imagePath => { + if(imagePath.startsWith("/") || imagePath.startsWith("~")){ + const errorMessage = "Image uses an absolute path: " + imagePath; + const content = tutorial.markdown; + const lineNumber = fileHelper.getLineNumberFromIndex(content.indexOf(imagePath), content); + errorsOccurred.push(new ValidationError(errorMessage, tutorial.contentFilePath, "error", lineNumber)); + } else if(!imagePath.startsWith("http") && !fs.existsSync(`${tutorial.path}/${imagePath}`)){ + const errorMessage = "Image doesn't exist: " + imagePath; + const content = tutorial.markdown; + const lineNumber = fileHelper.getLineNumberFromIndex(content.indexOf(imagePath), content); + errorsOccurred.push(new ValidationError(errorMessage, tutorial.contentFilePath, "error", lineNumber)); + } + }); + }); + return errorsOccurred; +}); + + +/** + * Ensures no nested lists are used + */ +validator.addValidation(async (tutorials) => { + let errorsOccurred = []; + if(config.allowNestedLists) return errorsOccurred; + tutorials.forEach(tutorial => { + let nodes = tutorial.html.querySelectorAll("li ul"); + if(nodes && nodes.length > 0){ + const errorMessage = "Content uses nested lists"; + errorsOccurred.push(new ValidationError(errorMessage, tutorial.contentFilePath)); + } + }); + return errorsOccurred; +}); + + +/** + * Verify that the images contain a description + */ +validator.addValidation(async (tutorials) => { + let errorsOccurred = []; + tutorials.forEach(tutorial => { + tutorial.imageNodes.forEach(image => { + const imageDescription = image.attributes.alt; + if(imageDescription.split(" ").length <= 1){ + const errorMessage = "Image doesn't have a description: " + image.attributes.src; + errorsOccurred.push(new ValidationError(errorMessage, tutorial.contentFilePath)); + } + }); + }); + return errorsOccurred; +}); + + + /** + * Verify that only allowed syntax specifiers are used + */ + validator.addValidation(async (tutorials) => { + let errorsOccurred = []; + tutorials.forEach(tutorial => { + tutorial.codeNodes.forEach(codeNode => { + let syntax = codeNode.classNames[0]; + if(syntax) syntax = syntax.replace(PARSER_SYNTAX_PREFIX, ''); + if(!config.allowedSyntaxSpecifiers.includes(syntax)){ + const errorMessage = "Code block uses unsupported syntax: " + syntax; + errorsOccurred.push(new ValidationError(errorMessage, tutorial.contentFilePath)); + } + }); + }); + return errorsOccurred; +}); + +/** + * Verifies that the content rules are met + */ +validator.addValidation(async (tutorials) => { + let errorsOccurred = []; + tutorials.forEach(tutorial => { + let htmlContent = tutorial.rawHTML; + let markdownContent = tutorial.markdown; + let allRules = []; + + try { + allRules.push(yaml.load(fs.readFileSync(`${CONFIG_PATH}/rules-spelling.yml`, 'utf8'))); + allRules.push(yaml.load(fs.readFileSync(`${CONFIG_PATH}/rules-trademarks.yml`, 'utf8'))); + allRules.push(yaml.load(fs.readFileSync(`${CONFIG_PATH}/rules-tutorials.yml`, 'utf8'))); + } catch (e) { + console.log(e); + } + + for(rules of allRules){ + for(rule of rules) { + debugPrint(`🕵️ Validating rule ${rule.regex} for ${tutorial.contentFilePath}`) + const content = rule.format == "html" ? htmlContent : markdownContent; + const regex = new RegExp(rule.regex, "g"); + const matches = content.matchAll(regex); + const ruleType = rule.type ?? "error"; + + if(Array.from(matches).length == 0 && rule.shouldMatch){ + const errorMessage = rule.errorMessage; + errorsOccurred.push(new ValidationError(errorMessage, tutorial.contentFilePath, ruleType)); + } else { + debugPrint(`👍 Passed rule ${rule.regex} for ${tutorial.contentFilePath}`) + } + + for(currentMatch of matches){ + const index = currentMatch.index; + let lineNumber = fileHelper.getLineNumberFromIndex(index,content); + + if(!rule.shouldMatch) { + const errorMessage = rule.errorMessage; + errorsOccurred.push(new ValidationError(errorMessage, tutorial.contentFilePath, ruleType, lineNumber)); + } else { + debugPrint(`👍 Passed rule ${rule.regex} for ${tutorial.contentFilePath}`) + } + } + + }; + } + + }); + return errorsOccurred; +}); + +/** + * Check if an error occurred and exit with the corresponding status code + */ +(function main() { + console.log(`🕵️ Validating ${tutorialPaths.length} tutorials...`); + validator.validate().then( validationIssues => { + if(validationIssues.length == 0){ + console.log("✅ No issues found.") + process.exit(0); + } + + let validationErrors = 0; + let validationWarnings = 0; + + for(issue of validationIssues){ + if(issue.type == "error") { + ++validationErrors; + } else { + ++validationWarnings; + } + const symbol = issue.type == "error" ? "❌" : "😬"; + const lineNumber = issue.lineNumber ? ":" + issue.lineNumber : ""; + console.log(symbol + " " + issue.message + " Location: " + issue.file + lineNumber); + } + + if(validationWarnings > 0) + console.log("😬 " + validationWarnings+ " warnings found.") + if(validationErrors > 0) + console.log("🚫 " + validationErrors + " errors found.") + process.exit(validationErrors > 0 ? 2 : 0); + }); +})() \ No newline at end of file diff --git a/tests/config.json b/tests/config.json deleted file mode 100644 index 22e56ae2..00000000 --- a/tests/config.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "headingMaxLength" : 50, - "excludePatterns" : [".git", "/template"], - "basePath" : "content/tutorials/portenta-h7/", - "allowedSyntaxSpecifiers" : ["cpp", "py", "text"], - "allowNestedLists" : false, - "metadataSchema" : "tests/metadata-schema.json" -} \ No newline at end of file diff --git a/tests/metadata-schema.json b/tests/metadata-schema.json deleted file mode 100644 index d0bdd541..00000000 --- a/tests/metadata-schema.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "index": { - "type": "integer" - }, - "beta": { - "type": "boolean" - }, - "title": { - "type": "string" - }, - "slug": { - "type": "string" - }, - "authors": { - "type": "array", - "items": [ - { - "type": "string" - } - ] - }, - "coverImage": { - "type": "object", - "properties": { - "src": { - "type": "string" - } - }, - "required": [ - "src" - ] - }, - "tags": { - "type": "array", - "items": [ - { - "type": "string" - }, - { - "type": "string" - }, - { - "type": "string" - } - ] - }, - "abstract": { - "type": "string" - }, - "previousArticle": { - "type": ["string", "null"] - }, - "nextArticle": { - "type": ["string", "null"] - } - }, - "required": [ - "id", - "index", - "title", - "slug", - "authors", - "coverImage", - "tags", - "abstract", - "previousArticle", - "nextArticle" - ] - } - \ No newline at end of file diff --git a/tests/package-lock.json b/tests/package-lock.json deleted file mode 100644 index cfc3dd8d..00000000 --- a/tests/package-lock.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "name": "validate", - "version": "1.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" - }, - "html-entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.1.0.tgz", - "integrity": "sha512-u+OHVGMH5P1HlaTFp3M4HolRnWepgx5rAnYBo+7/TrBZahuJjgQ4TMv2GjQ4IouGDzkgXYeOI/NQuF95VOUOsQ==" - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "jsonschema": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.2.6.tgz", - "integrity": "sha512-SqhURKZG07JyKKeo/ir24QnS4/BV7a6gQy93bUSe4lUdNp0QNpIz2c9elWJQ9dpc5cQYY6cvCzgRwy0MQCLyqA==" - }, - "marked": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-2.0.0.tgz", - "integrity": "sha512-NqRSh2+LlN2NInpqTQnS614Y/3NkVMFFU6sJlRFEpxJ/LHuK/qJECH7/fXZjk4VZstPW/Pevjil/VtSONsLc7Q==" - }, - "node-html-parser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-2.1.0.tgz", - "integrity": "sha512-kbCNfqjrwHAbG+mevL8aqjwVtF0Qv66XurWHoGLOc5G9rPR1L3k602jfeczAUUBldLNnCrdsDmO5G5nqAoMW+g==", - "requires": { - "he": "1.2.0" - } - }, - "path": { - "version": "0.12.7", - "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", - "integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=", - "requires": { - "process": "^0.11.1", - "util": "^0.10.3" - } - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" - }, - "title-case": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/title-case/-/title-case-3.0.3.tgz", - "integrity": "sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==", - "requires": { - "tslib": "^2.0.3" - } - }, - "tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" - }, - "util": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", - "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", - "requires": { - "inherits": "2.0.3" - } - } - } -} diff --git a/tests/rules.json b/tests/rules.json deleted file mode 100644 index 383ca7fa..00000000 --- a/tests/rules.json +++ /dev/null @@ -1,44 +0,0 @@ -[ - { - "regex" : "^\\*\\*Authors:\\*\\* .* \\n", - "shouldMatch" : true, - "format" : "markdown", - "errorMessage" : "The authors' format is incorrect." - }, - { - "regex" : "^#### .*\\n", - "shouldMatch" : false, - "format" : "markdown", - "errorMessage" : "H4 headings are not supported." - }, - { - "regex" : "^\\*\\*Reviewed by:\\*\\* .* \\[\\d\\d\\d?\\d\\-\\d?\\d\\-\\d\\d\\] \\n", - "shouldMatch" : true, - "format" : "markdown", - "errorMessage" : "The reviewer's format is incorrect. Review date added?" - }, - { - "regex" : "^\\*\\*Last revision:\\*\\* .* \\[\\d\\d\\d?\\d\\-\\d?\\d\\-\\d\\d\\]", - "shouldMatch" : true, - "format" : "markdown", - "errorMessage" : "The revisor's format is incorrect. Revision date correct YYYY-MM-DD?" - }, - { - "regex" : "^## Conclusion\\n", - "shouldMatch" : true, - "format" : "markdown", - "errorMessage" : "Missing conclusion section." - }, - { - "regex" : "^### You Will Learn\\n", - "shouldMatch" : true, - "format" : "markdown", - "errorMessage" : "Missing 'What You Will Learn' section." - }, - { - "regex" : "^### Required Hardware and Software\\n", - "shouldMatch" : true, - "format" : "markdown", - "errorMessage" : "Missing 'Required Hardware and Software' section." - } -] \ No newline at end of file diff --git a/tests/validate.js b/tests/validate.js deleted file mode 100644 index 8f5a6020..00000000 --- a/tests/validate.js +++ /dev/null @@ -1,232 +0,0 @@ -const parser = require('node-html-parser'); -const matcher = require('./matcher'); -const fs = require('fs'); -const validate = require('jsonschema').validate; -const path = require('path'); -const tc = require('title-case'); -const config = require('./config'); -const rules = require('./rules'); -const Validator = require('./validator').Validator; - -const PARSER_SYNTAX_PREFIX = "language-"; // Prepended by marked -const basePathFromCommandline = process.argv[2]; -let tutorialPaths; - -if(basePathFromCommandline) { - tutorialPaths = [basePathFromCommandline]; -} else { - tutorialPaths = matcher.getSubdirectories(config.basePath, config.excludePatterns); -} -const validator = new Validator(tutorialPaths); - - -/** - * Verify that all meta data files are valid JSON and contain the correct attributes - */ -validator.addValidation((tutorials) => { - let errorsOccurred = 0; - tutorials.forEach(tutorial => { - let jsonData = tutorial.metadata - if(!jsonData) { - console.log("❌ No metadata file found for tutorial " + tutorial.path); - ++errorsOccurred; - } - - try { - if(!jsonData.coverImage){ - console.log("❌ No cover image found for " + path); - ++errorsOccurred; - } else if (jsonData.coverImage.src.indexOf(".svg") == -1) { - console.log("❌ Cover image of " + path + "is not in SVG format."); - ++errorsOccurred; - } - - let jsonSchema = JSON.parse(fs.readFileSync(config.metadataSchema)); - let validationResult = validate(jsonData, jsonSchema); - if(validationResult.errors.length != 0){ - console.log("❌ An error occurred while validating the metadata of " + tutorial.path); - console.log(validationResult); - ++errorsOccurred; - } - - } catch (error) { - console.log("❌ An error occurred while parsing the metadata of " + tutorial.path); - console.log(error); - ++errorsOccurred; - } - }); - return errorsOccurred; -}); - -/** - * Verifies that the titles are in the correct format - */ -validator.addValidation((tutorials) => { - let errorsOccurred = 0; - tutorials.forEach(tutorial => { - tutorial.headings.forEach(heading => { - if(tc.titleCase(heading) != heading){ - console.log("❌ '" + heading + "' is not title case in tutorial " + tutorial.path); - ++errorsOccurred; - } - if(heading.length > config.headingMaxLength){ - console.log("❌ '" + heading + "' (" + heading.length + ") exceeds the max length (" + config.headingMaxLength + ") in tutorial " + tutorial.path); - ++errorsOccurred; - } - }); - }); - return errorsOccurred; -}); - - -/** - * Verify that SVG images don't contain embedded images - */ -validator.addValidation((tutorials) => { - let errorsOccurred = 0; - tutorials.forEach(tutorial => { - let svgFiles = tutorial.svgAssets; - svgFiles.forEach(path => { - const rawData = fs.readFileSync(path); - if(rawData.includes(" { - let errorsOccurred = 0; - tutorials.forEach(tutorial => { - let imageNames = tutorial.imagePaths.map(imagePath => path.basename(imagePath)); - let assetNames = tutorial.assets.map(asset => path.basename(asset)); - let linkNames = tutorial.linkPaths.map(link => path.basename(link)); - let coverImageName = path.basename(tutorial.coverImagePath); - - assetNames.forEach(asset => { - if(coverImageName == asset) return; - if(!imageNames.includes(asset) && !linkNames.includes(asset)){ - console.log("❌ " + asset + " is not used in tutorial " + tutorial.path); - ++errorsOccurred; - } - }); - }); - return errorsOccurred; -}); - - -/** - * Verify that the images don't have an absolute path - */ -validator.addValidation((tutorials) => { - let errorsOccurred = 0; - tutorials.forEach(tutorial => { - tutorial.imagePaths.forEach(imagePath => { - if(imagePath.startsWith("/") || imagePath.startsWith("~")){ - console.log("❌ Image uses an absolute path: " + imagePath + " in " + tutorial.path); - ++errorsOccurred; - } - }); - }); - return errorsOccurred; -}); - - -/** - * Ensures no nested lists are used - */ -validator.addValidation((tutorials) => { - if(config.allowNestedLists) return; - let errorsOccurred = 0; - tutorials.forEach(tutorial => { - let nodes = tutorial.html.querySelectorAll("li ul"); - if(nodes && nodes.length > 0){ - ++errorsOccurred; - console.log("❌ Content uses nested lists in " + tutorial.path); - } - }); - return errorsOccurred; -}); - - -/** - * Verify that the images contain a description - */ -validator.addValidation((tutorials) => { - let errorsOccurred = 0; - tutorials.forEach(tutorial => { - tutorial.imageNodes.forEach(image => { - const imageDescription = image.attributes.alt; - if(imageDescription.split(" ").length <= 1){ - console.log("❌ Image doesn't have a description: " + image.attributes.src + " in " + tutorial.path); - ++errorsOccurred; - } - }); - }); - return errorsOccurred; -}); - - - /** - * Verify that only allowed syntax specifiers are used - */ - validator.addValidation((tutorials) => { - let errorsOccurred = 0; - tutorials.forEach(tutorial => { - tutorial.codeNodes.forEach(codeNode => { - let syntax = codeNode.classNames[0]; - if(syntax) syntax = syntax.replace(PARSER_SYNTAX_PREFIX, ''); - if(!config.allowedSyntaxSpecifiers.includes(syntax)){ - console.log("❌ Code block uses unsupported syntax: " + syntax + " in " + tutorial.path); - ++errorsOccurred; - } - }); - }); - return errorsOccurred; -}); - -/** - * Verifies that the content rules are met - */ -validator.addValidation((tutorials) => { - let errorsOccurred = 0; - tutorials.forEach(tutorial => { - let htmlContent = tutorial.rawHTML; - let markdownContent = tutorial.markdown; - - rules.forEach(rule => { - const content = rule.format == "html" ? htmlContent :markdownContent; - const regex = new RegExp(rule.regex, 'gm'); - const match = content.match(regex); - if((match === null && rule.shouldMatch) || (match !== null && !rule.shouldMatch)) { - console.log("❌ " + rule.errorMessage + " in " + tutorial.path); - ++errorsOccurred; - } - }); - - }); - return errorsOccurred; -}); - -/** - * Check if an error occurred and exit with the corresponding status code - */ -const errorsFound = validator.validate() -if(errorsFound == 0){ - console.log("✅ No errors found.") - process.exit(0); -} else { - console.log("🚫 " + errorsFound + " errors found.") - process.exit(2); -} \ No newline at end of file diff --git a/tests/validator.js b/tests/validator.js deleted file mode 100644 index 63849512..00000000 --- a/tests/validator.js +++ /dev/null @@ -1,24 +0,0 @@ -const Tutorial = require('./tutorial').Tutorial; - -var Validator = class Validator { - validatonCallbacks = []; - - constructor(tutorialPaths){ - this.tutorials = tutorialPaths.map(tutorialPath => new Tutorial(tutorialPath) ); - } - - addValidation(func) { - this.validatonCallbacks.push(func); - } - - validate(){ - let errorsOccurred = 0; - this.validatonCallbacks.forEach(validation => { - errorsOccurred += validation(this.tutorials); - }); - - return errorsOccurred; - } -} - -module.exports = { Validator } \ No newline at end of file