Skip to content

Commit f91abc2

Browse files
committed
Tutorial documentation major update
1 parent f0d5492 commit f91abc2

File tree

3 files changed

+192
-58
lines changed

3 files changed

+192
-58
lines changed
299 KB
Loading
270 KB
Loading

content/hardware/04.pro/boards/portenta-x8/tutorials/04.python-arduino-data-exchange/content.md

Lines changed: 192 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,16 @@ If the service has stopped unexpectedly, you can restart it with the following c
9292
sudo systemctl restart m4-proxy
9393
```
9494

95-
## The Arduino Sketch
95+
## Arduino Sketch: Sensor Data Over RPC
9696

9797
The Arduino sketch to read sensor data does not look much different from an ordinary sketch. The only difference is that we expose the sensor data via RPC.
9898

9999
```arduino
100-
RPC.bind("temperature", []{ return bme.temperature; });
101-
RPC.bind("humidity", []{ return bme.humidity; });
102-
RPC.bind("pressure", []{ return bme.pressure / 100.0F; });
103-
RPC.bind("gas", []{ return bme.gas_resistance / 1000.0; });
104-
RPC.bind("altitude", []{ return bme.readAltitude(SEALEVELPRESSURE_HPA); });
100+
RPC.bind("temperature", []{ return 100; });
101+
RPC.bind("humidity", []{ return 200; });
102+
RPC.bind("pressure", []{ return 300; });
103+
RPC.bind("gas", []{ return 400; });
104+
RPC.bind("altitude", []{ return 500; });
105105
```
106106

107107
Two additional header files need to be included to enable the RPC mechanism on Portenta X8:
@@ -125,13 +125,13 @@ That is because the labeled I<sup>2</sup>C pins on the Portenta Breakout are onl
125125

126126
Make sure you have installed the **Arduino Mbed OS Portenta Boards** core and upload the sketch to the X8 in the Arduino IDE or via Arduino CLI.
127127

128-
***The example code provided in the repository uses preset sensor values (returning fixed values like 100, 200, 300, etc.) for demonstration purposes. It allows you to test the RPC mechanism without connecting actual hardware sensors. To use real sensor data, you will need to modify the Arduino sketch to include your sensor library and update the `RPC.bind()` calls to return actual sensor readings instead of the preset values.***
128+
***The example code provided in the repository uses preset sensor values (returning fixed values like 100, 200, 300, etc.) for demonstration purposes. It allows you to test the RPC mechanism without connecting actual hardware sensors. To use real sensor data, you will need to modify the Arduino sketch to include your sensor library and update the `RPC.bind()` calls to return actual sensor readings instead of the preset values. For sensor implementation example please refer to [this section](#sensor-implementation).***
129129

130130
### Debugging the Arduino Sketch
131131

132-
To check if the Arduino sketch is working correctly, you may want to read the messages from the `Serial.println` statements. You cannot currently read them directly from the Arduino IDE's serial monitor. Instead, you can use a simple service called **`py-serialrpc`**, which listens for those messages and prints them to the console.
132+
To check if the Arduino sketch is working correctly, you may want to read the messages from the `Serial.println` statements. You cannot currently read them directly from the Arduino IDE's serial monitor. Instead, you can use the **`python-rpc-serial`** container, which listens for those messages and prints them to the console.
133133

134-
This service needs to run on the Linux side of the X8. You can get the files [here](https://github.com/arduino/portenta-containers/tree/main/python-rpc-serial). Clone or download the repository to your local machine, then from the command prompt, navigate to the adb tool folder and upload the files to the X8 with the command:
134+
This container needs to run on the Linux side of the X8. You can get the files [here](https://github.com/arduino/portenta-containers/tree/main/python-rpc-serial). Clone or download the repository to your local machine, then from the command prompt, navigate to the adb tool folder and upload the files to the X8 with the command:
135135

136136
```bash
137137
adb push <local directory path>/python-rpc-serial /home/fio
@@ -177,6 +177,9 @@ Or if using `docker run`:
177177

178178
```bash
179179
docker stop python-rpc-serial
180+
```
181+
182+
```bash
180183
docker rm python-rpc-serial
181184
```
182185

@@ -200,16 +203,16 @@ docker logs -f python-rpc-serial
200203

