<?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>Forem: Bernard K</title>
    <description>The latest articles on Forem by Bernard K (@bernardkibathi).</description>
    <link>https://forem.com/bernardkibathi</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F940836%2F23301079-647b-4953-991f-bc09d5c6498f.jpg</url>
      <title>Forem: Bernard K</title>
      <link>https://forem.com/bernardkibathi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/bernardkibathi"/>
    <language>en</language>
    <item>
      <title>How I Built a Document Q&amp;A Bot Using LangChain, FAISS, and Docker</title>
      <dc:creator>Bernard K</dc:creator>
      <pubDate>Thu, 16 Apr 2026 14:02:47 +0000</pubDate>
      <link>https://forem.com/bernardkibathi/how-i-built-a-document-qa-bot-using-langchain-faiss-and-docker-19he</link>
      <guid>https://forem.com/bernardkibathi/how-i-built-a-document-qa-bot-using-langchain-faiss-and-docker-19he</guid>
      <description>&lt;p&gt;Setting up a Document Q&amp;amp;A Bot using LangChain, FAISS, and Docker has been quite the expedition into aligning advanced technology with practical constraints. Being based in Kenya with its infrastructure quirks and tight budgets, I needed something effective yet lightweight enough for environments with intermittent connectivity. Here's how I went about it and what I learned along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Chose LangChain, FAISS, and Docker
&lt;/h2&gt;

&lt;p&gt;Having worked with LangChain on previous AI automation projects, I knew it could process natural language effectively. The challenge was integrating it with a reliable search component,FAISS (Facebook AI Similarity Search),to manage document indexing and retrieval efficiently. Docker was the logical choice for maintaining environment consistency across different machines, especially when using budget hardware.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Core: LangChain and FAISS
&lt;/h2&gt;

&lt;p&gt;Integrating LangChain with FAISS allowed me to create a document Q&amp;amp;A bot that searches through large amounts of text to provide relevant answers. Here's a breakdown of how I implemented it:&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting Up LangChain
&lt;/h3&gt;

&lt;p&gt;Start by setting up LangChain. If you're unfamiliar with this framework, it simplifies the creation of pipeline structures for text processing. Here's a snippet to show how I initialized it:&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;langchain&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Pipeline&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize your langchain pipeline
&lt;/span&gt;&lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;steps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;# Here you would define steps like data processing, machine learning, etc.
&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This straightforward setup prepares for processing natural language queries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing FAISS for Search
&lt;/h3&gt;

&lt;p&gt;FAISS was transformative for the search functionality. It's fast and handles high-dimensional data well.&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;import&lt;/span&gt; &lt;span class="n"&gt;faiss&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;

&lt;span class="c1"&gt;# Create data to index
&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([[&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;5.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;6.0&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;float32&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Create an index
&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;faiss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IndexFlatL2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Add data to the index
&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# To search for the nearest neighbor
&lt;/span&gt;&lt;span class="n"&gt;distances&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([[&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;float32&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Output: array([[0]])
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I found FAISS particularly efficient when dealing with large datasets, which I anticipate as the data grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Packaging with Docker
&lt;/h2&gt;

&lt;p&gt;Using Docker helped manage dependencies and environment setups, crucial when deploying on systems with different specs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dockerfile for Environment Consistency
&lt;/h3&gt;

&lt;p&gt;Here's a snippet of the Dockerfile that I used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Use an official Python runtime as a parent image&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.9&lt;/span&gt;

&lt;span class="c"&gt;# Set the working directory in the container&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /usr/src/app&lt;/span&gt;

&lt;span class="c"&gt;# Copy the current directory contents into the container at /usr/src/app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="c"&gt;# Install any needed packages specified in requirements.txt&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="c"&gt;# Run langchain script when the container launches&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python", "langchain_script.py"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This setup prevented dependency issues on different machines and made deploying updates consistently easy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges of Connectivity and Budget Constraints
&lt;/h2&gt;

&lt;p&gt;In a region with spotty internet, building a system that handled offline queries was essential. I implemented local caching of common queries and some preprocessing techniques to ensure the app didn't rely on constant internet access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results: What Worked and What Didn't
&lt;/h2&gt;

&lt;p&gt;Deploying this setup reduced our search query time from around 2 seconds to under 200 milliseconds. But there were challenges. Budget hardware meant I had to limit the number of threads FAISS could use to avoid maxing out CPU resources.&lt;/p&gt;

&lt;p&gt;Offline capabilities were mixed. Local caching worked for common phrases, but less frequent queries still struggled without internet access. This balance is something I continually tweak.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;I'm exploring ways to include more efficient data preprocessing steps into the LangChain pipeline to further reduce internet dependency. Additionally, I'm optimizing FAISS to handle larger datasets on limited hardware.&lt;/p&gt;

&lt;p&gt;LangChain, FAISS, and Docker together brought significant improvements in managing large-scale document Q&amp;amp;A tasks despite constraints in emerging markets. It's a process of constant iteration and adaptation to our environment's realities.&lt;/p&gt;

</description>
      <category>langchain</category>
      <category>ai</category>
      <category>python</category>
      <category>docker</category>
    </item>
    <item>
      <title>RAG vs Fine-Tuning: What Really Solved My AI Challenges</title>
      <dc:creator>Bernard K</dc:creator>
      <pubDate>Tue, 14 Apr 2026 14:03:11 +0000</pubDate>
      <link>https://forem.com/bernardkibathi/rag-vs-fine-tuning-what-really-solved-my-ai-challenges-170g</link>
      <guid>https://forem.com/bernardkibathi/rag-vs-fine-tuning-what-really-solved-my-ai-challenges-170g</guid>
      <description>&lt;p&gt;I recently grappled with the choice between Retrieval-Augmented Generation (RAG) and fine-tuning a language model. The project was simple: integrate AI that's reliably intelligent on a budget, across over 2,500 IoT devices distributed in areas where internet connectivity is as steady as a shaky table. My mission was to enable these devices to answer user questions about local climate data,a feature that needed to be useful even when connections were unstable.&lt;/p&gt;

&lt;h2&gt;
  
  
  RAG: A smart choice
&lt;/h2&gt;

&lt;p&gt;RAG turned out to be a lifesaver in my scenario. Its appeal lay in working well with limited resources while maintaining performance. For those unfamiliar, RAG involves pulling relevant documents from a dataset and generating a response based on that retrieved information. Think of it like a librarian who pulls the right book off the shelf before you even finish asking your question.&lt;/p&gt;

&lt;p&gt;Why RAG? Maintaining a large language model locally on budget IoT hardware felt impractical. These devices don't have the processing power or memory for such a task. Streaming a lean model and outsourcing the heavy-lifting to RAG seemed smart and efficient.&lt;/p&gt;

&lt;h3&gt;
  
  
  How I implemented RAG
&lt;/h3&gt;

&lt;p&gt;I used Haystack, a Python framework that integrates well with RAG. The setup was surprisingly straightforward. Here's a simplified version of the code I used:&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;haystack.document_stores&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;InMemoryDocumentStore&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;haystack.nodes&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DensePassageRetriever&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FARMReader&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;haystack.pipelines&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ExtractiveQAPipeline&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize document store
&lt;/span&gt;&lt;span class="n"&gt;document_store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;InMemoryDocumentStore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Set up retriever and reader
&lt;/span&gt;&lt;span class="n"&gt;retriever&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DensePassageRetriever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document_store&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;document_store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FARMReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_name_or_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deepset/roberta-base-squad2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Pipeline for QA
&lt;/span&gt;&lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ExtractiveQAPipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retriever&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;run_pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;document_store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_documents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documents&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;pipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Sample output
&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;run_pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s the local climate today?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The climate is sunny with high temperatures.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}])&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;answers&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;answer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# Expected: "sunny with high temperatures"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result was consistent performance despite shaky connectivity. With RAG, not every query required a live internet connection, which drastically reduced latency issues and cut API costs. In numbers, I saw a 50% reduction in unnecessary internet fetches,a big win for us working on a tight budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fine-tuning: an ambitious endeavor
&lt;/h2&gt;

&lt;p&gt;Now, fine-tuning has its appeal. You can tailor a language model to your specific dataset. Sounds great, right? Unfortunately, it's a costly approach if each of your IoT devices has the computational power of a basic calculator.&lt;/p&gt;

&lt;p&gt;For the same task, fine-tuning a model was like sending these devices to space without oxygen. Fine-tuning is ideal when constant connectivity is guaranteed or when working with larger cloud setups.&lt;/p&gt;

&lt;h3&gt;
  
  
  My attempt with fine-tuning
&lt;/h3&gt;

&lt;p&gt;I tried using BERT, a popular choice known for its strong context understanding. With the dataset in hand, I attempted fine-tuning on a pre-trained model using Transformers:&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;transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BertTokenizer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BertForQuestionAnswering&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Trainer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TrainingArguments&lt;/span&gt;

&lt;span class="c1"&gt;# Tokenizer and model initialization
&lt;/span&gt;&lt;span class="n"&gt;tokenizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BertTokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bert-base-uncased&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BertForQuestionAnswering&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bert-base-uncased&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# A toy dataset split
&lt;/span&gt;&lt;span class="n"&gt;train_encodings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s the weather?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;truncation&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="n"&gt;padding&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="n"&gt;train_dataset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;input_ids&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;train_encodings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;input_ids&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;start_positions&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;end_positions&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]}]&lt;/span&gt;

