<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Liam</title>
    <description>The latest articles on DEV Community by Liam (@liammoss41).</description>
    <link>https://dev.to/liammoss41</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4006159%2F5d34c1e1-b5f6-453f-b685-bd12233f79ef.jpg</url>
      <title>DEV Community: Liam</title>
      <link>https://dev.to/liammoss41</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/liammoss41"/>
    <language>en</language>
    <item>
      <title>Robotics Concepts for Beginners</title>
      <dc:creator>Liam</dc:creator>
      <pubDate>Tue, 08 Sep 2026 20:18:12 +0000</pubDate>
      <link>https://dev.to/liammoss41/robotics-concepts-for-beginners-5alj</link>
      <guid>https://dev.to/liammoss41/robotics-concepts-for-beginners-5alj</guid>
      <description>&lt;h2&gt;
  
  
  Introduction: Why Robotics, Why Now
&lt;/h2&gt;

&lt;p&gt;Robotics is everywhere. Over the years we have seen products from the Romba, to Boston Dynamics dog to self-driving cars and now humanoid robots. This robots are made to handle differenet circumstances then a automationous machine in a factory. They go through differenet process to make them into what they are. Using fundamentals and concepts to help shape how each robot works. So weither you are a hobbyist, a student or a merely curious, in this post we will discuss the core concepts of robotics.&lt;br&gt;
Robotics Concepts for Beginners&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is a Robot, Really?
&lt;/h2&gt;

&lt;p&gt;Oxford diconary defines robot as "a machine that can perform a complicated series of tasks by itself". This is the very basic idea of a robot. Robots can complete tasks that a automatious machines can't do. Robots can think and decide on what they need to do and how to act apon that. So what counts as a robot then? Does a dishwasher count? No, I wouldn't think so. A dishwasher has set programs, it does count if there is 1, 5 or 30 dishes. Maybe a smart thermostat? Well it can detect if a room is colder or hotter then x amount and change accordingly so it must be a robot. Well a thermostat can't tell if the outside world is summer or winter. It can't tell if you the individual is roasting or shivering in their room. It cant't tell if their is people even in the room for there to be a need to be on in the first place. The user still has to control it.&lt;/p&gt;

&lt;p&gt;Take a Roomba for an example. A Roomba can clean a house unsupervised at a desired timed. A Roomba uses multiple sensor and trained data sets to learn and understand it's envirnoment. It maps your floor layout and will clean surfaces based on that. However let's say one day you deciede that you want to move your couch or you left a box in the middle of your living room. A Roomba has the ablility to notice that the floor plan has changed and is able to make the decision to go around that object. A user just has to set it up, pick a time and let the robot doe it's magic.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three Building Blocks: Sensors, Actuators, and Control
&lt;/h2&gt;

&lt;p&gt;A robot only really needs three things. It needs a way to take in information about the world, a way to physically do something about it, and something in the middle that decides what that something should be. Sensors, actuators and a controller. Every robot you have ever seen is some version of those three parts bolted together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sensors&lt;/strong&gt; are how a robot perceives. A camera gives it sight. An ultrasonic sensor or a lidar unit measures distance by firing out a signal and timing how long it takes to bounce back, which is how your Roomba knows the wall is 30cm away and not 3cm. A bump sensor is about as simple as it gets, it is a switch that gets pressed when the robot physically walks into something. A gyroscope tells the robot which way is up and how fast it is tipping over, which is the only reason a two wheeled balancing robot stays upright at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Actuators&lt;/strong&gt; are how a robot moves and acts. If sensors are the senses, actuators are the muscles. Motors spin the wheels. Servos rotate to a specific angle and hold there, which is what you want in a joint or a steering mechanism. Grippers open and close so the robot can pick things up. On their own they are dumb lumps of metal, they just do what they are told.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The controller&lt;/strong&gt; is the brain, and it sits between the two. It reads what the sensors are saying and it commands the actuators to respond. For beginners this is almost always an Arduino or a Raspberry Pi. An Arduino is a microcontroller, it runs one small program over and over forever with very precise timing and no operating system to get in the way. A Raspberry Pi is a full computer running Linux, so it can handle cameras, vision and networking, but it is worse at the kind of split second timing that motors like.&lt;/p&gt;

&lt;p&gt;So put it together with a robot that stops before it hits a wall. The ultrasonic sensor reports that the wall is 20cm away. The controller checks that number against a rule you wrote, something like "if the distance is under 30cm, stop". The controller then cuts power to the motors. Sense, decide, act. That is the whole loop, and it is running dozens of times a second.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Robots "Think": Control Loops and Feedback
&lt;/h2&gt;

&lt;p&gt;Forget robots for a second and think about driving down a road. You are not holding the wheel perfectly still. You are constantly looking at where the car is in the lane, noticing it has drifted slightly right, and nudging the wheel back left. Then you check again. That constant cycle of look, compare, correct is a feedback loop, and you run one every time you drive.&lt;/p&gt;

&lt;p&gt;Robots do the same thing, and the difference between a good robot and a frustrating one usually comes down to whether it bothers to check its own work.&lt;/p&gt;

&lt;p&gt;Open loop control is acting blindly. You tell the robot to drive forward for three seconds and you hope that gets it where you wanted. It has no idea whether it actually moved. If a wheel slips on a rug or the battery is a bit low, the robot has no clue and no way to fix it. Closed loop control is when the robot checks. It drives forward, reads a sensor, sees that it has only travelled half as far as it should have, and keeps going until the sensor agrees with the goal.&lt;/p&gt;

