<?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>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>