&lt;span class="c1"&gt;# Trainer setup
&lt;/span&gt;&lt;span class="n"&gt;training_args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TrainingArguments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;per_device_train_batch_size&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;trainer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Trainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;training_args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;train_dataset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;train_dataset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Mock training
&lt;/span&gt;&lt;span class="n"&gt;trainer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;train&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this on a more robust setup was insightful, but trying to execute similar fine-tuned models on-field failed badly. Any connectivity hiccup meant retrieving data from the cloud sometimes took longer than the questions themselves. Not to mention, the cost wasn't cheap.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision
&lt;/h2&gt;

&lt;p&gt;In a perfect world with limitless resources, fine-tuning would be the dream engine of AI. But here, keeping IoT functional in unreliable network zones with budget limitations paved a path where RAG shone like a beacon.&lt;/p&gt;

&lt;p&gt;For IoT deployments in regions like Kenya, where budget constraints are as ever-present as the sunsets, RAG is a solid solution. If you're dealing with devices with the processing power of an old Nokia but want a system that performs under challenging conditions, RAG is the way to go.&lt;/p&gt;

&lt;p&gt;For now, we're working to optimize RAG's document retrieval efficiency and exploring additional cloud computing solutions for occasional heavy lifting. Tech evolves fast, and staying ahead requires constant adjustment and experimentation.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>langchain</category>
      <category>python</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Quick Guide: Connecting n8n to Any REST API in 10 Minutes</title>
      <dc:creator>Bernard K</dc:creator>
      <pubDate>Thu, 09 Apr 2026 14:02:48 +0000</pubDate>
      <link>https://forem.com/bernardkibathi/quick-guide-connecting-n8n-to-any-rest-api-in-10-minutes-9f1</link>
      <guid>https://forem.com/bernardkibathi/quick-guide-connecting-n8n-to-any-rest-api-in-10-minutes-9f1</guid>
      <description>&lt;p&gt;Connecting n8n to a REST API in under 10 minutes might sound ambitious, but it's quite achievable, even for beginners. I stumbled across n8n a few months ago when I was dealing with automating my IoT data reporting. The need for a flexible automation tool was pressing, given the constraints I face working in Kenya: unreliable internet, budget limits, and a need for simplicity. My goal was to automate data fetching from a weather API, and n8n seemed like the perfect tool because of its visual workflow builder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why use n8n?
&lt;/h2&gt;

&lt;p&gt;Before getting started, let me explain why n8n caught my attention. It's open-source, meaning no monthly fees, which is a big win when you're watching your budget. Plus, its visual interface means I don't have to hard-code API calls like I used to, saving heaps of time. For someone juggling over 2,500 IoT devices across regions with sporadic connectivity, automating workflows with minimal overhead is incredibly helpful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started with n8n
&lt;/h2&gt;

&lt;p&gt;First, you need to have n8n running. You can deploy it locally using Docker or any cloud provider. I usually run it on a DigitalOcean droplet with 2GB RAM, costing about $10 a month. This setup has handled my modest workflows without a hitch.&lt;/p&gt;

&lt;p&gt;Once n8n is up and running, you’ll land on a simple canvas ready for your workflow creation. Here's how I connected n8n to a REST API to fetch weather data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the HTTP request node
&lt;/h2&gt;

&lt;p&gt;The HTTP Request node is your gateway to external APIs. Here’s how you set it up to connect to any REST API. I'll use the OpenWeather API as an example.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add the HTTP Request Node&lt;/strong&gt;: Drag this node from the left panel to your canvas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configure the Request&lt;/strong&gt;: Double-click the node and you’ll see options for configuring your HTTP request.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Method&lt;/strong&gt;: Select &lt;code&gt;GET&lt;/code&gt; since we are fetching data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URL&lt;/strong&gt;: Input the API endpoint, like &lt;code&gt;http://api.openweathermap.org/data/2.5/weather?q=Nairobi&amp;amp;appid=YOUR_API_KEY&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Testing the API&lt;/strong&gt;: Hit the "Execute Node" button to test your request. Within my setup, this worked smoothly. A successful call displays the response in JSON format, showing temperature, humidity, and more.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Parsing the API response
&lt;/h2&gt;

&lt;p&gt;After you have the raw data, you need to make it usable. Here, the JSON Parse node is quite handy.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add the JSON Parse Node&lt;/strong&gt;: Connect this to your HTTP Request node.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Configure the Node&lt;/strong&gt;: Simply set the "Fields to Convert" to the field containing your JSON data. In our case, it's the entire API response.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Executing this parses the JSON, making it structured data ready for further processing in your workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling connectivity issues
&lt;/h2&gt;

&lt;p&gt;Working in Nairobi, internet connectivity isn't always stable. I handle this by introducing retries and error-checking in n8n.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add a Function Node&lt;/strong&gt;: Create simple JavaScript code to retry the HTTP request if it fails. Here’s a quick snippet:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;   &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;retryCount&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maxRetries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;success&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

   &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;retryCount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;maxRetries&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="c1"&gt;// Call the HTTP Request Node here&lt;/span&gt;
       &lt;span class="nx"&gt;success&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// set to true if the request succeeds&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="nx"&gt;retryCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
       &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// wait 5 seconds before retrying&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;

   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;success&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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;This logic saved me when connecting to APIs from locations with flaky connections, significantly reducing request failure rates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automation with ease
&lt;/h2&gt;

&lt;p&gt;Once you have your data, you can either store it in a Google Sheet, send it via email, or trigger other IoT devices. n8n offers nodes for just about anything. For reporting my IoT data, I often push this info to a Google Sheet. Simply drop the Google Sheets node into your workflow, link it with your Google Account, and specify the sheet and cells to update.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world application
&lt;/h2&gt;

&lt;p&gt;Remarkably, setting up n8n to automate data collection cut manual processing time from 2 hours a day to just 10 minutes of setup and occasional monitoring. In one instance, this saved me roughly $200 a month by eliminating the need for a third-party service to handle HTTP requests and parsing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;If you're navigating constraints such as budget hardware or tough internet conditions, n8n can offer a simple, effective solution for automating data flows. While it’s not without its issues (node configuration can occasionally be a bit fiddly), it's dependable enough for most small to medium IoT applications I've managed.&lt;/p&gt;