&lt;p&gt;You will eventually run into something called PID control, and all it really is is a clever recipe for deciding how hard to correct when you are off target, so the robot does not overshoot and wobble back and forth forever. Do not worry about the maths yet, just know the name so it is not scary when you meet it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making Robots Move: Kinematics and Degrees of Freedom
&lt;/h2&gt;

&lt;p&gt;Hold your arm out in front of you. Your shoulder can swing your arm up and down, side to side, and rotate it. Your elbow bends one way. Your wrist bends, tilts and twists. Every one of those independent movements is a degree of freedom, and your arm has around seven of them. That is why you can reach around a corner to grab a mug, and why a cheap robotic arm with three joints cannot.&lt;/p&gt;

&lt;p&gt;Degrees of freedom is just a way of asking how many different ways a robot's body can move through space. Kinematics is the follow on question, if I move this joint by this much, where does the hand end up?&lt;/p&gt;

&lt;p&gt;Different bodies are good at different jobs. A wheeled robot is simple, cheap, efficient and stable, and it is completely useless the moment it meets a set of stairs. A legged robot can handle rough ground and steps, but it is far more complicated, it burns through battery, and it spends most of its processing power just not falling over. A robotic arm does not go anywhere at all, it stays bolted to one spot and does precise repeatable work in the space it can reach, which is exactly what a factory wants.&lt;/p&gt;

&lt;p&gt;If you are starting out, start with wheels.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Software Side: Programming and a Word on AI
&lt;/h2&gt;

&lt;p&gt;Programming a robot sounds intimidating until you realise what you are actually writing. You are writing the rules for the sense, decide, act loop. Read this sensor. Compare it to this number. Turn this motor on or off. That is it. The complexity comes later from stacking a lot of small rules on top of each other.&lt;/p&gt;

&lt;p&gt;There are a few sensible places to start. Block based coding, the Scratch style drag and drop stuff, is genuinely good for getting the logic into your head without fighting semicolons. Python is where most beginners land next, because it reads almost like English and both the Raspberry Pi and most hobby kits support it. ROS, the Robot Operating System, is the thing to graduate toward, it is a framework that handles the messy plumbing of getting sensors, motors and vision talking to each other on serious robots.&lt;/p&gt;

&lt;p&gt;One thing worth clearing up early. Not every robot uses AI, and the two words are not interchangeable. A line following robot is following a fixed rule you wrote by hand, if the left sensor sees dark, turn left. It will do that same thing perfectly forever and it will never get better at it. A robot using machine learning is different, it has been shown thousands of examples and has worked out its own rules from them, which is how a robot can recognise a cat sitting in the hallway when nobody ever wrote a rule describing a cat. Most hobby robots are the first kind, and that is completely fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Types of Robots You'll Encounter
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Where you'd see it&lt;/th&gt;
&lt;th&gt;Beginner friendly?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Industrial arms&lt;/td&gt;
&lt;td&gt;Repeat precise movements all day&lt;/td&gt;
&lt;td&gt;Car factories, packing lines&lt;/td&gt;
&lt;td&gt;No, but small desktop versions exist&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mobile robots&lt;/td&gt;
&lt;td&gt;Move around a space on wheels or tracks&lt;/td&gt;
&lt;td&gt;Vacuums, warehouse bots, delivery bots&lt;/td&gt;
&lt;td&gt;Yes, this is the usual starting point&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Humanoids&lt;/td&gt;
&lt;td&gt;Balance and move like a person&lt;/td&gt;
&lt;td&gt;Research labs, tech demos&lt;/td&gt;
&lt;td&gt;No, very hard and very expensive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drones&lt;/td&gt;
&lt;td&gt;Fly and carry a camera or payload&lt;/td&gt;
&lt;td&gt;Filming, surveying, agriculture&lt;/td&gt;
&lt;td&gt;Yes, and cheap kits are everywhere&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Educational kits&lt;/td&gt;
&lt;td&gt;Teach the fundamentals in a box&lt;/td&gt;
&lt;td&gt;Classrooms, your kitchen table&lt;/td&gt;
&lt;td&gt;Yes, start here&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you are buying or building your first robot, you are realistically choosing between a mobile robot, a small drone or an educational kit. Everything else is something to admire on YouTube for now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Hands-On: How a Beginner Actually Starts
&lt;/h2&gt;

&lt;p&gt;Buy a kit. I know it feels like cheating, but sourcing your own motor driver, working out why it is drawing too much current and discovering three weeks later that you bought the wrong voltage regulator is not learning robotics, it is learning procurement. A kit gives you parts that are guaranteed to work together, so when something breaks you know the problem is your code and not your shopping. An Arduino starter kit, a Raspberry Pi kit or a LEGO Mindstorms style set will all do the job.&lt;/p&gt;

&lt;p&gt;For a first project, build an obstacle avoiding car. It is two motors, one ultrasonic sensor, a controller and a chassis. The robot drives forward until the sensor says something is close, then it stops, turns and carries on. It sounds trivial, and it teaches you almost everything from this post at once. You will wire up a sensor and read real values off it. You will drive a motor. You will write the sense, decide, act loop yourself. You will discover that the sensor gives you a mad reading now and then and you need to handle it. That last one is the real lesson.&lt;/p&gt;