201204
If you do not wish to run the container in the background, skip the `-d` flag, you will get the console output directly in the executing shell. Once the container is running, you will see the messages being sent from the M4.
202205

203-
## The Python® Application
206+
## Python® Application: Reading Sensor Data
204207

205208
The Python® application requests the sensor data from the M4 over RPC and unpacks the message. Data can be requested by calling the function exposed over RPC on the M4, e.g.:
206209

207210
```python
208-
m4_proxy_address = 'm4-proxy'
209-
m4_proxy_port = 5001
210-
rpc_address = RpcAddress(m4_proxy_address, m4_proxy_port)
211-
rpc_client = RpcClient(rpc_address)
212-
temperature = rpc_client.call('temperature')
211+
m4_proxy_host = 'm4-proxy'
212+
m4_proxy_call_port = 5001
213+
rpc_address = RpcAddress(m4_proxy_host, m4_proxy_call_port)
214+
get_value = lambda value: RpcClient(rpc_address).call(value)
215+
temperature = get_value('temperature')
213216
```
214217

215218
You have two options to run the Python® application.
@@ -262,17 +265,7 @@ docker run -d \
262265

263266
After a few seconds, you should see the output from the Python application featuring the sensor readings on the M4 that exchanges through the RPC mechanism. The output should look similar to the following:
264267

265-
```bash
266-
python-rpc-sensors_1 | ============================================
267-
python-rpc-sensors_1 | == Portenta X8 Sensor reading ==
268-
python-rpc-sensors_1 | ============================================
269-
python-rpc-sensors_1 |
270-
python-rpc-sensors_1 | Temperature: 100
271-
python-rpc-sensors_1 | Humidity: 200
272-
python-rpc-sensors_1 | Pressure: 300
273-
python-rpc-sensors_1 | Gas: 400
274-
python-rpc-sensors_1 | Altitude: 500
275-
```
268+
![Portenta X8 RPC Example](assets/x8-python-sensor-factory.gif)
276269

277270
View the logs using:
278271