&lt;p&gt;The next steps? I'm planning to explore triggering device actions based on API responses automatically. Meanwhile, try n8n for your REST API needs. If my experience is anything to go by, you'll appreciate its straightforward approach in a complex world.&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>automation</category>
      <category>api</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Getting Started with AI Agents in n8n: A Non-Engineer's Guide</title>
      <dc:creator>Bernard K</dc:creator>
      <pubDate>Tue, 07 Apr 2026 14:02:45 +0000</pubDate>
      <link>https://forem.com/bernardkibathi/getting-started-with-ai-agents-in-n8n-a-non-engineers-guide-4a67</link>
      <guid>https://forem.com/bernardkibathi/getting-started-with-ai-agents-in-n8n-a-non-engineers-guide-4a67</guid>
      <description>&lt;p&gt;When I first started exploring AI automation several years ago, n8n wasn’t on my radar. Back then, I faced the challenge of orchestrating automation across multiple IoT devices in environments with unreliable internet connectivity. Solid reliability on a budget was essential. Fast forward to now, I've managed to streamline many tasks using n8n, an automation tool that became a core part of my toolkit for non-developers wanting to build AI agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I chose n8n
&lt;/h2&gt;

&lt;p&gt;I was initially skeptical. Could an open-source tool really handle the complexities of real-time IoT data processing, especially when robust cloud solutions came with hefty price tags? In Kenya, we often had to deal with spotty internet connections and tight hardware budgets. So, any tool that promised local deployment (such as running on budget Raspberry Pis) and straightforward APIs caught my attention.&lt;/p&gt;

&lt;p&gt;n8n’s appeal is in its flexibility. I can spin it up on a local server and get everything running without needing a powerful cloud instance. For instance, a workflow that would typically take at least a minute or two on IFTTT is cut down to mere seconds due to this local processing capability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up an automation pipeline
&lt;/h2&gt;

&lt;p&gt;Here's a quick use case: integrating weather data into our existing sensor network. Anyone who's worked with IoT knows that weather can heavily influence sensor readings. Automating adjustments based on weather forecasts means our devices work smarter, not harder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The main API integration node in n8n&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchWeatherData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Nairobi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://api.openweathermap.org/data/2.5/weather?q=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;appid=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;weatherData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Process data as needed, e.g., extract temperature&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;weatherData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Log temperature&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;weatherData&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error fetching weather data:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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="nf"&gt;fetchWeatherData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script calls a weather API and retrieves the necessary data we’ll want to use later in our automation workflow. Plugging this into n8n was straightforward. Within minutes, I'd configured it to grab weather updates every hour and use those data points to fine-tune our IoT devices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world wins and challenges
&lt;/h2&gt;

&lt;p&gt;After deploying it across 50 devices, I saw a 30% increase in efficiency due to better calibration. One unexpected benefit was the reduction in energy consumption by about 15%, thanks to automated device hibernation when certain environmental conditions were met. This technology was a huge relief both technologically and economically.&lt;/p&gt;

&lt;p&gt;However, not all experiments with n8n were easy. When the tool integrated with some legacy hardware, latency issues cropped up frequently. Given intermittent 3G connections, I often encountered data bottlenecks. Increasing retry intervals and batch processing worked wonders, but these aren't clearly documented solutions you'll find in n8n’s manuals. Bringing such workarounds to life required both persistence and a bit of creativity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison with other tools
&lt;/h2&gt;

&lt;p&gt;I had previously used tools like Node-RED and Zapier, but each had its drawbacks in my context. Node-RED was great but required a steeper learning curve for non-coders. Zapier was useful but not as customizable locally as n8n. The fluency with which n8n handled custom scripts made it blend better into tech ecosystems in emerging markets like Kenya.&lt;/p&gt;

&lt;p&gt;n8n enabled more solutions-focused discussions between me, a developer, and non-tech team members since they could visually follow the automation logic without diving into raw code. This eliminated errors between the technical and non-technical team and streamlined processes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What could be better
&lt;/h2&gt;

&lt;p&gt;I’m not saying n8n is a magic bullet. Although its UI is user-friendly, scaling workflows can become complex visually very quickly if you're not meticulous. During a deployment on more than 100 devices, the node management proved cumbersome. Also, working offline means documentation or community Q&amp;amp;As are sometimes unreachable when needed. Offline solutions or an offline-first approach to documentation would be beneficial.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s next?
&lt;/h2&gt;

&lt;p&gt;I've got a few IoT projects lined up where I'm eager to see how far I can push n8n. My next steps are building integrations with new AI models that predict environmental impacts, like LangChain, integrated directly into these pipelines. I'm also intrigued by the idea of crowdsourcing solutions to similar challenges: imagine a shared repository of automation blueprints without geographical data silos.&lt;/p&gt;

&lt;p&gt;n8n, with all its quirks, opened up a range of possibilities I wasn’t sure were realistic just several years ago. For those just starting to combine IoT and AI in budget-constrained environments, it’s definitely worth exploring. The tool has potential beyond what I initially expected, proving once again that innovation thrives on the edges of both budget and connectivity.&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>ai</category>
      <category>automation</category>
      <category>beginners</category>
    </item>
    <item>
      <title>n8n vs Make vs Zapier: What Made Me Switch and Why</title>
      <dc:creator>Bernard K</dc:creator>
      <pubDate>Thu, 02 Apr 2026 14:02:36 +0000</pubDate>
      <link>https://forem.com/bernardkibathi/n8n-vs-make-vs-zapier-what-made-me-switch-and-why-1noh</link>
      <guid>https://forem.com/bernardkibathi/n8n-vs-make-vs-zapier-what-made-me-switch-and-why-1noh</guid>
      <description>&lt;p&gt;I used to be heavily reliant on Zapier. It was the first automation tool I picked up back when managing IoT devices was chaotic. However, as the number of devices increased and budget constraints tightened, cracks started to show. My IoT devices in remote areas of Kenya needed reliable automations that wouldn't falter with poor connectivity, and Zapier's pricing became a burden on my tight budget. That's when I discovered n8n, followed later by Make (formerly Integromat).&lt;/p&gt;

&lt;h2&gt;
  
  
  The Zapier ceiling
&lt;/h2&gt;

&lt;p&gt;Initially, Zapier was helpful. It was easy to set up, had many integrations, and featured a user-friendly interface. But as my projects grew, so did the costs: $299/month was not sustainable. The setup became cumbersome with complex workflows, and I often struggled with limitations surrounding multi-step automations. Running an economical operation meant I couldn't afford Zapier every month just for convenience.&lt;/p&gt;

&lt;p&gt;A major issue was dealing with unreliable internet connections. With intermittent connectivity, waiting for automations to sync or just halt unexpectedly was frustrating. In Kenya, this is a reality many developers face. Zapier's dependence on constant connectivity made it difficult to trust when I needed to integrate sensor data economically and reliably.&lt;/p&gt;

&lt;h2&gt;
  
  
  Discovering n8n: an open-source alternative
&lt;/h2&gt;

&lt;p&gt;I found n8n while searching for open-source alternatives. It was free with the option to self-host, which sounded ideal. The transition wasn't simple, but the flexibility was refreshing after using Zapier. Setting up n8n on a local server took some effort, like configuring Docker and managing local network issues, but it paid off.&lt;/p&gt;

&lt;p&gt;Here's a snippet of a simple automation with n8n, where it listens to new MQTT messages from IoT devices and saves them in a database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// n8n simple workflow connecting MQTT and a DB&lt;/span&gt;

&lt;span class="c1"&gt;// Assume MQTT input node&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sensorData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  

&lt;span class="c1"&gt;// Example function node to process data&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;processedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sensorData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deviceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;humidity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;humidity&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;