&lt;p&gt;For free learning, the Arduino Project Hub and the official Arduino docs are worth your time, and the Raspberry Pi Foundation publishes free beginner projects on their site. On YouTube, DroneBot Workshop is excellent for the electronics side and James Bruton is great for watching someone build ambitious things and hit problems out loud. For getting unstuck, r/robotics and the Arduino forums are active and generally patient with beginners.&lt;/p&gt;

&lt;p&gt;Now set your expectations. Your first build will not work. Something will be wired backwards, a motor will spin the wrong way, or the robot will drive confidently into a wall while the sensor swears it is clear. This is normal and it is the job. The people who get good at this are not the ones who got it right first time, they are the ones who found it funny enough to try again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Beginner Misconceptions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"Robots are basically like the movies."&lt;/strong&gt; They are not. Real robots are narrow and a bit stupid. The most advanced humanoid in the world would struggle to make you a cup of tea in an unfamiliar kitchen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"You need to be a maths genius."&lt;/strong&gt; For your first year, you need to be able to compare two numbers and know what an average is. The heavy maths shows up in advanced control and motion planning, and by the time you need it you will actually understand why.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Robots and AI are the same thing."&lt;/strong&gt; A robot is a body that senses and acts. AI is one possible way of doing the deciding part. Plenty of robots use no AI at all, and plenty of AI never touches the physical world.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"It is too expensive to start."&lt;/strong&gt; A decent Arduino starter kit with motors and sensors costs less than a night out. The expensive part of robotics is the stuff you do not need yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: Your Next Step
&lt;/h2&gt;

&lt;p&gt;Strip everything else away and this is the mental model you keep. A robot senses the world, decides what to do, then acts on it, and it does that over and over. Sensors, controller, actuators. If you can point at those three things on a machine, you understand its basic shape.&lt;/p&gt;

&lt;p&gt;So here is the next step. This week, order an Arduino starter kit and build the obstacle avoiding car. Do not read another ten articles first. Get the thing on your desk and get it driving into a wall, then fix it.&lt;/p&gt;

&lt;p&gt;If you do build it, leave a comment and tell me what went wrong first. It is always something.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>hardware</category>
      <category>learning</category>
      <category>robotics</category>
    </item>
    <item>
      <title>Popular Programming Languages and Their Use Cases: What the Numbers Are Telling Us</title>
      <dc:creator>Liam</dc:creator>
      <pubDate>Sun, 23 Aug 2026 21:02:04 +0000</pubDate>
      <link>https://dev.to/liammoss41/popular-programming-languages-and-their-use-cases-what-the-numbers-are-telling-us-en3</link>
      <guid>https://dev.to/liammoss41/popular-programming-languages-and-their-use-cases-what-the-numbers-are-telling-us-en3</guid>
      <description>&lt;p&gt;Rankings of programming languages tend to move slowly. Ecosystems have inertia, codebases outlive the developers who wrote them and the cost of switching a production stack keeps most teams where they are. So when a widely watched index like the TIOBE Programming Community Index shows some movement, it's worth taking not as noise but as a signal about where developer attention is and where hiring demand is heading towards. &lt;/p&gt;

&lt;p&gt;Here's the current top ten with each language's share of the index and its year-over-year change:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rank&lt;/th&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Share&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;23.88%&lt;/td&gt;
&lt;td&gt;+8.72%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;C++&lt;/td&gt;
&lt;td&gt;11.37%&lt;/td&gt;
&lt;td&gt;+0.84%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;10.66%&lt;/td&gt;
&lt;td&gt;+1.79%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;9.84%&lt;/td&gt;
&lt;td&gt;−1.14%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;C#&lt;/td&gt;
&lt;td&gt;4.12%&lt;/td&gt;
&lt;td&gt;−3.41%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;JavaScript&lt;/td&gt;
&lt;td&gt;3.78%&lt;/td&gt;
&lt;td&gt;+0.61%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;SQL&lt;/td&gt;
&lt;td&gt;2.87%&lt;/td&gt;
&lt;td&gt;+1.04%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;2.26%&lt;/td&gt;
&lt;td&gt;+0.53%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Delphi/Object Pascal&lt;/td&gt;
&lt;td&gt;2.18%&lt;/td&gt;
&lt;td&gt;+0.78%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Visual Basic&lt;/td&gt;
&lt;td&gt;2.04%&lt;/td&gt;
&lt;td&gt;+0.52%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Python's Generational Run
&lt;/h2&gt;

&lt;p&gt;A +8.72% year-over-year swing is enormous for a language that was already number one. Python now commands nearly a quarter of the entire index, that's more than C++ and Java combined. Of course this isn't a fluke of methodology. It reflects Python's position as the default 'glue' language of the AI and data era. If your work touches machine learning, data pipelines, scientific computing or automation then Python is the path of least resistance. The surrounding tooling such as NumPy, pandas, PyTorch, the whole scientific stack keeps this alive.&lt;/p&gt;

&lt;p&gt;For working developers, the practical implication is less "should I learn Python" and more "Python is now table stakes." Even if it isn't your primary language, it's increasingly the language your data scientists, ML engineers, and platform teams expect you to be conversant in. Its weaknesses is still there however. With runtime performance, packaging headaches, the GIL. These are well known but they've done nothing to slow adoption, largely because the performance-critical paths get pushed down into C extensions anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Systems Languages Hold Their Ground
&lt;/h2&gt;