@@ -330,6 +323,174 @@ If you're wondering how to specify the Python® script to run when a container i
330323
ENTRYPOINT ["python3", "main.py"]
331324
```
332325

326+
## Sensor Implementation
327+
328+
The example provided in the repository uses preset sensor values for testing. To connect and read from actual sensors, you need to modify the Arduino sketch to include the appropriate sensor library and implement proper sensor initialization and reading.
329+
330+
### BME680 Sensor Example
331+
332+
The BME680 is an environmental sensor that provides temperature, humidity, pressure, gas resistance, and altitude readings. Here is an example implementation:
333+
334+
```arduino
335+
#include <RPC.h>
336+
#include <SerialRPC.h>
337+
#include <Wire.h>
338+
#include <Adafruit_Sensor.h>
339+
#include "Adafruit_BME680.h"
340+
341+
#define SEALEVELPRESSURE_HPA (1013.25)
342+
343+
Adafruit_BME680 bme;
344+
345+
void setup()
346+
{
347+
Serial.begin(115200);
348+
Serial.println("BME680 test on M4");
349+
350+
Wire.begin();
351+
RPC.begin();
352+
353+
Serial.println("Trying to find sensor...");
354+
355+
for (auto status = bme.begin(); !status; delay(250)) {
356+
Serial.println("Could not find a valid BME680 sensor, check wiring!");
357+
}
358+
359+
// Configure sensor oversampling and filter
360+
bme.setTemperatureOversampling(BME680_OS_8X);
361+
bme.setHumidityOversampling(BME680_OS_2X);
362+
bme.setPressureOversampling(BME680_OS_4X);
363+
bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
364+
bme.setGasHeater(320, 150); // 320°C for 150 ms
365+
366+
Serial.println("Registering RPC calls...");
367+
RPC.bind("temperature", []{ return bme.temperature; });
368+
RPC.bind("humidity", []{ return bme.humidity; });
369+
RPC.bind("pressure", []{ return bme.pressure / 100.0F; });
370+
RPC.bind("gas", []{ return bme.gas_resistance / 1000.0; });
371+
RPC.bind("altitude", []{ return bme.readAltitude(SEALEVELPRESSURE_HPA); });
372+
373+
Serial.println("Finished Init");
374+
}
375+
376+
void loop()
377+
{
378+
if (!bme.performReading()) {
379+
Serial.println("Failed to perform reading");
380+
return;
381+
}
382+
383+
Serial.print("Temperature = ");
384+
Serial.print(bme.temperature);
385+
Serial.println(" *C");
386+
387+
Serial.print("Pressure = ");
388+
Serial.print(bme.pressure / 100.0);
389+
Serial.println(" hPa");
390+
391+
Serial.print("Humidity = ");
392+
Serial.print(bme.humidity);
393+
Serial.println(" %");
394+
395+
Serial.print("Gas = ");
396+
Serial.print(bme.gas_resistance / 1000.0);
397+
Serial.println(" KOhms");
398+
399+
Serial.print("Approx. Altitude = ");
400+
Serial.print(bme.readAltitude(SEALEVELPRESSURE_HPA));
401+
Serial.println(" m");
402+
403+
Serial.println();
404+
delay(1000);
405+
}
406+
```
407+
408+
This sketch includes sensor initialization with error checking, configuration of oversampling rates, and continuous sensor readings in the loop that can be monitored through the serial output or accessed via RPC from the Python application.
409+
410+
### BME280 Sensor Example
411+
412+
The BME280 provides temperature, humidity, pressure, and altitude readings but lacks a gas sensor. Here is an example implementation:
413+
414+
```arduino
415+
#include <RPC.h>
416+
#include <SerialRPC.h>
417+
#include <Wire.h>
418+
#include <Adafruit_BME280.h>
419+
#include <Adafruit_Sensor.h>
420+
421+
#define SEALEVELPRESSURE_HPA (1013.25)
422+
423+
Adafruit_BME280 bme;
424+
425+
void setup()
426+
{
427+
Serial.begin(115200);
428+
Serial.println("BME280 test on M4");
429+
430+
Wire.begin();
431+
RPC.begin();
432+
433+
for (auto status = bme.begin(); !status; delay(250)) {
434+
Serial.println("Could not find a valid BME280 sensor, check wiring!");
435+
Serial.print("SensorID was: 0x");
436+
Serial.println(bme.sensorID(), 16);
437+
}
438+
439+
Serial.println("Registering RPC calls...");
440+
RPC.bind("temperature", []{ return bme.readTemperature(); });
441+
RPC.bind("humidity", []{ return bme.readHumidity(); });
442+
RPC.bind("pressure", []{ return bme.readPressure() / 100.0F; });
443+
RPC.bind("gas", []{ return 0; }); // BME280 has no gas sensor
444+
RPC.bind("altitude", []{ return bme.readAltitude(SEALEVELPRESSURE_HPA); });
445+
446+
Serial.println("Finished Init");
447+
}
448+
449+
void loop()
450+
{
451+
Serial.print("Temperature = ");
452+
Serial.print(bme.readTemperature());
453+
Serial.println(" *C");
454+
455+
Serial.print("Pressure = ");
456+
Serial.print(bme.readPressure() / 100.0F);
457+
Serial.println(" hPa");
458+
459+
Serial.print("Humidity = ");
460+
Serial.print(bme.readHumidity());
461+
Serial.println(" %");
462+
463+
Serial.print("Approx. Altitude = ");
464+
Serial.print(bme.readAltitude(SEALEVELPRESSURE_HPA));
465+
Serial.println(" m");
466+
467+
Serial.println();
468+
delay(1000);
469+
}
470+
```
471+
472+
For the BME280, a dummy gas RPC binding that returns 0 is included since this sensor does not have gas sensing capabilities. This provides compatibility with the Python script that expects all five RPC calls.
473+
474+
![Portenta X8 RPC](assets/x8-rpc-c.gif)
475+
476+
### Python Script Considerations
477+
478+
The Python script in the repository uses an optimized approach for making multiple RPC calls:
479+
480+
```python
481+
def get_data_from_m4(rpc_address):
482+
data = ()
483+
sensors = ('temperature', 'humidity', 'pressure', 'gas', 'altitude')
484+
try:
485+
get_value = lambda value: RpcClient(rpc_address).call(value)
486+
data = tuple(get_value(measure) for measure in sensors)
487+
except RpcError.TimeoutError:
488+
print("Unable to retrieve data from the M4.")
489+
return data
490+
```
491+
492+
This approach creates a new `RpcClient` instance for each call due to a known limitation with the `msgpackrpc` library. The lambda function and tuple comprehension provide a clean way to collect all sensor readings.
493+
333494
## Troubleshooting
334495

335496
### RPC Communication Issues
@@ -374,7 +535,9 @@ Flash the firmware using the programming script:
374535
sudo /usr/arduino/extra/program.sh
375536
```
376537