&lt;span class="c1"&gt;// MySQL Contribution Node (store processed data)&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;executeMariaDB&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;yourpassword&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;IoTData&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;processedData&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ran this on an older VPS with just 2GB of RAM, which was perfectly capable of parsing local MQTT messages and storing data. The cost? Just the server, far cheaper than the recurring costs with Zapier.&lt;/p&gt;

&lt;p&gt;n8n's visual workflow builder often made sense once I got used to it, though it's not without quirks. Having access to the code behind automations means you can customize things beyond typical limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make: the unexpected contender
&lt;/h2&gt;

&lt;p&gt;I transitioned to Make when projects required more granularity than what n8n could comfortably handle. While n8n excels in adaptability, Make provides a structured yet broad platform.&lt;/p&gt;

&lt;p&gt;Make stands out for conditional operations, and its interface for setting up conditional workflows is superior to n8n, often saving time when debugging extensive workflows. Testing scenarios, like turning on generators based on temperature spikes from sensor data, became less of a hassle.&lt;/p&gt;

&lt;h2&gt;
  
  
  When decisions get tough
&lt;/h2&gt;

&lt;p&gt;Choosing between Zapier, n8n, and Make relies on balancing needs and constraints. If I've learned anything from scaling small projects to manage thousands of IoT devices, it's the necessity to adapt. Sticking to one tool like Zapier is convenient until outgrown.&lt;/p&gt;

&lt;p&gt;Running n8n is more technical but cost-effective, ideal for those ready to dive into Docker setups and local servers. Make, with its reasonable pricing, offers a more refined experience for scenarios with complex conditionals and data flows.&lt;/p&gt;

&lt;h2&gt;
  
  
  The IoT developer's reality
&lt;/h2&gt;

&lt;p&gt;Living and working in Kenya involves dealing with infrastructure not always aligned with major SaaS tools. Unstable internet isn't just a challenge, it's a regular hurdle. By switching from Zapier and utilizing a mix of n8n and Make, I've saved hundreds per month on automation expenses while ensuring the system is capable of handling real-world connectivity issues.&lt;/p&gt;

&lt;p&gt;If you're in a similar spot,balancing ambitious goals with limited resources,consider trying self-hosted n8n for cost control and Make for efficient conditional workflows. The tools' perfection is less important than their adaptability and ability to meet your specific challenges.&lt;/p&gt;

&lt;p&gt;Next up for me: integrating more advanced AI-driven analytics into these workflows to process sensor data. For now, though, I’m pleased that my IoT integrations are both cost-effective and adaptable enough to function under Kenya's unpredictable digital conditions. That's a success in any developer's book.&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>automation</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Getting Started with Lead Scoring in n8n Using GPT-4o-mini</title>
      <dc:creator>Bernard K</dc:creator>
      <pubDate>Thu, 02 Apr 2026 09:00:00 +0000</pubDate>
      <link>https://forem.com/bernardkibathi/getting-started-with-lead-scoring-in-n8n-using-gpt-4o-mini-j3l</link>
      <guid>https://forem.com/bernardkibathi/getting-started-with-lead-scoring-in-n8n-using-gpt-4o-mini-j3l</guid>
      <description>&lt;p&gt;Building a lead scoring pipeline wasn't initially on my agenda, but necessity demanded it. Working with IoT and AI in Kenya often means grappling with unreliable internet and outdated hardware. Wherever you're working, leads are vital. To prioritize sales leads cost-effectively, I decided to test n8n. It wasn't merely about saving money; I wanted to see if a low-code tool could handle such an important task.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why n8n and GPT-4o-mini?
&lt;/h2&gt;

&lt;p&gt;I had used n8n for other automation tasks and was intrigued by its versatility. Being open-source, it has no licensing issues or hidden fees, which is crucial on a budget. The bigger question was whether it could integrate well with GPT-4o-mini for accurate lead scoring. GPT-4o-mini attracted me with its lightweight nature compared to full GPT versions, which suited my operating conditions. A strong, stable connection isn't always available, so something needing minimal cloud interaction was essential.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the workflow
&lt;/h2&gt;

&lt;p&gt;I set up my n8n instance, integrating it with daily tools like our CRM and email. The workflow needed to automate data gathering, scoring, and pushing results back into our CRM. n8n handled the flow, while GPT-4o-mini managed the scoring.&lt;/p&gt;

&lt;p&gt;Here is the core part of the workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// This node triggers when a new lead enters the system&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onNewLead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;leadData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Format the data for GPT-4o-mini&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formattedData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;leadData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Call GPT-4o-mini for scoring&lt;/span&gt;
    &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://gpt-4o-mini.local/score&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;formattedData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="c1"&gt;// Push the score back to the CRM&lt;/span&gt;
            &lt;span class="nf"&gt;updateLeadScoreInCRM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;leadData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;score&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="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error scoring lead:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;updateLeadScoreInCRM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;leadId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Integration with CRM API&lt;/span&gt;
    &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`http://crm.local/api/leads/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;leadId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Lead &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;leadId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; scored with &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Error updating CRM:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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;This code manages communication with both GPT-4o-mini and our CRM. n8n triggers when a new lead arrives, ensuring scores get stored back into the CRM efficiently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges and learnings
&lt;/h2&gt;

&lt;p&gt;Setting this up was anything but straightforward. I encountered latency issues due to intermittent connectivity, especially in initial tests. The response time from the GPT-4o-mini server exceeded 5 seconds about 40% of the time. Adding retry logic reduced response failures by nearly 60%. Here's the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;scoreLeadWithRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;leadData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;attempt&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;attemptScore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://gpt-4o-mini.local/score&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;leadData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="nf"&gt;updateLeadScoreInCRM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;leadData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;score&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="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Retry &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: Scoring lead failed, retrying...`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="nf"&gt;attemptScore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Scoring lead failed after several attempts:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;attemptScore&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;This retry strategy helped make the process more reliable. Working within hardware restrictions also forced me to simplify data structures as much as possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  The results
&lt;/h2&gt;

&lt;p&gt;Once sorted, I tested the system live with 100 leads. Processing time dropped from 4 minutes to just about 1.5 minutes post-optimization, which was a win. The scores helped the sales team prioritize their efforts more effectively, improving conversion rates by around 10% according to their initial feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;This was my first time merging n8n with a model like GPT-4o-mini for lead scoring, and it was a rewarding experience. For environments with flaky connectivity, on-the-ground constraints, or budget issues, this setup is worth considering. However, if you're after real-time processing with zero delays, a more advanced system might be needed. Still, for my needs, it balanced functionality and economy well.&lt;/p&gt;

&lt;p&gt;Next, I'm looking into running sentiment analysis on communications with these leads to further refine scoring. It's about maximizing the advantages of the tools available while facing the realities of my operating environment.&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>ai</category>
      <category>automation</category>
      <category>python</category>
    </item>
    <item>
      <title>Why Your IoT Data Isn't Fit for ML—And How to Fix It</title>
      <dc:creator>Bernard K</dc:creator>
      <pubDate>Thu, 26 Mar 2026 14:02:37 +0000</pubDate>
      <link>https://forem.com/bernardkibathi/why-your-iot-data-isnt-fit-for-ml-and-how-to-fix-it-53c7</link>
      <guid>https://forem.com/bernardkibathi/why-your-iot-data-isnt-fit-for-ml-and-how-to-fix-it-53c7</guid>
      <description>&lt;p&gt;When you’re dealing with IoT deployments, especially in places like Kenya where connectivity issues and budget constraints are common, you quickly learn that IoT data quality can fail in unexpected ways. Before it even reaches your ML model, numerous problems can arise. I've managed over 2,500 IoT devices under these conditions, and it can be quite a journey.&lt;/p&gt;