&lt;p&gt;C++ and C together still account for more than a fifth of the index and C++ is quietly up +0.84%. That stability is the point. These languages own the domains where you cannot afford abstraction overhead such as operating systems, embedded systems and real-time systems like game engines, databases, browsers and high-frequency trading. C's slight dip (−1.14%) doesn't signal decline so much as gradual intrusion by safer alternatives. It's worth noting that a lot of the "new" systems work is happening in Rust, which isn't in this top ten but is the language most often mentioned as C and C++'s eventual successor for memory-safe systems code.&lt;/p&gt;

&lt;p&gt;Java's +1.79% is the underrated result here. A language frequently written off as legacy is growing and for good reason: it remains the backbone of enterprise backends, Android and large-scale distributed systems. The JVM ecosystem, modern language improvements and an immense hiring pool keep it firmly relevant. If you build server-side software at scale then Java and its cousin Kotlin are still where a huge share of the jobs are.&lt;/p&gt;

&lt;h2&gt;
  
  
  The C# Question
&lt;/h2&gt;

&lt;p&gt;The most jarring number in the table is C#'s −3.41%. That's a steep single-year drop for a mature, well-supported language. C# remains a first-class choice for .NET backends, Windows desktop software, Unity game development and increasingly cross-platform work. A drop in an index isn't the same as a drop in production deployments. Still, if you're a C# developer, it's a reminder that mindshare is a competitive resource and the .NET ecosystem's future is tied to how visibly it competes outside its traditional Windows-and-enterprise home.&lt;/p&gt;

&lt;h2&gt;
  
  
  JavaScript, SQL and Go: The Quiet Essentials
&lt;/h2&gt;

&lt;p&gt;JavaScript at number six almost undersells its reality. TIOBE tends to underweight JavaScript relative to its actual footprint because so much of its use is implicit in web work that never shows up in the searches the index measures. In practice, JavaScript (and TypeScript on top of it) is unavoidable for anyone building for the browser and its reach through Node.js extends well into backend and tooling territory.&lt;/p&gt;

&lt;p&gt;SQL's presence at number seven, up +1.04%, is a healthy sign that the industry still recognizes data access as a core skill rather than an afterthought. It's not glamorous though but nearly every application eventually talks to a relational database and fluency in SQL routinely separates developers who can reason about performance from those who can't.&lt;/p&gt;

&lt;p&gt;Go rounds out the credible growth story at number eight. Built at Google for exactly the kind of networked, concurrent, cloud-native services that now dominate infrastructure, Go has become a default for microservices, CLI tooling, and DevOps software. Docker and Kubernetes were both written in it. Its combination of simplicity, fast compilation, and strong concurrency primitives makes it an easy recommendation for backend and platform engineers.&lt;/p&gt;

&lt;h2&gt;
  
  
  And Then There's Visual Basic
&lt;/h2&gt;

&lt;p&gt;Now for the entry that made me do a double take. Visual Basic sits at number ten, and it's &lt;em&gt;up&lt;/em&gt; +0.52%. Delphi/Object Pascal is right above it at number nine, also climbing. In a top ten otherwise defined by AI, cloud and web, we have two languages whose cultural peak was arguably the late 1990s, growing in 2026.&lt;/p&gt;

&lt;p&gt;I'll be honest this is the part of the rankings I'd treat with the most skepticism. I don't believe teams are starting new greenfield projects in Visual Basic in any meaningful numbers and no one should read this table as a reason to learn it. What VB's persistence actually measures is the astonishing durability of enterprise software. There are millions of lines of VB and VBA quietly running finance departments, manufacturing lines and government back offices and every one of those systems needs someone to maintain it. That maintenance work is real, it pays and it's often less contested than the crowded fields around Python and JavaScript but it's a career of tending existing systems, not building the future. The lesson isn't "VB is back." It's that code, once it works and touches money, almost never dies on schedule. Legacy is a use case  and it's a lucrative one for the people willing to do the unglamorous work.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Actually Do With This
&lt;/h2&gt;

&lt;p&gt;If you're a working developer reading these numbers as a career signal rather than trivia, a few takeaways hold up. Python is a skill worth having regardless of your specialty because it's where the industry's center of gravity now sits. The systems languages and Java aren't going anywhere, and depth in them remains genuinely valuable precisely because fewer people invest in it. JavaScript and SQL are close to mandatory for full-stack and application work. Go is the smart bet if you're moving toward cloud infrastructure. And the long tail of the list with VB, Delphi, is a reminder that maintenance is a market too even if it's not the one anyone puts on a conference badge.&lt;/p&gt;

&lt;p&gt;Rankings like TIOBE are a lagging, imperfect proxy. They measure attention, not truth. But read it directionally and it tells a coherent story: the languages that win are the ones that own a clear use case and keep the friction low for the people trying to get real work done. Pick your tools accordingly.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>python</category>
      <category>java</category>
    </item>
    <item>
      <title>Automating Receipt Tracking with Python OCR</title>
      <dc:creator>Liam</dc:creator>
      <pubDate>Sun, 28 Jun 2026 07:37:02 +0000</pubDate>
      <link>https://dev.to/liammoss41/building-a-lidl-receipt-manager-39p3</link>
      <guid>https://dev.to/liammoss41/building-a-lidl-receipt-manager-39p3</guid>
      <description>&lt;p&gt;I do most of my grocery shopping at Lidl, and like a lot of people I have a vague, unreliable mental model of what things actually cost. &lt;em&gt;Was that block of cheese €2.49 last week or €3.29? Is the "special" actually special?&lt;/em&gt; The receipt has all of that information on it and it just evaporates the moment the paper hits the bin.&lt;/p&gt;

