From bf37f33d1f95cec5a0bb44630bdcac171e0d6d74 Mon Sep 17 00:00:00 2001 From: TaddyHC Date: Wed, 3 Dec 2025 18:32:33 -0600 Subject: [PATCH 1/4] Portenta X8 RPC Links and example update --- .../assets/py-serialrpc.zip | Bin 1685 -> 0 bytes .../assets/python-sensor-rpc.zip | Bin 8805 -> 0 bytes .../content.md | 42 +++++++++--------- 3 files changed, 21 insertions(+), 21 deletions(-) delete mode 100644 content/hardware/04.pro/boards/portenta-x8/tutorials/04.python-arduino-data-exchange/assets/py-serialrpc.zip delete mode 100644 content/hardware/04.pro/boards/portenta-x8/tutorials/04.python-arduino-data-exchange/assets/python-sensor-rpc.zip diff --git a/content/hardware/04.pro/boards/portenta-x8/tutorials/04.python-arduino-data-exchange/assets/py-serialrpc.zip b/content/hardware/04.pro/boards/portenta-x8/tutorials/04.python-arduino-data-exchange/assets/py-serialrpc.zip deleted file mode 100644 index b79e56f25f34ce76ede2e15f67f7098aa69104ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1685 zcmWIWW@Zs#U|`^2u#Bw;iR~+YJQ>LQ1jO<{Tu`Z7oLZEbm{U}cte=vfoSj;vo1C9p zkYAjtSDBj=+INw!$$-c8{@<>)@@ED@lm79nyzpXDukPJ|Z{H6EH0%4mdwDWf`SfKT znU5?2MY54Rk(`I8G3IUFsN7`rzvAGri*rAo*N?GKo7Cd)&DBzDT9BKE(dXj}Hh!OM zyQxywaYadJ(1%Hn>h`v+y1MIV?i=s8Ndf;qd|a?2TlR>)zT+{Kx- z|FaK7E%DC}fcl34>>r>P;6NP6Lh%p6XE05WKv=WmlF|;K;+H_Ih+C5jI5^TWb5c|L z&w4o<3b=j$EaH4ktz19qmH*VF>U>U7&7(&ruvt%-?W4Z)!oIy;cX$4HkaMo_{Zanx zDc;u8YP>xy+#9YQew@^*T$pFyU6mg<({14-u63I1=X%QH^Y%QN^TnW0Pp@Sr;}3~H zw?AJ>HV=Kfs;$q3VP4x=KGS(>*DQ|RS(W{~Z5{Lfdy6+HJin!(y2VUfT0>^B?S@5H z>#xPXG4JI|xS0Pp&sgWT?z@XI^Jhq#m}&(Far)`8**}zqd7FXZW{Y^_xC4NPD_bKyW5$iiVsaZ$29Od}*xZcm&3)exw`2#7IKCL$(58uijMOT5qPc%IbR z6m;S8W%h+%&ItXz^JmJXIaiYYHl3Q(^T+1yqNbF)SGtbeX_^!A_tByhJqA?U3d=-7 z1Q{6^{sS?dumITxw_C5EGB@C0)?ovly}z|x^ve`l@-BEUY`^IL!mwqcrQfTwoy~8= zyk6}0@7lbj^`+A9moGj2x5?YOy>K?a_KV$JH^=FaZiiSc|HW@?ViWE=u{4J_7bN%v zCYS5p*D}1$pU}P{?8n|yEfNzqhAmrX>+`bkd}H*B^Dp%}SI#wM?zY*c*=M|L-h}>T zj>1h|MxLstSrWyMpY>dNVQaD1qL(=xY^i*^-5YNuSZxAR;AzTH? jk?45@VHgK9URS{*1DIn1yjj^m8rgwR5$J|E77z~rm^??8 diff --git a/content/hardware/04.pro/boards/portenta-x8/tutorials/04.python-arduino-data-exchange/assets/python-sensor-rpc.zip b/content/hardware/04.pro/boards/portenta-x8/tutorials/04.python-arduino-data-exchange/assets/python-sensor-rpc.zip deleted file mode 100644 index 45f3aa0061eb622e8cc1e01068b4882e55d09a72..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8805 zcmb_h1z1$u79LV!grO0nyE}%?E1g5fkTL_q4BdiABi$e(AV`NwDqRvHAO;GO0umwy zVE}KqUPUjw`{{jef8XJpIrGi`ueJ8SH*2q@r-_A44mcbI;Z$aSKK%8E7JYZ~_ky92 ze4dU-Pm~9rhnu|s6soPLtZQg0p!Z*Y$oJ1bJdXJd3J);Pn+r*x9GtU6L)4-#$=`+I zhZ?9Vs%We7yE@q0P}n=!*x77qYZ-468%OJ1#7v^b;sDWNxYL2cc4?`jF%y`W6!n&H z8La_5m@o}$l6ixHkIexwI5^<40B}q%TXAn07gr2h*&s$IV?@ObN48K{8J>3S8NfG- zk4H`HcP+N{aPWpBQU4H6<$nvQEeciARuz^Mgc|56e<%1FW#H+}*H&oY!zdg7e+c-Y zKN0eyz50L8fvuL2I%7cjwiPnzpC&=*cOCNdu>T=G26$H?g{nkb-{Pb6Z{YZ$uA)#c z6!fdy`Q7~g-?7M5ELLYUpQ@vJNs9Y}AQLdkA!vKQ4_5Tk@g;Zs&5@*ArE`Bm;Qd8} z<4QhU&fg6|KS=#1(ocH&VS)~E4Lr;aA~65}dn^Dz^MAtqiRDnW-Uiybm_ne|H#O85 zni9_xGC75q+3{$=HqkH{YI|?e5 zc_#LfH(=Y*63FTZoA{_jBlO`~WQ{;_u~en?DW{Fg+RX|fy$|aI*asZ37O*%CEwdo` zqa)&4v`Lt_R0WPwcFAt-uT{7eSS3j7nAo3=`s6jTmZmLI>Sd%*I9twhUJ~vq8?(wT zArBW7oV$b>ilgr8^G1#8a;&Y4{Q}C=qavqIfzwE9ZK8YhjnO`kAwj{mc`cYYLj{Vp z558A--je9`3TG@6JYa*J^EQ=cgSftFB+%aPz%=X@LoxRHP{E6a@th_c+8M_ZaYUYw zqco?_l#-AV)Caq9e1Yh8&;u@sit%^n67!y%NBTRRBbIe0d&Jb)6v;5i&LQCB5G}lI zIAtuck2K`+b+WKlyhQkg>PV0!f0OjTlactbeee)hT;XDWFD3whJoRs7^bPl`jQ;v` zD5D4?IO@3qXm0~5{LrA^lkf z<=NA~1tBM)GzcWZk+ueRw#4R{E4`u1qr^Mbr!Li8{~Yr|gyve>g3Ks*s1mtz-_y#g z%AeqYaI!;2Sqi>6LBB|oteBAvixny2W_2yu{{36v=(Bn@R@UR7dvLBC`P;c+s@y~)52U7rG3($%r?7Yidi)o>sw1ZE#Q@v!b^8qW6q^{*H#({hs%EZYZ802vz&FJue;^yuR)mOG&Mx z;z;T#tuOB6xbl0i97Og-xN5jM{nmOsrAK=)L<6G^=uYRnEx>>H5N2Tn9l#Qw5ngTCyPxzS8E9$cUjr2jTI0)9_&(0f;aNxH`A;KhG&bN`FS9_8wW@^s|)cST%Cyy@Jn0F2y`iBMY_((m_5ViPSe zG}jPuATY7GNsL%Y@E8dd47vh7#akmodPVa!YdHu|0mR#s!3+ zf`!~u6Ml%Mk`>3c{%T6}9<=6iq;u*X%fXf8&!ApR@>RXZW$q75b%AiTu174-EKhZG zPJUci-13v!(245azi`lUA!oG1YGoO^l*>@)$MuqeM{#`C{z04Fof~~4%P97@4{tpe zaD9fdq-=|y!?5&3eWJLxI8S>N73qLnNQnJZClno#W%Pc<|BF#k`7`?G;RweF{UM!h zK~T$Q9%I=L;dBLV1*E5})45d)BbXr=vAI26m=JV#&;jysKwM;uc=f zYb{*XNL2FCX%;phYzn$qVP2k_wChfw|b-&Koky@x@W}M#n5XQ~JAX%WP zDlJ^@;N0vQ%z5i<0#*Q3m7i-CoN>7L{lFcN((6${AJ%9~M01qHfKR3x*jTqs&D zBPDP2VyjH$jK?`YR3%R0py<5ZY+%7=@Y!PH%5(#d0^A$ye<$MI6o31`s=;h7PTHXYPwGDRS1Gj6#OqBXLj;88xp_FO9fyCnh01}jF z5&z;;eLuPMn!Z8OHj^AwmoVmH*5lMTShV$!%mW&Vc7y6!B_6V6(h~^O7qRB~W?H21 zTy-h=fyPpns&GApdiwyor>K&(6-k|MeC?b&lY9s=oeWd$#z2;XHJ-hLUY z1r$@d5#Lu~;*eAwo&v`dEz%7|Ry!zNtn~5l(_t=?6ncoX;>x%TNwEO4wVEzfj%D#j zuy0Z|5?g};+_X5ymiH`HOw%X^$WVNLA8t#yMoLqu1vRb&j@+eb-7l8&?`lRLkUZ6jAG1%oHn~p zSL$Tpb4K6-eb&^tSaP^5@m{}n9jkiv>1_8Dr<+BUg`jsmBbLihrwC%4?Nu1RvS3f7@ zp_~4!4&^tP9zAQoLo!$Y*1CC#yBOcMy1=}q*rTuNIc@O(g<$Tqc8h795?RH|^8s1v!mr z{BfT7VFckl&t$ft3m06;aH>7xk0-8wBA=9!$FkyVZAbVRAON zDxxClFp<1 zmX67@c0#M@{#uJ8#FC7NhLdONye8&y9&Vb6x2h^azgE(`hEgEMSKTCtgNm%w5kHNM zj?Z5yv~$+Uxk5BDVW#q9-6UQ&sGX)=6z>aI!BqZLO4t^uO?E|o>00N&O6Se%AmEO! z>2jL)#H3B|A0uJ!b-ZX=ib;s4+g3KmpK>4=7YHpC{ z;4;dT3oCVNDZ?6Bv@t*jIi9xO3!%rDml|KCaDnWE6`G!TE6qAF*F^%>%-x9>#$IIFjUa?2 z-_Id~#Q2Qg0hMy((;~_=bylUH+m!Q(TM5(4erRi@c>yHAtQiX%?^%bS!d^f}B~3vFSqW+q}rIjwdDgTwkDi_xW8z467Iqp=nr91Lw9~*u}?Gh1~P^91Qcz zcL9*gjFr~| zSxfM`DXtPDtw%Ow_9V}o{AX{|^r;z%V^QABSL{GN)m<2d_h!N9JR~tpKaCNCw&<6! zU|T!g#X{K|4U<|*m%%%(`kMXXH_F6aQY(-4k)OYiKBGGo(X?NQab=TF`c*exdv7R=KaXt6xV}d)U}Csw;Zs zkC3O{A4$(_^dwieNoY-3U|?H*H0Y>6;rN_?vEAVKc7s^d*xxYucGZFEq)ChN=Czv? zy(Yvs7-6>s9QF;mfbZKw_Gmp^2}4bhLFNmMQP)NdC`W`9UVXCdwQnmFyqBiSwRlBe z=#3&)mXctlO`KiTd^SF)2Z}4bTP`dA*-ZAnc)kq{Ik9qEDA2{8I{s}8%y1RO$M(C1+_v!u%R(kqwzCQRvOH+ck2TB)TB2tXP+|EPZ%UFiZSd zq{}Oj4k^8!ot%wbI^f8g#2IzV@B^O6Am|7q@p4feE#J(>Cx`$aBSj*V0#&Jcb^$9- zcvB!(Yp{>Z?wNKvxq)Wkf}5XDygHo*PC|b(&Z}u3&Vw=O0dKD==I0jrk0?oH!kfz` zCnH_f9?$p#`oO79=Tym3$0B+|;^->c?-Kce9^FYczP@JJgK&wTN$Hm8V^Tz zZ@7n}t0U6Oli$nF>-3-y(M3ieprbW}D^l=1q3<{&0mgFz*2ozbWUH0bIM=kaeL=hh z7x_Ak-czkEklnS9gZXLLlOh~hRh$eg9u=_RH#J0NlFV;~QRSN$-x4o$vW3o((KdY_ zoam?H=aJ)Yj#vLmN{bOq=qx)6T@{e{4V1r?{H_|gx=ad!_BWvJehhZR5=9mSOi79> z4ahh}mtwA~0Hn6gZ`3cmsDxtE zq)*qnDABeiYE&vFc-mLky#x>Gkx=aZWC?`9&6ali!p*>u_tTvwZQbOg`pDe|gZpm! zmmFd#V0!SHHb;!x&k(Bv&R0FJ_O`G-B^w$DcH}ZX(>21yn?CV6v^h?r2NFh7`v+%M zn%4lC)210~V(A|=ZcgN3TCnS_b~Ku_XB#^SsVT-UKNnwZ<5vo>|MsuYR9UINX}-p9Am{ zwi0I*JUMjvmN7AXMV)6#Ul?#`kMVuVA;gu4r1RMf#ol1Khd=b*M$|2F%BZHbAbpMe z!df7YZ^aj`jVC+C$bcThI)`8$dkcaRn8i%eWb516(}g<2rJuL^&(nCn)Rx>JO}jzM zzd#Ysr=R^vkfHzFILl^8y0_t|U%rq27ksGb+JrA$Y!C7oZu9nZkGuLfSAwmgJ#zwK z-;gAIGp_S)cjq*;#qQ&|Wd1caZw&l)G%`=Lqn-+>N~MPJ7u5Ey+OUzy*5j(>GzEK zzp&A_@ZW|0p^0?Fa&oKZE6bq+;^>3*wn@q=@;q6hBqC(RK81 zgeSLxzEJ`ItN$Ou6B<&xPINuk6b*doAZqT03blO4}Kf| zSa0V?cTQ@+9JzDvM84w|{D^o`L*@t(ANwQ^jw7C=x04z)N63c1iTuwp`+LLYkO!?V z^r-lscz$9%th@ghx|3Q5e{up2wUvYWd!hSE^{wE4#DfPosg-cZ^VPw3{}azotbcd# zq^j%@F(bjRIe4r_`=f6s6=aVnqJNL#hw|)?94D1Gk2vxOf6a}fV&{+8C)MMQu+>lh z8us_<+>g*FrNm#M8PPW;bn)-_Iz0a7?+Nk|!^t`AAp_t~0eAf(!%2JM-*enU%&&6A hqxv2C sensor, you can connect SCL to **PWM6** and SDA to **PWM8** on the Portenta breakout. +You can find the sketch and complete example [here](https://github.com/arduino/portenta-containers/tree/main/python-rpc-sensors). You may need to change the sketch depending on the choice of the sensor to read from. If you're using an I2C sensor, you can connect SCL to **PWM6** and SDA to **PWM8** on the Portenta breakout. That is because the labeled I2C pins on the Portenta Breakout are only available on the Linux side. If you are using an analog sensor, you can connect it to any analog pin. Please refer to the pinout diagram on the Portenta Breakout [documentation page](/hardware/portenta-breakout). @@ -129,16 +129,16 @@ Make sure you have installed the **Arduino Mbed OS Portenta Boards** core and up 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 in the serial monitor of the Arduino IDE. Instead, you can use a simple service called **`py-serialrpc`**, which listens for those messages and prints them to the console. -This service needs to run on the Linux side of the X8. You can get the files [here](assets/py-serialrpc.zip). The compressed file will have every file needed to build a container as the docker compose app. From the command prompt of your local machine, navigate to the adb tool folder and upload the files to the X8 with command: +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 command: ```bash -adb push /py-serialrpc /home/fio +adb push /python-rpc-serial /home/fio ``` -Log into the X8 shell with `adb shell` and navigate into the `serialrpc` folder. Build the container using +Log into the X8 shell with `adb shell` and navigate into the `python-rpc-serial` folder. Build the container using ```bash -docker build . -t py-serialrpc +docker build . -t python-rpc-serial ``` The `-t` flag assigns a tag to the container. Then run the container by executing `cd..` and then: @@ -181,16 +181,16 @@ rpc_client = RpcClient(rpc_address) temperature = rpc_client.call('temperature') ``` -The complete Python® application files are in the same package as the Arduino sketch (see above). Like in the previous step, upload the `python-sensor-rpc` folder to the Portenta X8 via: +The complete Python® application files are available in the repository [here](https://github.com/arduino/portenta-containers/tree/main/python-rpc-sensors). Clone or download the repository to your local machine, then upload the `python-rpc-sensors` folder to the Portenta X8 via: ```bash -adb push /python-sensor-rpc /home/fio +adb push /python-rpc-sensors /home/fio ``` -Log into the X8 via `adb shell`. Then navigate into the `python-sensor-rpc` folder and execute: +Log into the X8 via `adb shell`. Then navigate into the `python-rpc-sensors` folder and execute: ```bash -docker build . -t python-sensor-rpc +docker build . -t python-rpc-sensors ``` When it has finished, you can run the container with: @@ -202,22 +202,22 @@ docker compose up 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: ```bash -python-sensor-rpc_1 | ============================================ -python-sensor-rpc_1 | == Portenta X8 Sensor reading == -python-sensor-rpc_1 | ============================================ -python-sensor-rpc_1 | -python-sensor-rpc_1 | Temperature: 25.904266357421875 -python-sensor-rpc_1 | Humidity: 25.564695358276367 -python-sensor-rpc_1 | Pressure: 976.4400024414062 -python-sensor-rpc_1 | Gas: 136.496 -python-sensor-rpc_1 | Altitude: 311.0769348144531 +python-rpc-sensors_1 | ============================================ +python-rpc-sensors_1 | == Portenta X8 Sensor reading == +python-rpc-sensors_1 | ============================================ +python-rpc-sensors_1 | +python-rpc-sensors_1 | Temperature: 25.904266357421875 +python-rpc-sensors_1 | Humidity: 25.564695358276367 +python-rpc-sensors_1 | Pressure: 976.4400024414062 +python-rpc-sensors_1 | Gas: 136.496 +python-rpc-sensors_1 | Altitude: 311.0769348144531 ``` Whenever you change anything in the Python® script on your computer, you will have to resync and push the new script to the Portenta X8 and rebuild the container. Following command sequence will help you to do this process: ```bash # On your computer -adb push python-sensor-rpc /home/fio +adb push python-rpc-sensors /home/fio ``` ```bash @@ -227,7 +227,7 @@ docker compose down ```bash # On the Portenta X8 -docker build . -t python-sensor-rpc +docker build . -t python-rpc-sensors ``` ```bash From ff130f866230d11fc58d4d58ce7b95e210161544 Mon Sep 17 00:00:00 2001 From: TaddyHC Date: Wed, 3 Dec 2025 18:56:20 -0600 Subject: [PATCH 2/4] Update for prebuilt image for OS 934 compatibility --- .../04.python-arduino-data-exchange/content.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/content/hardware/04.pro/boards/portenta-x8/tutorials/04.python-arduino-data-exchange/content.md b/content/hardware/04.pro/boards/portenta-x8/tutorials/04.python-arduino-data-exchange/content.md index c247fc1648..29fe748181 100644 --- a/content/hardware/04.pro/boards/portenta-x8/tutorials/04.python-arduino-data-exchange/content.md +++ b/content/hardware/04.pro/boards/portenta-x8/tutorials/04.python-arduino-data-exchange/content.md @@ -181,6 +181,22 @@ rpc_client = RpcClient(rpc_address) temperature = rpc_client.call('temperature') ``` +You have two options to run the Python® application. For a quick setup, you can use the pre-built `arduino/python-rpc-sensors` image that's maintained. This makes sure you always have the latest compatible version: + +```bash +adb shell +``` + +Pull and run the pre-built image using following command: + +```bash +docker run --rm --network=host arduino/python-rpc-sensors +``` + +This approach is recommended as it's simpler and always uses the latest tested version compatible with the current OS. + +If you want to modify the Python® script or learn how the container is built, you can build the image from source. + The complete Python® application files are available in the repository [here](https://github.com/arduino/portenta-containers/tree/main/python-rpc-sensors). Clone or download the repository to your local machine, then upload the `python-rpc-sensors` folder to the Portenta X8 via: ```bash From fd2dcccc528db681dbdf25a242b296b0302bae2f Mon Sep 17 00:00:00 2001 From: TaddyHC Date: Thu, 11 Dec 2025 18:46:55 -0600 Subject: [PATCH 3/4] Tutorial documentation update (WIP) --- .../assets/STM32H747AII6_CM7.bin | Bin 0 -> 272316 bytes .../content.md | 259 +++++++++++++++--- 2 files changed, 218 insertions(+), 41 deletions(-) create mode 100644 content/hardware/04.pro/boards/portenta-x8/tutorials/04.python-arduino-data-exchange/assets/STM32H747AII6_CM7.bin diff --git a/content/hardware/04.pro/boards/portenta-x8/tutorials/04.python-arduino-data-exchange/assets/STM32H747AII6_CM7.bin b/content/hardware/04.pro/boards/portenta-x8/tutorials/04.python-arduino-data-exchange/assets/STM32H747AII6_CM7.bin new file mode 100644 index 0000000000000000000000000000000000000000..5a0839cdb218c4268e4f4e6c8afb4c75748670cb GIT binary patch literal 272316 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 V1Rwwb2tWV=5P$##An>~d{tGMZMdkni literal 0 HcmV?d00001 diff --git a/content/hardware/04.pro/boards/portenta-x8/tutorials/04.python-arduino-data-exchange/content.md b/content/hardware/04.pro/boards/portenta-x8/tutorials/04.python-arduino-data-exchange/content.md index 29fe748181..0eb75173fb 100644 --- a/content/hardware/04.pro/boards/portenta-x8/tutorials/04.python-arduino-data-exchange/content.md +++ b/content/hardware/04.pro/boards/portenta-x8/tutorials/04.python-arduino-data-exchange/content.md @@ -14,11 +14,11 @@ hardware: ![Portenta X8 RPC](assets/x8-rpc-c.gif) -The container infrastructure provided by Arduino contains a pre-built Python® image that you can use to run Python® applications on the Portenta X8. In this tutorial, we are going to build a container based on a provided one. +The container infrastructure provided by Arduino includes a prebuilt Python® image you can use to run Python® applications on the Portenta X8. In this tutorial, we are going to build a container based on a provided one. -While all the peripherals are accessible from the iMX8 processor running the Linux environment, it can be useful to let the onboard microcontroller take care of certain peripheral handling and exchange only the required data between the microcontroller and the Python® application. +While all peripherals are accessible from the iMX8 processor running the Linux environment, it can be useful to let the onboard microcontroller handle certain peripheral operations and exchange only the required data between the microcontroller and the Python® application. -You will be guided on how to achieve this setup. It is recommendable to familiarize yourself with the foundational elements of the Portenta X8 and its infrastructure by reading [fundamentals of the Portenta X8](https://docs.arduino.cc/tutorials/portenta-x8/x8-fundamentals) if you have not already done so. +You will be guided on how to set up this. It is recommended to familiarize yourself with the foundational elements of the Portenta X8 and its infrastructure by reading [fundamentals of the Portenta X8](https://docs.arduino.cc/tutorials/portenta-x8/x8-fundamentals) if you have not already done so. ## Goals @@ -43,9 +43,9 @@ The two processors within the Portenta X8 require a communication mechanism to e It is particularly useful for distributed computing in a client-server model. The **`procedure call`** behaves as a **`request`** from the client, and the **`return value`** serves as the server's **`response`**. This model uses multiple computers connected over a network (often the Internet) to solve large computational tasks. -While RPC aims to closely replicate local procedure calls, complete equivalence is not possible due to network communication challenges, which can introduce communication failures. To manage these issues, different RPC mechanisms adopt distinct semantics: +While RPC aims to closely replicate local procedure calls, complete equivalence is not possible due to network communication challenges that can cause failures. To manage these issues, different RPC mechanisms adopt distinct semantics: -- **`At most once` semantics** ensures that a remote call may fail but will not be run multiple times. +- **`At most once` semantics** ensures that a remote call may fail, but will not be run multiple times. - **`At least once` semantics** guarantees that the call is made at least once, even if it results in multiple activations. The Portenta X8 uses **MessagePack-RPC** for its communication (see the [library repository](https://github.com/msgpack-rpc/msgpack-rpc-python) for details). *MessagePack-RPC* relies on *MessagePack* as the serialization protocol, encoding data in *MsgPack* format, and is supported over: @@ -74,13 +74,13 @@ Python® is a modern and powerful scripting language used for a wide range of ap ### Communication Between Linux and Arduino Sketches -The Python® script will run on the Linux side and therefore on the iMX8 processor. The Arduino sketch, on the other hand, will run on the STM32H747 microcontroller. It allows for real-time processing on the Arduino side while running a fully-fledged operating system on iMX8. +The Python® script will run on the Linux side and therefore on the iMX8 processor. The Arduino sketch, on the other hand, will run on the STM32H747 microcontroller. It enables real-time processing on the Arduino side while running a fully-fledged operating system on the iMX8. -However, the two processors need a communication mechanism to exchange data with one another. **RPC (Remote Procedure Call)** is the communication mechanism for this task. To establish communication, the M7 core on the STM32H747 microcontroller is used to hand over any data/request to the M4 core. That means your Arduino sketch will solely run on the M4 core. Dual-core processing on the Arduino side is currently not supported. +However, the two processors need a communication mechanism to exchange data. **RPC (Remote Procedure Call)** is the communication mechanism for this task. To establish communication, the M7 core on the STM32H747 microcontroller is used to transfer data or requests to the M4 core. That means your Arduino sketch will solely run on the M4 core. Dual-core processing on the Arduino side is currently not supported. -On the Linux side, there is a service that takes care of sending data between the two worlds. It is called **`m4-proxy`**. +On the Linux side, a service handles sending data between the two worlds. It is called **`m4-proxy`**. -You can check if the service is running by logging into the X8 via `adb shell` and then executing the next command: +You can check if the service is running by logging into the X8 via `adb shell` and then executing the following command: ```bash sudo journalctl -fu m4-proxy @@ -94,7 +94,7 @@ sudo systemctl restart m4-proxy ## The Arduino Sketch -The Arduino sketch to read sensor data doesn't look much different from an ordinary sketch. The only difference is that we expose the sensor data via RPC. +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. ```arduino RPC.bind("temperature", []{ return bme.temperature; }); @@ -111,13 +111,13 @@ Two additional header files need to be included to enable the RPC mechanism on P #include ``` -The `RPC.bind()` method makes the data available via the specified name e.g. "temperature". In our example, an anonymous function is created to return the corresponding sensor property whenever requested. +The `RPC.bind()` method makes the data available under the specified name, e.g., "temperature". In our example, an anonymous function is created to return the corresponding sensor property whenever requested. -Alternatively, you could bind the name to an existing, named function instead. The data can then easily be requested using that name (e.g. "humidity") by querying the `m4-proxy` service. Once data is requested, it is packaged as a message and sent over SPI to the iMX8. +Alternatively, you could bind the name to an existing named function. The data can then be easily requested using that name (e.g., `humidity`) by querying the `m4-proxy` service. Once data is requested, it is packaged as a message and sent over SPI to the iMX8. ![The iMX8 and the STM32H747 processor communicate via SPI](assets/component-placement.svg) -You can find the sketch and complete example [here](https://github.com/arduino/portenta-containers/tree/main/python-rpc-sensors). You may need to change the sketch depending on the choice of the sensor to read from. If you're using an I2C sensor, you can connect SCL to **PWM6** and SDA to **PWM8** on the Portenta breakout. +You can find the sketch and complete example [here](https://github.com/arduino/portenta-containers/tree/main/python-rpc-sensors). You may need to change the sketch depending on the sensor you choose to read from. If you are using an I2C sensor, you can connect SCL to **PWM6** and SDA to **PWM8** on the Portenta breakout. That is because the labeled I2C pins on the Portenta Breakout are only available on the Linux side. If you are using an analog sensor, you can connect it to any analog pin. Please refer to the pinout diagram on the Portenta Breakout [documentation page](/hardware/portenta-breakout). @@ -125,17 +125,19 @@ That is because the labeled I2C pins on the Portenta Breakout are onl 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. +***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.*** + ### Debugging the Arduino Sketch -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 in the serial monitor of the Arduino IDE. Instead, you can use a simple service called **`py-serialrpc`**, which listens for those messages and prints them to the console. +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. -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 command: +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: ```bash adb push /python-rpc-serial /home/fio ``` -Log into the X8 shell with `adb shell` and navigate into the `python-rpc-serial` folder. Build the container using +Log into the X8 shell with `adb shell` and navigate into the `python-rpc-serial` folder. Build the container using: ```bash docker build . -t python-rpc-serial @@ -147,7 +149,23 @@ The `-t` flag assigns a tag to the container. Then run the container by executin docker compose up -d ``` -The `-d` flag detaches the container so it runs in the background. Note that this will run the docker compose app and have the container built persistently across reboots by registering it as a systemd service. +The `-d` flag detaches the container so it runs in the background. Note that this will run the Docker Compose app and have the container built persistently across reboots by registering it as a systemd service. + +If you encounter issues with `docker compose` (such as `invalid reference format` errors), you can run the container directly using: + +```bash +docker run -d \ + --name python-rpc-serial \ + --restart unless-stopped \ + -e PYTHONUNBUFFERED=1 \ + --tty \ + --read-only \ + -p 5002-5020:5002-5020 \ + -v /tmp:/tmp \ + --device /dev/ttyGS0 \ + --add-host m4-proxy:host-gateway \ + python-rpc-serial:latest +``` To stop the container, run: @@ -155,23 +173,36 @@ To stop the container, run: docker compose stop ``` +Or if using `docker run`: + +```bash +docker stop python-rpc-serial +docker rm python-rpc-serial +``` + Check if the container is running by executing: ```bash docker ps ``` -You can then access the log of its service at any time by using following command from the **same directory**: +You can then access the log of its service at any time by using the following command from the same directory: ```bash docker compose logs -f --tail 20 ``` +Or if using `docker run`: + +```bash +docker logs -f python-rpc-serial +``` + 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. ## The Python® Application -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.: +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.: ```python m4_proxy_address = 'm4-proxy' @@ -181,23 +212,21 @@ rpc_client = RpcClient(rpc_address) temperature = rpc_client.call('temperature') ``` -You have two options to run the Python® application. For a quick setup, you can use the pre-built `arduino/python-rpc-sensors` image that's maintained. This makes sure you always have the latest compatible version: +You have two options to run the Python® application. -```bash -adb shell -``` +### Using the Pre-built Docker Image -Pull and run the pre-built image using following command: +For a quick setup, you can use the pre-built `arduino/python-rpc-sensors` image that Arduino maintains. This way, you always have the latest compatible version. Log into the X8 via `adb shell` and then pull and run the pre-built image using the following command: ```bash docker run --rm --network=host arduino/python-rpc-sensors ``` -This approach is recommended as it's simpler and always uses the latest tested version compatible with the current OS. +This approach is recommended for most as it is simpler and always uses the latest tested version compatible with the current OS. -If you want to modify the Python® script or learn how the container is built, you can build the image from source. +### Building the Image from Source -The complete Python® application files are available in the repository [here](https://github.com/arduino/portenta-containers/tree/main/python-rpc-sensors). Clone or download the repository to your local machine, then upload the `python-rpc-sensors` folder to the Portenta X8 via: +If you want to modify the Python® script or learn how the container is built, you can build the image from source. The complete Python® application files are available in the repository [here](https://github.com/arduino/portenta-containers/tree/main/python-rpc-sensors). Clone or download the repository to your local machine, then upload the `python-rpc-sensors` folder to the Portenta X8 via: ```bash adb push /python-rpc-sensors /home/fio @@ -215,6 +244,22 @@ When it has finished, you can run the container with: docker compose up ``` +If you encounter issues with `docker compose`, you can run the container directly using: + +```bash +docker run -d \ + --name python-rpc-sensors \ + --restart unless-stopped \ + -e PYTHONUNBUFFERED=1 \ + --tty \ + --read-only \ + --network host \ + -v /tmp:/tmp \ + --device /dev/ttyGS0 \ + --add-host m4-proxy:host-gateway \ + python-rpc-sensors:latest +``` + 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: ```bash @@ -222,46 +267,178 @@ python-rpc-sensors_1 | ============================================ python-rpc-sensors_1 | == Portenta X8 Sensor reading == python-rpc-sensors_1 | ============================================ python-rpc-sensors_1 | -python-rpc-sensors_1 | Temperature: 25.904266357421875 -python-rpc-sensors_1 | Humidity: 25.564695358276367 -python-rpc-sensors_1 | Pressure: 976.4400024414062 -python-rpc-sensors_1 | Gas: 136.496 -python-rpc-sensors_1 | Altitude: 311.0769348144531 +python-rpc-sensors_1 | Temperature: 100 +python-rpc-sensors_1 | Humidity: 200 +python-rpc-sensors_1 | Pressure: 300 +python-rpc-sensors_1 | Gas: 400 +python-rpc-sensors_1 | Altitude: 500 +``` + +View the logs using: + +```bash +docker compose logs -f --tail 20 +``` + +Or if using `docker run`: + +```bash +docker logs -f python-rpc-sensors ``` -Whenever you change anything in the Python® script on your computer, you will have to resync and push the new script to the Portenta X8 and rebuild the container. Following command sequence will help you to do this process: +Whenever you change anything in the Python® script on your computer, you will need to resync and push the new script to the Portenta X8, then rebuild the container. The following command sequence will help you to do this process: ```bash -# On your computer adb push python-rpc-sensors /home/fio ``` ```bash -# On the Portenta X8 docker compose down ``` ```bash -# On the Portenta X8 docker build . -t python-rpc-sensors ``` ```bash -# On the Portenta X8 docker compose up ``` -Alternatively, you could modify the files directly on the X8 using an editor such as **VIM**, so you do not need to upload the files every time. Rebuilding the container will be necessary in any case though. +Or if using `docker run`: + +```bash +docker stop python-rpc-sensors +``` + +```bash +docker rm python-rpc-sensors +``` + +```bash +docker build . -t python-rpc-sensors +``` + +```bash +docker run -d --name python-rpc-sensors --restart unless-stopped -e PYTHONUNBUFFERED=1 --tty --read-only --network host -v /tmp:/tmp --device /dev/ttyGS0 --add-host m4-proxy:host-gateway python-rpc-sensors:latest +``` -If you wonder how to specify the Python® script that is executed when running a container, have a look at the `Dockerfile` file. There you will find the `ENTRYPOINT` command that takes multiple arguments. In our example: +Alternatively, you could modify the files directly on the X8 using an editor such as **VIM**, so you do not need to upload the files every time. Rebuilding the container will be necessary in any case, though. + +If you're wondering how to specify the Python® script to run when a container is started, have a look at the `Dockerfile` in the repository. There you will find the `ENTRYPOINT` command that takes multiple arguments. In this example: ```python -ENTRYPOINT ["python3", "m4_to_python.py"] +ENTRYPOINT ["python3", "main.py"] ``` +## Troubleshooting + +### RPC Communication Issues + +If you are experiencing issues with RPC communication, such as the Python script outputting: + +``` +Unable to retrieve data from the M4 +``` + +Even after following the setup correctly, it may be due to firmware compatibility issues with certain OS versions. + +### Manually Flashing the STM32H7 Firmware + +When encountering persistent RPC communication issues, you can manually flash a compatible firmware version to the STM32H7 microcontroller. You will need either a [pre-built firmware binary](assets/STM32H747AII6_CM7.bin) or you can build it yourself from [source](https://github.com/arduino/portentax8-stm32h7-fw/releases). + +From your local machine, push the firmware binary to the X8: + +```bash +adb push STM32H747AII6_CM7.bin /home/fio +``` + +Connect to the X8 and switch to root: + +```bash +cd /home/fio +``` + +Remount the `/usr` partition as read-write and copy the firmware to the correct location: + +```bash +sudo mount -o remount,rw /usr +``` + +```bash +sudo cp STM32H747AII6_CM7.bin /usr/lib/firmware/arduino/stm32h7-fw/STM32H747AII6_CM7.bin +``` + +Flash the firmware using the programming script: + +```bash +sudo /usr/arduino/extra/program.sh +``` + +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. + +### Building Firmware from Source + +If you need to build the firmware yourself or make modifications, you will need Git, the ARM GCC toolchain (`arm-none-eabi-gcc`), and Make installed on your system. + +Clone the firmware repository and initialize its submodules: + +```bash +git clone https://github.com/arduino/portentax8-stm32h7-fw.git +``` + +```bash +cd portentax8-stm32h7-fw +``` + +```bash +git submodule update --init --recursive +``` + +On Windows, using Git Bash, you will need to add the ARM toolchain to your `PATH`. The toolchain is typically installed with the Arduino IDE: + +```bash +export PATH=$PATH:/c/Users/YOUR_USERNAME/AppData/Local/Arduino15/packages/arduino/tools/arm-none-eabi-gcc/7-2017q4/bin +``` + +Replace `YOUR_USERNAME` with your actual Windows username. Build the firmware: + +```bash +make +``` + +### Understanding the Example Code + +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. + +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: + +```arduino +#include +Adafruit_BME680 bme; + +void setup() { + bme.begin(); + RPC.bind("temperature", []{ return bme.temperature; }); + RPC.bind("humidity", []{ return bme.humidity; }); + RPC.bind("pressure", []{ return bme.pressure / 100.0F; }); + RPC.bind("gas", []{ return bme.gas_resistance / 1000.0; }); + RPC.bind("altitude", []{ return bme.readAltitude(SEALEVELPRESSURE_HPA); }); +} +``` + +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. + +To use a BMP280 sensor, you can either modify your Arduino sketch to provide a dummy gas value by adding: + +```arduino +RPC.bind("gas", []{ return 0; }); +``` + +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. + ## Conclusion -In this tutorial, you learned how to use the docker infrastructure to build a container that runs a Python® application. You have also learned how to use the RPC mechanism to exchange data between the microcontroller and the iMX8, which runs the Linux operating system. +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. ### Next Steps From f0d5492cc572ff3bde44ac05a584b1bbe4bf844f Mon Sep 17 00:00:00 2001 From: TaddyHC Date: Thu, 11 Dec 2025 18:55:42 -0600 Subject: [PATCH 4/4] Linter minor update --- .../tutorials/04.python-arduino-data-exchange/content.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/hardware/04.pro/boards/portenta-x8/tutorials/04.python-arduino-data-exchange/content.md b/content/hardware/04.pro/boards/portenta-x8/tutorials/04.python-arduino-data-exchange/content.md index 0eb75173fb..998fc89bf7 100644 --- a/content/hardware/04.pro/boards/portenta-x8/tutorials/04.python-arduino-data-exchange/content.md +++ b/content/hardware/04.pro/boards/portenta-x8/tutorials/04.python-arduino-data-exchange/content.md @@ -214,7 +214,7 @@ temperature = rpc_client.call('temperature') You have two options to run the Python® application. -### Using the Pre-built Docker Image +### Using the Pre-Built Docker Image For a quick setup, you can use the pre-built `arduino/python-rpc-sensors` image that Arduino maintains. This way, you always have the latest compatible version. Log into the X8 via `adb shell` and then pull and run the pre-built image using the following command: @@ -224,7 +224,7 @@ docker run --rm --network=host arduino/python-rpc-sensors This approach is recommended for most as it is simpler and always uses the latest tested version compatible with the current OS. -### Building the Image from Source +### Building the Image From Source If you want to modify the Python® script or learn how the container is built, you can build the image from source. The complete Python® application files are available in the repository [here](https://github.com/arduino/portenta-containers/tree/main/python-rpc-sensors). Clone or download the repository to your local machine, then upload the `python-rpc-sensors` folder to the Portenta X8 via: @@ -376,7 +376,7 @@ sudo /usr/arduino/extra/program.sh 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. -### Building Firmware from Source +### Building Firmware From Source If you need to build the firmware yourself or make modifications, you will need Git, the ARM GCC toolchain (`arm-none-eabi-gcc`), and Make installed on your system.