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 f888d200..81f93271 100644 --- a/content/tutorials/portenta-h7/metadata.json +++ b/content/tutorials/portenta-h7/metadata.json @@ -18,6 +18,10 @@ "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 735fbaa4..873a781b 100644 --- a/content/tutorials/portenta-h7/por-ard-ap/content.md +++ b/content/tutorials/portenta-h7/por-ard-ap/content.md @@ -1,10 +1,17 @@ -# Portenta H7 as a WiFi Access Point +--- +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 Wi-Fi Access Point ## Overview -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 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 WiFi + Bluetooth module. +- 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. @@ -15,20 +22,20 @@ Portenta H7 comes with an on-board WiFi and a bluetooth Module that allows to de - 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. +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 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 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. @@ -36,9 +43,9 @@ Begin by plugging in your Portenta board to your computer using a USB-C cable an ![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**. +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 @@ -72,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); } @@ -199,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); @@ -214,7 +221,7 @@ 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.*** @@ -222,13 +229,13 @@ Here the web page is just a simple HTML page with buttons to toggle the LED stat 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" @@ -243,31 +250,31 @@ 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 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 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 @@ -285,15 +292,15 @@ 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 -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 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 5ba9c514..00000000 --- a/content/tutorials/portenta-h7/por-ard-ap/metadata.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "title":"Portenta H7 as a WiFi Access Point", - "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." -} diff --git a/content/tutorials/portenta-h7/por-ard-bl/content.md b/content/tutorials/portenta-h7/por-ard-bl/content.md index 8f4e1534..b7e4332c 100644 --- a/content/tutorials/portenta-h7/por-ard-bl/content.md +++ b/content/tutorials/portenta-h7/por-ard-bl/content.md @@ -1,3 +1,10 @@ +--- +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 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 21a935c2..00000000 --- a/content/tutorials/portenta-h7/por-ard-bl/metadata.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "title":"Updating the Portenta Bootloader", - "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." -} diff --git a/content/tutorials/portenta-h7/por-ard-ble/content.md b/content/tutorials/portenta-h7/por-ard-ble/content.md index 0342b664..0bb11e07 100644 --- a/content/tutorials/portenta-h7/por-ard-ble/content.md +++ b/content/tutorials/portenta-h7/por-ard-ble/content.md @@ -1,7 +1,14 @@ +--- +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 ## 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. +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 @@ -16,27 +23,27 @@ 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. +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 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 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) @@ -136,7 +143,7 @@ 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``` 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 40c8566e..00000000 --- a/content/tutorials/portenta-h7/por-ard-ble/metadata.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "title":"BLE Connectivity on Portenta H7", - "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." -} diff --git a/content/tutorials/portenta-h7/por-ard-dcp/content.md b/content/tutorials/portenta-h7/por-ard-dcp/content.md index cc45438e..2cbddad1 100644 --- a/content/tutorials/portenta-h7/por-ard-dcp/content.md +++ b/content/tutorials/portenta-h7/por-ard-dcp/content.md @@ -1,3 +1,10 @@ +--- +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 @@ -17,19 +24,19 @@ The Portenta H7 is equipped with a processor that has two processing units calle ## 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 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 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** @@ -85,7 +92,7 @@ If you would upload the sketch to the M4 at this point nothing would change. The ### 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. @@ -170,7 +177,7 @@ Now can upload the sketch to both the cores of the Portenta H7 individually. Wit 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. +- 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 [2020-03-20] 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 a81ca5fc..00000000 --- a/content/tutorials/portenta-h7/por-ard-dcp/metadata.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "title":"Dual Core Processing", - "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." -} diff --git a/content/tutorials/portenta-h7/por-ard-flash/content.md b/content/tutorials/portenta-h7/por-ard-flash/content.md index dc8b04c3..cfed382c 100644 --- a/content/tutorials/portenta-h7/por-ard-flash/content.md +++ b/content/tutorials/portenta-h7/por-ard-flash/content.md @@ -1,3 +1,10 @@ +--- +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. @@ -361,7 +368,7 @@ if(blockDevice.init() != 0 || blockDevice.size() != BLOCK_DEVICE_SIZE) { } ``` -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 WiFi 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. +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 diff --git a/content/tutorials/portenta-h7/por-ard-flash/metadata.json b/content/tutorials/portenta-h7/por-ard-flash/metadata.json deleted file mode 100644 index 80ff6abe..00000000 --- a/content/tutorials/portenta-h7/por-ard-flash/metadata.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "title": "Reading and Writing Flash Memory", - "coverImage": { - "src": "assets/por_ard_block_device_cover.svg?sanitize=true" - }, - "tags": [ - "Storage", - "Flash", - "Block Device" - ], - "abstract": "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." -} \ No newline at end of file diff --git a/content/tutorials/portenta-h7/por-ard-gs/content.md b/content/tutorials/portenta-h7/por-ard-gs/content.md index d05d0a5f..1ead36b7 100644 --- a/content/tutorials/portenta-h7/por-ard-gs/content.md +++ b/content/tutorials/portenta-h7/por-ard-gs/content.md @@ -1,3 +1,10 @@ +--- +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 @@ -24,7 +31,7 @@ The Portenta H7 is equipped with two Arm Cortex ST processors (Cortex-M4 and Cor 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 @@ -34,7 +41,7 @@ In this section, we will guide you through a step-by-step process of setting up ### 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 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). @@ -51,9 +58,10 @@ In this step you will check if Windows is able to detect the Portenta H7. To do ![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: +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 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 4084488c..00000000 --- a/content/tutorials/portenta-h7/por-ard-gs/metadata.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "title":"Setting Up Portenta H7 For Arduino", - "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." -} diff --git a/content/tutorials/portenta-h7/por-ard-kvs/content.md b/content/tutorials/portenta-h7/por-ard-kvs/content.md index dcbf24d4..cbc47feb 100644 --- a/content/tutorials/portenta-h7/por-ard-kvs/content.md +++ b/content/tutorials/portenta-h7/por-ard-kvs/content.md @@ -1,3 +1,10 @@ +--- +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 ## Overview 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 3f7ef847..00000000 --- a/content/tutorials/portenta-h7/por-ard-kvs/metadata.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "title":"Creating a Flash-Optimised Key-Value Store", - "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." -} diff --git a/content/tutorials/portenta-h7/por-ard-lvgl/content.md b/content/tutorials/portenta-h7/por-ard-lvgl/content.md index be07c902..9eedcf13 100644 --- a/content/tutorials/portenta-h7/por-ard-lvgl/content.md +++ b/content/tutorials/portenta-h7/por-ard-lvgl/content.md @@ -1,3 +1,10 @@ +--- +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 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 8f2c48ab..00000000 --- a/content/tutorials/portenta-h7/por-ard-lvgl/metadata.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "title":"Creating GUIs with LVGL", - "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." -} diff --git a/content/tutorials/portenta-h7/por-ard-trace32/content.md b/content/tutorials/portenta-h7/por-ard-trace32/content.md index d1fec7ae..d47b0f8d 100644 --- a/content/tutorials/portenta-h7/por-ard-trace32/content.md +++ b/content/tutorials/portenta-h7/por-ard-trace32/content.md @@ -1,3 +1,10 @@ +--- +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 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 318b2b42..00000000 --- a/content/tutorials/portenta-h7/por-ard-trace32/metadata.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "title":"Lauterbach TRACE32 GDB Front-End Debugger for Portenta H7", - "slug":"por-ard-trace32", - "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." -} diff --git a/content/tutorials/portenta-h7/por-ard-usb/content.md b/content/tutorials/portenta-h7/por-ard-usb/content.md index 6301498c..bde48cef 100644 --- a/content/tutorials/portenta-h7/por-ard-usb/content.md +++ b/content/tutorials/portenta-h7/por-ard-usb/content.md @@ -1,3 +1,11 @@ +--- +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 @@ -166,7 +174,7 @@ If you don't have a USB-C type hub you may complete this tutorial with a USB-C t + 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 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 088b3015..00000000 --- a/content/tutorials/portenta-h7/por-ard-usb/metadata.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "beta": true, - "title":"Portenta H7 as a USB Host", - "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." -} diff --git a/content/tutorials/portenta-h7/por-openmv-bt/content.md b/content/tutorials/portenta-h7/por-openmv-bt/content.md index 14cd264f..470c438d 100644 --- a/content/tutorials/portenta-h7/por-openmv-bt/content.md +++ b/content/tutorials/portenta-h7/por-openmv-bt/content.md @@ -1,3 +1,10 @@ +--- +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 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 71a5ab9c..00000000 --- a/content/tutorials/portenta-h7/por-openmv-bt/metadata.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "title":"Blob Detection with Portenta and OpenMV", - "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." -} diff --git a/content/tutorials/portenta-h7/por-openmv-fd/content.md b/content/tutorials/portenta-h7/por-openmv-fd/content.md index 7eef72ca..bb2fce92 100644 --- a/content/tutorials/portenta-h7/por-openmv-fd/content.md +++ b/content/tutorials/portenta-h7/por-openmv-fd/content.md @@ -1,3 +1,10 @@ +--- +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 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 36abd423..00000000 --- a/content/tutorials/portenta-h7/por-openmv-fd/metadata.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "title":"Creating a Basic Face Filter With OpenMV", - "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." -} 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 b8dfcbd0..62647fe4 100644 --- a/content/tutorials/portenta-h7/template/content.md +++ b/content/tutorials/portenta-h7/template/content.md @@ -1,3 +1,10 @@ +--- +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. diff --git a/content/tutorials/portenta-h7/template/metadata.json b/content/tutorials/portenta-h7/template/metadata.json deleted file mode 100644 index c1d709c5..00000000 --- a/content/tutorials/portenta-h7/template/metadata.json +++ /dev/null @@ -1,20 +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." -} 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 53186781..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 -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 a6afd509..00000000 --- a/content/tutorials/portenta-h7/vs-ard-ttn/metadata.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "title":"Connecting the Vision Shield to TTN using LoRa", - "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." -} 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/file-helper.js b/scripts/lib/file-helper.js similarity index 91% rename from tests/file-helper.js rename to scripts/lib/file-helper.js index e7df0797..17429993 100644 --- a/tests/file-helper.js +++ b/scripts/lib/file-helper.js @@ -70,10 +70,16 @@ function findAllFiles(startPath, searchPattern, excludePatterns = [], matchingFi return matchingFiles; }; +function createDirectoryIfNecessary(path){ + if(!fs.existsSync(path)){ + fs.mkdirSync(path, { recursive: true }); + } +} + function getLineNumberFromIndex(index, haystack){ const tempString = haystack.substring(0, index); const lineNumber = tempString.split('\n').length; return lineNumber; } -module.exports = { findAllFiles, getSubdirectories, getLineNumberFromIndex}; +module.exports = { findAllFiles, getSubdirectories, createDirectoryIfNecessary, getLineNumberFromIndex}; diff --git a/tests/matcher.js b/scripts/lib/matcher.js similarity index 100% rename from tests/matcher.js rename to scripts/lib/matcher.js 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/tests/metadata-schema.json b/scripts/validation/config/metadata-schema.json similarity index 61% rename from tests/metadata-schema.json rename to scripts/validation/config/metadata-schema.json index e697ee48..154636d1 100644 --- a/tests/metadata-schema.json +++ b/scripts/validation/config/metadata-schema.json @@ -9,31 +9,17 @@ "type": "string" }, "coverImage": { - "type": "object", - "properties": { - "src": { - "type": "string" - } - }, - "required": [ - "src" - ] + "type": "string" }, "tags": { "type": "array", "items": [ - { - "type": "string" - }, - { - "type": "string" - }, { "type": "string" } ] }, - "abstract": { + "description": { "type": "string" } }, @@ -41,7 +27,7 @@ "title", "coverImage", "tags", - "abstract" + "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(){ @@ -74,20 +79,16 @@ var Tutorial = class Tutorial { get assets(){ let files = fileHelper.findAllFiles(this.basePath + "/assets/", null, [".DS_Store"]); - return files.map(file => file.split("?")[0]); + return files ? files.map(file => file.split("?")[0]) : []; } get metadata(){ - const metadataPath = this.basePath + "/metadata.json"; - try { - if(!fs.existsSync(metadataPath)){ - console.log("❌ Metadata file doens't exist " + metadataPath); - return null; - } - 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; } diff --git a/tests/validation-error.js b/scripts/validation/domain/validation-error.js similarity index 52% rename from tests/validation-error.js rename to scripts/validation/domain/validation-error.js index a8ab88e4..1d4684a7 100644 --- a/tests/validation-error.js +++ b/scripts/validation/domain/validation-error.js @@ -1,8 +1,9 @@ var ValidationError = class ValidationError { - constructor(message, file, linenumber){ + constructor(message, file, type = "error", lineNumber = undefined){ this.message = message; this.file = file; - this.linenumber = linenumber; + this.type = type; + this.lineNumber = lineNumber; } } diff --git a/tests/validator.js b/scripts/validation/domain/validator.js similarity index 51% rename from tests/validator.js rename to scripts/validation/domain/validator.js index 66df23b0..cc0a6f66 100644 --- a/tests/validator.js +++ b/scripts/validation/domain/validator.js @@ -12,11 +12,18 @@ var Validator = class Validator { } async validate(){ - let errorsOccurred = 0; - for(const validation of this.validatonCallbacks){ - errorsOccurred += await validation(this.tutorials); - } - return errorsOccurred; + 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)); + }); + + }); } } diff --git a/tests/package-lock.json b/scripts/validation/package-lock.json similarity index 53% rename from tests/package-lock.json rename to scripts/validation/package-lock.json index af85e5e1..12051ca4 100644 --- a/tests/package-lock.json +++ b/scripts/validation/package-lock.json @@ -1,8 +1,513 @@ { "name": "validate", "version": "1.0.0", - "lockfileVersion": 1, + "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", @@ -25,6 +530,11 @@ "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", @@ -150,6 +660,11 @@ "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", @@ -191,6 +706,33 @@ "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", @@ -284,6 +826,14 @@ "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", @@ -500,6 +1050,11 @@ "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", diff --git a/tests/package.json b/scripts/validation/package.json similarity index 89% rename from tests/package.json rename to scripts/validation/package.json index 49d03f4e..5674bc0b 100644 --- a/tests/package.json +++ b/scripts/validation/package.json @@ -9,7 +9,9 @@ "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", 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 862a7e25..00000000 --- a/tests/config.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "headingMaxLength" : 60, - "excludePatterns" : [".git", "/template"], - "basePath" : "content/tutorials/portenta-h7/", - "allowedSyntaxSpecifiers" : ["cpp", "py", "text"], - "allowNestedLists" : false, - "metadataSchema" : "tests/metadata-schema.json", - "checkForBrokenLinks" : true, - "brokenLinkExcludePatterns" : ["assets\/"] -} \ No newline at end of file diff --git a/tests/rules.json b/tests/rules.json deleted file mode 100644 index 17c8d2e2..00000000 --- a/tests/rules.json +++ /dev/null @@ -1,56 +0,0 @@ -[ - { - "regex" : "\\*\\*Authors:\\*\\* .* \\n", - "shouldMatch" : true, - "format" : "markdown", - "errorMessage" : "The authors' format is incorrect. Trailing spaces missing?" - }, - { - "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 YYYY-MM-DD?" - }, - { - "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." - }, - { - "regex" : "\\d\\s?[Mm]ega\\s[Bb]yte|\\d\\s?Mega\\s?[Bb]yte|\\d\\s?mb[\\s\\.]|[Mm][Bb]ytes", - "shouldMatch" : false, - "format" : "markdown", - "errorMessage" : "Megabytes should be either written as 'megabytes' or abbreviated as 'MB'" - }, - { - "regex" : "\\s[\\*_][^*_](?:.*?)[\\*_]", - "shouldMatch" : false, - "format" : "markdown", - "errorMessage" : "The use of italic emphasis is discouraged." - } -] \ No newline at end of file diff --git a/tests/validate.js b/tests/validate.js deleted file mode 100644 index 4174e46e..00000000 --- a/tests/validate.js +++ /dev/null @@ -1,285 +0,0 @@ -const parser = require('node-html-parser'); -const fileHelper = require('./file-helper'); -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 { ValidationError } = require('./validation-error'); -const markdownLinkCheck = require('markdown-link-check'); - - -const PARSER_SYNTAX_PREFIX = "language-"; // Prepended by marked -const basePathFromCommandline = process.argv[2]; -let tutorialPaths; - -if(basePathFromCommandline) { - tutorialPaths = [basePathFromCommandline]; -} else { - tutorialPaths = fileHelper.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; - return; - } - - try { - if(!jsonData.coverImage){ - console.log("❌ No cover image found for " + tutorial.path); - ++errorsOccurred; - } else if (jsonData.coverImage.src.indexOf(".svg") == -1) { - console.log("❌ Cover image of " + tutorial.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(" { - if(!config.checkForBrokenLinks) return 0; - - 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 = 0; - results.forEach(function (result) { - if(result.status == "alive"){ - console.log('✅ %s is alive', result.link); - } else if(result.status == "dead"){ - ++errorsOccurred; - console.log('❌ %s is dead 💀 HTTP %s in %s', result.link, result.statusCode, tutorial.path); - } - }); - resolve(errorsOccurred); - }); - }); - }); - - return Promise.all(promises).then((results) => { - return results.reduce((a, b) => a + b, 0); - }); -}); - - -/** - * Verify that all files in the assets folder are referenced - */ -validator.addValidation((tutorials) => { - 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); - const match = content.match(regex); - let lineNumber = null; - - if(match){ - const index = match.index; - lineNumber = fileHelper.getLineNumberFromIndex(index,content); - } - if((match === null && rule.shouldMatch) || (match !== null && !rule.shouldMatch)) { - const errorMessage = "❌ " + rule.errorMessage + " in " + tutorial.path + ":" + lineNumber; - const error = new ValidationError(errorMessage,tutorial.path, lineNumber); - console.log(error.message); - ++errorsOccurred; - } - }); - - }); - return errorsOccurred; -}); - -/** - * Check if an error occurred and exit with the corresponding status code - */ -(async function main() { - const errorsFound = await 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