&lt;p&gt;So I built a small app to capture it. &lt;strong&gt;Lidl Receipt Manager&lt;/strong&gt; reads my Lidl (Ireland) receipts, builds a searchable price history of every product I've ever bought, and turns that history into tick-off shopping lists with a live running total. It runs on the desktop as a PyQt5 app and on my phone as a native Android &lt;code&gt;.apk&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This post walks through what it does, how it's designed, and the part that ate the most of my time. As well as what it actually takes to package a Python app into an Android build.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does:
&lt;/h2&gt;

&lt;p&gt;The feature set is deliberately small and focused on the one job: turning receipts into useful data.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Import receipts three ways.&lt;/strong&gt; Photograph the receipt and let on-device OCR read it, pick an existing image file, or just paste the receipt text in. The paste path always works, which matters more than you'd think (more on that later).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A product price database.&lt;/strong&gt; Every line item from every receipt is stored.
Those rows get rolled up per product into an average, minimum, and maximum unit price, a count of how many times I've bought it, and when I last did.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search as you type.&lt;/strong&gt; Filter the product list instantly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shopping lists.&lt;/strong&gt; Add a product and it comes pre-filled with its latest known price. Add manual items too. Tick things off as you walk the aisles and watch a live "remaining" total count down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate-safe imports.&lt;/strong&gt; Receipts are de-duplicated by their transaction ID, so accidentally importing the same receipt twice won't double-count anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fully local and offline.&lt;/strong&gt; Everything lives in a local SQLite database. Nothing is uploaded anywhere, not the photos, not the prices, nothing. Even the OCR runs on-device.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The design philosophy: a shared core, two skins
&lt;/h2&gt;

&lt;p&gt;The most important design decision in the whole project is this: &lt;strong&gt;the desktop app and the Android app share the exact same brain.&lt;/strong&gt; The parsing logic and the database schema are identical across both. Only the UI layer differs.&lt;/p&gt;

&lt;p&gt;The repository reflects that split cleanly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── lidlReceiptManager.py     # Desktop app (PyQt5) a single file
└── android/                  # Android port (KivyMD)
    ├── main.py               # Touch UI (KivyMD bottom-nav)
    ├── core/
    │   ├── parser.py         # parse_receipt() is shared with desktop
    │   ├── db.py             # SQLite layer (same schema)
    │   └── ocr.py            # ML Kit on Android, pytesseract on desktop
    └── buildozer.spec        # APK build config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The desktop app is a single self-contained file. When I ported it to Android, I pulled the three things that have nothing to do with the UI, the parser, the database, and OCR, out into a small &lt;code&gt;core/&lt;/code&gt; package, and rebuilt only the presentation layer on top of them.&lt;/p&gt;

&lt;p&gt;That gives a layered architecture where the dependency arrows all point inward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   PyQt5 UI                 KivyMD UI
 (desktop main)          (android/main.py)
        \                     /
         \                   /
          v                 v
        core: parser · db · ocr
                   |
                   v
              SQLite file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both UIs are "dumb" as they collect input, call into &lt;code&gt;core&lt;/code&gt;, and render whatever comes back. The parser is pure standard library with no UI dependencies at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  The parser
&lt;/h3&gt;

&lt;p&gt;The parser is the heart of the project, and it's just careful regular expressions over the receipt's plain text. Lidl Ireland receipts have a predictable shape: a store header, a transaction ID (&lt;code&gt;TRN-ID:&lt;/code&gt;), a date, a block of line items, and a &lt;code&gt;TOTAL&lt;/code&gt;. Each line item is a name followed by a price and a single-letter VAT&lt;br&gt;
class.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;parse_receipt()&lt;/code&gt; does two passes. The first sweeps the whole receipt for the metadata such as store, transaction ID, date. The second walks line by line building up items: a price line starts a new item, a following &lt;code&gt;2 x 1.49&lt;/code&gt; line attaches a quantity to it, and a trailing &lt;code&gt;-0.50&lt;/code&gt; applies a discount. Deposit lines and visual noise (rows of dashes, stray &lt;code&gt;EUR&lt;/code&gt;/&lt;code&gt;Copy&lt;/code&gt; tokens) are filtered out. At the end it computes a net unit price per item and returns a tidy dictionary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;store&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rdate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;trn_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;trn_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;total&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;items&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because it's pure text in, pure dict out, it's trivial to reason about and it behaves identically no matter which app calls it. The Android copy is lifted verbatim from the desktop original, that's a deliberate choice so parsing behaviour can never silently drift between the two platforms.&lt;/p&gt;

&lt;h3&gt;
  
  
  The database
&lt;/h3&gt;

&lt;p&gt;The data layer is a thin wrapper around SQLite with four tables: &lt;code&gt;receipts&lt;/code&gt;, &lt;code&gt;purchases&lt;/code&gt; (the individual line items, with a cascading foreign key back to the receipt), &lt;code&gt;lists&lt;/code&gt;, and &lt;code&gt;list_items&lt;/code&gt;. There's no ORM and no migrations machinery just &lt;code&gt;CREATE TABLE IF NOT EXISTS&lt;/code&gt; and a handful of hand-written queries.&lt;/p&gt;

