<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="https://community.element14.com/cfs-file/__key/system/syndication/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Forum - Recent Threads</title><link>https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum</link><description /><dc:language>en-US</dc:language><generator>Telligent Community 12</generator><lastBuildDate>Tue, 18 Aug 2026 06:09:21 GMT</lastBuildDate><atom:link rel="self" type="application/rss+xml" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum" /><item><title>DockBot - Part 6 - The Mostly Open Closed Loop Control</title><link>https://community.element14.com/thread/57203?ContentTypeID=0</link><pubDate>Tue, 18 Aug 2026 06:09:21 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:5ae5e618-f44e-4146-9dc4-e44e2bde42b6</guid><dc:creator>arvindsa</dc:creator><slash:comments>2</slash:comments><comments>https://community.element14.com/thread/57203?ContentTypeID=0</comments><wfw:commentRss>https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57203/dockbot---part-6---the-mostly-open-closed-loop-control/rss?ContentTypeId=0</wfw:commentRss><description>&lt;p&gt;I am building a robotic system that identifies the charging port on an EV and automatically moves a charger arm to plug the charger in.&lt;/p&gt;
&lt;p&gt;Past Forum Posts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57056/dockbot---part-1---the-concept" data-e14adj="t"&gt;DockBot - Part 1 - The Concept&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57069/dockbot---part-2---positioning-with-aruco-markers" data-e14adj="t"&gt;DockBot - Part 2 - Positioning with Aruco Markers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/p/addpost/community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57142/dockbot---part-3---new-plan-new-hardware-for-better-sensing?COM=e14c-direct-ugc&amp;amp;CMP=e14c-direct-ugc&amp;amp;osetc=e14c-direct-ugc" target="_blank" data-e14adj="t"&gt;DockBot - Part 3 - New Plan, New Hardware for Better Sensing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57162/dockbot---part-4---getting-the-arduino-q-to-move-the-tank-motors" data-e14adj="t"&gt;DockBot - Part 4 - Getting the Arduino Q to move the tank motors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57187/dockbot---part-5---the-mechanical-hand" data-e14adj="t"&gt;DockBot - Part 5 - The Mechanical Hand&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I wanted to have a proper closed loop control of my motors using an magenetic encoder TLE5012 which i have in surplus but I did not get the ordered diametrical magnets in time. So i had to do yet another work around. Use the Aruco marker as a sensor, move the robot incrementally, correct the motors. and repeat till the target. Sounds Simple. But Nooope.&lt;/p&gt;
&lt;h2 id="mcetoc_1k09k6tte0"&gt;Motor Calibration. Take 1&lt;/h2&gt;
&lt;p&gt;The idea was simple, run one motor at a known PWM speed for a small time, see how much the robot turns, through aruco marker, then incrementally increase and see the difference. Then the opposite direction. Left one worked out fine, but right one never turned on. It seems that while the left one started at PWM 84/255 duty, the right one started only at 184/255. This caused the right motor to never start in the calibration algorithm I made. Also, i noticed a fundamental flaw, the smaller bursts meant the tracks were slipping on my floor, and there was not enough time for it to stabilize the slipping. So this idea had to be junked&lt;/p&gt;
&lt;p&gt;&lt;a href="https://community.element14.com/cfs-file/__key/communityserver-discussions-components-files/456/20260817_5F00_220222.mp4"&gt;community.element14.com/.../20260817_5F00_220222.mp4&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1k09kr7171"&gt;Motor calibration, take 2&lt;/h2&gt;
&lt;p&gt;I decided to just junk auto calibration, and went the cannibal way. I decided to use constant motor speeds one set speed for each of forward, reverse, rotate left and rotate right. I kept tracking the motion and manually adjusted the speed&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://community.element14.com/cfs-file/__key/communityserver-discussions-components-files/456/p6_2D00_v1.mp4"&gt;community.element14.com/.../p6_2D00_v1.mp4&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you noticed, there was a slight flutter in the marker due to the ceiling fan&amp;#39;s air current. This did create some issues. So i decided to make it more robust by creating a cube out of cardboard.&lt;/p&gt;
&lt;p&gt;&lt;img alt="image" style="max-height:450px;max-width:800px;"  src="https://community.element14.com/resized-image/__size/1600x900/__key/communityserver-discussions-components-files/456/20260817_5F00_230227.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;The tank now wears a small cube with a marker on each face - back, top, left, right - instead of the single (actually two) rear marker from before. The point is that whichever face happens to be pointed at the camera, something is always trackable, even when it rotates, not just when the tank is driving directly away from it. Now, I never managed to get multi point tracking working, you will see that i had to resort to some hand adjustment of the robot, but with time I can get it fixed.&lt;/p&gt;
&lt;h2 id="mcetoc_1k09lrlhu2"&gt;Actually approaching a target: three wrong designs first&lt;/h2&gt;
&lt;p&gt;The Radxa&amp;#39;s camera is fixed - mounted separately. It is not mounted on the robot. I wrote something that would spin the tank until the target marker was centered in the camera frame, the way you&amp;#39;d steer if the camera were on the robot&amp;#39;s. Since the camera doesn&amp;#39;t move when the robot spins, and the target doesn&amp;#39;t move either, the target&amp;#39;s position in the frame&amp;nbsp; - never changes no matter what the robot does. The script just spun in place for forty steps, learning nothing. The fix: infer heading from how the robot&amp;#39;s own marker position shifts in the frame, sampled before and after a small forward nudge. That nudge does double duty - it&amp;#39;s both the heading measurement and real progress toward the goal, so nothing is wasted purely on sensing.&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="python"&gt;heading = (rx1 - rx0, rz1 - rz0)   # where the robot&amp;#39;s own marker moved
bearing = (goal_x - rx1, goal_z - rz1)   # where the goal is from here
angle = signed_angle_deg(*heading, *bearing) * turn_sign&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;Second wrong design: I started the approaching using raw pixel calulation. But I moved from raw pixel positions to real millimeters via &lt;code&gt;cv2.solvePnP&lt;/code&gt;, which has a sign convention of its own - &lt;code&gt;--turn-sign&lt;/code&gt; that worked for the pixel-only version was backwards for the mm version, and this caused the robot to move away from the target by making an u Turn.&lt;/p&gt;
&lt;p&gt;Third: close to the target&amp;#39;s marker isn&amp;#39;t the same as &amp;quot;in front of it.&amp;quot; solvePnP gives you the marker&amp;#39;s own facing direction, not just its position - I&amp;#39;d been computing distance to a point, not distance to a pose. Fixed by projecting a goal point along the target&amp;#39;s own facing normal, sampled once at startup since the target is static, rather than the marker&amp;#39;s raw position.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://community.element14.com/cfs-file/__key/communityserver-discussions-components-files/456/trial.mp4"&gt;community.element14.com/.../trial.mp4&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1k09o1alf0"&gt;Final Notes&lt;/h2&gt;
&lt;p&gt;Once these were fixed, the robot became tamed, There were many fine tuning steps in between but they are not very educative. Now to the final step of Getting the Charging Handle to a car and also before that Improve the accuracy.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;</description></item><item><title>SmartAssist EV - Mobile Robot Control and Live Video Streaming - Part 7</title><link>https://community.element14.com/thread/57198?ContentTypeID=0</link><pubDate>Mon, 17 Aug 2026 00:10:29 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:6dfb4781-bf8b-4831-b9a3-e762b953db39</guid><dc:creator>jelektro</dc:creator><slash:comments>1</slash:comments><comments>https://community.element14.com/thread/57198?ContentTypeID=0</comments><wfw:commentRss>https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57198/smartassist-ev---mobile-robot-control-and-live-video-streaming---part-7/rss?ContentTypeId=0</wfw:commentRss><description>&lt;p&gt;&lt;span class="user-query-container" style="font-size:150%;"&gt;&lt;span class="user-query-bubble-with-background ng-star-inserted" data-test-id="luminous-collapsed-bubble"&gt;&lt;span class="horizontal-container ng-star-inserted"&gt;Project Roadmap&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Experimental Smart Assistive Platform for Elderly and Disabled People - Part 1" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57188/smartassist-ev-experimental-smart-assistive-platform-for-elderly-and-disabled-people---part-1" data-e14adj="t"&gt;Part 1&lt;/a&gt;&lt;a title="Experimental Smart Assistive Platform for Elderly and Disabled People - Part 1" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57188/smartassist-ev-experimental-smart-assistive-platform-for-elderly-and-disabled-people---part-1" data-e14adj="t"&gt;&amp;nbsp;- Experimental Smart Assistive Platform for Elderly and Disabled People&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Hardware Platform - Part 2" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57189/smartassist-ev---hardware-platform---part-2" data-e14adj="t"&gt;Part 2&lt;/a&gt;&lt;a title="Hardware Platform - Part 2" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57189/smartassist-ev---hardware-platform---part-2" data-e14adj="t"&gt;&amp;nbsp;- Hardware Platform&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Wireless Command and H-Bridge Direct Drive - Part 3" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57190/smartassist-ev---wireless-command-and-h-bridge-direct-drive---part-3" data-e14adj="t"&gt;Part 3&lt;/a&gt;&lt;a title="Wireless Command and H-Bridge Direct Drive - Part 3" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57190/smartassist-ev---wireless-command-and-h-bridge-direct-drive---part-3" data-e14adj="t"&gt;&amp;nbsp;- Wireless Command and H-Bridge Direct Drive&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Part 4 - Introducing Autonomous Line Following (TCRT5000)" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57191/smartassist-ev---introducing-autonomous-line-following-tcrt5000---part-4" data-e14adj="t"&gt;Part 4 - Introducing Autonomous Line Following (TCRT5000)&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Part 5 - Non-Contact Proactive Shielding (HC-SR04 Range Finder)" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57194/smartassist-ev---non-contact-proactive-shielding-hc-sr04-range-finder---part-5" data-e14adj="t"&gt;Part 5 - Non-Contact Proactive Shielding (HC-SR04 Range Finder)&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Part 6 - Strict Priority Hierarchy with Tactile Mechanical Bumpers" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57196/smartassist-ev---strict-priority-hierarchy-with-tactile-mechanical-bumpers---part-6" data-e14adj="t"&gt;Part 6 - Strict Priority Hierarchy with Tactile Mechanical Bumpers&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Part 7 - Mobile Robot Control and Live Video Streaming" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57198/smartassist-ev---mobile-robot-control-and-live-video-streaming---part-7" data-e14adj="t"&gt;Part 7 - Mobile Robot Control and Live Video Streaming&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;span style="font-family:inherit;"&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style="font-family:inherit;"&gt;As part of this project, we have developed a fully wireless control system for a wheeled robot&amp;nbsp;integrated with a real-time live video stream. The heart of the entire setup is the Arduino Uno Q board.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style="font-family:inherit;"&gt;Architecture and Technologies Used&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style="font-family:inherit;"&gt;The project leverages the unique, dual-processor architecture of the Arduino Uno Q and the Arduino App Lab ecosystem, which allows splitting system operations into two separate layers:&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style="font-family:inherit;font-size:150%;"&gt;1. Application Layer (MPU Processor running Linux/Zephyr):&lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span style="font-family:inherit;"&gt;Responsible for directly handling the USB camera plugged into the board&amp;#39;s port.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="font-family:inherit;"&gt;Runs a native server hosting the Web User Interface (WebUI).&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="font-family:inherit;"&gt;Streams live video directly to any web browser in real-time, completely bypassing the need for heavy external libraries like Flask or OpenCV.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span style="font-family:inherit;font-size:150%;"&gt;2. Hardware Layer (MCU STM32 Microcontroller):&lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span style="font-family:inherit;"&gt;Handles real-time, low-level execution of the digital H-bridge pins (four input pins controlling motor direction).&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="font-family:inherit;"&gt;Generates diagnostic signals for the onboard peripherals of the Arduino Uno Q.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span style="font-family:inherit;"&gt;The engineered system seamlessly combines mechanical movement with advanced visual telemetry broadcasted directly on the robot itself:&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style="font-family:inherit;"&gt;1. Virtual Joystick (Local WebUI): The web interface is completely free of external network dependencies (no CDNs, no Cloudflare). The entire touch-control script is embedded directly into the index.html file within the assets folder. Touch drags on a smartphone screen or mouse movements on a PC are instantly calculated into directional vectors (F - forward, B - backward, L - left, R - right, S - stop).&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style="font-family:inherit;"&gt;2. Inter-Process Communication (RPC Bridge): To transmit data from the web interface (JavaScript/Python code) to the motors (C++ code), we used the official, built-in Arduino_RouterBridge communication bridge. It handles asynchronous requests, guaranteeing minimal latency and instantaneous drive response.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style="font-family:inherit;"&gt;3. Color Status Signaling (RGB LED): The system integrates the native mapping of the onboard LED4, which operates in an inverted logic state (Active LOW). When moving forward, the LED lights up green; when reversing, it turns red; during turns, it lights up blue; and it automatically turns off when the robot is idle.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style="font-family:inherit;"&gt;4. 8&amp;times;13 Directional LED Matrix: The large LED dot matrix embedded into the Arduino Uno Q is used to visualize the robot&amp;#39;s heading. Utilizing the low-level matrix.draw() function and raw pixel bitmaps (104-byte arrays defining pixel brightness on a 0&amp;ndash;7 scale), the matrix renders crisp, bright arrows (▲, ▼, &amp;lt;, &amp;gt;) in real-time corresponding to the active drive direction.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style="font-family:inherit;"&gt;By strictly utilizing the native libraries of the Arduino App Lab ecosystem (arduino.app_bricks.web_ui and Arduino_LED_Matrix), this solution boasts exceptional stability:It completely eliminates compilation errors related to memory pointer casting during array parsing.It ensures total offline independence &amp;ndash; the robot operates entirely within a local Wi-Fi network.The codebase serves as a production-ready template for Smart Assist EV applications and autonomous inspection platforms.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node="0"&gt;&lt;span style="font-family:inherit;"&gt;To run the entire system, you need an external USB hub with a USB-C plug that connects to the Arduino Uno Q, featuring USB-C Power Delivery ports and USB 2.0 or USB 3.0 ports to connect a USB camera.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node="1"&gt;&lt;span style="font-family:inherit;"&gt;&lt;img alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/7_2D00_2.jpg" /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node="1"&gt;&lt;span style="font-family:inherit;"&gt;I tested several cameras. Unfortunately, some of them do not work properly with the Arduino Q. Despite my efforts, I could not get the rather old Megapixel USB2 Webcam Live WB-5400 - 15007 (which runs on USB 2.0) to work. The project uses a 3-in-1 Waterproof USB Endoscope Inspection Camera. The integrated ultra-bright LEDs and 1200P HD camera make it able to present images with a &lt;span class="citation-3 citation-end-3"&gt;color effect while retaining high resolution and quality even in dark places. In addition, the lens is equipped with automatic exposure and blue light technology, which can effectively filter stray light, restore real scenes, and make image&lt;sup class="superscript" data-turn-source-index="1"&gt;&lt;/sup&gt;&lt;/span&gt;s clearer.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node="1"&gt;&lt;span style="font-family:inherit;"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/7_2D00_1.jpg" /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node="1"&gt;&lt;span style="font-family:inherit;"&gt;A web page containing a camera feed along with a joystick can be displayed on a computer or a mobile device. This offers tremendous possibilities for the user, allowing them to control the vehicle not only locally from an apartment but also remotely. The view of the page is shown in the image.&amp;nbsp;After refreshing the page in your phone/computer browser: Below the video window, you will see a dark gray circle with a dashed light blue border. When you click or tap inside this circle, a light blue, movable joystick will immediately appear there, allowing you to control the vehicle.&lt;/span&gt;&lt;/p&gt;
&lt;pre style="border-bottom:0px #0a0a0a;font-family:monospace;font-size:14px;font-weight:400;margin:14px 0px;text-decoration:none;" data-copy-service-computed-style="font-family: monospace; font-size: 14px; font-weight: 400; margin: 14px 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;img loading="lazy" style="max-height:360px;max-width:640px;" alt="WebApp" src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/WebApp.jpg" /&gt;&lt;/pre&gt;
&lt;p data-path-to-node="1"&gt;&lt;span style="font-family:inherit;"&gt;The image below shows the website running on a smartphone. As you can see, there&amp;#39;s no need to create and install a dedicated app&amp;mdash;a standard web browser will suffice.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node="1"&gt;&lt;span style="font-family:inherit;"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/7_2D00_3.jpg" /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node="1"&gt;&lt;span style="font-family:inherit;"&gt;For diagnostic purposes, the built-in LEDs on the Arduino Uno board were used to indicate the vehicle&amp;#39;s status: a&amp;nbsp;lit blue LED indicates turning left or right, green indicates moving forward, and red indicates moving backward. Additionally, the matrix display shows arrows indicating the direction in which the vehicle is moving.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node="1"&gt;&lt;span style="font-family:inherit;"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/7_2D00_4.jpg" /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node="1"&gt;&lt;span style="font-family:inherit;"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/7_2D00_5.jpg" /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node="1"&gt;&lt;span style="font-family:inherit;"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/7_2D00_6.jpg" /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node="1"&gt;&lt;span style="font-family:inherit;"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/7_2D00_7.jpg" /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node="1"&gt;&lt;span style="font-family:inherit;"&gt;&lt;a href="https://community.element14.com/cfs-file/__key/communityserver-discussions-components-files/456/SmartAssist-EV.mp4"&gt;community.element14.com/.../SmartAssist-EV.mp4&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div class="source-inline-chip-container luminous-sources ng-star-inserted"&gt;
&lt;pre style="border-bottom:0px #0a0a0a;font-family:monospace;font-size:14px;font-weight:400;margin:14px 0px;text-decoration:none;" data-copy-service-computed-style="font-family: monospace; font-size: 14px; font-weight: 400; margin: 14px 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;span class="HwtZe" lang="en"&gt;&lt;span class="jCAhz ChMk0b"&gt;&lt;span class="ryNqvb"&gt;You can also watch a video showing how the website works on YouTube:&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;a title="SmartAssist EV (Youtube)" href="https://www.youtube.com/watch?v=E3mmpFuwCU4" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;https://www.youtube.com/watch?v=E3mmpFuwCU4&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;span style="font-size:150%;"&gt;Firmware Implementation: Motor Control and RPC Communication&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;1. Libraries and System Architecture&lt;br /&gt;&lt;br /&gt;&lt;pre class="ui-code" data-mode="text"&gt;#include &amp;lt;Arduino_RouterBridge.h&amp;gt; // Official RPC header for Uno Q
#include &amp;lt;Arduino_LED_Matrix.h&amp;gt;   // 8x13 Matrix library

Arduino_LED_Matrix matrix; // Initialize the LED matrix

    &amp;lt;Arduino_RouterBridge.h&amp;gt;: The official Remote Procedure Call (RPC) library for the Arduino UNO Q. It establishes a communication bridge between the microcontroller unit (MCU) running C++ and the Linux host system running Python (main.py).

    &amp;lt;Arduino_LED_Matrix.h&amp;gt;: The library used to control the built-in 8&amp;#215;13 LED matrix display.

    Arduino_LED_Matrix matrix;: Instantiates the matrix driver object to draw graphical frames.&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;2. Pin Mapping and Constants&lt;br /&gt;&lt;br /&gt;&lt;pre class="ui-code" data-mode="text"&gt;// Motor pin definitions for Elecrow kit (H-bridges)
const int MOTOR_L_F = 4;
const int MOTOR_L_B = 5;
const int MOTOR_R_F = 6;
const int MOTOR_R_B = 7;

const uint8_t FRAME_SIZE = 8 * 13; // 104 pixels

    Motor Pins:

        MOTOR_L_F (Pin 4) &amp;amp; MOTOR_L_B (Pin 5): Left motor forward and backward channels.

        MOTOR_R_F (Pin 6) &amp;amp; MOTOR_R_B (Pin 7): Right motor forward and backward channels.

    FRAME_SIZE = 8 * 13: Defines the total pixel array size (104 pixels) for the LED matrix.&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;3. LED Matrix Graphic Data Arrays&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="text"&gt;// 1. UP ARROW (FORWARD)
uint8_t arrow_up[FRAME_SIZE] = {
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 7, 7, 7, 7, 7, 0, 0, 0, 0,
    0, 0, 0, 7, 7, 0, 7, 0, 7, 7, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0
};

// 5. CLEAR DISPLAY (STOP)
uint8_t stop_icon[FRAME_SIZE] = { 0 }; // All pixels off (value 0)&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;Four 1D byte arrays (arrow_up, arrow_down, arrow_left, arrow_right) and one blank array (stop_icon) represent visual icons.&lt;br /&gt;Grayscale Value Mapping: Each index corresponds to one pixel. Values range from 0 (LED completely off) to 7 (maximum LED brightness).&lt;br /&gt;The visual layout inside the code mimics the 8&amp;times;13 grid structure to visualize arrow shapes directly in the source file.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;4. Helper Functions&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;set_led4_color(bool r, bool g, bool b)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="text"&gt;// Function controlling the built-in LED4 using inverted logic
void set_led4_color(bool r, bool g, bool b) {
  digitalWrite(LED4_R, r ? LOW : HIGH);
  digitalWrite(LED4_G, g ? LOW : HIGH);
  digitalWrite(LED4_B, b ? LOW : HIGH);
}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Controls the onboard RGB LED (LED4).&lt;br /&gt;Active-Low Logic: The pins use inverted logic. Setting a pin to LOW turns the corresponding color component ON, whereas setting it to HIGH turns it OFF (handled via ternary operators r ? LOW : HIGH).&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;move_robot_mcu(String direction)&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre class="ui-code" data-mode="text"&gt;// Function receiving commands from the web page (via RPC bridge)
void move_robot_mcu(String direction) {
  if (direction == &amp;quot;F&amp;quot;) { // FORWARD
    digitalWrite(MOTOR_L_F, HIGH); digitalWrite(MOTOR_R_F, HIGH);
    digitalWrite(MOTOR_L_B, LOW);  digitalWrite(MOTOR_R_B, LOW);
    
    set_led4_color(false, true, false); // GREEN
    matrix.draw(arrow_up);               // Display up arrow
  } 
  else if (direction == &amp;quot;B&amp;quot;) { // BACKWARD
    digitalWrite(MOTOR_L_F, LOW);  digitalWrite(MOTOR_R_F, LOW);
    digitalWrite(MOTOR_L_B, HIGH); digitalWrite(MOTOR_R_B, HIGH);
    
    set_led4_color(true, false, false); // RED
    matrix.draw(arrow_down);             // Display down arrow
  } 
  // ... (additional conditions for &amp;quot;L&amp;quot; and &amp;quot;R&amp;quot;)
  else { // STOP (&amp;quot;S&amp;quot;)
    digitalWrite(MOTOR_L_F, LOW);  digitalWrite(MOTOR_L_B, LOW);
    digitalWrite(MOTOR_R_F, LOW);  digitalWrite(MOTOR_R_B, LOW);
    
    set_led4_color(false, false, false); // Turn off RGB LED
    matrix.draw(stop_icon);               // Clear LED matrix
  }
}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-path-to-node="21"&gt;The primary execution function triggered via the RPC bridge. It handles drive state, RGB lighting, and matrix rendering simultaneously:&lt;/p&gt;
&lt;table style="margin-bottom:32px;" data-path-to-node="22"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;strong&gt;Input Signal&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;strong&gt;Action&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;strong&gt;H-Bridge Motor Pin States&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;strong&gt;RGB Color&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;strong&gt;Matrix Graphic&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="22,1,0,0"&gt;&lt;code data-path-to-node="22,1,0,0" data-index-in-node="0"&gt;&amp;quot;F&amp;quot;&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="22,1,1,0"&gt;Forward&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="22,1,2,0"&gt;&lt;code data-path-to-node="22,1,2,0" data-index-in-node="0"&gt;L_F: HIGH&lt;/code&gt;, &lt;code data-path-to-node="22,1,2,0" data-index-in-node="11"&gt;R_F: HIGH&lt;/code&gt;, &lt;code data-path-to-node="22,1,2,0" data-index-in-node="22"&gt;L_B: LOW&lt;/code&gt;, &lt;code data-path-to-node="22,1,2,0" data-index-in-node="32"&gt;R_B: LOW&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="22,1,3,0"&gt;Green&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="22,1,4,0"&gt;&lt;code data-path-to-node="22,1,4,0" data-index-in-node="0"&gt;arrow_up&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="22,2,0,0"&gt;&lt;code data-path-to-node="22,2,0,0" data-index-in-node="0"&gt;&amp;quot;B&amp;quot;&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="22,2,1,0"&gt;Backward&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="22,2,2,0"&gt;&lt;code data-path-to-node="22,2,2,0" data-index-in-node="0"&gt;L_F: LOW&lt;/code&gt;, &lt;code data-path-to-node="22,2,2,0" data-index-in-node="10"&gt;R_F: LOW&lt;/code&gt;, &lt;code data-path-to-node="22,2,2,0" data-index-in-node="20"&gt;L_B: HIGH&lt;/code&gt;, &lt;code data-path-to-node="22,2,2,0" data-index-in-node="31"&gt;R_B: HIGH&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="22,2,3,0"&gt;Red&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="22,2,4,0"&gt;&lt;code data-path-to-node="22,2,4,0" data-index-in-node="0"&gt;arrow_down&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="22,3,0,0"&gt;&lt;code data-path-to-node="22,3,0,0" data-index-in-node="0"&gt;&amp;quot;L&amp;quot;&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="22,3,1,0"&gt;Spin Left&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="22,3,2,0"&gt;&lt;code data-path-to-node="22,3,2,0" data-index-in-node="0"&gt;L_F: LOW&lt;/code&gt;, &lt;code data-path-to-node="22,3,2,0" data-index-in-node="10"&gt;R_F: HIGH&lt;/code&gt;, &lt;code data-path-to-node="22,3,2,0" data-index-in-node="21"&gt;L_B: HIGH&lt;/code&gt;, &lt;code data-path-to-node="22,3,2,0" data-index-in-node="32"&gt;R_B: LOW&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="22,3,3,0"&gt;Blue&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="22,3,4,0"&gt;&lt;code data-path-to-node="22,3,4,0" data-index-in-node="0"&gt;arrow_left&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="22,4,0,0"&gt;&lt;code data-path-to-node="22,4,0,0" data-index-in-node="0"&gt;&amp;quot;R&amp;quot;&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="22,4,1,0"&gt;Spin Right&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="22,4,2,0"&gt;&lt;code data-path-to-node="22,4,2,0" data-index-in-node="0"&gt;L_F: HIGH&lt;/code&gt;, &lt;code data-path-to-node="22,4,2,0" data-index-in-node="11"&gt;R_F: LOW&lt;/code&gt;, &lt;code data-path-to-node="22,4,2,0" data-index-in-node="21"&gt;L_B: LOW&lt;/code&gt;, &lt;code data-path-to-node="22,4,2,0" data-index-in-node="31"&gt;R_B: HIGH&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="22,4,3,0"&gt;Blue&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="22,4,4,0"&gt;&lt;code data-path-to-node="22,4,4,0" data-index-in-node="0"&gt;arrow_right&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="22,5,0,0"&gt;Else / &lt;code data-path-to-node="22,5,0,0" data-index-in-node="7"&gt;&amp;quot;S&amp;quot;&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="22,5,1,0"&gt;Stop&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="22,5,2,0"&gt;All motor pins &lt;code data-path-to-node="22,5,2,0" data-index-in-node="15"&gt;LOW&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="22,5,3,0"&gt;Off&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="22,5,4,0"&gt;&lt;code data-path-to-node="22,5,4,0" data-index-in-node="0"&gt;stop_icon&lt;/code&gt; (Clear)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="mcetoc_1k0p7i56p0" data-path-to-node="24"&gt;5. Program Initialization (&lt;code data-path-to-node="24" data-index-in-node="27"&gt;setup&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="text"&gt;void setup() {
  // Initialize onboard RGB LED4
  pinMode(LED4_R, OUTPUT);
  pinMode(LED4_G, OUTPUT);
  pinMode(LED4_B, OUTPUT);
  set_led4_color(false, false, false);

  // Initialize matrix hardware
  matrix.begin();
  matrix.setGrayscaleBits(3); // Brightness levels from 0 to 7
  matrix.clear();

  // Start the official Linux communication bridge (main.py)
  Bridge.begin();
  Bridge.provide(&amp;quot;drive&amp;quot;, move_robot_mcu); 
  
  // Initialize motor H-bridge control pins
  pinMode(MOTOR_L_F, OUTPUT); pinMode(MOTOR_L_B, OUTPUT);
  pinMode(MOTOR_R_F, OUTPUT); pinMode(MOTOR_R_B, OUTPUT);
  
  move_robot_mcu(&amp;quot;S&amp;quot;); // Initial state (Vehicle stopped, matrix cleared)
}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&amp;nbsp; &amp;nbsp; RGB LED Setup: Configures LED4_R, LED4_G, and LED4_B as output pins and initializes them to an off state.&lt;/p&gt;
&lt;p&gt;&amp;nbsp; &amp;nbsp; Matrix Initialization: Starts the display hardware using matrix.begin(), sets brightness resolution to 3 bits (0-7 scale) via matrix.setGrayscaleBits(3), and clears any leftover pixels.&lt;br /&gt;&lt;br /&gt;&amp;nbsp; &amp;nbsp; RPC Bridge Exposure:&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Bridge.begin() starts the Linux-MCU communication.&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Bridge.provide(&amp;quot;drive&amp;quot;, move_robot_mcu); registers the move_robot_mcu function under the RPC service name &amp;quot;drive&amp;quot;. This allows external Linux scripts or web applications to execute this C++ function remotely over RPC.&lt;br /&gt;&amp;nbsp; &amp;nbsp; Motor Pin Modes: Sets all motor control pins as outputs and enforces an initial STOP state (move_robot_mcu(&amp;quot;S&amp;quot;)).&lt;br /&gt;&lt;br /&gt;6. Main Execution Loop (loop)&lt;br /&gt;&lt;br /&gt;&lt;pre class="ui-code" data-mode="text"&gt;void loop() {
  // Empty loop - RPC command handling occurs automatically in the background
  delay(10);
}&lt;/pre&gt;&amp;nbsp; &amp;nbsp;&lt;/p&gt;
&lt;p&gt;The loop() body is intentionally kept empty, containing only a small delay (delay(10)).&lt;br /&gt;&amp;nbsp; &amp;nbsp; Asynchronous Execution: Incoming commands sent from a smartphone, web interface, or Python script are handled in the background by the Arduino_RouterBridge middleware without blocking the main program thread.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style="font-family:inherit;"&gt;Here is the complete project, fully adapted to the native environment of Arduino App Lab, configured for the Elecrow car kit.&lt;/span&gt;&lt;/p&gt;
&lt;div data-animation-nesting="" data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sae="" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 20px; font-weight: 600; margin: 24px 0px 12px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;span style="font-family:inherit;font-size:150%;"&gt;1. Microcontroller Code (sketch.ino)&lt;/span&gt;&lt;/div&gt;
&lt;div data-animation-nesting="" data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sae="" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 20px; font-weight: 600; margin: 24px 0px 12px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;div data-animation-nesting="" data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sae="" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 20px; font-weight: 600; margin: 24px 0px 12px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;span style="font-family:inherit;"&gt;Upload this code to the MCU section. It handles the H-bridge motors, the LED4 RGB status light, and renders the arrows using the native matrix.draw() function when an RPC command arrives from the webpage.&lt;/span&gt;&lt;/div&gt;
&lt;div data-animation-nesting="" data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sae="" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 20px; font-weight: 600; margin: 24px 0px 12px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;span style="font-family:inherit;"&gt;Below is the basic code that allows you to control the vehicle through a web application.&amp;nbsp;&lt;/span&gt;&lt;/div&gt;
&lt;div data-animation-nesting="" data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sae="" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 20px; font-weight: 600; margin: 24px 0px 12px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;span style="font-family:inherit;font-size:inherit;"&gt;&lt;/span&gt;&lt;/div&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="text"&gt;#include &amp;lt;Arduino_RouterBridge.h&amp;gt; // Official RPC header for Uno Q
#include &amp;lt;Arduino_LED_Matrix.h&amp;gt;   // 8x13 matrix library

Arduino_LED_Matrix matrix; // Initialize the large matrix

// Motor pin definitions for the Elecrow kit (H-bridges)
const int MOTOR_L_F = 4;
const int MOTOR_L_B = 5;
const int MOTOR_R_F = 6;
const int MOTOR_R_B = 7;

const uint8_t FRAME_SIZE = 8 * 13; // 104 pixels

// 1. UP ARROW (FORWARD)
uint8_t arrow_up[FRAME_SIZE] = {
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 7, 7, 7, 7, 7, 0, 0, 0, 0,
    0, 0, 0, 7, 7, 0, 7, 0, 7, 7, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0
};

// 2. DOWN ARROW (BACKWARD)
uint8_t arrow_down[FRAME_SIZE] = {
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 7, 7, 0, 7, 0, 7, 7, 0, 0, 0,
    0, 0, 0, 0, 7, 7, 7, 7, 7, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0
};

// 3. LEFT ARROW
uint8_t arrow_left[FRAME_SIZE] = {
    0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    0, 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0
};

// 4. RIGHT ARROW
uint8_t arrow_right[FRAME_SIZE] = {
    0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 0,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0
};

// 5. CLEAR DISPLAY (STOP)
uint8_t stop_icon[FRAME_SIZE] = { 0 }; // All pixels off (value 0)

// Function controlling the built-in LED4 using inverted logic
void set_led4_color(bool r, bool g, bool b) {
  digitalWrite(LED4_R, r ? LOW : HIGH);
  digitalWrite(LED4_G, g ? LOW : HIGH);
  digitalWrite(LED4_B, b ? LOW : HIGH);
}

// Function receiving commands from the web page (via RPC bridge)
void move_robot_mcu(String direction) {
  if (direction == &amp;quot;F&amp;quot;) { // FORWARD
    digitalWrite(MOTOR_L_F, HIGH); digitalWrite(MOTOR_R_F, HIGH);
    digitalWrite(MOTOR_L_B, LOW);  digitalWrite(MOTOR_R_B, LOW);
    
    set_led4_color(false, true, false); // GREEN
    matrix.draw(arrow_up);               // Display up arrow
  } 
  else if (direction == &amp;quot;B&amp;quot;) { // BACKWARD
    digitalWrite(MOTOR_L_F, LOW);  digitalWrite(MOTOR_R_F, LOW);
    digitalWrite(MOTOR_L_B, HIGH); digitalWrite(MOTOR_R_B, HIGH);
    
    set_led4_color(true, false, false); // RED
    matrix.draw(arrow_down);             // Display down arrow
  } 
  else if (direction == &amp;quot;L&amp;quot;) { // LEFT
    digitalWrite(MOTOR_L_F, LOW);  digitalWrite(MOTOR_R_F, HIGH);
    digitalWrite(MOTOR_L_B, HIGH); digitalWrite(MOTOR_R_B, LOW);
    
    set_led4_color(false, false, true); // BLUE
    matrix.draw(arrow_left);             // Display left arrow
  } 
  else if (direction == &amp;quot;R&amp;quot;) { // RIGHT
    digitalWrite(MOTOR_L_F, HIGH); digitalWrite(MOTOR_R_F, LOW);
    digitalWrite(MOTOR_L_B, LOW);  digitalWrite(MOTOR_R_B, HIGH);
    
    set_led4_color(false, false, true); // BLUE
    matrix.draw(arrow_right);            // Display right arrow
  } 
  else { // STOP (&amp;quot;S&amp;quot;)
    digitalWrite(MOTOR_L_F, LOW);  digitalWrite(MOTOR_L_B, LOW);
    digitalWrite(MOTOR_R_F, LOW);  digitalWrite(MOTOR_R_B, LOW);
    
    set_led4_color(false, false, false); // Turn off RGB LED
    matrix.draw(stop_icon);               // Clear LED matrix
  }
}

void setup() {
  // Initialize built-in LED4
  pinMode(LED4_R, OUTPUT);
  pinMode(LED4_G, OUTPUT);
  pinMode(LED4_B, OUTPUT);
  set_led4_color(false, false, false);

  // Initialize the matrix exactly as in the working example
  matrix.begin();
  matrix.setGrayscaleBits(3); // Brightness levels from 0 to 7
  matrix.clear();

  // Start the official communication bridge with Linux (main.py)
  Bridge.begin();
  Bridge.provide(&amp;quot;drive&amp;quot;, move_robot_mcu); 
  
  // Initialize control pins for motor H-bridges
  pinMode(MOTOR_L_F, OUTPUT); pinMode(MOTOR_L_B, OUTPUT);
  pinMode(MOTOR_R_F, OUTPUT); pinMode(MOTOR_R_B, OUTPUT);
  
  move_robot_mcu(&amp;quot;S&amp;quot;); // Initial state (Vehicle stopped, matrix off)
}

void loop() {
  // Empty loop - handling RPC commands from the phone happens automatically in the background
  delay(10);
}&lt;/pre&gt;&lt;/p&gt;
&lt;div data-animation-nesting="" data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sae="" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 20px; font-weight: 600; margin: 24px 0px 12px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;span style="font-family:inherit;font-size:inherit;"&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div data-animation-nesting="" data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sae="" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 20px; font-weight: 600; margin: 24px 0px 12px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;span style="font-family:inherit;font-size:inherit;"&gt;After adding sensors, the code that additionally implements obstacle detection is as follows:&lt;/span&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-wiz-uids="wZkO1e_17,wZkO1e_16" data-hveid="CAAIBxAA" data-processed="true" data-complete="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 4px 0px 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;div data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;div data-animation-atomic="" data-sae="" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 1px solid rgb(240, 242, 245);"&gt;
&lt;div dir="ltr" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;pre data-copy-service-computed-style="font-family: monospace; font-size: 14px; font-weight: 400; margin: 14px 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/pre&gt;
&lt;div dir="ltr" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;pre class="ui-code" data-mode="text"&gt;#include &amp;lt;Arduino.h&amp;gt;
#include &amp;lt;Arduino_RouterBridge.h&amp;gt; // Official RPC header for Uno Q
#include &amp;lt;Arduino_LED_Matrix.h&amp;gt;   // Library for the 8x13 LED matrix

Arduino_LED_Matrix matrix; // Initialize the large LED matrix

// --- HARDWARE PIN CONFIGURATION ---
const int motorPinA_1A = 5; 
const int motorPinA_1B = 6; 
const int motorPinB_1A = 9; 
const int motorPinB_1B = 10;

const int IR_RECEIVE_PIN = 2; 
const int trigPin = 4; 
const int echoPin = 3; 

// Physical Tactile Bumpers
const int CollisionPin_L = 18; // Pin A4
const int CollisionPin_R = 19; // Pin A5

// Line Tracking Photo-interrupters
const int TrackingPin_L = A2;
const int TrackingPin_R = A3;

// --- ROBOT PARAMETERS AND MODES ---
enum RobotMode { MODE_MANUAL, MODE_AUTONOMOUS };
RobotMode currentMode = MODE_MANUAL; 

const int motorSpeed = 200; 
int spd = 195; // Specific heavy escape/evasive speed vector
int trackSpeed = 160; // Optimized track following velocity for Uno Q

unsigned long lastCommandTime = 0;
const unsigned long commandTimeout = 250;
const uint8_t FRAME_SIZE = 8 * 13; // 104 pixels for the LED matrix
byte lastCommand = 0;

// --- LED MATRIX ARROW ARRAYS (Brightness levels 0-7) ---
uint8_t arrow_up[FRAME_SIZE] = {
 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 7, 7, 7, 7, 7, 0, 0, 0, 0,
 0, 0, 0, 7, 7, 0, 7, 0, 7, 7, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0
};
uint8_t arrow_down[FRAME_SIZE] = {
 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 7, 7, 0, 7, 0, 7, 7, 0, 0, 0,
 0, 0, 0, 0, 7, 7, 7, 7, 7, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0
};
uint8_t arrow_left[FRAME_SIZE] = {
 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0,
 0, 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
 0, 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0,
 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0
};
uint8_t arrow_right[FRAME_SIZE] = {
 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0,
 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 0,
 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0,
 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0,
 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 0,
 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0,
 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0
};
uint8_t stop_icon[FRAME_SIZE] = { 0 }; 

// --- ONBOARD RGB LED CONTROL FUNCTIONS ---
void set_led3_color(int r, int g, int b) {
 analogWrite(LED3_R, r);
 analogWrite(LED3_G, g);
 analogWrite(LED3_B, b);
}
void set_led4_color(bool r, bool g, bool b) {
 digitalWrite(LED4_R, r ? LOW : HIGH);
 digitalWrite(LED4_G, g ? LOW : HIGH);
 digitalWrite(LED4_B, b ? LOW : HIGH);
}

// --- MOTOR DRIVE DRIVER ---
void motor(int A1, int A2, int B1, int B2) {
 analogWrite(motorPinA_1A, A1);
 analogWrite(motorPinA_1B, A2);
 analogWrite(motorPinB_1A, B1);
 analogWrite(motorPinB_1B, B2);
}

// --- NATIVE IR DECODER FOR ARDUINO UNO Q ---
long readElecrowIR() {
  int count = 0;
  while (digitalRead(IR_RECEIVE_PIN) == LOW &amp;amp;&amp;amp; count &amp;lt; 200) {
    count++;
    delayMicroseconds(60);
  }
  if (count &amp;gt;= 200) return -1;

  count = 0;
  while (digitalRead(IR_RECEIVE_PIN) == HIGH &amp;amp;&amp;amp; count &amp;lt; 80) {
    count++;
    delayMicroseconds(60);
  }
  if (count &amp;gt;= 80) return -1;

  int idx = 0;
  int cnt = 0;
  byte data[4] = {0, 0, 0, 0}; 

  for (int i = 0; i &amp;lt; 32; i++) {
    count = 0;
    while (digitalRead(IR_RECEIVE_PIN) == LOW &amp;amp;&amp;amp; count &amp;lt; 15) {
      count++;
      delayMicroseconds(60);
    }
    count = 0;
    while (digitalRead(IR_RECEIVE_PIN) == HIGH &amp;amp;&amp;amp; count &amp;lt; 40) {
      count++;
      delayMicroseconds(60);
    }
    if (count &amp;gt; 8) {
      data[idx] |= (1 &amp;lt;&amp;lt; cnt);
    }
    if (cnt == 7) {
      cnt = 0;
      idx++;
    } else {
      cnt++;
    }
  }

  if ((byte)(data[0] + data[1]) == 0xFF &amp;amp;&amp;amp; (byte)(data[2] + data[3]) == 0xFF) { 
    return data[2]; 
  }
  return -1;
}

// --- WEB INTERFACE INTERCEPTOR (FIXED: Changed argument type to String) ---
void move_robot_mcu(String direction) {
 if (currentMode != MODE_MANUAL) return; 
 
 if (direction == &amp;quot;F&amp;quot;) { 
 motor(0, motorSpeed, 0, motorSpeed);
 set_led4_color(false, true, false); 
 set_led3_color(0, 200, 0); 
 matrix.draw(arrow_up);
 } 
 else if (direction == &amp;quot;B&amp;quot;) { 
 motor(motorSpeed, 0, motorSpeed, 0);
 set_led4_color(true, false, false); 
 set_led3_color(200, 0, 0); 
 matrix.draw(arrow_down);
 }
 else if (direction == &amp;quot;L&amp;quot;) { 
 motor(motorSpeed, 0, 0, motorSpeed);
 set_led4_color(false, false, true); 
 set_led3_color(0, 0, 200); 
 matrix.draw(arrow_left);
 } 
 else if (direction == &amp;quot;R&amp;quot;) { 
 motor(0, motorSpeed, motorSpeed, 0);
 set_led4_color(false, false, true); 
 set_led3_color(0, 0, 200); 
 matrix.draw(arrow_right);
 } 
 else { 
 motor(0, 0, 0, 0);
 set_led4_color(false, false, false); 
 set_led3_color(0, 0, 0);
 matrix.draw(stop_icon);
 }
}

float getDistance() {
 digitalWrite(trigPin, LOW);
 delayMicroseconds(2);
 digitalWrite(trigPin, HIGH);
 delayMicroseconds(10);
 digitalWrite(trigPin, LOW);
 
 long duration = pulseIn(echoPin, HIGH, 30000); 
 float d = duration * 0.0343 / 2;
 if (d == 0) return 999.0; 
 return d;
}

// --- WEB MODE TOGGLE (FIXED: Changed argument type to String) ---
void toggle_mode_from_web(String cmd) {
 if (currentMode == MODE_MANUAL) {
 currentMode = MODE_AUTONOMOUS;
 set_led3_color(200, 200, 0); 
 Serial.println(&amp;quot;System Notification: AUTONOMY MODE ENGAGED VIA WEB.&amp;quot;);
 } else {
 currentMode = MODE_MANUAL;
 motor(0, 0, 0, 0); 
 set_led3_color(0, 0, 0);
 matrix.draw(stop_icon);
 Serial.println(&amp;quot;System Notification: MANUAL OVERRIDE ENGAGED VIA WEB.&amp;quot;);
 }
}

void exec_cmd(byte key_val) {
 switch (key_val) {
 case 0x1C: 
 if (currentMode == MODE_MANUAL) {
 currentMode = MODE_AUTONOMOUS;
 set_led3_color(200, 200, 0);
 Serial.println(&amp;quot;System Notification: AUTONOMY MODE ENGAGED.&amp;quot;);
 } else {
 currentMode = MODE_MANUAL;
 motor(0, 0, 0, 0); 
 set_led3_color(0, 0, 0);
 matrix.draw(stop_icon);
 Serial.println(&amp;quot;System Notification: MANUAL OVERRIDE ENGAGED.&amp;quot;);
 }
 lastCommand = 0;
 delay(500);
 break;
 case 0x18: if (currentMode == MODE_MANUAL) { motor(0, motorSpeed, 0, motorSpeed); matrix.draw(arrow_up); } break;
 case 0x08: if (currentMode == MODE_MANUAL) { motor(motorSpeed, 0, 0, motorSpeed); matrix.draw(arrow_left); } break;
 case 0x5A: if (currentMode == MODE_MANUAL) { motor(0, motorSpeed, motorSpeed, 0); matrix.draw(arrow_right); } break;
 case 0x52: if (currentMode == MODE_MANUAL) { motor(motorSpeed, 0, motorSpeed, 0); matrix.draw(arrow_down); } break;
 default: if (currentMode == MODE_MANUAL) { motor(0, 0, 0, 0); matrix.draw(stop_icon); } break;
 }
}

void setup() {
 Serial.begin(115200);
 
 pinMode(IR_RECEIVE_PIN, INPUT_PULLUP); 
 pinMode(trigPin, OUTPUT);
 pinMode(echoPin, INPUT);
 pinMode(CollisionPin_L, INPUT); 
 pinMode(CollisionPin_R, INPUT);
 pinMode(TrackingPin_L, INPUT_PULLUP);
 pinMode(TrackingPin_R, INPUT_PULLUP);
 
 pinMode(LED4_R, OUTPUT); pinMode(LED4_G, OUTPUT); pinMode(LED4_B, OUTPUT);
 set_led3_color(0, 0, 0);
 set_led4_color(false, false, false);
 matrix.begin();
 matrix.setGrayscaleBits(3);
 matrix.clear();
 
 // --- RPC BRIDGE REGISTRATION ---
 Bridge.begin();
 Bridge.provide(&amp;quot;drive&amp;quot;, move_robot_mcu); 
 Bridge.provide(&amp;quot;toggle_mode&amp;quot;, toggle_mode_from_web);
  
 Serial.println(&amp;quot;System Core Ready.&amp;quot;);
}

void loop() {
  // 1. IR Remote control handling
  if (digitalRead(IR_RECEIVE_PIN) == LOW) {
    long result = readElecrowIR();
    if (result != -1) {
      lastCommand = (byte)result;
      exec_cmd(lastCommand);
      if (currentMode == MODE_MANUAL) lastCommandTime = millis();
    }
  }

  // 2. Motion modes execution logic
  if (currentMode == MODE_MANUAL) {
    if (digitalRead(IR_RECEIVE_PIN) == HIGH) {
      if (millis() - lastCommandTime &amp;gt; commandTimeout) {
        motor(0, 0, 0, 0);
      }
    } else {
      exec_cmd(lastCommand);
    }
  } 
  else if (currentMode == MODE_AUTONOMOUS) {
    float distance = getDistance();
    int sumVal = (digitalRead(CollisionPin_L) == HIGH ? 1 : 0) * 2 + (digitalRead(CollisionPin_R) == HIGH ? 1 : 0);
    
    // --- HIERARCHY 1: Physical Bumpers ---
    if (sumVal &amp;gt; 0) {
      Serial.print(&amp;quot;TACTILE COLLISION TRIGGERED. Code: &amp;quot;);
      Serial.println(sumVal);
      matrix.draw(stop_icon);
      
      if (sumVal == 1) { 
        motor(spd, 0, spd, 0); delay(2000);
        motor(spd, 0, 0, spd); delay(2000);
      }
      else if (sumVal == 2) { 
        motor(spd, 0, spd, 0); delay(2000);
        motor(0, spd, spd, 0); delay(2000);
      }
      else if (sumVal == 3) { 
        motor(spd, 0, spd, 0); delay(2000);
        motor(spd, 0, 0, spd); delay(2000);
      }
    }
    // --- HIERARCHY 2: Ultrasonic Sensor (Sonar) ---
    else if (distance &amp;lt; 30) {
      matrix.draw(arrow_left);
      motor(motorSpeed, 0, 0, motorSpeed); 
      delay(300);
    }
    // --- HIERARCHY 3: Line Tracking ---
    else {
      int trackL = digitalRead(TrackingPin_L);
      int trackR = digitalRead(TrackingPin_R);
      int Track = trackL * 2 + trackR;
      
      switch (Track) {
        case 0: 
          motor(0, 0, 0, 0); 
          matrix.draw(stop_icon); 
          break;
        case 1: 
          motor(0, trackSpeed, trackSpeed, 0); 
          matrix.draw(arrow_right); 
          break;
        case 2: 
          motor(trackSpeed, 0, 0, trackSpeed); 
          matrix.draw(arrow_left); 
          break;
        case 3: 
          motor(0, trackSpeed, 0, trackSpeed); 
          matrix.draw(arrow_up); 
          break;
      }
      delay(10);
    }
  }
  Bridge.update(); 
}&lt;/pre&gt;
&lt;div dir="ltr" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;div dir="ltr" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;span style="font-family:inherit;font-size:150%;"&gt;2. Python Web Server (main.py)&lt;/span&gt;&lt;/p&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-hveid="CAAICRAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;span style="font-family:inherit;"&gt;Save this to the MPU/Python editor. It initializes your USB camera, acts as the API backend for your local interface touch events, and redirects commands to the MCU code.&lt;/span&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-wiz-uids="wZkO1e_1q,wZkO1e_1p" data-hveid="CAAIChAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 4px 0px 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;div data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;div data-animation-atomic="" data-sae="" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 1px solid rgb(240, 242, 245);"&gt;
&lt;div dir="ltr" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;pre data-copy-service-computed-style="font-family: monospace; font-size: 14px; font-weight: 400; margin: 14px 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;span style="font-family:inherit;"&gt;&lt;pre class="ui-code" data-mode="text"&gt;from arduino.app_utils import *
from arduino.app_bricks.web_ui import WebUI
from arduino.app_peripherals.camera import Camera

# Initialize the onboard USB camera module
camera = Camera(resolution=(640, 480), fps=15)  
ui = WebUI()
camera.start()

# API endpoint executed when a drag action occurs on the webpage
def handle_joystick(data: dict):
    char_cmd = data.get(&amp;#39;cmd&amp;#39;, &amp;#39;S&amp;#39;)
    # Forward command directly to the registered C++ function
    Bridge.call(&amp;quot;drive&amp;quot;, char_cmd)
    return {&amp;quot;status&amp;quot;: &amp;quot;ok&amp;quot;}

# Expose API and camera routes to App Lab web interface
ui.expose_api(&amp;quot;POST&amp;quot;, &amp;quot;/api/move&amp;quot;, handle_joystick)
ui.expose_camera(&amp;quot;/camera&amp;quot;, camera)

App.run()&lt;/pre&gt;&lt;/span&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;span style="font-family:inherit;font-size:150%;"&gt;3. Local Dashboard UI (assets/index.html)&lt;/span&gt;&lt;/p&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-hveid="CAAIDBAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;span style="font-family:inherit;font-size:inherit;"&gt;Save this code to your index.html inside your project&amp;#39;s assets directory. It renders a clean grid, displays your camera stream, and tracks touch drags or mouse movements inside a virtual joystick container without needing internet-based libraries.&lt;/span&gt;&lt;/div&gt;
&lt;div class="r1PmQe" style="border-bottom:0px #0a0a0a;font-family:Google Sans, Arial, sans-serif;font-size:14px;font-weight:400;margin:4px 0px 0px;text-decoration:none;" data-sfc-cp="" data-sfc-root="ep" data-wiz-uids="wZkO1e_2b,wZkO1e_2a" data-hveid="CAAIDRAA" data-processed="true" data-complete="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 4px 0px 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;div style="border-bottom:0px #0a0a0a;font-family:Google Sans, Arial, sans-serif;font-size:14px;font-weight:400;margin:0px;text-decoration:none;" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;div class="pHpOfb" style="border-bottom:1px solid #f0f2f5;font-family:Google Sans, Arial, sans-serif;font-size:14px;font-weight:400;margin:0px;text-decoration:none;" data-animation-atomic="" data-sae="" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 1px solid rgb(240, 242, 245);"&gt;
&lt;div class="pCTyYe" style="border-bottom:0px #0a0a0a;font-family:Google Sans, Arial, sans-serif;font-size:14px;font-weight:400;margin:0px;text-decoration:none;" dir="ltr" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;pre style="border-bottom:0px #0a0a0a;font-family:monospace;font-size:14px;font-weight:400;margin:14px 0px;text-decoration:none;" data-copy-service-computed-style="font-family: monospace; font-size: 14px; font-weight: 400; margin: 14px 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;span style="font-size:inherit;"&gt;&lt;span style="font-family:inherit;"&gt;&lt;/span&gt;&lt;/span&gt;&lt;pre class="ui-code" data-mode="text"&gt;&amp;lt;!doctype html&amp;gt;
&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot; /&amp;gt;
    &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot; /&amp;gt;
    &amp;lt;title&amp;gt;Uno Q Robot Dashboard&amp;lt;/title&amp;gt;
    &amp;lt;style&amp;gt;
      body { font-family: Arial, sans-serif; text-align: center; background: #111; color: #fff; margin: 0; padding: 10px; }
      #control-pad { width: 220px; height: 220px; background: #222; border-radius: 50%; margin: 25px auto; position: relative; border: 3px dashed #00ffcc; touch-action: none; }
      img { border: 4px solid #00ffcc; border-radius: 12px; max-width: 100%; height: auto; display: block; margin: 0 auto; }
      .status { font-weight: bold; color: #00ffcc; font-size: 1.2em; }
    &amp;lt;/style&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Uno Q Vehicle Control System&amp;lt;/h1&amp;gt;
    &amp;lt;div&amp;gt;
      &amp;lt;img src=&amp;quot;/camera&amp;quot; /&amp;gt;
    &amp;lt;/div&amp;gt;
    
    &amp;lt;div id=&amp;quot;control-pad&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;p&amp;gt;Drag your finger or mouse inside the circle to move the robot&amp;lt;/p&amp;gt;
    &amp;lt;p&amp;gt;Status: &amp;lt;span id=&amp;quot;status-val&amp;quot; class=&amp;quot;status&amp;quot;&amp;gt;STOP&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;

    &amp;lt;script&amp;gt;
      const pad = document.getElementById(&amp;#39;control-pad&amp;#39;);
      const statusText = document.getElementById(&amp;#39;status-val&amp;#39;);
      let isMoving = false;
      let lastDirection = &amp;quot;S&amp;quot;;
      let lastSend = 0;

      // Create a visual handle inside the control pad area
      const knob = document.createElement(&amp;#39;div&amp;#39;);
      knob.style.width = &amp;#39;60px&amp;#39;; knob.style.height = &amp;#39;60px&amp;#39;; knob.style.background = &amp;#39;#00ffcc&amp;#39;;
      knob.style.borderRadius = &amp;#39;50%&amp;#39;; knob.style.position = &amp;#39;absolute&amp;#39;;
      knob.style.left = &amp;#39;80px&amp;#39;; knob.style.top = &amp;#39;80px&amp;#39;; knob.style.pointerEvents = &amp;#39;none&amp;#39;;
      pad.appendChild(knob);

      function startAction() { isMoving = true; }

      function moveAction(e) {
        if (!isMoving) return;
        e.preventDefault();
        
        const rect = pad.getBoundingClientRect();
        const input = e.touches ? e.touches[0] : e;
        
        const centerX = rect.width / 2;
        const centerY = rect.height / 2;
        
        let x = input.clientX - rect.left - centerX;
        let y = input.clientY - rect.top - centerY;
        
        const distance = Math.sqrt(x*x + y*y);
        if (distance &amp;gt; 80) {
          x = (x / distance) * 80;
          y = (y / distance) * 80;
        }
        
        knob.style.transform = `translate(${x}px, ${y}px)`;

        if (Date.now() - lastSend &amp;lt; 70) return;
        
        let cmd = &amp;quot;S&amp;quot;;
        if (Math.abs(x) &amp;gt; Math.abs(y)) {
          cmd = x &amp;gt; 25 ? &amp;quot;R&amp;quot; : (x &amp;lt; -25 ? &amp;quot;L&amp;quot; : &amp;quot;S&amp;quot;);
        } else {
          cmd = y &amp;gt; 25 ? &amp;quot;B&amp;quot; : (y &amp;lt; -25 ? &amp;quot;F&amp;quot; : &amp;quot;S&amp;quot;);
        }

        if (cmd !== lastDirection) {
          lastDirection = cmd;
          lastSend = Date.now();
          
          const names = {&amp;quot;F&amp;quot;: &amp;quot;FORWARD ▲&amp;quot;, &amp;quot;B&amp;quot;: &amp;quot;BACKWARD ▼&amp;quot;, &amp;quot;L&amp;quot;: &amp;quot;LEFT Arrow backward&amp;quot;, &amp;quot;R&amp;quot;: &amp;quot;RIGHT Arrow forward&amp;quot;, &amp;quot;S&amp;quot;: &amp;quot;STOP&amp;quot;};
          statusText.innerText = names[cmd];
          
          sendCmd(cmd);
        }
      }

      function endAction() {
        isMoving = false;
        knob.style.transform = &amp;#39;translate(0px, 0px)&amp;#39;;
        if (lastDirection !== &amp;quot;S&amp;quot;) {
          lastDirection = &amp;quot;S&amp;quot;;
          statusText.innerText = &amp;quot;STOP&amp;quot;;
          sendCmd(&amp;quot;S&amp;quot;);
        }
      }

      pad.addEventListener(&amp;#39;mousedown&amp;#39;, startAction);
      window.addEventListener(&amp;#39;mousemove&amp;#39;, moveAction);
      window.addEventListener(&amp;#39;mouseup&amp;#39;, endAction);

      pad.addEventListener(&amp;#39;touchstart&amp;#39;, startAction);
      window.addEventListener(&amp;#39;touchmove&amp;#39;, moveAction, { passive: false });
      window.addEventListener(&amp;#39;touchend&amp;#39;, endAction);

      function sendCmd(character) {
        fetch(&amp;#39;/api/move&amp;#39;, {
          method: &amp;#39;POST&amp;#39;,
          headers: { &amp;#39;Content-Type&amp;#39;: &amp;#39;application/json&amp;#39; },
          body: JSON.stringify({ cmd: character })
        }).catch(err =&amp;gt; console.log(err));
      }
    &amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/pre&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;</description></item><item><title>The Master Blueprint, The Integration Wall, and Mechanical Realities</title><link>https://community.element14.com/thread/57197?ContentTypeID=0</link><pubDate>Sun, 16 Aug 2026 23:42:58 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:84241ee9-566a-48fc-993f-88a170a4ee7f</guid><dc:creator>UlolKidz</dc:creator><slash:comments>1</slash:comments><comments>https://community.element14.com/thread/57197?ContentTypeID=0</comments><wfw:commentRss>https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57197/the-master-blueprint-the-integration-wall-and-mechanical-realities/rss?ContentTypeId=0</wfw:commentRss><description>&lt;p&gt;Welcome to the penultimate post for Project GEPARD V5.0. Before I publish the final project summary and overall review for the Element14 EZ-EV Challenge, I need to take a massive step back and be completely honest about the realities of building an autonomous UGV from scratch.&lt;/p&gt;
&lt;p&gt;If there is one overarching takeaway from this entire experience, it is this: compared to the veteran engineers on this forum, I am at the absolute bottom of the robotics learning curve. I took on a project that required a deep understanding of mechanical stress distribution, software abstraction layers, and electrical routing. My biggest mistake wasn&amp;#39;t my ambition&amp;mdash;it was my isolation. I should have asked this community for help far more often. You all have decades of experience, and my stubbornness to brute-force my way through debugging cost me days of progress.&lt;/p&gt;
&lt;p&gt;This build taught me an incredible amount, but it was an absolute trial by fire. Here is my candid retrospective on my master blueprint, the mechanical flaws that emerged under load, the software walls I hit, and the hardware that didn&amp;#39;t survive the journey.&lt;/p&gt;
&lt;p&gt;The Master Blueprint and the &amp;quot;Rung&amp;quot; System&lt;/p&gt;
&lt;p&gt;When I started Project GEPARD, I knew I was building something complex, so I tried to be as disciplined as possible. I didn&amp;#39;t want to build a static toy; I wanted to build a modular autonomous IoT rover that could be used as a testbed for operational analytics.&lt;/p&gt;
&lt;p&gt;To prevent myself from getting overwhelmed by scope creep, I architected a strict PMO (Project Management Office) Gate System to lock my progress. I broke the build down into specific, non-negotiable rungs:&lt;/p&gt;
&lt;p&gt;&amp;nbsp;* Rung 1 (The True MVP): Motor validation to simply make the wheels spin using the UNO Q, TB6612 motor driver, and the battery.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;* Rung 2 &amp;amp; 3: Establishing basic communication tests and manual WASD UDP control from the PC to the rover.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;* Rung 4 &amp;amp; 5: Completing the drive chain and separating the ESP32 camera as its own independent module.&lt;/p&gt;
&lt;p&gt;I explicitly banned myself from writing code for auto-docking, YOLOv8 vision, or SLAM mapping during Build 1, pushing all of those features strictly into Phase 2.&lt;/p&gt;
&lt;p&gt;However, a perfect paper blueprint does not survive contact with reality. Even though I was only trying to execute Rung 1 and Rung 2, I learned the hard way that theoretical planning is only half the battle. If you don&amp;#39;t fully understand how each individual part interacts with the power bus and the logic board before you plug them all in, you will spend dozens of hours hopelessly debugging a tangled system. Integrating a multi-node system without testing component interactions individually creates a state where power, logic, and code bugs overlap.&lt;/p&gt;
&lt;p&gt;Mechanical Forensics: Chassis Wear Under Load&lt;/p&gt;
&lt;p&gt;Because I actually managed to get the chassis moving for testing, the physical realities of 3D-printed robotics quickly became apparent. Building a heavy, tracked FWD rover puts immense stress on plastic parts. Two major mechanical flaws revealed themselves over time:&lt;/p&gt;
&lt;p&gt;1. Rear Axle Spacer Degradation&lt;/p&gt;
&lt;p&gt;I initially designed and printed pipe-like cylindrical spacers on the rear axle to keep the idler wheels aligned. Under the heavy, constant friction of the flexible tracks, these pipe spacers ground down incredibly quickly. As they disintegrated, the rear axle developed a severe slant, which completely threw off the track tension. I learned that using flat, circular pads (like thick washers or shims) distributes the friction over a much larger surface area and prevents this rapid degradation.&lt;/p&gt;
&lt;p&gt;2. Front Sprocket Inward Camber&lt;/p&gt;
&lt;p&gt;Extended use of the rover highlighted a major stress-distribution issue at the front drive sprockets. Over time, the front shaft that connects the JGA25-370 motor to the drive wheel slowly caused the wheels to develop an inward camber (tilting in toward the chassis). The mechanical stress of the tracks pulling on the wheels migrated upwards, concentrating torque toward the roof of the axle housing. This progressively warped the alignment and caused the tracks to bind. FDM printing tolerances for tracked drivetrains require far more reinforcement at the motor mounting points than I originally calculated.&lt;/p&gt;
&lt;p&gt;Software Abstractions: App Lab &amp;amp; The RouterBridge Black Box&lt;/p&gt;
&lt;p&gt;Mechanically, despite the wear, the rover functioned. But getting the code to play nicely with the hardware was a completely different story.&lt;/p&gt;
&lt;p&gt;My biggest bottleneck was the software environment itself. Working with the new Arduino App Lab introduced a whole layer of troubleshooting I wasn&amp;#39;t prepared for. I experienced constant environment quirks where the App Lab interface would clearly state &amp;quot;Library Added,&amp;quot; but when I went to compile the code, the compiler would throw fatal errors stating the module could not be found. I wasted critical hours fighting the IDE over module imports, phantom libraries, and compiling errors that had absolutely nothing to do with my actual logic.&lt;/p&gt;
&lt;p&gt;The Arduino UNO Q is the hero component of this build, featuring an incredible dual-brain architecture. It utilizes an STM32U585 microcontroller alongside a Qualcomm QRB2210 Linux SBC. To get these two brains to talk to each other, you have to use a software protocol called the Arduino_RouterBridge.&lt;/p&gt;
&lt;p&gt;I will be completely honest: I still have absolutely no idea how this Bridge actually works under the hood.&lt;/p&gt;
&lt;p&gt;I successfully implemented the syntax to pass UDP strings from my Python base station script to the MCU. The Bridge.provide_safe and Bridge.notify commands worked syntactically to trigger motor functions and relay telemetry. But the actual underlying mechanics of how the Qualcomm chip parses and hands off that data to the STM32 chip is a complete black box to me. As someone coming from a Python and data analytics background rather than bare-metal C++ and RTOS, operating a system where I didn&amp;#39;t truly understand the core communication protocol made debugging terrifying. Without deeper diagnostic tooling, I felt like I was flying blind.&lt;/p&gt;
&lt;p&gt;The Hardware Graveyard (Failure Analysis)&lt;/p&gt;
&lt;p&gt;Beyond the software and the plastic warping, the physical hardware took a massive beating during the integration phase. Three specific components failed during this project, and troubleshooting them consumed days of bench time:&lt;/p&gt;
&lt;p&gt;1. The INA226 Telemetry Enigma&lt;/p&gt;
&lt;p&gt;The INA226 was supposed to be the core of my operational analytics, continuously polling voltage on the 12V rail and current draw to track power consumption. The I2C address was explicitly hardcoded to 0x40 in the firmware, requiring the A0 and A1 pads to be tied to ground. But the Arduino simply could not find it.&lt;/p&gt;
&lt;p&gt;To ensure I wasn&amp;#39;t dealing with a logic level or software bug, I took a multimeter, set it to Diode Mode, and ran a reverse-bias test on the SDA and SCL pins. The multimeter read a 500mV drop across both data pins to ground, which definitively proved that the internal silicon diode structures of the logic chip were physically intact and alive. I verified the 3.3V logic supply to the VCC pin, yet the I2C bus remained completely dead, and the scanner returned a total failure.&lt;/p&gt;
&lt;p&gt;The frustrating irony of the INA226 breakout board is that the massive physical shunt resistor still worked flawlessly. It continued to pass the full 11.3V battery power to the TB6612 motor driver&amp;#39;s VM pin, allowing the rover to drive with full physical torque. It functioned perfectly as a &amp;quot;dumb&amp;quot; power bridge, but the data lines remained totally silent. I never solved this mystery.&lt;/p&gt;
&lt;p&gt;2. The Dead MPU6050 IMU&lt;/p&gt;
&lt;p&gt;Tracked vehicles inherently suffer from severe heading drift and skid-steer errors. Because of this, an IMU is highly recommended to improve dead reckoning and odometry. I integrated an MPU6050 on the I2C bus at address 0x68. Without warning, the chip simply died. Losing the MPU meant losing the foundation for any future phase of reliable grid mapping. It was a harsh reminder of how fragile cheap breakout boards can be.&lt;/p&gt;
&lt;p&gt;3. The Fried Turret Servo&lt;/p&gt;
&lt;p&gt;The GEPARD sensory turret relies on an MG90S micro metal-gear servo to actively pan the HC-SR04 ultrasonic sensors. During active testing of the sweeping logic on the bench, the servo just gave up and burned out. Whether it was a voltage spike, a mechanical bind in the 3D printed housing, or simply a defective factory unit, it forced me to completely tear down the SPAAG turret assembly and replace the actuation plant mid-build.&lt;/p&gt;
&lt;p&gt;Moving Forward to the Finale&lt;/p&gt;
&lt;p&gt;This project humbled me. It taught me the brutal realities of mechanical tolerances, the frustration of black-box software APIs, and the absolute necessity of step-by-step unit testing for hardware before chassis integration.&lt;/p&gt;
&lt;p&gt;In the next and final post, I will be wrapping up Project GEPARD V5.0. I will showcase the final physical build, review the successes of the platform, and summarize my Element14 EZ-EV Challenge experience. Thank you to everyone on the forum who has followed along&amp;mdash;I promise I will be leveraging your expertise much earlier on the next build!&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;AI disclaimer: Google Gemini was used to edit the posts wording, gramma and formatting.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;</description></item><item><title>SmartAssist EV - Strict Priority Hierarchy with Tactile Mechanical Bumpers - Part 6</title><link>https://community.element14.com/thread/57196?ContentTypeID=0</link><pubDate>Sun, 16 Aug 2026 23:24:13 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:65731c16-9ff0-4a0c-9f53-d281e6d30c53</guid><dc:creator>jelektro</dc:creator><slash:comments>0</slash:comments><comments>https://community.element14.com/thread/57196?ContentTypeID=0</comments><wfw:commentRss>https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57196/smartassist-ev---strict-priority-hierarchy-with-tactile-mechanical-bumpers---part-6/rss?ContentTypeId=0</wfw:commentRss><description>&lt;div data-sfc-cp="" data-sfc-root="ep" data-hveid="CAAIEhAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-hveid="CAAIEhAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;p&gt;&lt;span class="user-query-container" style="font-size:150%;"&gt;&lt;span class="user-query-bubble-with-background ng-star-inserted" data-test-id="luminous-collapsed-bubble"&gt;&lt;span class="horizontal-container ng-star-inserted"&gt;Project Roadmap&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Experimental Smart Assistive Platform for Elderly and Disabled People - Part 1" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57188/smartassist-ev-experimental-smart-assistive-platform-for-elderly-and-disabled-people---part-1" data-e14adj="t"&gt;Part 1&lt;/a&gt;&lt;a title="Experimental Smart Assistive Platform for Elderly and Disabled People - Part 1" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57188/smartassist-ev-experimental-smart-assistive-platform-for-elderly-and-disabled-people---part-1" data-e14adj="t"&gt;&amp;nbsp;- Experimental Smart Assistive Platform for Elderly and Disabled People&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Hardware Platform - Part 2" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57189/smartassist-ev---hardware-platform---part-2" data-e14adj="t"&gt;Part 2&lt;/a&gt;&lt;a title="Hardware Platform - Part 2" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57189/smartassist-ev---hardware-platform---part-2" data-e14adj="t"&gt;&amp;nbsp;- Hardware Platform&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Wireless Command and H-Bridge Direct Drive - Part 3" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57190/smartassist-ev---wireless-command-and-h-bridge-direct-drive---part-3" data-e14adj="t"&gt;Part 3&lt;/a&gt;&lt;a title="Wireless Command and H-Bridge Direct Drive - Part 3" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57190/smartassist-ev---wireless-command-and-h-bridge-direct-drive---part-3" data-e14adj="t"&gt;&amp;nbsp;- Wireless Command and H-Bridge Direct Drive&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Part 4 - Introducing Autonomous Line Following (TCRT5000)" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57191/smartassist-ev---introducing-autonomous-line-following-tcrt5000---part-4" data-e14adj="t"&gt;Part 4 - Introducing Autonomous Line Following (TCRT5000)&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Part 5 - Non-Contact Proactive Shielding (HC-SR04 Range Finder)" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57194/smartassist-ev---non-contact-proactive-shielding-hc-sr04-range-finder---part-5" data-e14adj="t"&gt;Part 5 - Non-Contact Proactive Shielding (HC-SR04 Range Finder)&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Part 6 - Strict Priority Hierarchy with Tactile Mechanical Bumpers" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57196/smartassist-ev---strict-priority-hierarchy-with-tactile-mechanical-bumpers---part-6" data-e14adj="t"&gt;Part 6 - Strict Priority Hierarchy with Tactile Mechanical Bumpers&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Part 7 - Mobile Robot Control and Live Video Streaming" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57198/smartassist-ev---mobile-robot-control-and-live-video-streaming---part-7" data-e14adj="t"&gt;Part 7 - Mobile Robot Control and Live Video Streaming&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-hveid="CAAIEhAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;Ultrasonic sensors can miss low-profile objects, clear glass, or acoustic-absorbing fabrics. To handle these blind spots, we add physical Tactile Bumper Switches (CollisionPin_L, CollisionPin_R) as a final layer of defense.To manage all these inputs, the code uses a Strict Priority Hierarchy inside the core runtime evaluation loop:Priority 1 (Highest): Tactile Impact Check. If a physical collision is registered, the robot stops everything, backs up, and executes a wide turning maneuver.Priority 2 (Medium): Ultrasonic Proactive Clearance. If an object is detected within 30cm, the robot performs a quick corrective turn to avoid an accident.Priority 3 (Lowest): Standard Path Following. If both safety systems report a clear path, the robot continues tracking the line.&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-hveid="CAAIEhAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;The full software build adds tactical mechanical bumper microswitches routed to pins A4 (18) and A5 (19). This program runs a rigorous hierarchy matrix: physical bumper triggers grab immediate absolute priority (initiating multi-second reversing maneuvers), sonar triggers handle medium-priority non-contact adjustments, while line following proceeds only when both safety nets report a clear path.&amp;nbsp;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-hveid="CAAIEhAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;Here is the final, production-ready source architecture merging all four functional modules:&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-wiz-uids="pKKIde_3z,pKKIde_3y" data-hveid="CAAIExAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 4px 0px 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;div data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;div data-animation-atomic="" data-sae="" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 1px solid rgb(240, 242, 245);"&gt;
&lt;div dir="ltr" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;pre data-copy-service-computed-style="font-family: monospace; font-size: 14px; font-weight: 400; margin: 14px 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;img alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/6_2D00_1.jpg" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/6_2D00_2.jpg" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/0451.5_2D00_3.jpg" /&gt;&lt;/pre&gt;
&lt;p data-path-to-node="0"&gt;The primary addition is the support for physical collision sensors (&lt;b data-path-to-node="0" data-index-in-node="146"&gt;bumpers&lt;/b&gt;) along with a 3-level safety hierarchy for handling obstacles.&lt;/p&gt;
&lt;h3 id="mcetoc_1k0p5e8dh0" data-path-to-node="2"&gt;1. Pin Definitions and Escape Speed&lt;/h3&gt;
&lt;div class="code-block ng-tns-c998324306-285 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" style="display:block;" data-hveid="0" data-ved="0CAAQhtANahgKEwiD6aTnt7iWAxUAAAAAHQAAAAAQoQU"&gt;
&lt;div class="formatted-code-block-internal-container ng-tns-c998324306-285"&gt;
&lt;div class="animated-opacity ng-tns-c998324306-285"&gt;
&lt;pre class="ng-tns-c998324306-285"&gt;&lt;code class="code-container formatted ng-tns-c998324306-285" data-test-id="code-content"&gt;&lt;span class="hljs-comment"&gt;&lt;/span&gt;&lt;pre class="ui-code" data-mode="text"&gt;// Physical Tactile Bumpers (Mapped to pins A4 and A5 on Arduino)
const int COLLISION_PIN_L = 18; // Pin A4
const int COLLISION_PIN_R = 19; // Pin A5

const int ESCAPE_SPEED = 195; // Specific heavy escape/evasive speed vector&lt;/pre&gt;&lt;span class="hljs-comment"&gt;&lt;/span&gt;
&lt;/code&gt;Explanation:&lt;br /&gt;Added two new pins corresponding to the left (A4 / digital pin 18) and right (A5 / digital pin 19) physical collision microswitches/bumpers.&lt;br /&gt;Defined ESCAPE_SPEED = 195, a dedicated motor speed used during longer backing-up and evasive maneuvers triggered by physical impacts.&lt;br /&gt;&lt;b data-path-to-node="4,0,0" data-index-in-node="0"&gt;&lt;br /&gt;&lt;/b&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id="mcetoc_1k0p5e8dh1" data-path-to-node="6"&gt;2. Sensor Initialization in &lt;code data-path-to-node="6" data-index-in-node="28"&gt;setup()&lt;/code&gt;&lt;/h3&gt;
&lt;div class="code-block ng-tns-c998324306-286 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" style="display:block;" data-hveid="0" data-ved="0CAAQhtANahgKEwiD6aTnt7iWAxUAAAAAHQAAAAAQogU"&gt;
&lt;div class="formatted-code-block-internal-container ng-tns-c998324306-286"&gt;
&lt;div class="animated-opacity ng-tns-c998324306-286"&gt;
&lt;pre class="ng-tns-c998324306-286"&gt;&lt;code class="code-container formatted ng-tns-c998324306-286" data-test-id="code-content"&gt;&lt;span class="hljs-comment"&gt;&lt;/span&gt;&lt;pre class="ui-code" data-mode="text"&gt;// Set as INPUT to match the original sponatenous logic of Elecrow modules
pinMode(COLLISION_PIN_L, INPUT);
pinMode(COLLISION_PIN_R, INPUT);&lt;/pre&gt;&lt;br /&gt;Explanation: Sets the bumper pins as standard digital inputs (INPUT). These modules send a high signal (HIGH / 1) to the pin when pressed.&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 id="mcetoc_1k0p5e8dh2" data-path-to-node="10"&gt;3. Safety Hierarchy Logic in &lt;code data-path-to-node="10" data-index-in-node="29"&gt;loop()&lt;/code&gt;&lt;/h3&gt;
&lt;p data-path-to-node="11"&gt;In autonomous mode (&lt;code data-path-to-node="11" data-index-in-node="20"&gt;MODE_AUTONOMOUS&lt;/code&gt;), the code now executes operations based on a &lt;b data-path-to-node="11" data-index-in-node="82"&gt;3-level priority hierarchy&lt;/b&gt;:&lt;/p&gt;
&lt;h4 data-path-to-node="12"&gt;Step 1: Read Bumper States (Binary Code)&lt;/h4&gt;
&lt;div class="code-block ng-tns-c998324306-287 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" style="display:block;" data-hveid="0" data-ved="0CAAQhtANahgKEwiD6aTnt7iWAxUAAAAAHQAAAAAQowU"&gt;
&lt;div class="formatted-code-block-internal-container ng-tns-c998324306-287"&gt;
&lt;div class="animated-opacity ng-tns-c998324306-287"&gt;
&lt;pre class="ng-tns-c998324306-287"&gt;&lt;code class="code-container formatted ng-tns-c998324306-287" data-test-id="code-content"&gt;&lt;span class="hljs-keyword"&gt;&lt;/span&gt;&lt;pre class="ui-code" data-mode="text"&gt;int collisionState = (digitalRead(COLLISION_PIN_L) == HIGH ? 1 : 0) * 2 + (digitalRead(COLLISION_PIN_R) == HIGH ? 1 : 0);&lt;/pre&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node="14"&gt;Creates a bitwise variable &lt;code data-path-to-node="14" data-index-in-node="27"&gt;collisionState&lt;/code&gt; with the following possible values:&lt;/p&gt;
&lt;ul data-path-to-node="15"&gt;
&lt;li&gt;
&lt;p data-path-to-node="15,0,0"&gt;&lt;b data-path-to-node="15,0,0" data-index-in-node="0"&gt;&lt;code data-path-to-node="15,0,0" data-index-in-node="0"&gt;0&lt;/code&gt;&lt;/b&gt; &amp;ndash; No collision.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p data-path-to-node="15,1,0"&gt;&lt;b data-path-to-node="15,1,0" data-index-in-node="0"&gt;&lt;code data-path-to-node="15,1,0" data-index-in-node="0"&gt;1&lt;/code&gt;&lt;/b&gt; &amp;ndash; Right-side collision.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p data-path-to-node="15,2,0"&gt;&lt;b data-path-to-node="15,2,0" data-index-in-node="0"&gt;&lt;code data-path-to-node="15,2,0" data-index-in-node="0"&gt;2&lt;/code&gt;&lt;/b&gt; &amp;ndash; Left-side collision.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p data-path-to-node="15,3,0"&gt;&lt;b data-path-to-node="15,3,0" data-index-in-node="0"&gt;&lt;code data-path-to-node="15,3,0" data-index-in-node="0"&gt;3&lt;/code&gt;&lt;/b&gt; &amp;ndash; Central collision (both bumpers pressed).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-path-to-node="17"&gt;Priority 1: Physical Impact Response (Bumpers)&lt;/h4&gt;
&lt;p data-path-to-node="18"&gt;This is the top priority, handling cases where the robot has physically made contact with an obstacle (e.g., an object below the ultrasonic beam):&lt;/p&gt;
&lt;div class="code-block ng-tns-c998324306-288 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" style="display:block;" data-hveid="0" data-ved="0CAAQhtANahgKEwiD6aTnt7iWAxUAAAAAHQAAAAAQpAU"&gt;
&lt;div class="formatted-code-block-internal-container ng-tns-c998324306-288"&gt;
&lt;div class="animated-opacity ng-tns-c998324306-288"&gt;
&lt;pre class="ng-tns-c998324306-288"&gt;&lt;code class="code-container formatted ng-tns-c998324306-288" data-test-id="code-content"&gt;&lt;span class="hljs-keyword"&gt;&lt;/span&gt;&lt;pre class="ui-code" data-mode="text"&gt;if (collisionState &amp;gt; 0) {
  Serial.print(&amp;quot;TACTILE COLLISION TRIGGERED. Code: &amp;quot;);
  Serial.println(collisionState);
  
  switch (collisionState) {
    case 1: // Right collision -&amp;gt; Reverse for 2s, then turn left for 2s
      driveMotors(ESCAPE_SPEED, 0, ESCAPE_SPEED, 0); delay(2000);
      driveMotors(ESCAPE_SPEED, 0, 0, ESCAPE_SPEED); delay(2000);
      break;
    case 2: // Left collision -&amp;gt; Reverse for 2s, then turn right for 2s
      driveMotors(ESCAPE_SPEED, 0, ESCAPE_SPEED, 0); delay(2000);
      driveMotors(0, ESCAPE_SPEED, ESCAPE_SPEED, 0); delay(2000);
      break;
    case 3: // Central collision -&amp;gt; Reverse for 2s, then perform U-turn (left) for 2s
      driveMotors(ESCAPE_SPEED, 0, ESCAPE_SPEED, 0); delay(2000);
      driveMotors(ESCAPE_SPEED, 0, 0, ESCAPE_SPEED); delay(2000);
      break;
  }
}&lt;/pre&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Behavior: Upon impact, the robot reverses for 2 seconds, then spins away from the side of impact for 2 seconds to clear the obstacle.&lt;/p&gt;
&lt;h4 data-path-to-node="22"&gt;Priority 2: Proactive Distance Check (HC-SR04 Sonar)&lt;/h4&gt;
&lt;p data-path-to-node="23"&gt;If no physical collision occurred (&lt;code data-path-to-node="23" data-index-in-node="35"&gt;collisionState == 0&lt;/code&gt;), the code checks the ultrasonic rangefinder:&lt;/p&gt;
&lt;div class="code-block ng-tns-c998324306-289 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" style="display:block;" data-hveid="0" data-ved="0CAAQhtANahgKEwiD6aTnt7iWAxUAAAAAHQAAAAAQpQU"&gt;
&lt;div class="formatted-code-block-internal-container ng-tns-c998324306-289"&gt;
&lt;div class="animated-opacity ng-tns-c998324306-289"&gt;
&lt;pre class="ng-tns-c998324306-289"&gt;&lt;code class="code-container formatted ng-tns-c998324306-289" data-test-id="code-content"&gt;&lt;span class="hljs-keyword"&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre class="ui-code" data-mode="text"&gt;else if (distance &amp;lt; 30.0) {
  Serial.println(&amp;quot;Obstacle detected by Sonar! Turning left...&amp;quot;);
  driveMotors(MOTOR_SPEED, 0, 0, MOTOR_SPEED); 
  delay(300);                                   
} &lt;/pre&gt;
&lt;h4 data-path-to-node="27"&gt;&lt;/h4&gt;
&lt;h4 data-path-to-node="27"&gt;&lt;span style="font-size:inherit;"&gt;Behavior: If an obstacle is detected within 30 cm, the robot performs a short left turn 30ms to steer around the obstacle before physical contact happens.&lt;/span&gt;&lt;/h4&gt;
&lt;h4 data-path-to-node="27"&gt;Priority 3: Line Following Routine&lt;/h4&gt;
&lt;p data-path-to-node="28"&gt;If there is no physical impact and no obstacles within &lt;span class="math-inline" data-index-in-node="55"&gt;30cm&lt;/span&gt;, the robot falls back to standard line tracking logic (&lt;code data-path-to-node="28" data-index-in-node="123"&gt;else { ... }&lt;/code&gt;).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-hveid="CAAIEhAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-hveid="CAAIEhAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-hveid="CAAIEhAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-hveid="CAAIEhAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-hveid="CAAIEhAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-hveid="CAAIEhAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;span style="font-size:150%;"&gt;Final Code&lt;/span&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-hveid="CAAIEhAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-hveid="CAAIEhAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;p data-path-to-node="0"&gt;Here is the complete, merged C program for the &lt;b data-path-to-node="0" data-index-in-node="49"&gt;Arduino UNO Q&lt;/b&gt;, incorporating &lt;b data-path-to-node="0" data-index-in-node="78"&gt;all features from all previous versions&lt;/b&gt;:&lt;/p&gt;
&lt;ul data-path-to-node="1"&gt;
&lt;li&gt;
&lt;p data-path-to-node="1,0,0"&gt;IR Remote Control&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p data-path-to-node="1,1,0"&gt;Line Tracking&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p data-path-to-node="1,2,0"&gt;HC-SR04 Ultrasonic Distance Sensor&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p data-path-to-node="1,3,0"&gt;Physical Tactile Collision Bumpers&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p data-path-to-node="1,4,0"&gt;Onboard RGB LED indicators (&lt;code data-path-to-node="1,4,0" data-index-in-node="28"&gt;LED3&lt;/code&gt; and &lt;code data-path-to-node="1,4,0" data-index-in-node="37"&gt;LED4&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p data-path-to-node="1,5,0"&gt;&lt;span class="math-inline" data-index-in-node="0"&gt;8x13&lt;/span&gt; LED Matrix Display (displaying directional arrows and the STOP icon)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="text"&gt;#include &amp;lt;Arduino.h&amp;gt;
#include &amp;lt;Arduino_LED_Matrix.h&amp;gt; // Library for the 8x13 LED matrix

Arduino_LED_Matrix matrix; // Initialize LED matrix object

// L9110S Motor Driver Pins
const int MOTOR_PIN_A1 = 5; 
const int MOTOR_PIN_A2 = 6; 
const int MOTOR_PIN_B1 = 9; 
const int MOTOR_PIN_B2 = 10;

// Infrared Receiver Pin
const int IR_RECEIVE_PIN = 2; 

// Line Tracking Sensor Pins
const int TRACKING_PIN_L = A2;
const int TRACKING_PIN_R = A3;

// Ultrasonic HC-SR04 Sonar Pins
const int TRIG_PIN = 4;      
const int ECHO_PIN = 3;      

// Physical Tactile Bumpers
const int COLLISION_PIN_L = 18; // Pin A4
const int COLLISION_PIN_R = 19; // Pin A5

enum RobotMode { 
  MODE_MANUAL, 
  MODE_AUTONOMOUS 
};

RobotMode currentMode = MODE_MANUAL; 

// Speed settings
const int MOTOR_SPEED = 200; 
const int ESCAPE_SPEED = 195; // Speed vector for escape maneuvers after collision
const int TRACK_SPEED = 160;  

byte lastCommand = 0;

// LED Matrix Frame Buffer Size (104 pixels)
const uint8_t FRAME_SIZE = 8 * 13;

// --- LED MATRIX ARROW &amp;amp; ICON ARRAYS (Brightness levels 0-7) ---
uint8_t arrow_up[FRAME_SIZE] = {
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 7, 7, 7, 7, 7, 0, 0, 0, 0,
    0, 0, 0, 7, 7, 0, 7, 0, 7, 7, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0
};

uint8_t arrow_down[FRAME_SIZE] = {
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 7, 7, 0, 7, 0, 7, 7, 0, 0, 0,
    0, 0, 0, 0, 7, 7, 7, 7, 7, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0
};

uint8_t arrow_left[FRAME_SIZE] = {
    0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    0, 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0
};

uint8_t arrow_right[FRAME_SIZE] = {
    0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 0,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0
};

uint8_t stop_icon[FRAME_SIZE] = { 0 }; 

// --- ONBOARD RGB LED FUNCTIONS ---
void set_led3_color(int r, int g, int b) {
  analogWrite(LED3_R, r);
  analogWrite(LED3_G, g);
  analogWrite(LED3_B, b);
}

void set_led4_color(bool r, bool g, bool b) {
  digitalWrite(LED4_R, r ? LOW : HIGH);
  digitalWrite(LED4_G, g ? LOW : HIGH);
  digitalWrite(LED4_B, b ? LOW : HIGH);
}

// Elecrow IR Decoder
long readElecrowIR() {
  int count = 0;
  while (digitalRead(IR_RECEIVE_PIN) == LOW &amp;amp;&amp;amp; count &amp;lt; 200) { count++; delayMicroseconds(60); }
  if (count &amp;gt;= 200) return -1;

  count = 0;
  while (digitalRead(IR_RECEIVE_PIN) == HIGH &amp;amp;&amp;amp; count &amp;lt; 80) { count++; delayMicroseconds(60); }
  if (count &amp;gt;= 80) return -1;

  int idx = 0, cnt = 0;
  byte data[4] = {0, 0, 0, 0};

  for (int i = 0; i &amp;lt; 32; i++) {
    count = 0;
    while (digitalRead(IR_RECEIVE_PIN) == LOW &amp;amp;&amp;amp; count &amp;lt; 15) { count++; delayMicroseconds(60); }
    count = 0;
    while (digitalRead(IR_RECEIVE_PIN) == HIGH &amp;amp;&amp;amp; count &amp;lt; 40) { count++; delayMicroseconds(60); }

    if (count &amp;gt; 8) data[idx] |= (1 &amp;lt;&amp;lt; cnt);

    if (cnt == 7) { cnt = 0; idx++; } else { cnt++; }
  }

  if ((byte)(data[0] + data[1]) == 0xFF &amp;amp;&amp;amp; (byte)(data[2] + data[3]) == 0xFF) {
    return data[2];
  }
  return -1;
}

void driveMotors(int a1, int a2, int b1, int b2) {
  analogWrite(MOTOR_PIN_A1, a1);
  analogWrite(MOTOR_PIN_A2, a2);
  analogWrite(MOTOR_PIN_B1, b1);
  analogWrite(MOTOR_PIN_B2, b2);
}

float getDistance() {
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);
  
  long duration = pulseIn(ECHO_PIN, HIGH, 30000); 
  float d = duration * 0.0343 / 2;
  
  if (d == 0) return 999.0;
  return d;
}

void executeCommand(byte command) {
  switch (command) {
    case 0x1C: // OK Button - Toggle Manual/Autonomous operations
      if (currentMode == MODE_MANUAL) { 
        currentMode = MODE_AUTONOMOUS; 
        digitalWrite(LED_BUILTIN, HIGH);
        Serial.println(&amp;quot;System Notification: AUTONOMY MODE ENGAGED.&amp;quot;);
      } 
      else { 
        currentMode = MODE_MANUAL; 
        digitalWrite(LED_BUILTIN, LOW);
        driveMotors(0, 0, 0, 0); 
        set_led4_color(false, false, false);
        set_led3_color(0, 0, 0);
        matrix.draw(stop_icon);
        Serial.println(&amp;quot;System Notification: MANUAL OVERRIDE ENGAGED.&amp;quot;);
      }
      lastCommand = 0; 
      delay(500);      
      break;

    case 0x18: // Forward
      if (currentMode == MODE_MANUAL) {
        driveMotors(0, MOTOR_SPEED, 0, MOTOR_SPEED);
        set_led4_color(false, true, false); // Green
        set_led3_color(0, 200, 0);
        matrix.draw(arrow_up);
      }
      break;

    case 0x08: // Spin Left
      if (currentMode == MODE_MANUAL) {
        driveMotors(MOTOR_SPEED, 0, 0, MOTOR_SPEED);
        set_led4_color(false, false, true); // Blue
        set_led3_color(0, 0, 200);
        matrix.draw(arrow_left);
      }
      break;

    case 0x5A: // Spin Right
      if (currentMode == MODE_MANUAL) {
        driveMotors(0, MOTOR_SPEED, MOTOR_SPEED, 0);
        set_led4_color(false, false, true); // Blue
        set_led3_color(0, 0, 200);
        matrix.draw(arrow_right);
      }
      break;

    case 0x52: // Backward
      if (currentMode == MODE_MANUAL) {
        driveMotors(MOTOR_SPEED, 0, MOTOR_SPEED, 0);
        set_led4_color(true, false, false); // Red
        set_led3_color(200, 0, 0);
        matrix.draw(arrow_down);
      }
      break;

    default:   
      if (currentMode == MODE_MANUAL) {
        driveMotors(0, 0, 0, 0);
        set_led4_color(false, false, false);
        set_led3_color(0, 0, 0);
        matrix.draw(stop_icon);
      }
      break;
  }
}

void setup() {
  Serial.begin(115200); 

  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);

  pinMode(IR_RECEIVE_PIN, INPUT_PULLUP); 
  pinMode(TRACKING_PIN_L, INPUT_PULLUP);
  pinMode(TRACKING_PIN_R, INPUT_PULLUP);

  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);

  pinMode(COLLISION_PIN_L, INPUT);
  pinMode(COLLISION_PIN_R, INPUT);

  // RGB LED pin configuration
  pinMode(LED4_R, OUTPUT); 
  pinMode(LED4_G, OUTPUT); 
  pinMode(LED4_B, OUTPUT);
  set_led3_color(0, 0, 0);
  set_led4_color(false, false, false);

  // Initialize LED Matrix
  matrix.begin();
  matrix.setGrayscaleBits(3);
  matrix.clear();

  Serial.println(&amp;quot;System Core Ready on Arduino UNO Q (IR + Line + Sonar + Bumpers + Display/LEDs).&amp;quot;);
}

void loop() {
  // 1. Process incoming IR signals
  if (digitalRead(IR_RECEIVE_PIN) == LOW) {
    long result = readElecrowIR();
    if (result != -1) {
      lastCommand = (byte)result;
      executeCommand(lastCommand);
    }
  }

  // 2. Continuous Mode Execution
  if (currentMode == MODE_MANUAL) {
    if (digitalRead(IR_RECEIVE_PIN) == HIGH) {
      driveMotors(0, 0, 0, 0); 
      set_led4_color(false, false, false);
      set_led3_color(0, 0, 0);
      matrix.draw(stop_icon);
    } else {
      executeCommand(lastCommand);
    }
  } 
  else if (currentMode == MODE_AUTONOMOUS) {
    float distance = getDistance();
    int collisionState = (digitalRead(COLLISION_PIN_L) == HIGH ? 1 : 0) * 2 + (digitalRead(COLLISION_PIN_R) == HIGH ? 1 : 0);
    
    // --- HIERARCHY LEVEL 1: Physical Impact Interception (Bumpers) ---
    if (collisionState &amp;gt; 0) {
      Serial.print(&amp;quot;TACTILE COLLISION TRIGGERED. Code: &amp;quot;);
      Serial.println(collisionState);
      
      set_led4_color(true, false, false); // Red alert
      set_led3_color(200, 0, 0);

      switch (collisionState) {
        case 1: // Right-side collision -&amp;gt; Reverse, then turn Left
          matrix.draw(arrow_down);
          driveMotors(ESCAPE_SPEED, 0, ESCAPE_SPEED, 0); delay(2000);
          matrix.draw(arrow_left);
          driveMotors(ESCAPE_SPEED, 0, 0, ESCAPE_SPEED); delay(2000);
          break;
        case 2: // Left-side collision -&amp;gt; Reverse, then turn Right
          matrix.draw(arrow_down);
          driveMotors(ESCAPE_SPEED, 0, ESCAPE_SPEED, 0); delay(2000);
          matrix.draw(arrow_right);
          driveMotors(0, ESCAPE_SPEED, ESCAPE_SPEED, 0); delay(2000);
          break;
        case 3: // Central collision -&amp;gt; Reverse, then U-Turn (Left)
          matrix.draw(arrow_down);
          driveMotors(ESCAPE_SPEED, 0, ESCAPE_SPEED, 0); delay(2000);
          matrix.draw(arrow_left);
          driveMotors(ESCAPE_SPEED, 0, 0, ESCAPE_SPEED); delay(2000);
          break;
      }
    } 
    // --- HIERARCHY LEVEL 2: Proactive Clearance Monitoring (HC-SR04 Sonar) ---
    else if (distance &amp;lt; 30.0) {
      Serial.println(&amp;quot;Obstacle detected by Sonar! Turning left...&amp;quot;);
      driveMotors(MOTOR_SPEED, 0, 0, MOTOR_SPEED); // Evade Left
      set_led4_color(true, false, false);          // Red warning
      set_led3_color(200, 0, 0);
      matrix.draw(arrow_left); 
      delay(300);                                  
    } 
    // --- HIERARCHY LEVEL 3: Standard Path Routine (Line Tracking) ---
    else {
      int trackL = digitalRead(TRACKING_PIN_L);
      int trackR = digitalRead(TRACKING_PIN_R);
      int trackState = (trackL * 2) + trackR; 
      
      switch (trackState) {
        case 0: // Off track -&amp;gt; Stop
          driveMotors(0, 0, 0, 0); 
          set_led4_color(false, false, false);
          set_led3_color(0, 0, 0);
          matrix.draw(stop_icon);
          break;

        case 1: // Right sensor active -&amp;gt; Turn Right
          driveMotors(0, TRACK_SPEED, TRACK_SPEED, 0); 
          set_led4_color(false, false, true); // Blue
          set_led3_color(0, 0, 200);
          matrix.draw(arrow_right);
          break;

        case 2: // Left sensor active -&amp;gt; Turn Left
          driveMotors(TRACK_SPEED, 0, 0, TRACK_SPEED); 
          set_led4_color(false, false, true); // Blue
          set_led3_color(0, 0, 200);
          matrix.draw(arrow_left);
          break;

        case 3: // Both sensors active -&amp;gt; Move Forward
          driveMotors(0, TRACK_SPEED, 0, TRACK_SPEED); 
          set_led4_color(false, true, false); // Green
          set_led3_color(0, 200, 0);
          matrix.draw(arrow_up);
          break;
      }
      delay(10); 
    }
  }
}&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/div&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;</description></item><item><title>Project GEPARD V5.0 - Post 4: Proof of Concept Docking</title><link>https://community.element14.com/thread/57195?ContentTypeID=0</link><pubDate>Sun, 16 Aug 2026 23:15:41 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:a9fb28a1-cdeb-44e6-8996-aafa6b8627e8</guid><dc:creator>UlolKidz</dc:creator><slash:comments>1</slash:comments><comments>https://community.element14.com/thread/57195?ContentTypeID=0</comments><wfw:commentRss>https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57195/project-gepard-v5-0---post-4-proof-of-concept-docking/rss?ContentTypeId=0</wfw:commentRss><description>&lt;p data-path-to-node="1"&gt;&lt;a href="https://youtu.be/yx7YVVgXmXQ"&gt;https://youtu.be/yx7YVVgXmXQ&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Hardware engineering is inherently unpredictable. Sometimes the biggest challenges you face aren&amp;#39;t found in your C++ code, your EMI shielding, or your power distribution logic. Sometimes, the integration wall is the physical environment where you are building.&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;Over the past week of working on Project GEPARD V5.0 for the Element14 EZ-EV Challenge, I encountered an emergency that had absolutely nothing to do with microcontrollers or sensors. My house was hit by a sudden, severe mice infestation. Dealing with the cleaning, and completely securing my living space from pests consumed the crucial remaining days I had. Because of this localized emergency, I was forced to halt the physical build right before the final electrical wiring for the dock&amp;#39;s power supply could be completed.&lt;/p&gt;
&lt;p data-path-to-node="3"&gt;As a result, the current deliverable is a highly detailed, mechanical Proof of Concept (PoC) for the docking sequence, rather than a fully energized charging station. Here is the complete breakdown of the mechanical PoC, the theoretical electrical handshake, the power physics, and a deeply frustrating hardware mystery.&lt;/p&gt;
&lt;h3 id="mcetoc_1k06dk6294" data-path-to-node="4"&gt;The Mechanical PoC Video&lt;/h3&gt;
&lt;p data-path-to-node="5"&gt;In the accompanying demo video, you will see the rover fully mobile and executing the physical docking sequence. The dual JGA25-370 gear motors drive the heavy-duty tracks smoothly, and I manually pilot the rover squarely into the docking bay. You will see the physical alignment succeed as the front of the rover makes contact with the dock.&lt;/p&gt;
&lt;p data-path-to-node="6"&gt;However, because the dock itself is not cabled to the wall or the boost converter yet, this sequence is purely mechanical. No actual electrical power is being transferred during the video. The structural alignment, the chassis approach, and the magnetic mating of the pins work flawlessly, proving the physical viability of the custom dock design.&lt;/p&gt;
&lt;p data-path-to-node="7"&gt;If the dock were fully wired to the electrical mains, that simple mechanical connection would trigger a highly complex, carefully orchestrated electrical handshake.&lt;/p&gt;
&lt;h3 id="mcetoc_1k06dk6295" data-path-to-node="8"&gt;The Docking Interface &amp;amp; RS485 Handshake&lt;/h3&gt;
&lt;p id="p-rc_6bb2da698cbee7e5-83" data-path-to-node="9"&gt;&lt;span data-path-to-node="9,0"&gt;When building an autonomous charging dock, you cannot simply leave live voltage resting on exposed metal contacts. That is a massive fire hazard and a risk to the hardware. To solve this, Project GEPARD uses a strict &amp;quot;cold-launch&amp;quot; docking architecture&lt;/span&gt;&lt;span data-path-to-node="9,1"&gt;&lt;sup class="superscript"&gt;&lt;/sup&gt;&lt;/span&gt;&lt;span data-path-to-node="9,2"&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p id="p-rc_6bb2da698cbee7e5-84" data-path-to-node="10"&gt;&lt;span data-path-to-node="10,0"&gt;The interface on the rear bumper of the rover consists of two separate magnetic arrays: a 2-Pin Magnetic High-Current Pogo array dedicated exclusively to charging power, and a 4-Pin Magnetic Pogo array dedicated to data&lt;/span&gt;&lt;span data-path-to-node="10,1"&gt;&lt;sup class="superscript"&gt;&lt;/sup&gt;&lt;/span&gt;&lt;span data-path-to-node="10,2"&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p id="p-rc_6bb2da698cbee7e5-85" data-path-to-node="11"&gt;&lt;span data-path-to-node="11,0"&gt;When the rover approaches and makes physical contact with the dock, the 4-pin data pogo parasitically boots the dock&amp;#39;s logic&lt;/span&gt;&lt;span data-path-to-node="11,1"&gt;&lt;sup class="superscript"&gt;&lt;/sup&gt;&lt;/span&gt;&lt;span data-path-to-node="11,2"&gt;. This initiates the critical communication sequence. The system relies on ADI ADM3068E boards mounted on both the rover and the dock&lt;/span&gt;&lt;span data-path-to-node="11,3"&gt;&lt;sup class="superscript"&gt;&lt;/sup&gt;&lt;/span&gt;&lt;span data-path-to-node="11,4"&gt;. Once booted, the RS485 handshake begins, establishing an EMI-resistant communication line directly between the rover and the dock&lt;/span&gt;&lt;span data-path-to-node="11,5"&gt;&lt;sup class="superscript"&gt;&lt;/sup&gt;&lt;/span&gt;&lt;span data-path-to-node="11,6"&gt;. This isolated handshake ensures that the dock verifies the exact cryptographic identity of the rover before any high voltage is allowed to flow&lt;/span&gt;&lt;span data-path-to-node="11,7"&gt;&lt;sup class="superscript"&gt;&lt;/sup&gt;&lt;/span&gt;&lt;span data-path-to-node="11,8"&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h3 id="mcetoc_1k06dk6296" data-path-to-node="12"&gt;Power Delivery and the 12.85V Boost&lt;/h3&gt;
&lt;p id="p-rc_6bb2da698cbee7e5-86" data-path-to-node="13"&gt;&lt;span data-path-to-node="13,0"&gt;Once the dock successfully validates the rover&amp;#39;s identity, the physical power delivery sequence prepares to launch. The base station is powered by a standard 12V 3A wall adapter&lt;/span&gt;&lt;span data-path-to-node="13,1"&gt;&lt;sup class="superscript"&gt;&lt;/sup&gt;&lt;/span&gt;&lt;span data-path-to-node="13,2"&gt;. However, you cannot efficiently charge a multi-cell battery pack with a flat 12V supply; you need electrical pressure to push the current into the lithium-ion cells.&lt;/span&gt;&lt;/p&gt;
&lt;p id="p-rc_6bb2da698cbee7e5-87" data-path-to-node="14"&gt;&lt;span data-path-to-node="14,0"&gt;To achieve this necessary electrical pressure, the 12V input from the wall is routed through an XL6009 Boost Converter that is tuned specifically to output 12.85V&lt;/span&gt;&lt;span data-path-to-node="14,1"&gt;&lt;sup class="superscript"&gt;&lt;/sup&gt;&lt;/span&gt;&lt;span data-path-to-node="14,2"&gt;. This 12.85V threshold is the precise peak voltage required to safely push current into the internal UPS battery pack and fully saturate the cells without causing degradation or over-volting them.&lt;/span&gt;&lt;/p&gt;
&lt;h3 id="mcetoc_1k06dk6297" data-path-to-node="15"&gt;The Cold-Launch Relay Sequence&lt;/h3&gt;
&lt;p id="p-rc_6bb2da698cbee7e5-88" data-path-to-node="16"&gt;&lt;span data-path-to-node="16,0"&gt;Even with the voltage actively boosted to 12.85V within the dock, that power is kept completely isolated from the exposed external pogo pins until the exact moment of validation. The dock&amp;#39;s Arduino controls a 5V Single-Channel Relay to act as the ultimate gatekeeper&lt;/span&gt;&lt;span data-path-to-node="16,1"&gt;&lt;sup class="superscript"&gt;&lt;/sup&gt;&lt;/span&gt;&lt;span data-path-to-node="16,2"&gt;. The 12.85V positive output from the boost converter routes directly into the COM (Common) port of this relay&lt;/span&gt;&lt;span data-path-to-node="16,3"&gt;&lt;sup class="superscript"&gt;&lt;/sup&gt;&lt;/span&gt;&lt;span data-path-to-node="16,4"&gt;. The relay&amp;#39;s Normally Open (NO) terminal routes to the positive pad of the 2-pin magnetic charging pogo&lt;/span&gt;&lt;span data-path-to-node="16,5"&gt;&lt;sup class="superscript"&gt;&lt;/sup&gt;&lt;/span&gt;&lt;span data-path-to-node="16,6"&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p id="p-rc_6bb2da698cbee7e5-89" data-path-to-node="17"&gt;&lt;span data-path-to-node="17,0"&gt;When the RS485 handshake successfully validates the rover, the dock&amp;#39;s Arduino triggers the relay&lt;/span&gt;&lt;span data-path-to-node="17,1"&gt;&lt;sup class="superscript"&gt;&lt;/sup&gt;&lt;/span&gt;&lt;span data-path-to-node="17,2"&gt;. The internal switch flips from COM to NO, instantly sending the 12.85V through the pogo pins, into the rover&amp;#39;s UPS Box input, and through the internal Battery Management System (BMS) to charge the battery cells&lt;/span&gt;&lt;span data-path-to-node="17,3"&gt;&lt;sup class="superscript"&gt;&lt;/sup&gt;&lt;/span&gt;&lt;span data-path-to-node="17,4"&gt;. Because this high-voltage switch happens inside the relay &lt;i data-path-to-node="17,4" data-index-in-node="60"&gt;after&lt;/i&gt; the magnetic pins are already firmly mated together, there is absolutely no sparking or arcing at the physical contact points, maximizing the lifespan of the copper pads.&lt;/span&gt;&lt;/p&gt;
&lt;h3 id="mcetoc_1k06dk6298" data-path-to-node="18"&gt;Sentinel Mode Power State&lt;/h3&gt;
&lt;p id="p-rc_6bb2da698cbee7e5-90" data-path-to-node="19"&gt;&lt;span data-path-to-node="19,0"&gt;Once the relay energizes and the charger is officially connected, the rover enters a sleep state to minimize active current draw and allow the battery to charge as efficiently as possible&lt;/span&gt;&lt;span data-path-to-node="19,1"&gt;&lt;sup class="superscript"&gt;&lt;/sup&gt;&lt;/span&gt;&lt;span data-path-to-node="19,2"&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p id="p-rc_6bb2da698cbee7e5-91" data-path-to-node="20"&gt;&lt;span data-path-to-node="20,0"&gt;However, this sleep state is highly contextual based on the user&amp;#39;s commands. If the rover has been commanded into &amp;quot;Sentinel&amp;quot; mode, the primary drive motors and roaming loops are completely powered down to save energy, but the rover keeps its camera live to actively process the threat assessment triage engine&lt;/span&gt;&lt;span data-path-to-node="20,1"&gt;&lt;sup class="superscript"&gt;&lt;/sup&gt;&lt;/span&gt;&lt;span data-path-to-node="20,2"&gt;. This architecture allows the vehicle to act as a stationary, charging security turret, processing visual data and guarding the room while the battery cells replenish.&lt;/span&gt;&lt;/p&gt;
&lt;h3 id="mcetoc_1k06dk6299" data-path-to-node="21"&gt;Safe Undocking &amp;amp; The BMS Takeover&lt;/h3&gt;
&lt;p data-path-to-node="22"&gt;The charging sequence is relatively straightforward, but leaving the dock safely is where the most critical engineering logic occurs. When the charge is complete, or when the user issues a manual command to deploy, the rover must undock without damaging its sensitive logic boards.&lt;/p&gt;
&lt;p id="p-rc_6bb2da698cbee7e5-92" data-path-to-node="23"&gt;&lt;span data-path-to-node="23,0"&gt;First, the software actively releases the RS485 handshake&lt;/span&gt;&lt;span data-path-to-node="23,1"&gt;&lt;sup class="superscript"&gt;&lt;/sup&gt;&lt;/span&gt;&lt;span data-path-to-node="23,2"&gt;. Immediately after this data signal drops, the dock&amp;#39;s relay opens&lt;/span&gt;&lt;span data-path-to-node="23,3"&gt;&lt;sup class="superscript"&gt;&lt;/sup&gt;&lt;/span&gt;&lt;span data-path-to-node="23,4"&gt;. This action completely deadens the charging pins &lt;i data-path-to-node="23,4" data-index-in-node="51"&gt;before&lt;/i&gt; the magnets physically break apart and the rover pulls away&lt;/span&gt;&lt;span data-path-to-node="23,5"&gt;&lt;sup class="superscript"&gt;&lt;/sup&gt;&lt;/span&gt;&lt;span data-path-to-node="23,6"&gt;. By deadening the pins prior to physical separation, the system definitively prevents electrical arcing and potentially catastrophic voltage brownouts&lt;/span&gt;&lt;span data-path-to-node="23,7"&gt;&lt;sup class="superscript"&gt;&lt;/sup&gt;&lt;/span&gt;&lt;span data-path-to-node="23,8"&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node="24"&gt;Furthermore, cleanly cutting the external 12.85V supply while the rover is still physically stationary forces the rover&amp;#39;s internal hardware BMS to instantly take over the entire electrical load. Because the BMS takes over before the motors even begin to spin, the microcontrollers, sensors, and actuators never experience a sudden voltage drop. When the rover finally engages its tracks to physically pull away from the dock, it is already running entirely on its own internal, stable battery power.&lt;/p&gt;
&lt;h3 id="mcetoc_1k06dk629a" data-path-to-node="25"&gt;The INA226 Telemetry Mystery &amp;amp; Charge Cut-Off&lt;/h3&gt;
&lt;p data-path-to-node="26"&gt;Ideally, the Arduino UNO Q would monitor this entire charging curve and actively tell the dock when the battery is full. However, I have hit a massive, unresolved hardware wall: the INA226 I2C voltage sensor telemetry is completely dead.&lt;/p&gt;
&lt;p data-path-to-node="27"&gt;The sensor simply refuses to communicate with the Arduino. To ensure I wasn&amp;#39;t dealing with a software bug, I went as far as testing the raw silicon using electrical physics. I verified the 3.3V logic power, meticulously checked the wiring continuity, and ultimately ran a reverse-bias diode test using my multimeter directly on the SDA and SCL pins. The multimeter read a 500mV voltage drop, which definitively proves that the internal silicon diode structures of the logic chip are physically alive. Despite the silicon being intact, the I2C bus still refuses to find the address, and the scanner returns a total failure. I honestly still do not know why this is happening.&lt;/p&gt;
&lt;p data-path-to-node="28"&gt;The fascinating part of this failure is how the INA226 breakout board is physically architected. The board features a massive physical shunt resistor that the high-current battery line passes through. Because the logic chip is effectively comatose, the massive shunt resistor now acts as a &amp;quot;dumb&amp;quot; power bridge. It passes the raw 11.3V battery power straight to the motor drivers perfectly, allowing the rover to drive and maneuver with full physical torque. But because the I2C data lines remain entirely silent, the Arduino MCU is completely blind to its own battery voltage.&lt;/p&gt;
&lt;p data-path-to-node="29"&gt;Because the microcontroller cannot see its own battery percentage, it cannot send a software command to the dock to stop the 12.85V flow. As a result, the system must rely entirely on the hardware Battery Management System (BMS) for the charge cut-off. When the rover completes the pogo handshake and the 12.85V power flows in from the energized relay, the hardware BMS acts as the ultimate safety net. Once the lithium-ion cells physically reach their peak 12.6V capacity, the hardware controller physically cuts the current draw. This prevents the cells from overcharging and allows the rover to sit securely in the dock, fully powered and monitoring its environment in Sentinel mode, without relying on a dead I2C sensor.&lt;/p&gt;
&lt;p data-path-to-node="30"&gt;Hardware design is an exercise in mitigating failure. From rat infestations halting wiring, to I2C logic chips inexplicably going dark, having a robust, federated architecture allows the mechanical and safety systems to succeed even when other parameters fail.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;</description></item><item><title>SmartAssist EV - Non-Contact Proactive Shielding (HC-SR04 Range Finder) - Part 5</title><link>https://community.element14.com/thread/57194?ContentTypeID=0</link><pubDate>Sun, 16 Aug 2026 23:08:17 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:68010a55-6e6e-49f4-a287-376d0676dfd7</guid><dc:creator>jelektro</dc:creator><slash:comments>0</slash:comments><comments>https://community.element14.com/thread/57194?ContentTypeID=0</comments><wfw:commentRss>https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57194/smartassist-ev---non-contact-proactive-shielding-hc-sr04-range-finder---part-5/rss?ContentTypeId=0</wfw:commentRss><description>&lt;div data-sfc-cp="" data-sfc-root="ep" data-hveid="CAAIDxAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;p&gt;&lt;span class="user-query-container" style="font-size:150%;"&gt;&lt;span class="user-query-bubble-with-background ng-star-inserted" data-test-id="luminous-collapsed-bubble"&gt;&lt;span class="horizontal-container ng-star-inserted"&gt;Project Roadmap&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Experimental Smart Assistive Platform for Elderly and Disabled People - Part 1" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57188/smartassist-ev-experimental-smart-assistive-platform-for-elderly-and-disabled-people---part-1" data-e14adj="t"&gt;Part 1&lt;/a&gt;&lt;a title="Experimental Smart Assistive Platform for Elderly and Disabled People - Part 1" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57188/smartassist-ev-experimental-smart-assistive-platform-for-elderly-and-disabled-people---part-1" data-e14adj="t"&gt;&amp;nbsp;- Experimental Smart Assistive Platform for Elderly and Disabled People&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Hardware Platform - Part 2" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57189/smartassist-ev---hardware-platform---part-2" data-e14adj="t"&gt;Part 2&lt;/a&gt;&lt;a title="Hardware Platform - Part 2" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57189/smartassist-ev---hardware-platform---part-2" data-e14adj="t"&gt;&amp;nbsp;- Hardware Platform&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Wireless Command and H-Bridge Direct Drive - Part 3" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57190/smartassist-ev---wireless-command-and-h-bridge-direct-drive---part-3" data-e14adj="t"&gt;Part 3&lt;/a&gt;&lt;a title="Wireless Command and H-Bridge Direct Drive - Part 3" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57190/smartassist-ev---wireless-command-and-h-bridge-direct-drive---part-3" data-e14adj="t"&gt;&amp;nbsp;- Wireless Command and H-Bridge Direct Drive&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Part 4 - Introducing Autonomous Line Following (TCRT5000)" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57191/smartassist-ev---introducing-autonomous-line-following-tcrt5000---part-4" data-e14adj="t"&gt;Part 4 - Introducing Autonomous Line Following (TCRT5000)&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Part 5 - Non-Contact Proactive Shielding (HC-SR04 Range Finder)" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57194/smartassist-ev---non-contact-proactive-shielding-hc-sr04-range-finder---part-5" data-e14adj="t"&gt;Part 5 - Non-Contact Proactive Shielding (HC-SR04 Range Finder)&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Part 6 - Strict Priority Hierarchy with Tactile Mechanical Bumpers" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57196/smartassist-ev---strict-priority-hierarchy-with-tactile-mechanical-bumpers---part-6" data-e14adj="t"&gt;Part 6 - Strict Priority Hierarchy with Tactile Mechanical Bumpers&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Part 7 - Mobile Robot Control and Live Video Streaming" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57198/smartassist-ev---mobile-robot-control-and-live-video-streaming---part-7" data-e14adj="t"&gt;Part 7 - Mobile Robot Control and Live Video Streaming&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-hveid="CAAIDxAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;An autonomous tracking system can easily collide with static roadblocks. To mitigate this risk, we add an Ultrasonic Distance Module (HC-SR04). This sensor proactively measures the distance to upcoming obstacles by calculating the time-of-flight of high-frequency audio pings.To prevent software lag, we supply a 30,000 microsecond hardware limit to the pulseIn() function. This prevents the script from freezing if the sensor misses an echo pulse. If the calculated clearance falls beneath 30 cm, the robot suspends line tracking and maneuvers away.&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-hveid="CAAIDxAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;Here, the vehicle gains a contact-free defensive boundary. While navigating autonomously, if the ultrasonic sonar module captures a barrier closing within 30 cm, the robot overrides line following to execute a sharp evasive turn away from danger.&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;span class="HwtZe" lang="en"&gt;&lt;span class="jCAhz ChMk0b"&gt;&lt;span class="ryNqvb"&gt;The sensor module is mounted on the contact board&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;:&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;img alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/5_2D00_1.jpg" /&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/5_2D00_2.jpg" /&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;span class="HwtZe" lang="en"&gt;&lt;span class="jCAhz ChMk0b"&gt;&lt;span class="ryNqvb"&gt;The complete vehicle intended for testing looks as follows:&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/5_2D00_3.jpg" /&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;h3 id="mcetoc_1k0p4e8np1" data-path-to-node="7"&gt;Essential Code Snippets Related to the Ultrasonic Sensor&lt;/h3&gt;
&lt;h4 data-path-to-node="8"&gt;1. Hardware Pin Definitions&lt;/h4&gt;
&lt;p data-path-to-node="9"&gt;Pin assignments for the HC-SR04 sonar module:&lt;/p&gt;
&lt;div class="code-block ng-tns-c998324306-234 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" style="display:block;" data-hveid="0" data-ved="0CAAQhtANahgKEwiD6aTnt7iWAxUAAAAAHQAAAAAQvAQ"&gt;
&lt;div class="formatted-code-block-internal-container ng-tns-c998324306-234"&gt;
&lt;div class="animated-opacity ng-tns-c998324306-234"&gt;
&lt;pre class="ng-tns-c998324306-234"&gt;&lt;code class="code-container formatted ng-tns-c998324306-234" data-test-id="code-content"&gt;&lt;span class="hljs-comment"&gt;&lt;/span&gt;&lt;pre class="ui-code" data-mode="text"&gt;// Ultrasonic HC-SR04 Sonar Pins
const int TRIG_PIN = 4;      
const int ECHO_PIN = 3;  &lt;/pre&gt;&amp;nbsp;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-path-to-node="11"&gt;2. Pin Setup in &lt;code data-path-to-node="11" data-index-in-node="16"&gt;setup()&lt;/code&gt;&lt;/h4&gt;
&lt;p data-path-to-node="12"&gt;Configures &lt;code data-path-to-node="12" data-index-in-node="11"&gt;TRIG_PIN&lt;/code&gt; to output pulses and &lt;code data-path-to-node="12" data-index-in-node="41"&gt;ECHO_PIN&lt;/code&gt; to listen for returning sound waves:&lt;/p&gt;
&lt;div class="code-block ng-tns-c998324306-235 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" style="display:block;" data-hveid="0" data-ved="0CAAQhtANahgKEwiD6aTnt7iWAxUAAAAAHQAAAAAQvQQ"&gt;
&lt;div class="formatted-code-block-internal-container ng-tns-c998324306-235"&gt;
&lt;div class="animated-opacity ng-tns-c998324306-235"&gt;
&lt;pre class="ng-tns-c998324306-235"&gt;&lt;code class="code-container formatted ng-tns-c998324306-235" data-test-id="code-content"&gt;&lt;span class="hljs-comment"&gt;&lt;/span&gt;&lt;pre class="ui-code" data-mode="text"&gt;// Ultrasonic Sonar pins configuration
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);&lt;/pre&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-path-to-node="14"&gt;3. Distance Measurement Routine (&lt;code data-path-to-node="14" data-index-in-node="33"&gt;getDistance()&lt;/code&gt;)&lt;/h4&gt;
&lt;p data-path-to-node="15"&gt;Generates a 10us&amp;nbsp;ultrasonic burst and measures echo response time to calculate distance in centimeters. Includes a&amp;nbsp;&lt;span class="math-inline" data-index-in-node="126"&gt;30 ms&lt;/span&gt; hardware timeout to prevent code execution freezes:&lt;/p&gt;
&lt;div class="code-block ng-tns-c998324306-236 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" style="display:block;" data-hveid="0" data-ved="0CAAQhtANahgKEwiD6aTnt7iWAxUAAAAAHQAAAAAQvgQ"&gt;
&lt;div class="formatted-code-block-internal-container ng-tns-c998324306-236"&gt;
&lt;div class="animated-opacity ng-tns-c998324306-236"&gt;
&lt;pre class="ng-tns-c998324306-236"&gt;&lt;code class="code-container formatted ng-tns-c998324306-236" data-test-id="code-content"&gt;&lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;&lt;/span&gt;&lt;/span&gt;&lt;pre class="ui-code" data-mode="text"&gt;float getDistance() {
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);
  
  // 30ms hardware timeout constraint to avoid freezing the main execution loop
  long duration = pulseIn(ECHO_PIN, HIGH, 30000); 
  float d = duration * 0.0343 / 2;
  
  if (d == 0) return 999.0; // Return out-of-bounds constant on reading error
  return d;
}&lt;/pre&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-path-to-node="17"&gt;4. Obstacle Avoidance Logic in &lt;code data-path-to-node="17" data-index-in-node="31"&gt;loop()&lt;/code&gt;&lt;/h4&gt;
&lt;p data-path-to-node="18"&gt;When operating in &lt;code data-path-to-node="18" data-index-in-node="18"&gt;MODE_AUTONOMOUS&lt;/code&gt;, distance is checked before line tracking logic. If an obstacle is detected closer than &lt;span class="math-inline" data-index-in-node="122"&gt;30 cm&lt;/span&gt;, line tracking is temporarily overridden, and the robot performs an evasive left turn:&lt;/p&gt;
&lt;div class="code-block ng-tns-c998324306-237 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" style="display:block;" data-hveid="0" data-ved="0CAAQhtANahgKEwiD6aTnt7iWAxUAAAAAHQAAAAAQvwQ"&gt;
&lt;div class="formatted-code-block-internal-container ng-tns-c998324306-237"&gt;
&lt;div class="animated-opacity ng-tns-c998324306-237"&gt;
&lt;pre class="ng-tns-c998324306-237"&gt;&lt;code class="code-container formatted ng-tns-c998324306-237" data-test-id="code-content"&gt;&lt;span class="hljs-keyword"&gt;&lt;/span&gt;&lt;pre class="ui-code" data-mode="text"&gt;else if (currentMode == MODE_AUTONOMOUS) {
  float distance = getDistance();
  
  // Obstacle avoidance matching Elecrow&amp;#39;s motor logic and 0.3s timing
  if (distance &amp;lt; 30.0) {
    Serial.println(&amp;quot;Obstacle detected! Turning left...&amp;quot;);
    driveMotors(MOTOR_SPEED, 0, 0, MOTOR_SPEED); // Spin Left in place
    delay(300);                                  // Turn duration
  } 
  else {
    // Standard Line Tracking routine executes here...
  }
}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class="ng-tns-c998324306-237"&gt;&lt;code class="code-container formatted ng-tns-c998324306-237" data-test-id="code-content"&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class="ng-tns-c998324306-237"&gt;&lt;/pre&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;span class="HwtZe" style="font-size:150%;" lang="en"&gt;&lt;span class="jCAhz ChMk0b"&gt;&lt;span class="ryNqvb"&gt;Full Code&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;span class="HwtZe" lang="en"&gt;&lt;span class="jCAhz ChMk0b"&gt;&lt;span class="ryNqvb"&gt;The full code for this stage is as follows&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;:&lt;/div&gt;
&lt;pre class="ng-tns-c998324306-237"&gt;&lt;code class="code-container formatted ng-tns-c998324306-237" data-test-id="code-content"&gt;&lt;pre class="ui-code" data-mode="text"&gt;#include &amp;lt;Arduino.h&amp;gt;
#include &amp;lt;Arduino_LED_Matrix.h&amp;gt; // Library for the 8x13 LED matrix

Arduino_LED_Matrix matrix; // Initialize LED matrix object

// L9110S Motor Driver Pins
const int MOTOR_PIN_A1 = 5; 
const int MOTOR_PIN_A2 = 6; 
const int MOTOR_PIN_B1 = 9; 
const int MOTOR_PIN_B2 = 10;

// Infrared Receiver Pin
const int IR_RECEIVE_PIN = 2; 

// Line Tracking Sensor Pins
const int TRACKING_PIN_L = A2;
const int TRACKING_PIN_R = A3;

// Ultrasonic HC-SR04 Sonar Pins
const int TRIG_PIN = 4;      
const int ECHO_PIN = 3;      

enum RobotMode { 
  MODE_MANUAL, 
  MODE_AUTONOMOUS 
};

RobotMode currentMode = MODE_MANUAL; 

// Speed settings
const int MOTOR_SPEED = 200; 
const int TRACK_SPEED = 160; 

byte lastCommand = 0;

// LED Matrix Frame Buffer Size (104 pixels)
const uint8_t FRAME_SIZE = 8 * 13;

// --- LED MATRIX ARROW &amp;amp; ICON ARRAYS (Brightness levels 0-7) ---
uint8_t arrow_up[FRAME_SIZE] = {
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 7, 7, 7, 7, 7, 0, 0, 0, 0,
    0, 0, 0, 7, 7, 0, 7, 0, 7, 7, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0
};

uint8_t arrow_down[FRAME_SIZE] = {
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 7, 7, 0, 7, 0, 7, 7, 0, 0, 0,
    0, 0, 0, 0, 7, 7, 7, 7, 7, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0
};

uint8_t arrow_left[FRAME_SIZE] = {
    0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    0, 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0
};

uint8_t arrow_right[FRAME_SIZE] = {
    0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 0,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0
};

uint8_t stop_icon[FRAME_SIZE] = { 0 }; 

// --- ONBOARD RGB LED FUNCTIONS ---
void set_led3_color(int r, int g, int b) {
  analogWrite(LED3_R, r);
  analogWrite(LED3_G, g);
  analogWrite(LED3_B, b);
}

void set_led4_color(bool r, bool g, bool b) {
  digitalWrite(LED4_R, r ? LOW : HIGH);
  digitalWrite(LED4_G, g ? LOW : HIGH);
  digitalWrite(LED4_B, b ? LOW : HIGH);
}

// Elecrow IR Decoder
long readElecrowIR() {
  int count = 0;
  while (digitalRead(IR_RECEIVE_PIN) == LOW &amp;amp;&amp;amp; count &amp;lt; 200) { count++; delayMicroseconds(60); }
  if (count &amp;gt;= 200) return -1;

  count = 0;
  while (digitalRead(IR_RECEIVE_PIN) == HIGH &amp;amp;&amp;amp; count &amp;lt; 80) { count++; delayMicroseconds(60); }
  if (count &amp;gt;= 80) return -1;

  int idx = 0, cnt = 0;
  byte data[4] = {0, 0, 0, 0};

  for (int i = 0; i &amp;lt; 32; i++) {
    count = 0;
    while (digitalRead(IR_RECEIVE_PIN) == LOW &amp;amp;&amp;amp; count &amp;lt; 15) { count++; delayMicroseconds(60); }
    count = 0;
    while (digitalRead(IR_RECEIVE_PIN) == HIGH &amp;amp;&amp;amp; count &amp;lt; 40) { count++; delayMicroseconds(60); }

    if (count &amp;gt; 8) data[idx] |= (1 &amp;lt;&amp;lt; cnt);

    if (cnt == 7) { cnt = 0; idx++; } else { cnt++; }
  }

  if ((byte)(data[0] + data[1]) == 0xFF &amp;amp;&amp;amp; (byte)(data[2] + data[3]) == 0xFF) {
    return data[2];
  }
  return -1;
}

void driveMotors(int a1, int a2, int b1, int b2) {
  analogWrite(MOTOR_PIN_A1, a1);
  analogWrite(MOTOR_PIN_A2, a2);
  analogWrite(MOTOR_PIN_B1, b1);
  analogWrite(MOTOR_PIN_B2, b2);
}

float getDistance() {
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);
  
  long duration = pulseIn(ECHO_PIN, HIGH, 30000); 
  float d = duration * 0.0343 / 2;
  
  if (d == 0) return 999.0;
  return d;
}

void executeCommand(byte command) {
  switch (command) {
    case 0x1C: // OK Button - Toggle Manual / Autonomous Operations
      if (currentMode == MODE_MANUAL) { 
        currentMode = MODE_AUTONOMOUS; 
        Serial.println(&amp;quot;Mode changed: AUTONOMOUS (Tracking + Sonar)&amp;quot;);
      } 
      else { 
        currentMode = MODE_MANUAL; 
        driveMotors(0, 0, 0, 0); 
        set_led4_color(false, false, false);
        set_led3_color(0, 0, 0);
        matrix.draw(stop_icon);
        Serial.println(&amp;quot;Mode changed: MANUAL (IR Control)&amp;quot;);
      }
      lastCommand = 0; 
      delay(500);      
      break;

    case 0x18: // Forward
      if (currentMode == MODE_MANUAL) {
        driveMotors(0, MOTOR_SPEED, 0, MOTOR_SPEED);
        set_led4_color(false, true, false); // Green
        set_led3_color(0, 200, 0);
        matrix.draw(arrow_up);
      }
      break;

    case 0x08: // Spin Left
      if (currentMode == MODE_MANUAL) {
        driveMotors(MOTOR_SPEED, 0, 0, MOTOR_SPEED);
        set_led4_color(false, false, true); // Blue
        set_led3_color(0, 0, 200);
        matrix.draw(arrow_left);
      }
      break;

    case 0x5A: // Spin Right
      if (currentMode == MODE_MANUAL) {
        driveMotors(0, MOTOR_SPEED, MOTOR_SPEED, 0);
        set_led4_color(false, false, true); // Blue
        set_led3_color(0, 0, 200);
        matrix.draw(arrow_right);
      }
      break;

    case 0x52: // Backward
      if (currentMode == MODE_MANUAL) {
        driveMotors(MOTOR_SPEED, 0, MOTOR_SPEED, 0);
        set_led4_color(true, false, false); // Red
        set_led3_color(200, 0, 0);
        matrix.draw(arrow_down);
      }
      break;

    default:   
      if (currentMode == MODE_MANUAL) {
        driveMotors(0, 0, 0, 0);
        set_led4_color(false, false, false);
        set_led3_color(0, 0, 0);
        matrix.draw(stop_icon);
      }
      break;
  }
}

void setup() {
  Serial.begin(115200); 

  pinMode(IR_RECEIVE_PIN, INPUT_PULLUP); 
  pinMode(TRACKING_PIN_L, INPUT_PULLUP);
  pinMode(TRACKING_PIN_R, INPUT_PULLUP);

  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);

  // RGB LED Pin Configuration
  pinMode(LED4_R, OUTPUT); 
  pinMode(LED4_G, OUTPUT); 
  pinMode(LED4_B, OUTPUT);
  set_led3_color(0, 0, 0);
  set_led4_color(false, false, false);

  // Initialize LED Matrix
  matrix.begin();
  matrix.setGrayscaleBits(3); // 8 brightness levels (0-7)
  matrix.clear();

  Serial.println(&amp;quot;System Ready on Arduino UNO Q (IR + Line Tracking + Sonar + Matrix/LEDs).&amp;quot;);
}

void loop() {
  // 1. Process incoming IR signals
  if (digitalRead(IR_RECEIVE_PIN) == LOW) {
    long result = readElecrowIR();
    if (result != -1) {
      lastCommand = (byte)result;
      executeCommand(lastCommand);
    }
  }

  // 2. Continuous Mode Execution
  if (currentMode == MODE_MANUAL) {
    if (digitalRead(IR_RECEIVE_PIN) == HIGH) {
      driveMotors(0, 0, 0, 0); 
      set_led4_color(false, false, false);
      set_led3_color(0, 0, 0);
      matrix.draw(stop_icon);
    } else {
      executeCommand(lastCommand);
    }
  } 
  else if (currentMode == MODE_AUTONOMOUS) {
    float distance = getDistance();
    
    // Priority 1: Ultrasonic Obstacle Avoidance
    if (distance &amp;lt; 30.0) {
      Serial.println(&amp;quot;Obstacle detected! Evading left...&amp;quot;);
      driveMotors(MOTOR_SPEED, 0, 0, MOTOR_SPEED); // Spin Left in place
      set_led4_color(true, false, false);          // Red warning
      set_led3_color(200, 0, 0);
      matrix.draw(arrow_left);                     // Indicate left evasion turn
      delay(300);                                  // Turn duration
    } 
    // Priority 2: Line Tracking Routine
    else {
      int trackL = digitalRead(TRACKING_PIN_L);
      int trackR = digitalRead(TRACKING_PIN_R);
      int trackState = (trackL * 2) + trackR; 
      
      switch (trackState) {
        case 0: // Off track -&amp;gt; Stop
          driveMotors(0, 0, 0, 0); 
          set_led4_color(false, false, false);
          set_led3_color(0, 0, 0);
          matrix.draw(stop_icon);
          break;

        case 1: // Right sensor active -&amp;gt; Turn Right
          driveMotors(0, TRACK_SPEED, TRACK_SPEED, 0); 
          set_led4_color(false, false, true); // Blue
          set_led3_color(0, 0, 200);
          matrix.draw(arrow_right);
          break;

        case 2: // Left sensor active -&amp;gt; Turn Left
          driveMotors(TRACK_SPEED, 0, 0, TRACK_SPEED); 
          set_led4_color(false, false, true); // Blue
          set_led3_color(0, 0, 200);
          matrix.draw(arrow_left);
          break;

        case 3: // Both sensors active -&amp;gt; Move Forward
          driveMotors(0, TRACK_SPEED, 0, TRACK_SPEED); 
          set_led4_color(false, true, false); // Green
          set_led3_color(0, 200, 0);
          matrix.draw(arrow_up);
          break;
      }
      delay(10); 
    }
  }
}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-wiz-uids="pKKIde_3f,pKKIde_3e" data-hveid="CAAIEBAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 4px 0px 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;div data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;div data-animation-atomic="" data-sae="" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 1px solid rgb(240, 242, 245);"&gt;
&lt;div dir="ltr" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;pre data-copy-service-computed-style="font-family: monospace; font-size: 14px; font-weight: 400; margin: 14px 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;</description></item><item><title>Vape Cell EV - part V - 18650 vs Vape Cell</title><link>https://community.element14.com/thread/57193?ContentTypeID=0</link><pubDate>Sun, 16 Aug 2026 23:01:03 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:83873faf-0873-4535-a3cb-bdbac432a831</guid><dc:creator>saramic</dc:creator><slash:comments>1</slash:comments><comments>https://community.element14.com/thread/57193?ContentTypeID=0</comments><wfw:commentRss>https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57193/vape-cell-ev---part-v---18650-vs-vape-cell/rss?ContentTypeId=0</wfw:commentRss><description>&lt;p&gt;&lt;em&gt;shamelessly taking some extra time to complete the project as per the discussion in &lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57178/challengers-post-your-projects" data-e14adj="t"&gt;Challengers, Post your Projects!&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h1 id="recap"&gt;Recap&lt;/h1&gt;
&lt;p&gt;Can random Vape Cells with unknonw histories be bundled into a smart battery system to ultimately power an EV, power wall or other device?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57077/vape-cell-ev---part-i---what-s-in-a-vape" data-e14adj="t"&gt;Vape Cell EV - part I - What&amp;rsquo;s in a Vape&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57148/vape-cell-ev---part-ii---rs485-comms" data-e14adj="t"&gt;Vape Cell EV - part II - RS485 comms&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57186/vape-cell-ev---part-iii---ina219-power-monitor" data-e14adj="t"&gt;Vape Cell EV - part III - INA219 power monitor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57192/vape-cell-ev---part-iv---on-battery-charging" data-e14adj="t"&gt;Vape Cell EV - part IV - INA219 on battery charging&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="18650-vs-vape-cell"&gt;18650 vs Vape Cell&lt;/h2&gt;
&lt;p&gt;I setup a charging and discharging circuit controlled by the&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;strong&gt;UNO Q&lt;/strong&gt;, tracking using the&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57186/vape-cell-ev---part-iii---ina219-power-monitor" data-e14adj="t"&gt;INA219 power monitor&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;and added a few relays to control cycling between charging (up to 4.2V and currnet draw down to 150mA) and discharging (down to 3.0V). The setup was far from pretty:&lt;/p&gt;
&lt;p&gt;&lt;img alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/2026_2D00_08_2D00_19_5F00_charge_5F00_discharge_5F00_setup.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;I hooked up a cheap AliExpress 18650 &amp;mdash; label reads&amp;nbsp;&lt;/span&gt;&lt;strong&gt;2600 mAh 3.7 V 9.62 Wh&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/20260820_5F00_AliExpress_5F00_18560.jpg" /&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;and got the following results:&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/2026_2D00_08_2D00_19_5F00_charge_5F00_discharge_5F00_dashboard.png" /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Running a full charge then discharge at ~350 mA (10 &amp;Omega; resistor):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;AliExpress &amp;ldquo;18650&amp;rdquo;&lt;/th&gt;
&lt;th&gt;Genuine 18650 (ref)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claimed capacity&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2,600 mAh&lt;/strong&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(9.62 Wh)&lt;/td&gt;
&lt;td&gt;2,000&amp;ndash;3,600 mAh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Measured capacity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;617 mAh&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2,000&amp;ndash;3,600 mAh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claimed vs measured&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;24% of label&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discharge time to 3.0 V&lt;/td&gt;
&lt;td&gt;107 min&lt;/td&gt;
&lt;td&gt;~430 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg discharge current&lt;/td&gt;
&lt;td&gt;~347 mA&lt;/td&gt;
&lt;td&gt;&amp;mdash;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Voltage sag (full cycle)&lt;/td&gt;
&lt;td&gt;0.85 V over 617 mAh&lt;/td&gt;
&lt;td&gt;~0.3 V&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IR drop at switch-on&lt;/td&gt;
&lt;td&gt;284 mV &amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;strong&gt;~0.81 &amp;Omega;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&amp;lt; 100 m&amp;Omega;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flat discharge plateau&lt;/td&gt;
&lt;td&gt;none &amp;mdash; linear drop&lt;/td&gt;
&lt;td&gt;3.5&amp;ndash;3.7 V&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CC charge current&lt;/td&gt;
&lt;td&gt;460 mA&lt;/td&gt;
&lt;td&gt;&amp;mdash;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Charge time (CC + CV to 200 mA)&lt;/td&gt;
&lt;td&gt;~154 min&lt;/td&gt;
&lt;td&gt;&amp;mdash;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Charge accepted&lt;/td&gt;
&lt;td&gt;~604 mAh&lt;/td&gt;
&lt;td&gt;&amp;mdash;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;No flat discharge plateau, ~8&amp;times; the internal resistance of a real cell, and only&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;strong&gt;24% of the claimed capacity&lt;/strong&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;mdash; consistent with a re-wrapped 18350 cell (or a badly degraded pouch cell) inside an 18650 shell. The label&amp;rsquo;s 9.62 Wh figure is exactly 2,600 mAh &amp;times; 3.7 V, confirming the capacity claim; neither figure is anywhere near what the cell actually delivers.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;span&gt;I hooked up a Vape Cell &amp;mdash; label reads&amp;nbsp;&lt;/span&gt;&lt;strong&gt;18450 S50 3.7 V 5.18 Wh&lt;/strong&gt;&lt;span&gt;&amp;nbsp;(~1,400 mAh claimed)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/20260820_5F00_Vape_5F00_18450.jpg" /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;and got the following results:&lt;/span&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Vape Cell (18450 S50)&lt;/th&gt;
&lt;th&gt;AliExpress &amp;ldquo;18650&amp;rdquo;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claimed capacity&lt;/td&gt;
&lt;td&gt;~1,400 mAh (5.18 Wh)&lt;/td&gt;
&lt;td&gt;2,600 mAh (9.62 Wh)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Measured capacity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~480 mAh&lt;/strong&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(projected&amp;sup1;)&lt;/td&gt;
&lt;td&gt;617 mAh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claimed vs measured&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~35% of label&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;24% of label&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discharge time to 3.0 V&lt;/td&gt;
&lt;td&gt;~92 min (est.)&lt;/td&gt;
&lt;td&gt;107 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg discharge current&lt;/td&gt;
&lt;td&gt;~320 mA&lt;/td&gt;
&lt;td&gt;~347 mA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Starting voltage under load&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3.35 V&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3.85 V&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Voltage sag (full range)&lt;/td&gt;
&lt;td&gt;0.35 V over ~480 mAh&lt;/td&gt;
&lt;td&gt;0.85 V over 617 mAh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IR drop at switch-on&lt;/td&gt;
&lt;td&gt;725 mV &amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;strong&gt;~2.2 &amp;Omega;&lt;/strong&gt;&amp;sup2;&lt;/td&gt;
&lt;td&gt;284 mV &amp;rarr; ~0.81 &amp;Omega;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flat discharge plateau&lt;/td&gt;
&lt;td&gt;none &amp;mdash; linear drop&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CC charge current&lt;/td&gt;
&lt;td&gt;~460 mA&lt;/td&gt;
&lt;td&gt;~460 mA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Charge time (CV to 200 mA)&lt;/td&gt;
&lt;td&gt;~71 min (CV only)&lt;/td&gt;
&lt;td&gt;~154 min (CC + CV)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CV taper rate&lt;/td&gt;
&lt;td&gt;k &amp;asymp; 0.011 /min&lt;/td&gt;
&lt;td&gt;k &amp;asymp; 0.012 /min&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;sup1; Projected from 7 min of stable discharge data at &amp;minus;3.5 mV/min; actual result expected ~14:00&amp;ndash;16:00 UTC. &amp;sup2; Instantaneous IR at load switch-on; CV slope method gives ~0.21 &amp;Omega; &amp;mdash; the large discrepancy reflects polarisation overpotential built up during the 71-min CV charge phase.&lt;/p&gt;
&lt;p&gt;The 18450 is a real cell format (18 mm &amp;times; 45 mm vs 65 mm for 18650) &amp;mdash; a genuine 18450 typically delivers 650&amp;ndash;1,100 mAh. At 1,400 mAh claimed, the label is optimistic. At ~480 mAh measured it sits at 35% of that claim &amp;mdash; a pattern eerily similar to the 18650&amp;rsquo;s 24%.&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" alt="image" style="margin:auto;max-width:960px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/2026_2D00_08_2D00_20_5F00_cell_5F00_characteristics.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" alt="image" style="margin:auto;max-width:960px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/2026_2D00_08_2D00_20_5F00_cell_5F00_charge_5F00_profile.png" /&gt;&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Both cells tell the same story: wildly inflated labels, real-world capacity a fraction of what&amp;rsquo;s printed.&lt;/p&gt;
&lt;p&gt;The 18650 came in at 24% of its 2,600 mAh claim. The vape cell looks set to land at ~35% of its 1,400 mAh claim. Neither cell has a flat discharge plateau &amp;mdash; a hallmark of healthy lithium chemistry. Both start discharging well below where a genuine cell would: the fake 18650 at 3.85 V under load, the vape cell at only 3.35 V (despite being fully charged to 4.13 V moments earlier).&lt;/p&gt;
&lt;p&gt;One genuinely interesting finding: both cells have near-identical CV taper kinetics (k &amp;asymp; 0.012 /min). Different form factors, different histories, same electrochemical decay rate &amp;mdash; the underlying chemistry is consistent even if the capacity isn&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;For an EV or power wall application, neither cell is usable as-is. The fake 18650 delivers about 2.2 Wh; the vape cell about 1.6 Wh. A genuine 18650 would deliver 7&amp;ndash;13 Wh. You&amp;rsquo;d need 4&amp;ndash;8 of these salvaged cells to match one good one &amp;mdash; and that&amp;rsquo;s before accounting for the matching problem, the safety risk of unknown-history cells, and the cycle life you&amp;rsquo;d be starting with.&lt;/p&gt;
&lt;p&gt;The house didn&amp;rsquo;t burn down&amp;nbsp;&lt;span class="emoticon" data-url="https://community.element14.com/cfs-file/__key/system/emoji/1f3e1.svg" title="House with garden"&gt;&amp;#x1f3e1;&lt;/span&gt;&lt;span class="emoticon" data-url="https://community.element14.com/cfs-file/__key/system/emoji/274c.svg" title="X"&gt;&amp;#x274c;&lt;/span&gt;&lt;span class="emoticon" data-url="https://community.element14.com/cfs-file/__key/system/emoji/1f525.svg" title="Fire"&gt;&amp;#x1f525;&lt;/span&gt;, winning!!&lt;/p&gt;
&lt;h2 id="next"&gt;Next&lt;/h2&gt;
&lt;p&gt;I now have a bunch of vape cells that I need to prevent their terminals shorting, no doubt I will have to continue in some fashion to make this experiment come to a safe ending. Although I didn&amp;rsquo;t achieve all I wanted, I certainly made a solid dint in my knowledge of battery charging and some positive looking results.&lt;/p&gt;
&lt;h2 id="source"&gt;Source&lt;/h2&gt;
&lt;p&gt;&lt;a id="" href="https://github.com/saramic/vape-cell-EV" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;https://github.com/saramic/vape-cell-EV&lt;/a&gt;&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;</description></item><item><title>Vape Cell EV - part IV - On battery charging</title><link>https://community.element14.com/thread/57192?ContentTypeID=0</link><pubDate>Sun, 16 Aug 2026 23:00:30 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:f1bc04f6-a735-4b9c-b80b-b7cadb600344</guid><dc:creator>saramic</dc:creator><slash:comments>3</slash:comments><comments>https://community.element14.com/thread/57192?ContentTypeID=0</comments><wfw:commentRss>https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57192/vape-cell-ev---part-iv---on-battery-charging/rss?ContentTypeId=0</wfw:commentRss><description>&lt;h1 id="recap"&gt;Recap&lt;/h1&gt;
&lt;p&gt;Can random Vape Cells with unknonw histories be bundled into a smart battery system to ultimately power an EV, power wall or other device?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57077/vape-cell-ev---part-i---what-s-in-a-vape" data-e14adj="t"&gt;Vape Cell EV - part I - What&amp;rsquo;s in a Vape&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57148/vape-cell-ev---part-ii---rs485-comms" data-e14adj="t"&gt;Vape Cell EV - part II - RS485 comms&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57186/vape-cell-ev---part-iii---ina219-power-monitor" data-e14adj="t"&gt;Vape Cell EV - part III - INA219 power monitor&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="challanges"&gt;Challenges&lt;/h2&gt;
&lt;p&gt;Although I have been bit more busy recently with trips away, even though it is winter here in Australia , the biggest challenge is not fully knowing everything about battery charging and trying to take small steps to identify what is safe to do. Which has lead to stalling some of the things I wanted to do in this project.&lt;/p&gt;
&lt;h2 id="what-i-learnt"&gt;What I learnt&lt;/h2&gt;
&lt;p&gt;It seems that for most&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;strong&gt;Li-ion&lt;/strong&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;cells like the&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;strong&gt;18650&lt;/strong&gt;, the key to charging them is CC (Constant Current) and CV (Constant Voltage). The cell has a nominal voltage of&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;3.6V ~ 3.7V&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;and usually a maximum charge voltage of&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;4.20V&lt;/code&gt;. Cells can be chargec at between 0.2C - 1.0C where C is Capacity. For a cell that claims 6000mAh this would mean 0.2C 1.2A, 0.5C 3A or 1.0C 6A. These are indicative values and READ THE DOCUMENTATION FOR YOUR CELL. Not to say the least that I was not about to push 6A through my cell on a desk &lt;span style="font-size:30px;"&gt;&lt;span class="emoticon" data-url="https://community.element14.com/cfs-file/__key/system/emoji/1f937.svg" title="Shrug"&gt;&amp;#x1f937;&lt;/span&gt;&lt;/span&gt;. Even less so for an old vape cell that was not labled.&lt;/p&gt;
&lt;p&gt;Also cells can become damaged if they go below a minimum voltage, usually around 3V.&lt;/p&gt;
&lt;p&gt;A battery management system, BMS, allows multiple cells to be charged. A balance connector allows for each individual cell to be connected and managed for the above thresholds:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;over charge protection&lt;/li&gt;
&lt;li&gt;over discharge protection&lt;/li&gt;
&lt;li&gt;over current protection&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;From what I can see in builds like Chris Doel&amp;rsquo;s &amp;ldquo;I Powered My House Using 500 Disposable vapes&amp;rdquo;, although he adds a per battery fuse for short protection, there is actually no BMS, no ability to check the levels of any individual cell and from my understanding, he has manually charged the cells and put them into a battery configuration which he will never be able to re-charge safely - a one off YouTube video stunt?&lt;/p&gt;
&lt;div style="margin:auto;max-width:240px;"&gt;&lt;a href="https://youtu.be/dy-wFixuRVU" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt; &lt;img alt="I Powered My House Using 500 Disposable vapes - Chris Doel" src="http://i.ytimg.com/vi/dy-wFixuRVU/hqdefault.jpg" /&gt; &lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Interestingly in the vape cells, the over current protection is built into the vape cell wire - if you short the battery, the wire will immediately burn through and cut off any more current flow.&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/vape_5F00_cell_5F00_wire_5F00_fuse.gif" /&gt;&lt;/p&gt;
&lt;p&gt;In above video you can see when I short out the cell (DO NOT TRY THIS AT HOME) the wire from the vape cell is so thin, it burns out and acts as a fuse to immediately stop current flow.&lt;/p&gt;
&lt;p&gt;One of the online BMS circuits&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://github.com/stuartpittaway/diyBMS" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;https://github.com/stuartpittaway/diyBMS&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;allows you to monitor each cell via a web interface powered by&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;ESP8266&lt;/code&gt;. Interestingly over charge protection seems not to turn off the charging of the battery but redirecting the extra current into a big 3Ω3 10W resistor, which seems OK for balancing batteries that have been overcharged but seems like overkill to have that per battery, a resistor the size of a battery just to charge it? an informative overview can be seen in Great Scott&amp;rsquo;s DIY or Buy segment&lt;/p&gt;
&lt;div style="margin:auto;max-width:240px;"&gt;&lt;a href="https://youtu.be/rT-1gvkFj60" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt; &lt;img loading="lazy" alt="image"  src="http://i.ytimg.com/vi/rT-1gvkFj60/hqdefault.jpg" /&gt; &lt;/a&gt;&lt;/div&gt;
&lt;p&gt;&lt;span&gt;Electronoobs also has a hand built BMS. This circuit uses a zener diode to redirect extra current into a load of 4 diodes, again I am not fully sure of this but it does feel wasteful. More can be seen in the video below&lt;/span&gt;&lt;/p&gt;
&lt;div style="margin:auto;max-width:240px;"&gt;&lt;a href="https://youtu.be/qRVEJjk5B_g" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt; &lt;img loading="lazy" alt="Homemade BMS - Balanced LiPo Charger Multiple Cells and Current Limit - Electronoobs" src="http://i.ytimg.com/vi/qRVEJjk5B_g/hqdefault.jpg" /&gt; &lt;/a&gt;&lt;/div&gt;
&lt;p&gt;&lt;span&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/20260819_5F00_bms_5F00_diode_5F00_load.png" /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;Finally Haase Industris seems to point out some of the limitations of the above circuits and proposes using a&amp;nbsp;&lt;a id="" href="https://www.ti.com/product/BQ77915" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;BQ77915&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div style="margin:auto;max-width:240px;"&gt;&lt;a href="https://youtu.be/UUr-CJudg38" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt; &lt;img loading="lazy" alt="Most BMSs (Battery Management Systems) don't cut it... I Built a BETTER One. - Haase Industries" src="http://i.ytimg.com/vi/UUr-CJudg38/hqdefault.jpg" /&gt; &lt;/a&gt;&lt;/div&gt;
&lt;p&gt;&lt;span&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/20260819_5F00_bms_5F00_chip_5F00_BQ77915.png" /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;In between this I saw a video showing how overcharging can explode a cell&lt;/p&gt;
&lt;p&gt;And Adam Savage&amp;rsquo;s overview of 18650 lithium-Ion batteries with a review from&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://www.lumafield.com/battery-report" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;Lumafield&amp;rsquo;s Battery quality report&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;which seems to sugges that anything other than a branded cell is a ticking time bomb. Their X-ray like technology showing build flaws in batteries&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/pastedimage1787104139596v1.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;See the full video here:&lt;/span&gt;&lt;/p&gt;
&lt;div style="margin:auto;max-width:240px;"&gt;&lt;a href="https://youtu.be/-Y23nfAOiXQ" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt; &lt;img loading="lazy" alt="The Surprising Flaws in 18650 Lithium-Ion Batteries - Adam Savage&amp;rsquo;s Tested" src="http://i.ytimg.com/vi/-Y23nfAOiXQ/hqdefault.jpg" /&gt; &lt;/a&gt;&lt;/div&gt;
&lt;p&gt;That said, a recent YouTube short from Great Scott suggests it is actually quite hard to blow one of these batteries up&lt;/p&gt;
&lt;div style="margin:auto;max-width:240px;"&gt;&lt;a href="https://youtu.be/DQrXqrMx2z8" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt; &lt;img loading="lazy" alt="This Unprotected Battery Surprised Me! - Great Scott" src="http://i.ytimg.com/vi/DQrXqrMx2z8/hqdefault.jpg" /&gt; &lt;/a&gt;&lt;/div&gt;
&lt;p&gt;as you can see, a lot of information and heat&lt;span class="emoticon" data-url="https://community.element14.com/cfs-file/__key/system/emoji/1f525.svg" title="Fire"&gt;&amp;#x1f525;&lt;/span&gt;&amp;nbsp;generated that took me a little bit out of my comfort zone.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/20260819_5F00_battery_5F00_explosion.png" /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;care of&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://youtu.be/n3urBpFIBgY" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;How to keep LiPos from burning down your house (safe lipo charging) - Joshua Bardwell&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="up-next"&gt;Up Next&lt;/h2&gt;
&lt;p&gt;Using the INA219 curernt/voltage monitor from&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57186/vape-cell-ev---part-iii---ina219-power-monitor" data-e14adj="t"&gt;Vape Cell EV - part III - INA219 power monitor&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;I plan to cycle a known 18560 cell and a vape cell to see how close my tracking is to what is written on the packaging&lt;/p&gt;
&lt;h2 id="source"&gt;Source&lt;/h2&gt;
&lt;p&gt;&lt;a id="" href="https://github.com/saramic/vape-cell-EV" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;https://github.com/saramic/vape-cell-EV&lt;/a&gt;&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;</description></item><item><title>SmartAssist EV - Introducing Autonomous Line Following (TCRT5000) - Part 4</title><link>https://community.element14.com/thread/57191?ContentTypeID=0</link><pubDate>Sun, 16 Aug 2026 22:58:53 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:26638e00-c610-4558-a9fe-62d00dd7bd4e</guid><dc:creator>jelektro</dc:creator><slash:comments>0</slash:comments><comments>https://community.element14.com/thread/57191?ContentTypeID=0</comments><wfw:commentRss>https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57191/smartassist-ev---introducing-autonomous-line-following-tcrt5000---part-4/rss?ContentTypeId=0</wfw:commentRss><description>&lt;p&gt;&lt;span class="user-query-container" style="font-size:150%;"&gt;&lt;span class="user-query-bubble-with-background ng-star-inserted" data-test-id="luminous-collapsed-bubble"&gt;&lt;span class="horizontal-container ng-star-inserted"&gt;Project Roadmap&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Experimental Smart Assistive Platform for Elderly and Disabled People - Part 1" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57188/smartassist-ev-experimental-smart-assistive-platform-for-elderly-and-disabled-people---part-1" data-e14adj="t"&gt;Part 1&lt;/a&gt;&lt;a title="Experimental Smart Assistive Platform for Elderly and Disabled People - Part 1" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57188/smartassist-ev-experimental-smart-assistive-platform-for-elderly-and-disabled-people---part-1" data-e14adj="t"&gt;&amp;nbsp;- Experimental Smart Assistive Platform for Elderly and Disabled People&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Hardware Platform - Part 2" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57189/smartassist-ev---hardware-platform---part-2" data-e14adj="t"&gt;Part 2&lt;/a&gt;&lt;a title="Hardware Platform - Part 2" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57189/smartassist-ev---hardware-platform---part-2" data-e14adj="t"&gt;&amp;nbsp;- Hardware Platform&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Wireless Command and H-Bridge Direct Drive - Part 3" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57190/smartassist-ev---wireless-command-and-h-bridge-direct-drive---part-3" data-e14adj="t"&gt;Part 3&lt;/a&gt;&lt;a title="Wireless Command and H-Bridge Direct Drive - Part 3" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57190/smartassist-ev---wireless-command-and-h-bridge-direct-drive---part-3" data-e14adj="t"&gt;&amp;nbsp;- Wireless Command and H-Bridge Direct Drive&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Part 4 - Introducing Autonomous Line Following (TCRT5000)" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57191/smartassist-ev---introducing-autonomous-line-following-tcrt5000---part-4" data-e14adj="t"&gt;Part 4 - Introducing Autonomous Line Following (TCRT5000)&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Part 5 - Non-Contact Proactive Shielding (HC-SR04 Range Finder)" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57194/smartassist-ev---non-contact-proactive-shielding-hc-sr04-range-finder---part-5" data-e14adj="t"&gt;Part 5 - Non-Contact Proactive Shielding (HC-SR04 Range Finder)&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Part 6 - Strict Priority Hierarchy with Tactile Mechanical Bumpers" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57196/smartassist-ev---strict-priority-hierarchy-with-tactile-mechanical-bumpers---part-6" data-e14adj="t"&gt;Part 6 - Strict Priority Hierarchy with Tactile Mechanical Bumpers&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Part 7 - Mobile Robot Control and Live Video Streaming" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57198/smartassist-ev---mobile-robot-control-and-live-video-streaming---part-7" data-e14adj="t"&gt;Part 7 - Mobile Robot Control and Live Video Streaming&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Manual driving is limited by line-of-sight. To implement autonomous navigation, we integrate dual Infrared Line-Tracking Sensors (TrackingPin_L and TrackingPin_R). These sensors output binary feedback depending on whether they detect a highly reflective light floor or a non-reflective dark path.To allow transitioning between manual piloting and automatic tracking, we establish a State Machine using a RobotMode enumeration. Pressing a designated toggle button (0x1C or &amp;quot;OK&amp;quot;) alternates the core operating architecture.&lt;/p&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-hveid="CAAIDBAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;This step implements a Finite State Machine utilizing the remote&amp;#39;s OK button (0x1C). You can now toggle mid-flight between manual control and an automated tracking routine monitored by the optical sensors on pins A2 and A3.&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;img alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/4_2D00_1.jpg" /&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/4_2D00_2.jpg" /&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;h3 id="mcetoc_1k0p3qed25" data-path-to-node="2"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/4_2D00_3.jpg" /&gt;&lt;/h3&gt;
&lt;h3 id="mcetoc_1k0p3qeji6" data-path-to-node="2"&gt;&lt;/h3&gt;
&lt;h3 id="mcetoc_1k0p3pdm72" data-path-to-node="2"&gt;Hardware Wiring Diagram&lt;/h3&gt;
&lt;p data-path-to-node="3"&gt;Each IR tracking sensor module typically uses a 3-pin or 4-pin breakout board:&lt;/p&gt;
&lt;h4 data-path-to-node="4"&gt;1. Left Tracking Sensor (&lt;code data-path-to-node="4" data-index-in-node="25"&gt;TRACKING_PIN_L&lt;/code&gt;)&lt;/h4&gt;
&lt;ul data-path-to-node="5"&gt;
&lt;li&gt;
&lt;p data-path-to-node="5,0,0"&gt;&lt;b data-path-to-node="5,0,0" data-index-in-node="0"&gt;VCC Pin:&lt;/b&gt; Connects to &lt;b data-path-to-node="5,0,0" data-index-in-node="21"&gt;5V&lt;/b&gt; (or 3.3V, depending on sensor specifications).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p data-path-to-node="5,1,0"&gt;&lt;b data-path-to-node="5,1,0" data-index-in-node="0"&gt;GND Pin:&lt;/b&gt; Connects to &lt;b data-path-to-node="5,1,0" data-index-in-node="21"&gt;GND&lt;/b&gt; (Ground).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p data-path-to-node="5,2,0"&gt;&lt;b data-path-to-node="5,2,0" data-index-in-node="0"&gt;OUT / Signal Pin:&lt;/b&gt; Connects to Analog Pin &lt;b data-path-to-node="5,2,0" data-index-in-node="41"&gt;&lt;code data-path-to-node="5,2,0" data-index-in-node="41"&gt;A2&lt;/code&gt;&lt;/b&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-path-to-node="6"&gt;2. Right Tracking Sensor (&lt;code data-path-to-node="6" data-index-in-node="26"&gt;TRACKING_PIN_R&lt;/code&gt;)&lt;/h4&gt;
&lt;ul data-path-to-node="7"&gt;
&lt;li&gt;
&lt;p data-path-to-node="7,0,0"&gt;&lt;b data-path-to-node="7,0,0" data-index-in-node="0"&gt;VCC Pin:&lt;/b&gt; Connects to &lt;b data-path-to-node="7,0,0" data-index-in-node="21"&gt;5V&lt;/b&gt; (or 3.3V).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p data-path-to-node="7,1,0"&gt;&lt;b data-path-to-node="7,1,0" data-index-in-node="0"&gt;GND Pin:&lt;/b&gt; Connects to &lt;b data-path-to-node="7,1,0" data-index-in-node="21"&gt;GND&lt;/b&gt; (Ground).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p data-path-to-node="7,2,0"&gt;&lt;b data-path-to-node="7,2,0" data-index-in-node="0"&gt;OUT / Signal Pin:&lt;/b&gt; Connects to Analog Pin &lt;b data-path-to-node="7,2,0" data-index-in-node="41"&gt;&lt;code data-path-to-node="7,2,0" data-index-in-node="41"&gt;A3&lt;/code&gt;&lt;/b&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="mcetoc_1k0p3pdm83" data-path-to-node="9"&gt;Pin Mapping Summary&lt;/h3&gt;
&lt;table style="margin-bottom:32px;" data-path-to-node="10"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;strong&gt;Sensor Component&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;strong&gt;Module Pin&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;strong&gt;Arduino UNO Q Pin&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;strong&gt;Wire Function&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="10,1,0,0"&gt;&lt;b data-path-to-node="10,1,0,0" data-index-in-node="0"&gt;Left Sensor&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="10,1,1,0"&gt;VCC&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="10,1,2,0"&gt;5V&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="10,1,3,0"&gt;Power supply&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="10,2,1,0"&gt;GND&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="10,2,2,0"&gt;GND&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="10,2,3,0"&gt;Common ground&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="10,3,1,0"&gt;OUT / DO&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="10,3,2,0"&gt;&lt;b data-path-to-node="10,3,2,0" data-index-in-node="0"&gt;A2&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="10,3,3,0"&gt;Left line detection signal&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="10,4,0,0"&gt;&lt;b data-path-to-node="10,4,0,0" data-index-in-node="0"&gt;Right Sensor&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="10,4,1,0"&gt;VCC&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="10,4,2,0"&gt;5V&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="10,4,3,0"&gt;Power supply&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="10,5,1,0"&gt;GND&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="10,5,2,0"&gt;GND&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="10,5,3,0"&gt;Common ground&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="10,6,1,0"&gt;OUT / DO&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="10,6,2,0"&gt;&lt;b data-path-to-node="10,6,2,0" data-index-in-node="0"&gt;A3&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="10,6,3,0"&gt;Right line detection signal&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="mcetoc_1k0p3pdm84" data-path-to-node="12"&gt;Electrical Notes&lt;/h3&gt;
&lt;ul data-path-to-node="13"&gt;
&lt;li&gt;
&lt;p data-path-to-node="13,0,0"&gt;&lt;b data-path-to-node="13,0,0" data-index-in-node="0"&gt;Internal Pull-Ups:&lt;/b&gt; The code configures the pins using &lt;code data-path-to-node="13,0,0" data-index-in-node="54"&gt;pinMode(A2, INPUT_PULLUP)&lt;/code&gt; and &lt;code data-path-to-node="13,0,0" data-index-in-node="84"&gt;pinMode(A3, INPUT_PULLUP)&lt;/code&gt;. This activates the microcontroller&amp;#39;s built-in pull-up resistors, keeping the signal high by default and stabilizing digital state reads (&lt;code data-path-to-node="13,0,0" data-index-in-node="248"&gt;HIGH&lt;/code&gt; vs &lt;code data-path-to-node="13,0,0" data-index-in-node="256"&gt;LOW&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p data-path-to-node="13,1,0"&gt;&lt;b data-path-to-node="13,1,0" data-index-in-node="0"&gt;Digital Reading:&lt;/b&gt; Although &lt;code data-path-to-node="13,1,0" data-index-in-node="26"&gt;A2&lt;/code&gt; and &lt;code data-path-to-node="13,1,0" data-index-in-node="33"&gt;A3&lt;/code&gt; are labeled as analog input pins on the Arduino header, the &lt;code data-path-to-node="13,1,0" data-index-in-node="96"&gt;digitalRead()&lt;/code&gt; function processes them as standard digital binary inputs (returning &lt;code data-path-to-node="13,1,0" data-index-in-node="179"&gt;0&lt;/code&gt; or &lt;code data-path-to-node="13,1,0" data-index-in-node="184"&gt;1&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;p data-path-to-node="0"&gt;Here is the breakdown of the C code components responsible for line tracking (sensor reading, pin configuration, and autonomous navigation logic).&lt;/p&gt;
&lt;h3 id="mcetoc_1k0p38ong0" data-path-to-node="2"&gt;Line Tracking Code Snippets&lt;/h3&gt;
&lt;h4 data-path-to-node="3"&gt;1. Sensor Pin Definitions&lt;/h4&gt;
&lt;p data-path-to-node="4"&gt;The line tracking module uses two IR reflectance sensors connected to analog pins &lt;code data-path-to-node="4" data-index-in-node="82"&gt;A2&lt;/code&gt; (Left) and &lt;code data-path-to-node="4" data-index-in-node="96"&gt;A3&lt;/code&gt; (Right).&lt;/p&gt;
&lt;div class="code-block ng-tns-c998324306-190 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" style="display:block;" data-hveid="0" data-ved="0CAAQhtANahgKEwiD6aTnt7iWAxUAAAAAHQAAAAAQtAM"&gt;
&lt;div class="formatted-code-block-internal-container ng-tns-c998324306-190"&gt;
&lt;div class="animated-opacity ng-tns-c998324306-190"&gt;
&lt;pre class="ng-tns-c998324306-190"&gt;&lt;code class="code-container formatted ng-tns-c998324306-190" data-test-id="code-content"&gt;&lt;pre class="ui-code" data-mode="text"&gt;// Infrared Receiver and Tracking Sensor Pins
const int TRACKING_PIN_L = A2; // Left tracking sensor
const int TRACKING_PIN_R = A3; // Right tracking sensor

// Autonomous navigation speed threshold
const int TRACK_SPEED = 160; &lt;/pre&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-path-to-node="7"&gt;2. Pin Configuration in &lt;code data-path-to-node="7" data-index-in-node="24"&gt;setup()&lt;/code&gt;&lt;/h4&gt;
&lt;p data-path-to-node="8"&gt;In &lt;code data-path-to-node="8" data-index-in-node="3"&gt;setup()&lt;/code&gt;, the sensor pins are initialized as inputs with internal pull-up resistors enabled (&lt;code data-path-to-node="8" data-index-in-node="95"&gt;INPUT_PULLUP&lt;/code&gt;) to ensure stable digital reads.&lt;/p&gt;
&lt;div class="code-block ng-tns-c998324306-191 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" style="display:block;" data-hveid="0" data-ved="0CAAQhtANahgKEwiD6aTnt7iWAxUAAAAAHQAAAAAQtQM"&gt;
&lt;div class="formatted-code-block-internal-container ng-tns-c998324306-191"&gt;
&lt;div class="animated-opacity ng-tns-c998324306-191"&gt;
&lt;pre class="ng-tns-c998324306-191"&gt;&lt;code class="code-container formatted ng-tns-c998324306-191" data-test-id="code-content"&gt;&lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;&lt;/span&gt;&lt;/span&gt;&lt;pre class="ui-code" data-mode="text"&gt;void setup() {
  // Configure line tracking sensor pins as inputs with internal pull-ups
  pinMode(TRACKING_PIN_L, INPUT_PULLUP);
  pinMode(TRACKING_PIN_R, INPUT_PULLUP);
}&lt;/pre&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-path-to-node="11"&gt;3. Operational Mode Toggle (&lt;code data-path-to-node="11" data-index-in-node="28"&gt;MODE_AUTONOMOUS&lt;/code&gt;)&lt;/h4&gt;
&lt;p data-path-to-node="12"&gt;Pressing the &lt;b data-path-to-node="12" data-index-in-node="13"&gt;OK&lt;/b&gt; button (&lt;code data-path-to-node="12" data-index-in-node="24"&gt;0x1C&lt;/code&gt;) on the IR remote toggles between manual IR driving mode and autonomous line-following mode.&lt;/p&gt;
&lt;div class="code-block ng-tns-c998324306-192 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" style="display:block;" data-hveid="0" data-ved="0CAAQhtANahgKEwiD6aTnt7iWAxUAAAAAHQAAAAAQtgM"&gt;
&lt;div class="formatted-code-block-internal-container ng-tns-c998324306-192"&gt;
&lt;div class="animated-opacity ng-tns-c998324306-192"&gt;
&lt;pre class="ng-tns-c998324306-192"&gt;&lt;code class="code-container formatted ng-tns-c998324306-192" data-test-id="code-content"&gt;&lt;span class="hljs-keyword"&gt;&lt;/span&gt;&lt;pre class="ui-code" data-mode="text"&gt;case 0x1C: // OK Button on IR Remote
  if (currentMode == MODE_MANUAL) {
    currentMode = MODE_AUTONOMOUS;
    Serial.println(&amp;quot;Mode changed: AUTONOMOUS&amp;quot;);
  } else {
    currentMode = MODE_MANUAL;
    driveMotors(0, 0, 0, 0); // Stop motors immediately on switch
    Serial.println(&amp;quot;Mode changed: MANUAL&amp;quot;);
  }
  lastCommand = 0;
  delay(500); 
  break;&lt;/pre&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-path-to-node="15"&gt;4. Sensor Reading &amp;amp; Differential Motor Control Loop&lt;/h4&gt;
&lt;p&gt;Inside &lt;code data-path-to-node="16" data-index-in-node="7"&gt;loop()&lt;/code&gt;, when &lt;code data-path-to-node="16" data-index-in-node="20"&gt;currentMode == MODE_AUTONOMOUS&lt;/code&gt;, the system reads the digital state of both line sensors, combines them into a binary state value (&lt;code data-path-to-node="16" data-index-in-node="150"&gt;trackState&lt;/code&gt;), and adjusts motor outputs accordingly.&lt;/p&gt;
&lt;pre class="ui-code" data-mode="text"&gt;else if (currentMode == MODE_AUTONOMOUS) {
  // 1. Read individual sensor pin states (HIGH / LOW)
  int trackL = digitalRead(TRACKING_PIN_L);
  int trackR = digitalRead(TRACKING_PIN_R);

  // 2. Combine signals into a 2-bit state variable (0 to 3)
  // Bit 1 = Left Sensor, Bit 0 = Right Sensor
  int trackState = (trackL * 2) + trackR;
  
  // 3. Differential steering logic
  switch (trackState) {
    case 0: 
      // Both sensors off-line -&amp;gt; Stop
      driveMotors(0, 0, 0, 0); 
      break;                           
    case 1: 
      // Right sensor on line -&amp;gt; Turn Right
      driveMotors(0, TRACK_SPEED, TRACK_SPEED, 0); 
      break;       
    case 2: 
      // Left sensor on line -&amp;gt; Turn Left
      driveMotors(TRACK_SPEED, 0, 0, TRACK_SPEED); 
      break;       
    case 3: 
      // Both sensors centered on line -&amp;gt; Drive Forward
      driveMotors(0, TRACK_SPEED, 0, TRACK_SPEED); 
      break;       
  }
  delay(10); // Short delay to stabilize sensor polling rate
}&lt;/pre&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;br /&gt;
&lt;h3 id="mcetoc_1k0p38ong1" data-path-to-node="19"&gt;Logic Summary&lt;/h3&gt;
&lt;table style="margin-bottom:32px;" data-path-to-node="20"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;strong&gt;trackL&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;strong&gt;trackR&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;strong&gt;trackState&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;strong&gt;Action&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;strong&gt;Description&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="20,1,0,0"&gt;&lt;code data-path-to-node="20,1,0,0" data-index-in-node="0"&gt;0&lt;/code&gt; (LOW)&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="20,1,1,0"&gt;&lt;code data-path-to-node="20,1,1,0" data-index-in-node="0"&gt;0&lt;/code&gt; (LOW)&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="20,1,2,0"&gt;&lt;code data-path-to-node="20,1,2,0" data-index-in-node="0"&gt;0&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="20,1,3,0"&gt;&lt;b data-path-to-node="20,1,3,0" data-index-in-node="0"&gt;Stop&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="20,1,4,0"&gt;Line lost or end of track&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="20,2,0,0"&gt;&lt;code data-path-to-node="20,2,0,0" data-index-in-node="0"&gt;0&lt;/code&gt; (LOW)&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="20,2,1,0"&gt;&lt;code data-path-to-node="20,2,1,0" data-index-in-node="0"&gt;1&lt;/code&gt; (HIGH)&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="20,2,2,0"&gt;&lt;code data-path-to-node="20,2,2,0" data-index-in-node="0"&gt;1&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="20,2,3,0"&gt;&lt;b data-path-to-node="20,2,3,0" data-index-in-node="0"&gt;Turn Right&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="20,2,4,0"&gt;Vehicle drifted left; right wheel drives forward, left reverses&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="20,3,0,0"&gt;&lt;code data-path-to-node="20,3,0,0" data-index-in-node="0"&gt;1&lt;/code&gt; (HIGH)&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="20,3,1,0"&gt;&lt;code data-path-to-node="20,3,1,0" data-index-in-node="0"&gt;0&lt;/code&gt; (LOW)&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="20,3,2,0"&gt;&lt;code data-path-to-node="20,3,2,0" data-index-in-node="0"&gt;2&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="20,3,3,0"&gt;&lt;b data-path-to-node="20,3,3,0" data-index-in-node="0"&gt;Turn Left&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="20,3,4,0"&gt;Vehicle drifted right; left wheel drives forward, right reverses&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="20,4,0,0"&gt;&lt;code data-path-to-node="20,4,0,0" data-index-in-node="0"&gt;1&lt;/code&gt; (HIGH)&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="20,4,1,0"&gt;&lt;code data-path-to-node="20,4,1,0" data-index-in-node="0"&gt;1&lt;/code&gt; (HIGH)&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="20,4,2,0"&gt;&lt;code data-path-to-node="20,4,2,0" data-index-in-node="0"&gt;3&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="20,4,3,0"&gt;&lt;b data-path-to-node="20,4,3,0" data-index-in-node="0"&gt;Move Forward&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="20,4,4,0"&gt;Both sensors detect the track; vehicle drives straight ahead&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;span class="HwtZe" style="font-size:150%;" lang="en"&gt;&lt;span class="jCAhz ChMk0b"&gt;&lt;span class="ryNqvb"&gt;Full code&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;span class="HwtZe" lang="en"&gt;&lt;span class="jCAhz ChMk0b"&gt;&lt;span class="ryNqvb"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;span class="HwtZe" lang="en"&gt;&lt;span class="jCAhz ChMk0b"&gt;&lt;span class="ryNqvb"&gt;The full code for this stage is as follows&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;:&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;pre class="ui-code" data-mode="text"&gt;#include &amp;lt;Arduino.h&amp;gt;
#include &amp;lt;Arduino_LED_Matrix.h&amp;gt; // Library for the 8x13 LED matrix

Arduino_LED_Matrix matrix; // Initialize LED matrix object

// L9110S Motor Driver Pins
const int MOTOR_PIN_A1 = 5; 
const int MOTOR_PIN_A2 = 6; 
const int MOTOR_PIN_B1 = 9; 
const int MOTOR_PIN_B2 = 10;

// Infrared Receiver and Tracking Sensor Pins
const int IR_RECEIVE_PIN = 2; 
const int TRACKING_PIN_L = A2;
const int TRACKING_PIN_R = A3;

enum RobotMode {
  MODE_MANUAL,     
  MODE_AUTONOMOUS  
};

RobotMode currentMode = MODE_MANUAL; 

// Speed settings
const int MOTOR_SPEED = 200; 
const int TRACK_SPEED = 160; 

byte lastCommand = 0;

// LED Matrix Frame Buffer Size (104 pixels)
const uint8_t FRAME_SIZE = 8 * 13;

// --- LED MATRIX ARROW &amp;amp; ICON ARRAYS (Brightness levels 0-7) ---
uint8_t arrow_up[FRAME_SIZE] = {
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 7, 7, 7, 7, 7, 0, 0, 0, 0,
    0, 0, 0, 7, 7, 0, 7, 0, 7, 7, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0
};

uint8_t arrow_down[FRAME_SIZE] = {
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 7, 7, 0, 7, 0, 7, 7, 0, 0, 0,
    0, 0, 0, 0, 7, 7, 7, 7, 7, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0
};

uint8_t arrow_left[FRAME_SIZE] = {
    0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    0, 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0
};

uint8_t arrow_right[FRAME_SIZE] = {
    0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 0,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0
};

uint8_t stop_icon[FRAME_SIZE] = { 0 }; 

// Autonomous Mode Icon (Letter &amp;quot;A&amp;quot;)
uint8_t auto_icon[FRAME_SIZE] = {
    0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 0,
    0, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 0,
    0, 0, 0, 0, 7, 7, 7, 7, 7, 0, 0, 0, 0,
    0, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 0,
    0, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 0,
    0, 0, 0, 0, 7, 0, 0, 0, 7, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

// --- ONBOARD RGB LED FUNCTIONS ---
void set_led3_color(int r, int g, int b) {
  analogWrite(LED3_R, r);
  analogWrite(LED3_G, g);
  analogWrite(LED3_B, b);
}

void set_led4_color(bool r, bool g, bool b) {
  digitalWrite(LED4_R, r ? LOW : HIGH);
  digitalWrite(LED4_G, g ? LOW : HIGH);
  digitalWrite(LED4_B, b ? LOW : HIGH);
}

// Elecrow IR Decoder
long readElecrowIR() {
  int count = 0;
  while (digitalRead(IR_RECEIVE_PIN) == LOW &amp;amp;&amp;amp; count &amp;lt; 200) { count++; delayMicroseconds(60); }
  if (count &amp;gt;= 200) return -1;

  count = 0;
  while (digitalRead(IR_RECEIVE_PIN) == HIGH &amp;amp;&amp;amp; count &amp;lt; 80) { count++; delayMicroseconds(60); }
  if (count &amp;gt;= 80) return -1;

  int idx = 0, cnt = 0;
  byte data[4] = {0, 0, 0, 0};

  for (int i = 0; i &amp;lt; 32; i++) {
    count = 0;
    while (digitalRead(IR_RECEIVE_PIN) == LOW &amp;amp;&amp;amp; count &amp;lt; 15) { count++; delayMicroseconds(60); }
    count = 0;
    while (digitalRead(IR_RECEIVE_PIN) == HIGH &amp;amp;&amp;amp; count &amp;lt; 40) { count++; delayMicroseconds(60); }

    if (count &amp;gt; 8) data[idx] |= (1 &amp;lt;&amp;lt; cnt);

    if (cnt == 7) { cnt = 0; idx++; } else { cnt++; }
  }

  if ((byte)(data[0] + data[1]) == 0xFF &amp;amp;&amp;amp; (byte)(data[2] + data[3]) == 0xFF) {
    return data[2];
  }
  return -1;
}

void driveMotors(int a1, int a2, int b1, int b2) {
  analogWrite(MOTOR_PIN_A1, a1);
  analogWrite(MOTOR_PIN_A2, a2);
  analogWrite(MOTOR_PIN_B1, b1);
  analogWrite(MOTOR_PIN_B2, b2);
}

void executeCommand(byte command) {
  switch (command) {
    case 0x1C: // OK Button - Toggle Operational Mode
      if (currentMode == MODE_MANUAL) {
        currentMode = MODE_AUTONOMOUS;
        Serial.println(&amp;quot;Mode changed: AUTONOMOUS&amp;quot;);
      } else {
        currentMode = MODE_MANUAL;
        driveMotors(0, 0, 0, 0); 
        Serial.println(&amp;quot;Mode changed: MANUAL&amp;quot;);
      }
      lastCommand = 0;
      delay(500); 
      break;

    case 0x18: // Forward
      if (currentMode == MODE_MANUAL) {
        driveMotors(0, MOTOR_SPEED, 0, MOTOR_SPEED);
        set_led4_color(false, true, false); // Green
        set_led3_color(0, 200, 0);
        matrix.draw(arrow_up);
      }
      break;

    case 0x08: // Turn Left
      if (currentMode == MODE_MANUAL) {
        driveMotors(MOTOR_SPEED, 0, 0, MOTOR_SPEED);
        set_led4_color(false, false, true); // Blue
        set_led3_color(0, 0, 200);
        matrix.draw(arrow_left);
      }
      break;

    case 0x5A: // Turn Right
      if (currentMode == MODE_MANUAL) {
        driveMotors(0, MOTOR_SPEED, MOTOR_SPEED, 0);
        set_led4_color(false, false, true); // Blue
        set_led3_color(0, 0, 200);
        matrix.draw(arrow_right);
      }
      break;

    case 0x52: // Backward
      if (currentMode == MODE_MANUAL) {
        driveMotors(MOTOR_SPEED, 0, MOTOR_SPEED, 0);
        set_led4_color(true, false, false); // Red
        set_led3_color(200, 0, 0);
        matrix.draw(arrow_down);
      }
      break;

    default:
      if (currentMode == MODE_MANUAL) {
        driveMotors(0, 0, 0, 0);
        set_led4_color(false, false, false);
        set_led3_color(0, 0, 0);
        matrix.draw(stop_icon);
      }
      break;
  }
}

void setup() {
  Serial.begin(115200);
  
  pinMode(IR_RECEIVE_PIN, INPUT_PULLUP); 
  pinMode(TRACKING_PIN_L, INPUT_PULLUP);
  pinMode(TRACKING_PIN_R, INPUT_PULLUP);

  // RGB LED Pin Configuration
  pinMode(LED4_R, OUTPUT); pinMode(LED4_G, OUTPUT); pinMode(LED4_B, OUTPUT);
  set_led3_color(0, 0, 0);
  set_led4_color(false, false, false);

  // Initialize LED Matrix
  matrix.begin();
  matrix.setGrayscaleBits(3); // 8 brightness levels (0-7)
  matrix.clear();

  Serial.println(&amp;quot;Robot Ready with LED Matrix and RGB Feedback.&amp;quot;);
}

void loop() {
  // 1. Read IR Signal
  if (digitalRead(IR_RECEIVE_PIN) == LOW) {
    long result = readElecrowIR();
    if (result != -1) {
      lastCommand = (byte)result;
      executeCommand(lastCommand);
    }
  }

  // 2. Mode Execution Loop
  if (currentMode == MODE_MANUAL) {
    if (digitalRead(IR_RECEIVE_PIN) == HIGH) {
      driveMotors(0, 0, 0, 0);
      set_led4_color(false, false, false);
      set_led3_color(0, 0, 0);
      matrix.draw(stop_icon);
    } else {
      executeCommand(lastCommand);
    }
  } 
  else if (currentMode == MODE_AUTONOMOUS) {
    // Show Autonomous icon (&amp;quot;A&amp;quot;) and set indicator LEDs to Yellow/Cyan
    matrix.draw(auto_icon);
    set_led4_color(true, true, false); 
    set_led3_color(100, 100, 0);

    int trackL = digitalRead(TRACKING_PIN_L);
    int trackR = digitalRead(TRACKING_PIN_R);
    int trackState = (trackL * 2) + trackR;
    
    switch (trackState) {
      case 0: driveMotors(0, 0, 0, 0); break;                           // Stop
      case 1: driveMotors(0, TRACK_SPEED, TRACK_SPEED, 0); break;       // Turn Right
      case 2: driveMotors(TRACK_SPEED, 0, 0, TRACK_SPEED); break;       // Turn Left
      case 3: driveMotors(0, TRACK_SPEED, 0, TRACK_SPEED); break;       // Move Forward
    }
    delay(10);
  }
}&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-wiz-uids="pKKIde_2w,pKKIde_2v" data-hveid="CAAIDRAA" data-processed="true" data-complete="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 4px 0px 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;div data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;div data-animation-atomic="" data-sae="" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 1px solid rgb(240, 242, 245);"&gt;
&lt;div dir="ltr" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;pre data-copy-service-computed-style="font-family: monospace; font-size: 14px; font-weight: 400; margin: 14px 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;</description></item><item><title>SmartAssist EV - Wireless Command and H-Bridge Direct Drive - Part 3</title><link>https://community.element14.com/thread/57190?ContentTypeID=0</link><pubDate>Sun, 16 Aug 2026 22:57:20 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:15473ea3-0f49-46e7-a1e9-95dc40180524</guid><dc:creator>jelektro</dc:creator><slash:comments>0</slash:comments><comments>https://community.element14.com/thread/57190?ContentTypeID=0</comments><wfw:commentRss>https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57190/smartassist-ev---wireless-command-and-h-bridge-direct-drive---part-3/rss?ContentTypeId=0</wfw:commentRss><description>&lt;div data-sfc-cp="" data-sfc-root="ep" data-hveid="CAAIChAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;p&gt;&lt;span class="user-query-container" style="font-size:150%;"&gt;&lt;span class="user-query-bubble-with-background ng-star-inserted" data-test-id="luminous-collapsed-bubble"&gt;&lt;span class="horizontal-container ng-star-inserted"&gt;Project Roadmap&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Experimental Smart Assistive Platform for Elderly and Disabled People - Part 1" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57188/smartassist-ev-experimental-smart-assistive-platform-for-elderly-and-disabled-people---part-1" data-e14adj="t"&gt;Part 1&lt;/a&gt;&lt;a title="Experimental Smart Assistive Platform for Elderly and Disabled People - Part 1" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57188/smartassist-ev-experimental-smart-assistive-platform-for-elderly-and-disabled-people---part-1" data-e14adj="t"&gt;&amp;nbsp;- Experimental Smart Assistive Platform for Elderly and Disabled People&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Hardware Platform - Part 2" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57189/smartassist-ev---hardware-platform---part-2" data-e14adj="t"&gt;Part 2&lt;/a&gt;&lt;a title="Hardware Platform - Part 2" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57189/smartassist-ev---hardware-platform---part-2" data-e14adj="t"&gt;&amp;nbsp;- Hardware Platform&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Wireless Command and H-Bridge Direct Drive - Part 3" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57190/smartassist-ev---wireless-command-and-h-bridge-direct-drive---part-3" data-e14adj="t"&gt;Part 3&lt;/a&gt;&lt;a title="Wireless Command and H-Bridge Direct Drive - Part 3" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57190/smartassist-ev---wireless-command-and-h-bridge-direct-drive---part-3" data-e14adj="t"&gt;&amp;nbsp;- Wireless Command and H-Bridge Direct Drive&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Part 4 - Introducing Autonomous Line Following (TCRT5000)" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57191/smartassist-ev---introducing-autonomous-line-following-tcrt5000---part-4" data-e14adj="t"&gt;Part 4 - Introducing Autonomous Line Following (TCRT5000)&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Part 5 - Non-Contact Proactive Shielding (HC-SR04 Range Finder)" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57194/smartassist-ev---non-contact-proactive-shielding-hc-sr04-range-finder---part-5" data-e14adj="t"&gt;Part 5 - Non-Contact Proactive Shielding (HC-SR04 Range Finder)&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Part 6 - Strict Priority Hierarchy with Tactile Mechanical Bumpers" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57196/smartassist-ev---strict-priority-hierarchy-with-tactile-mechanical-bumpers---part-6" data-e14adj="t"&gt;Part 6 - Strict Priority Hierarchy with Tactile Mechanical Bumpers&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Part 7 - Mobile Robot Control and Live Video Streaming" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57198/smartassist-ev---mobile-robot-control-and-live-video-streaming---part-7" data-e14adj="t"&gt;Part 7 - Mobile Robot Control and Live Video Streaming&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-hveid="CAAIChAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;Every robotic vehicle requires a foundation for movement and communication. We begin with an Infrared (IR) receiver based on the standard IRremote library alongside a continuous PWM-controlled H-Bridge system.&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-hveid="CAAICxAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;By mapping explicit hex codes from a handheld remote, the car acts on standard movement directions. Because a single button press should not trigger indefinite movement, an optional timeout sequence safely cuts power to the motors when the transmitter goes quiet.&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-hveid="CAAICxAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-hveid="CAAICRAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;This sketch boots the vehicle into manual mode, giving you directional control via your handheld remote. When a key is released, a built-in safety timeout automatically halts the motors to prevent runaway scenarios.&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;On the Arduino UNO Q board, the analogWrite() function might not work correctly if you explicitly define the pin mode using pinMode(pin, OUTPUT).&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;Due to its architecture and a known issue in the underlying Zephyr core, removing the pinMode statement from your setup() function allows the PWM signal to generate properly.&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;How to use analogWrite on UNO Q?&lt;/div&gt;
&lt;ul&gt;
&lt;li data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;Do not call pinMode(pin, OUTPUT) for your chosen PWM pin.&lt;/li&gt;
&lt;li data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;Call analogWrite(pin, value) directly in your code.&lt;/li&gt;
&lt;li data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;The value ranges from 0 (always off) to 255 (always on).&lt;/li&gt;
&lt;/ul&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;span class="HwtZe" lang="en"&gt;&lt;span class="jCAhz ChMk0b"&gt;&lt;span class="ryNqvb"&gt;The STM32 microcontroller controls the motors using the L9110S chip located on a board attached to the motor. The power module board contains two L9110s chips, each controlling a single DC motor.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;img alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/3_2D00_2.jpg" /&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;span class="HwtZe" lang="en"&gt;&lt;span class="jCAhz ChMk0b"&gt;&lt;span class="ryNqvb"&gt;The vehicle can be controlled locally using a remote control&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;span class="HwtZe" lang="en"&gt;&lt;span class="jCAhz ChMk0b"&gt;&lt;span class="ryNqvb"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/3_2D00_3.jpg" /&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;span class="HwtZe" lang="en"&gt;&lt;span class="jCAhz ChMk0b"&gt;&lt;span class="ryNqvb"&gt;The assembled vehicle, ready for testing, is shown in the photo below.&lt;/span&gt;&lt;/span&gt; &lt;span class="jCAhz ChMk0b"&gt;&lt;span class="ryNqvb"&gt;The vehicle includes an Arduino Uno Q, a 4x1.5V battery pack, and a breadboard containing an infrared receiver.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;span class="HwtZe" lang="en"&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/3_2D00_1.jpg" /&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;p data-path-to-node="0"&gt;Here is the circuit connection and pin configuration guide for setting up your Arduino board with the L9110S motor driver, IR receiver, and power system.&lt;/p&gt;
&lt;h3 id="mcetoc_1k0p3gl4i0" data-path-to-node="2"&gt;&lt;span style="font-size:inherit;"&gt;Pin Mapping Table&lt;/span&gt;&lt;/h3&gt;
&lt;table style="margin-bottom:32px;" data-path-to-node="3"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;strong&gt;Component&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;strong&gt;Component Pin&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;strong&gt;Arduino UNO Q Pin&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;strong&gt;Notes / Description&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,1,0,0"&gt;&lt;b data-path-to-node="3,1,0,0" data-index-in-node="0"&gt;L9110S Motor Driver&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,1,1,0"&gt;&lt;code data-path-to-node="3,1,1,0" data-index-in-node="0"&gt;A-1A&lt;/code&gt; / &lt;code data-path-to-node="3,1,1,0" data-index-in-node="7"&gt;A-1B&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,1,2,0"&gt;Pin 5 (PWM)&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,1,3,0"&gt;Motor A Direction &amp;amp; Speed&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,2,1,0"&gt;&lt;code data-path-to-node="3,2,1,0" data-index-in-node="0"&gt;A-1B&lt;/code&gt; / &lt;code data-path-to-node="3,2,1,0" data-index-in-node="7"&gt;A-2A&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,2,2,0"&gt;Pin 6 (PWM)&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,2,3,0"&gt;Motor A Direction &amp;amp; Speed&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,3,1,0"&gt;&lt;code data-path-to-node="3,3,1,0" data-index-in-node="0"&gt;B-1A&lt;/code&gt; / &lt;code data-path-to-node="3,3,1,0" data-index-in-node="7"&gt;B-1B&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,3,2,0"&gt;Pin 9 (PWM)&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,3,3,0"&gt;Motor B Direction &amp;amp; Speed&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,4,1,0"&gt;&lt;code data-path-to-node="3,4,1,0" data-index-in-node="0"&gt;B-1B&lt;/code&gt; / &lt;code data-path-to-node="3,4,1,0" data-index-in-node="7"&gt;B-2A&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,4,2,0"&gt;Pin 10 (PWM)&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,4,3,0"&gt;Motor B Direction &amp;amp; Speed&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,5,1,0"&gt;&lt;code data-path-to-node="3,5,1,0" data-index-in-node="0"&gt;VCC&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,5,2,0"&gt;External Power (+)&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,5,3,0"&gt;&lt;b data-path-to-node="3,5,3,0" data-index-in-node="0"&gt;6V - 12V&lt;/b&gt; (Battery pack)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,6,1,0"&gt;&lt;code data-path-to-node="3,6,1,0" data-index-in-node="0"&gt;GND&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,6,2,0"&gt;Common Ground (-)&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,6,3,0"&gt;Shared GND with Arduino&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,7,0,0"&gt;&lt;b data-path-to-node="3,7,0,0" data-index-in-node="0"&gt;IR Receiver&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,7,1,0"&gt;&lt;code data-path-to-node="3,7,1,0" data-index-in-node="0"&gt;OUT&lt;/code&gt; / &lt;code data-path-to-node="3,7,1,0" data-index-in-node="6"&gt;DATA&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,7,2,0"&gt;Pin 2&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,7,3,0"&gt;Signal line (&lt;code data-path-to-node="3,7,3,0" data-index-in-node="13"&gt;INPUT_PULLUP&lt;/code&gt;)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,8,1,0"&gt;&lt;code data-path-to-node="3,8,1,0" data-index-in-node="0"&gt;VCC&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,8,2,0"&gt;5V / 3.3V&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,8,3,0"&gt;Logic power supply&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,9,1,0"&gt;&lt;code data-path-to-node="3,9,1,0" data-index-in-node="0"&gt;GND&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,9,2,0"&gt;Common Ground (-)&lt;/span&gt;&lt;/td&gt;
&lt;td style="border:1px solid #c4c7c5;padding:8px 12px;"&gt;&lt;span data-path-to-node="3,9,3,0"&gt;Shared GND with Arduino&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;
&lt;p data-path-to-node="3"&gt;This Arduino sketch implements a complete control system for a two-wheeled mobile robot managed via an Infrared (IR) remote. It decodes custom IR timing signals, controls DC motors using an L9110S H-bridge driver, provides visual status updates via onboard RGB LEDs, and displays directional arrows on an 8x13 LED matrix.&lt;/p&gt;
&lt;h3 id="mcetoc_1k0p0u3v60" data-path-to-node="5"&gt;Key Code Sections&lt;/h3&gt;
&lt;h4 data-path-to-node="6"&gt;1. Libraries and Hardware Definitions&lt;/h4&gt;
&lt;p data-path-to-node="7"&gt;Imports necessary libraries for the LED matrix and assigns GPIO pins for the L9110S motor driver, IR receiver, and motor speed parameters.&lt;/p&gt;
&lt;div class="code-block ng-tns-c998324306-36 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" style="display:block;" data-hveid="0" data-ved="0CAAQhtANahcKEwiD6aTnt7iWAxUAAAAAHQAAAAAQRw"&gt;
&lt;div class="formatted-code-block-internal-container ng-tns-c998324306-36"&gt;
&lt;div class="animated-opacity ng-tns-c998324306-36"&gt;
&lt;pre class="ng-tns-c998324306-36"&gt;&lt;code class="code-container formatted ng-tns-c998324306-36" data-test-id="code-content"&gt;&lt;pre class="ui-code" data-mode="text"&gt;#include &amp;lt;Arduino.h&amp;gt;
#include &amp;lt;Arduino_LED_Matrix.h&amp;gt;   // Library for the 8x13 LED matrix

Arduino_LED_Matrix matrix; // Initialize the large LED matrix

// L9110S Motor Driver Pins on Arduino UNO Q
const int MOTOR_PIN_A1 = 5; 
const int MOTOR_PIN_A2 = 6; 
const int MOTOR_PIN_B1 = 9; 
const int MOTOR_PIN_B2 = 10;

// Infrared Receiver Pin (Configured with PULLUP)
const int IR_RECEIVE_PIN = 2; 

// Motor speed (Scale 0-255 for Arduino analogWrite)
const int MOTOR_SPEED = 200; &lt;/pre&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-path-to-node="9"&gt;2. LED Matrix Graphics Arrays&lt;/h4&gt;
&lt;p data-path-to-node="10"&gt;Defines 104-element arrays (8x13 display) storing grayscale values from &lt;code data-path-to-node="10" data-index-in-node="72"&gt;0&lt;/code&gt; (OFF) to &lt;code data-path-to-node="10" data-index-in-node="83"&gt;7&lt;/code&gt; (max brightness). These pre-rendered graphics represent directional arrows (Up, Down, Left, Right) and an empty frame for stopping.&lt;/p&gt;
&lt;div class="code-block ng-tns-c998324306-37 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" style="display:block;" data-hveid="0" data-ved="0CAAQhtANahcKEwiD6aTnt7iWAxUAAAAAHQAAAAAQSA"&gt;
&lt;div class="formatted-code-block-internal-container ng-tns-c998324306-37"&gt;
&lt;div class="animated-opacity ng-tns-c998324306-37"&gt;
&lt;pre class="ng-tns-c998324306-37"&gt;&lt;code class="code-container formatted ng-tns-c998324306-37" data-test-id="code-content"&gt;&lt;span class="hljs-keyword"&gt;&lt;br /&gt;&lt;/span&gt;&lt;pre class="ui-code" data-mode="text"&gt;const uint8_t FRAME_SIZE = 8 * 13; // 104 pixels for the LED matrix

// Example: Arrow Up Array
uint8_t arrow_up[FRAME_SIZE] = {
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 7, 7, 7, 7, 7, 0, 0, 0, 0,
    0, 0, 0, 7, 7, 0, 7, 0, 7, 7, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0
};

uint8_t stop_icon[FRAME_SIZE] = { 0 }; &lt;/pre&gt;&lt;br /&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-path-to-node="12"&gt;3. Onboard RGB LED Helper Functions&lt;/h4&gt;
&lt;p data-path-to-node="13"&gt;Provides utility functions to control status indicator LEDs. &lt;code data-path-to-node="13" data-index-in-node="61"&gt;set_led3_color&lt;/code&gt; uses analog PWM values for smooth color mixing, while &lt;code data-path-to-node="13" data-index-in-node="130"&gt;set_led4_color&lt;/code&gt; handles active-LOW digital switching.&lt;/p&gt;
&lt;div class="code-block ng-tns-c998324306-38 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" style="display:block;" data-hveid="0" data-ved="0CAAQhtANahcKEwiD6aTnt7iWAxUAAAAAHQAAAAAQSQ"&gt;
&lt;div class="formatted-code-block-internal-container ng-tns-c998324306-38"&gt;
&lt;div class="animated-opacity ng-tns-c998324306-38"&gt;
&lt;pre class="ng-tns-c998324306-38"&gt;&lt;code class="code-container formatted ng-tns-c998324306-38" data-test-id="code-content"&gt;&lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;&lt;/span&gt;&lt;/span&gt;&lt;pre class="ui-code" data-mode="text"&gt;void set_led3_color(int r, int g, int b) {
  analogWrite(LED3_R, r);
  analogWrite(LED3_G, g);
  analogWrite(LED3_B, b);
}

void set_led4_color(bool r, bool g, bool b) {
  digitalWrite(LED4_R, r ? LOW : HIGH);
  digitalWrite(LED4_G, g ? LOW : HIGH);
  digitalWrite(LED4_B, b ? LOW : HIGH);
}&lt;/pre&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-path-to-node="15"&gt;4. Low-Level IR Signal Decoder (&lt;code data-path-to-node="15" data-index-in-node="32"&gt;readElecrowIR&lt;/code&gt;)&lt;/h4&gt;
&lt;p data-path-to-node="16"&gt;Implements custom pulse-width decoding for the Elecrow IR protocol without relying on standard IR libraries. It measures microsecond pulse durations, reconstructs 32 bits (4 bytes) of data, and validates signal integrity using a bitwise checksum (&lt;code data-path-to-node="16" data-index-in-node="247"&gt;data + data == 0xFF&lt;/code&gt;).&lt;/p&gt;
&lt;div class="code-block ng-tns-c998324306-39 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" style="display:block;" data-hveid="0" data-ved="0CAAQhtANahcKEwiD6aTnt7iWAxUAAAAAHQAAAAAQSg"&gt;
&lt;div class="formatted-code-block-internal-container ng-tns-c998324306-39"&gt;
&lt;div class="animated-opacity ng-tns-c998324306-39"&gt;
&lt;pre class="ng-tns-c998324306-39"&gt;&lt;code class="code-container formatted ng-tns-c998324306-39" data-test-id="code-content"&gt;&lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;&lt;/span&gt;&lt;/span&gt;&lt;pre class="ui-code" data-mode="text"&gt;long readElecrowIR() {
  int count = 0;
  
  // 1. Wait for leading LOW pulse
  while (digitalRead(IR_RECEIVE_PIN) == LOW &amp;amp;&amp;amp; count &amp;lt; 200) {
    count++;
    delayMicroseconds(60);
  }
  if (count &amp;gt;= 200) return -1;

  // 2. Wait for leading HIGH space
  count = 0;
  while (digitalRead(IR_RECEIVE_PIN) == HIGH &amp;amp;&amp;amp; count &amp;lt; 80) {
    count++;
    delayMicroseconds(60);
  }
  if (count &amp;gt;= 80) return -1;

  // 3. Read 32 bits of payload data
  int idx = 0, cnt = 0;
  byte data[4] = {0, 0, 0, 0};

  for (int i = 0; i &amp;lt; 32; i++) {
    count = 0;
    while (digitalRead(IR_RECEIVE_PIN) == LOW &amp;amp;&amp;amp; count &amp;lt; 15) {
      count++;
      delayMicroseconds(60);
    }
    
    count = 0;
    while (digitalRead(IR_RECEIVE_PIN) == HIGH &amp;amp;&amp;amp; count &amp;lt; 40) {
      count++;
      delayMicroseconds(60);
    }

    if (count &amp;gt; 8) {
      data[idx] |= (1 &amp;lt;&amp;lt; cnt); // Store logical &amp;#39;1&amp;#39;
    }

    if (cnt == 7) { cnt = 0; idx++; } else { cnt++; }
  }

  // 4. Checksum verification
  if ((byte)(data[0] + data[1]) == 0xFF &amp;amp;&amp;amp; (byte)(data[2] + data[3]) == 0xFF) {
    return data[2]; // Return valid command byte
  }

  return -1;
}&lt;/pre&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-path-to-node="18"&gt;5. Motor Control and Command Execution&lt;/h4&gt;
&lt;p data-path-to-node="19"&gt;&lt;code data-path-to-node="19" data-index-in-node="0"&gt;driveMotors&lt;/code&gt; sends PWM values directly to the driver pins. &lt;code data-path-to-node="19" data-index-in-node="58"&gt;executeCommand&lt;/code&gt; maps hex command codes (e.g., &lt;code data-path-to-node="19" data-index-in-node="103"&gt;0x18&lt;/code&gt; for forward) to corresponding motor movements, LED status colors, and matrix graphics.&lt;/p&gt;
&lt;div class="code-block ng-tns-c998324306-40 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" style="display:block;" data-hveid="0" data-ved="0CAAQhtANahcKEwiD6aTnt7iWAxUAAAAAHQAAAAAQSw"&gt;
&lt;div class="formatted-code-block-internal-container ng-tns-c998324306-40"&gt;
&lt;div class="animated-opacity ng-tns-c998324306-40"&gt;
&lt;pre class="ng-tns-c998324306-40"&gt;&lt;code class="code-container formatted ng-tns-c998324306-40" data-test-id="code-content"&gt;&lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;&lt;/span&gt;&lt;/span&gt;&lt;pre class="ui-code" data-mode="text"&gt;void driveMotors(int a1, int a2, int b1, int b2) {
  analogWrite(MOTOR_PIN_A1, a1);
  analogWrite(MOTOR_PIN_A2, a2);
  analogWrite(MOTOR_PIN_B1, b1);
  analogWrite(MOTOR_PIN_B2, b2);
}

void executeCommand(byte command) {
  switch (command) {
    case 0x18: // Up Arrow - Forward
      driveMotors(0, MOTOR_SPEED, 0, MOTOR_SPEED);
      set_led4_color(false, true, false);  
      set_led3_color(0, 200, 0);           
      matrix.draw(arrow_up);
      break;
    case 0x08: // Left Arrow - Turn Left
      driveMotors(MOTOR_SPEED, 0, 0, MOTOR_SPEED);
      set_led4_color(false, false, true);  
      set_led3_color(0, 0, 200);           
      matrix.draw(arrow_left);
      break;
    default: // Stop on unassigned keys
      driveMotors(0, 0, 0, 0); 
      set_led4_color(false, false, false); 
      set_led3_color(0, 0, 0);
      matrix.draw(stop_icon);
      break;
  }
}&lt;/pre&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-path-to-node="21"&gt;6. System Setup and Main Loop (&lt;code data-path-to-node="21" data-index-in-node="31"&gt;setup&lt;/code&gt; &amp;amp; &lt;code data-path-to-node="21" data-index-in-node="39"&gt;loop&lt;/code&gt;)&lt;/h4&gt;
&lt;p data-path-to-node="22"&gt;Configures input pins with internal pull-ups, initializes the display in 3-bit grayscale mode, and continuously polls the IR pin. The loop includes a fail-safe that stops the motors if no active transmission is detected.&lt;/p&gt;
&lt;div class="code-block ng-tns-c998324306-41 ng-animate-disabled ng-trigger ng-trigger-codeBlockRevealAnimation" style="display:block;" data-hveid="0" data-ved="0CAAQhtANahcKEwiD6aTnt7iWAxUAAAAAHQAAAAAQTA"&gt;
&lt;div class="formatted-code-block-internal-container ng-tns-c998324306-41"&gt;
&lt;div class="animated-opacity ng-tns-c998324306-41"&gt;
&lt;pre class="ng-tns-c998324306-41"&gt;&lt;code class="code-container formatted ng-tns-c998324306-41" data-test-id="code-content"&gt;&lt;span class="hljs-function"&gt;&lt;span class="hljs-keyword"&gt;&lt;/span&gt;&lt;/span&gt;&lt;pre class="ui-code" data-mode="text"&gt;void setup() {
  Serial.begin(115200);
  pinMode(IR_RECEIVE_PIN, INPUT_PULLUP); // Enable internal pull-up

  pinMode(LED4_R, OUTPUT); 
  pinMode(LED4_G, OUTPUT); 
  pinMode(LED4_B, OUTPUT);

  matrix.begin();
  matrix.setGrayscaleBits(3); // Enable 8-level brightness
  matrix.clear();
}

void loop() {
  if (digitalRead(IR_RECEIVE_PIN) == HIGH) {
    long result = readElecrowIR();
    if (result != -1) {
      lastCommand = (byte)result;
    }
  }

  // Safety mechanism: Stop motors if no IR signal is being transmitted
  if (digitalRead(IR_RECEIVE_PIN) == LOW) {
    driveMotors(0, 0, 0, 0); 
  } else {
    executeCommand(lastCommand);
  }
  
  delay(10); 
}&lt;/pre&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;span class="HwtZe" style="font-size:150%;" lang="en"&gt;&lt;span class="jCAhz ChMk0b"&gt;&lt;span class="ryNqvb"&gt;Full code&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;span class="HwtZe" lang="en"&gt;&lt;span class="jCAhz ChMk0b"&gt;&lt;span class="ryNqvb"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;span class="HwtZe" lang="en"&gt;&lt;span class="jCAhz ChMk0b"&gt;&lt;span class="ryNqvb"&gt;The full code for this stage is as follows&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;:&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-complete="true" data-processed="true" data-sfc-inited="2" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 14px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;pre class="ui-code" data-mode="text"&gt;#include &amp;lt;Arduino.h&amp;gt;

#include &amp;lt;Arduino_LED_Matrix.h&amp;gt;   // Library for the 8x13 LED matrix

Arduino_LED_Matrix matrix; // Initialize the large LED matrix

// L9110S Motor Driver Pins on Arduino UNO Q
const int MOTOR_PIN_A1 = 5; 
const int MOTOR_PIN_A2 = 6; 
const int MOTOR_PIN_B1 = 9; 
const int MOTOR_PIN_B2 = 10;

// Infrared Receiver Pin (Configured with PULLUP just like the manufacturer&amp;#39;s code)
const int IR_RECEIVE_PIN = 2; 

// Motor speed (Scale 0-255 for Arduino analogWrite)
const int MOTOR_SPEED = 200; 

// Variable to store the last successfully decoded command
byte lastCommand = 0;



const uint8_t FRAME_SIZE = 8 * 13; // 104 pixels for the LED matrix

// --- LED MATRIX ARROW ARRAYS (Brightness levels 0-7) ---
uint8_t arrow_up[FRAME_SIZE] = {
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 7, 7, 7, 7, 7, 0, 0, 0, 0,
    0, 0, 0, 7, 7, 0, 7, 0, 7, 7, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0
};

uint8_t arrow_down[FRAME_SIZE] = {
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 7, 7, 0, 7, 0, 7, 7, 0, 0, 0,
    0, 0, 0, 0, 7, 7, 7, 7, 7, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 7, 7, 7, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0
};

uint8_t arrow_left[FRAME_SIZE] = {
    0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    0, 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
    0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0
};

uint8_t arrow_right[FRAME_SIZE] = {
    0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 0,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0,
    7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0
};

uint8_t stop_icon[FRAME_SIZE] = { 0 }; 


// --- ONBOARD RGB LED CONTROL FUNCTIONS ---
void set_led3_color(int r, int g, int b) {
  analogWrite(LED3_R, r);
  analogWrite(LED3_G, g);
  analogWrite(LED3_B, b);
}

void set_led4_color(bool r, bool g, bool b) {
  digitalWrite(LED4_R, r ? LOW : HIGH);
  digitalWrite(LED4_G, g ? LOW : HIGH);
  digitalWrite(LED4_B, b ? LOW : HIGH);
}


// IR decoding function based exactly on Elecrow&amp;#39;s logic and timing
long readElecrowIR() {
  int count = 0;
  
  // 1. Wait for the leading LOW pulse
  while (digitalRead(IR_RECEIVE_PIN) == LOW &amp;amp;&amp;amp; count &amp;lt; 200) {
    count++;
    delayMicroseconds(60);
  }
  if (count &amp;gt;= 200) return -1;

  // 2. Wait for the leading HIGH space
  count = 0;
  while (digitalRead(IR_RECEIVE_PIN) == HIGH &amp;amp;&amp;amp; count &amp;lt; 80) {
    count++;
    delayMicroseconds(60);
  }
  if (count &amp;gt;= 80) return -1;

  // 3. Read 32 bits of payload data
  int idx = 0;
  int cnt = 0;
  byte data[4] = {0, 0, 0, 0};

  for (int i = 0; i &amp;lt; 32; i++) {
    count = 0;
    while (digitalRead(IR_RECEIVE_PIN) == LOW &amp;amp;&amp;amp; count &amp;lt; 15) {
      count++;
      delayMicroseconds(60);
    }
    
    count = 0;
    while (digitalRead(IR_RECEIVE_PIN) == HIGH &amp;amp;&amp;amp; count &amp;lt; 40) {
      count++;
      delayMicroseconds(60);
    }

    // If the HIGH pulse duration was long (count &amp;gt; 8), register bit as 1
    if (count &amp;gt; 8) {
      data[idx] |= (1 &amp;lt;&amp;lt; cnt);
    }

    if (cnt == 7) {
      cnt = 0;
      idx++;
    } else {
      cnt++;
    }
  }

  // 4. Check checksum integrity (matches Elecrow&amp;#39;s condition: data+data==0xFF)
  if ((byte)(data[0] + data[1]) == 0xFF &amp;amp;&amp;amp; (byte)(data[2] + data[3]) == 0xFF) {
    return data[2]; // Returns the valid command byte
  }

  return -1;
}

void driveMotors(int a1, int a2, int b1, int b2) {
  // Omit pinMode() in setup to keep PWM functional on the UNO Q Zephyr core
  analogWrite(MOTOR_PIN_A1, a1);
  analogWrite(MOTOR_PIN_A2, a2);
  analogWrite(MOTOR_PIN_B1, b1);
  analogWrite(MOTOR_PIN_B2, b2);
}

void executeCommand(byte command) {
  switch (command) {
    case 0x18: // Up Arrow - Forward
      driveMotors(0, MOTOR_SPEED, 0, MOTOR_SPEED);
      set_led4_color(false, true, false);  
      set_led3_color(0, 200, 0);           
      matrix.draw(arrow_up);
      break;
    case 0x08: // Left Arrow - Turn Left
      driveMotors(MOTOR_SPEED, 0, 0, MOTOR_SPEED);
      set_led4_color(false, false, true);  
      set_led3_color(0, 0, 200);           
      matrix.draw(arrow_left);
      break;
    case 0x5A: // Right Arrow - Turn Right
      driveMotors(0, MOTOR_SPEED, MOTOR_SPEED, 0);
    
      set_led4_color(false, false, true);  
      set_led3_color(0, 0, 200);           
      matrix.draw(arrow_right);
      
      break;
    case 0x52: // Down Arrow - Backward
      driveMotors(MOTOR_SPEED, 0, MOTOR_SPEED, 0);
      set_led4_color(true, false, false);  
      set_led3_color(200, 0, 0);           
      matrix.draw(arrow_down);
      break;
    default:
      driveMotors(0, 0, 0, 0); // Stop for unassigned keys
      set_led4_color(false, false, false); 
      set_led3_color(0, 0, 0);
      matrix.draw(stop_icon);
      
      break;
  }
}

void setup() {
  Serial.begin(115200);
  
  // Enable internal pull-up resistor (Equivalent to Pin.PULL_UP in MicroPython)
  pinMode(IR_RECEIVE_PIN, INPUT_PULLUP); 
  
  Serial.println(&amp;quot;Elecrow IR Decoder for Arduino UNO Q Ready.&amp;quot;);

  pinMode(LED4_R, OUTPUT); pinMode(LED4_G, OUTPUT); pinMode(LED4_B, OUTPUT);
  set_led3_color(0, 0, 0);
  set_led4_color(false, false, false);

  matrix.begin();
  matrix.setGrayscaleBits(3);
  matrix.clear();
  
}

void loop() {
  // Main loop logic translated 1:1 from the manufacturer&amp;#39;s MicroPython code
  if (digitalRead(IR_RECEIVE_PIN) == HIGH) {
    long result = readElecrowIR();
    
    if (result != -1) {
      lastCommand = (byte)result;
      Serial.print(&amp;quot;Retrieve key: 0x&amp;quot;);
      Serial.println(lastCommand, HEX);
    }
  }

  // Safety and control: if the IR pin is HIGH (no transmission), stop the robot.
  // Otherwise, continue executing the last received command.
  if (digitalRead(IR_RECEIVE_PIN) == LOW) {
    driveMotors(0, 0, 0, 0); // Stop
  } else {
    executeCommand(lastCommand);
  }
  
  delay(10); // Small delay for main loop stabilization
}&lt;/pre&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-hveid="CAAICxAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-hveid="CAAICxAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;
&lt;div data-sfc-cp="" data-sfc-root="ep" data-hveid="CAAICxAA" data-complete="true" data-processed="true" data-copy-service-computed-style="font-family: Google Sans, Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 12px 0px 16px; text-decoration: none; border-bottom: 0px rgb(10, 10, 10);"&gt;&lt;/div&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;</description></item><item><title>SmartAssist EV - Hardware Platform - Part 2</title><link>https://community.element14.com/thread/57189?ContentTypeID=0</link><pubDate>Sun, 16 Aug 2026 22:47:23 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:e4825904-f9e3-4b66-9d0e-9a8217b12371</guid><dc:creator>jelektro</dc:creator><slash:comments>1</slash:comments><comments>https://community.element14.com/thread/57189?ContentTypeID=0</comments><wfw:commentRss>https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57189/smartassist-ev---hardware-platform---part-2/rss?ContentTypeId=0</wfw:commentRss><description>&lt;p&gt;&lt;span class="user-query-container" style="font-size:150%;"&gt;&lt;span class="user-query-bubble-with-background ng-star-inserted" data-test-id="luminous-collapsed-bubble"&gt;&lt;span class="horizontal-container ng-star-inserted"&gt;Project Roadmap&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Experimental Smart Assistive Platform for Elderly and Disabled People - Part 1" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57188/smartassist-ev-experimental-smart-assistive-platform-for-elderly-and-disabled-people---part-1" data-e14adj="t"&gt;Part 1&lt;/a&gt;&lt;a title="Experimental Smart Assistive Platform for Elderly and Disabled People - Part 1" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57188/smartassist-ev-experimental-smart-assistive-platform-for-elderly-and-disabled-people---part-1" data-e14adj="t"&gt;&amp;nbsp;- Experimental Smart Assistive Platform for Elderly and Disabled People&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Hardware Platform - Part 2" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57189/smartassist-ev---hardware-platform---part-2" data-e14adj="t"&gt;Part 2&lt;/a&gt;&lt;a title="Hardware Platform - Part 2" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57189/smartassist-ev---hardware-platform---part-2" data-e14adj="t"&gt;&amp;nbsp;- Hardware Platform&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Wireless Command and H-Bridge Direct Drive - Part 3" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57190/smartassist-ev---wireless-command-and-h-bridge-direct-drive---part-3" data-e14adj="t"&gt;Part 3&lt;/a&gt;&lt;a title="Wireless Command and H-Bridge Direct Drive - Part 3" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57190/smartassist-ev---wireless-command-and-h-bridge-direct-drive---part-3" data-e14adj="t"&gt;&amp;nbsp;- Wireless Command and H-Bridge Direct Drive&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Part 4 - Introducing Autonomous Line Following (TCRT5000)" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57191/smartassist-ev---introducing-autonomous-line-following-tcrt5000---part-4" data-e14adj="t"&gt;Part 4 - Introducing Autonomous Line Following (TCRT5000)&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Part 5 - Non-Contact Proactive Shielding (HC-SR04 Range Finder)" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57194/smartassist-ev---non-contact-proactive-shielding-hc-sr04-range-finder---part-5" data-e14adj="t"&gt;Part 5 - Non-Contact Proactive Shielding (HC-SR04 Range Finder)&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Part 6 - Strict Priority Hierarchy with Tactile Mechanical Bumpers" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57196/smartassist-ev---strict-priority-hierarchy-with-tactile-mechanical-bumpers---part-6" data-e14adj="t"&gt;Part 6 - Strict Priority Hierarchy with Tactile Mechanical Bumpers&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Part 7 - Mobile Robot Control and Live Video Streaming" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57198/smartassist-ev---mobile-robot-control-and-live-video-streaming---part-7" data-e14adj="t"&gt;Part 7 - Mobile Robot Control and Live Video Streaming&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;SmartAssist EV (Experimental Smart Assistive Platform for Elderly and Disabled People) is an experimental mobile platform designed to support elderly people and people with disabilities. The system is based on the vehicle platform from the Pico with 32 Modules &amp;amp; Projects: Advanced Kit. However, the Raspberry Pi Pico is not used in the project. Instead, the Arduino UNO Q serves as the main control and computing unit.&lt;br /&gt;&lt;br /&gt;Only selected components from the kit are used, primarily the vehicle platform, H-bridges, and sensors. The remaining modules included in the kit are not required for the basic version of the system.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:150%;"&gt;1. Mobile Platform&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The physical foundation of SmartAssist EV is a small mobile vehicle platform equipped with drive motors and wheels. The platform provides mobility and serves as the mechanical base for the assistive system.&lt;br /&gt;&lt;br /&gt;The vehicle can perform basic movements, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;moving forward,&lt;/li&gt;
&lt;li&gt;moving backward,&lt;/li&gt;
&lt;li&gt;turning left,&lt;/li&gt;
&lt;li&gt;turning right,&lt;/li&gt;
&lt;li&gt;stopping,&lt;/li&gt;
&lt;li&gt;adjusting the speed of individual motors.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/2_2D00_4.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;The mobile design allows SmartAssist EV to function not only as a stationary monitoring system but also as a robotic assistive platform capable of responding to its environment.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:150%;"&gt;2. Arduino UNO Q&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The central component of the system is the Arduino UNO Q. In the original kit, the Raspberry Pi Pico is used as the main controller; however, in SmartAssist EV, the Pico is replaced by the UNO Q.&lt;br /&gt;&lt;br /&gt;The Arduino UNO Q is responsible for coordinating the operation of the entire system, including:&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span style="font-size:inherit;"&gt;reading data from sensors,&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="font-size:inherit;"&gt;processing environmental information,&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="font-size:inherit;"&gt;controlling the vehicle&amp;#39;s drive system,&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="font-size:inherit;"&gt;generating control signals for the H-bridges,&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="font-size:inherit;"&gt;implementing autonomous operation logic,&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="font-size:inherit;"&gt;communicating with additional system components,&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style="font-size:inherit;"&gt;implementing experimental intelligent-assistance functions.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The use of the UNO Q provides a foundation for further development toward more advanced data processing and algorithms supporting autonomous decision-making.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:150%;"&gt;3. H-Bridges&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The H-bridges included in the kit are used to control the drive motors. Their purpose is to provide an interface between the Arduino UNO Q and the motors.&lt;br /&gt;&lt;br /&gt;The Arduino UNO Q generates the control signals, while the H-bridges provide the appropriate electrical control of the motors. This makes it possible to control the direction of motor rotation and, depending on the particular driver and control method, the motor speed as well.&lt;br /&gt;&lt;br /&gt;The basic drive architecture can be represented as:&lt;br /&gt;&lt;br /&gt;Arduino UNO Q &amp;rarr; H-bridge &amp;rarr; motors &amp;rarr; vehicle wheels&lt;br /&gt;&lt;br /&gt;This approach separates the low-power logic circuitry from the motor power stage and prevents the Arduino board from having to drive the motors directly.&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/2_2D00_5.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:150%;"&gt;4. Sensors&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Another important part of SmartAssist EV is the set of sensors used from the kit. Their purpose is to provide the Arduino UNO Q with information about the vehicle&amp;#39;s current state and its surrounding environment.&lt;br /&gt;&lt;br /&gt;Depending on the particular sensors used, they can provide capabilities such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;obstacle detection,&lt;/li&gt;
&lt;li&gt;distance measurement,&lt;/li&gt;
&lt;li&gt;object or presence detection,&lt;/li&gt;
&lt;li&gt;environmental monitoring,&lt;/li&gt;
&lt;li&gt;providing information required for navigation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The sensor data is processed by the UNO Q and can be used to determine the vehicle&amp;#39;s next action.&lt;br /&gt;&lt;br /&gt;For example, when an obstacle is detected in front of the vehicle, the system can stop the drive, change its direction, or initiate an obstacle-avoidance procedure.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/2_2D00_1.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/2_2D00_2.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/2_2D00_3.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style="font-size:150%;"&gt;5. Hardware Architecture&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;The basic hardware architecture of SmartAssist EV consists of three main blocks:&lt;br /&gt;&lt;br /&gt;Sensors &amp;rarr; Arduino UNO Q &amp;rarr; H-bridges &amp;rarr; Motors&lt;br /&gt;&lt;br /&gt;The Arduino UNO Q is located at the center of the architecture. It receives data from the sensors, interprets the information, and generates appropriate control signals for the drive system.&lt;br /&gt;&lt;br /&gt;The system can therefore be divided into two main layers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Perception Layer&lt;br /&gt;&lt;br /&gt;This layer consists of sensors responsible for observing the vehicle&amp;#39;s environment. They provide the input data required for spatial awareness and detection of potential hazards.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;Actuation Layer&lt;br /&gt;&lt;br /&gt;This layer consists of the Arduino UNO Q, H-bridges, and motors. Based on information received from the sensors, the system determines the appropriate vehicle behavior and implements it by controlling the motors.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/2_2D00_11.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/2_2D00_10.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/2_2D00_12.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;SmartAssist EV is not intended to be a finished medical device, but rather an experimental research and development platform. Its modular architecture allows additional functions to be introduced without fundamentally changing the basic vehicle structure.&lt;br /&gt;&lt;br /&gt;The current version focuses on the integration of the mobile vehicle platform, sensors, drive system, and Arduino UNO Q. This configuration provides a foundation for further development of autonomous and assistive functions.&lt;br /&gt;&lt;br /&gt;In the future, the platform can be extended with additional sensors, communication systems, user interfaces, audio and visual signaling, and more advanced environmental-analysis mechanisms.&lt;/p&gt;
&lt;p&gt;The SmartAssist EV hardware uses only selected components from the Pico with 32 Modules &amp;amp; Projects: Advanced Kit, namely the vehicle platform, drive system, H-bridges, and sensors. The Raspberry Pi Pico is not used in the project; instead, it is replaced by the Arduino UNO Q, which serves as the central controller.&lt;br /&gt;&lt;br /&gt;This architecture combines vehicle mobility with environmental sensing and intelligent control, providing a foundation for an experimental assistive system designed to support elderly people and people with disabilities.&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;</description></item><item><title>SmartAssist EV – Experimental Smart Assistive Platform for Elderly and Disabled People - Part 1</title><link>https://community.element14.com/thread/57188?ContentTypeID=0</link><pubDate>Sun, 16 Aug 2026 22:27:45 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:829e8381-7e51-48d6-b8c7-75e93add6e6b</guid><dc:creator>jelektro</dc:creator><slash:comments>1</slash:comments><comments>https://community.element14.com/thread/57188?ContentTypeID=0</comments><wfw:commentRss>https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57188/smartassist-ev-experimental-smart-assistive-platform-for-elderly-and-disabled-people---part-1/rss?ContentTypeId=0</wfw:commentRss><description>&lt;p&gt;&lt;span class="user-query-container" style="font-size:150%;"&gt;&lt;span class="user-query-bubble-with-background ng-star-inserted" data-test-id="luminous-collapsed-bubble"&gt;&lt;span class="horizontal-container ng-star-inserted"&gt;Project Roadmap&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Experimental Smart Assistive Platform for Elderly and Disabled People - Part 1" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57188/smartassist-ev-experimental-smart-assistive-platform-for-elderly-and-disabled-people---part-1" data-e14adj="t"&gt;Part 1&lt;/a&gt;&lt;a title="Experimental Smart Assistive Platform for Elderly and Disabled People - Part 1" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57188/smartassist-ev-experimental-smart-assistive-platform-for-elderly-and-disabled-people---part-1" data-e14adj="t"&gt;&amp;nbsp;- Experimental Smart Assistive Platform for Elderly and Disabled People&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Hardware Platform - Part 2" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57189/smartassist-ev---hardware-platform---part-2" data-e14adj="t"&gt;Part 2&lt;/a&gt;&lt;a title="Hardware Platform - Part 2" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57189/smartassist-ev---hardware-platform---part-2" data-e14adj="t"&gt;&amp;nbsp;- Hardware Platform&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Wireless Command and H-Bridge Direct Drive - Part 3" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57190/smartassist-ev---wireless-command-and-h-bridge-direct-drive---part-3" data-e14adj="t"&gt;Part 3&lt;/a&gt;&lt;a title="Wireless Command and H-Bridge Direct Drive - Part 3" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57190/smartassist-ev---wireless-command-and-h-bridge-direct-drive---part-3" data-e14adj="t"&gt;&amp;nbsp;- Wireless Command and H-Bridge Direct Drive&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Part 4 - Introducing Autonomous Line Following (TCRT5000)" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57191/smartassist-ev---introducing-autonomous-line-following-tcrt5000---part-4" data-e14adj="t"&gt;Part 4 - Introducing Autonomous Line Following (TCRT5000)&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Part 5 - Non-Contact Proactive Shielding (HC-SR04 Range Finder)" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57194/smartassist-ev---non-contact-proactive-shielding-hc-sr04-range-finder---part-5" data-e14adj="t"&gt;Part 5 - Non-Contact Proactive Shielding (HC-SR04 Range Finder)&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Part 6 - Strict Priority Hierarchy with Tactile Mechanical Bumpers" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57196/smartassist-ev---strict-priority-hierarchy-with-tactile-mechanical-bumpers---part-6" data-e14adj="t"&gt;Part 6 - Strict Priority Hierarchy with Tactile Mechanical Bumpers&lt;/a&gt;&lt;/p&gt;
&lt;p data-path-to-node="2"&gt;&lt;a title="Part 7 - Mobile Robot Control and Live Video Streaming" href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57198/smartassist-ev---mobile-robot-control-and-live-video-streaming---part-7" data-e14adj="t"&gt;Part 7 - Mobile Robot Control and Live Video Streaming&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The development of intelligent transportation systems is becoming increasingly important, especially in areas related to healthcare, elderly care, and assistive technologies. Autonomous mobility solutions may one day help people with limited mobility move safely and independently inside hospitals, rehabilitation centers, nursing homes, or smart buildings. Before such systems can be implemented in real-life environments, their control algorithms, safety mechanisms, and sensor systems must first be tested on smaller experimental platforms. The SmartAssist EV project was created with this purpose in mind.&lt;br /&gt;&lt;br /&gt;SmartAssist EV is not intended to be a full-sized transport vehicle, but rather an experimental prototype platform designed for testing autonomous navigation, obstacle detection, and smart mobility algorithms. The project is based on a compact DIY DC&amp;nbsp; Smart Robot Car Chassis equipped with magnetic speed encoders. Although small in size, the platform is capable of carrying lightweight objects such as a cup of tea, medicine, or small personal items. This makes it ideal for demonstrating assistive transport concepts in a safe and affordable way.&lt;br /&gt;&lt;br /&gt;The vehicle is controlled by the Arduino UNO Q SBC, which acts as the main processing unit of the system. Motor control is handled using the L9110 PWM motor drivers, allowing smooth speed regulation, controlled acceleration, and directional movement of the two DC motors. The magnetic wheel encoders provide feedback about wheel rotation and traveled distance, enabling the platform to follow predefined movement paths with improved precision.&lt;br /&gt;&lt;br /&gt;A major focus of the project is safety and environmental awareness. To achieve this, the prototype uses a multi-sensor obstacle detection system based on ultrasonic, infrared (IR) and collision sensors. Each sensing technology provides different advantages and helps improve the reliability of the platform.&lt;br /&gt;&lt;br /&gt;Ultrasonic sensors measure the distance to nearby obstacles and help prevent collisions with walls, furniture, or people. Infrared sensors are especially useful for detecting edges, stairs, or low obstacles close to the floor surface.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;The data collected from all sensors is processed by the Arduino UNO Q SBC, creating a simple sensor fusion system. This allows the platform to intelligently react to its surroundings by slowing down, stopping, changing direction, or warning the operator when obstacles are detected. The project therefore serves as a practical environment for testing mobility-related safety algorithms and autonomous driving logic.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p style="line-height:100%;margin-bottom:0cm;"&gt;Main Vehicle Features:&lt;/p&gt;
&lt;p style="line-height:100%;margin-bottom:0cm;"&gt;1. Remote Control Operation&lt;/p&gt;
&lt;p style="line-height:100%;margin-bottom:0cm;"&gt;The user or caregiver can take full control of the vehicle using a wireless remote controller. This feature increases safety and allows manual obstacle avoidance or route modification whenever needed.&lt;/p&gt;
&lt;p style="line-height:100%;margin-bottom:0cm;"&gt;2. Advanced Obstacle Detection System&lt;/p&gt;
&lt;p style="line-height:100%;margin-bottom:0cm;"&gt;SmartAssist EV uses a multi-sensor safety system based on ultrasonic, infrared (IR), and collision sensors. By combining multiple sensing technologies, the vehicle can effectively detect obstacles and react in real time.&lt;/p&gt;
&lt;p style="line-height:100%;margin-bottom:0cm;"&gt;Ultrasonic sensors are responsible for measuring the distance to obstacles located in front of and beside the vehicle.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Infrared sensors are used to detect:&lt;/p&gt;
&lt;ul&gt;
&lt;li style="line-height:100%;margin-bottom:0cm;"&gt;edges,&lt;/li&gt;
&lt;li style="line-height:100%;margin-bottom:0cm;"&gt;stairs,&lt;/li&gt;
&lt;li style="line-height:100%;margin-bottom:0cm;"&gt;small obstacles,&lt;/li&gt;
&lt;li style="line-height:100%;margin-bottom:0cm;"&gt;objects located close to the ground.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style="line-height:100%;margin-bottom:0cm;"&gt;This solution significantly improves user safety, especially in indoor environments.&lt;/p&gt;
&lt;p style="line-height:100%;margin-bottom:0cm;"&gt;The Arduino UNO Q SBC processes data from all sensors simultaneously, creating a basic sensor fusion system. This allows the vehicle to:&lt;/p&gt;
&lt;ul&gt;
&lt;li style="line-height:100%;margin-bottom:0cm;"&gt;make more accurate decisions,&lt;/li&gt;
&lt;li style="line-height:100%;margin-bottom:0cm;"&gt;reduce false alarms,&lt;/li&gt;
&lt;li style="line-height:100%;margin-bottom:0cm;"&gt;improve driving safety,&lt;/li&gt;
&lt;li style="line-height:100%;margin-bottom:0cm;"&gt;automatically adjust navigation paths.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/5482.SmartAssist-EV.jpg" /&gt;&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;</description></item><item><title>DockBot - Part 5 - The Mechanical Hand</title><link>https://community.element14.com/thread/57187?ContentTypeID=0</link><pubDate>Sun, 16 Aug 2026 17:55:55 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:20380f14-87f3-4cc9-912e-e73b1dc879e4</guid><dc:creator>arvindsa</dc:creator><slash:comments>2</slash:comments><comments>https://community.element14.com/thread/57187?ContentTypeID=0</comments><wfw:commentRss>https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57187/dockbot---part-5---the-mechanical-hand/rss?ContentTypeId=0</wfw:commentRss><description>&lt;h2 id="mcetoc_1jvl2tclo0"&gt;Recap&lt;/h2&gt;
&lt;p&gt;I am building a robotic system that identifies the charging port on an EV and automatically moves a charger arm to plug the charger in.&lt;/p&gt;
&lt;p&gt;Past Forum Posts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57056/dockbot---part-1---the-concept" data-e14adj="t"&gt;DockBot - Part 1 - The Concept&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57069/dockbot---part-2---positioning-with-aruco-markers" data-e14adj="t"&gt;DockBot - Part 2 - Positioning with Aruco Markers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/p/addpost/community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57142/dockbot---part-3---new-plan-new-hardware-for-better-sensing?COM=e14c-direct-ugc&amp;amp;CMP=e14c-direct-ugc&amp;amp;osetc=e14c-direct-ugc" target="_blank" data-e14adj="t"&gt;DockBot - Part 3 - New Plan, New Hardware for Better Sensing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57162/dockbot---part-4---getting-the-arduino-q-to-move-the-tank-motors" data-e14adj="t"&gt;DockBot - Part 4 - Getting the Arduino Q to move the tank motors&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is going to be a short post, the kind you write finishing your assignment on the eve of the deadline. We have a robot base, we have a guidance system - what&amp;#39;s left (mechanically) is an actuator to actually place a mock charger on the car.&amp;nbsp;&lt;/p&gt;
&lt;h2 id="mcetoc_1k05pdqq40"&gt;The actuator design&lt;/h2&gt;
&lt;p&gt;With the platform able to move itself around the floor, all I needed was an actuator that could lift and position the mock charger at different heights. That height adjustment matters because different car models have their charging port at different heights, and the mock charging dock on the car side won&amp;#39;t always be identical either.&lt;/p&gt;
&lt;p&gt;I found a design that did exactly this at &lt;a href="https://www.thingiverse.com/thing:5225652/files" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;thingiverse.com/thing:5225652&lt;/a&gt; - credit for that goes to SnakeP (&lt;a href="https://www.thingiverse.com/SnakeP/designs" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;thingiverse.com/SnakeP&lt;/a&gt;). It worked brilliantly, and mounting it on the Devastator platform was painless.&lt;/p&gt;
&lt;p&gt;&lt;img alt="image" style="display:block;margin-left:auto;margin-right:auto;max-height:450px;max-width:800px;"  src="https://community.element14.com/resized-image/__size/1600x900/__key/communityserver-discussions-components-files/456/pastedimage1786900862605v1.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;I was able to lift around 300gms safely. I dare not try more, as I printed in the parts in the default infill of 15% and I do not have the time to reprint it if I broke it just to see how much weight it can lift.&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" alt="image" style="display:block;margin-left:auto;margin-right:auto;max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/20260813_5F00_144929.gif" /&gt;&lt;/p&gt;
&lt;p&gt;.&lt;/p&gt;
&lt;h2 id="mcetoc_1k05q8sat5"&gt;The mock charger design&lt;/h2&gt;
&lt;p&gt;I wanted the charger itself to be forgiving of small positioning errors - remember Part 3 measured up to 3% error on positioning at range. So I leaned into a &lt;a href="https://en.wikipedia.org/wiki/Poka-yoke" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;poka-yoke design&lt;/a&gt;&amp;nbsp;(Mistake Proofing) : the charger is made from a length of PVC pipe with a flange on each end. Eventually I&amp;#39;ll add a magnet so it sticks to the car once docked. For the robot to actually grab it, I gave the handle two chamfered collars, so the geometry itself nudges the handle into alignment even if the robot&amp;#39;s approach isn&amp;#39;t perfectly centered.&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" alt="image" style="display:block;margin-left:auto;margin-right:auto;max-height:450px;max-width:800px;"  src="https://community.element14.com/resized-image/__size/1600x900/__key/communityserver-discussions-components-files/456/20260816_5F00_225853_2800_1_2900_.jpg" /&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1k05q8sat6"&gt;The grabber design&lt;/h2&gt;
&lt;p&gt;The grabber is four fingers with a wide opening - essentially a V shape that guides the handle into the locking area, oriented perpendicular to the charging handle itself. Between the chamfered collar and the V fingers, the mechanism forgives about &amp;plusmn;1.8cm of positioning error.&lt;/p&gt;
&lt;p&gt;I also designed a small dock for the charger to rest in when it&amp;#39;s not &amp;quot;charging a car.&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" alt="image" style="display:block;margin-left:auto;margin-right:auto;max-height:450px;max-width:800px;"  src="https://community.element14.com/resized-image/__size/1600x900/__key/communityserver-discussions-components-files/456/20260816_5F00_225905_2800_1_2900_.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://community.element14.com/cfs-file/__key/communityserver-discussions-components-files/456/p5_2D00_v1.mp4"&gt;community.element14.com/.../p5_2D00_v1.mp4&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1k05q8sat8"&gt;Final Notes&lt;/h2&gt;
&lt;p&gt;The results worked brilliantly in my hand. Now I hope things don&amp;#39;t fall apart tomorrow when i integrate everything. I still have not received the diametrically magnetized magnet for my encoder. So I will have to improvise (again???, This entire projects had way too many improvisations)&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;</description></item><item><title>Vape Cell EV - part III - INA219 power monitor</title><link>https://community.element14.com/thread/57186?ContentTypeID=0</link><pubDate>Sun, 16 Aug 2026 15:06:25 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:3fae09d9-a4c5-4b2b-a390-ae74673109a5</guid><dc:creator>saramic</dc:creator><slash:comments>1</slash:comments><comments>https://community.element14.com/thread/57186?ContentTypeID=0</comments><wfw:commentRss>https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57186/vape-cell-ev---part-iii---ina219-power-monitor/rss?ContentTypeId=0</wfw:commentRss><description>&lt;p&gt;Our consumerist society throws out millions of rechargeable vape cells, but the more I read about it sounds like lithium batteries should explode all the time.&lt;/p&gt;
&lt;h1 id="recap"&gt;Recap&lt;/h1&gt;
&lt;p&gt;Vape Cell EV is the idea of a smart cell charging system that can handle cells with unknown histories, charging, power distribution, any safety issues and ultimately if a cell should be removed from a battery of many cells.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57077/vape-cell-ev---part-i---what-s-in-a-vape" data-e14adj="t"&gt;Vape Cell EV - part I - What&amp;rsquo;s in a Vape&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57148/vape-cell-ev---part-ii---rs485-comms" data-e14adj="t"&gt;Vape Cell EV - part II - RS485 comms&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="ina219"&gt;INA219&lt;/h2&gt;
&lt;p&gt;To characterise a cell you need at least three things: voltage, current, and time. Voltage alone tells you roughly where you are on the discharge curve. Current alone tells you the load. Put them together over time and you get energy in milliwatt-hours &amp;mdash; which is the actual capacity number you are trying to measure. The INA219 gives you all three from a single cheap I2C chip.&lt;/p&gt;
&lt;p&gt;It works by sitting in series with the circuit. A small shunt resistor &amp;mdash; 0.1 &amp;Omega; on the standard breakout module &amp;mdash; is placed between VIN+ and VIN&amp;minus;. The INA219 measures the tiny voltage drop across that shunt (10 mV per amp at 0.1 &amp;Omega;) and from that calculates current. Simultaneously it measures the bus voltage: the voltage at VIN&amp;minus; relative to GND, which is the voltage the load actually sees. The series resistance is small enough that it barely affects the circuit &amp;mdash; 0.1 &amp;Omega; in series with a 10 &amp;Omega; load is less than 1% overhead.&lt;/p&gt;
&lt;p&gt;The first real test was a known 18650 cell discharged through a 10 &amp;Omega; / 5 W resistor: the INA219 read&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;strong&gt;~385 mA, ~3.87 V, ~1420 mW&lt;/strong&gt;. The resistor got warm, as expected &amp;mdash; 1.4 W in a 5 W part is entirely within rating. The session tracking in the dashboard accumulates mWh in real time, which means leaving it running through a full discharge gives you the cell capacity directly, without any manual timing or maths.&lt;/p&gt;
&lt;h2 id="wirebegin-isnt-optional"&gt;Wire.begin() isn&amp;rsquo;t optional&lt;/h2&gt;
&lt;p&gt;On this platform,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;Wire2.begin()&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;isn&amp;rsquo;t just good practice &amp;mdash; without it, every I2C call fails immediately regardless of wiring. The Zephyr arduino port uses lazy initialisation: the hardware peripheral doesn&amp;rsquo;t start until you explicitly call&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;begin()&lt;/code&gt;. Nothing about the error messages hints at this; everything just returns 1.&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;void setup() {
    Bridge.begin();
    Wire2.begin();   // i2c3: A4=SDA, A5=SCL
    // ...
}&lt;/pre&gt;&lt;/p&gt;
&lt;h2 id="current-without-voltage"&gt;Current without voltage&lt;/h2&gt;
&lt;p&gt;First real test with an 18650 across a 10 &amp;Omega; resistor: current read correctly at about 196 mA, but bus voltage was 0.012 V &amp;mdash; effectively zero. The fix was obvious once the INA219&amp;rsquo;s measurement model clicked: bus voltage is VIN&amp;minus;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;em&gt;relative to GND&lt;/em&gt;, so if the battery&amp;rsquo;s negative terminal isn&amp;rsquo;t connected to the INA219&amp;rsquo;s GND pin, the reference floats. Current measurement is differential (VIN+ minus VIN&amp;minus;) and works regardless &amp;mdash; voltage measurement doesn&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;Once a wire ran from the battery negative node to GND: 3.87 V, 385 mA, 1420 mW.&lt;/p&gt;
&lt;h2 id="a-silent-json-truncation"&gt;A silent JSON truncation&lt;/h2&gt;
&lt;p&gt;With everything reading correctly on the MCU, the web dashboard was stuck on NO LINK. The Python side was receiving data but failing to parse it:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="text"&gt;[bridge/config] Expecting &amp;#39;,&amp;#39; delimiter: line 1 column 29 (char 28)&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;Tracing back through the JSON the sketch was producing, the config handler built the sensor address string into a fixed buffer:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;char addrStr[6];
snprintf(addrStr, sizeof(addrStr), &amp;quot;\&amp;quot;0x%02X\&amp;quot;&amp;quot;, sensors[i].addr);&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;&amp;quot;0x40&amp;quot;&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;is six characters plus a null terminator &amp;mdash; seven bytes. The&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;snprintf&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;quietly truncated the closing&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;&amp;quot;&lt;/code&gt;, leaving the JSON&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;["0x40,"rShunt":...]&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;mdash; which is a parse error exactly at character 28. Bumping the buffer to&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;char addrStr[8]&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;fixed it.&lt;/p&gt;
&lt;h2 id="the-display"&gt;The display&lt;/h2&gt;
&lt;p&gt;With readings flowing, there&amp;rsquo;s a live dashboard served directly from the UNO Q&amp;rsquo;s MPU:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Per-sensor cards showing bus voltage (amber), current (green), power&lt;/li&gt;
&lt;li&gt;Dual-axis sparklines with fixed scales &amp;mdash; 0&amp;ndash;6 V and 0&amp;ndash;500 mA &amp;mdash; so a steady reading looks like a flat line instead of thrashing wall to wall&lt;/li&gt;
&lt;li&gt;Live CSV logging and a download button&lt;/li&gt;
&lt;li&gt;Session tracking: name a trace, press START, watch mWh accumulate in real time&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="image" style="max-height:360px;max-width:640px;" src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/20260816_5F00_INA219_5F00_discharge_5F00_dashboard.png"  /&gt;&lt;/p&gt;
&lt;h2 id="next"&gt;Next&lt;/h2&gt;
&lt;p&gt;Actual cell characterisation &amp;mdash; charge a known 18650 to 4.2 V, discharge it through a measured load down to 3.0 V, and get a real capacity number. Then do the same with salvaged vape cells and see how they compare.&lt;/p&gt;
&lt;h2 id="source"&gt;Source&lt;/h2&gt;
&lt;p&gt;&lt;a id="" href="https://github.com/saramic/vape-cell-EV" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;https://github.com/saramic/vape-cell-EV&lt;/a&gt;&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;</description></item><item><title>hall-w-EV - Post 5 - Chase ball</title><link>https://community.element14.com/thread/57185?ContentTypeID=0</link><pubDate>Sun, 16 Aug 2026 13:29:31 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:379032eb-dc9a-493d-ad2f-f249251e78ab</guid><dc:creator>tamadillo</dc:creator><slash:comments>2</slash:comments><comments>https://community.element14.com/thread/57185?ContentTypeID=0</comments><wfw:commentRss>https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57185/hall-w-ev---post-5---chase-ball/rss?ContentTypeId=0</wfw:commentRss><description>&lt;p&gt;Hi again, it&amp;rsquo;s&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://github.com/hambreros" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;Hambreros&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;and&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://github.com/tamadillo" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;Tamadillo&lt;/a&gt;. Last post&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57184/hall-w-ev---post-4---giving-it-eyes" data-e14adj="t"&gt;gave it eyes&lt;/a&gt;. This one gives it something to do with them: place a tennis ball&amp;nbsp;&lt;span class="emoticon" data-url="https://community.element14.com/cfs-file/__key/system/emoji/1f3be.svg" title="Tennis"&gt;&amp;#x1f3be;&lt;/span&gt;&amp;nbsp;in front of it and the robot drives itself towards it, no hands.&lt;/p&gt;
&lt;h2 id="what-weve-actually-built"&gt;What we&amp;rsquo;ve actually built&lt;/h2&gt;
&lt;p&gt;A&amp;nbsp;&lt;span class="emoticon" data-url="https://community.element14.com/cfs-file/__key/system/emoji/1f3be.svg" title="Tennis"&gt;&amp;#x1f3be;&lt;/span&gt;&amp;nbsp;CHASE&amp;nbsp;toggle next to the joystick. Flip it on, and:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The robot looks for a ball in the camera feed.&lt;/li&gt;
&lt;li&gt;Off-center &amp;rarr; it turns towards it.&lt;/li&gt;
&lt;li&gt;Small (far away) &amp;rarr; it drives forward. Big enough (close) &amp;rarr; it eases off and stops.&lt;/li&gt;
&lt;li&gt;Ball out of frame &amp;rarr; it just stops, same as letting go of the joystick.&lt;/li&gt;
&lt;li&gt;Manual controls (joystick, keyboard, per-wheel sliders) go greyed-out and unresponsive while this is on, so nothing&amp;rsquo;s fighting the robot for the wheel. The ⏻ MOTOR ON/OFF buttons still work regardless &amp;mdash; always an independent kill switch, whoever&amp;rsquo;s driving.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="finding-the-right-brick"&gt;Finding the right brick&lt;/h2&gt;
&lt;p&gt;Same move as every other feature so far: before writing a line of code, went and read&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://github.com/arduino/app-bricks-py" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;arduino/app-bricks-py&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;to see what already exists. Turns out there&amp;rsquo;s a whole family of vision bricks &amp;mdash;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;gesture_recognition&lt;/code&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;mood_detector&lt;/code&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;image_classification&lt;/code&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;object_detection&lt;/code&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;visual_anomaly_detection&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;mdash; and the one that actually fits is&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;video_objectdetection&lt;/code&gt;: continuous detection off a live camera stream, with per-label callbacks carrying a confidence score and a bounding box.&lt;/p&gt;
&lt;p&gt;Its&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;brick_config.yaml&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;lists:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="python"&gt;model_by_boards:
    - platform: ventunoq
      model: yolox-qnn-object-detection
    - platform: unoq
      model: yolox-object-detection&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;UNO Q&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;mdash; this exact board. Not a VENTUNO-only NPU thing, unlike the neural TTS detour a couple posts back. Genuinely usable here.&lt;/p&gt;
&lt;h2 id="no-training-required--just-check-the-label-list"&gt;No training required &amp;mdash; just check the label list&lt;/h2&gt;
&lt;p&gt;The obvious worry: does a generic pretrained model know what a tennis ball is? Didn&amp;rsquo;t want to assume, so went and checked&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://github.com/arduino/app-bricks-py/blob/main/models/models-list.yaml" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;models/models-list.yaml&lt;/code&gt;&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;in the same repo before writing any detection code &amp;mdash; it&amp;rsquo;s a YOLOX-Nano model trained on COCO&amp;rsquo;s 80 classes, and the label list includes, verbatim:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="python"&gt;- sports ball&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;That&amp;rsquo;s the actual class name (there&amp;rsquo;s no separate &amp;ldquo;tennis ball&amp;rdquo; class in COCO, but &amp;ldquo;sports ball&amp;rdquo; covers it &amp;mdash; it&amp;rsquo;s the canonical example object for that class in the dataset). So: zero custom training, zero Edge Impulse model work. Just register a callback for a class that&amp;rsquo;s already in the box.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;pre class="ui-code" data-mode="python"&gt;from arduino.app_bricks.video_objectdetection import VideoObjectDetection

detector = VideoObjectDetection(camera=shared_camera, confidence=0.5)
detector.on_detect(&amp;quot;sports ball&amp;quot;, on_ball_detected)
detector.start()&lt;/pre&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 id="one-camera-two-features-fighting-over-it"&gt;One camera, two features fighting over it&lt;/h2&gt;
&lt;p&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;video_objectdetection&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;wants its own&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;Camera&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;to forward frames to the detection sidecar. We already have one open, for&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57184/hall-w-ev---post-4---giving-it-eyes" data-e14adj="t"&gt;last post&amp;rsquo;s live feed&lt;/a&gt;. Tried to hand-wave past this and it immediately mattered:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;Camera&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;claims its physical device the moment it&amp;rsquo;s constructed &amp;mdash; there&amp;rsquo;s an actual registry in the framework&amp;rsquo;s own source specifically so auto-selection doesn&amp;rsquo;t grab something already in use &amp;mdash; so a second, independent&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;Camera(&amp;quot;usb:0&amp;quot;, ...)&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;for the same webcam wouldn&amp;rsquo;t just contend for bandwidth, it&amp;rsquo;d fail outright.&lt;/p&gt;
&lt;p&gt;Good thing&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;VideoObjectDetection(camera=...)&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;takes an existing instance instead of always making its own. Added a small accessor to&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;camera.py:&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="python"&gt;def get_camera():
    &amp;quot;&amp;quot;&amp;quot;Blocks until the startup attempt above has settled, then returns the
    shared Camera instance — or None if it never started successfully.&amp;quot;&amp;quot;&amp;quot;
    _ready.wait()
    return _camera&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;vision.py&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;calls that instead of constructing its own, so both features share the one physical connection to the one webcam instead of racing for it.&lt;/p&gt;
&lt;h2 id="steering-is-just-proportional-control"&gt;Steering is just proportional control&lt;/h2&gt;
&lt;p&gt;No path planning, no PID tuning, nothing fancy &amp;mdash; just &amp;ldquo;how far off-center is it&amp;rdquo; and &amp;ldquo;how big is it,&amp;rdquo; recomputed fresh on every detection message:&lt;/p&gt;
&lt;blockquote style="border-left:4px solid #f2a93b;color:#4a4e4d;font-style:italic;margin-left:0;padding:2px 16px;"&gt;
&lt;p&gt;PID - Proportional Kp, Integral Ki, and Derivative Kd, only recently saw this video from Electronoobs and it looks complicated&amp;nbsp;&lt;a href="https://www.youtube.com/watch?v=JFTJ2SS4xyA"&gt;www.youtube.com/watch&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span&gt;&lt;pre class="ui-code" data-mode="python"&gt;def _steer_towards(bbox):
    x1, y1, x2, y2 = bbox
    frame_w, frame_h = camera.RESOLUTION
    center_x = (x1 + x2) / 2
    box_h    = max(1, y2 - y1)

    offset     = (center_x - frame_w / 2) / (frame_w / 2)  # -1 .. +1
    size_ratio = box_h / frame_h                             # 0 .. 1

    turn     = max(-100, min(100, offset * TURN_GAIN))
    throttle = max(0, min(MAX_THROTTLE,
                          (TARGET_SIZE_RATIO - size_ratio) / TARGET_SIZE_RATIO * MAX_THROTTLE))
    return turn, throttle&lt;/pre&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;turn&lt;/code&gt;/&lt;code class="language-plaintext highlighter-rouge"&gt;throttle&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;go through the exact same&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;mix_drive()&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;the joystick posts through from&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57174/hall-w-ev---post-3---joystick-control" data-e14adj="t"&gt;two posts ago&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;mdash; one function, one place that knows how a turn+throttle pair becomes two wheel speeds, whether a human or a neural network produced them.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;MAX_THROTTLE&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;is capped well under full speed on purpose &amp;mdash; this thing drives itself with nothing watching for obstacles, no reason to let it move at joystick speeds.&lt;/p&gt;
&lt;h2 id="what-actually-broke-on-real-hardware"&gt;What actually broke on real hardware&lt;/h2&gt;
&lt;p&gt;First real test went nowhere:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;App.run()&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;never scheduled&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;VideoObjectDetection&lt;/code&gt;&amp;rsquo;s background loops, since the brick got built (on its own thread, after&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;App.run()&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;had already started) too late for the scheduler to notice &amp;mdash; confirmed by the sidecar sitting there waiting for a connection that never came. Fixed by just running those two loops ourselves in plain daemon threads instead.&lt;/p&gt;
&lt;h2 id="seeing-what-its-actually-seeing"&gt;Seeing what it&amp;rsquo;s actually seeing&lt;/h2&gt;
&lt;p&gt;Once frames were flowing, the ball&amp;rsquo;s box kept flickering against other objects (a bed, a cup) even on a dead-static scene &amp;mdash; turned out to be real per-frame confidence noise, not a &amp;ldquo;one object at a time&amp;rdquo; limitation (it&amp;rsquo;s a genuine multi-object detector, and no, it can&amp;rsquo;t be restricted to only look for balls &amp;mdash; fixed 80-class model, no filter option). Fix: draw a box for everything it sees, not just the ball, and fade them out over a few seconds instead of hard-cutting the instant one frame doesn&amp;rsquo;t reconfirm them.&lt;/p&gt;
&lt;h2 id="the-lost-the-ball-behavior-that-didnt-need-writing"&gt;The &amp;ldquo;lost the ball&amp;rdquo; behavior that didn&amp;rsquo;t need writing&lt;/h2&gt;
&lt;p&gt;Didn&amp;rsquo;t need a lost-ball timeout &amp;mdash;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57131/hall-w-ev-post-1---the-wheels-are-turning-mostly" data-e14adj="t"&gt;Post 1&lt;/a&gt;&amp;rsquo;s STM32 watchdog already stops the wheels when commands stop arriving, same as a dropped wifi connection, for free.&lt;/p&gt;
&lt;h2 id="it-worked--and-then-drove-straight-past-the-ball"&gt;It worked &amp;mdash; and then drove straight past the ball&lt;/h2&gt;
&lt;p&gt;Reacting to every single detection message overshot the ball almost every time &amp;mdash; no braking distance. Fixed by pulsing instead: one short move, stop, pause, then decide again from a fresh look &amp;mdash; and&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;VideoObjectDetection&lt;/code&gt;&amp;rsquo;s own per-label lock already discards anything that arrives mid-pause, so no new state machine was needed to make that stick.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://community.element14.com/cfs-file/__key/communityserver-discussions-components-files/456/20260816_5F00_hall_2D00_w_2D00_EV_2D00_chase_2D00_ball.mp4"&gt;community.element14.com/.../20260816_5F00_hall_2D00_w_2D00_EV_2D00_chase_2D00_ball.mp4&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="whats-next"&gt;What&amp;rsquo;s next&lt;/h2&gt;
&lt;p&gt;Well that&amp;rsquo;s kind of it. Thrilled at how far we got to an actual auto driving EV.&lt;/p&gt;
&lt;h2 id="the-codes"&gt;The codes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/tamadillo/hall-w-EV" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;https://github.com/tamadillo/hall-w-EV&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;mdash;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://github.com/hambreros" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;Hambreros&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(and&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://github.com/tamadillo" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;Tamadillo&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;&lt;/code&gt;&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;</description></item><item><title>hall-w-EV - Post 4 - Giving it eyes</title><link>https://community.element14.com/thread/57184?ContentTypeID=0</link><pubDate>Sun, 16 Aug 2026 12:23:30 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:025465f3-6ae5-4863-bfed-7977041a8582</guid><dc:creator>tamadillo</dc:creator><slash:comments>2</slash:comments><comments>https://community.element14.com/thread/57184?ContentTypeID=0</comments><wfw:commentRss>https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57184/hall-w-ev---post-4---giving-it-eyes/rss?ContentTypeId=0</wfw:commentRss><description>&lt;p&gt;Hi again, it&amp;rsquo;s&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://github.com/hambreros" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;Hambreros&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;and&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://github.com/tamadillo" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;Tamadillo&lt;/a&gt;. Last post ended with&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57174/hall-w-ev---post-3---joystick-control" data-e14adj="t"&gt;a promise&lt;/a&gt;: the joystick makes driving nicer, but you&amp;rsquo;re still driving blind. So a USB webcam went on, and the control page now shows what the robot sees.&lt;/p&gt;
&lt;h2 id="what-weve-actually-built"&gt;What we&amp;rsquo;ve actually built&lt;/h2&gt;
&lt;p&gt;A live video feed right on the same page as the joystick &amp;mdash; the &amp;ldquo;puppy on a leash from your phone&amp;rdquo; post the original plan called for, minus the actual leash. Point the robot somewhere without needing to be in the room with it.&lt;/p&gt;
&lt;h2 id="checking-what-the-framework-already-gives-you"&gt;Checking what the framework already gives you&lt;/h2&gt;
&lt;p&gt;Same move as the&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57173/hall-w-ev---post-2---giving-the-robot-a-voice" data-e14adj="t"&gt;TTS detour&lt;/a&gt;: before writing any camera code, went and read&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://github.com/arduino/app-bricks-py" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;arduino/app-bricks-py&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;to see what Arduino already ships. Turned out&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;app_peripherals/camera&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;is a whole unified abstraction &amp;mdash; one&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;Camera&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;class covering CSI, USB (V4L), IP, and even WebSocket sources, same family as the&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;Speaker&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;peripheral the sound system already uses:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="python"&gt;from arduino.app_peripherals.camera import Camera

camera = Camera(&amp;quot;usb:0&amp;quot;, resolution=(640, 480), fps=15)
camera.start()
frame = camera.capture()   # numpy array, or None
# or: for frame in camera.stream(): ...&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;Since&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;Speaker&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;was already confirmed bundled in this app&amp;rsquo;s base container with zero extra install, betting&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;Camera&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;is too &amp;mdash; no new brick, no sidecar container, just a normal peripheral call from our own code. The only actual new dependency is&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;opencv-python-headless&lt;/code&gt;, for turning each frame into a JPEG.&lt;/p&gt;
&lt;h2 id="the-dumbest-frontend-that-works"&gt;The dumbest frontend that works&lt;/h2&gt;
&lt;p&gt;The tempting-but-overbuilt version of this involves a&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;, a WebSocket, and a JS render loop pulling frames off it. Skipped all of that:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="html"&gt;&amp;lt;img id=&amp;quot;cameraFeed&amp;quot; src=&amp;quot;/api/camera/stream&amp;quot; alt=&amp;quot;Live camera feed&amp;quot;&amp;gt;&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;That&amp;rsquo;s the entire client-side video pipeline.&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;GET /api/camera/stream&lt;/code&gt;&lt;span&gt;&amp;nbsp;returns&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;multipart/x-mixed-replace&lt;/code&gt;&lt;span&gt;&amp;nbsp;&amp;mdash; a boundary-delimited stream of JPEG frames &amp;mdash; and browsers have known how to render that straight into an&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;&amp;lt;img&amp;gt;&lt;/code&gt;&lt;span&gt;&amp;nbsp;tag since basically forever. No JS needed for the video itself, just a listener on the&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;error&lt;/code&gt;&lt;span&gt;&amp;nbsp;event for when there&amp;rsquo;s no camera to show:&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;pre class="ui-code" data-mode="javascript"&gt;cameraFeed.addEventListener(&amp;#39;error&amp;#39;, () =&amp;gt; {
  cameraFeed.style.display = &amp;#39;none&amp;#39;;
  fetch(&amp;#39;/api/camera/status&amp;#39;).then(r =&amp;gt; r.json()).then(data =&amp;gt; {
    cameraError.hidden = false;
    cameraError.textContent = data.error || &amp;#39;camera stream unavailable&amp;#39;;
  });
});&lt;/pre&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Same &amp;ldquo;surface the real reason, not a cryptic broken icon&amp;rdquo; instinct as the sound system&amp;rsquo;s error banner from a couple of posts back.&lt;/p&gt;
&lt;h2 id="starting-the-camera-without-blocking-everything-else"&gt;Starting the camera without blocking everything else&lt;/h2&gt;
&lt;p&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;Camera.start()&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;has its own connection retry loop with exponential backoff &amp;mdash; reasonable for &amp;ldquo;give the USB device a moment to enumerate,&amp;rdquo; bad if it&amp;rsquo;s sitting on the same startup path as the wheel and sound APIs. A slow or missing camera shouldn&amp;rsquo;t hold up driving the robot.&lt;/p&gt;
&lt;p&gt;So&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;python/camera.py&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;kicks off&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;Camera(...).start()&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;on a background thread at import time, mirroring&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;sounds.py&lt;/code&gt;&amp;rsquo;s general shape:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="python"&gt;def _start():
    global _camera, _error
    try:
        cam = Camera(SOURCE, resolution=RESOLUTION, fps=FPS)
        cam.start()
        _camera = cam
    except Exception as e:
        _error = str(e)

threading.Thread(target=_start, daemon=True).start()&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;status()&lt;/code&gt;&lt;span&gt;&amp;nbsp;reports&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;(ok, error)&lt;/code&gt;&lt;span&gt;&amp;nbsp;off that shared state, and the stream route just checks it before handing back the actual&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;multipart&lt;/code&gt;&lt;span&gt;&amp;nbsp;response:&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;pre class="ui-code" data-mode="python"&gt;@ web.route(&amp;#39;/api/camera/stream&amp;#39;) # space added not to look like email address
def camera_stream():              # which flags the post as inappropriate
    ok, error = camera.status()
    if not ok:
        return jsonify({&amp;#39;ok&amp;#39;: False, &amp;#39;error&amp;#39;: error}), 503
    return Response(camera.mjpeg_frames(),
                     mimetype=&amp;#39;multipart/x-mixed-replace; boundary=frame&amp;#39;)&lt;/pre&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;mjpeg_frames() itself is a small generator wrapping Camera.stream(), JPEG-encoding (cv2.imencode) each frame as it comes.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;a href="https://community.element14.com/cfs-file/__key/communityserver-discussions-components-files/456/2352.20260816_5F00_hall_2D00_w_2D00_EV_2D00_w_2D00_camera.mp4"&gt;community.element14.com/.../2352.20260816_5F00_hall_2D00_w_2D00_EV_2D00_w_2D00_camera.mp4&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 id="whats-next"&gt;What&amp;rsquo;s next&lt;/h2&gt;
&lt;p&gt;Camera&amp;rsquo;s on, joystick works &amp;mdash; next logical step is doing something with the two together: point-and-drive, or finally trying that on-device object detection now that we know&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;UNO Q&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;supports in it&amp;rsquo;s demo bricks.&lt;/p&gt;
&lt;h2 id="the-codes"&gt;The codes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/tamadillo/hall-w-EV" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;https://github.com/tamadillo/hall-w-EV&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;mdash;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://github.com/hambreros" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;Hambreros&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(and&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://github.com/tamadillo" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;Tamadillo&lt;/a&gt;)&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;</description></item><item><title>hall-w-EV - Post 4 - Giving it eyes</title><link>https://community.element14.com/thread/57183?ContentTypeID=0</link><pubDate>Sun, 16 Aug 2026 12:14:46 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:a569a952-1695-478e-ad6c-c6abc12f8afe</guid><dc:creator>tamadillo</dc:creator><slash:comments>1</slash:comments><comments>https://community.element14.com/thread/57183?ContentTypeID=0</comments><wfw:commentRss>https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57183/hall-w-ev---post-4---giving-it-eyes/rss?ContentTypeId=0</wfw:commentRss><description>&lt;p&gt;Hi again, it&amp;rsquo;s&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://github.com/hambreros" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;Hambreros&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;and&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://github.com/tamadillo" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;Tamadillo&lt;/a&gt;. Last post ended with&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57174/hall-w-ev---post-3---joystick-control" data-e14adj="t"&gt;a promise&lt;/a&gt;: the joystick makes driving nicer, but you&amp;rsquo;re still driving blind. So a USB webcam went on, and the control page now shows what the robot sees.&lt;/p&gt;
&lt;h2 id="what-weve-actually-built"&gt;What we&amp;rsquo;ve actually built&lt;/h2&gt;
&lt;p&gt;A live video feed right on the same page as the joystick &amp;mdash; the &amp;ldquo;puppy on a leash from your phone&amp;rdquo; post the original plan called for, minus the actual leash. Point the robot somewhere without needing to be in the room with it.&lt;/p&gt;
&lt;h2 id="checking-what-the-framework-already-gives-you"&gt;Checking what the framework already gives you&lt;/h2&gt;
&lt;p&gt;Same move as the&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://tamadillo.github.io/hall-w-EV/post/sound/docker/tts/2026/08/13/post-2-giving-the-robot-a-voice.html" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;TTS detour&lt;/a&gt;: before writing any camera code, went and read&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://github.com/arduino/app-bricks-py" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;arduino/app-bricks-py&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;to see what Arduino already ships. Turned out&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;app_peripherals/camera&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;is a whole unified abstraction &amp;mdash; one&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;Camera&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;class covering CSI, USB (V4L), IP, and even WebSocket sources, same family as the&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;Speaker&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;peripheral the sound system already uses:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;from arduino.app_peripherals.camera import Camera

camera = Camera(&amp;quot;usb:0&amp;quot;, resolution=(640, 480), fps=15)
camera.start()
frame = camera.capture()   # numpy array, or None
# or: for frame in camera.stream(): ...&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;Since&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;Speaker&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;was already confirmed bundled in this app&amp;rsquo;s base container with zero extra install, betting&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;Camera&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;is too &amp;mdash; no new brick, no sidecar container, just a normal peripheral call from our own code. The only actual new dependency is&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;opencv-python-headless&lt;/code&gt;, for turning each frame into a JPEG.&lt;/p&gt;
&lt;h2 id="the-dumbest-frontend-that-works"&gt;The dumbest frontend that works&lt;/h2&gt;
&lt;p&gt;The tempting-but-overbuilt version of this involves a&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;, a WebSocket, and a JS render loop pulling frames off it. Skipped all of that:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="html"&gt;&amp;lt;img id=&amp;quot;cameraFeed&amp;quot; src=&amp;quot;/api/camera/stream&amp;quot; alt=&amp;quot;Live camera feed&amp;quot;&amp;gt;&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;That&amp;rsquo;s the entire client-side video pipeline.&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;GET /api/camera/stream&lt;/code&gt;&lt;span&gt;&amp;nbsp;returns&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;multipart/x-mixed-replace&lt;/code&gt;&lt;span&gt;&amp;nbsp;&amp;mdash; a boundary-delimited stream of JPEG frames &amp;mdash; and browsers have known how to render that straight into an&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;&amp;lt;img&amp;gt;&lt;/code&gt;&lt;span&gt;&amp;nbsp;tag since basically forever. No JS needed for the video itself, just a listener on the&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;error&lt;/code&gt;&lt;span&gt;&amp;nbsp;event for when there&amp;rsquo;s no camera to show:&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;pre class="ui-code" data-mode="javascript"&gt;cameraFeed.addEventListener(&amp;#39;error&amp;#39;, () =&amp;gt; {
  cameraFeed.style.display = &amp;#39;none&amp;#39;;
  fetch(&amp;#39;/api/camera/status&amp;#39;).then(r =&amp;gt; r.json()).then(data =&amp;gt; {
    cameraError.hidden = false;
    cameraError.textContent = data.error || &amp;#39;camera stream unavailable&amp;#39;;
  });
});&lt;/pre&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Same &amp;ldquo;surface the real reason, not a cryptic broken icon&amp;rdquo; instinct as the sound system&amp;rsquo;s error banner from a couple of posts back.&lt;/p&gt;
&lt;h2 id="starting-the-camera-without-blocking-everything-else"&gt;Starting the camera without blocking everything else&lt;/h2&gt;
&lt;p&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;Camera.start()&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;has its own connection retry loop with exponential backoff &amp;mdash; reasonable for &amp;ldquo;give the USB device a moment to enumerate,&amp;rdquo; bad if it&amp;rsquo;s sitting on the same startup path as the wheel and sound APIs. A slow or missing camera shouldn&amp;rsquo;t hold up driving the robot.&lt;/p&gt;
&lt;p&gt;So&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;python/camera.py&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;kicks off&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;Camera(...).start()&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;on a background thread at import time, mirroring&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;sounds.py&lt;/code&gt;&amp;rsquo;s general shape:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="python"&gt;def _start():
    global _camera, _error
    try:
        cam = Camera(SOURCE, resolution=RESOLUTION, fps=FPS)
        cam.start()
        _camera = cam
    except Exception as e:
        _error = str(e)

threading.Thread(target=_start, daemon=True).start()&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;status()&lt;/code&gt;&lt;span&gt;&amp;nbsp;reports&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;(ok, error)&lt;/code&gt;&lt;span&gt;&amp;nbsp;off that shared state, and the stream route just checks it before handing back the actual&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;multipart&lt;/code&gt;&lt;span&gt;&amp;nbsp;response:&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;pre class="ui-code" data-mode="python"&gt;@web.route(&amp;#39;/api/camera/stream&amp;#39;)
def camera_stream():
    ok, error = camera.status()
    if not ok:
        return jsonify({&amp;#39;ok&amp;#39;: False, &amp;#39;error&amp;#39;: error}), 503
    return Response(camera.mjpeg_frames(),
                     mimetype=&amp;#39;multipart/x-mixed-replace; boundary=frame&amp;#39;)
&lt;/pre&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;mjpeg_frames()&lt;/code&gt;&amp;nbsp;itself is a small generator wrapping&amp;nbsp;&lt;code class="language-plaintext highlighter-rouge"&gt;Camera.stream()&lt;/code&gt;, JPEG-encoding (&lt;code class="language-plaintext highlighter-rouge"&gt;cv2.imencode&lt;/code&gt;) each frame as it comes.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;a href="https://community.element14.com/cfs-file/__key/communityserver-discussions-components-files/456/20260816_5F00_hall_2D00_w_2D00_EV_2D00_w_2D00_camera.mp4"&gt;community.element14.com/.../20260816_5F00_hall_2D00_w_2D00_EV_2D00_w_2D00_camera.mp4&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div class="post-content"&gt;
&lt;h2 id="whats-next"&gt;What&amp;rsquo;s next&lt;/h2&gt;
&lt;p&gt;Camera&amp;rsquo;s on, joystick works &amp;mdash; next logical step is doing something with the two together: point-and-drive, or finally trying that on-device object detection now that we know&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;UNO Q&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;supports in it&amp;rsquo;s demo bricks.&lt;/p&gt;
&lt;h2 id="the-codes"&gt;The codes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/tamadillo/hall-w-EV" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;https://github.com/tamadillo/hall-w-EV&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;mdash;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://github.com/hambreros" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;Hambreros&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(and&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://github.com/tamadillo" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;Tamadillo&lt;/a&gt;)&lt;/p&gt;
&lt;/div&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;</description></item><item><title>EVA Guardian[Part_5] - The Integrated System</title><link>https://community.element14.com/thread/57181?ContentTypeID=0</link><pubDate>Sat, 15 Aug 2026 17:36:00 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:8b5556fe-2506-4458-9f39-4b28794f18d1</guid><dc:creator>Sumanth_m_n</dc:creator><slash:comments>4</slash:comments><comments>https://community.element14.com/thread/57181?ContentTypeID=0</comments><wfw:commentRss>https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57181/eva-guardian-part_5---the-integrated-system/rss?ContentTypeId=0</wfw:commentRss><description>&lt;div class="flex min-h-0 grow flex-col text-sm"&gt;
&lt;div class="qMYqUG_convSearchResultHighlightRoot"&gt;
&lt;div class="" data-turn-id-container="request-6a0deab6-e2f8-8321-beb6-4abed4003c0e-2" data-is-intersecting="true"&gt;
&lt;div data-conversation-screenshot-content=""&gt;
&lt;div class="flex max-w-full flex-col gap-4 grow"&gt;
&lt;div dir="auto" data-message-author-role="assistant" data-message-id="fd0e633a-d32f-469e-85d7-086c2b640994" data-message-model-slug="gpt-5-6" data-turn-start-message="true"&gt;
&lt;div class="flex w-full flex-col gap-1 empty:hidden"&gt;
&lt;div class="markdown prose dark:prose-invert wrap-break-word w-full light markdown-new-styling"&gt;
&lt;h1 id="mcetoc_1k035787u0" class="PDq2pG_selectionAnchorContainer" data-section-id="4jtnwl" data-start="481" data-end="506"&gt;When Battery Management Meets Incident Detection&lt;/h1&gt;
&lt;p data-start="559" data-end="649"&gt;In the previous posts,&lt;/p&gt;
&lt;p data-start="559" data-end="649"&gt;&lt;span&gt;Previous forum posts:&lt;/span&gt;&lt;/p&gt;
&lt;p data-start="559" data-end="649"&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57059/eva-guardian-part_1---the-idea" data-e14adj="t"&gt;Part 1 : The Idea behind EVA Guardian&lt;/a&gt;&lt;br /&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57133/eva-guardian-part_2---the-architecture" data-e14adj="t"&gt;Part 2 : The Architecture of EVA Guardian&lt;/a&gt;&lt;br /&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57152/eva-guardian-part_3---the-incident-detection-system" data-e14adj="t"&gt;Part 3 : The Incident detection system in EVA Guardian&lt;/a&gt;&lt;br /&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57164/eva-guardian-part_4---the-battery-management-system" data-e14adj="t"&gt;Part 4 : The Battery management system in EVA Guardian&lt;/a&gt;&lt;/p&gt;
&lt;p data-start="559" data-end="649"&gt;I developed the two major subsystems of EVA Guardian independently. The &lt;strong data-start="655" data-end="684"&gt;Battery Management System&lt;/strong&gt; monitors the battery and provides parameters such as voltage, current, temperature, SoC and SoH. The &lt;strong data-start="787" data-end="816"&gt;Incident Detection System&lt;/strong&gt; monitors vehicle dynamics using the MPU6500 and Edge AI. Both systems were working independently. But an EV is not made up of independent systems.&lt;/p&gt;
&lt;p data-start="967" data-end="1013"&gt;A battery condition can affect vehicle safety.&lt;/p&gt;
&lt;p data-start="1015" data-end="1057"&gt;A vehicle incident can affect the battery.&lt;/p&gt;
&lt;p data-start="1059" data-end="1151"&gt;And a communication failure between two safety-critical systems can itself become a problem.&lt;/p&gt;
&lt;p data-start="1153" data-end="1179"&gt;So the next challenge was:&lt;/p&gt;
&lt;blockquote data-start="1181" data-end="1326"&gt;
&lt;p data-start="1183" data-end="1326"&gt;&lt;strong data-start="1183" data-end="1326"&gt;How do I make these two systems communicate reliably and, more importantly, how do I know when that communication can no longer be trusted?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="1328" data-end="1380"&gt;This became the focus of this stage of EVA Guardian.&lt;/p&gt;
&lt;hr data-start="1382" data-end="1385" /&gt;
&lt;h1 id="mcetoc_1k035787u1" data-section-id="1f9d9zl" data-start="1387" data-end="1425"&gt;Creating a Master&amp;ndash;Slave Architecture&lt;/h1&gt;
&lt;p data-start="1427" data-end="1510"&gt;For the integration, I used an &lt;strong data-start="1458" data-end="1509"&gt;RS485 transceiver on both Arduino UNO Q systems&lt;/strong&gt;.The communication between the two nodes is based on UART, using:&lt;/p&gt;
&lt;ul data-start="1578" data-end="1611"&gt;
&lt;li data-section-id="1ujvz75" data-start="1578" data-end="1594"&gt;&lt;strong data-start="1580" data-end="1594"&gt;Pin 1 &amp;ndash; TX&lt;/strong&gt;&lt;/li&gt;
&lt;li data-section-id="1567tp2" data-start="1595" data-end="1611"&gt;&lt;strong data-start="1597" data-end="1611"&gt;Pin 0 &amp;ndash; RX&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-start="1613" data-end="1654"&gt;The architecture is intentionally simple:&lt;/p&gt;
&lt;p data-start="1656" data-end="1694"&gt;&lt;strong data-start="1656" data-end="1694"&gt;Incident Detection System &amp;rarr; Master&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="1696" data-end="1733"&gt;&lt;strong data-start="1696" data-end="1733"&gt;Battery Management System &amp;rarr; Slave&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="1735" data-end="1847"&gt;The IDS is responsible for initiating communication, while the BMS responds with its latest battery information.This gives me a deterministic communication model rather than having both systems continuously transmitting data and potentially interfering with each other.&lt;/p&gt;
&lt;hr data-start="2008" data-end="2011" /&gt;
&lt;h2 id="mcetoc_1k035787u2" data-section-id="ae0k7h" data-start="2013" data-end="2074"&gt;&lt;img alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/Screenshot-2026_2D00_08_2D00_15-221836.png" /&gt;&lt;/h2&gt;
&lt;hr data-start="2076" data-end="2079" /&gt;
&lt;h1 id="mcetoc_1k035787u3" data-section-id="1p08adc" data-start="2081" data-end="2122"&gt;Designing My Own Communication Protocol&lt;/h1&gt;
&lt;p data-start="2124" data-end="2247"&gt;Rather than simply sending a raw CSV string over UART, I wanted the communication layer to have a defined packet structure. This became particularly important because the system would later need to detect communication failures and distinguish between different types of faults.&lt;/p&gt;
&lt;p data-start="2405" data-end="2469"&gt;The master sends a &lt;strong data-start="2424" data-end="2450"&gt;battery status request&lt;/strong&gt; every two seconds. The basic command packet is structured as:&lt;/p&gt;
&lt;p data-start="2515" data-end="2581"&gt;&lt;strong data-start="2515" data-end="2581"&gt;Start of Frame &amp;rarr; Payload Length &amp;rarr; Command &amp;rarr; CRC &amp;rarr; End of Frame&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="2583" data-end="2605"&gt;The start of frame is:&amp;nbsp;&lt;code data-start="2607" data-end="2618"&gt;0x55 0xAA&lt;/code&gt;&lt;/p&gt;
&lt;p data-start="2620" data-end="2650"&gt;The battery status command is:&amp;nbsp;&lt;code data-start="2652" data-end="2658"&gt;0x01&lt;/code&gt;&lt;/p&gt;
&lt;p data-start="2660" data-end="2691"&gt;And the packet terminates with:&lt;/p&gt;
&lt;p data-start="2693" data-end="2699"&gt;&lt;code data-start="2693" data-end="2699"&gt;0xAA&lt;/code&gt;&lt;/p&gt;
&lt;p data-start="2701" data-end="2782"&gt;The CRC is used to provide an additional integrity check on the transmitted data.&lt;/p&gt;
&lt;hr data-start="2784" data-end="2787" /&gt;
&lt;h2 id="mcetoc_1k035787u4" data-section-id="1h2psa0" data-start="2789" data-end="2806"&gt;Master Request&lt;/h2&gt;
&lt;p data-start="2808" data-end="2835"&gt;The IDS periodically sends:&lt;/p&gt;
&lt;p data-start="2837" data-end="2844"&gt;&lt;strong data-start="2837" data-end="2844"&gt;SOF&amp;nbsp;&lt;/strong&gt;&amp;rarr; &lt;code data-start="2848" data-end="2859"&gt;0x55 0xAA&lt;/code&gt;&lt;br /&gt;&lt;strong data-start="2861" data-end="2879"&gt;Payload Length&amp;nbsp;&lt;/strong&gt;&amp;rarr; Length of command payload&lt;br /&gt;&lt;strong data-start="2910" data-end="2921"&gt;Command&amp;nbsp;&lt;/strong&gt;&amp;rarr; &lt;code data-start="2925" data-end="2931"&gt;0x01&lt;/code&gt;&lt;br /&gt;&lt;strong data-start="2933" data-end="2940"&gt;CRC&amp;nbsp;&lt;/strong&gt;&amp;rarr; Packet integrity check&lt;br /&gt;&lt;strong data-start="2968" data-end="2975"&gt;EOF&lt;/strong&gt;&amp;rarr; &lt;code data-start="2979" data-end="2985"&gt;0xAA&lt;/code&gt;&lt;/p&gt;
&lt;p data-start="2987" data-end="3022"&gt;In simple terms, the IDS is asking:&lt;/p&gt;
&lt;blockquote data-start="3024" data-end="3072"&gt;
&lt;p data-start="3026" data-end="3072"&gt;&lt;strong data-start="3026" data-end="3072"&gt;&amp;quot;BMS, give me your latest battery status.&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-start="3074" data-end="3077" /&gt;
&lt;h1 id="mcetoc_1k035787u5" data-section-id="koq9yx" data-start="3079" data-end="3093"&gt;BMS Response&lt;/h1&gt;
&lt;p data-start="3095" data-end="3188"&gt;The BMS receives the request and prepares a response containing the latest battery telemetry.&lt;/p&gt;
&lt;p data-start="3190" data-end="3212"&gt;The response contains:&lt;/p&gt;
&lt;ul data-start="3214" data-end="3298"&gt;
&lt;li data-section-id="kq999m" data-start="3214" data-end="3223"&gt;Voltage&lt;/li&gt;
&lt;li data-section-id="jv6ha9" data-start="3224" data-end="3233"&gt;Current&lt;/li&gt;
&lt;li data-section-id="1f3vflw" data-start="3234" data-end="3247"&gt;Temperature&lt;/li&gt;
&lt;li data-section-id="kgsuzx" data-start="3248" data-end="3270"&gt;Open Circuit Voltage&lt;/li&gt;
&lt;li data-section-id="ciu4ou" data-start="3271" data-end="3286"&gt;Coulomb Count&lt;/li&gt;
&lt;li data-section-id="1o4ruf" data-start="3287" data-end="3292"&gt;SoC&lt;/li&gt;
&lt;li data-section-id="1o4ruk" data-start="3293" data-end="3298"&gt;SoH&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-start="3300" data-end="3354"&gt;The packet follows the same general framing structure&lt;/p&gt;
&lt;p data-start="3523" data-end="3562"&gt;The response effectively tells the IDS:&lt;/p&gt;
&lt;blockquote data-start="3564" data-end="3615"&gt;
&lt;p data-start="3566" data-end="3615"&gt;&lt;strong data-start="3566" data-end="3615"&gt;&amp;quot;Here is the latest condition of my battery.&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-start="3617" data-end="3620" /&gt;
&lt;h2 id="mcetoc_1k035787u6" data-section-id="1mddobm" data-start="3622" data-end="3698"&gt;Why Poll Every Two Seconds?&lt;/h2&gt;
&lt;p data-start="3736" data-end="3792"&gt;The IDS polls the BMS approximately every &lt;strong data-start="3778" data-end="3791"&gt;2 seconds&lt;/strong&gt;. The BMS itself is continuously updating its battery measurements, so the IDS does not need to request the data continuously at the same rate as the IMU.&lt;/p&gt;
&lt;p data-start="3948" data-end="4019"&gt;The 2-second polling interval provides a reasonable compromise between:&lt;/p&gt;
&lt;ul data-start="4021" data-end="4120"&gt;
&lt;li data-section-id="18d2mkj" data-start="4021" data-end="4048"&gt;Fresh battery information&lt;/li&gt;
&lt;li data-section-id="5brxbi" data-start="4049" data-end="4073"&gt;Communication overhead&lt;/li&gt;
&lt;li data-section-id="1y20svj" data-start="4074" data-end="4099"&gt;Processing requirements&lt;/li&gt;
&lt;li data-section-id="130bawt" data-start="4100" data-end="4120"&gt;System reliability&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-start="4122" data-end="4269"&gt;The IDS therefore gets a periodic snapshot of the battery condition while continuing to perform its own high-rate incident detection independently.&lt;/p&gt;
&lt;hr data-start="4271" data-end="4274" /&gt;
&lt;h1 id="mcetoc_1k035787u8" data-section-id="1t6amff" data-start="4276" data-end="4323"&gt;From Separate Telemetry to Combined Telemetry&lt;/h1&gt;
&lt;p data-start="4325" data-end="4436"&gt;Once communication was working, the next step was to bring the BMS data into the existing IDS telemetry system.&lt;/p&gt;
&lt;p data-start="4438" data-end="4494"&gt;Previously, the IDS primarily knew about things such as:&lt;/p&gt;
&lt;ul data-start="4496" data-end="4550"&gt;
&lt;li data-section-id="1ts16qe" data-start="4496" data-end="4504"&gt;Motion&lt;/li&gt;
&lt;li data-section-id="178ertb" data-start="4505" data-end="4512"&gt;Speed&lt;/li&gt;
&lt;li data-section-id="vq2a0d" data-start="4513" data-end="4524"&gt;Incidents&lt;/li&gt;
&lt;li data-section-id="be33u5" data-start="4525" data-end="4550"&gt;Accident classification&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-start="4552" data-end="4573"&gt;Now it also receives:&lt;/p&gt;
&lt;ul data-start="4575" data-end="4666"&gt;
&lt;li data-section-id="1y9vuyv" data-start="4575" data-end="4592"&gt;Battery voltage&lt;/li&gt;
&lt;li data-section-id="1dkyz70" data-start="4593" data-end="4610"&gt;Battery current&lt;/li&gt;
&lt;li data-section-id="wi4vs9" data-start="4611" data-end="4632"&gt;Battery temperature&lt;/li&gt;
&lt;li data-section-id="1o4ac2" data-start="4633" data-end="4638"&gt;OCV&lt;/li&gt;
&lt;li data-section-id="d3xvmm" data-start="4639" data-end="4654"&gt;Coulomb count&lt;/li&gt;
&lt;li data-section-id="1o4ruf" data-start="4655" data-end="4660"&gt;SoC&lt;/li&gt;
&lt;li data-section-id="1o4ruk" data-start="4661" data-end="4666"&gt;SoH&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-start="4668" data-end="4752"&gt;This information is stored in the IDS telemetry state and exposed through the WebUI. The result is a much more useful dashboard. Instead of having one screen for battery information and another for vehicle dynamics, the IDS becomes the central point from which both can be observed.&lt;/p&gt;
&lt;hr data-start="4954" data-end="4957" /&gt;
&lt;h2 id="mcetoc_1k035787u9" data-section-id="c0k3kw" data-start="4959" data-end="5027"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/Screenshot-2026_2D00_08_2D00_15-223307.png" /&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/Screenshot-2026_2D00_08_2D00_15-223330.png" /&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/Screenshot-2026_2D00_08_2D00_15-223347.png" /&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/Screenshot-2026_2D00_08_2D00_15-223403.png" /&gt;&lt;/h2&gt;
&lt;hr data-start="5029" data-end="5032" /&gt;
&lt;h1 id="mcetoc_1k035787ua" data-section-id="115ob26" data-start="5034" data-end="5078"&gt;But What Happens When Communication Stops?&lt;/h1&gt;
&lt;p data-start="5080" data-end="5140"&gt;This was where the integration became much more interesting.&lt;/p&gt;
&lt;p data-start="5142" data-end="5168"&gt;It is easy to demonstrate:&lt;/p&gt;
&lt;blockquote data-start="5170" data-end="5225"&gt;
&lt;p data-start="5172" data-end="5225"&gt;&lt;strong data-start="5172" data-end="5225"&gt;BMS connected &amp;rarr; data received &amp;rarr; everything works.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="5227" data-end="5273"&gt;But I wanted to deliberately break the system. The communication path has three possible failure points:&lt;/p&gt;
&lt;h3 id="mcetoc_1k035787ub" data-section-id="1uxnbef" data-start="5334" data-end="5347"&gt;Failure 1&lt;/h3&gt;
&lt;p data-start="5348" data-end="5381"&gt;&lt;strong data-start="5348" data-end="5381"&gt;IDS RS485 transceiver failure&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="mcetoc_1k035787uc" data-section-id="1uxnbec" data-start="5383" data-end="5396"&gt;Failure 2&lt;/h3&gt;
&lt;p data-start="5397" data-end="5430"&gt;&lt;strong data-start="5397" data-end="5430"&gt;BMS RS485 transceiver failure&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="mcetoc_1k035787ud" data-section-id="1uxnbed" data-start="5432" data-end="5445"&gt;Failure 3&lt;/h3&gt;
&lt;p data-start="5446" data-end="5479"&gt;&lt;strong data-start="5446" data-end="5479"&gt;Cable between the two systems&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="5481" data-end="5499"&gt;The challenge was:&lt;/p&gt;
&lt;blockquote data-start="5501" data-end="5578"&gt;
&lt;p data-start="5503" data-end="5578"&gt;&lt;strong data-start="5503" data-end="5578"&gt;If communication stops, how can the IDS determine which one has failed?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="5580" data-end="5598"&gt;Simply displaying:&lt;/p&gt;
&lt;blockquote data-start="5600" data-end="5629"&gt;
&lt;p data-start="5602" data-end="5629"&gt;&amp;quot;RS485 communication error&amp;quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="5631" data-end="5645"&gt;is not enough.&lt;/p&gt;
&lt;p data-start="5647" data-end="5716"&gt;I wanted EVA Guardian to identify the probable source of the problem.&lt;/p&gt;
&lt;hr data-start="5718" data-end="5721" /&gt;
&lt;h1 id="mcetoc_1k035787ue" data-section-id="brux4z" data-start="5723" data-end="5750"&gt;Step 1 &amp;ndash; Wait for the BMS&lt;/h1&gt;
&lt;p data-start="5752" data-end="5816"&gt;After startup, the IDS begins polling the BMS every two seconds.&lt;/p&gt;
&lt;p data-start="5818" data-end="5843"&gt;If everything is healthy:&lt;/p&gt;
&lt;p data-start="5845" data-end="5862"&gt;&lt;strong data-start="5845" data-end="5862"&gt;IDS &amp;rarr; Request&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="5864" data-end="5882"&gt;&lt;strong data-start="5864" data-end="5882"&gt;BMS &amp;rarr; Response&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="5884" data-end="5901"&gt;&lt;strong data-start="5884" data-end="5901"&gt;IDS &amp;rarr; Request&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="5903" data-end="5921"&gt;&lt;strong data-start="5903" data-end="5921"&gt;BMS &amp;rarr; Response&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="5923" data-end="5933"&gt;and so on.&lt;/p&gt;
&lt;p data-start="5935" data-end="6052"&gt;But if the IDS doesn&amp;#39;t receive a valid response for &lt;strong data-start="5987" data-end="6001"&gt;30 seconds&lt;/strong&gt;, the system assumes that something has gone wrong.&lt;/p&gt;
&lt;p data-start="6054" data-end="6133"&gt;Instead of immediately blaming the cable, the IDS begins a diagnostic sequence.&lt;/p&gt;
&lt;hr data-start="6135" data-end="6138" /&gt;
&lt;h1 id="mcetoc_1k035787uf" data-section-id="1xgd6so" data-start="6140" data-end="6181"&gt;Step 2 &amp;ndash; Test the IDS RS485 Transceiver&lt;/h1&gt;
&lt;p data-start="6183" data-end="6205"&gt;The first question is:&lt;/p&gt;
&lt;blockquote data-start="6207" data-end="6249"&gt;
&lt;p data-start="6209" data-end="6249"&gt;&lt;strong data-start="6209" data-end="6249"&gt;Is my own RS485 transceiver working?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="6251" data-end="6296"&gt;The IDS performs a local loopback diagnostic. Specific GPIO lines are controlled to configure the RS485 transceiver into its loopback/test condition.&lt;/p&gt;
&lt;p data-start="6403" data-end="6434"&gt;The diagnostic command used is:&lt;/p&gt;
&lt;p data-start="6436" data-end="6442"&gt;&lt;code data-start="6436" data-end="6442"&gt;0x02&lt;/code&gt;&lt;/p&gt;
&lt;p data-start="6444" data-end="6457"&gt;with payload:&lt;/p&gt;
&lt;p data-start="6459" data-end="6467"&gt;&lt;code data-start="6459" data-end="6467"&gt;0x1010&lt;/code&gt;&lt;/p&gt;
&lt;p data-start="6469" data-end="6538"&gt;The IDS sends the diagnostic pattern through its own RS485 interface.&lt;/p&gt;
&lt;p data-start="6540" data-end="6594"&gt;If the transmitted data is successfully received back:&lt;/p&gt;
&lt;blockquote data-start="6596" data-end="6628"&gt;
&lt;p data-start="6598" data-end="6628"&gt;&lt;strong data-start="6598" data-end="6628"&gt;IDS RS485 transceiver = OK&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="6630" data-end="6676"&gt;If the IDS cannot receive what it transmitted:&lt;/p&gt;
&lt;blockquote data-start="6678" data-end="6713"&gt;
&lt;p data-start="6680" data-end="6713"&gt;&lt;strong data-start="6680" data-end="6713"&gt;IDS RS485 transceiver = FAULT&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="6715" data-end="6775"&gt;The WebUI then reports the appropriate diagnostic condition.&lt;/p&gt;
&lt;p data-start="6777" data-end="6884"&gt;This is useful because the system doesn&amp;#39;t immediately assume that the external cable or BMS is responsible.&lt;/p&gt;
&lt;hr data-start="6961" data-end="6964" /&gt;
&lt;h1 id="mcetoc_1k035787uh" data-section-id="1djswyz" data-start="6966" data-end="7007"&gt;Step 3 &amp;ndash; Test the BMS RS485 Transceiver&lt;/h1&gt;
&lt;p data-start="7009" data-end="7062"&gt;Suppose the IDS transceiver passes its loopback test.&lt;/p&gt;
&lt;p data-start="7064" data-end="7076"&gt;We now know:&lt;/p&gt;
&lt;blockquote data-start="7078" data-end="7112"&gt;
&lt;p data-start="7080" data-end="7112"&gt;&lt;strong data-start="7080" data-end="7112"&gt;My side is probably working.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="7114" data-end="7160"&gt;But the BMS transceiver could still be faulty.&lt;/p&gt;
&lt;p data-start="7162" data-end="7314"&gt;So the next diagnostic step is to communicate with the BMS through the network connection and ask the BMS to perform the same local loopback diagnostic.&lt;/p&gt;
&lt;p data-start="7316" data-end="7341"&gt;The same command is used:&lt;/p&gt;
&lt;p data-start="7343" data-end="7362"&gt;&lt;strong data-start="7343" data-end="7355"&gt;Command:&lt;/strong&gt; &lt;code data-start="7356" data-end="7362"&gt;0x02&lt;/code&gt;&lt;/p&gt;
&lt;p data-start="7364" data-end="7385"&gt;&lt;strong data-start="7364" data-end="7376"&gt;Payload:&lt;/strong&gt; &lt;code data-start="7377" data-end="7385"&gt;0x1010&lt;/code&gt;&lt;/p&gt;
&lt;p data-start="7387" data-end="7432"&gt;The BMS performs its own RS485 loopback test.&lt;/p&gt;
&lt;p data-start="7434" data-end="7488"&gt;If the BMS successfully receives its transmitted data:&lt;/p&gt;
&lt;blockquote data-start="7490" data-end="7522"&gt;
&lt;p data-start="7492" data-end="7522"&gt;&lt;strong data-start="7492" data-end="7522"&gt;BMS RS485 transceiver = OK&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="7524" data-end="7568"&gt;The BMS reports this status back to the IDS.&lt;/p&gt;
&lt;p data-start="7570" data-end="7610"&gt;If the BMS cannot complete the loopback:&lt;/p&gt;
&lt;blockquote data-start="7612" data-end="7647"&gt;
&lt;p data-start="7614" data-end="7647"&gt;&lt;strong data-start="7614" data-end="7647"&gt;BMS RS485 transceiver = FAULT&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="7649" data-end="7701"&gt;The IDS then displays this information on the WebUI.&lt;/p&gt;
&lt;hr data-start="7703" data-end="7706" /&gt;
&lt;h1 id="mcetoc_1k035787ui" data-section-id="oc7rmd" data-start="7708" data-end="7743"&gt;Step 4 &amp;ndash; So Where Is the Problem?&lt;/h1&gt;
&lt;p data-start="7745" data-end="7776"&gt;Now comes the interesting part.&lt;/p&gt;
&lt;p data-start="7778" data-end="7786"&gt;Imagine:&lt;/p&gt;
&lt;p data-start="7788" data-end="7812"&gt;&lt;strong data-start="7788" data-end="7812"&gt;IDS transceiver &amp;rarr; OK&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="7814" data-end="7838"&gt;&lt;strong data-start="7814" data-end="7838"&gt;BMS transceiver &amp;rarr; OK&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="7840" data-end="7844"&gt;but:&lt;/p&gt;
&lt;p data-start="7846" data-end="7880"&gt;&lt;strong data-start="7846" data-end="7880"&gt;IDS &amp;lt;-&amp;gt;&amp;nbsp;BMS communication &amp;rarr; FAIL&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="7882" data-end="7980"&gt;At this point, both ends have independently verified that their own RS485 hardware is functioning.&lt;/p&gt;
&lt;p data-start="7982" data-end="8018"&gt;That leaves the most likely problem:&lt;/p&gt;
&lt;blockquote data-start="8020" data-end="8086"&gt;
&lt;p data-start="8022" data-end="8086"&gt;&lt;strong data-start="8022" data-end="8086"&gt;The communication cable or physical connection between them.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="8088" data-end="8119"&gt;The WebUI can therefore report:&lt;/p&gt;
&lt;h3 id="mcetoc_1k035787uj" data-section-id="1p6xb2i" data-start="8121" data-end="8153"&gt;Communication Cable Fault&lt;/h3&gt;
&lt;p data-start="8155" data-end="8212"&gt;instead of giving the user a generic communication error.&lt;/p&gt;
&lt;p data-start="8214" data-end="8316"&gt;This is a small feature, but from a system engineering perspective, it makes a significant difference.&lt;/p&gt;
&lt;hr data-start="8318" data-end="8321" /&gt;
&lt;h2 id="mcetoc_1k035787uk" data-section-id="5i2d1e" data-start="8323" data-end="8392"&gt;&lt;a href="https://youtu.be/ciR2tdJ3-0Y"&gt;https://youtu.be/ciR2tdJ3-0Y&lt;/a&gt;&lt;/h2&gt;
&lt;hr data-start="8394" data-end="8397" /&gt;
&lt;h1 id="mcetoc_1k035787ul" data-section-id="m2s19x" data-start="8399" data-end="8440"&gt;Why I Wanted This Diagnostic Capability&lt;/h1&gt;
&lt;p data-start="8442" data-end="8487"&gt;Imagine this system inside an actual vehicle.&lt;/p&gt;
&lt;p data-start="8489" data-end="8520"&gt;A technician receives an error:&lt;/p&gt;
&lt;blockquote data-start="8522" data-end="8553"&gt;
&lt;p data-start="8524" data-end="8553"&gt;&lt;strong data-start="8524" data-end="8553"&gt;BMS Communication Failure&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="8555" data-end="8584"&gt;What should they check first?&lt;/p&gt;
&lt;p data-start="8586" data-end="8594"&gt;The BMS? The IDS? The transceiver? The cable?&lt;/p&gt;
&lt;p data-start="8636" data-end="8698"&gt;Without diagnostics, troubleshooting becomes a manual process.&lt;/p&gt;
&lt;p data-start="8700" data-end="8801"&gt;With the diagnostic logic implemented in EVA Guardian, the system itself can narrow down the problem.&lt;/p&gt;
&lt;p data-start="8803" data-end="8814"&gt;Instead of:&lt;/p&gt;
&lt;blockquote data-start="8816" data-end="8843"&gt;
&lt;p data-start="8818" data-end="8843"&gt;&lt;strong data-start="8818" data-end="8843"&gt;Communication Failure&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="8845" data-end="8868"&gt;the system can provide:&lt;/p&gt;
&lt;blockquote data-start="8870" data-end="8891"&gt;
&lt;p data-start="8872" data-end="8891"&gt;&lt;strong data-start="8872" data-end="8891"&gt;IDS RS485 Fault&amp;nbsp;&lt;/strong&gt;or&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote data-start="8897" data-end="8918"&gt;
&lt;p data-start="8899" data-end="8918"&gt;&lt;strong data-start="8899" data-end="8918"&gt;BMS RS485 Fault&amp;nbsp;&lt;/strong&gt;or&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote data-start="8924" data-end="8955"&gt;
&lt;p data-start="8926" data-end="8955"&gt;&lt;strong data-start="8926" data-end="8955"&gt;Communication Cable Fault&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="8957" data-end="9040"&gt;This is the difference between &lt;strong data-start="8988" data-end="9011"&gt;monitoring a system&lt;/strong&gt; and &lt;strong data-start="9016" data-end="9039"&gt;diagnosing a system&lt;/strong&gt;.&lt;/p&gt;
&lt;hr data-start="9042" data-end="9045" /&gt;
&lt;h1 id="mcetoc_1k035787um" data-section-id="1xg041y" data-start="9047" data-end="9102"&gt;Another Problem: What Happens to the Power Connector?&lt;/h1&gt;
&lt;p data-start="9104" data-end="9206"&gt;After dealing with communication failures, I wanted to investigate another practical failure scenario. The physical power connection between the BMS and IDS can also deteriorate. A connector doesn&amp;#39;t necessarily go from:&lt;/p&gt;
&lt;p data-start="9327" data-end="9358"&gt;&lt;strong data-start="9327" data-end="9358"&gt;Perfect &amp;rarr; Completely Broken&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="9360" data-end="9390"&gt;It can gradually become worse. A poor connection can introduce additional resistance and therefore cause a voltage drop. So I added another voltage measurement point at the IDS side.&lt;/p&gt;
&lt;hr data-start="9546" data-end="9549" /&gt;
&lt;h1 id="mcetoc_1k035787un" data-section-id="beiol6" data-start="9551" data-end="9584"&gt;Monitoring the Power Connection&lt;/h1&gt;
&lt;p data-start="9586" data-end="9708"&gt;The IDS measures the voltage arriving through the power connection using its own voltage measurement circuit connected to:&lt;/p&gt;
&lt;p data-start="9710" data-end="9716"&gt;&lt;strong data-start="9710" data-end="9716"&gt;A0&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="9718" data-end="9796"&gt;At the same time, the BMS provides its measured battery voltage through RS485. Now I have two values:&lt;/p&gt;
&lt;h3 id="mcetoc_1k035787uo" data-section-id="hv18j2" data-start="9822" data-end="9842"&gt;BMS-side voltage&lt;/h3&gt;
&lt;p data-start="9844" data-end="9873"&gt;Measured directly by the BMS.&lt;/p&gt;
&lt;h3 id="mcetoc_1k035787up" data-section-id="l0p7kc" data-start="9875" data-end="9895"&gt;IDS-side voltage&lt;/h3&gt;
&lt;p data-start="9897" data-end="9933"&gt;Measured after the power connection. The system continuously compares the two.&lt;/p&gt;
&lt;p data-start="9978" data-end="9991"&gt;Conceptually:&lt;/p&gt;
&lt;p data-start="9993" data-end="10012"&gt;&lt;strong data-start="9993" data-end="10012"&gt;Battery voltage -&amp;gt;&amp;nbsp;&lt;/strong&gt;&lt;strong data-start="10017" data-end="10036"&gt;BMS measurement -&amp;gt;&amp;nbsp;&lt;/strong&gt;&lt;strong data-start="10041" data-end="10068"&gt;Power connector / cable -&amp;gt;&amp;nbsp;&lt;/strong&gt;&lt;strong data-start="10073" data-end="10092"&gt;IDS measurement&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="10094" data-end="10199"&gt;The difference between these measurements gives an indication of what is happening across the connection.&lt;/p&gt;
&lt;hr data-start="10201" data-end="10204" /&gt;
&lt;h1 id="mcetoc_1k035787uq" data-section-id="1wejfwu" data-start="10206" data-end="10239"&gt;Detecting Connector Degradation&lt;/h1&gt;
&lt;p data-start="10241" data-end="10366"&gt;If the voltage difference starts becoming noticeable but the connection is still functioning, the system generates a warning. For the current prototype, I configured a threshold around &lt;strong data-start="10427" data-end="10437"&gt;500 mV&lt;/strong&gt; for the degradation warning.&lt;/p&gt;
&lt;p data-start="10468" data-end="10508"&gt;The WebUI can display a message such as:&lt;/p&gt;
&lt;blockquote data-start="10510" data-end="10614"&gt;
&lt;p data-start="10512" data-end="10614"&gt;️&amp;nbsp;&lt;strong data-start="10515" data-end="10614"&gt;There is a connector degradation. Please show it to the mechanic soon before any severe damage.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="10616" data-end="10680"&gt;The purpose is not to wait until the connector completely fails. The objective is to identify a potentially deteriorating connection early.&lt;/p&gt;
&lt;hr data-start="10758" data-end="10761" /&gt;
&lt;h1 id="mcetoc_1k035787ur" data-section-id="8eeu21" data-start="10763" data-end="10794"&gt;Detecting a Broken Connection&lt;/h1&gt;
&lt;p data-start="10796" data-end="10928"&gt;The other extreme is a significant voltage drop indicating that the power path may have become disconnected or severely compromised. In this situation, the system moves from a warning condition to an error condition.&lt;/p&gt;
&lt;p data-start="11015" data-end="11043"&gt;The WebUI can then indicate:&lt;/p&gt;
&lt;blockquote data-start="11045" data-end="11076"&gt;
&lt;p data-start="11047" data-end="11076"&gt;&lt;strong data-start="11050" data-end="11076"&gt;Power connection fault&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="11078" data-end="11126"&gt;This gives the user a clear distinction between:&lt;/p&gt;
&lt;p data-start="11128" data-end="11139"&gt;&lt;strong data-start="11128" data-end="11139"&gt;Warning&amp;nbsp;&lt;/strong&gt;and&amp;nbsp;&lt;strong data-start="11146" data-end="11166"&gt;Critical failure&lt;/strong&gt;&lt;/p&gt;
&lt;hr data-start="11168" data-end="11171" /&gt;
&lt;h2 id="mcetoc_1k035787us" data-section-id="he32y0" data-start="11173" data-end="11246"&gt;&lt;a href="https://youtu.be/ZFzXiJqRlYU"&gt;https://youtu.be/ZFzXiJqRlYU&lt;/a&gt;&lt;/h2&gt;
&lt;p data-start="11364" data-end="11500"&gt;In this demonstration, I intentionally introduce a degraded connection and observe the voltage difference between the BMS and IDS sides.&lt;/p&gt;
&lt;hr data-start="11502" data-end="11505" /&gt;
&lt;h1 id="mcetoc_1k035787uu" data-section-id="56yspc" data-start="11507" data-end="11548"&gt;Making Accident Detection More Reliable&lt;/h1&gt;
&lt;p data-start="11550" data-end="11641"&gt;The integration also gave me an opportunity to improve the accident notification mechanism. One of the problems with a pure ML-based event trigger is that a single inference window can occasionally produce an incorrect classification.&lt;/p&gt;
&lt;p data-start="11787" data-end="11813"&gt;Imagine the model outputs:&lt;/p&gt;
&lt;p data-start="11815" data-end="11833"&gt;&lt;strong data-start="11815" data-end="11833"&gt;Accident &amp;ndash; 91%&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="11835" data-end="11860"&gt;for one inference window. Should that immediately trigger an emergency message? I decided that for this system, the answer should be:&lt;/p&gt;
&lt;blockquote data-start="11972" data-end="11986"&gt;
&lt;p data-start="11974" data-end="11986"&gt;&lt;strong data-start="11974" data-end="11986"&gt;Not yet.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="11988" data-end="12163"&gt;Instead, the system now requires the model to classify the event as &lt;strong data-start="12056" data-end="12121"&gt;Accident across more than three consecutive inference windows&lt;/strong&gt; before triggering the emergency response.&lt;/p&gt;
&lt;p data-start="12165" data-end="12178"&gt;Conceptually:&lt;/p&gt;
&lt;p data-start="12180" data-end="12192"&gt;&lt;strong data-start="12180" data-end="12192"&gt;Accident -&amp;gt;&amp;nbsp;&lt;/strong&gt;&lt;strong data-start="12197" data-end="12209"&gt;Accident -&amp;gt;&amp;nbsp;&lt;/strong&gt;&lt;strong data-start="12214" data-end="12226"&gt;Accident -&amp;gt;&amp;nbsp;&lt;/strong&gt;&lt;strong data-start="12231" data-end="12253"&gt;Accident confirmed -&amp;gt;&amp;nbsp;&lt;/strong&gt;&lt;strong data-start="12258" data-end="12276"&gt;Telegram alert&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="12278" data-end="12342"&gt;This gives the system an additional layer of event confirmation.&lt;/p&gt;
&lt;hr data-start="12344" data-end="12347" /&gt;
&lt;h1 id="mcetoc_1k035787uv" data-section-id="gl1qi1" data-start="12349" data-end="12385"&gt;Preventing Telegram Alert Flooding&lt;/h1&gt;
&lt;p data-start="12387" data-end="12435"&gt;Another practical issue appeared during testing.&lt;/p&gt;
&lt;p data-start="12437" data-end="12512"&gt;If the accident condition remains active, the model can continue producing:&lt;/p&gt;
&lt;blockquote data-start="12514" data-end="12566"&gt;
&lt;p data-start="12516" data-end="12566"&gt;Accident&lt;br data-start="12524" data-end="12527" /&gt; Accident&lt;br data-start="12537" data-end="12540" /&gt; Accident&lt;br data-start="12550" data-end="12553" /&gt; Accident...&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="12568" data-end="12673"&gt;Without protection, the Telegram system could potentially send multiple notifications for the same event.&lt;/p&gt;
&lt;p data-start="12675" data-end="12741"&gt;To prevent this, I implemented an &lt;strong data-start="12709" data-end="12740"&gt;accident cooldown mechanism&lt;/strong&gt;. Once an emergency alert has been dispatched, the system temporarily suppresses additional alerts for the same event.&lt;/p&gt;
&lt;p data-start="12861" data-end="12893"&gt;This creates a cleaner workflow:&lt;/p&gt;
&lt;p data-start="12895" data-end="12908"&gt;&lt;strong data-start="12895" data-end="12908"&gt;Detection&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="12910" data-end="12928"&gt;&amp;rarr; &lt;strong data-start="12912" data-end="12928"&gt;Confirmation&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="12930" data-end="12962"&gt;&amp;rarr; &lt;strong data-start="12932" data-end="12962"&gt;One emergency notification&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="12964" data-end="12978"&gt;&amp;rarr; &lt;strong data-start="12966" data-end="12978"&gt;Cooldown&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="12980" data-end="13046"&gt;rather than flooding the emergency contact with repeated messages.&lt;/p&gt;
&lt;hr data-start="13120" data-end="13123" /&gt;
&lt;h2 id="mcetoc_1k035787u10" data-section-id="p70fam" data-start="13053" data-end="13118"&gt;The Complete Data Flow&lt;/h2&gt;
&lt;p data-start="13151" data-end="13248"&gt;At this point, the integrated system has several independent data paths operating simultaneously.&lt;/p&gt;
&lt;h3 id="mcetoc_1k035787u12" data-section-id="1wg8ezc" data-start="13250" data-end="13270"&gt;Vehicle Dynamics&lt;/h3&gt;
&lt;p data-start="13272" data-end="13283"&gt;&lt;strong data-start="13272" data-end="13283"&gt;MPU6500 -&amp;gt;&amp;nbsp;&lt;/strong&gt;&lt;strong data-start="13288" data-end="13309"&gt;42 Hz acquisition -&amp;gt;&amp;nbsp;&lt;/strong&gt;&lt;strong data-start="13314" data-end="13331"&gt;EMA filtering -&amp;gt;&amp;nbsp;&lt;/strong&gt;&lt;strong data-start="13336" data-end="13357"&gt;Edge AI inference -&amp;gt;&amp;nbsp;&lt;/strong&gt;&lt;strong data-start="13362" data-end="13389"&gt;Incident classification&lt;/strong&gt;&lt;/p&gt;
&lt;hr data-start="13391" data-end="13394" /&gt;
&lt;h3 id="mcetoc_1k035787u13" data-section-id="6ybnqj" data-start="13396" data-end="13407"&gt;Battery&lt;/h3&gt;
&lt;p data-start="13409" data-end="13444"&gt;&lt;strong data-start="13409" data-end="13444"&gt;Voltage / Current / Temperature -&amp;gt;&amp;nbsp;&lt;/strong&gt;&lt;strong data-start="13449" data-end="13467"&gt;BMS processing -&amp;gt;&amp;nbsp;&lt;/strong&gt;&lt;strong data-start="13472" data-end="13507"&gt;SoC / SoH / OCV / Coulomb Count -&amp;gt;&amp;nbsp;&lt;/strong&gt;&lt;strong data-start="13512" data-end="13521"&gt;RS485 -&amp;gt;&amp;nbsp;&lt;/strong&gt;&lt;strong data-start="13526" data-end="13533"&gt;IDS&lt;/strong&gt;&lt;/p&gt;
&lt;hr data-start="13535" data-end="13538" /&gt;
&lt;h3 id="mcetoc_1k035787u14" data-section-id="c3g0cy" data-start="13540" data-end="13555"&gt;Diagnostics&lt;/h3&gt;
&lt;p data-start="13557" data-end="13574"&gt;&lt;strong data-start="13557" data-end="13574"&gt;RS485 timeout -&amp;gt;&amp;nbsp;&lt;/strong&gt;&lt;strong data-start="13579" data-end="13600"&gt;IDS loopback test -&amp;gt;&amp;nbsp;&lt;/strong&gt;&lt;strong data-start="13605" data-end="13626"&gt;BMS loopback test -&amp;gt;&amp;nbsp;&lt;/strong&gt;&lt;strong data-start="13631" data-end="13661"&gt;Cable fault identification&lt;/strong&gt;&lt;/p&gt;
&lt;hr data-start="13663" data-end="13666" /&gt;
&lt;h3 id="mcetoc_1k035787u15" data-section-id="5b43oa" data-start="13668" data-end="13687"&gt;Power Connector&lt;/h3&gt;
&lt;p data-start="13689" data-end="13704"&gt;&lt;strong data-start="13689" data-end="13704"&gt;BMS voltage -&amp;gt;&amp;nbsp;&lt;/strong&gt;&lt;strong data-start="13709" data-end="13729"&gt;IDS-side voltage -&amp;gt;&amp;nbsp;&lt;/strong&gt;&lt;strong data-start="13734" data-end="13756"&gt;Voltage difference -&amp;gt;&amp;nbsp;&lt;/strong&gt;&lt;strong data-start="13761" data-end="13790"&gt;Connector warning / fault&lt;/strong&gt;&lt;/p&gt;
&lt;hr data-start="13792" data-end="13795" /&gt;
&lt;h3 id="mcetoc_1k035787u16" data-section-id="7ffwto" data-start="13797" data-end="13819"&gt;Emergency Response&lt;/h3&gt;
&lt;p data-start="13821" data-end="13843"&gt;&lt;strong data-start="13821" data-end="13843"&gt;Confirmed accident -&amp;gt;&amp;nbsp;&lt;/strong&gt;&lt;strong data-start="13848" data-end="13860"&gt;Telegram -&amp;gt;&amp;nbsp;&lt;/strong&gt;&lt;strong data-start="13865" data-end="13880"&gt;WebUI alert&lt;/strong&gt;&lt;/p&gt;
&lt;hr data-start="13882" data-end="13885" /&gt;
&lt;p data-start="13887" data-end="13947"&gt;All these paths finally converge at the IDS telemetry layer.&lt;/p&gt;
&lt;hr data-start="13949" data-end="13952" /&gt;
&lt;h1 id="mcetoc_1k035787u17" data-section-id="ydnela" data-start="13954" data-end="13981"&gt;And Now... Let&amp;#39;s Break It&lt;/h1&gt;
&lt;p data-start="13983" data-end="14048"&gt;For me, this is one of the most interesting parts of the project.&lt;/p&gt;
&lt;p data-start="14050" data-end="14179"&gt;Instead of only showing the system working perfectly, I wanted to demonstrate what happens when I intentionally introduce faults.1.&amp;nbsp;&lt;/p&gt;
&lt;ol&gt;
&lt;li id="mcetoc_1k035787u18" data-section-id="qo9cje" data-start="14181" data-end="14208"&gt;&lt;strong&gt;Pull the RS485 cable :&amp;nbsp;&lt;/strong&gt;Does the system identify the communication failure.&lt;/li&gt;
&lt;li data-section-id="qo9cje" data-start="14181" data-end="14208"&gt;&lt;strong&gt;Simulate a transceiver problem :&amp;nbsp;&lt;/strong&gt;Can it distinguish an IDS-side fault from a BMS-side fault?&lt;/li&gt;
&lt;li data-section-id="qo9cje" data-start="14181" data-end="14208"&gt;&lt;strong&gt;Introduce connector degradation:&lt;/strong&gt; Does the voltage comparison detect it&lt;/li&gt;
&lt;li data-section-id="qo9cje" data-start="14181" data-end="14208"&gt;&lt;strong&gt;Simulate an accident: &lt;/strong&gt;Does the ML model confirm the event before sending the alert&lt;/li&gt;
&lt;li data-section-id="qo9cje" data-start="14181" data-end="14208"&gt;&lt;strong&gt;Trigger a confirmed accident:&lt;/strong&gt; Does the Telegram notification reach the registered user?&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-start="14630" data-end="14716"&gt;These tests make the project much more representative of an actual engineering system.&lt;/p&gt;
&lt;hr data-start="14718" data-end="14721" /&gt;
&lt;h1 id="mcetoc_1k035787u1d" data-section-id="1r7wpr5" data-start="14723" data-end="14768"&gt;Complete Integrated System Demonstration&lt;br /&gt;&lt;a href="https://youtu.be/ZM7amrmEOIg"&gt;https://youtu.be/ZM7amrmEOIg&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;This video demonstrates the complete system operating as a single platform, with the BMS and IDS communicating through RS485 while the WebUI displays the combined telemetry.&lt;/p&gt;
&lt;hr data-start="15023" data-end="15026" /&gt;
&lt;h1 id="mcetoc_1k035787u1e" data-section-id="1cj8fcf" data-start="15028" data-end="15058"&gt;Automatic Power Switching&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://youtu.be/iJWrgn_RvO4"&gt;https://youtu.be/iJWrgn_RvO4&lt;/a&gt;&lt;br /&gt;This demonstration focuses specifically on the safety relay and automatic power switching mechanism.&lt;/p&gt;
&lt;hr data-start="15224" data-end="15227" /&gt;
&lt;h1 id="mcetoc_1k035787u1f" data-section-id="11e2l22" data-start="15229" data-end="15263"&gt;Communication Fault Detection&lt;/h1&gt;
&lt;p data-start="15265" data-end="15326"&gt;&lt;strong data-start="15265" data-end="15326"&gt;&lt;a href="https://youtu.be/ciR2tdJ3-0Y"&gt;https://youtu.be/ciR2tdJ3-0Y&lt;/a&gt;&lt;br /&gt;&lt;/strong&gt;Here I intentionally interrupt the communication link and demonstrate how EVA Guardian performs its diagnostic sequence.&lt;/p&gt;
&lt;hr data-start="15450" data-end="15453" /&gt;
&lt;h1 id="mcetoc_1k035787u1g" data-section-id="4x6ulr" data-start="15455" data-end="15487"&gt;Power Connector Degradation&lt;/h1&gt;
&lt;p data-start="15489" data-end="15552"&gt;&lt;strong data-start="15489" data-end="15552"&gt;&lt;a href="https://youtu.be/ZFzXiJqRlYU"&gt;https://youtu.be/ZFzXiJqRlYU&lt;/a&gt;&lt;br /&gt;&lt;/strong&gt;This demonstration shows how the system compares the BMS-side and IDS-side voltage to identify a potentially degrading power connection.&lt;/p&gt;
&lt;hr data-start="15692" data-end="15695" /&gt;
&lt;h1 id="mcetoc_1k035787u1h" data-section-id="m67aon" data-start="15697" data-end="15735"&gt;Moving From Electronics to a Vehicle&lt;/h1&gt;
&lt;p data-start="15737" data-end="15793"&gt;The electronics are now beginning to look like a system. But I wanted the final demonstration to look like a vehicle rather than a collection of development boards sitting on a table. So I started building a small &lt;strong data-start="15953" data-end="15975"&gt;cardboard EV model&lt;/strong&gt;. The intention is to physically represent where the different subsystems would exist in an actual vehicle and route the wiring accordingly. The model is still under development, but it will provide the physical foundation for the final demonstration.&lt;/p&gt;
&lt;hr data-start="16288" data-end="16291" /&gt;
&lt;h1 id="mcetoc_1k035787u1j" data-section-id="1mdehow" data-start="16293" data-end="16317"&gt;The Finishing Touch&lt;/h1&gt;
&lt;p data-start="16319" data-end="16390"&gt;&lt;strong data-start="16319" data-end="16390"&gt;&lt;a href="https://youtu.be/CVnq63eEgyo"&gt;https://youtu.be/CVnq63eEgyo&lt;/a&gt;&lt;br /&gt;&lt;/strong&gt;&lt;em data-start="16392" data-end="16491"&gt;This part of the physical model is currently in progress and will be completed in the next stage.&lt;/em&gt;&lt;/p&gt;
&lt;hr data-start="16493" data-end="16496" /&gt;
&lt;h1 id="mcetoc_1k035787u1k" data-section-id="742nu3" data-start="16498" data-end="16517"&gt;What Has Changed?&lt;/h1&gt;
&lt;p data-start="16519" data-end="16592"&gt;Looking back at the beginning of the project, I had two separate systems.&lt;/p&gt;
&lt;p data-start="16594" data-end="16615"&gt;The BMS could answer:&lt;/p&gt;
&lt;blockquote data-start="16617" data-end="16649"&gt;
&lt;p data-start="16619" data-end="16649"&gt;&lt;strong data-start="16619" data-end="16649"&gt;&amp;quot;How is my battery doing?&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="16651" data-end="16672"&gt;The IDS could answer:&lt;/p&gt;
&lt;blockquote data-start="16674" data-end="16714"&gt;
&lt;p data-start="16676" data-end="16714"&gt;&lt;strong data-start="16676" data-end="16714"&gt;&amp;quot;What is happening to my vehicle?&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="16716" data-end="16771"&gt;After this integration, the system can start answering:&lt;/p&gt;
&lt;blockquote data-start="16773" data-end="16878"&gt;
&lt;p data-start="16775" data-end="16878"&gt;&lt;strong data-start="16775" data-end="16878"&gt;&amp;quot;How is my vehicle doing, how is its battery doing, and can I trust the information I&amp;#39;m receiving?&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="16880" data-end="16925"&gt;That last question is particularly important. Because a smart system should not only provide information. It should also know when &lt;strong data-start="17013" data-end="17057"&gt;the information itself may be unreliable&lt;/strong&gt;. That is why the communication diagnostics, connector monitoring and fault handling became such an important part of this stage.&lt;/p&gt;
&lt;hr data-start="17189" data-end="17192" /&gt;
&lt;h1 id="mcetoc_1k035787u1l" data-section-id="1h02p40" data-start="17194" data-end="17208"&gt;What&amp;#39;s Next?&lt;/h1&gt;
&lt;p data-start="17210" data-end="17301"&gt;The next stage is to move the integrated electronics into the &lt;strong data-start="17272" data-end="17300"&gt;physical EV demonstrator&lt;/strong&gt;.&lt;/p&gt;
&lt;p data-start="17303" data-end="17434"&gt;I am currently working on the cardboard vehicle model that will house the BMS, IDS, power switching and communication architecture.&lt;/p&gt;
&lt;p data-start="17436" data-end="17465"&gt;The next phase will focus on:&lt;/p&gt;
&lt;ul data-start="17467" data-end="17772"&gt;
&lt;li data-section-id="1myzxtw" data-start="17467" data-end="17519"&gt;Integrating the electronics into the vehicle model&lt;/li&gt;
&lt;li data-section-id="1567ruq" data-start="17520" data-end="17569"&gt;Finalizing the automatic safety power switching&lt;/li&gt;
&lt;li data-section-id="1cutsmj" data-start="17570" data-end="17590"&gt;Refining the WebUI&lt;/li&gt;
&lt;li data-section-id="mvg60j" data-start="17591" data-end="17637"&gt;Improving fault indication and system status&lt;/li&gt;
&lt;li data-section-id="wb2j41" data-start="17638" data-end="17693"&gt;Demonstrating the complete end-to-end safety workflow&lt;/li&gt;
&lt;li data-section-id="1uiwuk1" data-start="17694" data-end="17772"&gt;Presenting the system as a single EV platform rather than individual modules&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-start="17774" data-end="17821"&gt;The goal for the final demonstration is simple:&lt;/p&gt;
&lt;blockquote data-start="17823" data-end="17869"&gt;
&lt;p data-start="17825" data-end="17869"&gt;&lt;strong data-start="17825" data-end="17869"&gt;Don&amp;#39;t just show that EVA Guardian works.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote data-start="17871" data-end="17921"&gt;
&lt;p data-start="17873" data-end="17921"&gt;&lt;strong data-start="17873" data-end="17921"&gt;Show what happens when something goes wrong.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="17923" data-end="17982"&gt;That is where I believe the real value of the project lies.&lt;/p&gt;
&lt;hr data-start="17984" data-end="17987" /&gt;
&lt;h1 id="mcetoc_1k035787v1m" data-section-id="1329ug4" data-start="17989" data-end="18005"&gt;Final Thoughts&lt;/h1&gt;
&lt;p data-start="18007" data-end="18182"&gt;This stage has been one of the most interesting parts of EVA Guardian because I moved away from developing individual features and started thinking about &lt;strong data-start="18161" data-end="18181"&gt;system behaviour&lt;/strong&gt;. A real vehicle doesn&amp;#39;t operate in perfect conditions.&lt;/p&gt;
&lt;ul&gt;
&lt;li data-start="18007" data-end="18182"&gt;Cables can fail.&lt;/li&gt;
&lt;li data-start="18007" data-end="18182"&gt;Connectors can degrade.&lt;/li&gt;
&lt;li data-start="18007" data-end="18182"&gt;Sensors can produce abnormal values.&lt;/li&gt;
&lt;li data-start="18007" data-end="18182"&gt;Communication links can disappear.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-start="18356" data-end="18395"&gt;And an accident can happen at any time. The objective of EVA Guardian is therefore not simply to detect these events. It is to &lt;strong data-start="18485" data-end="18532"&gt;identify, communicate, diagnose and respond&lt;/strong&gt; to them. And with the BMS and IDS now working together, EVA Guardian is getting much closer to the original vision:&lt;/p&gt;
&lt;blockquote data-start="18651" data-end="18793"&gt;
&lt;p data-start="18653" data-end="18793"&gt;&lt;strong data-start="18653" data-end="18793"&gt;A vehicle that can sense what is happening, understand the situation, identify when something is wrong, and take the appropriate action.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="18795" data-end="18882" data-is-last-node="" data-is-only-node=""&gt;&lt;strong data-start="18795" data-end="18882" data-is-last-node=""&gt;The two systems are now talking.&lt;br data-start="18829" data-end="18832" /&gt; The next step is to put them inside the vehicle.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Github Repo:&lt;a href="https://github.com/ForgedCircuits/EVA-Guardian.git" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;&amp;nbsp;https://github.com/ForgedCircuits/EVA-Guardian.git&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;</description></item><item><title>Challengers, Post your Projects!</title><link>https://community.element14.com/thread/57178?ContentTypeID=0</link><pubDate>Fri, 14 Aug 2026 15:12:06 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:611482fc-ee00-45d8-a65c-da153efd9975</guid><dc:creator>cstanton</dc:creator><slash:comments>14</slash:comments><comments>https://community.element14.com/thread/57178?ContentTypeID=0</comments><wfw:commentRss>https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57178/challengers-post-your-projects/rss?ContentTypeId=0</wfw:commentRss><description>&lt;p&gt;All accepted challengers should now be able to post their final write-ups in:&amp;nbsp;&amp;nbsp;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/b/projects"&gt;Projects&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;We&amp;#39;ve had some team members out of the office, so this may be a little short notice (we usually give a week for write-ups) so if you&amp;#39;re a&amp;nbsp;&lt;em&gt;few&lt;/em&gt; days&lt;strong&gt; &lt;/strong&gt;late&amp;nbsp;I think&amp;nbsp;&lt;a href="https://community.element14.com/members/joratcliffe"&gt;JoRatcliffe&lt;/a&gt;&amp;nbsp;will forgive you, but you have to let us know.&lt;/p&gt;
&lt;p&gt;Good luck&amp;nbsp;&lt;span class="emoticon" data-url="https://community.element14.com/cfs-file/__key/system/emoji/1f642.svg" title="Slight smile"&gt;&amp;#x1f642;&lt;/span&gt;&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;</description></item><item><title>Post 3 Project GEPARD: The Bridge Was Not One Bug</title><link>https://community.element14.com/thread/57176?ContentTypeID=0</link><pubDate>Fri, 14 Aug 2026 13:30:54 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:4b8a949d-3be5-49e9-b3f2-f267f132b42c</guid><dc:creator>UlolKidz</dc:creator><slash:comments>0</slash:comments><comments>https://community.element14.com/thread/57176?ContentTypeID=0</comments><wfw:commentRss>https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57176/post-3-project-gepard-the-bridge-was-not-one-bug/rss?ContentTypeId=0</wfw:commentRss><description>&lt;p&gt;How I misunderstood the Arduino UNO Q Bridge three different ways before the architecture finally made sense.&amp;nbsp;&lt;/p&gt;
&lt;div&gt;Special thanks to &lt;strong&gt;ralphjy&lt;/strong&gt; and &lt;strong&gt;BigG&lt;/strong&gt; for taking the time to comment on my original vision post. The significance of your suggestions only became clearer as the project evolved, and they ultimately helped guide me toward the architecture I use today.&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;a href="https://youtube.com/shorts/0RKAN0TM7Mg?feature=share"&gt;youtube.com/.../0RKAN0TM7Mg&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;div&gt;
&lt;h2 id="mcetoc_1k00794270"&gt;Sometimes Advice Makes More Sense in Hindsight&lt;/h2&gt;
&lt;p&gt;When I published my first vision post for Project GEPARD, several members of the Element14 community shared suggestions about how I should approach the software architecture.&lt;/p&gt;
&lt;p&gt;One comment suggested looking deeper into the UNO Q Bridge system and the documented examples rather than building a custom communication layer from scratch.&lt;/p&gt;
&lt;p&gt;Another highlighted something I had underestimated at the time: the Arduino UNO Q is not simply another Arduino board. It combines a Linux computer and a microcontroller on the same platform, and understanding that distinction is important when designing larger applications.&lt;/p&gt;
&lt;p&gt;At the time, those suggestions made sense intellectually, but I had not yet encountered the problems they were trying to help me avoid.&lt;/p&gt;
&lt;p&gt;So I continued developing the architecture I had already planned.&lt;/p&gt;
&lt;p&gt;For a while, everything seemed to be moving in the right direction. Components came online, code compiled, and individual subsystems appeared to work.&lt;/p&gt;
&lt;p&gt;As the project grew, however, the cracks started to appear.&lt;/p&gt;
&lt;p&gt;The telemetry architecture became increasingly difficult to reason about.&lt;/p&gt;
&lt;p&gt;The control system became harder to debug.&lt;/p&gt;
&lt;p&gt;Subsystems that worked independently did not always work together.&lt;/p&gt;
&lt;p&gt;Fixing one problem often revealed another.&lt;/p&gt;
&lt;p&gt;Eventually I found myself spending more time fighting the architecture than building the rover.&lt;/p&gt;
&lt;p&gt;That was when I went back to the documentation, reread the community feedback, studied example projects, and looked more carefully at how the UNO Q was designed to operate.&lt;/p&gt;
&lt;p&gt;What I discovered was not that my original ideas were completely wrong.&lt;/p&gt;
&lt;p&gt;It was that I was trying to use the UNO Q like a traditional microcontroller platform when it was designed to operate as a dual-processor system.&lt;/p&gt;
&lt;p&gt;Once I understood that distinction, several weeks of confusing software issues suddenly started making sense.&lt;/p&gt;
&lt;p&gt;This post is about those lessons.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;h1 id="mcetoc_1k00794271"&gt;&lt;/h1&gt;
&lt;h1 id="mcetoc_1k00794272"&gt;Bridge Issue #1: I Treated It Like a Serial Cable&lt;/h1&gt;
&lt;p&gt;My original mental model looked something like this:&lt;/p&gt;
&lt;div&gt;&lt;span&gt;STM32&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt; &amp;darr;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt;Push telemetry&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt; &amp;darr;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt;Linux&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt; &amp;darr;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt;Dashboard&lt;/span&gt;&lt;/div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;That seems reasonable.&lt;/p&gt;
&lt;p&gt;The microcontroller gathers data and pushes it upward whenever it wants.&lt;/p&gt;
&lt;p&gt;That is how I had structured a significant portion of the project.&lt;/p&gt;
&lt;p&gt;The UNO Q does not work that way.&lt;/p&gt;
&lt;p&gt;The UNO Q Bridge is designed around a request-response model.&lt;/p&gt;
&lt;p&gt;The Linux side initiates communication.&lt;/p&gt;
&lt;p&gt;The STM32 responds.&lt;/p&gt;
&lt;p&gt;Once I finally understood that, the architecture changed from:&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;&lt;span&gt;Bridge.notify(&amp;quot;telemetry&amp;quot;, distance, voltage, ticks);&lt;/span&gt;&lt;/div&gt;
&lt;p&gt;to:&lt;/p&gt;
&lt;p&gt;Bridge.provide(&amp;quot;get_telemetry&amp;quot;, get_telemetry);&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;and&lt;/p&gt;
&lt;p&gt;telemetry = Bridge.call(&amp;quot;get_telemetry&amp;quot;)&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The difference sounds tiny.&lt;/p&gt;
&lt;p&gt;The impact was enormous.&lt;/p&gt;
&lt;p&gt;Instead of trying to continuously push data upward, the Linux side now requests data when it actually needs it.&lt;/p&gt;
&lt;p&gt;That immediately simplified the entire system.&lt;/p&gt;
&lt;h1 id="mcetoc_1k00794273"&gt;Bridge Issue #2: The Motors Worked in the Test Sketch but Not in the Real Firmware&lt;/h1&gt;
&lt;p&gt;At one point I had a very confusing problem.&lt;/p&gt;
&lt;p&gt;The motors worked perfectly.&lt;/p&gt;
&lt;p&gt;Until they didn&amp;#39;t.&lt;/p&gt;
&lt;p&gt;My simple diagnostic sketch spun the wheels every time.&lt;/p&gt;
&lt;p&gt;My &amp;quot;real&amp;quot; firmware did not.&lt;/p&gt;
&lt;p&gt;From the outside that sounds like a wiring issue.&lt;/p&gt;
&lt;p&gt;It wasn&amp;#39;t.&lt;/p&gt;
&lt;p&gt;The multimeter eventually gave me the answer:&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;&lt;span&gt;VM = 10.97 V&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt;STBY = 3.3 V&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt;PWMA = 0 V&lt;/span&gt;&lt;/div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The motor driver had power.&lt;/p&gt;
&lt;p&gt;The standby pin was enabled.&lt;/p&gt;
&lt;p&gt;The PWM signal simply never existed.&lt;/p&gt;
&lt;p&gt;That pointed directly at the software stack.&lt;/p&gt;
&lt;p&gt;The root cause turned out to be a combination of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Incorrect Bridge architecture&lt;/li&gt;
&lt;li&gt;Telemetry logic&lt;/li&gt;
&lt;li&gt;A safety override that was stopping forward motion&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ironically, my safety feature was preventing me from proving the motors worked.&lt;/p&gt;
&lt;p&gt;Once I removed the unnecessary override and rebuilt the communication architecture around proper Bridge calls, the motors came back to life.&lt;/p&gt;
&lt;h1 id="mcetoc_1k00794274"&gt;Bridge Issue #3: Not All Providers Are Equal&lt;/h1&gt;
&lt;p&gt;This one took me much longer to understand.&lt;/p&gt;
&lt;p&gt;I originally treated these as interchangeable:&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;&lt;span&gt;Bridge.provide(...)&lt;/span&gt;&lt;/div&gt;
&lt;p&gt;and&lt;/p&gt;
&lt;p&gt;Bridge.provide_safe(...)&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;They are not.&lt;/p&gt;
&lt;p&gt;The distinction matters.&lt;/p&gt;
&lt;p&gt;Functions that touch hardware:&lt;/p&gt;
&lt;p&gt;digitalWrite(...)&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;&lt;span&gt;analogWrite(...)&lt;/span&gt;&lt;/div&gt;
&lt;p&gt;should use:&lt;/p&gt;
&lt;p&gt;Bridge.provide_safe(...)&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Examples:&lt;/p&gt;
&lt;p&gt;set_drive()&lt;br /&gt;&lt;span&gt;set_estop()&lt;br /&gt;&lt;/span&gt;&lt;span&gt;test_module()&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;These affect the physical world.&lt;/p&gt;
&lt;p&gt;Motors move.&lt;/p&gt;
&lt;p&gt;Sensors trigger.&lt;/p&gt;
&lt;p&gt;Pins change state.&lt;/p&gt;
&lt;p&gt;Those belong in the safe execution context.&lt;/p&gt;
&lt;p&gt;On the other hand:&lt;/p&gt;
&lt;p&gt;get_telemetry()&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;should ideally be a read-only operation.&lt;/p&gt;
&lt;p&gt;That means it can use:&lt;/p&gt;
&lt;p&gt;Bridge.provide(...)&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;&lt;br /&gt;if it remains short and thread-safe.&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;p&gt;That led directly into the next mistake.&lt;/p&gt;
&lt;h1 id="mcetoc_1k00794275"&gt;Bridge Issue #4: My Telemetry Function Wasn&amp;#39;t Actually Read-Only&lt;/h1&gt;
&lt;p&gt;I thought this function was simple:&lt;/p&gt;
&lt;p&gt;get_telemetry()&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;It wasn&amp;#39;t.&lt;/p&gt;
&lt;p&gt;Inside it I was:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Triggering ultrasonic pulses&lt;/li&gt;
&lt;li&gt;Reading I2C sensors&lt;/li&gt;
&lt;li&gt;Waiting on pulse timing&lt;/li&gt;
&lt;li&gt;Accessing hardware directly&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Which meant the function wasn&amp;#39;t actually just returning telemetry.&lt;/p&gt;
&lt;p&gt;It was collecting telemetry on demand.&lt;/p&gt;
&lt;p&gt;That sounds harmless until you realize the ultrasonic routine alone can block for milliseconds.&lt;/p&gt;
&lt;p&gt;The fix was to separate two jobs.&lt;/p&gt;
&lt;p&gt;Instead of:&lt;/p&gt;
&lt;p&gt;Request &amp;rarr; Read Sensors &amp;rarr; Return&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I moved to:&lt;/p&gt;
&lt;p&gt;Loop()&lt;br /&gt;&lt;span style="font-family:inherit;"&gt;&amp;darr;&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;&lt;span&gt;Sample everything continuously&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt; &amp;darr;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt;Cache data&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt;get_telemetry()&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt; &amp;darr;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt;Return cached copy&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;p&gt;Now the microcontroller continuously updates a telemetry cache and the Bridge simply serves the latest values.&lt;/p&gt;
&lt;p&gt;The function became short.&lt;/p&gt;
&lt;p&gt;Predictable.&lt;/p&gt;
&lt;p&gt;Thread-safe.&lt;/p&gt;
&lt;p&gt;And much easier to reason about.&lt;/p&gt;
&lt;h1 id="mcetoc_1k00794276"&gt;The Surprise Bug: The Sketch Wasn&amp;#39;t Even the Main Problem&lt;/h1&gt;
&lt;p&gt;At one point I became convinced the firmware itself was broken.&lt;/p&gt;
&lt;p&gt;But after rebuilding the project structure I discovered that many issues were actually App Lab configuration problems.&lt;/p&gt;
&lt;p&gt;Things that changed included:&lt;/p&gt;
&lt;h3 id="mcetoc_1k00794277"&gt;Old Architecture&lt;/h3&gt;
&lt;p&gt;Laptop application&lt;br /&gt;&lt;span&gt;UDP transport&lt;br /&gt;&lt;/span&gt;&lt;span&gt;Custom dashboard&lt;br /&gt;&lt;/span&gt;&lt;span&gt;Mixed project structure&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h3 id="mcetoc_1k00794278"&gt;New Architecture&lt;/h3&gt;
&lt;p&gt;UNO Q Linux application&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;&lt;span&gt;Official WebUI Brick&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt;Bridge RPC&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt;App Lab project structure&lt;/span&gt;&lt;/div&gt;
&lt;p&gt;I also discovered things like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Project structure matters more than I thought.&lt;/li&gt;
&lt;li&gt;UNO Q examples assume a specific layout.&lt;/li&gt;
&lt;li&gt;The WebUI Brick was solving problems I was manually creating.&lt;/li&gt;
&lt;li&gt;I was fighting the platform instead of using it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That was a humbling realization.&lt;/p&gt;
&lt;h1 id="mcetoc_1k00794279"&gt;The Comment I Should Have Listened To&lt;/h1&gt;
&lt;p&gt;Looking back, the most frustrating part is that several people had effectively pointed me toward the solution weeks earlier.&lt;/p&gt;
&lt;p&gt;Not the exact solution.&lt;/p&gt;
&lt;p&gt;But the correct direction.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;div&gt;I had not yet encountered the problems that made those suggestions valuable.&lt;/div&gt;
&lt;br /&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;I tend to learn in a very stubborn way:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Build the thing.&lt;/li&gt;
&lt;li&gt;Assume the thing should work.&lt;/li&gt;
&lt;li&gt;Watch it fail.&lt;/li&gt;
&lt;li&gt;Spend hours troubleshooting.&lt;/li&gt;
&lt;li&gt;Finally accept that the original assumption was wrong.&lt;/li&gt;
&lt;li&gt;Open the documentation.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I joke about it, but Project GEPARD has forced me to become better at step 6.&lt;/p&gt;
&lt;h1 id="mcetoc_1k0079427a"&gt;The Architecture We Settled On&lt;/h1&gt;
&lt;p&gt;After all of the redesigns, experimentation, broken assumptions, and late-night troubleshooting sessions, the architecture finally became simple.&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;&lt;span&gt;Browser HUD&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt; │&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt; ▼&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt;UNO Q Linux (Brain)&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt; │&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt; Bridge.call()&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt; │&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt; ▼&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt;STM32 (Spine)&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt; │&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt; ▼&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt;Motors, sensors, hardware&lt;/span&gt;&lt;/div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The responsibilities are now clear.&lt;/p&gt;
&lt;h2 id="mcetoc_1k0079427b"&gt;The Spine (STM32)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Reads sensors&lt;/li&gt;
&lt;li&gt;Controls motors&lt;/li&gt;
&lt;li&gt;Handles hardware timing&lt;/li&gt;
&lt;li&gt;Provides telemetry&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="mcetoc_1k0079427c"&gt;The Brain (Linux)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Collects telemetry&lt;/li&gt;
&lt;li&gt;Hosts the WebUI dashboard&lt;/li&gt;
&lt;li&gt;Runs autonomy logic&lt;/li&gt;
&lt;li&gt;Interfaces with future AI systems&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="mcetoc_1k0079427d"&gt;The Browser&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Displays the tactical HUD&lt;/li&gt;
&lt;li&gt;Shows telemetry&lt;/li&gt;
&lt;li&gt;Sends commands&lt;/li&gt;
&lt;li&gt;Works from a laptop or phone&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The architecture finally matches the way the UNO Q was designed to work.&lt;/p&gt;
&lt;h1 id="mcetoc_1k0079427e"&gt;Current Status&lt;/h1&gt;
&lt;p&gt;&lt;span class="emoticon" data-url="https://community.element14.com/cfs-file/__key/system/emoji/2705.svg" title="White check mark"&gt;&amp;#x2705;&lt;/span&gt;&amp;nbsp;Motors responding&lt;/p&gt;
&lt;p&gt;&lt;span class="emoticon" data-url="https://community.element14.com/cfs-file/__key/system/emoji/2705.svg" title="White check mark"&gt;&amp;#x2705;&lt;/span&gt;&amp;nbsp;Bridge architecture redesigned&lt;/p&gt;
&lt;p&gt;&lt;span class="emoticon" data-url="https://community.element14.com/cfs-file/__key/system/emoji/2705.svg" title="White check mark"&gt;&amp;#x2705;&lt;/span&gt;&amp;nbsp;WebUI architecture established&lt;/p&gt;
&lt;p&gt;&lt;span class="emoticon" data-url="https://community.element14.com/cfs-file/__key/system/emoji/2705.svg" title="White check mark"&gt;&amp;#x2705;&lt;/span&gt;&amp;nbsp;Ultrasonic testing functional&lt;/p&gt;
&lt;p&gt;&lt;span class="emoticon" data-url="https://community.element14.com/cfs-file/__key/system/emoji/2705.svg" title="White check mark"&gt;&amp;#x2705;&lt;/span&gt;&amp;nbsp;Individual subsystem testing implemented&lt;/p&gt;
&lt;p&gt;&lt;span class="emoticon" data-url="https://community.element14.com/cfs-file/__key/system/emoji/2705.svg" title="White check mark"&gt;&amp;#x2705;&lt;/span&gt;&amp;nbsp;Brain / spine architecture finalized&lt;/p&gt;
&lt;p&gt;&lt;span class="emoticon" data-url="https://community.element14.com/cfs-file/__key/system/emoji/26a0.svg" title="Warning"&gt;&amp;#x26a0;&lt;/span&gt;️ INA226 still being validated&lt;/p&gt;
&lt;p&gt;&lt;span class="emoticon" data-url="https://community.element14.com/cfs-file/__key/system/emoji/26a0.svg" title="Warning"&gt;&amp;#x26a0;&lt;/span&gt;️ MPU6050 still being validated&lt;/p&gt;
&lt;p&gt;&lt;span class="emoticon" data-url="https://community.element14.com/cfs-file/__key/system/emoji/26a0.svg" title="Warning"&gt;&amp;#x26a0;&lt;/span&gt;️ Encoder tuning still required&lt;/p&gt;
&lt;p&gt;&lt;span class="emoticon" data-url="https://community.element14.com/cfs-file/__key/system/emoji/1f527.svg" title="Wrench"&gt;&amp;#x1f527;&lt;/span&gt;&amp;nbsp;Final assembly underway&lt;/p&gt;
&lt;h1 id="mcetoc_1k0079427f"&gt;Final Thoughts&lt;/h1&gt;
&lt;p&gt;I think one of the most important lessons from this challenge is that being wrong is not the problem.&lt;/p&gt;
&lt;p&gt;Staying wrong is.&lt;/p&gt;
&lt;p&gt;The UNO Q Bridge was not one bug.&lt;/p&gt;
&lt;p&gt;It was a chain of misunderstandings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Wrong communication model&lt;/li&gt;
&lt;li&gt;Wrong assumptions about providers&lt;/li&gt;
&lt;li&gt;Wrong telemetry strategy&lt;/li&gt;
&lt;li&gt;Wrong project architecture&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each fix revealed the next problem underneath it.&lt;/p&gt;
&lt;p&gt;And honestly, that is probably the most realistic description of engineering I can give.&lt;/p&gt;
&lt;p&gt;You don&amp;#39;t usually solve the problem.&lt;/p&gt;
&lt;p&gt;You solve enough problems that eventually only the real problem remains.&lt;/p&gt;
&lt;p&gt;More soon.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Bridge works. Mostly.&lt;/strong&gt;&amp;nbsp;&lt;span class="emoticon" data-url="https://community.element14.com/cfs-file/__key/system/emoji/1f680.svg" title="Rocket"&gt;&amp;#x1f680;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/div&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;</description></item><item><title>Part 2 Project GEPARD: Scope Creep, Smoke, and the 3.3 Volts That Broke Everything</title><link>https://community.element14.com/thread/57175?ContentTypeID=0</link><pubDate>Thu, 13 Aug 2026 14:07:17 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:50233ef7-f8c1-4d9d-ad7b-277e183cb185</guid><dc:creator>UlolKidz</dc:creator><slash:comments>3</slash:comments><comments>https://community.element14.com/thread/57175?ContentTypeID=0</comments><wfw:commentRss>https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57175/part-2-project-gepard-scope-creep-smoke-and-the-3-3-volts-that-broke-everything/rss?ContentTypeId=0</wfw:commentRss><description>&lt;p&gt;&lt;em&gt;An honest devlog of how a tank became a car, a &amp;quot;simple&amp;quot; telemetry link ate 3 weeks, and why the pile of failed prints in the corner is the most useful part of this project.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1jvtivdi00"&gt;Intro: the gap between the render and the reality&lt;/h2&gt;
&lt;p&gt;When I pitched Project GEPARD, the vision was clean: a tracked, Ripsaw-inspired autonomous UGV with a sensor turret, self-docking, on-board AI, and a tactical command dashboard. Scaled-down EV-fleet tech in a one-foot rover.&lt;/p&gt;
&lt;p&gt;What I actually have, weeks later, is a wheeled rover, a graveyard of failed prints, a multimeter I now can&amp;#39;t live without, some chips I need to re-order, and a much deeper respect for the phrase &lt;em&gt;&amp;quot;it&amp;#39;s just a wiring problem.&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This post is the honest version &amp;mdash; every place the plan bent, every thing that released the magic smoke, and what I learned. The polished final build is the last post. This one is the mess in the middle, because that&amp;#39;s where the actual engineering happened.&lt;/p&gt;
&lt;h2 id="mcetoc_1jvtivdi01"&gt;Part 1: The Great Pivots (Scope Creep in Reverse)&lt;/h2&gt;
&lt;p&gt;Most scope creep &lt;em&gt;adds&lt;/em&gt; features. Mine mostly &lt;em&gt;removed&lt;/em&gt; them &amp;mdash; the harder and more valuable kind.&lt;/p&gt;
&lt;p&gt;Pivot 1 &amp;mdash; Tracks &amp;rarr; Wheels (a printing defeat, told honestly). The whole identity was the aggressive tracked &amp;quot;Ripsaw&amp;quot; look, and I fought &lt;em&gt;hard&lt;/em&gt; for it. I printed multiple sprocket designs and tested three different track-link designs, and hit a wall of print-reality problems the CAD never warns you about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Getting a printed track to actually seat and travel over the sprocket &amp;mdash; it kept binding or skipping teeth.&lt;/li&gt;
&lt;li&gt;I redesigned the sprocket for dual-tooth engagement to grip better. It failed again.&lt;/li&gt;
&lt;li&gt;The real killer was tolerances and print settings &amp;mdash; the gap between &amp;quot;looks right in CAD&amp;quot; and &amp;quot;flexes, meshes, and holds under load in PETG/TPU&amp;quot; was a tuning rabbit hole I couldn&amp;#39;t climb out of in the time I had.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Eventually I made the honest call: switch to a differential wheeled drivetrain. Same tank-steer control, same motor driver, same code &amp;mdash; but &lt;em&gt;far&lt;/em&gt; better odometry (tracked skid-steer scrubs sideways and wrecks the encoder data any future SLAM needs), much faster to print, and it deleted my single highest-risk mechanical subsystem in one move. I lost the aesthetic. I kept the deadline. Sometimes engineering is knowing when to stop fine-tuning a thing that isn&amp;#39;t converging.&lt;/p&gt;
&lt;p&gt;Pivot 2 &amp;mdash; Tri-Node Compute &amp;rarr; &amp;quot;the board already does this.&amp;quot; The original plan was three brains: Arduino for motors, ESP32 for Wi-Fi, a PC for heavy lifting. Then I actually read the Arduino UNO Q datasheet and realized it&amp;#39;s &lt;em&gt;already&lt;/em&gt; a dual-brain board &amp;mdash; an STM32 real-time MCU plus a Qualcomm Linux computer with built-in Wi-Fi 5. I was bolting a weaker ESP32 Wi-Fi onto a board that already had a better one. The ESP32 got demoted to &lt;em&gt;just&lt;/em&gt; the camera.&lt;/p&gt;
&lt;p&gt;Pivot 3 &amp;mdash; &amp;quot;Do Everything&amp;quot; AI &amp;rarr; a finishable MVP. The design doc had Google Cast control, Workspace alarms, Bluetooth audio, cloud upload, facial recognition (owner vs. stranger vs. &lt;em&gt;pet vs. rat&lt;/em&gt;), and IR-beacon precision docking &amp;mdash; on a four-week clock. I cut all cloud/Bluetooth, kept and simplified manual drive + obstacle avoidance (the real MVP), offline voice, and April tag-marker docking instead of research-grade IR beacons. Face-rec, SLAM, and full autonomy became a documented Phase-2 roadmap instead of broken half-features.&lt;/p&gt;
&lt;p&gt;Pivot 4 &amp;mdash; The rear idler: sliding mount &amp;rarr; eccentric cam &amp;rarr; just a wheel. This one&amp;#39;s a perfect little case study in over-engineering and then coming to my senses. The rear support started as a sliding-mount idler (slot + screw to tension). Then I got fancy and redesigned it as a 12-sided eccentric-cam idler &amp;mdash; rotate the cam, move the axle, tension the track. Genuinely clever&amp;hellip; for a track system I no longer had after Pivot 1. Once the tracks were gone, the whole reason for a tensioning idler evaporated. It collapsed down to what it should have been all along: a plain free-rolling wheel on a bearing, with a simple printed circle spacer to hold it off the chassis wall. Three designs to arrive at &amp;quot;a wheel.&amp;quot;&lt;/p&gt;
&lt;p&gt;Pivot 5 &amp;mdash; The Docking Dream &amp;rarr; a cardboard box with a printed face. Auto-docking is one of the most failure-prone things in mobile robotics. The honest MVP: a cardboard enclosure for the relay + a printed faceplate holding the pogo pads and ArUco marker at the right height. Proof of concept, documented as such.&lt;/p&gt;
&lt;h2 id="mcetoc_1jvtivdi02"&gt;Part 2: The Errors (the actually-useful part)&lt;/h2&gt;
&lt;p&gt;Error 1 &amp;mdash; The 3.3 V that broke everything. The UNO Q &lt;em&gt;looks&lt;/em&gt; like a 5 V Arduino. It is not &amp;mdash; its headers are 3.3 V logic. My spec said &amp;quot;all sensors 5 V.&amp;quot; That mismatch meant the HC-SR04&amp;#39;s 5 V ECHO needed level-shifting, and only D2/D3 are bench-verified for interrupts on its STM32 &amp;mdash; so my five-IR-encoders-on-five-interrupts plan was a coin-flip. Fix: two encoders on the two proven interrupt pins, the rest polled.&lt;/p&gt;
&lt;p&gt;Error 2 &amp;mdash; Powering I2C sensors at 5 V killed the whole bus. My INA226 &lt;em&gt;and&lt;/em&gt; MPU6050 both showed offline at once. I assumed two dead boards. Wrong. Powering them from 5 V made their pull-up resistors drag the I2C lines to 5 V, so the 3.3 V UNO Q couldn&amp;#39;t talk to &lt;em&gt;either&lt;/em&gt;. Three symptoms, one cause. Fix: moved one wire from 5 V to 3.3 V; both woke up. Lesson: when multiple boards fail at once, look for the one shared thing &amp;mdash; a rail, a ground, a bus &amp;mdash; not multiple faults.&lt;/p&gt;
&lt;p&gt;Error 3 &amp;mdash; The 1 a.m. wiring mistake that cost me chips. Late-night bench work is a trap. At 1 in the morning I wired the ultrasonic sensor backwards &amp;mdash; VCC into ground, ground into VCC. Reverse-polarity into a sensor is exactly how you release the magic smoke. That chip (and a couple of friends) are now on a re-order list. Lesson: past midnight, walk away. The mistake you make tired costs more than the sleep would have.&lt;/p&gt;
&lt;p&gt;Error 4 &amp;mdash; The motors that wouldn&amp;#39;t spin (my code, not my wiring). My raw test sketch spun the motors. My &amp;quot;real&amp;quot; firmware left PWMA reading 0 V on the multimeter. Same wiring, so it was software. Two culprits, both mine: (1) an obstacle-override &amp;quot;safety&amp;quot; that force-stopped forward motion when the ultrasonic read too close &lt;em&gt;or wasn&amp;#39;t wired&lt;/em&gt; &amp;mdash; my safety feature was stopping the wheels; (2) the Bridge used backwards. The UNO Q&amp;#39;s two brains talk over an RPC &amp;quot;Bridge,&amp;quot; and the official rule is: &lt;em&gt;the MCU never initiates &amp;mdash; Python always makes the call, the MCU only responds.&lt;/em&gt; My code had the MCU &lt;em&gt;pushing&lt;/em&gt; telemetry, which silently fails. The fix is Python polling the MCU.&lt;/p&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td width="601"&gt;
&lt;p&gt;&amp;nbsp; &amp;nbsp;WRONG (what I did): MCU tries to push data at Linux &amp;mdash; silently drops&lt;/p&gt;
&lt;p&gt;&amp;nbsp;Bridge.notify(&amp;quot;telemetry&amp;quot;, distance, voltage, ticks /* ... */);&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;RIGHT: MCU exposes a function; Linux POLLS it when ready&lt;/p&gt;
&lt;p&gt;void get_telemetry() { /* return sensor struct */ }&lt;/p&gt;
&lt;p&gt;void setup() {&lt;/p&gt;
&lt;p&gt;Bridge.begin();&lt;/p&gt;
&lt;p&gt;Bridge.provide_safe(&amp;quot;get_telemetry&amp;quot;, get_telemetry);&amp;nbsp; // MCU responds only&lt;/p&gt;
&lt;p&gt;Bridge.provide_safe(&amp;quot;set_drive&amp;quot;,&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; set_drive);&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The multimeter ended the guessing: STBY = 3.3 V (driver awake), VM = 10.97 V (power good), PWMA = 0 V on &amp;quot;forward&amp;quot; (no signal) &amp;rarr; the code, not the wiring.&lt;/p&gt;
&lt;p&gt;Error 5 &amp;mdash; Servo.h freezes the board. A diagnostic sketch used the classic AVR Servo.h. On the UNO Q&amp;#39;s STM32/Zephyr stack it grabs a hardware timer the motor PWM needs &amp;mdash; servo swept once, board froze. Classic &amp;quot;treat the UNO Q like a 5 V ATmega&amp;quot; trap.&lt;/p&gt;
&lt;p&gt;Error 6 &amp;mdash; The suppressed holes I found 25 hours too late. While cleaning up the rear chassis CAD, I suppressed the mounting holes and forgot to un-suppress them before hitting print. I noticed after the 25-hour print finished. No way I&amp;#39;m reprinting a full day of filament four days from deadline &amp;mdash; so the fix became a printed drill-jig to re-add the holes with a soldering iron. Lesson: a pre-print checklist (&amp;quot;are all the holes actually there?&amp;quot;) is cheaper than 25 hours.&lt;/p&gt;
&lt;p&gt;Error 7 &amp;mdash; I flipped the battery bay&amp;#39;s length and width. Here&amp;#39;s a fun one. I got the orientation of the battery bay wrong in CAD &amp;mdash; swapped length and width &amp;mdash; so when I went to install the 464 g battery pack, it didn&amp;#39;t fit lying down the way I&amp;#39;d planned. My only option post-print was to stand the battery up vertically at the very back. That single mistake shoved the center of mass rearward, which then forced me to actually sit down and do a tipping calculation to make sure the rover wouldn&amp;#39;t wheelie backward. (It won&amp;#39;t &amp;mdash; the CoM lands ~95 mm from the rear, inside the wheelbase, so it&amp;#39;s stable, just rear-biased.) An orientation typo in a sketch turned into a physics homework problem. Lesson: double-check which dimension is length before you commit a 20-hour print.&lt;/p&gt;
&lt;p&gt;Error 8 &amp;mdash; The pan-tilt turret that ended in double-sided tape. The camera turret went through &lt;em&gt;rounds&lt;/em&gt; of design &amp;rarr; print &amp;rarr; reprint: servo pockets that didn&amp;#39;t fit, mounts that didn&amp;#39;t hold, a built-in-spacer-vs-bearing conflict. After enough reprints eating time I didn&amp;#39;t have, I did the honest maker thing and mounted it with double-sided tape.&amp;nbsp;Not elegant. It works. Four days out, &amp;quot;works&amp;quot; wins.&amp;nbsp;&lt;span class="emoticon" data-url="https://community.element14.com/cfs-file/__key/system/emoji/1f605.svg" title="Sweat smile"&gt;&amp;#x1f605;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Error 9 &amp;mdash; The print farm of sadness. 23-hour chassis prints (fine layers + high infill) &amp;rarr; fixed with 0.28 mm layers, ~15&amp;ndash;20% infill, 3 walls, and supports OFF. Warping on big flat PETG &amp;rarr; brim, non-negotiable. An extruder squeal/skip (filament pushed then backed out) turned out to be wet PETG &amp;mdash; drying the spool fixed the squeal, the stringing, &lt;em&gt;and&lt;/em&gt; the surface finish at once. Wiggly soldered pins = cold joints; a board can read &amp;quot;dead&amp;quot; when it&amp;#39;s really an intermittent joint, so re-flow before you condemn it.&lt;/p&gt;
&lt;p&gt;Error 10 &amp;mdash; Mechanical odds and ends. The motor mount alone evolved faceplate &amp;rarr; pipe-clamp saddle &amp;rarr; full sleeve &amp;rarr; a half-U cradle (open top, prints with no supports, motor drops in, screws hold it). And the rear wheel axle taught me a 3 mm bearing needs a 3 mm axle &amp;mdash; an M3 screw or literally a nail. Imperial 6-32 and 8-32 are both too fat to pass through a 3 mm bore; I measured, doubted, re-measured, and gave up on the hardware-store bin.&lt;/p&gt;
&lt;h2 id="mcetoc_1jvtivdi03"&gt;Part 3: How the software is structured now&lt;/h2&gt;
&lt;p&gt;The Bridge lesson forced a clean brain / spine model (which is also the officially-supported App Lab pattern):&lt;/p&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td width="601"&gt;
&lt;p&gt;1&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; STM32 MCU&amp;nbsp; ── &amp;quot;spine&amp;quot; ── reflexes: read sensors, drive motors, fast + dumb&lt;/p&gt;
&lt;p&gt;2&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp; (Bridge RPC: Python polls, MCU responds)&lt;/p&gt;
&lt;p&gt;3&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;▼&lt;/p&gt;
&lt;p&gt;4&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; UNO Q Linux ── &amp;quot;brain&amp;#39;s home&amp;quot; ── holds ALL sensor feeds, hosts dashboard,&lt;/p&gt;
&lt;p&gt;5&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;│&amp;nbsp; (Wi-Fi)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; runs decision logic; AI sees every feed&lt;/p&gt;
&lt;p&gt;6&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;▼&lt;/p&gt;
&lt;p&gt;7&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Browser (laptop or phone) ── tactical HUD at :7000&lt;/p&gt;
&lt;p&gt;8&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;▲&lt;/p&gt;
&lt;p&gt;9&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Laptop GPU (RTX 3050) ── heavy muscle: YOLO / local LLM, only when needed&lt;/p&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Principles earned the hard way: the spine has veto and the brain proposes (but in manual mode nothing overrides me); every sensor reports its own ACTIVE/OFFLINE status so I bring the system up one circuit at a time, like an electrician testing a panel breaker by breaker; and the dashboard is hosted on the rover itself, reachable from any browser on my Wi-Fi &amp;mdash; laptop or phone, no app to install.&lt;/p&gt;
&lt;h2 id="mcetoc_1jvtivdi04"&gt;Part 4: The Tactical HUD&lt;/h2&gt;
&lt;p&gt;If you&amp;#39;re going to build a UGV, the command interface should &lt;em&gt;look&lt;/em&gt; like one &amp;mdash; dark theme, live camera center-stage, a sweeping proximity radar that plots the live ultrasonic contact, battery/voltage/speed gauges, a scrolling command console, and an &amp;quot;AI Cognition&amp;quot; panel that shows the brain&amp;#39;s perception &amp;rarr; decision stream in autonomous modes. &lt;em&gt;(Insert HUD screenshot here.)&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1jvtivdi05"&gt;Where it stands + what&amp;#39;s next&lt;/h2&gt;
&lt;p&gt;Working / in-hand: wheeled drivetrain (motors verified on the bench), the 3.3 V sensor suite coming online one circuit at a time, the WebUI control architecture, the printed chassis, and an educational pile of failed prints.&lt;/p&gt;
&lt;p&gt;Next posts: getting all sensors green on the HUD; tuning the IR wheel-encoders (odometry is the road to mapping); the ArUco docking attempt (and the honest manual-dock fallback).&lt;/p&gt;
&lt;p&gt;Phase-2 roadmap (documented, not promised): 2D SLAM room-mapping from encoder + IMU + sonar fusion, named locations in roam mode, and local (privacy-preserving) facial recognition.&lt;/p&gt;
&lt;h3 id="mcetoc_1jvtivdi06"&gt;&lt;/h3&gt;
&lt;div&gt;
&lt;h2 id="mcetoc_1jvtlndvp8"&gt;The One Takeaway&lt;/h2&gt;
&lt;p&gt;The render never smokes. The prototype always does.&lt;/p&gt;
&lt;p&gt;Every failed print, every wiring mistake, and every 0-volt reading in this post taught me something a clean success never would. Honestly, documenting those mistakes is probably the most useful thing I can hand to the next person who picks up an Arduino UNO Q and assumes it is just another 5 V Arduino.&lt;/p&gt;
&lt;p&gt;If you are still reading, I&amp;#39;d like to share why this project means so much to me.&lt;/p&gt;
&lt;p&gt;When I was in high school, I spent a lot of time building things. We had STEM classes where we worked on robotics-related projects, and one of the most memorable was building a small submarine. Outside of school, I was always experimenting with random ideas and turning them into increasingly complicated devices. One time I set out to build a simple handheld vacuum to clean my desk. By the end of the project, it had somehow evolved into a wheeled robot that could only vacuum very tiny particles.&lt;/p&gt;
&lt;p&gt;After the military takeover in Myanmar, life took a very different direction. I stopped building projects altogether. Years passed, I moved to Canada, and eventually started my undergraduate studies. Somewhere along the way I realized I no longer had a hobby that I was genuinely excited about and life felt flat.&lt;/p&gt;
&lt;p&gt;Then, by pure chance, I found a 3D printer on Facebook Marketplace for $100. About a week later I came across the Element14 EZ-EV Design Challenge. Before I knew it, I was designing parts, printing prototypes, making wiring mistakes, and staying up far too late troubleshooting electronics.&lt;/p&gt;
&lt;p&gt;In a way, Project GEPARD is not just about building a rover. It is about reconnecting with the version of myself that loved making things when I was younger.&lt;/p&gt;
&lt;p&gt;So please excuse the cable spaghetti in the videos, the rough prototype parts, and the occasional piece of double-sided tape holding something together. This project is very much a work in progress.&lt;/p&gt;
&lt;p&gt;I would also like to thank Element14 for providing me with a sponsored kit. As a university student, I simply would not have been able to justify buying all of this hardware myself. The challenge gave me both the tools and the motivation to start building again. Without the competition, I probably would have kept telling myself, &amp;quot;I&amp;#39;ll start next week.&amp;quot;&lt;/p&gt;
&lt;p&gt;I also owe everyone an apology for the quality of the video. It was recorded around 1 a.m. after a long day of work. I kept telling myself I would make a post after fixing one more bug, printing one more part, or reaching one more milestone. The result was that I kept pushing updates further and further back.&lt;/p&gt;
&lt;p&gt;No more.&lt;/p&gt;
&lt;p&gt;From this point forward, I am going to focus on documenting the process as it happens, mistakes and all. The written portions will remain organized and professional, but I am completely new to making videos, so thank you for bearing with me while I learn that side of the process as well.&lt;/p&gt;
&lt;p&gt;More soon.&lt;/p&gt;
&lt;p&gt;The wheels are turning. Mostly.&lt;br /&gt;&lt;a href="https://www.youtube.com/watch?v=m_eVlDfhR_E"&gt;www.youtube.com/watch&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;p&gt;&lt;/p&gt;
&lt;div&gt;
&lt;p&gt;&lt;strong&gt;Featured in this post:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;3 failed track designs&lt;/li&gt;
&lt;li&gt;Multiple failed sprocket revisions&lt;/li&gt;
&lt;li&gt;Rear idler redesigns&lt;/li&gt;
&lt;li&gt;Battery packaging mistakes&lt;/li&gt;
&lt;li&gt;25-hour print failure&lt;/li&gt;
&lt;li&gt;Ultrasonic sensor wiring failure&lt;/li&gt;
&lt;li&gt;Current rover prototype footage&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;br /&gt;
&lt;p&gt;&lt;/p&gt;
&lt;/div&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;</description></item><item><title>Hall-w-EV - Post 3 - Joystick control</title><link>https://community.element14.com/thread/57174?ContentTypeID=0</link><pubDate>Thu, 13 Aug 2026 13:56:38 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:e1fea7bd-9829-42ec-a09e-5f70469ffc3a</guid><dc:creator>tamadillo</dc:creator><slash:comments>2</slash:comments><comments>https://community.element14.com/thread/57174?ContentTypeID=0</comments><wfw:commentRss>https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57174/hall-w-ev---post-3---joystick-control/rss?ContentTypeId=0</wfw:commentRss><description>&lt;p&gt;Hi again, it&amp;rsquo;s&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://github.com/hambreros" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;Hambreros&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;and&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://github.com/tamadillo" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;Tamadillo&lt;/a&gt;. Last post the robot&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57173/hall-w-ev---post-2---giving-the-robot-a-voice" data-e14adj="t"&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;learned to make noise&lt;/a&gt;. This one is smaller but makes the whole thing way more fun to actually drive: a real joystick &amp;mdash; drag it with a mouse or thumb, or just use&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;WASD&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;/ vim-style&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;hjkl&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;on a keyboard &amp;mdash; instead of wrestling two separate wheel sliders at once.&lt;/p&gt;
&lt;h2 id="what-weve-actually-built"&gt;What we&amp;rsquo;ve actually built&lt;/h2&gt;
&lt;p&gt;Since&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57131/hall-w-ev-post-1---the-wheels-are-turning-mostly" data-e14adj="t"&gt;Post 1&lt;/a&gt;, driving meant dragging&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;strong&gt;two&lt;/strong&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;independent vertical sliders. Well actually up till now we haven&amp;rsquo;t connected both servos but yeay a slider per wheel like driving a tank, and how do you even control 2 controls with 1 mouse? So the control page now has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An on-screen joystick pad &amp;mdash; drag the stick in any direction, let go and it springs back to center and stops, same &amp;ldquo;throttle stick, not a light switch&amp;rdquo; feel as the wheel sliders had.&lt;/li&gt;
&lt;li&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;WASD&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;and vim&amp;rsquo;s&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;hjkl&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;both drive the same stick &amp;mdash; whichever one you reach for first works, and they combine, so forward + turn gives you a proper diagonal instead of a hard pivot.&lt;/li&gt;
&lt;li&gt;The old per-wheel sliders are still there underneath, now relabeled &amp;ldquo;Manual Wheel Control&amp;rdquo; &amp;mdash; occasionally useful for trimming one wheel on its own, but the joystick is the one you actually want to drive with.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="image" style="cursor:zoom-in;display:block;margin-left:auto;margin-right:auto;max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/20260813_5F00_02_5F00_joystick_5F00_controls.gif" /&gt;&lt;/p&gt;
&lt;h2 id="keeping-the-mixing-in-one-place"&gt;Keeping the mixing in one place&lt;/h2&gt;
&lt;p&gt;The tempting shortcut was to do the &amp;ldquo;turn this drag angle into two wheel speeds&amp;rdquo; math in JavaScript and post straight to the existing&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;/api/wheel/&amp;lt;n&amp;gt;&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;endpoint per wheel. We didn&amp;rsquo;t do that &amp;mdash; the frontend has no business knowing how many wheels this thing has or how they&amp;rsquo;re mixed. Instead the page posts one thing,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;{x, y}&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(turn, throttle, both -100..100), to a new&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;POST /api/drive&lt;/code&gt;, and the actual arcade-mixing math lives entirely on the Python side:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;def mix_drive(x, y):
    x = max(-100, min(100, int(x)))
    y = max(-100, min(100, int(y)))
    return max(-100, min(100, y + x)), max(-100, min(100, y - x))&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;One function, one place that knows&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;wheel1 = throttle + turn&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;and&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;wheel2 = throttle - turn&lt;/code&gt;. If we ever add a third wheel, a different chassis, or want to curve the turn response, that&amp;rsquo;s a one-function change, not a hunt through frontend code.&lt;/p&gt;
&lt;h2 id="mirrored-servos-mirrored-bug"&gt;Mirrored servos, mirrored bug&lt;/h2&gt;
&lt;p&gt;Software done, so time to actually push the stick forward with both wheels connected at once &amp;mdash; first time we&amp;rsquo;d had them both hooked up and driven together rather than one at a time. Robot spun in place instead of driving forward. Wheel 1 was doing exactly what it should; wheel 2 was going backward.&lt;/p&gt;
&lt;p&gt;Both servos are the same part, wired the same way, running the same firmware &amp;mdash; but they&amp;rsquo;re bolted to opposite sides of the chassis, mirror image of each other, the same way your left shoe and right shoe are mirror images built from the same last. &amp;ldquo;Spin clockwise&amp;rdquo; looks like forward from one side and backward from the other, so the exact same pulse width that drove wheel 1 forward drove wheel 2 in reverse. Nothing wrong with the mixing math from the last section &amp;mdash;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;mix_drive()&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;was handing out perfectly correct forward speeds for both wheels, it&amp;rsquo;s just that one wheel&amp;rsquo;s servo interprets &amp;ldquo;forward&amp;rdquo; backwards from the other.&lt;/p&gt;
&lt;p&gt;Fixed it at the one point in the firmware that turns a commanded speed into an actual pulse, not by touching the mixing math or anything upstream of it:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;#define WHEEL1_REVERSED false
#define WHEEL2_REVERSED true
...
servoFrame(SERVO1_PIN, speedToPulseUs(WHEEL1_REVERSED ? -wheel1Speed : wheel1Speed),
           SERVO2_PIN, speedToPulseUs(WHEEL2_REVERSED ? -wheel2Speed : wheel2Speed));&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;wheel1Speed&lt;/code&gt;/&lt;code class="language-plaintext highlighter-rouge"&gt;wheel2Speed&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;themselves &amp;mdash; the values the Bridge handlers store, the values the Python side and the joystick&amp;rsquo;s arcade mixing both reason about &amp;mdash; still mean &amp;ldquo;positive is forward&amp;rdquo; for both wheels. The mirroring correction is a single negation right at the pulse-generation step, isolated to the one wheel that&amp;rsquo;s actually mounted backwards. If it turns out a future chassis needs the other wheel flipped too (or flipped back), it&amp;rsquo;s a one-line change, not a rethink of the mixing.&lt;/p&gt;
&lt;h2 id="a-real-off-switch"&gt;A real OFF switch&lt;/h2&gt;
&lt;p&gt;With the direction sorted, one servo was still making a faint noise even sitting at commanded speed 0 &amp;mdash; the same self-correcting buzz&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57131/hall-w-ev-post-1---the-wheels-are-turning-mostly" data-e14adj="t"&gt;Post 1&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;first ran into, just quieter now that both servos are trimmed better. Trimming the pot gets you close to the servo&amp;rsquo;s true center, not exactly onto it, and a held 1500us &amp;ldquo;stop&amp;rdquo; pulse still gives the servo&amp;rsquo;s internal position-holding loop a target to compare itself against. Close-but-not-perfect is still enough for it to keep nudging.&lt;/p&gt;
&lt;p&gt;So instead of chasing the trim pot further, we added a real motor power toggle &amp;mdash; a button per wheel that does something a commanded speed of 0 can&amp;rsquo;t: stop sending that servo a pulse train&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;em&gt;at all&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;static void servoFrame(int pin1, unsigned int pulse1Us, bool enable1,
                       int pin2, unsigned int pulse2Us, bool enable2) {
    if (enable1) {
        digitalWrite(pin1, HIGH);
        delayMicroseconds(pulse1Us);
        digitalWrite(pin1, LOW);
    }
    // ...same for pin2/enable2
}&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;No pulse means nothing for the internal loop to react to &amp;mdash; quieter than any stop pulse we could trim to, held or not.&lt;/p&gt;
&lt;p&gt;Worth being upfront about what this isn&amp;rsquo;t: it&amp;rsquo;s not a real power switch. The board only ever drove the servo&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;em&gt;signal&lt;/em&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;line &amp;mdash; the 5V rail has always come straight off the shared supply with no relay or MOSFET in between , so &amp;ldquo;motor off&amp;rdquo; here can&amp;rsquo;t cut actual voltage to the servo. That would need new hardware &amp;mdash; a MOSFET or relay switched from a spare GPIO &amp;mdash; not just a firmware change, so we deliberately scoped this to the signal-only version rather than reaching for a soldering iron mid-feature. Given the noise was coming from the&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;em&gt;signal&lt;/em&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;being held near-but-not-quite-center rather than from anything drawing power at true idle, it&amp;rsquo;s also very likely the actual fix for the buzz, not just a consolation prize.&lt;/p&gt;
&lt;h2 id="holding-a-key-isnt-a-real-browser-event"&gt;&amp;ldquo;Holding a key&amp;rdquo; isn&amp;rsquo;t a real browser event&lt;/h2&gt;
&lt;p&gt;First pass: listen for&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;keydown&lt;/code&gt;, send the drive command once. Worked for about half a second &amp;mdash; press&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;w&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;and the robot lurches forward, then stops on its own even though the key&amp;rsquo;s still very much held down.&lt;/p&gt;
&lt;p&gt;Turns out&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;keydown&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;fires once per press, and after that the browser&amp;rsquo;s own key-repeat kicks in &amp;mdash; which is inconsistent across OSes, has a noticeable initial delay, and isn&amp;rsquo;t something we should be relying on for &amp;ldquo;keep the motor running.&amp;rdquo; Worse:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57131/hall-w-ev-post-1---the-wheels-are-turning-mostly" data-e14adj="t"&gt;Post 1&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;built a 1-second watchdog into the STM32 side specifically so a dropped connection stops the wheels instead of leaving them spinning &amp;mdash; and a&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;keydown&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;that fires once and then goes quiet for a while looks exactly like a dropped connection to that watchdog.&lt;/p&gt;
&lt;p&gt;Fix was the same pattern the wheel sliders already used for drag events, just driven by a timer instead of input events &amp;mdash; track which keys are currently down in a&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;Set&lt;/code&gt;, and re-send the current vector on a plain interval for as long as any of them are held:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;setInterval(() =&amp;gt; {
  if (keyboardDriving) drive(...keyboardVector(), false);
}, SEND_INTERVAL_MS);&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;keydown&lt;/code&gt;/&lt;code class="language-plaintext highlighter-rouge"&gt;keyup&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;just add/remove from the set; the interval is what actually keeps commands flowing often enough to stay ahead of the watchdog.&lt;/p&gt;
&lt;h2 id="the-keyup-that-never-comes"&gt;The keyup that never comes&lt;/h2&gt;
&lt;p&gt;Second gotcha, found by alt-tabbing away mid-drive without letting go of&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;w&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;first: the robot kept driving.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;keyup&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;only fires if the browser is still the one listening &amp;mdash; alt-tab, clicking outside the page, anything that steals focus, and the browser just stops delivering key events altogether. No&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;keyup&lt;/code&gt;, so our held-keys set never clears.&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="javascript"&gt;window.addEventListener(&amp;#39;blur&amp;#39;, () =&amp;gt; {
  if (pressedKeys.size === 0) return;
  pressedKeys.clear();
  keyboardDriving = false;
  drive(0, 0, true);
});&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;Losing focus now stops the robot immediately instead of waiting out the watchdog&amp;rsquo;s full second &amp;mdash; which, at &amp;ldquo;robot with wheels in a hallway,&amp;rdquo; felt like the actually-important version of this bug, not just a nice-to-have.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;a href="https://community.element14.com/cfs-file/__key/communityserver-discussions-components-files/456/20260813_5F00_02_5F00_demo_5F00_joystick_5F00_control_5F00_web.mp4"&gt;community.element14.com/.../20260813_5F00_02_5F00_demo_5F00_joystick_5F00_control_5F00_web.mp4&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div class="post-content"&gt;
&lt;h2 id="whats-next"&gt;What&amp;rsquo;s next&lt;/h2&gt;
&lt;p&gt;The joystick makes driving nicer, but you&amp;rsquo;re still driving blind &amp;mdash; next up is the camera, so this actually becomes the &amp;ldquo;puppy on a leash from your phone&amp;rdquo; post the original plan promised.&lt;/p&gt;
&lt;h2 id="the-codes"&gt;The codes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/tamadillo/hall-w-EV" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;https://github.com/tamadillo/hall-w-EV&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;mdash;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://github.com/hambreros" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;Hambreros&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(and&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://github.com/tamadillo" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;Tamadillo&lt;/a&gt;)&lt;/p&gt;
&lt;/div&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;</description></item><item><title>Hall-w-EV - Post 2 - Giving the robot a voice</title><link>https://community.element14.com/thread/57173?ContentTypeID=0</link><pubDate>Thu, 13 Aug 2026 04:28:09 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:52944598-6fda-44ae-8a96-11aa9663471b</guid><dc:creator>tamadillo</dc:creator><slash:comments>5</slash:comments><comments>https://community.element14.com/thread/57173?ContentTypeID=0</comments><wfw:commentRss>https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57173/hall-w-ev---post-2---giving-the-robot-a-voice/rss?ContentTypeId=0</wfw:commentRss><description>&lt;p&gt;Hi again, it&amp;rsquo;s&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://github.com/hambreros" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;Hambreros&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;and&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://github.com/tamadillo" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;Tamadillo&lt;/a&gt;. Last post the robot learned to roll&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57131/hall-w-ev-post-1---the-wheels-are-turning-mostly" data-e14adj="t"&gt;Post 1 - The wheels are turning (mostly)&lt;/a&gt;. This post it learned to make noise &amp;mdash; sound effects, an air raid siren, an announcement system, and it can even talk now. Getting there was way more of an adventure than the wheels were, mostly because the bug wasn&amp;rsquo;t actually a bug, it was a whole container we didn&amp;rsquo;t know existed.&lt;/p&gt;
&lt;h2 id="what-weve-actually-built"&gt;What we&amp;rsquo;ve actually built&lt;/h2&gt;
&lt;p&gt;The control web page now has a &amp;ldquo;Sound System&amp;rdquo; panel underneath the wheel controls:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A grid of sound effects like laser blasts and guns reloading&lt;/li&gt;
&lt;li&gt;An&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;strong&gt;Air Raid Siren&lt;/strong&gt;, and an&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;strong&gt;AusAlert&lt;/strong&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;tone (853Hz and 960Hz played together) inspired by Australia&amp;rsquo;s emergency phone-alert system&lt;/li&gt;
&lt;li&gt;Text-to-speech (TTS) announcements &amp;mdash; inspired by &amp;ldquo;Giant Voice&amp;rdquo; systems we&amp;rsquo;ve seen in videos from Middle East conflict zones and school lockdown drills, telling you to &amp;ldquo;shelter in place&amp;rdquo;. Partly inspired by the new Arduino App Lab update, which mentions TTS bricks.&lt;/li&gt;
&lt;li&gt;A volume slider, and a big&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;strong&gt;SHUT UP&lt;/strong&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;button, because once you give a robot a siren you will absolutely need a way to make it stop&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And most recently: cruising music, so it can play a track while it drives around. More on why that one was trickier than it sounds in a minute.&lt;/p&gt;
&lt;h2 id="hardware-distraction"&gt;Hardware distraction&lt;/h2&gt;
&lt;p&gt;The Braitenberg vehicle chassis we&amp;rsquo;re using is inherently unstable. Two wheels and a stopper meant it would often tip during testing. Inspired by the pantographs on trains that pass our back yard, as well as drones that can land and stick to a moving object, we tried to build a self-correcting leg system for our EV.&lt;/p&gt;
&lt;div style="cursor:zoom-in;display:block;margin-left:auto;margin-right:auto;max-height:360px;max-width:960px;"&gt;&lt;img alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/20260813_5F00_tram_5F00_pantograph_5F00_02.gif" /&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/20260813_5F00_tram_5F00_pantograph_5F00_01.gif" /&gt;&lt;/div&gt;
&lt;div style="cursor:zoom-in;display:block;margin-left:auto;margin-right:auto;"&gt;&lt;a href="https://community.element14.com/cfs-file/__key/communityserver-discussions-components-files/456/20260813_5F00_drone_5F00_land_5F00_and_5F00_cling_5F00_web.mp4"&gt;community.element14.com/.../20260813_5F00_drone_5F00_land_5F00_and_5F00_cling_5F00_web.mp4&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="https://www.instagram.com/p/DboDwK_gXT7" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;https://www.instagram.com/p/DboDwK_gXT7&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;credit&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://www.instagram.com/zaruba.tech/" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;Zaruba&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This took a bunch of experimenting and time. In the end the results weren&amp;rsquo;t that good, and we realised that once we finally attached a camera to the setup, we&amp;rsquo;d also need to compensate and auto-correct the camera to point at the horizon.&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" alt="image" style="cursor:zoom-in;display:block;margin-left:auto;margin-right:auto;max-height:360px;max-width:960px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/20260813_5F00_adjustable_5F00_leg_5F00_combined.gif" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;In the end we dropped the idea and moved on with a couple of wooden blocks.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;img loading="lazy" alt="image" style="cursor:zoom-in;display:block;margin-left:auto;margin-right:auto;max-height:960px;max-width:960px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/20260813_5F00_block_5F00_legs.jpg" /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 id="tts-no-work"&gt;TTS no work&lt;/h2&gt;
&lt;p&gt;As the UNO Q updated to the latest firmware of Arduino App Lab, we got briefly excited that there might also be a&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;em&gt;neural&lt;/em&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;text-to-speech option built in (an actual AI voice model instead of the classic robot monotone) &amp;mdash; and there is one,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;arduino:tts&lt;/code&gt;. Got all the way to testing it before finding out it&amp;rsquo;s built specifically for the new&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;Ventuno Q&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;board and not supported by the&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;UNO Q&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Turns out Arduino just announced the&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://community.element14.com/products/arduino/b/blog/posts/arduino-ventuno" data-e14adj="t"&gt;VENTUNO Q&lt;/a&gt;. Our UNO Q has 2GB of RAM and no AI chip. VENTUNO Q packs a&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;strong&gt;Qualcomm Dragonwing IQ‑8275&lt;/strong&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;mdash; a proper 40 TOPS neural processor &amp;mdash; plus 16GB of RAM, specifically so it can run real local AI: computer vision,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://community.element14.com/products/roadtest/rt/roadtests/722/roadtest-open-call?CommentId=63a8ef97-9664-436f-a11a-6178ed0f25c8" data-e14adj="t"&gt;offline AI assistants running local speech models&lt;/a&gt;, that kind of thing. The neural TTS brick we found needs that NPU to run at all, so on our board it was never going to work &amp;mdash; not a bug, just the wrong hardware for the job. There are a couple of overview videos from embedded world if you want to see it in action:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://www.youtube.com/watch?v=gVd1qKlfCyY" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;developer walkthrough&lt;/a&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://www.youtube.com/watch?v=5wYzlrZVPXY" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;demo reel&lt;/a&gt;. But let&amp;rsquo;s not let the inspiration of TTS go to waste, after scrounging around we worked out we can use the unix&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;espeak-ng&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;for the talking instead.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s where it got interesting. The siren and the AusAlert tone worked first try. Wav/MP3 Sound effects and text-to-speech? Dead silent. No errors, no sound, nothing.&lt;/p&gt;
&lt;p&gt;First theory: volume. Turned out to be half right &amp;mdash; the board&amp;rsquo;s speaker volume genuinely was too low by default, and cranking it with&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;amixer&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;fixed&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;em&gt;some&lt;/em&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;of it. But effects and TTS still didn&amp;rsquo;t work, even after that.&lt;/p&gt;
&lt;p&gt;Second theory, once we actually looked: the programs we needed (&lt;code class="language-plaintext highlighter-rouge"&gt;mpg123&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;for mp3s,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;espeak-ng&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;for the talking) just weren&amp;rsquo;t installed. Fair enough, we thought &amp;mdash;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;sudo apt-get install&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;them and done. Except we&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;em&gt;did&lt;/em&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;that, and&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;which mpg123&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;on the board clearly showed it existed. So why couldn&amp;rsquo;t our own code find it?&lt;/p&gt;
&lt;p&gt;Turns out: the robot&amp;rsquo;s Python code doesn&amp;rsquo;t actually run directly on the board&amp;rsquo;s Linux. It runs&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;em&gt;inside a Docker container&lt;/em&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;mdash; basically a little sealed box with its own separate copy of everything. We&amp;rsquo;d installed the programs onto the board itself, not into the box our code was actually running in. Two completely different places, both called &amp;ldquo;the board&amp;rdquo; if you&amp;rsquo;re not paying attention. Once we&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;docker exec&lt;/code&gt;&amp;lsquo;d into the actual container and installed things there instead, everything clicked into place &amp;mdash; almost. Even&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;em&gt;that&lt;/em&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;had one more gotcha: the container&amp;rsquo;s default user isn&amp;rsquo;t allowed to install anything (&lt;code class="language-plaintext highlighter-rouge"&gt;Permission denied&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;on a folder called&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;apt/lists/partial&lt;/code&gt;, if you&amp;rsquo;re curious), so it needed:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="batchfile"&gt;docker exec -u root hall-w-ev-main-1 apt-get install -y espeak-ng&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;-u root&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;= &amp;ldquo;no really, let me actually install this.&amp;rdquo; Small thing, cost us an hour.&lt;/p&gt;
&lt;h2 id="finding-the-good-stuff"&gt;Finding the good stuff&lt;/h2&gt;
&lt;p&gt;While we were down in that container digging around, we found something way better than what we were looking for: Arduino ships their own official audio tools baked right in &amp;mdash;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;arduino.app_peripherals.speaker.Speaker&lt;/code&gt;. It&amp;rsquo;s a proper Python class for playing sound directly, no external programs needed at all.&lt;/p&gt;
&lt;p&gt;As mentioned above, the VENTUNO Q&amp;rsquo;s TTS brick was kind of cool to stumble into by accident while debugging a sound effect &amp;mdash; but a dead end for now. We&amp;rsquo;re sticking with the classic robot voice, which honestly suits an emergency-siren robot better anyway.&lt;/p&gt;
&lt;p&gt;Since the&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://github.com/arduino/app-bricks-py/tree/main/src/arduino/app_bricks/tts" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;TTS brick&amp;rsquo;s code is public&lt;/a&gt;, we went and actually read it out of curiosity, and it turns out it does basically the same chunk-and-check-cancelled trick we were about to build by hand, just with a lot more going on underneath:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It doesn&amp;rsquo;t run the AI voice model in the same program at all &amp;mdash; it makes a network request to a separate always-on service and streams the audio&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;em&gt;back&lt;/em&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;as the model generates it, piece by piece, instead of waiting for the whole sentence to finish.&lt;/li&gt;
&lt;li&gt;Long text gets split at up to 1024 characters, cut on the last&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;.&lt;/code&gt;/&lt;code class="language-plaintext highlighter-rouge"&gt;!&lt;/code&gt;/&lt;code class="language-plaintext highlighter-rouge"&gt;?&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;it can find before the limit, so it doesn&amp;rsquo;t chop a sentence in half &amp;mdash; smarter than our &amp;ldquo;just cut it off at 300 characters and hope.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Cancelling has to happen in&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;em&gt;two&lt;/em&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;places: locally (stop feeding audio to the speaker) and remotely (tell the AI service currently mid-sentence over the network to actually stop generating).&lt;/li&gt;
&lt;li&gt;There&amp;rsquo;s even a &amp;ldquo;warmup&amp;rdquo; &amp;mdash; the instant it starts up, it quietly synthesizes the word &amp;ldquo;ok&amp;rdquo; to itself, just so the neural network is already loaded by the time you need it for real, instead of your first sentence being the slow one.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Kind of validating, honestly. The &amp;ldquo;cut it into pieces, check a flag between each one&amp;rdquo; idea wasn&amp;rsquo;t a hack we made up &amp;mdash; it&amp;rsquo;s the same shape of solution the actual Arduino engineers reached for. Theirs just has a neural network and a network request bolted on the front of it.&lt;/p&gt;
&lt;p&gt;The&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;Speaker&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;class was the real win though. The simple way to use it plays a whole sound start-to-finish with no way to interrupt it &amp;mdash; fine for a short laser blast, useless for &amp;ldquo;stop the siren right now.&amp;rdquo; So instead we feed it small chunks (a tenth of a second each) in a loop, and check &amp;ldquo;should I stop?&amp;rdquo; between every single chunk:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;def _stream_pcm(speaker, sample_rate, channels, samples, stop_event):
    chunk_len = max(1, int(sample_rate * 0.1)) * channels
    for i in range(0, len(samples), chunk_len):
        if stop_event.is_set():
            break
        speaker.play(samples[i:i + chunk_len])&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s the whole trick behind the SHUT UP button, and behind &amp;ldquo;press the siren again while it&amp;rsquo;s already going&amp;rdquo; restarting it cleanly instead of two sirens fighting each other.&lt;/p&gt;
&lt;h2 id="making-espeak-ng-actually-stick-around"&gt;Making espeak-ng actually stick around&lt;/h2&gt;
&lt;p&gt;Remember that&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;docker exec -u root ... apt-get install espeak-ng&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;fix from earlier? It worked great &amp;mdash; for one restart. Then we rebooted the board again and it was just gone. Turns out installing something into a running container by hand doesn&amp;rsquo;t actually stick &amp;mdash; the container gets rebuilt from scratch every time you redeploy, and &amp;ldquo;by hand&amp;rdquo; doesn&amp;rsquo;t survive being rebuilt. Cool, so our talking robot&amp;rsquo;s voice box was actually a ticking time bomb this whole time.&lt;/p&gt;
&lt;p&gt;We did not want to just re-run that command forever every time we updated the code. So: real fix time.&lt;/p&gt;
&lt;p&gt;We remembered seeing a&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;brick_compose.yaml&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;mentioned in Arduino&amp;rsquo;s own code while we were poking around earlier, and it turns out there&amp;rsquo;s a whole&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href="https://blog.arduino.cc/2026/04/29/arduino-app-lab-0-7-custom-bricks-are-here/" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;custom bricks feature&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;for exactly this &amp;mdash; you can package up your own little service, container and all, as part of your app. Every example of it we could find online only used pre-built images though, never a Dockerfile you write yourself, so we genuinely didn&amp;rsquo;t know if that part actually worked or if we&amp;rsquo;d be wasting an evening.&lt;/p&gt;
&lt;p&gt;Quick test first: a throwaway folder with just a&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;Dockerfile&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;that installs espeak-ng and then does nothing (&lt;code class="language-plaintext highlighter-rouge"&gt;sleep infinity&lt;/code&gt;), wired up as a brick. Restarted the app to see what would happen.&lt;/p&gt;
&lt;p&gt;It built the Dockerfile. For real. Docker log spam and everything, right there in the deploy output &amp;mdash; our own robot, building its own container image, from a text file we wrote, installing a package with full root access and zero permission drama, because this time it&amp;rsquo;s happening at&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;em&gt;build&lt;/em&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;time, not sneaking in through&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;docker exec&lt;/code&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;afterward.&lt;/p&gt;
&lt;p&gt;So we built the actual thing: a tiny container that does nothing but run espeak-ng behind a dead-simple web server &amp;mdash;&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;# POST /synthesize {&amp;quot;text&amp;quot;: &amp;quot;...&amp;quot;, &amp;quot;voice&amp;quot;: &amp;quot;en-us+m3&amp;quot;, &amp;quot;speed&amp;quot;: 150}
result = subprocess.run(
    [&amp;#39;espeak-ng&amp;#39;, &amp;#39;--stdout&amp;#39;, &amp;#39;-v&amp;#39;, voice, &amp;#39;-s&amp;#39;, speed, text],
    capture_output=True, timeout=10,
)
# ...and send result.stdout back as the response body&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;&amp;mdash; and now the main robot code just sends it a sentence over the network and gets a WAV file back, instead of running espeak-ng itself. Since the whole container gets rebuilt from that same Dockerfile every single time we deploy, there&amp;rsquo;s nothing left to mysteriously vanish. Tested it properly too &amp;mdash; full restart, both containers rebuilt from nothing, and the robot could talk again immediately, no manual fixing required. That&amp;rsquo;s the actual fix, not a &amp;ldquo;seems fine for now.&amp;rdquo;&lt;/p&gt;
&lt;h2 id="cruising-music-without-a-50mb-file"&gt;Cruising music (without a 50MB file)&lt;/h2&gt;
&lt;p&gt;Last thing: we wanted the robot to play a music track while driving around &amp;mdash; cruising music. Obvious approach: convert the song to the same format as the sound effects. Except the effects are only a few seconds long, and this song is almost 5 minutes &amp;mdash; converted the &amp;ldquo;simple&amp;rdquo; way, it would&amp;rsquo;ve turned a 7MB mp3 into something like 50MB sitting on the robot for no reason.&lt;/p&gt;
&lt;p&gt;So instead of converting the whole song upfront, it gets decoded a tiny piece at a time, right as it&amp;rsquo;s needed, and each piece goes straight into the same chunk-player from before. The song is never sitting fully unpacked in memory or on disk, and we get to reuse all the cancel/restart logic we&amp;rsquo;d already built.&lt;/p&gt;
&lt;p&gt;the sound board controls&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" alt="image" style="cursor:zoom-in;display:block;margin-left:auto;margin-right:auto;max-height:960px;max-width:960px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/pastedimage1786625900480v1.png" /&gt;&lt;/p&gt;
&lt;div style="cursor:zoom-in;display:block;margin-left:auto;margin-right:auto;max-height:960px;max-width:960px;"&gt;&lt;a href="https://community.element14.com/cfs-file/__key/communityserver-discussions-components-files/456/20260813_5F00_demo_5F00_hall_2D00_w_2D00_EV_5F00_sound_5F00_board.mp4"&gt;community.element14.com/.../20260813_5F00_demo_5F00_hall_2D00_w_2D00_EV_5F00_sound_5F00_board.mp4&lt;/a&gt;&lt;/div&gt;
&lt;h2 id="whats-next"&gt;What&amp;rsquo;s next&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Camera + remote driving&lt;/strong&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;mdash; the &amp;ldquo;puppy on a leash from your phone&amp;rdquo; post&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="the-codes"&gt;The codes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/tamadillo/hall-w-EV" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;https://github.com/tamadillo/hall-w-EV&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;mdash;&lt;span&gt;&amp;nbsp;&lt;a href="https://community.element14.com/members/hambreros"&gt;hambreros&lt;/a&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(and&lt;span&gt;&amp;nbsp;&lt;a href="https://community.element14.com/members/tamadillo"&gt;tamadillo&lt;/a&gt;&amp;nbsp;&lt;/span&gt;)&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;</description></item><item><title>EVA Guardian[Part_4] - The Battery Management System</title><link>https://community.element14.com/thread/57164?ContentTypeID=0</link><pubDate>Mon, 10 Aug 2026 18:40:28 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:97d5f14d-48f7-4e03-8716-bc3ca2c0cdfe</guid><dc:creator>Sumanth_m_n</dc:creator><slash:comments>1</slash:comments><comments>https://community.element14.com/thread/57164?ContentTypeID=0</comments><wfw:commentRss>https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57164/eva-guardian-part_4---the-battery-management-system/rss?ContentTypeId=0</wfw:commentRss><description>&lt;p&gt;In my previous post, &lt;strong data-start="127" data-end="177"&gt;&amp;ldquo;EVA Guardian: The Incident Detection System,&amp;rdquo;&lt;/strong&gt; I explored the safety side of the project, where the Arduino UNO Q and MPU6500 were used to detect different vehicle motion patterns using Edge AI. While detecting an accident is important, there is another equally important question for an electric vehicle:&lt;/p&gt;
&lt;blockquote data-start="439" data-end="502"&gt;
&lt;p data-start="441" data-end="502"&gt;&lt;strong data-start="441" data-end="502"&gt;What is happening to the battery that powers the vehicle?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;span&gt;Previous forum posts&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Part 1: &lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57059/eva-guardian-the-idea" data-e14adj="t"&gt;The Idea behind EVA Guardian&lt;/a&gt;&lt;br /&gt;Part 2: &lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57133/eva-guardian---the-architecture" data-e14adj="t"&gt;The Architecture of EVA Guardian&lt;/a&gt;&lt;br /&gt;Part 3: &lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57152/eva-guardian---the-incident-detection-system" data-e14adj="t"&gt;The Incident detection System in EVA Guardian&lt;/a&gt;&lt;/p&gt;
&lt;p data-start="504" data-end="608"&gt;&lt;br /&gt;For this part of EVA Guardian, I started building the &lt;strong data-start="558" data-end="607"&gt;Battery Management and Battery Profiling Node&lt;/strong&gt;.&lt;/p&gt;
&lt;p data-start="610" data-end="877"&gt;The objective of this module is to continuously measure the battery&amp;#39;s &lt;strong data-start="680" data-end="716"&gt;voltage, current and temperature&lt;/strong&gt;, and use these parameters to derive meaningful battery information such as &lt;strong data-start="792" data-end="876"&gt;State of Charge (SoC), State of Health (SoH), energy usage and battery condition&lt;/strong&gt;.&lt;/p&gt;
&lt;p data-start="879" data-end="1016"&gt;For the first prototype, I built the complete measurement circuitry on a &lt;strong data-start="952" data-end="965"&gt;veroboard&lt;/strong&gt; and interfaced it directly with the Arduino UNO Q.&lt;/p&gt;
&lt;hr data-start="1018" data-end="1021" /&gt;
&lt;h1 id="mcetoc_1jvmcq8b111" data-section-id="qdqitz" data-start="1023" data-end="1056"&gt;The Battery Management Approach&lt;/h1&gt;
&lt;p data-start="1058" data-end="1156"&gt;The battery used for this prototype is a &lt;strong data-start="1099" data-end="1155"&gt;2-cell 18650 Li-ion battery pack connected in series&lt;/strong&gt;. Each cell has a nominal voltage of approximately 4.2 V, giving a fully charged voltage of approximately &lt;strong data-start="1312" data-end="1321"&gt;8.4 V&lt;/strong&gt;. For the initial prototype, I am using an &lt;strong data-start="1365" data-end="1401"&gt;HX-2S-A2 2S BMS protection board&lt;/strong&gt; to provide the basic battery protection functionality.The Arduino UNO Q is then used as the monitoring and intelligence layer above the protection circuit.&lt;/p&gt;
&lt;p data-start="1561" data-end="1587"&gt;&lt;img alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/20260810_5F00_230113.jpg" /&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/20260810_5F00_230107.jpg" /&gt;&lt;/p&gt;
&lt;p data-start="1561" data-end="1587"&gt;The basic architecture is:&lt;/p&gt;
&lt;p data-start="1589" data-end="1688"&gt;&lt;strong data-start="1589" data-end="1688"&gt;Battery &amp;rarr; BMS &amp;rarr; Voltage / Current / Temperature Measurement &amp;rarr; Arduino UNO Q &amp;rarr; Battery Analytics&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="1690" data-end="1725"&gt;The three primary measurements are:&lt;/p&gt;
&lt;ul data-start="1727" data-end="1784"&gt;
&lt;li data-section-id="1y9vuyv" data-start="1727" data-end="1744"&gt;Battery voltage&lt;/li&gt;
&lt;li data-section-id="1dkyz70" data-start="1745" data-end="1762"&gt;Battery current&lt;/li&gt;
&lt;li data-section-id="wi4vs9" data-start="1763" data-end="1784"&gt;Battery temperature&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-start="1786" data-end="1892"&gt;These measurements form the foundation for the higher-level battery calculations that I plan to implement.&lt;/p&gt;
&lt;hr data-start="1894" data-end="1897" /&gt;
&lt;h2 id="mcetoc_1jvmcq8b112" data-section-id="152hoex" data-start="1899" data-end="1973"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/pastedimage1786384605765v1.png" /&gt;&lt;/h2&gt;
&lt;hr data-start="1975" data-end="1978" /&gt;
&lt;h1 id="mcetoc_1jvmcq8b113" data-section-id="1p46upc" data-start="1980" data-end="2014"&gt;Building the Measurement Circuit&lt;/h1&gt;
&lt;p data-start="2016" data-end="2139"&gt;For the first hardware prototype, I assembled the voltage, current and temperature measurement circuits on a &lt;strong data-start="2125" data-end="2138"&gt;veroboard&lt;/strong&gt;.&lt;/p&gt;
&lt;p data-start="2141" data-end="2219"&gt;The three analogue measurements are connected to the Arduino UNO Q as follows:&lt;/p&gt;
&lt;div class="group TyagGW_tableContainer"&gt;
&lt;div class="TyagGW_tableWrapper flex flex-col-reverse w-fit"&gt;
&lt;table class="w-fit min-w-(--thread-content-width)" data-start="2221" data-end="2341"&gt;
&lt;thead data-start="2221" data-end="2252"&gt;
&lt;tr data-start="2221" data-end="2252"&gt;
&lt;th class="last:pe-10" data-start="2221" data-end="2237" data-col-size="sm"&gt;Arduino UNO Q&lt;/th&gt;
&lt;th class="last:pe-10" data-start="2237" data-end="2252" data-col-size="sm"&gt;Measurement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody data-start="2263" data-end="2341"&gt;
&lt;tr data-start="2263" data-end="2287"&gt;
&lt;td data-start="2263" data-end="2268" data-col-size="sm"&gt;A0&lt;/td&gt;
&lt;td data-start="2268" data-end="2287" data-col-size="sm"&gt;Battery Current&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-start="2288" data-end="2312"&gt;
&lt;td data-start="2288" data-end="2293" data-col-size="sm"&gt;A1&lt;/td&gt;
&lt;td data-start="2293" data-end="2312" data-col-size="sm"&gt;Battery Voltage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-start="2313" data-end="2341"&gt;
&lt;td data-start="2313" data-end="2318" data-col-size="sm"&gt;A2&lt;/td&gt;
&lt;td data-start="2318" data-end="2341" data-col-size="sm"&gt;Battery Temperature&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-start="2343" data-end="2464"&gt;This keeps the measurement interface simple and gives me a clear separation between the three primary battery parameters.&lt;/p&gt;
&lt;hr data-start="2466" data-end="2469" /&gt;
&lt;h2 id="mcetoc_1jvmcq8b114" data-section-id="7605o0" data-start="2471" data-end="2538"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/20260810_5F00_230052.jpg" /&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/20260810_5F00_230100.jpg" /&gt;&lt;/h2&gt;
&lt;hr data-start="2540" data-end="2543" /&gt;
&lt;h1 id="mcetoc_1jvmcq8b115" data-section-id="hxvf0c" data-start="2545" data-end="2577"&gt;1. Battery Voltage Measurement&lt;/h1&gt;
&lt;p data-start="2579" data-end="2679"&gt;The battery pack can reach &lt;strong data-start="2606" data-end="2615"&gt;8.4 V&lt;/strong&gt;, which is higher than the Arduino UNO Q&amp;#39;s analogue input range. Therefore, I cannot directly connect the battery voltage to the ADC. I used a &lt;strong data-start="2760" data-end="2804"&gt;6.8 k&amp;Omega; and 3 k&amp;Omega; resistor voltage divider&lt;/strong&gt; to scale the battery voltage down to a suitable level for measurement. The divided voltage is then passed through an &lt;strong data-start="2923" data-end="2962"&gt;LF412 op-amp configured as a buffer&lt;/strong&gt; before being connected to the Arduino UNO Q. The buffer provides a high-impedance interface between the voltage divider and the ADC, helping prevent the ADC input from significantly loading the divider.&lt;/p&gt;
&lt;p data-start="3168" data-end="3203"&gt;The scaled voltage is connected to:&lt;/p&gt;
&lt;p data-start="3205" data-end="3227"&gt;&lt;strong data-start="3205" data-end="3227"&gt;Arduino UNO Q &amp;rarr; A1&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="3229" data-end="3353"&gt;The software then converts the measured ADC voltage back into the actual battery voltage using the calibrated divider ratio. This gives me the first fundamental parameter required for battery profiling.&lt;/p&gt;
&lt;hr data-start="3434" data-end="3437" /&gt;
&lt;h2 id="mcetoc_1jvmcq8b116" data-section-id="h6kpul" data-start="3439" data-end="3517"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/20260810_5F00_233523.jpg" /&gt;&lt;/h2&gt;
&lt;hr data-start="3519" data-end="3522" /&gt;
&lt;h1 id="mcetoc_1jvmcq8b117" data-section-id="hzxuzc" data-start="3524" data-end="3561"&gt;2. Current Measurement Using ACS723&lt;/h1&gt;
&lt;p data-start="3563" data-end="3613"&gt;The second important parameter is battery current. For this, I am using the &lt;strong data-start="3640" data-end="3665"&gt;ACS723 current sensor&lt;/strong&gt;.The sensor provides an analogue output proportional to the current flowing through it. For my setup, the sensor output is approximately &lt;strong data-start="3805" data-end="3831"&gt;1.70 V at zero current&lt;/strong&gt;, which becomes the reference or zero-current point. The current sensitivity used in my implementation is approximately &lt;strong data-start="3952" data-end="3965"&gt;1.336 V/A&lt;/strong&gt;.&lt;/p&gt;
&lt;p data-start="3968" data-end="4032"&gt;Therefore, the Arduino UNO Q measures the ACS723 output through:&lt;/p&gt;
&lt;p data-start="4034" data-end="4064"&gt;&lt;strong data-start="4034" data-end="4064"&gt;A0 &amp;rarr; ACS723 Current Sensor&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="4066" data-end="4180"&gt;The software then uses the calibrated zero-current offset and sensitivity to determine the actual battery current.&lt;/p&gt;
&lt;p data-start="4182" data-end="4241"&gt;This allows the system to determine whether the battery is:&lt;/p&gt;
&lt;ul data-start="4243" data-end="4274"&gt;
&lt;li data-section-id="1yqv0af" data-start="4243" data-end="4253"&gt;Charging&lt;/li&gt;
&lt;li data-section-id="ufdyp5" data-start="4254" data-end="4267"&gt;Discharging&lt;/li&gt;
&lt;li data-section-id="1j3wrn0" data-start="4268" data-end="4274"&gt;Idle&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-start="4276" data-end="4355"&gt;and also provides the current value required for power and energy calculations.&lt;/p&gt;
&lt;hr data-start="4357" data-end="4360" /&gt;
&lt;h2 id="mcetoc_1jvmcq8b118" data-section-id="vgedv5" data-start="4362" data-end="4442"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/20260810_5F00_233528.jpg" /&gt;&lt;/h2&gt;
&lt;hr data-start="4444" data-end="4447" /&gt;
&lt;h1 id="mcetoc_1jvmcq8b119" data-section-id="1s1dfps" data-start="4449" data-end="4485"&gt;3. Battery Temperature Measurement&lt;/h1&gt;
&lt;p data-start="4487" data-end="4550"&gt;Temperature is another critical parameter for a Li-ion battery. For the prototype, I am using a &lt;strong data-start="4584" data-end="4608"&gt;10 k&amp;Omega; NTC thermistor&lt;/strong&gt; attached to the battery module. The thermistor is combined with resistor networks to generate a temperature-dependent voltage that can be measured by the Arduino UNO Q.&lt;/p&gt;
&lt;p data-start="4780" data-end="4833"&gt;The midpoint of the resistor network is connected to:&lt;/p&gt;
&lt;p data-start="4835" data-end="4857"&gt;&lt;strong data-start="4835" data-end="4857"&gt;Arduino UNO Q &amp;rarr; A2&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="4859" data-end="4992"&gt;As the battery temperature changes, the resistance of the NTC changes, which produces a corresponding change in the measured voltage. The software can then convert this ADC measurement into an estimated battery temperature. Monitoring temperature becomes particularly important when analysing high-current charging and discharging conditions.&lt;/p&gt;
&lt;hr data-start="5205" data-end="5208" /&gt;
&lt;h2 id="mcetoc_1jvmcq8b11a" data-section-id="1exz2dh" data-start="5210" data-end="5307"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/20260810_5F00_233532.jpg" /&gt;&lt;/h2&gt;
&lt;hr data-start="5309" data-end="5312" /&gt;
&lt;h1 id="mcetoc_1jvmcq8b11b" data-section-id="1cexjes" data-start="5314" data-end="5366"&gt;Bringing Voltage, Current and Temperature Together&lt;/h1&gt;
&lt;p data-start="5368" data-end="5442"&gt;Individually, voltage, current and temperature provide useful information. But the real value comes from looking at all three together.&lt;/p&gt;
&lt;p data-start="5506" data-end="5546"&gt;The Arduino UNO Q continuously acquires:&lt;/p&gt;
&lt;p data-start="5548" data-end="5583"&gt;&lt;strong data-start="5548" data-end="5583"&gt;Voltage + Current + Temperature&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="5585" data-end="5664"&gt;From these measurements, I can calculate additional battery parameters such as:&lt;/p&gt;
&lt;ul data-start="5666" data-end="5821"&gt;
&lt;li data-section-id="1ute5zu" data-start="5666" data-end="5681"&gt;Battery power&lt;/li&gt;
&lt;li data-section-id="4xtyj2" data-start="5682" data-end="5710"&gt;Charging/discharging state&lt;/li&gt;
&lt;li data-section-id="e6evov" data-start="5711" data-end="5731"&gt;Energy consumption&lt;/li&gt;
&lt;li data-section-id="gt0w0" data-start="5732" data-end="5754"&gt;Open-circuit voltage&lt;/li&gt;
&lt;li data-section-id="1l6wlm4" data-start="5755" data-end="5772"&gt;State of Charge&lt;/li&gt;
&lt;li data-section-id="1pi8s2y" data-start="5773" data-end="5790"&gt;State of Health&lt;/li&gt;
&lt;li data-section-id="627vh4" data-start="5791" data-end="5821"&gt;Battery operating conditions&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-start="5823" data-end="5958"&gt;This is where the project begins moving from a simple battery monitoring system toward a &lt;strong data-start="5912" data-end="5957"&gt;battery profiling and intelligence system&lt;/strong&gt;.&lt;/p&gt;
&lt;hr data-start="5960" data-end="5963" /&gt;
&lt;h1 id="mcetoc_1jvmcq8b11c" data-section-id="1a1x3sh" data-start="5965" data-end="5993"&gt;Estimating State of Charge&lt;/h1&gt;
&lt;div class="flex w-full grow flex-col overflow-hidden"&gt;
&lt;div class="h-full overflow-y-auto min-h-0 w-full relative flex w-full grow flex-col overflow-y-auto"&gt;
&lt;div class="mx-auto w-full"&gt;
&lt;div class="relative flex flex-col gap-y-3 px-4"&gt;
&lt;div class="flex items-start"&gt;
&lt;div class="flex flex-col gap-0.5 group w-full"&gt;
&lt;div&gt;
&lt;div class="flex flex-col gap-0.5"&gt;
&lt;div class="flex flex-row"&gt;
&lt;div class="min-w-0 grow"&gt;
&lt;div class="flex flex-col gap-0.5"&gt;
&lt;div class="px-2 py-1"&gt;
&lt;div&gt;
&lt;p&gt;Determining the exact state of charge (SOC) of a battery pack isn&amp;#39;t as simple as checking a voltage level. If you only look at voltage, it sags significantly when you draw a load (discharging) and rises when you apply a charger. To solve this, our Battery Management System (BMS) uses a&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;strong&gt;Hybrid SOC Algorithm&lt;/strong&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;combining&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;strong&gt;Coulomb Counting&lt;/strong&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;and&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;strong&gt;Open-Circuit Voltage (OCV) Recalibration&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Here is how the algorithm works:&lt;/p&gt;
&lt;h3 id="user-content-1-real-time-tracking-coulomb-counting"&gt;1. Real-Time Tracking: Coulomb Counting&lt;/h3&gt;
&lt;p&gt;During active operation, the BMS tracks the flow of current in and out of the battery second-by-second.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The Math:&lt;/strong&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Capacity (Ah) = Current (A) * Time (hours)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Charge Efficiency:&lt;/strong&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;When discharging (negative current), energy is drawn directly. When charging (positive current), a&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;strong&gt;98% Coulombic efficiency&lt;/strong&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;multiplier is applied to account for chemical and thermal losses during charge conversion.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Benefit:&lt;/strong&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;It responds instantly to sudden load changes and updates the SOC in real-time, regardless of voltage sags.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="user-content-2-auto-calibration-ocv-lookup-table--rest-timer"&gt;2. Auto-Calibration: OCV Lookup Table + Rest Timer&lt;/h3&gt;
&lt;p&gt;Over time, current sensors accumulate tiny measurement errors (drift), which causes Coulomb counting to become inaccurate. To correct this drift, I use an&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;strong&gt;OCV Lookup Table (LUT)&lt;/strong&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;containing the chemistry&amp;#39;s non-linear voltage-to-capacity curve.&lt;/p&gt;
&lt;p&gt;However, voltage is only a true representation of SOC when the battery chemistry has relaxed (neutralized).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The Rest Timer:&lt;/strong&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;The BMS continuously checks if the battery is &amp;quot;at rest&amp;quot; (current &amp;lt; 50mA).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Relaxation Window:&lt;/strong&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Once the battery remains at rest for&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;strong&gt;60 continuous seconds&lt;/strong&gt;, the algorithm trusts the voltage reading.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Recalibration:&lt;/strong&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;The OCV lookup table calculates the true chemical SOC, and the Coulomb counter (&lt;code class="whitespace-pre-wrap"&gt;accumulatedAh&lt;/code&gt;) is calibrated back to this exact value, erasing any accumulated sensor drift.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="user-content-3-smart-boot-synchronization"&gt;3. Smart Boot Synchronization&lt;/h3&gt;
&lt;p&gt;Since SOC is volatile, standard systems often write the SOC to EEPROM constantly, causing premature flash wear.&lt;/p&gt;
&lt;p&gt;Our hybrid algorithm solves this by only saving long-term health parameters (SOH and Cycle count) to EEPROM. When the system powers up:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It waits for the power rail to stabilize.&lt;/li&gt;
&lt;li&gt;It takes an averaged, direct voltage measurement.&lt;/li&gt;
&lt;li&gt;It snaps the Coulomb counter to match this OCV reading on boot.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This guarantees the system never boots up showing a stale or incorrect 0% SOC even if you swap in a different pack.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-start="6648" data-end="6651" /&gt;
&lt;h1 id="mcetoc_1jvmcq8b11d" data-section-id="1brgd79" data-start="6653" data-end="6682"&gt;Energy and Coulomb Tracking&lt;/h1&gt;
&lt;p data-start="6684" data-end="6785"&gt;Current measurement also allows me to track the amount of charge flowing into and out of the battery.By integrating current over time, I can estimate the accumulated charge in &lt;strong data-start="6862" data-end="6890"&gt;coulombs or ampere-hours&lt;/strong&gt;. Combining current with voltage also allows the system to calculate instantaneous power and accumulated energy.&lt;/p&gt;
&lt;p data-start="7005" data-end="7057"&gt;This information will eventually help me understand:&lt;/p&gt;
&lt;ul data-start="7059" data-end="7199"&gt;
&lt;li data-section-id="1olydm3" data-start="7059" data-end="7097"&gt;How much energy the battery delivers&lt;/li&gt;
&lt;li data-section-id="6nin27" data-start="7098" data-end="7139"&gt;How much energy is consumed by the load&lt;/li&gt;
&lt;li data-section-id="itcavp" data-start="7140" data-end="7157"&gt;Charging energy&lt;/li&gt;
&lt;li data-section-id="13sl4ej" data-start="7158" data-end="7178"&gt;Discharging energy&lt;/li&gt;
&lt;li data-section-id="1jcr82a" data-start="7179" data-end="7199"&gt;Battery efficiency&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-start="7201" data-end="7282"&gt;These measurements will also provide useful data for the battery profiling stage.&lt;/p&gt;
&lt;hr data-start="7360" data-end="7363" /&gt;
&lt;h1 id="mcetoc_1jvmcq8b11f" data-section-id="pvc8bt" data-start="7365" data-end="7390"&gt;Battery State of Health&lt;/h1&gt;
&lt;p data-start="7392" data-end="7405"&gt;SoC tells me:&lt;/p&gt;
&lt;blockquote data-start="7407" data-end="7452"&gt;
&lt;p data-start="7409" data-end="7452"&gt;&lt;strong data-start="7409" data-end="7452"&gt;How much charge is available right now?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="7454" data-end="7488"&gt;But SoH asks a different question:&lt;/p&gt;
&lt;blockquote data-start="7490" data-end="7553"&gt;
&lt;p data-start="7492" data-end="7553"&gt;&lt;strong data-start="7492" data-end="7553"&gt;How healthy is the battery compared with when it was new?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="7555" data-end="7683"&gt;For this project, I want to use the data collected during repeated charging and discharging cycles to study battery degradation.&lt;/p&gt;
&lt;p data-start="7685" data-end="7704"&gt;Parameters such as:&lt;/p&gt;
&lt;ul data-start="7706" data-end="7830"&gt;
&lt;li data-section-id="1mihjdx" data-start="7706" data-end="7726"&gt;Available capacity&lt;/li&gt;
&lt;li data-section-id="1lyrta8" data-start="7727" data-end="7755"&gt;Charge/discharge behaviour&lt;/li&gt;
&lt;li data-section-id="13n1z6x" data-start="7756" data-end="7774"&gt;Voltage response&lt;/li&gt;
&lt;li data-section-id="w3047x" data-start="7775" data-end="7798"&gt;Temperature behaviour&lt;/li&gt;
&lt;li data-section-id="a9rky2" data-start="7799" data-end="7816"&gt;Current profile&lt;/li&gt;
&lt;li data-section-id="wvefwb" data-start="7817" data-end="7830"&gt;Cycle count&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-start="7832" data-end="7875"&gt;can be analysed to estimate battery health.&lt;/p&gt;
&lt;p data-start="7877" data-end="8050"&gt;This will eventually become one of the more interesting parts of the Battery Management Node, where I plan to explore &lt;strong data-start="7995" data-end="8049"&gt;data-driven and ML-based battery health estimation&lt;/strong&gt;.&lt;/p&gt;
&lt;hr data-start="8052" data-end="8055" /&gt;
&lt;h1 id="mcetoc_1jvmcq8b11g" data-section-id="xnt40w" data-start="8057" data-end="8093"&gt;Using the Arduino UNO Q LED Matrix&lt;/h1&gt;
&lt;p data-start="8095" data-end="8176"&gt;I also wanted to make the battery information visible without requiring a laptop. The Arduino UNO Q provides an onboard LED matrix, so I decided to use it to display the current &lt;strong data-start="8274" data-end="8304"&gt;State of Charge percentage&lt;/strong&gt;.&lt;/p&gt;
&lt;p data-start="8307" data-end="8319"&gt;For example:&lt;/p&gt;
&lt;p data-start="8321" data-end="8328"&gt;&lt;strong data-start="8321" data-end="8328"&gt;75%&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="8330" data-end="8374"&gt;can be displayed directly on the LED matrix. As the battery is discharged, the displayed value updates accordingly. This provides a simple visual indication of battery status and also makes the system much more engaging during a live demonstration.&lt;/p&gt;
&lt;hr data-start="8582" data-end="8585" /&gt;
&lt;h2 id="mcetoc_1jvmcq8b11h" data-section-id="jhi3jp" data-start="8587" data-end="8655"&gt;&lt;img loading="lazy" alt="image" style="max-height:360px;max-width:640px;"  src="https://community.element14.com/resized-image/__size/1280x720/__key/communityserver-discussions-components-files/456/20260810_5F00_225950.jpg" /&gt;&lt;/h2&gt;
&lt;hr data-start="8657" data-end="8660" /&gt;
&lt;h1 id="mcetoc_1jvmcq8b11i" data-section-id="f56t4s" data-start="8662" data-end="8685"&gt;Software Architecture&lt;/h1&gt;
&lt;p data-start="8687" data-end="8750"&gt;The Arduino UNO Q continuously performs the following sequence:&lt;/p&gt;
&lt;p data-start="8752" data-end="8881"&gt;&lt;strong data-start="8752" data-end="8881"&gt;Read ADC &amp;rarr; Convert to Physical Value &amp;rarr; Filter/Calibrate &amp;rarr; Calculate Battery Parameters &amp;rarr; Update LED Matrix &amp;rarr; Update Telemetry&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="8883" data-end="8931"&gt;The three ADC channels are sampled periodically:&lt;/p&gt;
&lt;p data-start="8933" data-end="8949"&gt;&lt;strong data-start="8933" data-end="8949"&gt;A0 &amp;rarr; Current&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="8951" data-end="8975"&gt;&lt;strong data-start="8951" data-end="8975"&gt;A1 &amp;rarr; Battery Voltage&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="8977" data-end="8997"&gt;&lt;strong data-start="8977" data-end="8997"&gt;A2 &amp;rarr; Temperature&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="8999" data-end="9119"&gt;The raw ADC values are then converted into meaningful engineering units using the calibration parameters of each sensor. The processed values can then be used for the higher-level battery calculations.&lt;/p&gt;
&lt;hr data-start="9266" data-end="9269" /&gt;
&lt;h1 id="mcetoc_1jvmcq8b11k" data-section-id="9mxr15" data-start="9271" data-end="9292"&gt;Code Implementation&lt;/h1&gt;
&lt;p data-start="9294" data-end="9448"&gt;The software is being developed in stages, starting with reliable sensor acquisition and calibration before moving towards the battery intelligence layer.&lt;/p&gt;
&lt;p data-start="9450" data-end="9508"&gt;Some of the important parts of the implementation include:&lt;/p&gt;
&lt;ul data-start="9510" data-end="9736"&gt;
&lt;li data-section-id="58jfr" data-start="9510" data-end="9527"&gt;ADC acquisition&lt;/li&gt;
&lt;li data-section-id="zemwun" data-start="9528" data-end="9556"&gt;ACS723 current calculation&lt;/li&gt;
&lt;li data-section-id="1xes2jy" data-start="9557" data-end="9586"&gt;Battery voltage calculation&lt;/li&gt;
&lt;li data-section-id="1jlklro" data-start="9587" data-end="9616"&gt;NTC temperature calculation&lt;/li&gt;
&lt;li data-section-id="s10570" data-start="9617" data-end="9633"&gt;SoC estimation&lt;/li&gt;
&lt;li data-section-id="127bwam" data-start="9634" data-end="9652"&gt;Coulomb counting&lt;/li&gt;
&lt;li data-section-id="nxj28n" data-start="9653" data-end="9683"&gt;Power and energy calculation&lt;/li&gt;
&lt;li data-section-id="4gf671" data-start="9684" data-end="9711"&gt;Battery status monitoring&lt;/li&gt;
&lt;li data-section-id="dntz97" data-start="9712" data-end="9736"&gt;LED matrix SoC display&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-start="9738" data-end="9741" /&gt;
&lt;h2 id="mcetoc_1jvmcq8b11l" data-section-id="1cqptil" data-start="9743" data-end="9817"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;// Force high-resolution ADC in setup()
analogReadResolution(14); 

// Exponential Moving Average filter configuration
const float FILTER_ALPHA = 0.1f;
bool isFirstRead = true;

void readSensors() {
  // Read raw inputs
  float rawCurrent = readACS723Current();
  float rawVoltage = readVoltageDivider();
  float rawTemp    = readNTCTemperature();

  // Smooth signals using EMA Filter
  if (isFirstRead) {
    systemCurrent     = rawCurrent;
    systemVoltage     = rawVoltage;
    systemTemperature = rawTemp;
    isFirstRead       = false;
  } else {
    systemCurrent     = FILTER_ALPHA * rawCurrent + (1.0f - FILTER_ALPHA) * systemCurrent;
    systemVoltage     = FILTER_ALPHA * rawVoltage + (1.0f - FILTER_ALPHA) * systemVoltage;
    if (rawTemp &amp;gt; -900.0f) {
      systemTemperature = FILTER_ALPHA * rawTemp + (1.0f - FILTER_ALPHA) * systemTemperature;
    }
  }
}&lt;/pre&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span&gt;This snippet shows the sensor acquisition routine configured for high-precision&amp;nbsp;&lt;/span&gt;&lt;strong&gt;14-bit ADC resolution&lt;/strong&gt;&lt;span&gt;&amp;nbsp;(0 - 16383 counts) and smoothed using an&amp;nbsp;&lt;/span&gt;&lt;strong&gt;Exponential Moving Average (EMA)&lt;/strong&gt;&lt;span&gt;&amp;nbsp;filter to filter out switching noise&lt;/span&gt;&lt;/p&gt;
&lt;hr data-start="9819" data-end="9822" /&gt;
&lt;h2 id="mcetoc_1jvmcq8b11m" data-section-id="td4fqo" data-start="9824" data-end="9902"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;// Configuration parameters
const float CURRENT_ZERO_POINT_V       = 1.70f;  // Mid-point offset (V)
const float CURRENT_SENSITIVITY_V_PER_A = 1.336f; // V/A sensitivity

// Conversion logic
int   currentADC  = analogRead(CURRENT_PIN);
float currentPinV = ((float)currentADC / ADC_MAX_VALUE) * ADC_REF_VOLTAGE;

// Calculate current (negative = discharge/load, positive = charging)
float rawCurrent  = (currentPinV - CURRENT_ZERO_POINT_V) / CURRENT_SENSITIVITY_V_PER_A;
&lt;/pre&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span&gt;The current is calculated using an ACS723 Hall-Effect sensor. Negative current denotes discharge (load connected), and positive current denotes charging&lt;/span&gt;&lt;/p&gt;
&lt;hr data-start="9904" data-end="9907" /&gt;
&lt;h2 id="mcetoc_1jvmcq8b11n" data-section-id="robk9c" data-start="9909" data-end="9988"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;// Configuration parameters (6.8K and 3K resistors)
const float R1_VOLTAGE_DIVIDER = 6800.0f;
const float R2_VOLTAGE_DIVIDER = 3000.0f;
const float VOLTAGE_MULTIPLIER = (R1_VOLTAGE_DIVIDER + R2_VOLTAGE_DIVIDER) / R2_VOLTAGE_DIVIDER;

// Conversion logic
int   voltageADC  = analogRead(VOLTAGE_PIN);
float voltagePinV = ((float)voltageADC / ADC_MAX_VALUE) * ADC_REF_VOLTAGE;

// Scaling to calculate full pack voltage
float rawVoltage  = voltagePinV * VOLTAGE_MULTIPLIER;
&lt;/pre&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span&gt;The total battery voltage is calculated by scaling the voltage at the divider pin back to the full pack voltage using the calculated scaling multiplier.&lt;/span&gt;&lt;/p&gt;
&lt;hr data-start="9990" data-end="9993" /&gt;
&lt;h2 id="mcetoc_1jvmcq8b11o" data-section-id="zkcrkd" data-start="9995" data-end="10074"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;// Configuration parameters
const float NTC_VCC                = 5.0f;
const float NTC_R_FIXED            = 3300.0f;
const float NTC_R_SERIES           = 3300.0f;
const float NTC_NOMINAL_RESISTANCE = 10000.0f;
const float NTC_NOMINAL_TEMP       = 298.15f; // 25 C
const float NTC_BETA               = 3950.0f;

// Steinhart-Hart conversion logic
int   tempADC   = analogRead(TEMP_PIN);
float tempPinV  = ((float)tempADC / ADC_MAX_VALUE) * ADC_REF_VOLTAGE;
float rawTemp   = -999.0f;

if (tempPinV &amp;gt; 0.01f) {
  float rNTC    = (NTC_VCC * NTC_R_FIXED / tempPinV) - (NTC_R_FIXED + NTC_R_SERIES);
  if (rNTC &amp;gt; 0.0f) {
    float s = log(rNTC / NTC_NOMINAL_RESISTANCE);
    s = (s / NTC_BETA) + (1.0f / NTC_NOMINAL_TEMP);
    rawTemp = (1.0f / s) - 273.15f; // Kelvin to Celsius conversion
  }
}
&lt;/pre&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span&gt;The thermistor temperature is processed using the&amp;nbsp;&lt;/span&gt;&lt;strong&gt;Steinhart-Hart equation&lt;/strong&gt;&lt;span&gt;&amp;nbsp;to convert NTC resistance changes into Degrees Celsius&lt;/span&gt;&lt;/p&gt;
&lt;hr data-start="10076" data-end="10079" /&gt;
&lt;h2 id="mcetoc_1jvmcq8b11p" data-section-id="1sql7gt" data-start="10081" data-end="10168"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;// 1. Coulomb Counter integration (runs every 100ms)
void integrateCoulombs(unsigned long timeDeltaMs) {
  float dt_hours = (float)timeDeltaMs / 3600000.0f;

  if (systemCurrent &amp;gt; 0.0f) {
    // Charging: apply coulombic efficiency (98%)
    accumulatedAh += systemCurrent * CHARGE_EFFICIENCY * dt_hours;
  } else {
    // Discharging: subtract directly
    accumulatedAh += systemCurrent * dt_hours;
  }

  // Hard clamp accumulated capacity inside limits
  if (accumulatedAh &amp;gt; persistentState.actualMaxCapacityAh) accumulatedAh = persistentState.actualMaxCapacityAh;
  if (accumulatedAh &amp;lt; 0.0f) accumulatedAh = 0.0f;
}

// 2. Hybrid OCV Calibration and SOC calculation
void calculateSOC() {
  bool isAtRest = (fabs(systemCurrent) &amp;lt; REST_CURRENT_THRESHOLD_A);

  if (isAtRest) {
    rest_timer_ms += READ_INTERVAL_MS;
  } else {
    rest_timer_ms = 0; // Current flow resets the rest timer
  }

  // Recalibration: only trust OCV when battery has rested (60 seconds)
  if (rest_timer_ms &amp;gt;= REST_DURATION_MS) {
    float ocvSOC = get_soc_from_ocv(systemVoltage);
    accumulatedAh = (ocvSOC / 100.0f) * persistentState.actualMaxCapacityAh;
    rest_timer_ms = 0; // Reset calibration trigger
    Serial.println(&amp;quot;Coulomb counter recalibrated using resting OCV.&amp;quot;);
  }

  // Calculate final system SOC
  systemSOC = (accumulatedAh / persistentState.actualMaxCapacityAh) * 100.0f;
  if (systemSOC &amp;gt; 100.0f) systemSOC = 100.0f;
  if (systemSOC &amp;lt;   0.0f) systemSOC =   0.0f;
}
&lt;/pre&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span&gt;This snippet integrates Coulomb counting during current flow (applying charging efficiency) and runs the rest-timer OCV recalibration when the battery relaxes.&lt;/span&gt;&lt;/p&gt;
&lt;hr data-start="10170" data-end="10173" /&gt;
&lt;h2 id="mcetoc_1jvmcq8b11q" data-section-id="1x13ghs" data-start="10175" data-end="10249"&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;void render_soc(float soc) {
  // Convert float SOC to rounded 2-digit integer
  int soc_int = (int)(soc + 0.5f);
  if (soc_int &amp;gt; 99) soc_int = 99;
  if (soc_int &amp;lt; 0) soc_int = 0;

  // Lock buffer to prevent race conditions during rendering
  k_mutex_lock(&amp;amp;matrix_mtx, K_FOREVER);
  clear_matrix_buf();

  if (soc_int &amp;gt;= 10) {
    int tens = soc_int / 10;
    int units = soc_int % 10;
    render_digit(tens, 1, 7);  // Left digit (cols 1-5)
    render_digit(units, 7, 7); // Right digit (cols 7-11)
  } else {
    render_digit(soc_int, 4, 7); // Center single digit (cols 4-8)
  }

  flush_matrix_locked(); // Output buffer to Arduino LED matrix hardware
  k_mutex_unlock(&amp;amp;matrix_mtx);
}
&lt;/pre&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span&gt;This snippet shows how a custom pixel font renders the SOC integers, safely locked behind Zephyr RTOS mutexes to ensure thread safety while writing to the matrix buffer.&lt;/span&gt;&lt;/p&gt;
&lt;hr data-start="10251" data-end="10254" /&gt;
&lt;h1 id="mcetoc_1jvmcq8b11r" data-section-id="1rzek42" data-start="10256" data-end="10305"&gt;From Battery Monitoring to Battery Intelligence&lt;/h1&gt;
&lt;p data-start="10307" data-end="10390"&gt;At this stage, the system can measure the three most important physical parameters:&lt;/p&gt;
&lt;p data-start="10392" data-end="10427"&gt;&lt;strong data-start="10392" data-end="10427"&gt;Voltage &amp;rarr; Current &amp;rarr; Temperature&lt;/strong&gt;&lt;/p&gt;
&lt;p data-start="10429" data-end="10460"&gt;But this is only the beginning. The ultimate goal of this Battery Management Node is not simply to display these values.&lt;/p&gt;
&lt;p data-start="10552" data-end="10613"&gt;I want the system to understand what these measurements mean.&lt;/p&gt;
&lt;p data-start="10615" data-end="10654"&gt;For example, instead of simply showing:&lt;/p&gt;
&lt;blockquote data-start="10656" data-end="10676"&gt;
&lt;p data-start="10658" data-end="10676"&gt;Temperature = 42&amp;deg;C&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="10678" data-end="10728"&gt;the system should eventually be able to determine:&lt;/p&gt;
&lt;blockquote data-start="10730" data-end="10790"&gt;
&lt;p data-start="10732" data-end="10790"&gt;&lt;span class="emoticon" data-url="https://community.element14.com/cfs-file/__key/system/emoji/26a0.svg" title="Warning"&gt;&amp;#x26a0;&lt;/span&gt;️ Battery temperature is increasing faster than expected.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="10792" data-end="10819"&gt;Instead of only displaying:&lt;/p&gt;
&lt;blockquote data-start="10821" data-end="10832"&gt;
&lt;p data-start="10823" data-end="10832"&gt;SoH = 78%&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="10834" data-end="10870"&gt;the system could eventually provide:&lt;/p&gt;
&lt;blockquote data-start="10872" data-end="10938"&gt;
&lt;p data-start="10874" data-end="10938"&gt;&lt;span class="emoticon" data-url="https://community.element14.com/cfs-file/__key/system/emoji/26a0.svg" title="Warning"&gt;&amp;#x26a0;&lt;/span&gt;️ Battery degradation detected. Battery inspection recommended.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="10940" data-end="11063"&gt;Similarly, abnormal combinations of voltage, current and temperature could be used to identify potential battery anomalies. This is where the collected battery data will become extremely valuable for&lt;strong data-start="11156" data-end="11213"&gt;&amp;nbsp;battery profiling and predictive maintenance&lt;/strong&gt; features planned for EVA Guardian.&lt;/p&gt;
&lt;hr data-start="11250" data-end="11253" /&gt;
&lt;h1 id="mcetoc_1jvmcq8b11s" data-section-id="1hqyib6" data-start="11255" data-end="11294"&gt;Testing the Battery Management System&lt;/h1&gt;
&lt;p data-start="11296" data-end="11397"&gt;The next stage is to systematically test the measurement system under different operating conditions.&lt;/p&gt;
&lt;p data-start="11399" data-end="11433"&gt;I plan to test the battery during:&lt;/p&gt;
&lt;ul data-start="11435" data-end="11567"&gt;
&lt;li data-section-id="1yqv0af" data-start="11435" data-end="11445"&gt;Charging&lt;/li&gt;
&lt;li data-section-id="3bkfjn" data-start="11446" data-end="11460"&gt;Idle/resting&lt;/li&gt;
&lt;li data-section-id="14ko7to" data-start="11461" data-end="11484"&gt;Low-current discharge&lt;/li&gt;
&lt;li data-section-id="1ofhu92" data-start="11485" data-end="11509"&gt;High-current discharge&lt;/li&gt;
&lt;li data-section-id="ff0ttt" data-start="11510" data-end="11544"&gt;Different temperature conditions&lt;/li&gt;
&lt;li data-section-id="1hs651z" data-start="11545" data-end="11567"&gt;Different SoC levels&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-start="11569" data-end="11712"&gt;The goal is to compare the measured values against reference measurements and calibrate the system before using the data.Therefore, the initial focus is on making sure that the &lt;strong data-start="11894" data-end="11971"&gt;voltage, current and temperature measurements are reliable and repeatable&lt;/strong&gt;.&lt;/p&gt;
&lt;hr data-start="12061" data-end="12064" /&gt;
&lt;h1 id="mcetoc_1jvmcq8b11u" data-section-id="1gsfyhe" data-start="12066" data-end="12087"&gt;Demonstration Video&lt;/h1&gt;
&lt;p data-start="12089" data-end="12263"&gt;I have also recorded a demonstration of the Battery Management Node showing the hardware setup, sensor measurements and real-time SoC display on the Arduino UNO Q LED matrix.&lt;/p&gt;
&lt;hr data-start="12265" data-end="12268" /&gt;
&lt;p&gt;&lt;a href="https://youtu.be/LWDSxiQ9Io4"&gt;https://youtu.be/LWDSxiQ9Io4&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1jvmcq8b11v" data-section-id="fl6481" data-start="12270" data-end="12350"&gt;&lt;span&gt;&lt;strong data-start="12273" data-end="12350"&gt;&lt;/strong&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;hr data-start="12352" data-end="12355" /&gt;
&lt;h1 id="mcetoc_1jvmcq8b120" data-section-id="1h02p40" data-start="12357" data-end="12371"&gt;What&amp;#39;s Next?&lt;/h1&gt;
&lt;div class="qMYqUG_convSearchResultHighlightRoot"&gt;
&lt;div class="" data-turn-id-container="request-6a0deab6-e2f8-8321-beb6-4abed4003c0e-1" data-is-intersecting="true"&gt;
&lt;div&gt;
&lt;div data-conversation-screenshot-content=""&gt;
&lt;div class="flex max-w-full flex-col gap-4 grow"&gt;
&lt;div dir="auto" data-message-author-role="assistant" data-message-id="e1bfbb42-3d40-437d-abf8-3c34eced781c" data-message-model-slug="gpt-5-6" data-turn-start-message="true"&gt;
&lt;div class="flex w-full flex-col gap-1 empty:hidden"&gt;
&lt;div class="markdown prose dark:prose-invert wrap-break-word w-full light markdown-new-styling"&gt;
&lt;p class="PDq2pG_selectionAnchorContainer" data-start="16" data-end="194"&gt;With the basic Battery Management Node now assembled, the next stage of &lt;strong data-start="88" data-end="104"&gt;EVA Guardian&lt;/strong&gt; is to bring the individual modules together into a complete safety and monitoring system. The first step will be to &lt;strong data-start="222" data-end="307"&gt;integrate the Battery Management System (BMS) and Incident Detection System (IDS)&lt;/strong&gt; so that battery information and vehicle safety information can be monitored together.&lt;/p&gt;
&lt;p data-start="395" data-end="431"&gt;The integrated system will focus on:&lt;/p&gt;
&lt;ul data-start="433" data-end="695"&gt;
&lt;li data-section-id="1mjr4bt" data-start="433" data-end="486"&gt;Battery voltage, current and temperature monitoring&lt;/li&gt;
&lt;li data-section-id="k9ek48" data-start="487" data-end="521"&gt;SoC and battery operating status&lt;/li&gt;
&lt;li data-section-id="1w5r2jx" data-start="522" data-end="576"&gt;Overvoltage, undervoltage and overcurrent protection&lt;/li&gt;
&lt;li data-section-id="x9sw8s" data-start="577" data-end="616"&gt;Vehicle motion and incident detection&lt;/li&gt;
&lt;li data-section-id="1ayr5v8" data-start="617" data-end="637"&gt;Accident detection&lt;/li&gt;
&lt;li data-section-id="7zzhlf" data-start="638" data-end="669"&gt;Distance and trip information&lt;/li&gt;
&lt;li data-section-id="cfl5uj" data-start="670" data-end="695"&gt;Real-time system status&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-start="697" data-end="1074"&gt;I will also be implementing a &lt;strong data-start="727" data-end="783"&gt;safety relay and automatic power switching mechanism&lt;/strong&gt;. The objective is to allow the system to respond to critical conditions by automatically controlling the battery/load power path. A backup power source will also be incorporated to keep essential functions such as the safety controller and emergency communication operational when required.&lt;/p&gt;
&lt;p data-start="1076" data-end="1250"&gt;Finally, I will bring all the information together through a &lt;strong data-start="1137" data-end="1160"&gt;web-based interface&lt;/strong&gt;, providing a single dashboard for monitoring both the battery and vehicle safety systems.&lt;/p&gt;
&lt;p data-start="1252" data-end="1351"&gt;The overall system will therefore evolve from two independent prototypes into a connected platform:&lt;/p&gt;
&lt;blockquote data-start="1353" data-end="1461"&gt;
&lt;p data-start="1355" data-end="1461"&gt;&lt;strong data-start="1355" data-end="1461"&gt;Battery Management + Incident Detection &amp;rarr; Safety Control &amp;rarr; Automatic Power Management &amp;rarr; Web Monitoring&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-start="1463" data-end="1739" data-is-last-node="" data-is-only-node=""&gt;The next phase will focus on this system-level integration and demonstrating how EVA Guardian can continuously monitor the EV, detect abnormal conditions, take appropriate safety action, and provide the user with a clear view of the vehicle&amp;#39;s overall health and safety status.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div class="text-center"&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div data-conversation-screenshot-content=""&gt;
&lt;div&gt;Github Repo: &lt;a href="https://github.com/ForgedCircuits/EVA-Guardian.git" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;https://github.com/ForgedCircuits/EVA-Guardian.git&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="pointer-events-none -mt-px h-px translate-y-(--scroll-root-safe-area-inset-bottom)"&gt;&lt;/div&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;</description></item><item><title>DockBot - Part 4 - Getting the Arduino Q to move the tank motors</title><link>https://community.element14.com/thread/57162?ContentTypeID=0</link><pubDate>Mon, 10 Aug 2026 06:04:12 GMT</pubDate><guid isPermaLink="false">93d5dcb4-84c2-446f-b2cb-99731719e767:96a64840-3e35-47bc-a1fd-1e67ff5fc3c4</guid><dc:creator>arvindsa</dc:creator><slash:comments>5</slash:comments><comments>https://community.element14.com/thread/57162?ContentTypeID=0</comments><wfw:commentRss>https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57162/dockbot---part-4---getting-the-arduino-q-to-move-the-tank-motors/rss?ContentTypeId=0</wfw:commentRss><description>&lt;h2 id="mcetoc_1jvl2tclo0"&gt;Recap&lt;/h2&gt;
&lt;p&gt;I am building a robotic system that identifies the charging port on an EV and automatically moves a charger arm to plug the charger in.&lt;/p&gt;
&lt;p&gt;Past Forum Posts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57056/dockbot---part-1---the-concept" data-e14adj="t"&gt;DockBot - Part 1 - The Concept&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57069/dockbot---part-2---positioning-with-aruco-markers" data-e14adj="t"&gt;DockBot - Part 2 - Positioning with Aruco Markers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/p/addpost/community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57142/dockbot---part-3---new-plan-new-hardware-for-better-sensing?COM=e14c-direct-ugc&amp;amp;CMP=e14c-direct-ugc&amp;amp;osetc=e14c-direct-ugc" target="_blank" data-e14adj="t"&gt;DockBot - Part 3 - New Plan, New Hardware for Better Sensing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This part is about turning the Devastator chassis from Part 3 into something that actually drives, and getting the Arduino UNO Q to be the one driving it.&lt;/p&gt;
&lt;h2 id="mcetoc_1jvl2tclo1"&gt;The new motors and chassis mods&lt;/h2&gt;
&lt;p&gt;In the last post I mentioned that the Devastator Chassis motors were burnt out and I had ordered a All Gear Motors, I received my motors went ahead and installed the replacement all-metal BO motor and found two problems - one I&amp;#39;d anticipated, one I hadn&amp;#39;t. The original Yellow BO motors were L-shaped; the replacement is straight. Before I even ordered it I had checked for collisions and found that a flange with a nut, which connects the roof of the platform to the side, would be in the way. Fix for that is to cut the flange and replace it&amp;#39;s functionality with a 3D printed bracket.&lt;/p&gt;
&lt;p&gt;&lt;img style="display:block;margin-left:auto;margin-right:auto;max-height:450px;max-width:800px;" alt="Modding the chassis" src="https://community.element14.com/resized-image/__size/1600x900/__key/communityserver-discussions-components-files/456/p4_2D00_1.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Below is a&amp;nbsp; video of me modding the Chassis. Watch it only if you love watching such logs. Its a bore for others.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://community.element14.com/cfs-file/__key/communityserver-discussions-components-files/456/p4_2D00_v2.mp4"&gt;community.element14.com/.../p4_2D00_v2.mp4&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The second problem was new: a little plastic stub that I think holds one of the gears in place looked mechanically identical to the original but wasn&amp;#39;t quite. There was a minor location difference, Had to drill out the location hole to get it to fit.&lt;/p&gt;
&lt;p&gt;&lt;img loading="lazy" style="display:block;margin-left:auto;margin-right:auto;max-height:450px;max-width:800px;" alt="Drilling locatr hole" src="https://community.element14.com/resized-image/__size/1600x900/__key/communityserver-discussions-components-files/456/p4_2D00_2.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Both motors installed, tracks back on, and I confirmed both spin the right way before moving on to electronics.&lt;/p&gt;
&lt;h2 id="mcetoc_1jvl30b9a2"&gt;The electronics&lt;/h2&gt;
&lt;p&gt;The Arduino UNO Q becomes the brain of the whole platform. The Qualcomm Dragonwing QRB2210 side runs Linux and acts as the overall controller - it&amp;#39;ll receive positioning data from the Radxa Q6A about where the robot and the car actually are. The STM32U585 side handles the real time motor control loop through a DRV8833 driver.&lt;/p&gt;
&lt;p&gt;As a mechatronics engineer I know that manufacturing differences between motors - different inertia and friction in the gear train, different friction in the track and wheel system - mean the two motors won&amp;#39;t turn at exactly the same speed for the same commanded voltage. Sometimes you get lucky and the mismatch is tiny, but I didn&amp;#39;t want to bet on luck, so I planned ahead for an encoder on each motor shaft, which is why I bought motors with a shaft on both ends in the first place. I&amp;#39;ll be using a TLE5012B as a non-contact magnetic encoder for that - not wired up yet, more on that below. I am using TLE5012B just because I have surplus of it from another project, But there are more easier to procure and easier to interface encoders available.&lt;/p&gt;
&lt;p&gt;For power, I&amp;#39;m using a 3500mAh 2S 7.4V Li-ION battery. The Arduino Q gets powered through its VIN pin (&lt;a href="https://docs.arduino.cc/tutorials/uno-q/power-specification/" rel="noopener noreferrer nofollow" target="_blank" data-e14adj="t"&gt;official power spec&lt;/a&gt;). During testing I found it needs a minimum of 6.7V to run reliably - the official docs say 7V, but that&amp;#39;s presumably for full-load conditions. The motors are rated at 6V, so a buck converter in parallel brings that down to 5.9V before it reaches the motors via the DRV8833.&lt;/p&gt;
&lt;p&gt;I made a custom perfboard PCB to house the DRV8833, a screw terminal for battery in, the motor leads, and a 2.54mm JST-XH connector carrying the 6V rail into the driver. The one snag: the battery uses a female T-Deans connector, and I didn&amp;#39;t have a matching male one on hand. I had to do a makeshift, admittedly dangerous workaround to get power flowing for bench testing. I would not recommend anyone do what I did - that battery has a high discharge rate and is a genuine fire hazard if shorted. I&amp;#39;ve ordered the correct connector; whether it arrives before the submission deadline is a different question.&lt;/p&gt;
&lt;p style="text-align:center;"&gt;&lt;img loading="lazy" style="display:block;margin-left:auto;margin-right:auto;max-height:450px;max-width:800px;" alt="Custom Perfboard with the Buck Converter" src="https://community.element14.com/resized-image/__size/1600x900/__key/communityserver-discussions-components-files/456/p4_2D00_3.jpg" /&gt;&lt;br /&gt;Perfboard driver PCB - DRV8833, screw terminals, JST-XH power in&lt;/p&gt;
&lt;p style="text-align:center;"&gt;&lt;img loading="lazy" alt="image" style="max-height:450px;max-width:800px;"  src="https://community.element14.com/resized-image/__size/1600x900/__key/communityserver-discussions-components-files/456/p4_2D00_4.jpg" /&gt;&lt;/p&gt;
&lt;h2 id="mcetoc_1jvl38va33"&gt;The code&lt;/h2&gt;
&lt;p&gt;For Arduino UNO Q programming, I&amp;#39;m building on the work from the last design challenge - &lt;a href="https://community.element14.com/challenges-projects/design-challenges/on-the-line/" data-e14adj="t"&gt;On the Line&lt;/a&gt; - where I &lt;a href="https://community.element14.com/challenges-projects/design-challenges/on-the-line/f/forum/57018/solarsense---part-2---can-arduino-can" data-e14adj="t"&gt;disabled the bridge between the Dragonwing Linux side and the STM32&lt;/a&gt;, giving the STM32 full bare-metal control instead of going through the Arduino sketch layer. Same STM32CubeMX HAL project setup carried over, pins reassigned for this board.&lt;/p&gt;
&lt;p&gt;For now I&amp;#39;m deliberately skipping the encoder and any PID control - the goal of this post is just getting the hardware moving, closed-loop speed control is a separate step.&lt;/p&gt;
&lt;p&gt;The wire protocol between the Linux side and the STM32 is a tiny line-based ASCII format:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="text"&gt;M &amp;lt;left&amp;gt; &amp;lt;right&amp;gt;   set track motor speeds, -255..255 each
S &amp;lt;id&amp;gt; &amp;lt;angle&amp;gt;     set servo &amp;lt;id&amp;gt; (0 or 1) to &amp;lt;angle&amp;gt; degrees, 0-180
H                  halt (motors only)&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;with a 500ms command-timeout fail safe in STM32 firmware - if no valid line shows up within that window it halts the motors on its own, so a crashed Linux-side process can&amp;#39;t leave the tank driving into something:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;void UartProtocol_Poll(void)
{
  if (s_line_ready) {
    s_line_buf[s_line_len] = &amp;#39;\0&amp;#39;;
    handle_line(s_line_buf);
    s_line_len = 0;
    s_line_ready = 0;
  }

  if (HAL_GetTick() - s_last_command_ms &amp;gt; COMMAND_TIMEOUT_MS) {
    Motor_Stop();
  }
}&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;First bench test immediately turned up two bugs. One was the usual - the left motor&amp;#39;s direction sense was wired backwards relative to the `M &amp;lt;left&amp;gt; &amp;lt;right&amp;gt;` sign, so a positive command drove it in reverse. Easy fix, just invert it in software rather than rewire anything.&lt;/p&gt;
&lt;p&gt;The second one took longer to track down. With the DRV8833&amp;#39;s DIR pin held HIGH, the driver alternates full-drive and brake every PWM cycle instead of the documented drive/coast behavior - so with DIR high, a small commanded duty was actually the fastest the motor would go, and a large duty stalled it. Completely backwards from what I expected. Once I figured that out, complementing the duty (`255 - magnitude`) whenever DIR is HIGH fixed it:&lt;/p&gt;
&lt;p&gt;&lt;pre class="ui-code" data-mode="c_cpp"&gt;static uint32_t compute_duty(int16_t speed)
{
  uint32_t magnitude = (uint32_t)(speed &amp;lt; 0 ? -speed : speed);
  return speed &amp;lt; 0 ? 255U - magnitude : magnitude;
}&lt;/pre&gt;&lt;/p&gt;
&lt;p&gt;Both directions now scale proportionally from zero on the bench. There&amp;#39;s still a small deadband near zero but that is due to the the PWM having too less duty to overcome the static friction. I&amp;#39;ll deal with that later.&lt;/p&gt;
&lt;p&gt;For actually driving the thing during bench testing, I have to credit the author of &lt;a href="https://community.element14.com/challenges-projects/design-challenges/ez-ev-challenge/f/forum/57131/hall-w-ev-post-1---the-wheels-are-turning-mostly" data-e14adj="t"&gt;Hall W EV - Post 1 - The Wheels Are Turning (Mostly)&lt;/a&gt;&amp;nbsp;&amp;nbsp;&lt;a href="https://community.element14.com/members/tamadillo"&gt;tamadillo&lt;/a&gt;&amp;nbsp;and&amp;nbsp;&lt;a href="https://community.element14.com/members/hambreros"&gt;hambreros&lt;/a&gt;&amp;nbsp;&amp;nbsp;for the idea of hosting a web UI directly on the UNO Q for manual control. I would have made a python tkinter GUI to tun on host computer. Only difference is I set mine up by hand instead of through App Lab - a plain Python&amp;nbsp;&lt;code&gt;http.server&lt;/code&gt; handler that serves a page with sliders and posts JSON to &lt;code&gt;/api/drive&lt;/code&gt;, &lt;code&gt;/api/servo&lt;/code&gt;, &lt;code&gt;/api/halt&lt;/code&gt;, which get relayed straight down the serial link to the STM32:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://community.element14.com/cfs-file/__key/communityserver-discussions-components-files/456/p4_2D00_v1.mp4"&gt;community.element14.com/.../p4_2D00_v1.mp4&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Since the STM32 halts on a 500ms silence, the page has to keep resending the current slider values every 150ms while a slider is held - releasing it (or hitting Stop) sends &lt;code&gt;H&lt;/code&gt; and stops the resend loop. I later added +/-1 and +/-5 bump buttons alongside the drag sliders, for the fine adjustments that are basically impossible to hit by dragging a slider with a mouse.&lt;/p&gt;
&lt;h2 id="mcetoc_1jvl3kjcj4"&gt;What&amp;#39;s next&lt;/h2&gt;
&lt;p&gt;Encoder wiring and a basic speed-control loop are next on the STM32 side (Assuming the Diametrically polarized magnets come in time), then getting the Linux side actually talking guidance over the serial link instead of me driving it by hand with sliders with the Radxa showing the absolute positioning.&lt;/p&gt;
&lt;h2 id="mcetoc_1jvl3kjcj5"&gt;Final Notes&lt;/h2&gt;
&lt;p&gt;Devastator Chassis is a tank again. I feel I should some RGB lights, What do you say?&lt;/p&gt;&lt;div style="clear:both;"&gt;&lt;/div&gt;</description></item></channel></rss>