&lt;h2&gt;
  
  
  The data collection chaos
&lt;/h2&gt;

&lt;p&gt;Initially, I assumed that gathering data from devices would be simple. The first signs of trouble appeared when we installed a new batch of sensors in a remote area with unreliable internet. Instead of a clean stream of telemetry data, I received an erratic mess. There were nonsensical data spikes, inconsistent timestamps, and sometimes data packets arrived out of order.&lt;/p&gt;

&lt;p&gt;I learned that poor connectivity can wreak havoc on data integrity. The issue isn’t just about data loss; it’s about receiving corrupted or incomplete information. Reliability isn't guaranteed. To address this, implementing a simple retry logic with a buffer on the IoT device helped stabilize 75% of our data gaps.&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;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Simulate sending data
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Data sent successfully.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Failed to send data.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;retry_attempts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retry_attempts&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;send_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sensor_reading&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Attempt &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; failed. Retrying in 5 seconds...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This straightforward approach improved our data quality significantly without incurring additional costs beyond the initial setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world spikes and noise
&lt;/h2&gt;

&lt;p&gt;Another challenge was the quality of the raw data. I soon realized that sensors are highly sensitive to real-world conditions. Dust, temperature swings, and even rodents can affect readings. In one instance, temperature sensor readings fluctuated wildly, not due to a system error, but because a gecko had settled on the sensor.&lt;/p&gt;

&lt;p&gt;Buffering raw data for a few minutes and calculating a moving average helped smooth out these spikes, reducing noise by about 60%.&lt;/p&gt;

&lt;h2&gt;
  
  
  The firmware factor
&lt;/h2&gt;

&lt;p&gt;Managing devices with various firmware versions felt like dealing with a chaotic family reunion. I discovered that inconsistent firmware led to inconsistent data formats and payloads. Outdated firmware wouldn't support certain data packet headers, leading to data drops.&lt;/p&gt;

&lt;p&gt;This taught me the importance of a unified update mechanism. By using an over-the-air (OTA) update strategy, I unified our firmware versions. This single change reduced data failure rates by 30%.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data transmission gotchas
&lt;/h2&gt;

&lt;p&gt;Handling sensor data over MQTT on budget devices is another challenge. These low-cost devices don't handle high volume well. During one month, I observed load spikes up to 1Mbps, which overwhelmed the devices and caused packet loss.&lt;/p&gt;

&lt;p&gt;To address this, batching data before transmission made a significant difference. It allowed us to manage traffic better and improved overall network reliability, cutting the transmission failure rate in half.&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;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;batch_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_list&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_list&lt;/span&gt;&lt;span class="p"&gt;)&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;return&lt;/span&gt;
    &lt;span class="n"&gt;batched_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Simulate sending batched data
&lt;/span&gt;    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Batched data sent: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;batched_data&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;data_buffer&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="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&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="c1"&gt;# Assume we collect 10 readings
&lt;/span&gt;    &lt;span class="n"&gt;data_buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sensor_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reading&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;

&lt;span class="nf"&gt;batch_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pre-ML processing struggles
&lt;/h2&gt;

&lt;p&gt;Even if everything goes as planned up to this point, pre-processing before feeding the data into an ML model presents its own problems. Cleansing data for missing or malformed entries was more complex than I anticipated. It's not just about removing anomalies, but also preserving context that might be useful for ML inferences.&lt;/p&gt;

&lt;p&gt;One experience stands out. A rule-based anomaly detection system seemed easy to set up, but my initial attempts increased data prep time to hours. This was clearly inefficient. Switching to a threshold-based, real-time processing model reduced preparation time drastically to less than 10 minutes per day, ensuring timely insights.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building resiliency
&lt;/h2&gt;

&lt;p&gt;IoT in emerging markets has a unique set of challenges, but through various lessons, I’ve come to value the small wins. While I can't make unreliable internet connections stable or turn budget devices into high-end systems, I can build around these constraints to make data as reliable as possible before it reaches those ML models.&lt;/p&gt;

&lt;p&gt;Next, I plan to explore edge computing to handle some of these issues locally. I'm sure there will be more challenges to face,I'll update you when I dive into that.&lt;/p&gt;

</description>
      <category>python</category>
      <category>iot</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Building an IoT Monitoring Pipeline: MQTT to Anomaly Detection</title>
      <dc:creator>Bernard K</dc:creator>
      <pubDate>Tue, 24 Mar 2026 14:02:48 +0000</pubDate>
      <link>https://forem.com/bernardkibathi/building-an-iot-monitoring-pipeline-mqtt-to-anomaly-detection-4cbd</link>
      <guid>https://forem.com/bernardkibathi/building-an-iot-monitoring-pipeline-mqtt-to-anomaly-detection-4cbd</guid>
      <description>&lt;p&gt;Managing an IoT fleet with over 2,500 devices in Kenya isn't always straightforward, especially when you're dealing with intermittent connectivity and budget hardware. Recently, I had to set up a pipeline to catch anomalies in our data before they caused real headaches. Here's how I stitched together MQTT, Python, and some basic anomaly detection to get it done.&lt;/p&gt;

&lt;h2&gt;
  
  
  The context
&lt;/h2&gt;

&lt;p&gt;Our devices are spread across rural areas with spotty internet, sending telemetry data every few minutes. This data includes temperature, humidity, and some custom sensor readings. Setting up a real-time monitoring system to alert us to anomalies, such as an unexpected spike in temperature, was challenging without exceeding our budget or falling apart due to connectivity issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter MQTT: The messenger
&lt;/h2&gt;

&lt;p&gt;I chose MQTT for our messaging protocol because it’s lightweight, which is perfect for devices with limited resources. We’ve set up an MQTT broker on a local server that each device publishes to. The setup is straightforward and has worked reliably for us in the field. By using MQTT, we can achieve low-latency communication, which is essential for real-time anomaly detection.&lt;/p&gt;

&lt;p&gt;Here's a quick look at the basic MQTT setup using the &lt;code&gt;paho-mqtt&lt;/code&gt; library in Python:&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;import&lt;/span&gt; &lt;span class="n"&gt;paho.mqtt.client&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;mqtt&lt;/span&gt;

&lt;span class="c1"&gt;# Define connection parameters
&lt;/span&gt;&lt;span class="n"&gt;BROKER_ADDRESS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;TOPIC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sensor/data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userdata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Connected with result code &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TOPIC&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;on_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userdata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Message received: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mqtt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_connect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;on_connect&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;on_message&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BROKER_ADDRESS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loop_start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is just a basic setup to show you how these events are handled. The real work happens in processing the incoming data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Processing data with Python
&lt;/h2&gt;

&lt;p&gt;Once the data hits our broker, we move it through a Python pipeline. The goal is to detect anomalies in sensor readings. For that, I've relied on &lt;code&gt;scikit-learn&lt;/code&gt; and &lt;code&gt;numpy&lt;/code&gt;. The great thing about &lt;code&gt;scikit-learn&lt;/code&gt; is that it's fairly light and performs decently even on constrained hardware.&lt;/p&gt;

&lt;p&gt;I used a basic z-score method for anomaly detection. It wasn't about using the fanciest model but rather ensuring it runs efficiently across multiple devices under our infrastructure constraints.&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;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sklearn.preprocessing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;StandardScaler&lt;/span&gt;