&lt;p&gt;The price-history "rollup" that powers the product view isn't a stored table at all; it's a single &lt;code&gt;GROUP BY&lt;/code&gt; query that aggregates on demand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unit_price&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;avg_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unit_price&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;min_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unit_price&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;max_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rdate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;last_seen&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;purchases&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;receipts&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;receipt_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;COLLATE&lt;/span&gt; &lt;span class="n"&gt;NOCASE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Duplicate protection is enforced at the schema level as &lt;code&gt;trn_id&lt;/code&gt; is &lt;code&gt;UNIQUE&lt;/code&gt; and checked in code before insert, so re-importing a receipt returns a &lt;code&gt;'duplicate'&lt;/code&gt; status instead of doubling your data.&lt;/p&gt;

&lt;p&gt;The one real change between platforms is &lt;em&gt;where&lt;/em&gt; the database lives. On desktop it defaults to &lt;code&gt;~/.lidl_receipts/receipts.db&lt;/code&gt;, exactly as the original did. But on Android, &lt;code&gt;~&lt;/code&gt; isn't writable, so the data directory is &lt;strong&gt;injectable&lt;/strong&gt;: the Android app passes in its private &lt;code&gt;user_data_dir&lt;/code&gt; and everything else stays the same.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;folder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;folder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;folder&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;default_data_dir&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's also a small Android-specific concession, &lt;code&gt;check_same_thread=False&lt;/code&gt;, on the connection because Kivy may touch the database from clock callbacks on a different thread than it was created on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pluggable OCR
&lt;/h3&gt;

&lt;p&gt;OCR is the one piece of "core" that genuinely &lt;em&gt;must&lt;/em&gt; differ by platform, so it's designed as a pluggable backend with a two-function public API: &lt;code&gt;ocr_available()&lt;/code&gt; and &lt;code&gt;image_to_text(path)&lt;/code&gt;. The module sniffs the environment once at import time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;ON_ANDROID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ANDROID_ARGUMENT&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;getandroidapilevel&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;On the desktop&lt;/strong&gt;, it uses Tesseract via &lt;code&gt;pytesseract&lt;/code&gt;, even auto-detecting the Tesseract executable in the usual Windows install locations so you don't have to fiddle with &lt;code&gt;PATH&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On Android&lt;/strong&gt;, it reaches into &lt;strong&gt;Google ML Kit's on-device text recognizer&lt;/strong&gt; through &lt;code&gt;pyjnius&lt;/code&gt;, which lets Python call Java/Android APIs directly. The text recognition model is bundled into the APK, so it runs fully offline with no Google account and no network round-trip.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That ML Kit call is a nice little window into how Python-on-Android actually works. You grab Java classes by name and call them as if they were Python objects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;jnius&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;autoclass&lt;/span&gt;
&lt;span class="n"&gt;InputImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;autoclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;com.google.mlkit.vision.common.InputImage&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;TextRecognition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;autoclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;com.google.mlkit.vision.text.TextRecognition&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;task_await&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Tasks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;await&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# 'await' is a Python keyword!
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;task_await&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recognizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are real-world wrinkles baked in here: the gallery picker hands back a &lt;code&gt;content://&lt;/code&gt; URI while the camera returns a plain file path, so the code builds the right kind of &lt;code&gt;Uri&lt;/code&gt; for each. And &lt;code&gt;await&lt;/code&gt; is a reserved word in Python, so the Java &lt;code&gt;Tasks.await()&lt;/code&gt; method has to be reached via &lt;code&gt;getattr&lt;/code&gt;. Little things, but exactly the kind of friction you hit when bridging two runtimes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two UIs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The desktop app (PyQt5)&lt;/strong&gt; is a classic two-tab desktop layout: a &lt;em&gt;Products&lt;/em&gt; tab with an import toolbar, a search box, and a sortable table of every product with its price stats; and a &lt;em&gt;Shopping Lists&lt;/em&gt; tab with a split view of lists on the left and their items on the right. It's styled with a bit of custom Qt stylesheet to look less like a 2005 application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Android app (KivyMD)&lt;/strong&gt; rebuilds the same two screens for touch using a Material Design bottom navigation bar into &lt;em&gt;Products&lt;/em&gt; and &lt;em&gt;Lists&lt;/em&gt;. The receipt import buttons become big tappable "Photo / Image / Paste" buttons, list items become rows with a checkbox and a delete button, and the whole thing is laid out in KivyMD's declarative KV language right inside &lt;code&gt;main.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The one genuinely tricky bit of the mobile UI is threading. OCR can take a second or two, and you must never block the UI thread, so image import spins the recognition work onto a background thread and then marshals the result back to the main thread to update the screen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ocr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;image_to_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_parsed_main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parse_receipt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;# @mainthread-decorated
&lt;/span&gt;
&lt;span class="n"&gt;threading&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;daemon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;KivyMD's &lt;code&gt;@mainthread&lt;/code&gt; decorator makes that hop back onto the UI thread clean.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Android APK: the part nobody warns you about
&lt;/h2&gt;

&lt;p&gt;Here's where things get real. Writing a Kivy app is the easy 20%. Packaging it into an &lt;code&gt;.apk&lt;/code&gt; that installs and runs on a phone is the other 80%, and it's full of sharp edges. Here's the path I landed on after a fair amount of trial and error.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test on the PC first
&lt;/h3&gt;