377-
The programming script will verify and flash the new firmware. You should see output indicating the programming progress, verification, and successful reset. After flashing completes, restart the X8 and try rerunning your Python application.
538+
The programming script will verify and flash the new firmware. You should see output indicating the programming progress, verification, and successful reset. After flashing completes, restart the X8 and try rerunning your Python application or [example](#building-the-image-from-source).
539+
540+
![Portenta X8 STM32H7 firmware flashed](assets/stm32h7-flash-rpc.png)
378541

379542
### Building Firmware From Source
380543

@@ -406,41 +569,12 @@ Replace `YOUR_USERNAME` with your actual Windows username. Build the firmware:
406569
make
407570
```
408571

409-
### Understanding the Example Code
410-
411-
The `python-rpc-sensors` example uses preset sensor values for demonstration purposes. The Arduino sketch returns fixed preset values (100, 200, 300, 400, 500) rather than reading from actual hardware sensors. It allows you to test the RPC mechanism without connecting physical sensors.
412-
413-
To use real sensor data, you need to modify the Arduino sketch to include your sensor library (such as `Adafruit_BME680` or `Adafruit_BMP280`) and update the `RPC.bind()` calls to return actual sensor readings. For example, with a real BME680 sensor:
414-
415-
```arduino
416-
#include <Adafruit_BME680.h>
417-
Adafruit_BME680 bme;
418-
419-
void setup() {
420-
bme.begin();
421-
RPC.bind("temperature", []{ return bme.temperature; });
422-
RPC.bind("humidity", []{ return bme.humidity; });
423-
RPC.bind("pressure", []{ return bme.pressure / 100.0F; });
424-
RPC.bind("gas", []{ return bme.gas_resistance / 1000.0; });
425-
RPC.bind("altitude", []{ return bme.readAltitude(SEALEVELPRESSURE_HPA); });
426-
}
427-
```
428-
429-
If you are using a BMP280 sensor instead of a BME680, be aware that the BMP280 provides temperature, humidity, pressure, and altitude readings but lacks a gas resistance sensor. The example Python script calls all five RPC functions, including `gas`, which will cause a timeout error if your Arduino sketch does not implement it.
430-
431-
To use a BMP280 sensor, you can either modify your Arduino sketch to provide a dummy gas value by adding:
432-
433-
```arduino
434-
RPC.bind("gas", []{ return 0; });
435-
```
436-
437-
Since the BMP280 does not have a gas sensor, you can modify the Python script to skip the gas reading by commenting out the gas RPC call. The BMP280 does support altitude calculation, so you can include that RPC call in your BMP280 sketch if needed.
438-
439572
## Conclusion
440573

441-
In this tutorial, you learned how to use the Docker infrastructure to run a Python® application on the Portenta X8. You explored two approaches to running the application: using a prebuilt Docker image for quick deployment and building the image from source for customization. You have also learned how to use the RPC mechanism to exchange data between the microcontroller and the iMX8, which runs Linux.
574+
In this tutorial, you learned how to use the Docker infrastructure to run a Python® application on the Portenta X8. You explored two approaches to running the application: using a prebuilt Docker image for quick deployment and building the image from source for customization. You have also learned how to use the RPC mechanism to exchange data between the microcontroller and the iMX8, which runs Linux, and how to implement real sensor readings using BME680 and BME280 sensors.
442575

443576
### Next Steps
444577

445578
- You may further process the data you receive from the Arduino sketch and, e.g., upload it to a Cloud service or similar.
446579
- Familiarize yourself with Docker commands to adjust the docker configuration to your needs.
580+
- Experiment with other sensors and create custom RPC bindings for your specific use case.

0 commit comments

Comments
 (0)