&lt;span class="c1"&gt;# Simulating incoming data
&lt;/span&gt;&lt;span class="n"&gt;sensor_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;23.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;23.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;24.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;50.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;23.7&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# Note the anomaly?
&lt;/span&gt;
&lt;span class="c1"&gt;# Standardizing data
&lt;/span&gt;&lt;span class="n"&gt;scaler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StandardScaler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;sensor_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sensor_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reshape&lt;/span&gt;&lt;span class="p"&gt;(&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;scaled_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scaler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fit_transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sensor_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Checking for anomalies with z-score
&lt;/span&gt;&lt;span class="n"&gt;z_scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scaled_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;anomalies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;z_scores&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# Assuming anything &amp;gt;2 is an anomaly
&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Anomalies detected at indices:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;anomalies&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In practical terms, this helped us flag temperature spikes over 10 degrees above the mean in near real-time. With IoT fleets, speed is critical, so minimizing the lag between data collection and anomaly detection was essential.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connectivity challenges
&lt;/h2&gt;

&lt;p&gt;One of the main challenges was dropped connections. MQTT with Quality of Service (QoS) levels helped, but not entirely. Initially, about 20% of our data was missing due to dropped connections. By incorporating retries and redundancy in the data publication, I managed to bring it down to about 5%.&lt;/p&gt;

&lt;p&gt;We also set up a local buffer on each device. When there’s a connection issue, the device holds onto its data and publishes it once a stable connection's back. Here’s a quick structure of how the buffering looked:&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;publish_sensor_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TOPIC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qos&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="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;local_buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Buffering data due to connection issue:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&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;retry_buffered_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;local_buffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TOPIC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qos&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;local_buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Retry failed, keeping data in buffer:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This local buffer was a lifesaver many times, especially in rural areas where network stability is unpredictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost management
&lt;/h2&gt;

&lt;p&gt;Budgeting is a constant concern in our setup. Every additional processing step could mean higher costs, either from energy consumption or added computational load. We've found that keeping our anomaly detection model simple helps us maintain a balance between performance and cost.&lt;/p&gt;

&lt;p&gt;Switching from cloud-based heavy analyzers to these lightweight solutions reduced our AWS bills by about $200/month. That's a significant saving when you're looking at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s next?
&lt;/h2&gt;

&lt;p&gt;I’m considering expanding our pipeline to explore more advanced anomaly detection methods without drastically increasing resource usage. One promising direction is incorporating edge computing. Processing data closer to where it's collected could cut down on latency and further improve reliability.&lt;/p&gt;

&lt;p&gt;I’m also looking at solutions like TinyML, which could integrate nicely with our existing infrastructure. They seem promising for running models directly on the devices, given how resource-hungry transmitting data can get.&lt;/p&gt;

&lt;p&gt;For anyone in a similar position, especially dealing with infrastructure constraints, remember: The simplest solution that works is usually the best. Keep iterating, refine based on field feedback, and don't shy away from getting your hands dirty with what you’ve got.&lt;/p&gt;

</description>
      <category>python</category>
      <category>iot</category>
      <category>ai</category>
      <category>mqtt</category>
    </item>
    <item>
      <title>I Built curl for Modbus</title>
      <dc:creator>Bernard K</dc:creator>
      <pubDate>Thu, 12 Mar 2026 16:54:55 +0000</pubDate>
      <link>https://forem.com/bernardkibathi/i-built-curl-for-modbus-1c42</link>
      <guid>https://forem.com/bernardkibathi/i-built-curl-for-modbus-1c42</guid>
      <description>&lt;p&gt;I spent three years managing 2,500 IoT fuel dispensing kiosks across Kenya and Rwanda. Every one of them had Modbus sensors: flow meters, level sensors, temperature probes, all talking RS485 Modbus RTU or TCP.&lt;/p&gt;

&lt;p&gt;When something went wrong at 2am (and it always did at 2am), debugging meant one of two things: fire up QModMaster on a Windows laptop, or write yet another throwaway Python script with pymodbus boilerplate.&lt;/p&gt;

&lt;p&gt;Both options are terrible when you're SSH'd into a headless Linux gateway in the field.&lt;/p&gt;

&lt;p&gt;So I built modbus-cli. It's curl for Modbus.&lt;/p&gt;

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

&lt;p&gt;One command. No config files. No GUI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;modbus-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Read 10 holding registers&lt;/span&gt;
modbus &lt;span class="nb"&gt;read &lt;/span&gt;192.168.1.10 40001 &lt;span class="nt"&gt;--count&lt;/span&gt; 10

&lt;span class="c"&gt;# Write a value&lt;/span&gt;
modbus write 192.168.1.10 40001 1234

&lt;span class="c"&gt;# Find all devices on a bus&lt;/span&gt;
modbus scan 192.168.1.10 &lt;span class="nt"&gt;--range&lt;/span&gt; 1-10

&lt;span class="c"&gt;# Live monitoring dashboard&lt;/span&gt;
modbus watch 192.168.1.10 40001 &lt;span class="nt"&gt;--count&lt;/span&gt; 8

&lt;span class="c"&gt;# Dump 200 registers to CSV&lt;/span&gt;
modbus dump 192.168.1.10 40001 40200 &lt;span class="nt"&gt;--csv&lt;/span&gt; registers.csv

&lt;span class="c"&gt;# JSON output for scripting&lt;/span&gt;
modbus &lt;span class="nb"&gt;read &lt;/span&gt;192.168.1.10 40001 &lt;span class="nt"&gt;-c&lt;/span&gt; 5 &lt;span class="nt"&gt;--json&lt;/span&gt; | jq &lt;span class="s1"&gt;'.registers[].value'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It auto-detects register types from standard Modbus addressing. Type &lt;code&gt;40001&lt;/code&gt; and it knows you want a holding register. Type &lt;code&gt;30001&lt;/code&gt; and it reads input registers. No flags needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The watch mode is where it gets interesting
&lt;/h2&gt;

&lt;p&gt;I built the monitoring dashboard with Textual, the Python TUI framework from the Rich team. It gives you a full-screen terminal app with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Live data table that updates every poll cycle&lt;/li&gt;
&lt;li&gt;Sparkline history per register (last 60 samples)&lt;/li&gt;
&lt;li&gt;Change detection showing deltas between polls&lt;/li&gt;
&lt;li&gt;Stats bar tracking poll count, change rate, and timing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keybindings: &lt;code&gt;q&lt;/code&gt; to quit, &lt;code&gt;f&lt;/code&gt; to cycle between decimal/hex/binary/signed, &lt;code&gt;p&lt;/code&gt; to pause, &lt;code&gt;r&lt;/code&gt; to reset stats.&lt;/p&gt;

&lt;p&gt;This is the feature that would have saved me hours at KOKO. When a flow meter starts drifting, you need to watch the raw register values over time and spot the pattern. Staring at a terminal running &lt;code&gt;while True: print(client.read_holding_registers(...))&lt;/code&gt; is not it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The design choices
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Standard Modbus addressing.&lt;/strong&gt; This was the #1 source of confusion for everyone on my team. Is register 0 the same as 40001? Is it 0-based or 1-based? modbus-cli handles both. If you type &lt;code&gt;40001&lt;/code&gt;, it subtracts 40001 and reads holding register 0. If you type &lt;code&gt;0 --type holding&lt;/code&gt;, it reads the same thing. No more off-by-one debugging.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TCP and serial RTU in the same tool.&lt;/strong&gt; Just add &lt;code&gt;--serial /dev/ttyUSB0&lt;/code&gt; and it switches to RTU mode. Same commands, same output. I needed this because our kiosks used TCP gateways in some sites and direct RS485 in others.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Styled terminal output.&lt;/strong&gt; Every command shows colored panels, connection status, and value bars. This isn't just cosmetic. When you're scanning through 247 slave IDs, you want to see results as they come in, not wait for a wall of text at the end. The progress bars and live discovery output make that possible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CSV and JSON export.&lt;/strong&gt; &lt;code&gt;modbus dump 192.168.1.10 40001 40500 --csv device_map.csv&lt;/code&gt; reads registers in chunks of 125 (the Modbus protocol max per request) and writes everything to a file. Add &lt;code&gt;--json&lt;/code&gt; to any read, scan, or dump command to get structured output you can pipe into &lt;code&gt;jq&lt;/code&gt; or feed into automation scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I tested it without hardware
&lt;/h2&gt;

&lt;p&gt;The repo includes a simulator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python simulator.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It starts a Modbus TCP server on port 5020 with three slave devices and 100 registers. The values drift every 500ms to simulate real sensor behavior: temperature wanders between 20-28C, pressure fluctuates around 1000 mbar, battery voltage slowly drops.&lt;/p&gt;

&lt;p&gt;Then in another terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;modbus &lt;span class="nb"&gt;read &lt;/span&gt;localhost 40001 &lt;span class="nt"&gt;-c&lt;/span&gt; 10 &lt;span class="nt"&gt;-p&lt;/span&gt; 5020
modbus watch localhost 40001 &lt;span class="nt"&gt;-c&lt;/span&gt; 8 &lt;span class="nt"&gt;-p&lt;/span&gt; 5020
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The drifting values make the watch dashboard sparklines come alive.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The project already has its first contributor and Docker support. The short list of features I'm working on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Register map files (&lt;code&gt;modbus read --map device.yaml&lt;/code&gt;) so you see &lt;code&gt;temperature&lt;/code&gt; instead of &lt;code&gt;40001&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;32-bit float decoding across register pairs (&lt;code&gt;--float&lt;/code&gt; with byte/word order options)&lt;/li&gt;
&lt;li&gt;Modbus ASCII protocol support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you work with Modbus devices and want a feature, open an issue. PRs welcome.&lt;/p&gt;

&lt;p&gt;The repo: &lt;a href="https://github.com/19bk/modbus-cli" rel="noopener noreferrer"&gt;github.com/19bk/modbus-cli&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;modbus-curl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>python</category>
      <category>iot</category>
      <category>cli</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Detecting Calibration Drift in Flow Meters with Python: A Hands-On Guide</title>
      <dc:creator>Bernard K</dc:creator>
      <pubDate>Wed, 11 Mar 2026 16:30:12 +0000</pubDate>
      <link>https://forem.com/bernardkibathi/detecting-calibration-drift-in-flow-meters-with-python-a-hands-on-guide-24lp</link>
      <guid>https://forem.com/bernardkibathi/detecting-calibration-drift-in-flow-meters-with-python-a-hands-on-guide-24lp</guid>
      <description>&lt;p&gt;I ran into the problem of detecting calibration drift in flow meters when our clients started complaining about inaccurate readings. We have over 2,500 IoT devices scattered across remote locations in Kenya, and dealing with real infrastructure constraints like intermittent connectivity and budget hardware often makes managing these devices a challenge. Detecting calibration drift in flow meters is important because inaccurate readings can result in significant operational inefficiencies and potentially large financial losses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding calibration drift
&lt;/h2&gt;

&lt;p&gt;The first step in tackling this issue was understanding what calibration drift actually looks like. Over time, flow meters can deviate from their calibrated settings due to environmental factors, wear and tear, or simply because the sensor ages. This drift usually shows up as a steady deviation from expected readings over a period of time.&lt;/p&gt;

&lt;p&gt;To put it simply, you might expect a certain volume of flow per hour, say 100 liters, but over time, the meter might start reading 95 liters or 105 liters. This drift can go unnoticed for a while, and that's where things get problematic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a baseline
&lt;/h2&gt;

&lt;p&gt;To handle drift detection, I first needed a reliable baseline to compare incoming telemetry against. For our project, I collected historical sensor data over a stable operation period and calculated the average flow rate along with standard deviation. This gave us a normal operational window to use for comparison.&lt;/p&gt;

&lt;p&gt;Here's a snippet of how I prepared the baseline using Python:&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;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="c1"&gt;# Assume data is a DataFrame containing historical flow meter data
&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;flow_meter_data.csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;baseline_window&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;flow_rate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;rolling&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;baseline_std&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;flow_rate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;rolling&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;std&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;baseline_mean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;baseline_window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;allowed_deviation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;baseline_std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;  &lt;span class="c1"&gt;# Adjust this multiplier based on tolerance
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script uses a rolling window to smooth out the noise in the historical data and establish a reliable baseline and standard deviation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detecting the drift
&lt;/h2&gt;

&lt;p&gt;Once I had the baseline, the next step was real time drift detection. Python makes it easy to process incoming telemetry by comparing it against the baseline values we pre computed.&lt;/p&gt;

&lt;p&gt;I set threshold levels to define what constitutes an "acceptable" drift. Anything outside these boundaries would trigger an alert for recalibration or further inspection:&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;detect_drift&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flow_rate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;baseline_mean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allowed_deviation&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flow_rate&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;baseline_mean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;allowed_deviation&lt;/span&gt;

&lt;span class="c1"&gt;# Example usage with incoming telemetry
&lt;/span&gt;&lt;span class="n"&gt;incoming_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;incoming_telemetry.csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;incoming_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;drift_detected&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;incoming_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;flow_rate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;detect_drift&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;baseline_mean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allowed_deviation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Trigger actions based on detected drifts
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;incoming_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;iterrows&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;drift_detected&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Drift detected at index &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, flow rate: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;flow_rate&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# Send recalibration alert here
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In tests across several sites, this method reliably identified instances of calibration drift, allowing intervention before significant discrepancies affected operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real world challenges
&lt;/h2&gt;

&lt;p&gt;A significant issue was dealing with data transmission over unreliable networks. Many of our devices operate in areas with flaky connectivity, making real time monitoring difficult. To address this, I added a caching mechanism on the devices, where data is stored locally and synced when a connection is available. This ensured that even with connection loss, our systems didn't miss critical data.&lt;/p&gt;

&lt;p&gt;Another challenge was setting the right threshold for detecting drift. If set too low, we would be flooded with false positives, overwhelming the systems and the technical team. Set too high, we risk missing critical drifts. It took several iterations and real world testing to get this balance right. We ended up with thresholds that scale based on historical variance, providing adaptability to different operational environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alerting logic
&lt;/h2&gt;

&lt;p&gt;It's pointless to have a detection system without a reliable alerting mechanism. I integrated an SMS alert system using Twilio for immediate notification as connectivity isn't always reliable enough for constant online monitoring. This allowed us to promptly address issues before they spiraled.&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;twilio.rest&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TWILIO_ACCOUNT_SID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TWILIO_AUTH_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_PHONE_NUMBER&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;from_&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TWILIO_PHONE_NUMBER&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Use this function when drift is detected
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;drift_detected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;send_alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Flow meter drift detected: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;flow_rate&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; at &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="si"&gt;}&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;p&gt;While SMS might be considered old school, it's practical for locations with basic mobile coverage, ensuring alerts are received promptly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Detecting calibration drift in flow meters isn't just about the tech. It's about understanding real world operational constraints and finding solutions that cater to those realities. We learned some valuable lessons: high tech solutions aren't always feasible in low connectivity environments, and adaptability is essential.&lt;/p&gt;