&lt;p&gt;The single biggest time-saver was realising I &lt;strong&gt;didn't&lt;/strong&gt; need an Android build to iterate. KivyMD runs on the desktop, so I could develop the entire UI and data layer with a plain &lt;code&gt;python main.py&lt;/code&gt; and a fast feedback loop. Only camera capture is phone-only; everything else behaves identically.&lt;/p&gt;

&lt;p&gt;But getting the desktop preview running surfaced two Windows gotchas worth knowing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Python version matters.&lt;/strong&gt; Kivy 2.3.0 has no Windows wheels for Python 3.13/3.14, so you have to pin to &lt;strong&gt;Python 3.11 or 3.12&lt;/strong&gt;. (&lt;code&gt;uv python install 3.12&lt;/code&gt; makes this painless.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;KivyMD 1.1.1 is broken out of the box on modern setuptools.&lt;/strong&gt; It's distributed only as an sdist, and modern setuptools silently drops all of its non-Python data files when building it, the &lt;code&gt;.kv&lt;/code&gt; layouts, the GLSL shaders, the fonts. The result is cryptic &lt;code&gt;FileNotFoundError: ... label.kv&lt;/code&gt; at import or &lt;code&gt;... header.frag&lt;/code&gt; at first render. I wrote a little helper, &lt;code&gt;fix_kivymd_kv.py&lt;/code&gt;, that downloads the official sdist and copies every missing data file back into the installed package. One command and the preview runs.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The actual build: Buildozer on WSL2
&lt;/h3&gt;

&lt;p&gt;The packaging tool is &lt;strong&gt;Buildozer&lt;/strong&gt;, which orchestrates &lt;strong&gt;python-for-android (p4a)&lt;/strong&gt; to cross-compile CPython, your code, and all the native dependencies into an APK. The catch: &lt;strong&gt;Buildozer only runs on Linux/macOS.&lt;/strong&gt; On my Windows 10 machine, that means &lt;strong&gt;WSL2 (Ubuntu)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The one-time setup is a hefty apt install (JDK 17, the Android build toolchain, autoconf, cmake, libffi, libssl, and so on) plus a virtualenv with Buildozer and a pinned Cython. Then the build itself is one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;buildozer &lt;span class="nt"&gt;-v&lt;/span&gt; android debug
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first run is a &lt;em&gt;commitment&lt;/em&gt;: it downloads the entire Android SDK and NDK and compiles everything from scratch so about &lt;strong&gt;20 to 40 minutes&lt;/strong&gt;. Subsequent builds are minutes. One important tip: build inside the WSL filesystem (&lt;code&gt;~&lt;/code&gt;), not on &lt;code&gt;/mnt/c/...&lt;/code&gt;, because building across the Windows/Linux filesystem boundary is slow and hits path issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;buildozer.spec&lt;/code&gt; is where the real decisions live
&lt;/h3&gt;

&lt;p&gt;The build is configured by &lt;code&gt;buildozer.spec&lt;/code&gt;, and almost every line in mine exists because something broke without it. The highlights:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Requirements:&lt;/strong&gt;
&lt;code&gt;python3,kivy==2.3.0,kivymd==1.1.1,pyjnius,plyer,android&lt;/code&gt;. &lt;code&gt;pyjnius&lt;/code&gt; is what lets me call ML Kit; &lt;code&gt;plyer&lt;/code&gt; provides the camera and file picker; &lt;code&gt;android&lt;/code&gt; provides the permissions API. Notably, &lt;strong&gt;Pillow is deliberately omitted&lt;/strong&gt; as it's only used by the desktop OCR fallback, and on Android OCR is ML Kit, so PIL would just add a fragile native build for no benefit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ML Kit as a Gradle dependency.&lt;/strong&gt; This is the elegant bit:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight gradle"&gt;&lt;code&gt;  &lt;span class="n"&gt;android&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;gradle_dependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;google&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mlkit&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nl"&gt;recognition:&lt;/span&gt;&lt;span class="mf"&gt;16.0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one line pulls Google's on-device text recognition into the APK at build time and bundles the Latin model so it works offline.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pinned versions everywhere.&lt;/strong&gt; This is the hardest-won lesson. p4a is pinned to the &lt;code&gt;v2024.01.21&lt;/code&gt; release, which builds &lt;strong&gt;Python 3.11&lt;/strong&gt; because the p4a master default targets Python 3.14, and &lt;strong&gt;Kivy 2.3.0 does not compile against 3.14&lt;/strong&gt; (private CPython C-API functions it relies on, like &lt;code&gt;_PyLong_AsByteArray&lt;/code&gt;, changed or were removed). The NDK is pinned to &lt;code&gt;25b&lt;/code&gt; to match. KivyMD is pinned to 1.1.1 in both the build spec and the desktop requirements, because KivyMD's API churns between releases. &lt;strong&gt;In this corner of the ecosystem, "just use the latest version" is how you lose a weekend.&lt;/strong&gt; &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permissions and targets:&lt;/strong&gt; &lt;code&gt;CAMERA&lt;/code&gt; plus storage read/write, target API 34, minimum API 24, building for both &lt;code&gt;arm64-v8a&lt;/code&gt; and &lt;code&gt;armeabi-v7a&lt;/code&gt;, and &lt;code&gt;accept_sdk_license = True&lt;/code&gt; so the first build doesn't block on an interactive license prompt.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The APK lands in &lt;code&gt;bin/&lt;/code&gt;, gets copied back across to Windows, and installs either over USB with &lt;code&gt;adb&lt;/code&gt; or just by tapping the file on the phone. On first launch it asks for camera and storage permissions, and from then on it's a self-contained, offline little app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons learned
&lt;/h2&gt;

&lt;p&gt;A few things I'm taking away from this project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Separate the brain from the skin early.&lt;/strong&gt; Pulling the parser, DB, and OCR into a UI-agnostic core meant the entire Android port was "write a new UI" rather than "rewrite the app." The shared &lt;code&gt;core&lt;/code&gt; is the reason two apps can stay in lockstep.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A pluggable seam is worth it where platforms genuinely differ.&lt;/strong&gt; OCR was the one thing that &lt;em&gt;had&lt;/em&gt; to change per platform, and giving it a tiny two-function interface kept that difference from leaking everywhere else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Always leave an escape hatch.&lt;/strong&gt; OCR is magic when it works and useless on a crumpled receipt in bad light. The "paste the text" import path costs almost nothing and means the app is never completely stuck.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile packaging is a versioning minefield.&lt;/strong&gt; The code was the easy part. The real engineering was discovering the exact combination of Python, Kivy, KivyMD, p4a, and NDK versions that actually build together and then pinning every one of them so it stays buildable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The end result is exactly what I wanted: I photograph my receipt on the way out of the shop, and over time I've built up a private, offline, searchable history of what everything actually costs and a shopping list that knows the price of&lt;br&gt;
things before I get to the till.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>python</category>
      <category>software</category>
      <category>learning</category>
    </item>
    <item>
      <title>Making my own Sonar System</title>
      <dc:creator>Liam</dc:creator>
      <pubDate>Sun, 28 Jun 2026 07:08:30 +0000</pubDate>
      <link>https://dev.to/liammoss41/making-my-own-sonar-system-2f93</link>
      <guid>https://dev.to/liammoss41/making-my-own-sonar-system-2f93</guid>
      <description>&lt;p&gt;Initial:&lt;/p&gt;

&lt;p&gt;In my boredom I designed and made my own sonar detection system with a few parts and an Arduino. I used a ultrasonic sensor to detect objects in the way with a servo to get a measurement over 180 degrees. I connected the ultrasonic sensor to pins 9 for the trigger and 8 for the echo. The servo used its own library that would take the servo degree as an int. Click here to go to the github page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;trigPin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;echoPin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;servoPos&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To start off I initialised the ultrasonic sensor and got a reading off it, using the reading to get a distance in centimetres. To get better results, I added a noise filter by getting 20 samples and using them to get the average distance from the sensor. The more samples I got, the more consistent the measurement were. However this could cause the Arduino to take longer to do each calculation, so I kept it to 20 samples.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;getMeasurement&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;

  &lt;span class="c1"&gt;//Get 15 samples&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;sample&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;sample&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="n"&gt;filterMeasures&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ultrasonicMeasurement&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;//delay(10);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


  &lt;span class="c1"&gt;//Filter samples by ascending&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
      &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;swap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filterMeasures&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="n"&gt;filterMeasures&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filterMeasures&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="n"&gt;filterMeasures&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;swap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;//filter noise&lt;/span&gt;
  &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;sample&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;sample&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
     &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;filterMeasures&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;distance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;intDistance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;distance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;intDistance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="nf"&gt;ultrasonicMeasurement&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
  &lt;span class="c1"&gt;//20 microsecond pulses to trigPin&lt;/span&gt;
  &lt;span class="n"&gt;digitalWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trigPin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HIGH&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;delayMicroseconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;digitalWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trigPin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LOW&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;//Use echoPin to measure pulses&lt;/span&gt;
  &lt;span class="n"&gt;durationUs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pulseIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;echoPin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HIGH&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="n"&gt;distanceCm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.017&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;durationUs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;distanceCm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, I added the servo motor control, so I used a for loop to set the degree the servo was supposed to be. This made it straightforward to record the servo's position and log the distance at each angle. This would mean the servo would move to the set degree and then get measure the distance before moving again. So, with that I managed to get the servo to move smoothly from side to side. However, when the ultrasonic sensor was getting it measurements, it seemed like some were a little random or even plain wrong I had both parts were sharing the 5V rail and I fixed randomness by using another 5V power supply for the servo. This allowed both to get a good supply of power.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Loop one"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;

    &lt;span class="n"&gt;servoControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;currentMeasurement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getMeasurement&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;servoPos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;posArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;servoPos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;currentMeasurement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Pos: "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;servoPos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentMeasurement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;" cm"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;//delay(30);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Loop two"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;-=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="n"&gt;servoControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;currentMeasurement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ultrasonicMeasurement&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;servoPos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;posArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;servoPos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;currentMeasurement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Pos: "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;servoPos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentMeasurement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;" cm"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;//delay(30);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fainjznysoj896mf3i294.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fainjznysoj896mf3i294.png" alt=" " width="218" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, I went to FreeCAD and designed a case that held everything together. The Arduino would be seated under everything with the servo and ultrasonic sensor mounted on top of the case.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmj1776217chaz1090zy6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmj1776217chaz1090zy6.png" alt=" " width="792" height="690"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