&lt;p&gt;Next, I'm looking to further refine our alerting system to include predictive analytics for maintenance scheduling. This will allow us to proactively deal with potential issues before they evolve into significant operational problems. Working within the constraints we have here in Kenya inspires innovative solutions that can often outperform more traditional approaches in developed markets.&lt;/p&gt;

</description>
      <category>python</category>
      <category>iot</category>
      <category>datascience</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>How to Build a Data Quality Framework for IoT Telemetry</title>
      <dc:creator>Bernard K</dc:creator>
      <pubDate>Thu, 05 Mar 2026 18:16:30 +0000</pubDate>
      <link>https://forem.com/bernardkibathi/how-to-build-a-data-quality-framework-for-iot-telemetry-3ogp</link>
      <guid>https://forem.com/bernardkibathi/how-to-build-a-data-quality-framework-for-iot-telemetry-3ogp</guid>
      <description>&lt;p&gt;Handling IoT device data can get messy fast. With over 2,500 live devices under my belt, building a data quality validation framework became essential. This framework ensures your data is accurate and reliable before you move further. I'll walk you through an 11-step pipeline I built.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You'll need Python 3.10+ installed and some API keys, depending on your data source (e.g., AWS or Azure). Also, familiarity with pandas and n8n will help.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation/Setup
&lt;/h2&gt;

&lt;p&gt;Begin by installing the necessary Python packages. Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;&lt;span class="nv"&gt;pandas&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;1.3.5 &lt;span class="nv"&gt;numpy&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;1.21.4 &lt;span class="nv"&gt;n8n&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;0.147.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Common error? If you hit a "module not found" issue, ensure virtual environments aren't messing things up. A simple &lt;code&gt;pip list&lt;/code&gt; can help you spot missing packages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Framework
&lt;/h2&gt;

&lt;p&gt;This 11-element pipeline starts with fetching raw data and ends with storing results. I'll highlight the critical parts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Fetching the Data
&lt;/h3&gt;

&lt;p&gt;Set up a node in n8n to grab data from your IoT devices. I use HTTP nodes, but MQTT works too. Here's a simple strategy:&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;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_device_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://iot.api/devices/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;device_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Failed to fetch device data&lt;/span&gt;&lt;span class="sh"&gt;"&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Usage
&lt;/span&gt;&lt;span class="n"&gt;device_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_device_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;device123&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;h3&gt;
  
  
  Step 2-3: Initial Validation and Data Parsing
&lt;/h3&gt;

&lt;p&gt;You'll want to check if each entry matches expected formats. Pandas shines here:&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;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;data_frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error parsing data: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4-5: Type Checks and Range Validations
&lt;/h3&gt;

&lt;p&gt;Use pandas for type validation. Set your expectations or default values:&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;data_frame&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;temperature&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data_frame&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;temperature&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data_frame&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;humidity&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data_frame&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;humidity&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6: Duplicate Removal
&lt;/h3&gt;

&lt;p&gt;Simple deduplication to keep your data clean:&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;data_frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drop_duplicates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;keep&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;last&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inplace&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 7-8: Missing Value Checks
&lt;/h3&gt;

&lt;p&gt;Identify missing data. You'll decide whether to fill or flag:&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;missing_values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data_frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isnull&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Missing values per column: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;missing_values&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;data_frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillna&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ffill&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inplace&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="c1"&gt;# Forward fill for continuity
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 9: Outlier Detection
&lt;/h3&gt;

&lt;p&gt;Here's a basic example using z-scores:&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;scipy.stats&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;zscore&lt;/span&gt;

&lt;span class="n"&gt;data_frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data_frame&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;zscore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_frame&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;temperature&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 10-11: Aggregation and Result Storage
&lt;/h3&gt;

&lt;p&gt;Once validated, aggregate data and store results.&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;aggregated_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data_frame&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;device_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Save to CSV or SQL
&lt;/span&gt;&lt;span class="n"&gt;aggregated_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;validated_device_data.csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Alternatively, send to a database or cloud storage
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tips
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API Limitations&lt;/strong&gt;: Some device APIs have strict rate limits. Batch requests if needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debugging&lt;/strong&gt;: Always log API responses. This saved me countless hours when debugging.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Volume&lt;/strong&gt;: For datasets over 1GB, consider chunk processing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Integrate real-time alerts when data quality issues arise.&lt;/li&gt;
&lt;li&gt;Scale up to support more devices with multiprocessing.&lt;/li&gt;
&lt;li&gt;Explore ML models for anomaly detection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Building this framework drastically reduced our data handling headaches. It's straightforward once you grasp the flow. Happy coding!&lt;/p&gt;

</description>
      <category>python</category>
      <category>iot</category>
      <category>dataquality</category>
      <category>ai</category>
    </item>
    <item>
      <title>How to Enable NVFP4 Support in Llama.cpp GGUF Format</title>
      <dc:creator>Bernard K</dc:creator>
      <pubDate>Thu, 05 Mar 2026 18:03:55 +0000</pubDate>
      <link>https://forem.com/bernardkibathi/how-to-enable-nvfp4-support-in-llamacpp-gguf-format-53p9</link>
      <guid>https://forem.com/bernardkibathi/how-to-enable-nvfp4-support-in-llamacpp-gguf-format-53p9</guid>
      <description>&lt;p&gt;We're on the brink of getting true NVFP4 support in Llama.cpp's GGUF format. This is exciting because NVFP4 is expected to improve performance and efficiency, especially on NVIDIA GPUs. I'll walk you through setting this up, so you're ready to roll when it drops.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.10+&lt;/li&gt;
&lt;li&gt;Git installed on your machine&lt;/li&gt;
&lt;li&gt;NVIDIA drivers updated&lt;/li&gt;
&lt;li&gt;Familiarity with command-line basics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Make sure your environment is sorted. Believe me, keeping Python updated saved me a headache or two. &lt;/p&gt;

&lt;h2&gt;
  
  
  Installation/Setup
&lt;/h2&gt;

&lt;p&gt;You'll want the latest Llama.cpp version from their repo. Clone the repo and navigate to the directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/user/llama.cpp.git
&lt;span class="nb"&gt;cd &lt;/span&gt;llama.cpp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you encounter "fatal: repository not found," double-check your repo URL. It’s a common one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Environment
&lt;/h2&gt;

&lt;p&gt;We'll be preparing to use GGUF format with NVFP4. When I did this, I found using &lt;code&gt;virtualenv&lt;/code&gt; keeps things clean:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv myenv
&lt;span class="nb"&gt;source &lt;/span&gt;myenv/bin/activate
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I used &lt;code&gt;virtualenv&lt;/code&gt; because it isolates dependencies. Works wonders when you have multiple projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring GGUF Format
&lt;/h3&gt;

&lt;p&gt;The magic happens in &lt;code&gt;src/config.json&lt;/code&gt;. Ensure your file looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GGUF"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nvfp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I first tried this, I missed the &lt;code&gt;nvfp&lt;/code&gt; setting. Don’t skip that!&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Examples
&lt;/h2&gt;

&lt;p&gt;Here's an example script to start processing with Llama.cpp:&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;import&lt;/span&gt; &lt;span class="n"&gt;llama_cpp&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize
&lt;/span&gt;&lt;span class="n"&gt;model_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;models/llama.gguf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;llama_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;llama_cpp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error loading model: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&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="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&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;process_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;llama_model&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;input_data&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;result&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Processing error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="n"&gt;input_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What is the weather today?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;process_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Output: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="si"&gt;}&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;p&gt;Here, the &lt;code&gt;Exception&lt;/code&gt; handling is crucial. Once it threw a "Model not found" error. It kept happening because I mistyped my model path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tips
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Virtual Envs&lt;/strong&gt;: Use them. With Python projects, isolation is your friend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Debugging&lt;/strong&gt;: Use print statements liberally when debugging. Outputs are gold.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batch Processing&lt;/strong&gt;: If the dataset is big, chunk it up. &lt;code&gt;batch_size = 32&lt;/code&gt; usually works for me.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;Once NVFP4 support is official, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Benchmark with various datasets to see performance gains.&lt;/li&gt;
&lt;li&gt;Tweak model parameters for specific use cases.&lt;/li&gt;
&lt;li&gt;Dive into the source code to understand the under-the-hood improvements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the lowdown. Get prepped and let me know how it goes! I’m excited to see how this impact unfolds for us devs using Llama.cpp.&lt;/p&gt;

</description>
      <category>python</category>
      <category>nvidia</category>
      <category>machinelearning</category>
      <category>gpus</category>
    </item>
  </channel>
</rss>
