<?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: Blues</title>
    <description>The latest articles on Forem by Blues (@blues).</description>
    <link>https://forem.com/blues</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%2Forganization%2Fprofile_image%2F3549%2F8ad67773-51de-481f-b112-0e8e80973de5.jpg</url>
      <title>Forem: Blues</title>
      <link>https://forem.com/blues</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/blues"/>
    <language>en</language>
    <item>
      <title>Sending and Receiving Binary Files with the Notecard and Notehub</title>
      <dc:creator>Rob Lauer</dc:creator>
      <pubDate>Tue, 28 Nov 2023 16:13:33 +0000</pubDate>
      <link>https://forem.com/blues/sending-and-receiving-binary-files-with-the-notecard-and-notehub-1ep2</link>
      <guid>https://forem.com/blues/sending-and-receiving-binary-files-with-the-notecard-and-notehub-1ep2</guid>
      <description>&lt;p&gt;The Notecard was originally designed as a &lt;a href="https://dev.blues.io/notecard/notecard-walkthrough/low-bandwidth-design/#low-bandwidth-design" rel="noopener noreferrer"&gt;low-bandwidth&lt;/a&gt; and &lt;a href="https://dev.blues.io/notecard/notecard-walkthrough/low-power-design/#low-power-design" rel="noopener noreferrer"&gt;low-power&lt;/a&gt; device that effortlessly syncs user-defined JSON payloads (e.g. key/value pairs of strings, booleans, and integers) with any cloud endpoint. However, one of the most-repeated requests we heard from customers is the ability to &lt;em&gt;also&lt;/em&gt; sync larger payloads of binary data (most often in the form of images).&lt;/p&gt;

&lt;p&gt;With the release of the &lt;a href="https://dev.blues.io/notecard/notecard-firmware-updates/#v5-3-1-september-18th-2023" rel="noopener noreferrer"&gt;Notecard developer firmware release v5.3.1&lt;/a&gt;, this is now possible using the new &lt;a href="https://dev.blues.io/api-reference/notecard-api/card-requests/#card-binary" rel="noopener noreferrer"&gt;card.binary APIs&lt;/a&gt;. Even better, our firmware libraries for &lt;a href="https://dev.blues.io/tools-and-sdks/firmware-libraries/arduino-library/" rel="noopener noreferrer"&gt;Arduino/C&lt;/a&gt; and &lt;a href="https://dev.blues.io/tools-and-sdks/firmware-libraries/python-library/" rel="noopener noreferrer"&gt;Python&lt;/a&gt; abstract away the complexity of the &lt;code&gt;card.binary&lt;/code&gt; requests for you.&lt;/p&gt;

&lt;p&gt;In this article, I'm going to show an example in Arduino/C of how to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Request a JPEG image from a cloud endpoint.&lt;/li&gt;
&lt;li&gt;Write that JPEG image to a Micro SD card.&lt;/li&gt;
&lt;li&gt;Send that same JPEG image back to a different cloud endpoint.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Hardware Setup
&lt;/h2&gt;

&lt;p&gt;For this example, I'm using the following hardware:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://shop.blues.io/collections/swan/products/swan" rel="noopener noreferrer"&gt;Blues Swan&lt;/a&gt; (STM32L4-based host MCU)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shop.blues.io/collections/swan/products/stlink-v3mini" rel="noopener noreferrer"&gt;STLINK Programmer/Debugger&lt;/a&gt; (optional, but makes working with STM32-based MCUs more pleasant)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shop.blues.io/products/notecard" rel="noopener noreferrer"&gt;Blues Notecard Cellular&lt;/a&gt; (any Notecard will do!)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shop.blues.io/products/carr-al" rel="noopener noreferrer"&gt;Blues Notecarrier A&lt;/a&gt; (a development board for interfacing with the Notecard)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.adafruit.com/product/254" rel="noopener noreferrer"&gt;Adafruit's Micro SD breakout board&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://shop.blues.io/collections/accessories/products/5-000-mah-lipo-battery" rel="noopener noreferrer"&gt;2 x LiPo batteries&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Wiring the Micro SD Breakout to the Swan
&lt;/h3&gt;

&lt;p&gt;First, you'll want to wire up the Swan to the Micro SD breakout. The following table outlines the wiring needed to connect the two:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;SD Breakout&lt;/th&gt;
&lt;th&gt;Blues Swan&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GND&lt;/td&gt;
&lt;td&gt;GND&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3v&lt;/td&gt;
&lt;td&gt;3V3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLK&lt;/td&gt;
&lt;td&gt;CK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DO&lt;/td&gt;
&lt;td&gt;MI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DI&lt;/td&gt;
&lt;td&gt;MO&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CS&lt;/td&gt;
&lt;td&gt;A3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Attaching the Notecarrier to the Swan
&lt;/h3&gt;

&lt;p&gt;Next, attach the Notecarrier A to the Swan via a Qwiic cable. Note that the Swan and Notecarrier A &lt;em&gt;also&lt;/em&gt; need to be powered separately either over Micro USB or an attached LiPo battery, as Qwiic doesn't deliver enough power to boot the Notecard.&lt;/p&gt;

&lt;p&gt;Your hardware should look something like the following (with or without the STLINK debugger of course):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5954s20rudz3avygvv4b.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5954s20rudz3avygvv4b.jpg" alt="wiring sd breakout to swan and notecarrier" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Format the Micro SD Card
&lt;/h2&gt;

&lt;p&gt;If you haven't already, be sure the Micro SD card you're using is already formatted as either FAT16 (if &amp;lt; 2GB) or FAT32. If not, you can use a USB SD card reader with your PC to format it using the SD Association's Memory Card Formatter (available on &lt;a href="https://www.sdcard.org/downloads/formatter/" rel="noopener noreferrer"&gt;macOS/Win&lt;/a&gt; and &lt;a href="https://www.sdcard.org/downloads/sd-memory-card-formatter-for-linux/" rel="noopener noreferrer"&gt;Linux&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Now it's time to write the sketch!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Looking for a shortcut? &lt;a href="https://gist.github.com/rdlauer/4f50bbb44a1b10100a0bc62b7cc820b2" rel="noopener noreferrer"&gt;Check out this gist&lt;/a&gt; to see the completed sketch.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Setup the Notecard and SD Card Breakout
&lt;/h2&gt;

&lt;p&gt;Include the necessary libraries in your project, including &lt;a href="https://dev.blues.io/tools-and-sdks/firmware-libraries/arduino-library/" rel="noopener noreferrer"&gt;note-arduino&lt;/a&gt; for the Notecard and the &lt;a href="https://github.com/arduino-libraries/SD" rel="noopener noreferrer"&gt;Arduino SD Library&lt;/a&gt; for the Micro SD breakout board. Also, add the variables needed to interface with the components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;Arduino.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;Wire.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;Notecard.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;SD.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="n"&gt;Notecard&lt;/span&gt; &lt;span class="n"&gt;notecard&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="n"&gt;myImageFile&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;Sd2Card&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;SdVolume&lt;/span&gt; &lt;span class="n"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;SdFile&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;chipSelect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;A3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;HardwareSerial&lt;/span&gt; &lt;span class="nf"&gt;stlinkSerial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PIN_VCP_RX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PIN_VCP_TX&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;#define usbSerial Serial
&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;ledPin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LED_BUILTIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// references onboard LED on Swan&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Initialize the Notecard and Micro SD Breakout
&lt;/h2&gt;

&lt;p&gt;Most everything else occurs in the &lt;code&gt;setup()&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;Set the &lt;code&gt;pinMode&lt;/code&gt; of the Swan's onboard LED to &lt;code&gt;OUTPUT&lt;/code&gt; so we can blink it later. Also, if you're using the STLINK, be sure to initialize it so you can see output in the serial monitor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;pinMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ledPin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OUTPUT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;Wire&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// if using STLINK debugger&lt;/span&gt;
&lt;span class="n"&gt;stlinkSerial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;115200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;usb_timeout_ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;start_ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;millis&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;stlinkSerial&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;millis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start_ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;usb_timeout_ms&lt;/span&gt;&lt;span class="p"&gt;;)&lt;/span&gt;
  &lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;notecard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setDebugOutputStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stlinkSerial&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, initialize the Notecard, &lt;strong&gt;link your Notecard to your project in Notehub&lt;/strong&gt; via the &lt;code&gt;product&lt;/code&gt; key, and reset the Notecard's binary storage area to make sure we have the space required to save an image.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you haven't &lt;a href="https://notehub.io/" rel="noopener noreferrer"&gt;set up a Notehub account yet&lt;/a&gt;, you should, because it's free to use (up to 5,000 events routed per month!) and only takes an email address.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;notecard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;J&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;notecard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hub.set"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;JAddStringToObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"product"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"com.your.project:uid"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;JAddStringToObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"continuous"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;JAddBoolToObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"sync"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;notecard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sendRequestWithRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&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;span class="c1"&gt;// wait until we are connected to notehub&lt;/span&gt;
&lt;span class="c1"&gt;// this is REQUIRED when sending binary data!&lt;/span&gt;
&lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;connected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;connected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;notecard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hub.status"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;J&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rsp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;notecard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requestAndResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;connected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JGetBool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"connected"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Reset the Notecard's binary storage area, so we can start fresh&lt;/span&gt;
&lt;span class="n"&gt;NoteBinaryStoreReset&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, initialize the connected Micro SD card:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// init the SD card for usage&lt;/span&gt;
&lt;span class="n"&gt;stlinkSerial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Initializing SD card..."&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SPI_HALF_SPEED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chipSelect&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;stlinkSerial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SD card initialization failed!"&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// try to open the 'volume'/'partition' - it should be FAT16 or FAT32&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;stlinkSerial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Could not find FAT16/FAT32 partition.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Make sure you've formatted the card"&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="mi"&gt;1&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;That's it for our initialization tasks. Next up, it's time to request an image file!&lt;/p&gt;

&lt;h2&gt;
  
  
  Request an Image from the Cloud
&lt;/h2&gt;

&lt;p&gt;For security reasons, the Notecard cannot connect directly to any arbitrary cloud endpoint. It doesn't have a public IP address, nor would you want it to have one. This is where Notehub comes into play, as the Notecard and Notehub work together to form a secure "off the public Internet" link.&lt;/p&gt;

&lt;p&gt;To request a binary file from a cloud end point, we'll have to set up a &lt;a href="https://dev.blues.io/notehub/notehub-walkthrough/#routing-data-with-notehub" rel="noopener noreferrer"&gt;Notehub Route&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Routes allow you to send data from Notehub to a public cloud like AWS, Azure, or Google Cloud, a messaging platform like MQTT, or a custom HTTP/HTTPS endpoint. Routes are defined in a Notehub for a single project, and can target any number of Notecard fleets and devices.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's say you have a cloud endpoint that will return binary data (i.e. an image file). Make sure you have the URL of the endpoint available.&lt;/p&gt;

&lt;p&gt;In Notehub, navigate to the &lt;strong&gt;Routes&lt;/strong&gt; menu option, and create a new &lt;strong&gt;Proxy for Notecard Web Requests&lt;/strong&gt; route:&lt;/p&gt;

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

&lt;p&gt;Supply an arbitrary &lt;strong&gt;Route Name&lt;/strong&gt; and the &lt;strong&gt;URL&lt;/strong&gt; that Notehub should use to fetch the image with a &lt;code&gt;GET&lt;/code&gt;. Also, you'll need to add an arbitrary &lt;strong&gt;Alias&lt;/strong&gt; string, which is how you'll reference this Route in the sketch below.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;That's it!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Back in your sketch, you'll use the &lt;a href="https://dev.blues.io/api-reference/notecard-api/web-requests/#web-get" rel="noopener noreferrer"&gt;web.get API&lt;/a&gt; to call the newly-created Notehub Route (called &lt;code&gt;GetImage&lt;/code&gt;) and store whatever binary data is returned in flash on the Notecard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;J&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NoteNewRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"web.get"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;JAddStringToObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"route"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"GetImage"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;JAddBoolToObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"binary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;true&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;NoteRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;notecard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logDebug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error receiving image&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;data_len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;NoteBinaryStoreDecodedLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;data_len&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;rx_buffer_len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NoteBinaryCodecMaxEncodedLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_len&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rx_buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;malloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rx_buffer_len&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Receive the data&lt;/span&gt;
&lt;span class="n"&gt;NoteBinaryStoreReceive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reinterpret_cast&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rx_buffer&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;rx_buffer_len&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="n"&gt;data_len&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;notecard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logDebugf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;[INFO] Received %d bytes.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data_len&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Save the Image to the Micro SD Card
&lt;/h2&gt;

&lt;p&gt;Next, you'll want to write the saved binary buffer from the Notecard to the Micro SD card, using methods provided by the Arduino library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// save the buffer to the specified file name on the SD card&lt;/span&gt;
&lt;span class="n"&gt;SD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chipSelect&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;SD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"logo.jpg"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// remove the file if it already exists&lt;/span&gt;
&lt;span class="n"&gt;myImageFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"logo.jpg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FILE_WRITE&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="n"&gt;myImageFile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;myImageFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rx_buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data_len&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;myImageFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="n"&gt;stlinkSerial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Completed writing the file!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&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;You can now pull the SD card out of the breakout board and pop it into your PC to see if the file was saved correctly (in my case I downloaded the Blues logo of course).&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;Yes you'll just have to take my word that it worked!&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Send the Image Back to a Cloud Endpoint
&lt;/h2&gt;

&lt;p&gt;So far you've downloaded a file and saved it to SD, but what about uploading it back to another cloud endpoint?&lt;/p&gt;

&lt;p&gt;Welcome to the magic of Notehub Routes (yes, they work both ways)!&lt;/p&gt;

&lt;p&gt;Back in Notehub, again navigate to the &lt;strong&gt;Routes&lt;/strong&gt; menu option, and create a new &lt;strong&gt;General HTTP/HTTPS Request/Response&lt;/strong&gt; route (this is different than the &lt;strong&gt;Proxy for Notecard Web Requests&lt;/strong&gt; route type, as this time we will be generating a payload locally using &lt;a href="https://dev.blues.io/api-reference/glossary/#note" rel="noopener noreferrer"&gt;Notes&lt;/a&gt; to send data from the Notecard to Notehub).&lt;/p&gt;

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

&lt;p&gt;Provide an arbitrary Route Name and the remote URL that Notehub will use to &lt;code&gt;POST&lt;/code&gt; the binary data. In my case, I used the venerable webhook.site for an easy way to inspect outbound data to the cloud.&lt;/p&gt;

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

&lt;p&gt;Continue to the &lt;strong&gt;Filters&lt;/strong&gt; section and be sure that you are only routing data from the Notecard that appears in the &lt;code&gt;binary.qo&lt;/code&gt; Notefile (this is the name of the Notefile that will contain the binary payload).&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;All set?&lt;/strong&gt; Back to your sketch!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;notecard_binary_area_offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;NoteBinaryStoreTransmit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reinterpret_cast&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rx_buffer&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;data_len&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rx_buffer&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;notecard_binary_area_offset&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;notecard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logDebugf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;[INFO] Transmitted %d bytes.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data_len&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Send the binary data to Notehub&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;J&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;notecard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"note.add"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;JAddStringToObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"file"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"binary.qo"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;JAddBoolToObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"binary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;JAddBoolToObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"live"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;notecard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sendRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Free the receive buffer&lt;/span&gt;
&lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rx_buffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;stlinkSerial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Completed sending the file!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Believe it or not, that's all that is required. In this code block, you are using the saved binary buffer that's already stored in flash on the Notecard (the image file just downloaded previously) and sending it to Notehub using the &lt;a href="https://dev.blues.io/api-reference/notecard-api/note-requests/#note-add" rel="noopener noreferrer"&gt;note.add API&lt;/a&gt;. Note that you're also supplying the &lt;code&gt;binary:true&lt;/code&gt; and &lt;code&gt;live:true&lt;/code&gt; arguments of &lt;code&gt;note.add&lt;/code&gt; in order to pass the binary data from the Notecard to Notehub!&lt;/p&gt;

&lt;h2&gt;
  
  
  Viewing the Binary Payload in the Cloud
&lt;/h2&gt;

&lt;p&gt;Depending on how you set up the Notehub Route that &lt;code&gt;POST&lt;/code&gt;s data, you should be able to see the binary data delivered. As mentioned earlier, I used webhook.site which lets me inspect delivered payloads. Here is a snippet of the binary file I delivered:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Wrapping Up with a Blink and a Loop
&lt;/h2&gt;

&lt;p&gt;Since I like to get some visual indication that my sketch has run to completion, in the &lt;code&gt;loop()&lt;/code&gt; method, I simply flash the Swan's onboard LED on and off:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Finally, blink the Swan's LED when done!&lt;/span&gt;
  &lt;span class="n"&gt;digitalWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ledPin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HIGH&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;digitalWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ledPin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LOW&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&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;blockquote&gt;
&lt;p&gt;For a quick view of the full Arduino sketch, &lt;a href="https://gist.github.com/rdlauer/4f50bbb44a1b10100a0bc62b7cc820b2" rel="noopener noreferrer"&gt;consult this gist&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;What I've shown here is a relatively simple way of downloading and uploading binary data with the Notecard. More likely you may be dealing with larger binary files that have to be processed in "chunks" that the Notecard can handle. Be sure to consult our &lt;a href="https://github.com/blues/note-arduino/tree/master/examples/Example9_BinarySendReceiveChunked" rel="noopener noreferrer"&gt;additional example Arduino sketch&lt;/a&gt; that shows how to accomplish this more advanced scenario.&lt;/p&gt;

&lt;p&gt;If you haven't already, &lt;a href="https://shop.blues.io/collections/blues-starter-kits" rel="noopener noreferrer"&gt;grab your own Blues Starter Kit&lt;/a&gt; and see how easy it really can be to sync data from a physical device to the cloud (over Cellular, Wi-Fi, or LoRa!).&lt;/p&gt;

&lt;p&gt;Happy Hacking! 💙&lt;/p&gt;

</description>
      <category>iot</category>
      <category>embedded</category>
      <category>arduino</category>
    </item>
    <item>
      <title>The Easiest Way to Upgrade Raspberry Pi OS from Bullseye to Bookworm</title>
      <dc:creator>Rob Lauer</dc:creator>
      <pubDate>Tue, 07 Nov 2023 17:26:45 +0000</pubDate>
      <link>https://forem.com/blues/the-easiest-way-to-upgrade-raspberry-pi-os-from-bullseye-to-bookworm-c3m</link>
      <guid>https://forem.com/blues/the-easiest-way-to-upgrade-raspberry-pi-os-from-bullseye-to-bookworm-c3m</guid>
      <description>&lt;p&gt;If you're like me, when a new version of an OS comes out (I don't care of it's macOS, Windows, or Raspberry Pi OS) I feel an irrational need to install it. &lt;strong&gt;I'm also exceptionally lazy&lt;/strong&gt;, so I usually opt for the upgrade path versus a clean install.&lt;/p&gt;

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

&lt;p&gt;Today I want to take a look at how you can update a Raspberry Pi (or compatible single board computer) to the newly released &lt;a href="https://www.raspberrypi.com/news/bookworm-the-new-version-of-raspberry-pi-os/" rel="noopener noreferrer"&gt;"Bookworm" version of Raspberry Pi OS&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Let me be crystal clear: This is an &lt;strong&gt;unsupported upgrade path&lt;/strong&gt; that is not endorsed by the Raspberry Pi Foundation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But I also like to live dangerously.&lt;/p&gt;

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

&lt;p&gt;Before continuing, I'm sure at least some of you are wondering: &lt;em&gt;What exactly is Bookworm?&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Bookworm?
&lt;/h2&gt;

&lt;p&gt;Bookworm is the codename for a &lt;a href="https://raspberrytips.com/raspberry-pi-os-versions/" rel="noopener noreferrer"&gt;new version of Raspberry Pi OS&lt;/a&gt;. Based on Debian GNU/Linux 12, Bookworm is a significant new release for Raspberry Pi enthusiasts.&lt;/p&gt;

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

&lt;p&gt;Arguably the three most important updates in Bookworm are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A switch from the X11 Windowing System to &lt;a href="https://wayland.freedesktop.org/" rel="noopener noreferrer"&gt;Wayland&lt;/a&gt;, which improves security and performance (and includes some pleasing desktop UI updates).&lt;/li&gt;
&lt;li&gt;Adopting &lt;a href="https://pipewire.org/" rel="noopener noreferrer"&gt;PipeWire&lt;/a&gt; as the default audio backend (replacing PulseAudio).&lt;/li&gt;
&lt;li&gt;Use of &lt;a href="https://networkmanager.dev/" rel="noopener noreferrer"&gt;NetworkManager&lt;/a&gt; as the default network suite (replacing dhcpcd).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Bullseye to Bookworm Upgrade Options
&lt;/h2&gt;

&lt;p&gt;The &lt;em&gt;good news&lt;/em&gt; is there are multiple upgrade options from Bullseye to Bookworm.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;bad news&lt;/em&gt; is there are multiple upgrade options from Bullseye to Bookworm.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easy Upgrade Path&lt;/li&gt;
&lt;li&gt;Better Upgrade Path&lt;/li&gt;
&lt;li&gt;
Best "Upgrade" Path (the officially recommended path, but that's not why you're here)&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;WARNING:&lt;/strong&gt; Regardless of the path you take, be sure to &lt;strong&gt;make a full backup&lt;/strong&gt; of the SD card you're using for your current Raspberry Pi installation. Yes, just in case.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Easy Upgrade Path from Bullseye to Bookworm
&lt;/h2&gt;

&lt;p&gt;Sorry, one more warning:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;WARNING:&lt;/strong&gt; These instructions are &lt;strong&gt;only valid for upgrading from Bullseye&lt;/strong&gt;. If you are currently running Buster, you'll have to install Bullseye first (but at that point, seriously, just install a fresh version of Bookworm and ignore the rest of this blog post, except for the ending).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ready? Backup finished? Anxious to get started? &lt;strong&gt;Let's begin!&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Perform a &lt;strong&gt;full upgrade&lt;/strong&gt; of your existing Bullseye installation. Open a Terminal and enter these commands, sequentially:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   sudo apt update
   sudo apt full-upgrade
   sudo reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;After rebooting, enter the following command to edit &lt;code&gt;sources.list&lt;/code&gt;, which is how your Pi references the archives for the Debian repositories:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   sudo nano /etc/apt/sources.list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Update the first line, replacing &lt;code&gt;bullseye&lt;/code&gt; with &lt;code&gt;bookworm&lt;/code&gt; and adding &lt;code&gt;non-free-firmware&lt;/code&gt; to the end of the line. Your &lt;code&gt;sources.list&lt;/code&gt; file should end up looking something like this:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   deb http://raspbian.raspberrypi.org/raspbian/ bookworm main contrib non-free rpi non-free-firmware
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Type &lt;code&gt;ctrl-x&lt;/code&gt; to exit &lt;code&gt;nano&lt;/code&gt;, making sure you save the file on exit. Next, you need to open &lt;code&gt;raspi.list&lt;/code&gt; (to update the Debian Bookworm base) with this command:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   sudo nano /etc/apt/sources.list.d/raspi.list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;In this file, you only need to change &lt;code&gt;bullseye&lt;/code&gt; to &lt;code&gt;bookworm&lt;/code&gt;. Again, hit &lt;code&gt;ctrl-x&lt;/code&gt;, save your changes, and exit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Perform &lt;em&gt;another&lt;/em&gt; full upgrade, but this time sourcing from the Bookworm repositories!&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   sudo apt update
   sudo apt full-upgrade
   sudo reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;And now you wait. And wait some more.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;After this final reboot, confirm that you're now running an &lt;em&gt;almost complete&lt;/em&gt; version of Bookworm with this command:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   cat /etc/os-release
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Success! 🥳&lt;/p&gt;

&lt;p&gt;However, while it &lt;em&gt;looks and feels&lt;/em&gt; like this is a full upgrade to Bookworm, some smarter people than I have pointed out that changes like PipeWire and NetworkManager may not be properly configured via this route. That's what the "Better Way" is for:&lt;/p&gt;

&lt;h2&gt;
  
  
  A Better Way to Upgrade to Bookworm
&lt;/h2&gt;

&lt;p&gt;Yes, there is a better way. It's not for the faint of heart though.&lt;/p&gt;

&lt;p&gt;A brave soul on GitHub is actively updating a series of commands for a &lt;strong&gt;full in-place upgrade&lt;/strong&gt; from Bullseye to Bookworm &lt;a href="https://gist.github.com/jauderho/6b7d42030e264a135450ecc0ba521bd8" rel="noopener noreferrer"&gt;in this gist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As of the publishing of this blog post in November 2023, here is the full list of commands. Godspeed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;### WARNING: READ CAREFULLY BEFORE ATTEMPTING ###
#
# Credit to anfractuosity and fgimenezm for figuring out additional details for kernels
#

# Make sure everything is up-to-date
sudo apt-get update &amp;amp;&amp;amp; sudo apt-get dist-upgrade

# Point to bookworm repos instead
sudo sed -i -e 's/bullseye/bookworm/g' /etc/apt/sources.list
sudo sed -i -e 's/bullseye/bookworm/g' /etc/apt/sources.list.d/raspi.list

# Do actual update
sudo apt update &amp;amp;&amp;amp; sudo apt -y full-upgrade &amp;amp;&amp;amp; sudo apt -y clean &amp;amp;&amp;amp; sudo apt -y autoremove

# Reboot
sudo reboot

# Remove old config files after doing sanity checks
sudo apt purge ?config-files

### Switch to the new kernels ###
# Prep
sudo dpkg --purge --force-depends raspberrypi-kernel raspberrypi-bootloader
sudo umount /boot
sudo fsck -y /boot
sudo mkdir /boot/firmware
sudo sed -i.bak -e "s#boot#boot/firmware#" /etc/fstab
sudo systemctl daemon-reload
sudo mount /boot/firmware
sudo apt install raspi-firmware

# Actually install the kernels. Make sure you pick the right version for your Pi
# sudo apt install linux-image-rpi-v8 linux-headers-rpi-v8      # 64bit
# sudo apt install linux-image-rpi-v7l linux-headers-rpi-v7l    # 32bit
# sudo apt install linux-image-rpi-v6 linux-headers-rpi-v6      

# Append auto_initramfs=1 to the bottom of file where it says [all]
sudo sed -i.bak '$ a\auto_initramfs=1' /boot/firmware/config.txt 

# Reboot
sudo reboot

# Verify using "uname -a" (correct as of 10/2023)
# Old kernel
# Linux raspberrypi 6.1.21-v8+ #1642 SMP PREEMPT Mon Apr  3 17:24:16 BST 2023 aarch64 GNU/Linux
# New kernel
# Linux raspberrypi 6.1.0-rpi4-rpi-v8 #1 SMP PREEMPT Debian 1:6.1.54-1+rpt2 (2023-10-05) aarch64 GNU/Linux

# If you are not converted to using NetworkManager, you might lose networking upon reboot. 
# Check the ARP table to see what the new DHCP assigned IP address is. You may have to manually set the IP address again
# Thanks to solsticedhiver for identifying this
#
# Switch to NetworkManager from dhcpcd
sudo systemctl enable --now NetworkManager
sudo systemctl disable --now dhcpcd
#
# Set up static IP. Adjust as necessary 
sudo nmcli -p connection show 
sudo nmcli -p connection show "Wired connection 1"
sudo nmcli con mod "Wired connection 1" ipv4.method manual ipv4.addresses 192.168.1.5/24 ipv4.gateway 192.168.1.1

# Reboot
sudo reboot

#
# Bonus steps
#
# Install btop
sudo apt-get install btop
#
# Update /etc/ssh/sshd_config for up to date, secure by default config. Use ssh-audit to verify
KexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org
HostKeyAlgorithms ssh-ed25519-cert-v01@openssh.com,ssh-ed25519
Ciphers chacha20-poly1305@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Best Way to "Upgrade" to Bookworm
&lt;/h2&gt;

&lt;p&gt;After all we just went through, do you really want to know what I think is the &lt;strong&gt;best way to upgrade to Bookworm&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;Well, it's quite simple and foolproof:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Backup all of the directories and files you want to keep from your Pi.&lt;/li&gt;
&lt;li&gt;Download and install the &lt;a href="https://www.raspberrypi.com/software/" rel="noopener noreferrer"&gt;Raspberry Pi Imager&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Format an SD card and install a fresh copy of Bookworm.&lt;/li&gt;
&lt;li&gt;Copy your files back to the Pi.&lt;/li&gt;
&lt;li&gt;Success! 🥳&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Easy Cellular, LoRa, and Wi-Fi on the Pi
&lt;/h2&gt;

&lt;p&gt;Now that you're done, if you're in the market for a new way to add &lt;strong&gt;swappable Cellular, LoRa, and Wi-Fi to your Raspberry Pi&lt;/strong&gt;, take a look at the &lt;a href="https://shop.blues.io/collections/notecard" rel="noopener noreferrer"&gt;Blues Notecard&lt;/a&gt; and the &lt;a href="https://shop.blues.io/products/carr-pi" rel="noopener noreferrer"&gt;Blues Notecarrier Pi HAT&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fblues-wireless%2Fimage%2Ffetch%2Ff_auto%2Cc_limit%2Cw_1920%2Cq_auto%2Fhttps%3A%2F%2Fdev.blues.io%2F_next%2Fstatic%2Fmedia%2Frpi-and-notecarrier.88be48ad.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fblues-wireless%2Fimage%2Ffetch%2Ff_auto%2Cc_limit%2Cw_1920%2Cq_auto%2Fhttps%3A%2F%2Fdev.blues.io%2F_next%2Fstatic%2Fmedia%2Frpi-and-notecarrier.88be48ad.webp" alt="raspberry pi and blues notecard" width="740" height="511"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The cellular-based Notecards provide &lt;strong&gt;500MB of prepaid data and 10 years of global cellular service&lt;/strong&gt;. These are commonly used in edge deployments of Raspberry Pis that don't have access to reliable Wi-Fi connectivity.&lt;/p&gt;

&lt;p&gt;Learn more about adding low-bandwidth cellular to the Raspberry Pi in &lt;a href="https://www.hackster.io/blues-wireless/projects" rel="noopener noreferrer"&gt;many of these project tutorials on Hackster&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Happy Hacking! 💙&lt;/p&gt;

</description>
      <category>bullseye</category>
      <category>raspberrypi</category>
      <category>bookworm</category>
    </item>
    <item>
      <title>Read and Write Images and Text Files with a Micro SD Card and Arduino</title>
      <dc:creator>Rob Lauer</dc:creator>
      <pubDate>Tue, 24 Oct 2023 17:01:15 +0000</pubDate>
      <link>https://forem.com/blues/read-and-write-images-and-text-files-with-a-micro-sd-card-and-arduino-be6</link>
      <guid>https://forem.com/blues/read-and-write-images-and-text-files-with-a-micro-sd-card-and-arduino-be6</guid>
      <description>&lt;p&gt;We've all been there. Well, at least &lt;em&gt;I've&lt;/em&gt; certainly been there:&lt;/p&gt;

&lt;p&gt;I want to perform what I assume to be a simple task with C (Arduino): &lt;em&gt;"I just want to read and write some data to a Micro SD card! This can't be that difficult can it!?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Five searches deep on Google, I realize there are far too many ways to skin this particular cat. Different components, different libraries, and even different libraries for the same components.&lt;/p&gt;

&lt;p&gt;Time to take a deep breath and channel my internal Bob Ross...&lt;/p&gt;

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

&lt;p&gt;This article is meant cut out the extraneous info and provide a guide for what I consider to be the easiest way to use a Micro SD card with Arduino to read/write text and image files.&lt;/p&gt;

&lt;p&gt;In this mini-project, I'm using the following hardware:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://shop.blues.io/collections/swan/products/swan" rel="noopener noreferrer"&gt;STM32-based Blues Swan&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.adafruit.com/product/254" rel="noopener noreferrer"&gt;Adafruit Micro SD breakout board&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.amazon.com/SanDisk-Ultra-microSDXC-Memory-Adapter/dp/B073JWXGNT" rel="noopener noreferrer"&gt;SanDisk 32GB Micro SD card&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Hardware Wiring
&lt;/h2&gt;

&lt;p&gt;Since you're are only wiring a couple of components together this is a relatively simple step, but it's important to get the wiring correct:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;SD Breakout&lt;/th&gt;
&lt;th&gt;Blues Swan&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GND&lt;/td&gt;
&lt;td&gt;GND&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3v&lt;/td&gt;
&lt;td&gt;3V3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CLK&lt;/td&gt;
&lt;td&gt;CK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DO&lt;/td&gt;
&lt;td&gt;MI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DI&lt;/td&gt;
&lt;td&gt;MO&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CS&lt;/td&gt;
&lt;td&gt;A3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Your wiring should end up looking like something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7twzz4pdgs9k21ugf6zn.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7twzz4pdgs9k21ugf6zn.jpg" alt="wiring blues swan to sd breakout board" width="800" height="615"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prepping the Micro SD Card
&lt;/h2&gt;

&lt;p&gt;You'll want to make sure the Micro SD card is formatted as either FAT16 (if &amp;lt;= 2GB) or FAT32. If not, simply get ahold of a USB SD card reader and use your PC to format it using the SD Association's Memory Card Formatter (&lt;a href="https://www.sdcard.org/downloads/formatter/" rel="noopener noreferrer"&gt;macOS/Win&lt;/a&gt; and &lt;a href="https://www.sdcard.org/downloads/sd-memory-card-formatter-for-linux/" rel="noopener noreferrer"&gt;Linux&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Slide that Micro SD card back in the breakout board and we are ready to write some code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Arduino Micro SD Breakout Library
&lt;/h2&gt;

&lt;p&gt;One of the reasons I chose &lt;a href="https://www.adafruit.com/product/254" rel="noopener noreferrer"&gt;Adafruit's Micro SD breakout board&lt;/a&gt; (aside from the generally high quality of their hardware) is because they offer well-supported and well-documented libraries. This one is no exception.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Consult &lt;a href="https://github.com/arduino-libraries/SD" rel="noopener noreferrer"&gt;this GitHub repository&lt;/a&gt; for the latest version of the Arduino SD Library.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's look at how we might perform some typical file-based tasks with an SD card, in particular:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Writing and reading text files.&lt;/li&gt;
&lt;li&gt;Writing and reading images (PNGs).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Writing a Text File
&lt;/h2&gt;

&lt;p&gt;Using a Micro SD card as a data logger is very common in our industry. They are inexpensive and a relatively stable medium.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You didn't ask, but if you want my opinion, your data should only be saved to SD as a backup. I'll always recommend you sync data with the cloud using the &lt;a href="https://shop.blues.io/collections/notecard" rel="noopener noreferrer"&gt;Cellular, Wi-Fi, or LoRa-based Notecard&lt;/a&gt;, but I digress...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The following sketch will initialize the SD card breakout board and write a string to a text file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;SD.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="n"&gt;textFile&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;Sd2Card&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;SdVolume&lt;/span&gt; &lt;span class="n"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;chipSelect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;A3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9600&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Starting SD card init..."&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SPI_HALF_SPEED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chipSelect&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SD card initialization failed!"&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// try to open the 'volume'/'partition' - it should be FAT16 or FAT32&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Could not find FAT16/FAT32 partition."&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="mi"&gt;1&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="n"&gt;SD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chipSelect&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SD initialization complete!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// open a new file for writing&lt;/span&gt;
  &lt;span class="n"&gt;textFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hello.txt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FILE_WRITE&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="n"&gt;textFile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;textFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello World!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;textFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Completed writing a text file!"&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="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;loop&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that there are a few things to be aware of when working with files:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You'll want to keep filenames short and use the 8.3 format (e.g. &lt;code&gt;MYFILE.TXT&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Filenames are NOT case sensitive, so in code, &lt;code&gt;myfile.txt&lt;/code&gt; == &lt;code&gt;MYFILE.TXT&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Always &lt;code&gt;close()&lt;/code&gt; your files when done to make sure any pending writes are properly saved.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Reading from a Text File
&lt;/h2&gt;

&lt;p&gt;Now that you've written a simple text file to a Micro SD card, let's see how easy it can be to &lt;strong&gt;read data from an existing file&lt;/strong&gt; (specifically the &lt;code&gt;hello.txt&lt;/code&gt; file we just created).&lt;/p&gt;

&lt;p&gt;Add the following code snippet &lt;strong&gt;after the file is created&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// open the existing file for reading&lt;/span&gt;
&lt;span class="n"&gt;textFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hello.txt"&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="n"&gt;textFile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"here's the data in hello.txt:"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// read until there's nothing else in it:&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;textFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;textFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// close the file:&lt;/span&gt;
  &lt;span class="n"&gt;textFile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&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;If you're using the serial monitor to see the output, you may be surprised to see "Hello World!" repeated many times. What's up with that?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Starting SD card init...
SD initialization complete!
Completed writing a text file!
here's the data in hello.txt:
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is because writing to an existing file &lt;em&gt;appends data&lt;/em&gt; by default and doesn't overwrite existing data. In my case, I had clearly tested the write sequence a handful of times &lt;strong&gt;before&lt;/strong&gt; I read the data out.&lt;/p&gt;

&lt;p&gt;To fix this, you can always &lt;code&gt;remove()&lt;/code&gt; the file first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;SD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hello.txt"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Other Useful Functions When Working with Files on SD
&lt;/h3&gt;

&lt;p&gt;Before writing to a file, you might want to see if it already exists by using the &lt;code&gt;SD.exists("thefile.txt")&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;Create directories and subdirectories with the &lt;code&gt;SD.mkdir("/newdir")&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;Use the aforementioned &lt;code&gt;SD.remove("thefile.txt")&lt;/code&gt; function to delete files.&lt;/p&gt;

&lt;p&gt;Be sure to consult &lt;a href="https://learn.adafruit.com/adafruit-micro-sd-breakout-board-card-tutorial" rel="noopener noreferrer"&gt;Adafruit's full tutorial&lt;/a&gt; for additional information on using this breakout board with a Micro SD card.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working with Images
&lt;/h2&gt;

&lt;p&gt;Let's switch gears and ramp up the complexity a little bit by writing and reading images to/from the Micro SD card.&lt;/p&gt;

&lt;p&gt;As with every complex Arduino task, we first need to ask: &lt;strong&gt;"Is there library for this?"&lt;/strong&gt;. The answer is almost always "yes", and this time is no exception.&lt;/p&gt;

&lt;p&gt;When working with image files on host microcontrollers with a relatively small amount of memory and processing power, we usually want to try to work with small files. Bitmap (.bmp) is a good format for lossless, but simple, images. Modern hosts are more often dealing with higher fidelity images that can't reasonably be sent uncompressed over the wire. This is where the usage of a format like JPEG or PNG come in handy.&lt;/p&gt;

&lt;p&gt;Choosing the &lt;strong&gt;optimal type of compression to use will depend on the image itself&lt;/strong&gt; (e.g. photos usually compress best with JPEG while drawings/line art are best compressed with PNG or WEBP).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example of thermal camera images saved as BMP, PNG, and JPG:&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;And yes there are libraries for these options as well! &lt;a href="https://github.com/bitbank2/JPEGenc" rel="noopener noreferrer"&gt;JPGenc&lt;/a&gt; and &lt;a href="https://github.com/bitbank2/JPEGdec" rel="noopener noreferrer"&gt;JPGdec&lt;/a&gt; for encoding/decoding JPG images, and &lt;a href="https://github.com/bitbank2/PNGenc" rel="noopener noreferrer"&gt;PNGenc&lt;/a&gt; and &lt;a href="https://github.com/bitbank2/PNGdec" rel="noopener noreferrer"&gt;PNGdec&lt;/a&gt; for encoding/decoding PNGs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Looking for a functional example of image compression in action? &lt;a href="https://www.hackster.io/rob-lauer/id-chicken-eggs-with-thermal-images-ml-and-cellular-iot-748c88" rel="noopener noreferrer"&gt;Check out this project on Hackster&lt;/a&gt; that uses a thermal camera to capture images, show them on a TFT display, and save them to a Micro SD card.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Writing an Image to a Micro SD Card
&lt;/h2&gt;

&lt;p&gt;While this is a bit of a contrived example, let's strip down a code example to its bare minimum and look at how you might draw a basic image and save it to the SD card.&lt;/p&gt;

&lt;p&gt;The following sketch creates a 128x128 image with a green border and green "x" going through it, like so, using the &lt;code&gt;PNGenc&lt;/code&gt; library:&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;PNGenc.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;SD.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="n"&gt;PNG&lt;/span&gt; &lt;span class="n"&gt;png&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="n"&gt;myPNG&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;Sd2Card&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;SdVolume&lt;/span&gt; &lt;span class="n"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;chipSelect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;A3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;myOpen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Attempting to open %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;myPNG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;O_READ&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;O_WRITE&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;O_CREAT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;myPNG&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;myClose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PNGFILE&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;fHandle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="nf"&gt;myRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PNGFILE&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;fHandle&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;f&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="nf"&gt;myWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PNGFILE&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;fHandle&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;f&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="nf"&gt;mySeek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PNGFILE&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;fHandle&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;f&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;seek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cp"&gt;#define WIDTH 128
#define HEIGHT 128
&lt;/span&gt;
&lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;ucPal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;768&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;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="mi"&gt;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="mi"&gt;255&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;// black, green&lt;/span&gt;
&lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;ucAlphaPal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;        &lt;span class="c1"&gt;// first color (black) is fully transparent&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iDataSize&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;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;ucLine&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;WIDTH&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;115200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Starting SD card init..."&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SPI_HALF_SPEED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chipSelect&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SD card initialization failed!"&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// try to open the 'volume'/'partition' - it should be FAT16 or FAT32&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Could not find FAT16/FAT32 partition."&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="mi"&gt;1&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="n"&gt;SD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chipSelect&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SD initialization complete!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;micros&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;png&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/testimg.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;myOpen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;myClose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;myRead&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;myWrite&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mySeek&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="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;PNG_SUCCESS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;png&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encodeBegin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WIDTH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HEIGHT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PNG_PIXEL_INDEXED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ucPal&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="n"&gt;png&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setAlphaPalette&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ucAlphaPal&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="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;PNG_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;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;HEIGHT&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;PNG_SUCCESS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// prepare a line of image to create a red box with an x on a transparent background&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;HEIGHT&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="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;memset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ucLine&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;WIDTH&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// top+bottom green lines&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="n"&gt;memset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ucLine&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="n"&gt;WIDTH&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="n"&gt;ucLine&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="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ucLine&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;WIDTH&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="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="c1"&gt;// left/right border&lt;/span&gt;
                    &lt;span class="n"&gt;ucLine&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ucLine&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;WIDTH&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;y&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="c1"&gt;// X in the middle&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;png&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ucLine&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// for y&lt;/span&gt;
            &lt;span class="n"&gt;iDataSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;png&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;micros&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%d bytes of data written to file in %d us&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iDataSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;l&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="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;loop&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;This is a lightly-edited version of the &lt;a href="https://github.com/bitbank2/PNGenc/blob/master/examples/PNGenc_Test/PNGenc_Test.ino" rel="noopener noreferrer"&gt;example sketch from the PNGenc&lt;/a&gt; repository.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The devil is always in the details when dealing with images, as every project has differing requirements. You likely either have images pre-loaded on an SD card or you are capturing new images with a camera. If using a camera, you'll need a host MCU with enough memory to encode/compress/save the image AND you'll have to find a library specific to the camera you are using.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Reading an Image from an SD Card
&lt;/h2&gt;

&lt;p&gt;Now that we can write a simple PNG image, let's see how we might &lt;strong&gt;read PNGs&lt;/strong&gt; using the &lt;code&gt;PNGdec&lt;/code&gt; library.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your mileage may vary, but at the time of this writing, both &lt;code&gt;PNGenc&lt;/code&gt; and &lt;code&gt;PNGdec&lt;/code&gt; cannot be used at the same time in the same project.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The following sketch will &lt;strong&gt;read all PNG files from the root directory&lt;/strong&gt; of an SD card and load them, one at a time, into memory. Again, this is not very functional because it doesn't actually "do" anything with the images, but it's a good first step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;PNGdec.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;SD.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="n"&gt;PNG&lt;/span&gt; &lt;span class="n"&gt;png&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="n"&gt;myPNG&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;Sd2Card&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;SdVolume&lt;/span&gt; &lt;span class="n"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;chipSelect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;A3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;115200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Starting SD card init..."&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SPI_HALF_SPEED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chipSelect&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SD card initialization failed!"&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// try to open the 'volume'/'partition' - it should be FAT16 or FAT32&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Could not find FAT16/FAT32 partition."&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="mi"&gt;1&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="n"&gt;SD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chipSelect&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SD initialization complete!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;myOpen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Attempting to open %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;myPNG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;myPNG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;myPNG&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;myClose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&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="n"&gt;myPNG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;myPNG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="nf"&gt;myRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PNGFILE&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;myPNG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&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;myPNG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="nf"&gt;mySeek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PNGFILE&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;myPNG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&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;myPNG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Function to draw pixels to the display&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;PNGDraw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PNGDRAW&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pDraw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;uint16_t&lt;/span&gt; &lt;span class="n"&gt;usPixels&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;320&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="n"&gt;png&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLineAsRGB565&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pDraw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;usPixels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PNG_RGB565_LITTLE_ENDIAN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mh"&gt;0xffffffff&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Main loop, scan for all .PNG files on the card and display them&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filecount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="n"&gt;dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&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="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;openNextFile&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="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;break&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="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isDirectory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&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="n"&gt;len&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;strcmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;len&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="s"&gt;"PNG"&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="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"File: "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;png&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;myOpen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;myClose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;myRead&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mySeek&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PNGDraw&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="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;PNG_SUCCESS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;Serial&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"image specs: (%d x %d), %d bpp, pixel type: %d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;png&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getWidth&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;png&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getHeight&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;png&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getBpp&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;png&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getPixelType&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
                    &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;png&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;NULL&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;// do something...anything..with the PNG file!&lt;/span&gt;
                    &lt;span class="n"&gt;png&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&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="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And what does the output look like in the serial monitor?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;File: TESTIMG.PNG
Attempting to open TESTIMG.PNG
image specs: (128 x 128), 8 bpp, pixel type: 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Success! 🥳&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Hopefully you've seen how easy your first steps can be when using a Micro SD card to read and write images and text files with Arduino/C.&lt;/p&gt;

&lt;p&gt;As mentioned above, every project scenario is different (whether you're just reading images pre-loaded to the SD card, &lt;a href="https://www.hackster.io/rob-lauer/id-chicken-eggs-with-thermal-images-ml-and-cellular-iot-748c88" rel="noopener noreferrer"&gt;creating PNGs from a thermal camera module&lt;/a&gt;, or most likely taking a picture with an attached camera module and storing them as JPEGs).&lt;/p&gt;

&lt;p&gt;Including a Micro SD card as a data logger is a simple and effective way to store data (which &lt;strong&gt;of course&lt;/strong&gt; you'll later end up syncing to the cloud via &lt;a href="https://shop.blues.io/collections/notecard" rel="noopener noreferrer"&gt;Cellular, Wi-Fi, or LoRa using the Notecard&lt;/a&gt;!).&lt;/p&gt;

&lt;p&gt;Happy Hacking! 💙&lt;/p&gt;

</description>
      <category>arduino</category>
      <category>iot</category>
      <category>c</category>
    </item>
    <item>
      <title>How It's Made — Manufacturing Notecard at Scale</title>
      <dc:creator>David Scheltema</dc:creator>
      <pubDate>Wed, 27 Sep 2023 16:00:00 +0000</pubDate>
      <link>https://forem.com/blues/how-its-made-manufacturing-notecard-at-scale-je1</link>
      <guid>https://forem.com/blues/how-its-made-manufacturing-notecard-at-scale-je1</guid>
      <description>&lt;p&gt;While it is easy to get your hands on a Blues Notecard, it's not as easy to learn what it takes to manufacture one. That is... until today.&lt;/p&gt;

&lt;p&gt;This post walks you through the factory floor as a panel of Notecards pass through each step of the manufacturing process — going from an unpopulated printed circuit board (PCB) to a working Notecard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before the Manufacturing Even Begins
&lt;/h2&gt;

&lt;p&gt;The manufacturing process is the culmination of a huge amount of behind the scenes engineering and operational work. Before a single component is shipped to the manufacturing facility, electrical engineers must finish the PCB design and layout. This information packaged in a Gerber file format, which provides the exact design requirements for the PCB as well as the placement of the components.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq738lsjitjo15em1rffa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq738lsjitjo15em1rffa.png" alt="Notecard approximately twice the actual size."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Simultaneously to the work of the electrical engineers, operation engineers work with component vendors and suppliers to find the best prices, and component variants for the quantities necessary to successfully manufacture the electrical engineer's design.&lt;/p&gt;

&lt;h2&gt;
  
  
  It All Starts with Printed Circuit Boards
&lt;/h2&gt;

&lt;p&gt;Notecard PCBs are the first component of the manufacturing process that need to be manufactured. Using a layering process of copper and FR4—a type of fire-resistant fiberglass—PCBs are built one layer at a time. Advanced PCBs like Notecard's use multiple layers of FR4 and copper to properly connect all of the components necessary to make up the final design.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;a href="https://www.youtube.com/watch?v=ljOoGyCso8s" rel="noopener noreferrer"&gt;Scotty from the YouTube channel Strange Parts&lt;/a&gt; has a great video tour of the PCB manufacturing process if you're interested in the step-by-step process.&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;With Notecard manufacturing a panel of eight Notecards is the smallest unit of material that goes through the line. This improves production output while reducing the time that the manufacturing line is operational.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4lr7omw6hqqmxtkxljyx.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4lr7omw6hqqmxtkxljyx.jpg" alt="Eight Notecards in one PCB panel."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see in the unpopulated panel above, the exterior layer of the Notecard PCB consists of exposed copper pads and green areas of solder mask. Copper when heated will attract solder whereas the solder mask when heated will not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Application of Solder Paste
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fto2njz2g034590vfnc8c.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fto2njz2g034590vfnc8c.jpg" alt="Image of solder paste application."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first stop on the manufacturing line is the solder paste machine. The Notecard panel is placed inside the machine and a thin metal stencil is secured over the panel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8e72m3rvfj099qwp8hmn.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8e72m3rvfj099qwp8hmn.jpg" alt="Close up of the squeegee applying the paste and flux."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An automated squeegee then smears a combination of solder paste and flux across the stencil depositing globs of the gooey material on the exposed copper pads. After a few passes of the squeegee, the stencil is removed and the panel is placed on a conveyer belt bound for the next step of the manufacturing line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optical Inspection
&lt;/h2&gt;

&lt;p&gt;Before the manufacturing process goes any farther, a series of high-powered cameras inspect the dollops of solder paste and flux are correctly placed on the copper. Too much or too little solder and flux could be detrimental to the assembly process and potentially create bad solder joints between the PCB and the components.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe48hi9rc5h6yqzts1wc1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe48hi9rc5h6yqzts1wc1.jpg" alt="Optical Inspection of Solder Paste &amp;amp; Flux."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This early inspection and anomaly detection is just one of the steps meant to ensure production efficiency and reduces costly rework later on in the manufacturing process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu4be2ygz5lt9wjhns3ta.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu4be2ygz5lt9wjhns3ta.jpg" alt="All Good - optical inspection pass screen."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Component Placement on the PCB
&lt;/h2&gt;

&lt;p&gt;With fresh solder paste and flux on the PCB pads, the next step in the assembly process is component placement. It's here at the Pick-and-Place machine that automation is at its most advanced.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdfq05ny54zo73rz9rm8t.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdfq05ny54zo73rz9rm8t.jpg" alt="Two enormous pick-and-place machines."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All the components that make up Notecard are loaded into the pick-and-place machine. Each component is packaged on a reel that loads into the pick and place machine almost like a printer cartridge to a laser printer. &lt;/p&gt;

&lt;p&gt;The pick-and-place machine is programmed to know exactly where each component is stored on a reel as well as where each component must be placed on the PCB panel. Using a suction device on a gantry arm to lift the component and move it into position, the pick-and-place machine is able to precisely select and deposit tens of thousands of components per hour.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx96mk6uoyxbjbof26c6y.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx96mk6uoyxbjbof26c6y.jpg" alt="IC being loaded into the PnP machine."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The speed and accuracy of these machines boggle the mind. They are simply put the backbone of the modern manufacturing line.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flsphhlhrxq1rlxxjhh44.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flsphhlhrxq1rlxxjhh44.jpg" alt="Pick-and-Place machine adding a header to the PCB."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Soldering at Scale
&lt;/h2&gt;

&lt;p&gt;With all the components placed on the globs of solder paste and flux, the only thing holding them in place is surface tension. To make a more permanent connection heat needs to be carefully applied to the panel of Notecards.&lt;/p&gt;

&lt;p&gt;Unlike your typical handheld soldering iron where you solder one component at a time, manufacturing lines use reflow ovens to solder all of the components on one side of the PCB at once.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqgd4zbjq5yzzjb5dxq91.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqgd4zbjq5yzzjb5dxq91.jpg" alt="A panel of Notecards enters the reflow oven."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These high-tech ovens precisely modulate the temperature based on temperature curves input by engineers so that they heat up and cool down period is very deliberate and conforms to the temperature tolerances of the materials being reflowed. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsu2f26trxg7oh53z7du8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsu2f26trxg7oh53z7du8.jpg" alt="A look inside the reflow oven."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When a reflow oven heats up, the solder paste and flux melt. The surface tension of the molten hot solder pulls the SMT component towards the copper pad on the PCB and as the oven temperatures drop, the solder cools and forms a solder joint — an electrical connection between the component and the PCB pad.&lt;/p&gt;

&lt;p&gt;Astonishingly, reflow ovens are so precise that they are able to heat one side of a PCB and not melt the solder on the other side of the board. This allows for two-sided boards like Notecard to be reflowed in two passes — one for the top side and one for the bottom side — without any components falling off during the process.&lt;/p&gt;

&lt;p&gt;After the solder joints are cooled, the panel of Notecards is conveyed to another automated optical inspection machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automated Optical Inspection
&lt;/h2&gt;

&lt;p&gt;Automated optical inspection at this point in the manufacturing line is focused on ensuring the solder joints and component placement have no defects.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr8ygjyq1o3hz08kgb3j9.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr8ygjyq1o3hz08kgb3j9.jpg" alt="Automated Optical Inspection machine in action."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As with the first round of optical inspections, it's far better to catch any problems here before the panel advances to the next stage of manufacturing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quality Control with a Human Touch
&lt;/h2&gt;

&lt;p&gt;Quality control is the last stage of the manufacturing process where the eight Notecards are connected on one panel. Rather than rely on automation, this quality control step is performed by a manufacturing engineer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx0m6nj93ndz8hd8f73dm.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx0m6nj93ndz8hd8f73dm.jpg" alt="Visual Inspection of Notecard for Quality Control."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;They perform a visual inspection of all areas of the panel using large magnifying glasses as well as conduct other testing to ensure the Notecards have been properly assembled. Once approved, the panel of Notecards is ready for depanelization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Depaneling into Single Notecards
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4sylehbnvhaucry3z3cy.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4sylehbnvhaucry3z3cy.jpg" alt="The routing fixture secures the Notecards securely in place."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using an advanced router, the depaneling machine uses a bit to remove the excess FR4 material that held the eight Notecards together. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqpt2o58dch6wo27xgkcc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqpt2o58dch6wo27xgkcc.jpg" alt="The fixture ensures the router cannot damage components or cut the wrong part of the PCB panel."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To ensure that the Notecards are not damaged during the process, the panel is loaded into a tray and a fixture is placed over the panel to guide the the router bit as the depanelization occurs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notecard Functional Testing
&lt;/h2&gt;

&lt;p&gt;With each Notecard free from its panel and fully populated with components, it's ready for functional testing. This step ensures that the Notecard is able to power on properly and execute a series of test commands.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs6o5yicbcoob987znjeb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs6o5yicbcoob987znjeb.jpg" alt="Multiple Notecards undergo software testing at the same time."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once the Notecard passes the functional tests, it's off to the labeling and packaging department.&lt;/p&gt;

&lt;h2&gt;
  
  
  Individual Packaging and Boxing
&lt;/h2&gt;

&lt;p&gt;Each Notecard is packaged individually inside a zip-locked back and placed into a large tray.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fidn74a02n198isuf35fh.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fidn74a02n198isuf35fh.jpg" alt="Notecards are packaged individually and loaded into trays for shipment."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Multiple trays are stacked to snugly fit into larger shipping boxes to optimize the number of Notecards that can fit ship at a time.&lt;/p&gt;

</description>
      <category>hardware</category>
      <category>iot</category>
    </item>
    <item>
      <title>Establishing a Robust MLOps Workflow for TinyML</title>
      <dc:creator>TJ VanToll</dc:creator>
      <pubDate>Tue, 26 Sep 2023 17:37:10 +0000</pubDate>
      <link>https://forem.com/blues/establishing-a-robust-mlops-workflow-for-tinyml-4oo3</link>
      <guid>https://forem.com/blues/establishing-a-robust-mlops-workflow-for-tinyml-4oo3</guid>
      <description>&lt;p&gt;A machine learning model is only as good as the data it’s trained on. Gathering a robust set of training data is challenging for any ML project, but it’s especially difficult in the world of TinyML, where devices are often underpowered and running in areas with limited connectivity.&lt;/p&gt;

&lt;p&gt;Here at Blues we specializing in making connectivity easy for embedded projects, and together with our friends at &lt;a href="https://edgeimpulse.com/"&gt;Edge Impulse&lt;/a&gt; and &lt;a href="https://zephyrproject.org/"&gt;Zephyr&lt;/a&gt;, we’ve put together a workflow that we think helps make MLOps—or the process of continuously improving an ML model—possible for tiny devices.&lt;/p&gt;

&lt;p&gt;In this article I’m going to lay out an opinionated way to implement a robust MLOps process. We’ll start by looking at how to collect ML data from your remote devices, and then show how you can update ML models running on our devices in the field. At the end you’ll have a workflow that looks like this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Y6IND9vK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d5yz2fnzjq5uzhajpgb4.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y6IND9vK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d5yz2fnzjq5uzhajpgb4.gif" alt="A full process of ingesting ML data and posting it to Edge Impulse" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s get started.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: See &lt;a href="https://www.youtube.com/watch?v=7cLzNz0Sa-Y"&gt;&lt;em&gt;Optimized MLOps with Edge Impulse, Blues, and Zephyr&lt;/em&gt;&lt;/a&gt; on YouTube for a video version of this tutorial.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Collecting ML Data
&lt;/h2&gt;

&lt;p&gt;For this article we’re going to assume we manage a fleet of devices tasked with monitoring industrial equipment. Each device is fitted with an accelerometer that measures machine vibrations, and runs those readings through an ML model that has been trained to detect abnormalities.&lt;/p&gt;

&lt;p&gt;To adopt an MLOps workflow, this project needs a way to gather accelerometer samples from the field, and a way to send those samples to the cloud so that they can be used to train an updated model.&lt;/p&gt;

&lt;p&gt;To send the data we’re going to use the &lt;a href="https://blues.io/products/notecard/"&gt;Blues Notecard&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blues.io/products/notecard/"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oNr-9f-n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vqva5sj1nlqqe1o6thhz.png" alt="The Blues Notecard" width="200" height="234"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Notecard is a system on module that makes connectivity simple for embedded projects. Specifically, the Notecard offers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;500MB of cellular connectivity&lt;/li&gt;
&lt;li&gt;Global cellular over LTE-M, NB-IoT, or Cat-1&lt;/li&gt;
&lt;li&gt;Secure “off the internet” communications from device-to-cloud&lt;/li&gt;
&lt;li&gt;Low-power hardware (~8µA when idle), and power-conscious firmware&lt;/li&gt;
&lt;li&gt;Easy embedding with onboard M.2 Key E connector or via a companion board&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Notecard is great for connectivity on remote devices as it can work in areas without Wi-Fi coverage, and can work on equipment that moves between locations or on vehicles. You can even &lt;a href="https://www.youtube.com/watch?v=MVYVVkgdDC4"&gt;use the Notecard for asset tracking&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To communicate with the Notecard in our project we need a few other pieces of hardware.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up your hardware
&lt;/h3&gt;

&lt;p&gt;The easiest way to get started with the Notecard is with a &lt;a href="https://shop.blues.io/collections/blues-starter-kits"&gt;Blues Starter Kit&lt;/a&gt;. The Starter Kit comes with a development board (Notecarrier), as well as a &lt;a href="https://blues.io/products/swan/"&gt;Swan microcontroller&lt;/a&gt; that can run this project’s firmware.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2QTUCVjV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/363x127owravhk0jsqvn.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2QTUCVjV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/363x127owravhk0jsqvn.jpg" alt="A Blues starter kit all connected" width="800" height="620"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: The &lt;a href="https://dev.blues.io/quickstart/blues-quickstart/"&gt;Blues quickstart&lt;/a&gt; has full details on how to assemble a Blues Starter Kit.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With the Starter Kit assembled, the last thing we need is an accelerometer to use to take readings. For that accelerometer we’ll use an &lt;a href="https://www.adafruit.com/product/2809"&gt;LIS3DH&lt;/a&gt;, which you can connect to the Starter Kit’s Notecarrier via a Qwiic cable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dps0us2n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pf9h5xvgmiwr24h2z6iy.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dps0us2n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pf9h5xvgmiwr24h2z6iy.jpg" alt="The full hardware set up of this MLOps project" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With all hardware assembled, let’s look at the firmware we’ll use to gather the accelerometer readings.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Firmware
&lt;/h3&gt;

&lt;p&gt;This project’s firmware is responsible for gathering readings from the accelerometer, and using the Notecard to send those readings to the cloud.&lt;/p&gt;

&lt;p&gt;For the firmware implementation we’re using &lt;a href="https://zephyrproject.org/"&gt;Zephyr&lt;/a&gt;, as Zephyr is low-level enough to give us access to features like &lt;a href="https://docs.zephyrproject.org/latest/kernel/services/threads/index.html"&gt;threading&lt;/a&gt;, while high-level enough to provide &lt;a href="https://docs.zephyrproject.org/latest/hardware/peripherals/sensor.html"&gt;sensor APIs&lt;/a&gt; and other development conveniences. Plus, Zephyr firmware runs great on the STM32-based boards like the Swan, and the Notecard has a &lt;a href="https://dev.blues.io/tools-and-sdks/firmware-libraries/zephyr-sdk/"&gt;ready-to-use Zephyr SDK&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The full firmware source code for this project is &lt;a href="https://github.com/tjvantoll/mlops-zephyr/"&gt;available on GitHub&lt;/a&gt;, and I will highlight some of the most important parts here.&lt;/p&gt;

&lt;p&gt;First, the firmware has to establish a connection between the Notecard and the Notecard’s backing cloud service, &lt;a href="https://notehub.io"&gt;Notehub&lt;/a&gt;. It does so by performing a &lt;a href="https://dev.blues.io/api-reference/notecard-api/hub-requests/#hub-set"&gt;&lt;code&gt;hub.set&lt;/code&gt; request&lt;/a&gt; on the Notecard, and sending a &lt;a href="https://dev.blues.io/api-reference/glossary/#productuid"&gt;ProductUID&lt;/a&gt; that tells what project to associate this Notecard with.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;J&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NoteNewRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hub.set"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;JAddStringToObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"product"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"com.your-company:your-product-name"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;JAddStringToObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"continuous"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;JAddStringToObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"sn"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"your-device-name"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;NoteRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A couple of notes on the code above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All of the Notecard’s API use JSON. The Notecard’s C SDK includes a number of helper functions such as &lt;code&gt;JAddStringToObject&lt;/code&gt; to make it easier to create JSON objects and send them to the Notecard.&lt;/li&gt;
&lt;li&gt;The development board that comes in a Blues Starter Kit, the Notecarrier F, performs all the necessary wiring so that the Swan can communicate with the Notecard via I2C. The &lt;code&gt;NoteRequest&lt;/code&gt; API performs this communication.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With a connection with Notehub established we next need to gather data from the accelerometer. The Zephyr code to take a reading from the LIS3DH is relatively straightforward on its own.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;sensor_value&lt;/span&gt; &lt;span class="n"&gt;accelerometerData&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="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sensor_sample_fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sensor&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="n"&gt;rc&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="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;printk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to fetch sensor data (error: %d)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&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="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sensor_channel_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sensor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SENSOR_CHAN_ACCEL_XYZ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;accelerometerData&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="n"&gt;rc&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="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;printk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to get sensor data (error: %d)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&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="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sensor_value_to_float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;accelerometerData&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="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sensor_value_to_float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;accelerometerData&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="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sensor_value_to_float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;accelerometerData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="n"&gt;printk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Reading - x: %f, y: %f, z: %f&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&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;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z&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 snippet takes a single accelerometer reading and places the values in three floating point variables named &lt;code&gt;x&lt;/code&gt;, &lt;code&gt;y&lt;/code&gt;, and &lt;code&gt;z&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For this example we need code that is a little more complex though, as we’re not taking one accelerometer reading—instead, we’re taking a series of readings over a period of time so that it can be sent to an ML model to look for patterns.&lt;/p&gt;

&lt;p&gt;For this project we’re using &lt;a href="https://edgeimpulse.com/"&gt;Edge Impulse&lt;/a&gt; to create and train our models. After you’ve &lt;a href="https://dev.blues.io/guides-and-tutorials/building-edge-ml-applications/blues-swan/"&gt;built a model with Edge Impulse&lt;/a&gt;, Edge Impulse lets you download an SDK with sample code that helps you gather sensor data at the intervals their platform expects for training purposes. Here’s what that ends up looking like in our Zephyr code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;sensor_value&lt;/span&gt; &lt;span class="n"&gt;accelerometerData&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="kt"&gt;float&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;malloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt; &lt;span class="n"&gt;ix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;ix&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;ix&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Determine the next tick (and then sleep later)&lt;/span&gt;
    &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;next_tick_ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NoteGetMs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;EI_CLASSIFIER_INTERVAL_MS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sensor_sample_fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sensor&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="n"&gt;rc&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="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;printk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to fetch sensor data (error: %d)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&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="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sensor_channel_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sensor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SENSOR_CHAN_ACCEL_XYZ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;accelerometerData&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="n"&gt;rc&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="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;printk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to get sensor data (error: %d)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&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="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sensor_value_to_float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;accelerometerData&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="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sensor_value_to_float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;accelerometerData&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="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sensor_value_to_float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;accelerometerData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="n"&gt;printk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"x: %f, y: %f, z: %f&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&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;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ix&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ix&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="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ix&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="n"&gt;delay_ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;next_tick_ms&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;NoteGetMs&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;NoteDelayMs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delay_ms&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;delay_ms&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&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;The various constants in this sample, &lt;code&gt;EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE&lt;/code&gt;, &lt;code&gt;EI_CLASSIFIER_RAW_SAMPLES_PER_FRAME&lt;/code&gt;, and &lt;code&gt;EI_CLASSIFIER_INTERVAL_MS&lt;/code&gt;, help dictate exactly how often the code takes accelerometer samples and loads them into the &lt;code&gt;buffer&lt;/code&gt;. If you run the code and look at the logs you’ll see the accelerometer samples logged live.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--P7mWGKq5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/374tdep41du2dmnt1n7c.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P7mWGKq5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/374tdep41du2dmnt1n7c.gif" alt="Stream of accelerometer readings from the firmware" width="800" height="356"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the data loaded into a &lt;code&gt;buffer&lt;/code&gt; variable, our firmware’s last task is to send that data to Notehub.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sending the Data
&lt;/h3&gt;

&lt;p&gt;Large accelerometer samples like we’re using in this project require a decent amount of storage, and historically, working with these larger data packets was problematic for the Notecard, as the Notecard is primarily a low-bandwidth and low-power friendly device.&lt;/p&gt;

&lt;p&gt;However, the new Notecard &lt;a href="https://dev.blues.io/notecard/notecard-firmware-updates"&gt;v5.3.1 firmware release&lt;/a&gt; includes a new feature aimed at drastically speeding up data transfer for these types of transfers, and allowing you to upload up to 128kb at a time—which is plenty for our situation.&lt;/p&gt;

&lt;p&gt;The new Notecard APIs are named &lt;code&gt;card.binary&lt;/code&gt;, and the way you use them in Zephyr apps is with the &lt;code&gt;NoteBinaryStoreTransmit&lt;/code&gt; and &lt;code&gt;NoteBinaryStoreReceive&lt;/code&gt; methods. Here’s what that looks like in our sample.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;NoteBinaryStoreTransmit&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE_COBS&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;NoteBinaryStoreTransmit&lt;/code&gt; method loads the buffer into a dedicated area of the Notecard’s memory. Once loaded, you can send the data to your Notecard using the &lt;a href="https://dev.blues.io/notecard/notecard-walkthrough/web-transactions/"&gt;Notecard’s &lt;code&gt;web.post&lt;/code&gt; method&lt;/a&gt; with the &lt;code&gt;binary&lt;/code&gt; flag set to &lt;code&gt;true&lt;/code&gt;. Here’s what that looks like.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;J&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NoteNewRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"web.post"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;JAddStringToObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"route"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ingest"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;JAddStringToObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"binary/octet-stream"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;JAddBoolToObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"binary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;NoteRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point our code is now sending batches of accelerometer readings to Notehub as a binary stream of floating-point values. Our final task is to write an endpoint that can accept that stream, parse out the floating-point values, and send them to Edge Impulse as new training data for our ML model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting the Data to Edge Impulse
&lt;/h3&gt;

&lt;p&gt;In this step we’re going to look at how to create an HTTP server that can accept data from Notehub and forward it to Edge Impulse. The example code I’m providing is written in Node.js, &lt;a href="https://expressjs.com/"&gt;Express&lt;/a&gt;, and JavaScript, as that’s my toolchain of choice, but the server needed for this step can be written in any language and using any framework you’d like.&lt;/p&gt;

&lt;p&gt;As with before, the full source code required to create the server is &lt;a href="https://github.com/tjvantoll/accelerometer-data-ingest"&gt;open source and available on GitHub&lt;/a&gt;, and I’ll be highlighting the most important parts here.&lt;/p&gt;

&lt;p&gt;At the highest level, this endpoint has to listen for POST requests coming in at the root of the server. Here’s how that’s done in Express.&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;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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="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;Once you have the request, you next have to parse out the floating-point accelerometer values coming from Notehub. Specifically, the code has to parse the request, get out 32-bit floating point values one at a time, and put them into an array of readings to use later. Here’s what that code looks like in JavaScript.&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;binaryFromRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;binary&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;dataView&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;DataView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;binaryFromRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;12&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;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dataView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getFloat32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dataView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getFloat32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;4&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="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dataView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getFloat32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&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;push&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;z&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;With the data aggregated, the last step is to get our data to Edge Impulse. For this, Edge Impulse provides an &lt;a href="https://edgeimpulse.readme.io/reference/ingestion-api"&gt;ingestion API&lt;/a&gt; that expects data in a &lt;a href="https://edgeimpulse.readme.io/reference/data-acquisition-format"&gt;given format&lt;/a&gt;, and places a new entry in your model’s training set.&lt;/p&gt;

&lt;p&gt;Here’s what the code to do that in JavaScript looks like.&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;emptySignature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&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;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;protected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;ver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;v1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;alg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;iat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;emptySignature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;device_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;device-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;device_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;LIS2HH12&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;interval_ms&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="na"&gt;sensors&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;accX&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;units&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;m/s2&lt;/span&gt;&lt;span class="dl"&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;accY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;units&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;m/s2&lt;/span&gt;&lt;span class="dl"&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;accZ&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;units&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;m/s2&lt;/span&gt;&lt;span class="dl"&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;values&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="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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://ingestion.edgeimpulse.com/api/training/data&lt;/span&gt;&lt;span class="dl"&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;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-api-key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EDGE_IMPULSE_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-file-name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-label&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&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;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&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;e&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error publishing 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;e&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;Now that you have a server you need to make the server available on the public internet. You can do this via your favorite hosting provider, or, if you’re looking for a quick way to test you can use a service like &lt;a href="https://theboroer.github.io/localtunnel-www/"&gt;localtunnel&lt;/a&gt; or &lt;a href="https://ngrok.com/"&gt;ngrok&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I’m a big fan of ngrok as I’ve found it works well and they have a generous free tier. After &lt;a href="https://ngrok.com/docs/getting-started/"&gt;installing&lt;/a&gt;, you can run ngrok with a simple command from your terminal or command prompt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ngrok http 3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this command makes the provided port (in this case 3000) available on the public internet at the provided URL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--k6nuCBzI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yq0t6sfaefvapybkqw52.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6nuCBzI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yq0t6sfaefvapybkqw52.png" alt="Example usage of ngrok" width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you have this URL your very last step is to &lt;a href="https://dev.blues.io/notecard/notecard-walkthrough/web-transactions"&gt;create a proxy route in Notehub&lt;/a&gt;. The proxy route provides the connection so that Notehub knows to invoke your web server when it receives a &lt;code&gt;web.post&lt;/code&gt; request from the Notecard. Here’s an example of the proxy route you need to create, making sure to provide your own URL for the &lt;strong&gt;URL&lt;/strong&gt; field.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SzIDWMuy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6s91ecwkodf0bvv4q8bh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SzIDWMuy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6s91ecwkodf0bvv4q8bh.png" alt="An example proxy route in Notehub" width="800" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And with that—you’re all set. If you start up your firmware on your device it’ll start collecting accelerometer samples. The firmware will then use the Notecard to send your data to Notehub, which invokes your proxy route, which sends the data to Edge Impulse where you can further refine your ML models.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Y6IND9vK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d5yz2fnzjq5uzhajpgb4.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y6IND9vK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d5yz2fnzjq5uzhajpgb4.gif" alt="A full process of ingesting ML data and posting it to Edge Impulse" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pretty cool!&lt;/p&gt;

&lt;p&gt;The power of this approach is that it can run in any environment that has cellular connectivity. Meaning, you could adapt this code to run on your production hardware, and start collecting training data from devices in real-world conditions.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Exactly&lt;/em&gt; how you’ll want to use this code will likely vary. Maybe you’d want to take a few samples a day at different intervals. Maybe you’ll want to create a &lt;a href="https://dev.blues.io/guides-and-tutorials/notecard-guides/understanding-environment-variables"&gt;Notehub environment variable&lt;/a&gt; and only send readings when that variable is activated. The idea behind this sample is to you how the raw data transfer works, and to serve as inspiration for the code and techniques you may want to incorporate into your own projects.&lt;/p&gt;

&lt;p&gt;And speaking of techniques you may want to incorporate, I have one more fun thing to show you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Updating ML Models
&lt;/h2&gt;

&lt;p&gt;In this article we’re looking at how to implement a robust MLOps process. In the previous section we saw how to gather training data from devices in the field, which allows you to continuously improve your ML models with real data. But that leaves a big problem—after you’ve done the work to build an improved version of your model, how do you get that model back out to your devices in the field?&lt;/p&gt;

&lt;p&gt;The Notecard offers a compelling answer to this question with &lt;a href="https://dev.blues.io/guides-and-tutorials/notecard-guides/notecard-outboard-firmware-update/"&gt;Notecard Outboard Firmware Update&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Notecard Outboard Firmware Update?
&lt;/h3&gt;

&lt;p&gt;Over-the-the firmware updates are table stakes in IoT, but implementing the updates themselves is often a complex and highly risky endeavor. Most firmware-update processes require developers to write code, generally placed within the application itself, or within its RTOS, requiring a special ‘bootloader’ and firmware layout. This requires a high degree of skill, is susceptible to bad updates “bricking” or rendering devices non-functional, and often removes developer choice from the equation.&lt;/p&gt;

&lt;p&gt;Outboard Firmware Update is a Notecard feature that allows device builders to implement OTA firmware updates in their devices without writing any code. Additionally, developers have the freedom of choice, and may select from a large number of microcontrollers (MCUs), programming languages, and real-time operating systems (RTOS), and can even perform updates on ‘native’ applications with no code from Blues and no RTOS at all.&lt;/p&gt;

&lt;p&gt;Outboard Firmware Update is compelling for machine-learning projects like the one we’re discussing in this article, because it allows you update an app’s full ML model, and it allows you to do so remotely, and all without the risk of bricking your devices.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does Notecard Outboard Firmware Update work?
&lt;/h3&gt;

&lt;p&gt;The Notecard’s docs have a &lt;a href="https://dev.blues.io/guides-and-tutorials/notecard-guides/notecard-outboard-firmware-update/#how-it-works"&gt;convenient checklist&lt;/a&gt; for everything you need to do to perform an outboard firmware update. Let’s look at how each item in that list works in this project.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You ensure your hardware is using the required wiring.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Because we’re using a Notecarrier F from the Blues Starter Kit for this project, all wiring between the Notecard and our host (the Blues Swan) is already ready to go. If you’re using other boards, see &lt;a href="https://dev.blues.io/guides-and-tutorials/notecard-guides/notecard-outboard-firmware-update/#required-wiring"&gt;this guide&lt;/a&gt; for details on the required wiring.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You enable Notecard Outboard Firmware Update on your Notecard.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We’re performing the necessary call to make this happen in our Zephyr firmware. Specifically, the sample code performs a &lt;code&gt;card.dfu&lt;/code&gt; request on the Notecard setting &lt;code&gt;"name"&lt;/code&gt; to &lt;code&gt;"stm32"&lt;/code&gt; (because the Swan is an STM32-based MCU), and &lt;code&gt;"on"&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;J&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NoteNewRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"card.dfu"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;JAddStringToObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"stm32"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;JAddBoolToObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"on"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;NoteRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You build your firmware image file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This step requires you to build the binary firmware image file you’d like to update your device with. In a production workflow, this step would occur after you have an updated version of your model ready, have placed the updated model into your application, and have done the necessary testing to push the model out to some, or all, of your devices.&lt;/p&gt;

&lt;p&gt;For local testing, I’d recommend adding a simple logging statement near the top of &lt;code&gt;main()&lt;/code&gt; so you can see the firmware update in action.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;printk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[INFO] version: 2.0&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the app updated you’ll need to &lt;a href="https://dev.blues.io/tools-and-sdks/firmware-libraries/zephyr-sdk/#building-and-running"&gt;build your Zephyr app&lt;/a&gt;, and then grab the built binary in your project from &lt;code&gt;build/zephyr/zephyr.bin&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You upload your firmware on Notehub.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once you have your new binary visit the &lt;strong&gt;Firmware&lt;/strong&gt; section of your Notehub project, click the &lt;strong&gt;Upload firmware&lt;/strong&gt; button, and upload your newly built file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3QYLuXlt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kn603emjp7g83p9vd9ya.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3QYLuXlt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kn603emjp7g83p9vd9ya.png" alt="The firmware listing in Notehub" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this point Notehub has your firmware and is ready to push it out to your devices. To do so, go to your Notehub project’s &lt;strong&gt;Devices&lt;/strong&gt; page, and then select the &lt;strong&gt;Host Firmware&lt;/strong&gt; tab. Here you’ll see all available firmware versions for your host devices, and hitting &lt;strong&gt;Apply&lt;/strong&gt; starts the process of getting the updated firmware on your host.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yKH89rO8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s3a5wvdpshvvcrufuvzl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yKH89rO8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s3a5wvdpshvvcrufuvzl.png" alt="A look at the page in Notehub that allows you to apply host firmware updates" width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Notecard downloads the firmware, verifies it, and performs the update.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once you’ve queued up a firmware update in Notehub, the Notecard detects a new host binary is available on its next sync and downloads the firmware into its own flash storage.&lt;/p&gt;

&lt;p&gt;The Notecard will then perform a RESET on the host microcontroller, which places it into its ROM bootloader. Then, using a microcontroller-specific communications protocol, the Notecard reprograms the various areas in flash as directed by instructions within the firmware image file, verifies them via MD5 hashes, and restarts the MCU.&lt;/p&gt;

&lt;p&gt;The great thing is this all happens automatically, and at the end of the process your device will start running your updated model automatically.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vLMG58SE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zbcv7ee1808icshgsiay.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vLMG58SE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zbcv7ee1808icshgsiay.png" alt="A log of the updated version running on a device" width="800" height="328"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it all together
&lt;/h2&gt;

&lt;p&gt;The big picture is each of the two processes discussed in this article—an easy way to collect ML training data from remote devices, and a workflow to remotely update your models—are both tools that can help you build a more robust MLOps process. The end goal is to avoid shipping your models once and forgetting about them, and to instead establish a process of continuously collecting data, and continuously improving the quality of the models you use for your production apps.&lt;/p&gt;

&lt;p&gt;This article’s samples are reference implementations and are both available on GitHub. You may wish to use their code verbatim, or take the bits and pieces that make sense for your project.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/tjvantoll/mlops-zephyr/"&gt;Zephyr MLOps Sample&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tjvantoll/accelerometer-data-ingest"&gt;Accelerometer Data Ingest&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At Blues we create a lot of real-world TinyML samples. If you found this discussion interesting, you might want to check out our &lt;a href="https://dev.blues.io/accelerators/tag/machine-learning"&gt;ML accelerator projects&lt;/a&gt;, and the &lt;a href="https://dev.blues.io/community/projects/machine-learning"&gt;many ML projects our community have built&lt;/a&gt;. And if you have any questions about this post, or want to discuss anything further, &lt;a href="https://discuss.blues.io/"&gt;check out our forum&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>zephyr</category>
    </item>
    <item>
      <title>When to Use Tadiran (Li-SOCl2) vs Lithium Polymer (LiPo) Batteries in IoT Applications</title>
      <dc:creator>Rob Lauer</dc:creator>
      <pubDate>Mon, 10 Jul 2023 17:52:26 +0000</pubDate>
      <link>https://forem.com/blues/when-to-use-tadiran-li-socl2-vs-lithium-polymer-lipo-batteries-in-iot-applications-3da7</link>
      <guid>https://forem.com/blues/when-to-use-tadiran-li-socl2-vs-lithium-polymer-lipo-batteries-in-iot-applications-3da7</guid>
      <description>&lt;p&gt;As a product designer or embedded engineer, selecting the appropriate battery technology is crucial for ensuring the best performance/longevity combination to power your embedded device or IoT application.&lt;/p&gt;

&lt;p&gt;In this article, we will perform a high-level comparison of Tadiran (i.e. Li-SOCl2) batteries and Lithium Polymer (LiPo) batteries, helping you to decide which is best suited for your deployment scenario.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Specifically, we will answer the following questions:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What are Tadiran Li-SOCl2 batteries?&lt;/li&gt;
&lt;li&gt;What are LiPo batteries?&lt;/li&gt;
&lt;li&gt;What are the optimal temperature ranges for Tadiran and LiPo batteries?&lt;/li&gt;
&lt;li&gt;What are the self-discharge rates of Tadiran and LiPo batteries?&lt;/li&gt;
&lt;li&gt;What are some valid use cases for Tadiran vs LiPo batteries?&lt;/li&gt;
&lt;li&gt;And why aren't we talking about alkaline batteries!?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is a Tadiran Battery?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://tadiranbat.com/" rel="noopener noreferrer"&gt;Tadiran&lt;/a&gt; is the brand name of a type of Lithium Thionyl Chloride (Li-SOCl2) battery. Tadiran Li-SOCl2 batteries are known for their &lt;strong&gt;long shelf-life&lt;/strong&gt; (up to 40 years in optimal conditions), relatively &lt;strong&gt;high voltage&lt;/strong&gt; (3.6V), &lt;strong&gt;low self-discharge rates&lt;/strong&gt; (~0.7% per year), and the ability to operate in &lt;strong&gt;extreme temperature ranges&lt;/strong&gt; (-80°C to 125°C).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Yes, Tadiran likes to acknowledge these "extreme" capabilities in their marketing materials!&lt;/em&gt;&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;NOTE: While known for their Li-SOCl2 batteries, Tadiran does offer Li-ion batteries as well. However, for the purposes of this article we are going to focus only on their Li-SOCl2 batteries.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While Li-SOCl2 Tadirans are &lt;strong&gt;not rechargeable&lt;/strong&gt;, they are often used in low-power, long-life applications such as remote monitoring systems, smart meters, asset tracking, and IoT deployments in relatively harsh environmental conditions.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do Li-SOCl2 Tadiran Batteries Work?
&lt;/h3&gt;

&lt;p&gt;Li-SOCl2 battery technology takes advantage of the high reactivity of lithium, the lightest of all metals, and a highly electronegative chlorinated compound, thionyl chloride (SOCl2).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5vxn7xcsf9b0r854op0o.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5vxn7xcsf9b0r854op0o.jpg" alt="li-socl2 battery structure" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Two types of Li-SOCl2 battery structures. Image credit &lt;a href="http://www.gebc-energy.com/" rel="noopener noreferrer"&gt;gebc-energy.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In these batteries, lithium serves as the anode, and thionyl chloride serves as the cathode active material. When the battery discharges, the lithium is oxidized, and the thionyl chloride is reduced. The resulting reaction generates a high nominal voltage (again, typically around 3.6V).&lt;/p&gt;

&lt;h3&gt;
  
  
  Pros and Cons of Li-SOCl2 Tadiran Batteries
&lt;/h3&gt;

&lt;p&gt;Li-SOCl2 Tadirans shine in scenarios where a long shelf-life, low self-discharge rate, high pulses, and ability to function in wildly varying temperature ranges are key requirements. Their high cell voltage also may allow &lt;strong&gt;fewer batteries to be used in a series&lt;/strong&gt;, potentially saving space and reducing complexity in the design of device enclosures.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1d000rf0t2fa63x8aimr.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1d000rf0t2fa63x8aimr.jpg" alt="tadiran plusespulse series" width="267" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Tadiran PlusesPulse series of Li-SOCl2 batteries.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;However, Li-SOCl2 batteries are not without their limitations. Since they are not rechargeable, once they are depleted they must be manually replaced. Therefore, they aren't suitable for high-power applications that discharge rapidly, nor in remote scenarios where solar/wind/hydro power is readily available for recharging purposes. Likewise, Tadirans are not inexpensive! Li-SOCl2 Tadiran batteries often cost considerably more than LiPo batteries when comparing $ per amp hour (though the total cost of ownership may vary, depending on the use case).&lt;/p&gt;

&lt;h3&gt;
  
  
  Common IoT Applications for Li-SOCl2 Tadiran Batteries
&lt;/h3&gt;

&lt;p&gt;Thanks to their unique properties, Li-SOCl2 Tadiran batteries are often found in a variety of IoT deployments, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Utility Metering Systems:&lt;/strong&gt; Gas, water, and electricity metering systems often use Tadirans due to their long operational lifespan and reliability.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Emergency Devices:&lt;/strong&gt; Devices like fire/smoke detectors and alarm systems often use these batteries due to their long shelf-life.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Edge Computing Devices:&lt;/strong&gt; Devices deployed in remote or difficult-to-access locations may want to use Tadrian batteries due to their high energy density and long operational life.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When it comes to integrating with the &lt;a href="https://blues.io/products/notecard/" rel="noopener noreferrer"&gt;Blues Notecard&lt;/a&gt;, &lt;a href="https://shop.blues.io/collections/accessories/products/tadiran-lithium-battery" rel="noopener noreferrer"&gt;this specific Tadiran&lt;/a&gt; is ideal for the high current pulses the Notecard requires when used in a region that requires access to a GSM network:&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;The Blues version of the Tadiran TLP-93111 battery.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; This battery is based on the Tadiran TLP-93111 and was modified for Blues to include a JST connector so that you can easily use this battery with any of our &lt;a href="https://blues.io/products/notecarrier/" rel="noopener noreferrer"&gt;Notecarriers&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What is a Lithium Polymer (LiPo) Battery?
&lt;/h2&gt;

&lt;p&gt;Lithium Polymer (LiPo) batteries are a type of rechargeable battery that utilize a &lt;em&gt;polymer electrolyte&lt;/em&gt; instead of a &lt;em&gt;liquid electrolyte&lt;/em&gt; found in other lithium-ion batteries. This unique composition allows LiPo batteries to be &lt;strong&gt;lightweight and flexible&lt;/strong&gt;, making them a popular choice for low-cost powering of embedded/IoT devices, consumer electronics, and devices that require custom battery sizes.&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;An example LiPo battery.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How do LiPo Batteries Work?
&lt;/h3&gt;

&lt;p&gt;LiPo batteries work on the same fundamental principles as other lithium-ion batteries. They move lithium ions from the negative electrode (anode) to the positive electrode (cathode) during discharge - and move them back while charging. The anode in a LiPo battery is typically made of a type of lithium compound (e.g. lithium cobalt oxide), which can readily give up lithium ions.&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;LiPo battery structure. Image credit &lt;a href="https://www.dnkpower.com/lithium-polymer-battery-guide/" rel="noopener noreferrer"&gt;dnkpower.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pros and Cons of LiPo Batteries
&lt;/h3&gt;

&lt;p&gt;Like other lithium-ion batteries, LiPo batteries have a &lt;strong&gt;high energy density&lt;/strong&gt;, meaning they can store a large amount of energy for their size and weight. Unlike Tadiran batteries, LiPo batteries &lt;strong&gt;can be recharged hundreds of times&lt;/strong&gt;, making them a popular choice for many electronics devices. LiPo batteries are also &lt;strong&gt;lightweight&lt;/strong&gt;, can deliver power quickly with high discharge rates, and as mentioned are very &lt;strong&gt;flexible in their shape and size&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0sfzve7gjbm6cnsou8s8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0sfzve7gjbm6cnsou8s8.jpg" alt="unique lipo battery shapes" width="735" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;LiPo batteries can be manufactured in virtually any shape or size. Image credit &lt;a href="https://www.lipolbattery.com/Novel-LiPo-Battery.html" rel="noopener noreferrer"&gt;lipolbattery.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;LiPo batteries are no silver bullet though - they do have some disadvantages! Care must be taken not to over-charge or over-discharge them, as this can significantly reduce their lifespan, or even cause them to catch fire. But it's important to note that most devices using LiPo batteries include safety circuits to prevent these issues from happening.&lt;/p&gt;

&lt;p&gt;Finally, while LiPo batteries can be recharged many times, each charge and discharge cycle &lt;em&gt;slightly&lt;/em&gt; reduces their capacity. After numerous cycles, the maximum capacity will be significantly reduced, at which point the battery may need to be replaced.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common IoT Applications for LiPo Batteries
&lt;/h3&gt;

&lt;p&gt;While there is plenty of overlap between LiPo and Li-SOCl2 Tadiran batteries in terms of use cases, lower-power and more cost-sensitive applications tend to use LiPo battery technology:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Remote Monitoring Systems:&lt;/strong&gt; Remote systems for weather monitoring, wildlife tracking, and other environmental sensors often use LiPo batteries for their longevity, ability to be recharged, and operation in somewhat unpredictable conditions (but less "extreme" conditions than a Tadiran).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Smart Agriculture:&lt;/strong&gt; In smart farming, sensors and devices are used for monitoring soil moisture, weather conditions, and livestock tracking. These devices often use LiPo batteries because they can provide low-cost reliable operation for an extended period.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Backing Up Home Automation Devices:&lt;/strong&gt; Devices like smart locks, security cameras, and other IoT home automation systems are often line-powered, but also utilize LiPo batteries as a backup in case of a power outage.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  So...Which Should I Choose?
&lt;/h2&gt;

&lt;p&gt;The answer, as you may expect, is: "it depends on your use case!". Which of the following are most important for your deployment scenario?&lt;/p&gt;

&lt;h3&gt;
  
  
  Operating Temperature Range
&lt;/h3&gt;

&lt;p&gt;When comparing the operating temperature ranges of Tadiran and LiPo batteries, &lt;strong&gt;Tadiran batteries have a clear advantage&lt;/strong&gt; of -80°C to 125°C. Their Li-SOCl2 chemistry enables them to perform well in extreme temperatures, making them suitable for applications in harsh environments.&lt;/p&gt;

&lt;p&gt;While LiPo batteries have a more limited temperature range (-20°C to +60°C) compared to Tadiran batteries, they still offer a wide operating range suitable for many implementations.&lt;/p&gt;

&lt;p&gt;✅ Tadiran Li-SOCl2 ⛔️ LiPo&lt;/p&gt;

&lt;h3&gt;
  
  
  Longevity (Self-Discharge Rate)
&lt;/h3&gt;

&lt;p&gt;The self-discharge rate of a battery refers to the percentage of charge it loses over time when not in use. This is an important factor to consider, especially for applications requiring long periods of inactivity. &lt;strong&gt;Tadiran batteries offer an extremely low self-discharge rate&lt;/strong&gt; (~0.7% per year) while LiPo batteries self-discharge at a rate of up to 5% per month when not in use.&lt;/p&gt;

&lt;p&gt;✅ Tadiran Li-SOCl2 ⛔️ LiPo&lt;/p&gt;

&lt;h3&gt;
  
  
  Flexibility
&lt;/h3&gt;

&lt;p&gt;The unique composition of LiPo batteries allows them to be not only lightweight (which is critical for many solutions) but also flexible and able to be shaped into virtually any size or shape. Tadirans, on the other hand, are limited to very specific shapes (though considering their higher voltages, may consume less physical space in an enclosure depending on the power requirements).&lt;/p&gt;

&lt;p&gt;⛔️ Tadiran Li-SOCl2 ✅ LiPo&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost
&lt;/h3&gt;

&lt;p&gt;Comparing the cost of Tadiran Li-SOCl2 batteries and LiPo batteries can be complex because the true cost of a battery depends on many different factors. You need to factor in its capacity along with recharge cycles/total power delivered, and the specifics by manufacturer/model (not to mention quantity purchased!).&lt;/p&gt;

&lt;p&gt;In general, Tadiran Li-SOCl2 batteries are considered "specialty batteries" that aren't as widely available. They tend to be more expensive than LiPo batteries, as they possess the unique properties noted previously. When comparing total energy delivered between Tadiran and LiPo batteries, &lt;strong&gt;LiPo tends to come out ahead when it comes to total cost of ownership&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;WARNING:&lt;/strong&gt; Please note that the specifics can vary, and for an accurate and up-to-date comparison, you would need to look at the prices from various suppliers or directly from the manufacturers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;⛔️ Tadiran Li-SOCl2 ✅ LiPo&lt;/p&gt;

&lt;h2&gt;
  
  
  What about Alkaline Batteries?
&lt;/h2&gt;

&lt;p&gt;Generally speaking, alkalines are the "worst of both worlds" in comparison with LiPo and Tadiran batteries. Aside from their low cost and wide availability, alkalines have a very low energy density (when compared to LiPo and Tadiran). This primarily impacts longevity, as devices won't last as long under comparable usage conditions. Alkalines also perform more poorly at high current-drain rates, aren't rechargeable, and their capacity decreases dramatically in low temperatures.&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;The venerable Duracell AA alkaline battery. Image credit &lt;a href="https://www.duracell.com/" rel="noopener noreferrer"&gt;Duracell&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;When choosing between Tadiran Li-SOCl2 and LiPo batteries for an embedded device or IoT application, it is crucial to consider factors such as energy density, operating temperature range, self-discharge rate, and the specific requirements of your application.&lt;/p&gt;

&lt;p&gt;Tadiran batteries excel in low-power, long-life applications, and in extreme temperature environments, while LiPo batteries are ideal for lower-cost deployments, scenarios where they may be recharged, and devices requiring custom battery shapes.&lt;/p&gt;

&lt;p&gt;If you need help determining the best battery tech for your individual solution, feel free to reach out via the &lt;a href="https://discuss.blues.io/" rel="noopener noreferrer"&gt;Blues community forum&lt;/a&gt;. 🔋&lt;/p&gt;

</description>
      <category>battery</category>
      <category>lipo</category>
      <category>power</category>
      <category>iot</category>
    </item>
    <item>
      <title>Use Cell Tower and Wi-Fi Triangulation to Achieve Pin-Point Locations, without GPS</title>
      <dc:creator>Rob Lauer</dc:creator>
      <pubDate>Thu, 04 Aug 2022 19:00:14 +0000</pubDate>
      <link>https://forem.com/blues/use-cell-tower-and-wi-fi-triangulation-to-achieve-pin-point-locations-without-gps-4ko5</link>
      <guid>https://forem.com/blues/use-cell-tower-and-wi-fi-triangulation-to-achieve-pin-point-locations-without-gps-4ko5</guid>
      <description>&lt;p&gt;Many IoT solutions include one basic requirement: to know precisely &lt;em&gt;where&lt;/em&gt; they are in the world.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzq0z4r3ztyje6u08wmtj.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzq0z4r3ztyje6u08wmtj.gif" alt="where in the world am i" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While ascertaining location is usually handled with a GNSS/GPS module, there are scenarios where GPS is not reliable or practical. In fact, I'll give you three exact scenarios:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Indoor deployments where getting a GPS satellite fix is impossible.&lt;/li&gt;
&lt;li&gt;Use of a physical enclosure that prevents a GPS fix.&lt;/li&gt;
&lt;li&gt;Low-power solutions that can't afford the power drain of GPS.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here at Blues Wireless, when we talk about gathering lat/lon coordinates, we usually refer people to the onboard GPS capabilities of the &lt;a href="https://blues.io/products/notecard/" rel="noopener noreferrer"&gt;Cellular Notecard&lt;/a&gt;, which we highly recommend as the first (and often only) way of ascertaining location.&lt;/p&gt;

&lt;p&gt;However, today I want to look more closely at a lesser-known capability of the Notecard: &lt;a href="https://dev.blues.io/notecard/notecard-walkthrough/time-and-location-requests/#using-cell-tower-and-wi-fi-triangulation" rel="noopener noreferrer"&gt;Cell Tower and Wi-Fi Triangulation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Triangulation
&lt;/h2&gt;

&lt;p&gt;If you've taken basic trigonometry or geometry courses, you already know that triangulation is the process of determining a location by forming triangles to the point of interest, from a series of other known points.&lt;/p&gt;

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

&lt;p&gt;The Notecard provides built-in capabilities of utilizing triangulation in a few different ways: cell tower triangulation, Wi-Fi triangulation, or both!&lt;/p&gt;

&lt;h2&gt;
  
  
  Cell Tower Location
&lt;/h2&gt;

&lt;p&gt;If you're experienced with the Cellular Notecard, you've likely noticed the &lt;code&gt;tower_&lt;/code&gt; parameters that show up in Notehub events:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"tower_when": 1656000634,
"tower_lat": 43.074087500000005,
"tower_lon": -89.44282812499999,
"tower_country": "US",
"tower_location": "Shorewood Hills WI",
"tower_timezone": "America/Chicago",
"tower_id": "310,410,17169,77315594",
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;tower_lat&lt;/code&gt; and &lt;code&gt;tower_lon&lt;/code&gt; values are an approximate location of the &lt;strong&gt;single cell tower&lt;/strong&gt; this Notecard was connected to at the given &lt;code&gt;tower_when&lt;/code&gt; timestamp. While this is a nice (rough) location, it's not useful on its own when trying to determine the precise location of a device.&lt;/p&gt;

&lt;p&gt;For example, here is the approximate position of the cell tower used in a recent request, relative to my location:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnznr85vp8gn0lcfgznog.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnznr85vp8gn0lcfgznog.png" alt="single cell tower location with notecard" width="800" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Cell Tower Triangulation
&lt;/h2&gt;

&lt;p&gt;In scenarios where GPS is unusable, performing &lt;strong&gt;cell tower triangulation&lt;/strong&gt; with the Notecard can provide a massive improvement, thanks to the magic of the &lt;a href="https://dev.blues.io/reference/notecard-api/card-requests/#card-triangulate" rel="noopener noreferrer"&gt;card.triangulate&lt;/a&gt; API.&lt;/p&gt;

&lt;p&gt;To enable cell tower triangulation on a Cellular Notecard, simply issue this request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "req": "card.triangulate",
  "mode": "cell"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next time the Notecard establishes a &lt;em&gt;new session&lt;/em&gt; with Notehub, information about every &lt;em&gt;visible&lt;/em&gt; cell tower will be uploaded and processed. A new set of location data will appear in every Notehub event, prepended with &lt;code&gt;tri_&lt;/code&gt;, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"tri_when": 1656011112,
"tri_lat": 43.07113895,
"tri_lon": -89.43272533,
"tri_location": "Shorewood Hills WI",
"tri_country": "US",
"tri_timezone": "America/Chicago",
"tri_points": 16,
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the updated location estimate, based on cell tower triangulation only:&lt;/p&gt;

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

&lt;p&gt;As you can see, the actual location has improved significantly, and will only improve as more cell towers are visible.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; It's important to know that cell tower triangulation comes with a non-trivial power penalty because the modem scan for nearby cell towers can take almost as long as an entire sync. Triangulation also only occurs during a connection to Notehub and there is no way to "re-triangulate" during a &lt;code&gt;continuous&lt;/code&gt; Notehub session.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Wi-Fi Triangulation
&lt;/h2&gt;

&lt;p&gt;If your host MCU has an onboard Wi-Fi module (like the ESP32), you can perform a Wi-Fi access point (AP) scan and send this data to the Notecard to perform Wi-Fi triangulation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; Wi-Fi triangulation is an experimental, technical preview feature from Blues Wireless that is free &lt;br&gt;
for use today, but may use &lt;a href="https://blues.io/pricing/" rel="noopener noreferrer"&gt;Consumption Credits&lt;/a&gt; in &lt;br&gt;
the future.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The format of the &lt;code&gt;card.triangulate&lt;/code&gt; call is a little different when using Wi-Fi triangulation. Note that you can use &lt;code&gt;cell&lt;/code&gt;, &lt;code&gt;wifi&lt;/code&gt;, or both with &lt;code&gt;wifi,cell&lt;/code&gt; when enabling triangulation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "req": "card.triangulate",
  "mode": "wifi",
  "text": "+CWLAP:(4,\"Blues\",-51,\"74:ac:b9:12:12:f8\",1)\n+CWLAP:(3,\"AAAA-62DD\",-70,\"6c:55:e8:91:62:e1\",11)\n+CWLAP:(4,\"Blues\",-81,\"74:ac:b9:11:12:23\",1)\n+CWLAP:(4,\"Blues\",-82,\"74:ac:a9:12:19:48\",11)\n+CWLAP:(4,\"Free Parking\",-83,\"02:18:4a:11:60:31\",6)\n+CWLAP:(5,\"GO\",-84,\"01:13:6a:13:90:30\",6)\n+CWLAP:(4,\"AAAA-5C62-2.4\",-85,\"d8:97:ba:7b:fd:60\",1)\n+CWLAP:(3,\"DIRECT-a5-HP MLP50\",-86,\"fa:da:0c:1b:16:a5\",6)\n+CWLAP:(3,\"DIRECT-c6-HP M182 LaserJet\",-88,\"da:12:65:44:31:c6\",6)\n\n"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's that scary-looking &lt;code&gt;text&lt;/code&gt; field? It's a newline-terminated list of Wi-Fi access points that follows a pattern similar to the &lt;a href="https://docs.espressif.com/projects/esp-at/en/latest/esp32/AT_Command_Set/Wi-Fi_AT_Commands.html#id17" rel="noopener noreferrer"&gt;ESP32's AT+CWLAP command output&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deriving Access Point Data with Arduino
&lt;/h3&gt;

&lt;p&gt;If you're comfortable issuing AT commands to your host MCU, you can send &lt;code&gt;AT+CWLAP&lt;/code&gt; and format the response per the above requirements. However, not many of us are so cozy with AT syntax, which is why we provide the &lt;br&gt;
&lt;a href="https://github.com/blues/notecard-aux-wifi" rel="noopener noreferrer"&gt;Notecard Auxiliary Wi-Fi Arduino library&lt;/a&gt; &lt;br&gt;
for an easier way of programmatically pulling a list of Wi-Fi access points with &lt;br&gt;
your Wi-Fi enabled host MCU, and sending them to the Notecard in one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Update Notecard Triangulation Data
aux_wifi.updateTriangulationData();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Deriving Access Point Data with CircuitPython
&lt;/h3&gt;

&lt;p&gt;Developing with &lt;a href="https://circuitpython.org/" rel="noopener noreferrer"&gt;CircuitPython&lt;/a&gt;? Here is an example function that uses the built-in &lt;code&gt;wifi&lt;/code&gt; library to gather the same WAP data when used with an ESP32 MCU:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import wifi
import binascii

def get_wifi_access_points():
    """ returns a set of visible access points, corresponds to esp32 AT+CWLAP format """
    # https://docs.espressif.com/projects/esp-at/en/latest/esp32/AT_Command_Set/Wi-Fi_AT_Commands.html#cmd-lap

    all_wifi_aps = ""

    for network in wifi.radio.start_scanning_networks():

        wifi_ap = "+CWLAP:("

        if wifi.AuthMode.ENTERPRISE in network.authmode:
            wifi_ap += "5"
        elif wifi.AuthMode.PSK in network.authmode:
            wifi_ap += "6"
        elif wifi.AuthMode.WPA3 in network.authmode:
            wifi_ap += "6"
        elif wifi.AuthMode.WPA2 in network.authmode:
            wifi_ap += "3"
        elif wifi.AuthMode.WPA in network.authmode:
            wifi_ap += "2"
        elif wifi.AuthMode.WEP in network.authmode:
            wifi_ap += "1"
        else:
            wifi_ap += "0"

        bssid = binascii.hexlify(network.bssid).decode("ascii")
        bssid = ':'.join(bssid[i:i+2] for i in range(0,12,2))

        wifi_ap = wifi_ap + ",\"" + str(network.ssid) + "\"," + str(network.rssi) + ",\"" + bssid + "\"," + str(network.channel) + ")\n"

        all_wifi_aps += wifi_ap

    wifi.radio.stop_scanning_networks()

    return all_wifi_aps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Wi-Fi Triangulation in Action
&lt;/h3&gt;

&lt;p&gt;How different are my results when using Wi-Fi triangulation? Well the only access point my ESP32 was able to find was my own router. However, even with just that one AP, here are the results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"tri_when": 1656011112,
"tri_lat": 43.07113895,
"tri_lon": -89.43272533,
"tri_location": "Shorewood Hills WI",
"tri_country": "US",
"tri_timezone": "America/Chicago",
"tri_points": 16,
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which becomes amazingly accurate when displayed on a map!&lt;/p&gt;

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

&lt;p&gt;Wi-Fi triangulation becomes &lt;strong&gt;even more valuable&lt;/strong&gt; when you realize the power requirements are minimal, and it adds only 1-2 seconds to the sync time with Notehub.&lt;/p&gt;

&lt;p&gt;Be sure to consult the developer documentation on &lt;a href="https://dev.blues.io/notecard/notecard-walkthrough/time-and-location-requests/#using-cell-tower-and-wi-fi-triangulation" rel="noopener noreferrer"&gt;Cell Tower and Wi-Fi Triangulation&lt;/a&gt; as there are additional settings in Notehub that you may want to change, depending on how you use triangulation in your solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Triangulation with Wi-Fi Notecard
&lt;/h2&gt;

&lt;p&gt;Up until now, all of these instructions have revolved around using the Cellular Notecard. What about if you're using the &lt;a href="https://blues.io/products/wifi-notecard/" rel="noopener noreferrer"&gt;Wi-Fi Notecard&lt;/a&gt; (which doesn't have an onboard GNSS/GPS module)?&lt;/p&gt;

&lt;p&gt;Luckily enough, Wi-Fi triangulation is enabled on the Wi-Fi Notecard by default, no configuration changes required!&lt;/p&gt;

&lt;p&gt;After setting up a Wi-Fi Notecard, here were the results in a given event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"tri_when": 47,
"tri_lat": 43.07121864,
"tri_lon": -89.43263674,
"tri_location": "Shorewood Hills WI",
"tri_country": "US",
"tri_timezone": "America/Chicago",
"tri_points": 3,
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which, again, when plotted on a map, comes shockingly close to landing on my location:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;While GPS is the de facto standard for identifying the precise location of an IoT device, and should always be the first option when gathering location data, the Notecard's triangulation capabilities provide options when GPS isn't available.&lt;/p&gt;

&lt;p&gt;Happy Mapping with the Notecard and Notehub! 📍🗺&lt;/p&gt;

</description>
      <category>gps</category>
      <category>cellular</category>
      <category>iot</category>
    </item>
    <item>
      <title>Easiest Way to Add Cellular to an ESP32 IoT Project</title>
      <dc:creator>Rob Lauer</dc:creator>
      <pubDate>Mon, 02 May 2022 18:45:55 +0000</pubDate>
      <link>https://forem.com/blues/easiest-way-to-add-cellular-to-an-esp32-iot-project-10nm</link>
      <guid>https://forem.com/blues/easiest-way-to-add-cellular-to-an-esp32-iot-project-10nm</guid>
      <description>&lt;p&gt;I've come to appreciate the &lt;a href="https://en.wikipedia.org/wiki/ESP32" rel="noopener noreferrer"&gt;ESP32&lt;/a&gt; line of microcontrollers from &lt;a href="https://www.espressif.com/" rel="noopener noreferrer"&gt;Espressif Systems&lt;/a&gt;, mostly due to a combination of low-cost, low-power, yet solid MCU performance. In particular, the ESP32-based &lt;a href="https://www.adafruit.com/product/3405" rel="noopener noreferrer"&gt;HUZZAH32 Feather from Adafruit&lt;/a&gt; is a board you'll routinely see in some &lt;a href="https://www.hackster.io/blues-wireless" rel="noopener noreferrer"&gt;tutorials we post on Hackster&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhyiirqff1a6pxe6yfbh0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhyiirqff1a6pxe6yfbh0.jpg" alt="adafruit huzzah 32 feather" width="800" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Adafruit HUZZAH32&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;While Wi-Fi and Bluetooth are integrated into some ESP32 MCUs, adding cellular to the mix broadens the deployment options of any IoT project. Having a &lt;a href="https://blues.io/blog/network-connectivity/?utm_source=devto" rel="noopener noreferrer"&gt;combination of connectivity options&lt;/a&gt; also enables you to use Wi-Fi when available, but fall back on cellular should Wi-Fi be unavailable or the physical location require it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Developer-Friendly Cellular with the Notecard
&lt;/h2&gt;

&lt;p&gt;The key to success with cellular IoT connectivity on the ESP32 is a secure and reliable System-on-Module, the &lt;a href="https://blues.io/products/notecard/?utm_source=devto" rel="noopener noreferrer"&gt;Blues Wireless Notecard&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;The Notecard is a 30mm x 35mm device-to-cloud data pump. With the included cellular and GPS capabilities (and a &lt;a href="https://blues.io/products/wifi-notecard/?utm_source=devto" rel="noopener noreferrer"&gt;Wi-Fi option&lt;/a&gt;), the Notecard is an easy choice for securely syncing data with the cloud over a variety of global cellular protocols (specifically Cat-1, LTE-M, and NB-IoT).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And the cost?&lt;/strong&gt; The Notecard comes &lt;em&gt;prepaid&lt;/em&gt; with 10 years of &lt;a href="https://dev.blues.io/hardware/notecard-datasheet/note-nbgl-500/?utm_source=devto#cellular-service" rel="noopener noreferrer"&gt;global service&lt;/a&gt; and 500MB of data, starting at just $49.&lt;/p&gt;

&lt;p&gt;But...what about the M.2 edge connector on the Notecard? How does that connect to an ESP32 microcontroller?&lt;/p&gt;

&lt;p&gt;Probably not like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F52du2h1xex87c9iga6o6.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F52du2h1xex87c9iga6o6.jpg" alt="not the way to connect notecard and esp32 huzzah feather" width="800" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Easy Integration Options
&lt;/h2&gt;

&lt;p&gt;Let me introduce you to the &lt;a href="https://blues.io/products/notecarrier/?utm_source=devto" rel="noopener noreferrer"&gt;Blues Wireless Notecarriers&lt;/a&gt;. These are development boards that make it dead simple to &lt;strong&gt;connect virtually any MCU or SBC to a Notecard&lt;/strong&gt;. You place the Notecard into the provided M.2 slot and the Notecarrier exposes all of the pins on the Notecard to the MCU.&lt;/p&gt;

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

&lt;p&gt;Now it gets even easier if you're using a Feather-based ESP32 (like I often do). The &lt;a href="https://blues.io/products/notecarrier/notecarrier-af/?utm_source=devto" rel="noopener noreferrer"&gt;Notecarrier-AF&lt;/a&gt; (part of the &lt;a href="https://shop.blues.io/products/feather-starter-kit?utm_source=devto" rel="noopener noreferrer"&gt;Feather Starter kit&lt;/a&gt;) has a Feather-compatible socket so you can insert your MCU directly onto the board, for wire-free connectivity.&lt;/p&gt;

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

&lt;p&gt;The Notecarrier-AF also includes onboard antennas, two Grove I2C ports, and JST connectors for solar and/or LiPo batteries.&lt;/p&gt;

&lt;p&gt;Notecard + Notecarrier-AF + ESP32 = ❤️&lt;/p&gt;

&lt;h2&gt;
  
  
  From Device To Cloud, Securely
&lt;/h2&gt;

&lt;p&gt;A key component of the Notecard's security architecture is that it doesn't live on the public Internet. &lt;strong&gt;The Notecard doesn't have a publicly-accessible IP address.&lt;/strong&gt; You must use a proxy, accessed via private VPN tunnels, to communicate between the Notecard and your cloud application.&lt;/p&gt;

&lt;p&gt;This is where the Blues Wireless cloud service &lt;a href="https://blues.io/services/?utm_source=devto" rel="noopener noreferrer"&gt;Notehub&lt;/a&gt; comes into play.&lt;/p&gt;

&lt;p&gt;Notehub is a thin cloud service that securely (over TLS) receives, processes, and &lt;strong&gt;routes data to your cloud endpoint of choice&lt;/strong&gt;. This could be a big cloud like AWS, Azure, or Google Cloud - or any IoT-optimized platform like Losant, Datacake, or Ubidots. Your own self-hosted custom RESTful API is fine as well!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcr6gw1zi1rd9w2868r18.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcr6gw1zi1rd9w2868r18.jpg" alt="blues wireless notehub and notecard data flow" width="800" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As an added bonus, Notehub provides OTA firmware update capabilities along with full device fleet management and &lt;a href="https://dev.blues.io/notecard/notecard-guides/understanding-environment-variables/?utm_source=devto" rel="noopener noreferrer"&gt;cloud-based environment variables&lt;/a&gt; for easily sharing data across fleets of Notecards.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Use Notecard, and When Not
&lt;/h2&gt;

&lt;p&gt;It's important to note that the cellular Notecard is a &lt;strong&gt;low-power, low-bandwidth, and high-latency&lt;/strong&gt; device. Its design center is focused on sending small packets of data (e.g. accumulated sensor readings, generated ML inferences, and the like) on an occasional cadence to the cloud.&lt;/p&gt;

&lt;p&gt;👍 Scenarios when the Notecard makes sense:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Any low-bandwidth transfer of data over Cat-1, LTE-M, or NB-IoT cellular&lt;/li&gt;
&lt;li&gt;Low-power edge computing deployments (agriculture, smart meters, environmental monitoring, and so on)&lt;/li&gt;
&lt;li&gt;High-latency remote control solutions&lt;/li&gt;
&lt;li&gt;When secure and encrypted communications are critical&lt;/li&gt;
&lt;li&gt;When turnkey cloud integrations are desired&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;👎 Scenarios when the Notecard doesn't quite work:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;As an "always connected" drop-in replacement for Wi-Fi&lt;/li&gt;
&lt;li&gt;When you need sub-millisecond latency&lt;/li&gt;
&lt;li&gt;Streaming HD video or high res images&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Ready to See How it All Works?
&lt;/h2&gt;

&lt;p&gt;With a &lt;a href="https://shop.blues.io/products/feather-starter-kit?utm_source=devto" rel="noopener noreferrer"&gt;Blues Wireless Feather Starter Kit for ESP32&lt;/a&gt; you can follow the remainder of this blog post for a quick and easy getting started experience.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Be sure to &lt;a href="https://dev.blues.io/?utm_source=devto" rel="noopener noreferrer"&gt;browse our full developer site&lt;/a&gt; for Notecard datasheets and additional technical information.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After creating a free account at &lt;a href="https://notehub.io/" rel="noopener noreferrer"&gt;Notehub.io&lt;/a&gt; and initializing your first project, copy the provided unique &lt;a href="https://dev.blues.io/reference/glossary/?utm_source=devto#productuid" rel="noopener noreferrer"&gt;ProductUID&lt;/a&gt; from the project and save it for the next step.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2qf81ivuby7zwcuav6ba.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2qf81ivuby7zwcuav6ba.png" alt="getting your productuid from notehub" width="736" height="578"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Programming ESP32 Firmware
&lt;/h2&gt;

&lt;p&gt;The ESP32 supports commonly used languages like Arduino/C and MicroPython. While I'm an avid Python lover, most ESP32 developers are likely writing Arduino or C/C++, so we'll stick with that path for now.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://dev.blues.io/reference/notecard-api/introduction/?utm_source=devto" rel="noopener noreferrer"&gt;API that powers the Notecard&lt;/a&gt; is completely JSON-based. So technically speaking, it doesn't matter what programming language you use. We provide SDKs for &lt;a href="https://dev.blues.io/tools-and-sdks/?utm_source=devto" rel="noopener noreferrer"&gt;Arduino, Go, C/C++, and Python&lt;/a&gt; and our community has built SDKs for &lt;a href="https://blues.io/blog/dot-net-developer-iot-too/?utm_source=devto" rel="noopener noreferrer"&gt;.NET&lt;/a&gt; and &lt;a href="https://github.com/gauteh/notecard-rs" rel="noopener noreferrer"&gt;Rust&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Whichever route you take, open up your preferred IDE and paste in this basic sketch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Include the Arduino library for the Notecard
#include &amp;lt;Notecard.h&amp;gt;
#define productUID "com.your-company.your-name:your_project"

Notecard notecard;

void setup() {
  delay(2500);
  Serial.begin(115200);
  notecard.begin();

  J *req = notecard.newRequest("hub.set");
  JAddStringToObject(req, "product", productUID);
  JAddStringToObject(req, "mode", "continuous");
  notecard.sendRequest(req);
}

void loop() {
  J *req = notecard.newRequest("note.add");
  if (req != NULL) {
    JAddStringToObject(req, "file", "sensors.qo");
    JAddBoolToObject(req, "sync", true);

    J *body = JCreateObject();
    if (body != NULL) {
      JAddNumberToObject(body, "temp", 25.6);
      JAddNumberToObject(body, "humidity", 48.2);
      JAddItemToObject(req, "body", body);
    }

    notecard.sendRequest(req);
  }

  delay(15000);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What exactly is happening in this sketch?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We are using a &lt;a href="https://dev.blues.io/reference/notecard-api/hub-requests/?utm_source=devto#hub-set" rel="noopener noreferrer"&gt;hub.set&lt;/a&gt; request to associate the Notecard with the Notehub project.&lt;/li&gt;
&lt;li&gt;We are sending a JSON object (a &lt;a href="https://dev.blues.io/reference/glossary/?utm_source=devto#note" rel="noopener noreferrer"&gt;Note&lt;/a&gt;) with mock temperature and humidity data to the cloud, over cellular.&lt;/li&gt;
&lt;li&gt;Wait 15 seconds and do it all again!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can build and upload this sketch to your ESP32 now, then check your Notehub project to see it in the cloud.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cellular Data in the Cloud
&lt;/h2&gt;

&lt;p&gt;Once that &lt;code&gt;note.add&lt;/code&gt; request is initiated, your Note is queued on the Notecard for syncing with the cloud. Since we are keeping this example simple, we are also using the &lt;code&gt;sync:true&lt;/code&gt; option in the &lt;code&gt;note.add&lt;/code&gt; request to &lt;em&gt;immediately&lt;/em&gt; initiate a cellular connection and send the data to Notehub ASAP.&lt;/p&gt;

&lt;p&gt;Go ahead and head back to Notehub.io and take a look at the recently uploaded events in the &lt;strong&gt;Event&lt;/strong&gt; panel for your project:&lt;/p&gt;

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

&lt;p&gt;But we don't want your data to live on Notehub! The most important part of the story is &lt;strong&gt;securely routing your data to any cloud endpoint&lt;/strong&gt;. Take a look at our &lt;a href="https://dev.blues.io/guides-and-tutorials/routing-data-to-cloud/?utm_source=devto" rel="noopener noreferrer"&gt;extensive Routing Tutorials&lt;/a&gt; and pick your favorite cloud, such as &lt;a href="https://ubidots.com/" rel="noopener noreferrer"&gt;Ubidots&lt;/a&gt;, and create an engaging cloud dashboard to interpret your data:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9n9gr588xznsbms5si1x.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9n9gr588xznsbms5si1x.gif" alt="example ubidots dashboard" width="600" height="314"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TIP:&lt;/strong&gt; Communication with the Notecard is &lt;em&gt;bi-directional&lt;/em&gt; in nature. This means the Notecard can also &lt;em&gt;receive&lt;/em&gt; data from the cloud. See &lt;a href="https://dev.blues.io/notecard/notecard-walkthrough/inbound-requests-and-shared-data/?utm_source=devto" rel="noopener noreferrer"&gt;Inbound Requests &amp;amp; Shared Data Guide&lt;/a&gt; for more details.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;We've barely even started our journey with cellular IoT and the ESP32! Hopefully you've seen that with minimal hardware and minimal firmware, you can easily add cellular connectivity to a new or existing IoT project.&lt;/p&gt;

&lt;p&gt;Looking for some good next steps?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you haven't already, pick up your own &lt;a href="https://shop.blues.io/products/feather-starter-kit?utm_source=devto" rel="noopener noreferrer"&gt;Feather Starter Kit for ESP32&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Next, the &lt;em&gt;real&lt;/em&gt; &lt;a href="https://dev.blues.io/quickstart/notecard-quickstart/?utm_source=devto" rel="noopener noreferrer"&gt;Notecard Quickstart&lt;/a&gt; does a MUCH better job getting you started.&lt;/li&gt;
&lt;li&gt;Consult the &lt;a href="https://dev.blues.io/reference/notecard-api/introduction/?utm_source=devto" rel="noopener noreferrer"&gt;Notecard API reference&lt;/a&gt; to get a full grasp of the capabilities of the Notecard.&lt;/li&gt;
&lt;li&gt;Check out all the awesome &lt;a href="https://www.hackster.io/blues-wireless" rel="noopener noreferrer"&gt;Blues Wireless projects on Hackster&lt;/a&gt; for inspiration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Happy hacking on the ESP32! 👩‍💻&lt;/p&gt;

</description>
      <category>iot</category>
      <category>cloud</category>
      <category>esp32</category>
    </item>
    <item>
      <title>Anti-Theft GPS Tracker and Recovery System</title>
      <dc:creator>Paige Niedringhaus</dc:creator>
      <pubDate>Wed, 13 Apr 2022 14:00:21 +0000</pubDate>
      <link>https://forem.com/blues/anti-theft-gps-tracker-and-recovery-system-jmb</link>
      <guid>https://forem.com/blues/anti-theft-gps-tracker-and-recovery-system-jmb</guid>
      <description>&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/222JbIOauIg"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The words “asset tracking” typically bring to mind moving dots on a map: cars, trucks, planes, and the like. Things that are supposed to move.&lt;/p&gt;

&lt;p&gt;But think about things that &lt;em&gt;shouldn't&lt;/em&gt; move, things that, if they are on the move, means something is amiss: historical statues, expensive environmental monitors, high value equipment in storage, or even (believe it or not) beehives.&lt;/p&gt;

&lt;p&gt;In this &lt;a href="https://www.hackster.io/paige-niedringhaus/blues-where-s-my-car-5eac7d"&gt;Hackster project&lt;/a&gt;, I built a &lt;a href="https://blues.io/products/notecard/"&gt;Blues Wireless Notecard&lt;/a&gt;-powered anti-theft tracker that integrates with &lt;a href="https://www.twilio.com/"&gt;Twilio&lt;/a&gt; to send SMS alerts to a user’s phone when motion is detected, complete with last known location coordinates and a simple Google Maps URL link.&lt;/p&gt;

&lt;p&gt;At a high level, here’s how it works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The ultra-low-power &lt;a href="https://blues.io/products/notecard/"&gt;cellular Notecard&lt;/a&gt; (a mere ~8uA when idle) is configured to act as a GPS device, and only begin taking location readings when motion is detected.&lt;/li&gt;
&lt;li&gt;When it’s in motion, it begins taking GPS tagged readings every minute and sending them as JSON data to &lt;a href="https://notehub.io/"&gt;Notehub&lt;/a&gt; - a thin cloud service that securely accepts data from the Notecard.&lt;/li&gt;
&lt;li&gt;Notehub immediately transforms the raw JSON with the help of &lt;a href="https://docs.jsonata.org/overview.html"&gt;JSONata&lt;/a&gt; into a payload of relevant info for &lt;a href="https://www.twilio.com/"&gt;Twilio&lt;/a&gt;, and routes this data on.&lt;/li&gt;
&lt;li&gt;Finally, Twilio forwards this data along to the user’s phone and they receive an alert like the screenshot below, with a clickable &lt;a href="https://www.google.com/maps/"&gt;Google Maps&lt;/a&gt; link. Hopefully speeding up recovery of whatever’s gone missing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--97DGNUev--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fgrywmfwsnmhgt82p30k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--97DGNUev--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fgrywmfwsnmhgt82p30k.png" alt="Twilio alert complete with Notecard name, last time seen, and location and Google Maps link." width="800" height="866"&gt;&lt;/a&gt;&lt;em&gt;The Twilio alert complete with Notecard name, last seen time and location and Google Maps link.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To see how I built this, &lt;a href="https://www.hackster.io/paige-niedringhaus/blues-where-s-my-car-5eac7d"&gt;view the full tutorial on Hackster&lt;/a&gt;, where I detail exactly how to set up the hardware and software that made this anti-theft and recovery device possible.&lt;/p&gt;

</description>
      <category>iot</category>
      <category>sms</category>
      <category>gps</category>
      <category>cellular</category>
    </item>
    <item>
      <title>Low Fi LoJack® with Blues Wireless and React</title>
      <dc:creator>Paige Niedringhaus</dc:creator>
      <pubDate>Wed, 23 Feb 2022 22:44:52 +0000</pubDate>
      <link>https://forem.com/blues/low-fi-lojackr-with-blues-wireless-and-react-npe</link>
      <guid>https://forem.com/blues/low-fi-lojackr-with-blues-wireless-and-react-npe</guid>
      <description>&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/H1oHs9GvNzY"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The day after Thanksgiving my parents' car was stolen out of the driveway while we were all inside eating dinner. There was a screech of tires, I went to the door to see about the noise and watched their car heading down the driveway and out of sight at high speed.&lt;/p&gt;

&lt;p&gt;We called the police, filed a report, and normally that would have been the end of it. The car didn't have LoJack®, OnStar® or any other built-in GPS tracking systems. It did, however, have a &lt;a href="https://blues.io/products/notecard/?&amp;amp;utm_source=dev.to&amp;amp;utm_medium=web&amp;amp;utm_campaign=niedringhaus-effect&amp;amp;utm_content=ep-2"&gt;Blues Wireless Notecard&lt;/a&gt; and &lt;a href="https://blues.io/products/notecarrier/notecarrier-a/?&amp;amp;utm_source=dev.to&amp;amp;utm_medium=web&amp;amp;utm_campaign=niedringhaus-effect&amp;amp;utm_content=ep-2"&gt;Notecarrier AL&lt;/a&gt; attached to a battery in the backseat recording GPS coordinates every 10 minutes.&lt;/p&gt;

&lt;p&gt;And so begins the tale of how a hobby React asset tracker dashboard became a low fidelity LoJack® tracking a stolen car in the St. Louis area.&lt;/p&gt;

&lt;p&gt;As the situation began to unfold, I started live-tweeting what was happening. If you'd like to see the drama in real-time, you can read this Tweet thread:&lt;/p&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1464414605145522179-626" src="https://platform.twitter.com/embed/Tweet.html?id=1464414605145522179"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1464414605145522179-626');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1464414605145522179&amp;amp;theme=dark"
  }



&lt;/p&gt;




&lt;h2&gt;
  
  
  Let's Talk About How We Got Here
&lt;/h2&gt;

&lt;p&gt;In my &lt;a href="https://blues.io/blog/reactjs-vs-blues-wireless-iot/?&amp;amp;utm_source=dev.to&amp;amp;utm_medium=web&amp;amp;utm_campaign=niedringhaus-effect&amp;amp;utm_content=ep-2"&gt;previous blog post&lt;/a&gt;, I discussed how Blues Wireless makes getting started with IoT development easier in the same way that Create React App makes spinning up a new React application easier. And that's awesome.&lt;/p&gt;

&lt;p&gt;But once that sensor data from an IoT device is flowing in to Blues' &lt;a href="https://notehub.io?&amp;amp;utm_source=dev.to&amp;amp;utm_medium=web&amp;amp;utm_campaign=niedringhaus-effect&amp;amp;utm_content=ep-2"&gt;Notehub&lt;/a&gt; application, that's only half the battle. The other half is getting that data back out of and turning it into something useful in the form of charts and maps.&lt;/p&gt;

&lt;p&gt;Luckily, Blues makes that pretty darn easy as well. Not only does the data from sensors come in to Notehub in the &lt;a href="https://dev.blues.io/notecard/notecard-walkthrough/json-fundamentals/?&amp;amp;utm_source=dev.to&amp;amp;utm_medium=web&amp;amp;utm_campaign=niedringhaus-effect&amp;amp;utm_content=ep-2"&gt;form of JSON&lt;/a&gt;, Notehub also sends JSON back out to wherever you want.&lt;/p&gt;

&lt;p&gt;It can push that data pushed via &lt;a href="https://dev.blues.io/start/tutorials/route-tutorial/?&amp;amp;utm_source=dev.to&amp;amp;utm_medium=web&amp;amp;utm_campaign=niedringhaus-effect&amp;amp;utm_content=ep-2"&gt;Notehub routes&lt;/a&gt; wherever you want: low code platforms Quibtro or Datacake, or other cloud providers like AWS or Microsoft Azure. Or it can be pulled from Notehub via the &lt;a href="https://dev.blues.io/reference/notehub-api/api-introduction/?&amp;amp;utm_source=dev.to&amp;amp;utm_medium=web&amp;amp;utm_campaign=niedringhaus-effect&amp;amp;utm_content=ep-2"&gt;Notehub API&lt;/a&gt;: this is great for getting up and running quickly, building proof of concept apps before you stand up a third-party database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Today I'll show you how to take a Notecard and my React-based application and make your own asset tracker dashboard. The app pulls data from Notehub, renders that data in charts and maps, and regularly fetches new data to update the UI. Ready?&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Configure your Asset Tracker Hardware
&lt;/h2&gt;

&lt;p&gt;Before we get to the dashboard code itself, let's set up a new Notecard project to generate data for the dashboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fimzd3ptdkakudstdxrh4.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fimzd3ptdkakudstdxrh4.jpg" alt="The asset tracker composed of a Blues Wireless Notecard, Notecarrier AL, and LiPo battery."&gt;&lt;/a&gt;&lt;em&gt;The asset tracker composed of a Blues Wireless Notecard, Notecarrier AL, and LiPo battery.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  IoT Hardware List
&lt;/h3&gt;

&lt;p&gt;Here's the equipment you'll need to make this project happen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Blues Wireless &lt;a href="https://shop.blues.io/products/note-nbgl-500?&amp;amp;utm_source=dev.to&amp;amp;utm_medium=web&amp;amp;utm_campaign=niedringhaus-effect&amp;amp;utm_content=ep-2"&gt;NB-IoT &amp;amp; LTE-M Notecard Global&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Blues Wireless &lt;a href="https://shop.blues.io/products/carr-al?&amp;amp;utm_source=dev.to&amp;amp;utm_medium=web&amp;amp;utm_campaign=niedringhaus-effect&amp;amp;utm_content=ep-2"&gt;Notecarrier AL with LiPo battery connector&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.adafruit.com/product/328" rel="noopener noreferrer"&gt;LiPo battery&lt;/a&gt; from an IoT supplier like Adafruit (mine is the 3.7v 2500mAh version)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Initial Notecard and Notecarrier AL Configuration
&lt;/h3&gt;

&lt;p&gt;To keep this portion of my post more evergreen, I'm going to point you to the &lt;a href="https://dev.blues.io/start/quickstart/notecarrier-al/?&amp;amp;utm_source=dev.to&amp;amp;utm_medium=web&amp;amp;utm_campaign=niedringhaus-effect&amp;amp;utm_content=ep-2"&gt;Blues quickstart guide&lt;/a&gt; to set up a Notecard, Notecarrier, and Notehub project, and the &lt;a href="https://dev.blues.io/notecard/notecard-guides/asset-tracking/?&amp;amp;utm_source=dev.to&amp;amp;utm_medium=web&amp;amp;utm_campaign=niedringhaus-effect&amp;amp;utm_content=ep-2"&gt;Blues asset tracking guides&lt;/a&gt; for the commands needed to configure a Notecard for GPS location tracking.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In addition to these instructions, there's a few important caveats to make this asset tracker work for our purposes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;When setting the &lt;code&gt;inbound&lt;/code&gt; and &lt;code&gt;outbound&lt;/code&gt; time periods in your &lt;a href="https://dev.blues.io/notecard/notecard-guides/asset-tracking/#tracker-configuration-requests?&amp;amp;utm_source=dev.to&amp;amp;utm_medium=web&amp;amp;utm_campaign=niedringhaus-effect&amp;amp;utm_content=ep-2"&gt;&lt;code&gt;hub.set&lt;/code&gt; request&lt;/a&gt;, make the time period for each 10 minutes. The reason being, there's no way to force a remote update from Notehub when things like environment variables changes, so if we need the Notecard to read new environment variables shortly after they're added, the sync times between Notehub and the Notecard need to be regular.&lt;/li&gt;
&lt;li&gt;In the final config step of &lt;a href="https://dev.blues.io/notecard/notecard-guides/asset-tracking/#tracker-configuration-requests?&amp;amp;utm_source=dev.to&amp;amp;utm_medium=web&amp;amp;utm_campaign=niedringhaus-effect&amp;amp;utm_content=ep-2"&gt;&lt;code&gt;card.location.track&lt;/code&gt;&lt;/a&gt; where we start the tracker running, include the property of: &lt;code&gt;"sync":true&lt;/code&gt;. This property means &lt;em&gt;as soon as&lt;/em&gt; a new event is acquired by the Notecard (a new GPS location, in this case), the Notecard will sync the event to Notehub instead of waiting for its regularly scheduled &lt;code&gt;outbound&lt;/code&gt; time.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're curious, here's all of the commands I used to set up my Notecard from start to finish using the &lt;a href="https://dev.blues.io/notecard-playground/?&amp;amp;utm_source=dev.to&amp;amp;utm_medium=web&amp;amp;utm_campaign=niedringhaus-effect&amp;amp;utm_content=ep-2"&gt;built-in web REPL&lt;/a&gt; on the &lt;a href="https://dev.blues.io/" rel="noopener noreferrer"&gt;Blues Developers site&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;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"req"&lt;/span&gt;:&lt;span class="s2"&gt;"card.restore"&lt;/span&gt;,&lt;span class="s2"&gt;"delete"&lt;/span&gt;:true&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;#factory reset card&lt;/span&gt;

&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"req"&lt;/span&gt;:&lt;span class="s2"&gt;"hub.set"&lt;/span&gt;,&lt;span class="s2"&gt;"product"&lt;/span&gt;:&lt;span class="s2"&gt;"com.blues.[NOTEHUB_PROJECT_ID_HERE]"&lt;/span&gt;,&lt;span class="s2"&gt;"mode"&lt;/span&gt;:&lt;span class="s2"&gt;"periodic"&lt;/span&gt;,&lt;span class="s2"&gt;"outbound"&lt;/span&gt;:10,&lt;span class="s2"&gt;"inbound"&lt;/span&gt;:10&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;#attach tracker to Notehub project, set it to periodic mode,&lt;/span&gt;
&lt;span class="c"&gt;#sync outbound reqs every 10 mins and inbound reqs from Notehub every 10 mins&lt;/span&gt;

&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"req"&lt;/span&gt;:&lt;span class="s2"&gt;"card.location.mode"&lt;/span&gt;,&lt;span class="s2"&gt;"mode"&lt;/span&gt;:&lt;span class="s2"&gt;"periodic"&lt;/span&gt;,&lt;span class="s2"&gt;"seconds"&lt;/span&gt;:360&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;#tell card how often to get GPS reading and only when motion is detected&lt;/span&gt;

&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"req"&lt;/span&gt;:&lt;span class="s2"&gt;"card.location.track"&lt;/span&gt;,&lt;span class="s2"&gt;"start"&lt;/span&gt;:true,&lt;span class="s2"&gt;"heartbeat"&lt;/span&gt;:true,&lt;span class="s2"&gt;"hours"&lt;/span&gt;:12,&lt;span class="s2"&gt;"sync"&lt;/span&gt;:true&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="c"&gt;#start tracking, issue heartbeat every 12 hours when no motion detected,&lt;/span&gt;
&lt;span class="c"&gt;#sync data with Notehub as soon as a tracking event is acquired (this is an important one)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Asset Tracker Software
&lt;/h2&gt;

&lt;p&gt;This dashboard was a fun challenge to build, and it turned out be much more useful than I could have imagined when it was put to the test in a real-world situation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Technology Powering the Low Fi LoJack® Tracker App
&lt;/h3&gt;

&lt;p&gt;I chose to build this application using the following technologies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The React-based framework &lt;a href="https://nextjs.org/" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; for its built-in server-side rendering and API data fetching capabilities&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.typescriptlang.org/" rel="noopener noreferrer"&gt;Typescript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://react-leaflet.js.org/" rel="noopener noreferrer"&gt;React Leaflet&lt;/a&gt; and &lt;a href="https://docs.mapbox.com/help/getting-started/map-design/" rel="noopener noreferrer"&gt;Mapbox&lt;/a&gt; for my map component&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://recharts.org/en-US" rel="noopener noreferrer"&gt;Recharts&lt;/a&gt; for my chart components&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://react-table.tanstack.com/" rel="noopener noreferrer"&gt;React Table&lt;/a&gt; for my list of events from Notehub&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Dashboard Look and Feel
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Low Fi LoJack® App&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/uUJiX9q8x9M"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;When all is said and done, the dashboard looks like this short video above: charts, map and list of events.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tracker Map&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhgqbpe8mn3d43d73uwzw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhgqbpe8mn3d43d73uwzw.png" alt="React Leaflet map displaying Notecard GPS coordinates over time."&gt;&lt;/a&gt;&lt;em&gt;React Leaflet map displaying Notecard GPS coordinates over time.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The map is using the &lt;strong&gt;React Leaflet&lt;/strong&gt; library to map all the coordinates from Notehub, display them on the map as blue circles, draw the lines between each circle, and display a marker with clickable tooltip of last recorded location. Mapbox provides the actual &lt;a href="https://docs.mapbox.com/api/maps/styles/" rel="noopener noreferrer"&gt;map style&lt;/a&gt; displayed in the component.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tracker Charts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4yrif2mh35toitp7d5np.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4yrif2mh35toitp7d5np.png" alt="Recharts chart displaying Notecard temperature over time"&gt;&lt;/a&gt;&lt;em&gt;Recharts chart displaying Notecard temperature over time&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recharts&lt;/strong&gt; is displaying both the temperature of the Notecard and the voltage every time a GPS location reading is taken. It also has tooltips to display relevant information when a user hovers over the chart.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5skd0kf2rl6all6k2u7s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5skd0kf2rl6all6k2u7s.png" alt="Recharts chart displaying Notecard voltage over time"&gt;&lt;/a&gt;&lt;em&gt;Recharts chart displaying Notecard voltage over time&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tracker Events&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fff7a5niul5lo1ig1vlxr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fff7a5niul5lo1ig1vlxr.png" alt="React Table list displaying paginated Notecard events over time"&gt;&lt;/a&gt;&lt;em&gt;React Table list displaying paginated Notecard events over time&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For a sanity check as much as anything else, there's an event list in a &lt;strong&gt;React Table&lt;/strong&gt; at the bottom of the application showing a paginated list of all the events the dashboard has pulled from Notehub. It shows when the last event was received and a couple of other pieces of information so it's easy to tell, at a glance, what's going on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interesting Notes about How the Dashboard's Built
&lt;/h3&gt;

&lt;p&gt;If you're ready to set up your own asset tracker dashboard, skip to the next section, but if you're interested in some of the challenges I ran into along the way and how I solved for them, read on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;I pulled data via the Notehub API instead of using WebSockets to accept data pushed via Notehub Routes.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many of the low-code dashboard platforms rely on &lt;a href="https://dev.blues.io/start/tutorials/route-tutorial/datacake/?&amp;amp;utm_source=dev.to&amp;amp;utm_medium=web&amp;amp;utm_campaign=niedringhaus-effect&amp;amp;utm_content=ep-2"&gt;Notehub's routes&lt;/a&gt; to push Notecard events to them, but doing that, there's no way to get historical data from Notehub that occurred before the route was hooked up.&lt;/p&gt;

&lt;p&gt;To get my React app up and running and populated with data quickly, I decided to use the &lt;a href="https://dev.blues.io/reference/notehub-api/api-introduction/?&amp;amp;utm_source=dev.to&amp;amp;utm_medium=web&amp;amp;utm_campaign=niedringhaus-effect&amp;amp;utm_content=ep-2"&gt;Notehub API&lt;/a&gt; to fetch events directly from Notehub. The Notehub API requires users to create an &lt;a href="https://dev.blues.io/reference/notehub-api/api-introduction/#authentication?&amp;amp;utm_source=dev.to&amp;amp;utm_medium=web&amp;amp;utm_campaign=niedringhaus-effect&amp;amp;utm_content=ep-2"&gt;authorization token&lt;/a&gt; to be passed along with requests, but the process is well documented, as is the &lt;a href="https://dev.blues.io/reference/notehub-api/event-api/#get-events-by-project?&amp;amp;utm_source=dev.to&amp;amp;utm_medium=web&amp;amp;utm_campaign=niedringhaus-effect&amp;amp;utm_content=ep-2"&gt;API to fetch all events&lt;/a&gt; and then filter down to the events I wanted on my end (&lt;code&gt;_track.qo&lt;/code&gt; events in this case).&lt;/p&gt;

&lt;p&gt;Doing it this way also eliminated my need for a persistence layer of some sort in the form of a database, although for a production ready app I would recommend going this route.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The map component needs server-rendered data. Next.js makes it easy.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Another interesting challenge I ran into is that in order to render all the data points and connecting lines on the map, the data must be fetched at app build time, not after the component's rendered.&lt;/p&gt;

&lt;p&gt;At first, I built a simple &lt;code&gt;refreshPage()&lt;/code&gt; function to run on an interval and force-refresh a page to server-side render and refetch fresh data from Notehub.&lt;/p&gt;

&lt;p&gt;Then I found a better way: &lt;a href="https://nextjs.org/docs/basic-features/data-fetching#incremental-static-regeneration" rel="noopener noreferrer"&gt;incremental static regeneration&lt;/a&gt;. ISR allows you to create or update static pages &lt;em&gt;after&lt;/em&gt; the site's been built &lt;strong&gt;without need to rebuild the entire site&lt;/strong&gt;. The key is an optional &lt;code&gt;revalidate&lt;/code&gt; parameter that tells the &lt;code&gt;getStaticProps()&lt;/code&gt; function fetching the data server-side to re-fetch new data on an interval specified in seconds and re-generate the page.&lt;/p&gt;

&lt;p&gt;Once I'd figured out how to fetch the data on the server-side and get all the events out of Notehub via the API, it was a simple matter of transforming the data into the shape the app needed to render its various components.&lt;/p&gt;




&lt;h2&gt;
  
  
  Make the React Dashboard Work for You
&lt;/h2&gt;

&lt;p&gt;Ok, so let's make your own tracker app connected to your own Notecard and Notehub project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Run Low Fi LoJack® Locally
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Download the asset tracker app code&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First, go ahead and fork my &lt;a href="https://github.com/paigen11/react-gps-asset-tracker-dashboard" rel="noopener noreferrer"&gt;asset tracker repo in GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Clone or download it to your local machine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git clone https://github.com/paigen11/react-gps-asset-tracker-dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Install all the project dependencies&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Open up your code in your IDE of choice: VSCode, WebStorm, Sublime, Atom, etc.&lt;/p&gt;

&lt;p&gt;In a terminal, at the root of the project, run &lt;code&gt;npm install&lt;/code&gt; to download all the project's dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Generate an access token for Notehub&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To access the events in Notehub, you'll need to generate an access token. The &lt;a href="https://dev.blues.io/reference/notehub-api/api-introduction/#authentication?&amp;amp;utm_source=dev.to&amp;amp;utm_medium=web&amp;amp;utm_campaign=niedringhaus-effect&amp;amp;utm_content=ep-2"&gt;documentation to generate the token&lt;/a&gt; is straightforward. Open a terminal instance and follow the instructions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST
&lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="s1"&gt;'https://api.notefile.net/auth/login'&lt;/span&gt;
&lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"username":"[you@youremail.com]", "password": "[your_password]"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy this token after it's generated - you'll be putting it into your project shortly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create a &lt;code&gt;.env.local&lt;/code&gt; file at the root of the project&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This &lt;code&gt;.env.local&lt;/code&gt; file is how &lt;a href="https://nextjs.org/docs/basic-features/environment-variables#loading-environment-variables" rel="noopener noreferrer"&gt;Next.js automatically reads in environment variables&lt;/a&gt; used at build time or on the client side. All the variables you'll need are build time variables so none of them need to be prefixed with &lt;code&gt;NEXT_PUBLIC_&lt;/code&gt;, which allows for variable access on the client side.&lt;/p&gt;

&lt;p&gt;These are the variables you'll need to add to the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;NOTEHUB_PROJECT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;APP_ID_GOES_HERE &lt;span class="c"&gt;# get this from Notehub&lt;/span&gt;
&lt;span class="nv"&gt;NOTEHUB_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;NOTEHUB_GENERATED_TOKEN_GOES_HERE &lt;span class="c"&gt;# paste in token generated in previous step&lt;/span&gt;
&lt;span class="nv"&gt;MAPBOX_ACCESS_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;MAPBOX_ACCESS_TOKEN_GOES_HERE_IF_NOT_USING_MINE &lt;span class="c"&gt;# if you have your own Mapbox API token, add it here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Start up the app and see if you're getting data&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once all the local environment variables are set with your Notehub credentials, and the project dependencies have been installed, start the app up locally by typing &lt;code&gt;npm run dev&lt;/code&gt; at the root of the project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything goes according to plan, when you go to &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt; you should see something like the screenshots in this article in your browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy Low Fi LoJack® to Netlify
&lt;/h3&gt;

&lt;p&gt;It's cool to have this up and running on your local machine and all, but what about if you want to deploy it somewhere where other people can see it?&lt;/p&gt;

&lt;p&gt;The quickest and easiest solution I would recommend is the &lt;a href="https://netlify.com/" rel="noopener noreferrer"&gt;Netlify platform&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I deployed my own version of this app to Netlify in order to share the map and history with the police detective working to recover the vehicle after it was stolen.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Sign up for a free Netlify account&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you don't already have a Netlify account, &lt;a href="https://app.netlify.com/signup" rel="noopener noreferrer"&gt;sign up&lt;/a&gt; for a free one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn0irdp29wb1bkk5ugc32.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn0irdp29wb1bkk5ugc32.png" alt="Netlify signup options"&gt;&lt;/a&gt;&lt;em&gt;Netlify signup options&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Grant Netlify access to your GitHub repo&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After you've logged in to Netlify, you'll need to &lt;a href="https://www.netlify.com/blog/2016/09/29/a-step-by-step-guide-deploying-on-netlify/" rel="noopener noreferrer"&gt;follow these instructions&lt;/a&gt; to grant Netlify access to your GitHub GPS tracker repo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Set Netlify environment variables during first deployment&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;During the very first build and deployment of your repo on Netlify, you'll need to add the environment variables we set locally via our &lt;code&gt;.env.local&lt;/code&gt; file via &lt;a href="https://docs.netlify.com/configure-builds/environment-variables/" rel="noopener noreferrer"&gt;Netlify's build and deploy variables&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After you've selected the repo you want Netlify to build, click the &lt;strong&gt;"Advanced Build Settings" button&lt;/strong&gt;, and add each key-value pair from your &lt;code&gt;.env.local&lt;/code&gt; file as a new variable here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5o69dwdmnymtjma72qvp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5o69dwdmnymtjma72qvp.png" alt="Netlify environment variables added in build process"&gt;&lt;/a&gt;&lt;em&gt;Netlify environment variables added in build process&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;These variables need to be protected on the server side because they give access to Notehub, your project, etc. and shouldn't be stored in GitHub or anywhere else accessible by the general public.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Beyond these steps, the &lt;code&gt;netlify.toml&lt;/code&gt; file in the root of the project handles the rest of the &lt;a href="https://docs.netlify.com/configure-builds/common-configurations/next-js/" rel="noopener noreferrer"&gt;Next.js deployment on Netlify&lt;/a&gt; without having to do much else.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;build]
publish &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;".next"&lt;/span&gt;

&lt;span class="o"&gt;[[&lt;/span&gt;plugins]]
package &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"@netlify/plugin-nextjs"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The secret is the &lt;a href="https://github.com/netlify/netlify-plugin-nextjs/blob/main/docs/release-notes/v4.md" rel="noopener noreferrer"&gt;&lt;code&gt;@netlify/plugin-nextjs&lt;/code&gt; package&lt;/a&gt; - it takes care of all the more complicated parts of deploying a Next.js app so you don't have to.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Grand Theft Auto Conclusion
&lt;/h2&gt;

&lt;p&gt;For about an hour after the car was stolen we were able to follow it around on my app's map because my asset tracker remained undetected in the backseat. After that, the thieves discovered it and ditched it in a park and a few days later the police retrieved it - the tracker was still transmitting its location every 12 hours even though no new motion was detected.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I want to give a special shout out to my coworker Sean, who jumped on a Slack call 20 minutes after the car was stolen and helped me remotely update the Notecard via Notehub environment variables to take more frequent GPS readings so we could more closely follow the car in real time. I feel fortunate to work with such good people.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By the time the tracker was returned to my parents, I'd already left for my own home, so they mailed it to me, and since it was still operating fine, I was able to track its progress via USPS as it made its way towards me.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0utw29nup3lvg0r66zfi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0utw29nup3lvg0r66zfi.png" alt="The OG asset tracker Notecard on its way back to me via USPS after its harrowing ordeal, still working like a champ"&gt;&lt;/a&gt;&lt;em&gt;The OG asset tracker Notecard on its way back to me via USPS after its harrowing ordeal, still working like a champ&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here's the tracker when it arrived back at my house.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjoec46oqwpr0juczzsfo.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjoec46oqwpr0juczzsfo.jpg" alt="The asset tracker Notecard safely packed in a shipping box full of bubble wrap"&gt;&lt;/a&gt;&lt;em&gt;The asset tracker Notecard safely packed in a shipping box full of bubble wrap&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Over a month after the car was stolen it was found abandoned in terrible condition in an impound lot: bullet holes in the roof (which ruined the interior), dents and scratches on every outside panel, the stench of marijuana inside, an extra 2,500 miles on the odometer, and that's just what could be seen without checking under the hood.&lt;/p&gt;

&lt;p&gt;Hopefully the insurance company will write it off.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where to go from here
&lt;/h2&gt;

&lt;p&gt;Make and deploy your own asset tracker - from hardware to software it's all available with Blues tech and my dashboard app code. You can even use this &lt;a href="https://shop.blues.io/discount/NiedringhausEffect-Ep2?redirect=%2Fcollections%2Fdevelopment-kits" rel="noopener noreferrer"&gt;discount code&lt;/a&gt; to get 20% off your first development kit purchase.&lt;/p&gt;

&lt;p&gt;There's also a ton of ways this tracker dashboard could be even better. Improvements could include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An option to download event data to a spreadsheet for easy sharing&lt;/li&gt;
&lt;li&gt;Customizable timeframes via a date picker to see different amounts of events on the map and the charts&lt;/li&gt;
&lt;li&gt;Add a cloud database that Notehub can push new events to reducing the amount of direct queries to Notehub&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I actually extended this idea into another project where I made an &lt;a href="https://github.com/paigen11/notelink-tracker-dashboard" rel="noopener noreferrer"&gt;SOS-equipped dashboard&lt;/a&gt; during a day-long internal company hackathon.&lt;/p&gt;

&lt;p&gt;It follows the same principles as the project here, but includes an "SOS Mode", which updates the Notecard remotely to do more frequent GPS location locks, and displays it on the dashboard with a red colored line.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6gwn01mwa985m62y8e6q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6gwn01mwa985m62y8e6q.png" alt="This SOS asset tracker project changes all connecting lines between points to red when SOS mode is enabled."&gt;&lt;/a&gt;&lt;em&gt;This SOS asset tracker project changes all connecting lines between points to red when SOS mode is enabled.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What will you follow with your own asset tracker?&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;LoJack® and OnStar® are registered trademarks of the LoJack Corporation and OnStar, LLC. respectively.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>nextjs</category>
      <category>iot</category>
      <category>gps</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Thermal Image Anomaly Detection with TinyML</title>
      <dc:creator>Rob Lauer</dc:creator>
      <pubDate>Wed, 16 Feb 2022 20:55:45 +0000</pubDate>
      <link>https://forem.com/blues/thermal-image-anomaly-detection-with-tinyml-48n0</link>
      <guid>https://forem.com/blues/thermal-image-anomaly-detection-with-tinyml-48n0</guid>
      <description>&lt;p&gt;When I say "anomaly detection" you may imagine an overly complicated process, something exclusive to deep learning algorithms and indecipherable coding. In reality, the concept of uncovering anomalous behavior in a system is really just the act of &lt;em&gt;identifying an unclassified or uncertain state&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://www.hackster.io/rob-lauer/thermal-image-anomaly-detection-with-tinyml-36831c" rel="noopener noreferrer"&gt;this Hackster project&lt;/a&gt;, I created a basic &lt;strong&gt;anomaly detection ML model&lt;/strong&gt; with &lt;a href="https://edgeimpulse.com/" rel="noopener noreferrer"&gt;Edge Impulse&lt;/a&gt; that processes thermal images to detect unknown states of thermal readings and relays collected data to the cloud with the &lt;a href="https://blues.io/products/notecard/?utm_source=devto&amp;amp;utm_medium=web&amp;amp;utm_campaign=featured-project&amp;amp;utm_content=thermal" rel="noopener noreferrer"&gt;Blues Wireless Notecard&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/hYVXLxFa8aY"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;By using the "edge ML" capabilities provided by Edge Impulse, the cellular Notecard device-to-cloud data pump from Blues Wireless, a &lt;a href="https://www.raspberrypi.com/products/raspberry-pi-zero-2-w/" rel="noopener noreferrer"&gt;Raspberry Pi Zero 2 W&lt;/a&gt;, and an &lt;a href="https://ubidots.com/" rel="noopener noreferrer"&gt;Ubidots&lt;/a&gt; dashboard, I was able to create a simple, low-power, thermal monitoring station with only a small bit of Python coding required 🐍.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqdt7jvi92vwuaji7of2x.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqdt7jvi92vwuaji7of2x.gif" alt="Animation of all the recorded thermal images" width="320" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Animation of all the recorded thermal images.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Be sure to &lt;a href="https://www.hackster.io/rob-lauer/thermal-image-anomaly-detection-with-tinyml-36831c" rel="noopener noreferrer"&gt;view the full tutorial on Hackster&lt;/a&gt; to see how this project was built from scratch!&lt;/p&gt;

</description>
      <category>iot</category>
      <category>machinelearning</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Blues Wireless - Just Like React, but For IoT</title>
      <dc:creator>Paige Niedringhaus</dc:creator>
      <pubDate>Wed, 02 Feb 2022 21:29:41 +0000</pubDate>
      <link>https://forem.com/blues/blues-wireless-just-like-react-but-for-iot-4eah</link>
      <guid>https://forem.com/blues/blues-wireless-just-like-react-but-for-iot-4eah</guid>
      <description>&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/Nr-VclCFwzE"&gt;
&lt;/iframe&gt;
&lt;br&gt;
Welcome to the start of a new blog series where I'll attempt to show other people with web development backgrounds (like my own), just how simple Internet of Things (IoT) development can be with the help of the right tools like Blues Wireless.&lt;/p&gt;

&lt;p&gt;How, you ask? Why, by using an analogy many web developers are familiar with: comparing the unfamiliar (IoT and hardware) to the familiar (JavaScript and the web).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In this first post I'll be showing how &lt;a href="https://blues.io"&gt;Blues Wireless&lt;/a&gt; makes getting started with IoT development easier in the same way that popular JavaScript frameworks, like &lt;a href="https://reactjs.org/"&gt;React&lt;/a&gt;, make building websites easier.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Two different pieces of tech, solving for the same sorts of problems
&lt;/h2&gt;

&lt;p&gt;It might not be immediately obvious, but React and Blues Wireless actually have a lot in common.&lt;/p&gt;

&lt;p&gt;While neither is absolutely essential to building tech today, there's no reason not to use them: both make the process infinitely easier.&lt;/p&gt;

&lt;p&gt;Let's face it, most of the time the hardest part of anything is getting started. Whether it's a blank IDE staring at you as you gaze at your keyboard willing the code to appear, or an IoT-connected device, but no path to get the data from the real world where it originates to your virtual world where it will be useful.&lt;/p&gt;

&lt;p&gt;It's the worst, but that's where these two seemingly unrelated pieces of tech converge.&lt;/p&gt;

&lt;h3&gt;
  
  
  For websites, it's JS frameworks to the rescue
&lt;/h3&gt;

&lt;p&gt;To get you started, JavaScript frameworks like &lt;a href="https://reactjs.org/"&gt;React&lt;/a&gt;, &lt;a href="https://vuejs.org/"&gt;Vue&lt;/a&gt;, &lt;a href="https://angular.io/"&gt;Angular&lt;/a&gt;, and &lt;a href="https://svelte.dev/"&gt;Svelte&lt;/a&gt; all have simple templates to give devs a starting point and let them get over that first hurdle of getting something, &lt;em&gt;anything&lt;/em&gt; in the browser.&lt;/p&gt;

&lt;p&gt;Once that's been accomplished, developers are free to focus on the more important (and fun) stuff: solving business problems and building useful tools, and less on if the server's running on the right port, the code's compiling, or they've correctly bound event handlers to clicks in the DOM.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BqJyVzDP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hcwgi1so70i682268qxz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BqJyVzDP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hcwgi1so70i682268qxz.png" alt="The promise of Create React App: to get devs up and running quickly" width="800" height="495"&gt;&lt;/a&gt;&lt;em&gt;React's quick start template: Create React App. It takes care of the hard stuff so we can get to fun stuff faster.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  For IoT, it's Blues Wireless and its device-to-cloud data pump
&lt;/h3&gt;

&lt;p&gt;Blues Wireless does the same thing - just for Internet of Things engineers (and especially the ones who know that where their hardware is going, reliable Internet connectivity is not just &lt;em&gt;not a guarantee&lt;/em&gt; - it might not even be an option).&lt;/p&gt;

&lt;p&gt;Instead of learning the archaic language of &lt;a href="https://doc.qt.io/archives/qtextended4.4/atcommands.html"&gt;AT commands&lt;/a&gt;, negotiating exorbitant fees with cellular companies to transmit data to the cloud, and figuring out how to turn that cryptic cloud data into something useful, Blues takes care of all of that for you.&lt;/p&gt;

&lt;p&gt;Just buy a &lt;a href="https://blues.io/products/notecard/"&gt;Notecard&lt;/a&gt;, attach it to a &lt;a href="https://blues.io/products/notecarrier/"&gt;Notecarrier&lt;/a&gt;, issue a few simple JSON commands, and you're ready to roll. &lt;strong&gt;It just works.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Idq1ICze--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kq1fcnvmileuewucmv4q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Idq1ICze--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kq1fcnvmileuewucmv4q.png" alt="The flow of data from a Blues Wireless Notecard to the Blues Wireless Notehub cloud" width="800" height="361"&gt;&lt;/a&gt; &lt;em&gt;This is a simple diagram showing the flow of data from a Blues Wireless Notecard in the field to the Blues Wireless Notehub cloud. Again, development made easier.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  No Really, Blues Wireless Just Works
&lt;/h2&gt;

&lt;p&gt;If you're like me, you're rolling your eyes right now after that last sentence. I get it - I too, would think what I'm saying is too good to be true. But it's not.&lt;/p&gt;

&lt;p&gt;Let me list my &lt;em&gt;lack&lt;/em&gt; of IoT qualifications when I joined Blues Wireless. When I started, I:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did not own a &lt;a href="https://www.raspberrypi.com/products/"&gt;Raspberry Pi&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;Did not know what a &lt;a href="https://learn.sparkfun.com/tutorials/how-to-use-a-breadboard/all"&gt;breadboard&lt;/a&gt; was,&lt;/li&gt;
&lt;li&gt;Could not write, much less debug, &lt;a href="https://www.arduino.cc/en/tutorial/sketch"&gt;Arduino code&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After working at Blues Wireless for close to 4 months, I still can't write or debug Arduino code.&lt;/p&gt;

&lt;p&gt;Yet despite this, within 6 weeks of starting, I built a &lt;a href="https://www.hackster.io/paige-niedringhaus/low-code-gps-asset-tracker-and-map-display-b10419"&gt;&lt;strong&gt;fully-functional asset tracker&lt;/strong&gt;&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;This little guy collects GPS and temperature data, sends that data to the Blues Wireless cloud &lt;a href="https://notehub.io/"&gt;Notehub&lt;/a&gt;, and Notehub pumps that data to the low-code IoT platform &lt;a href="https://datacake.co/"&gt;Datacake&lt;/a&gt; where it decodes the data and displays it.&lt;/p&gt;

&lt;p&gt;With the help of the detailed asset tracking documentation on the &lt;a href="https://dev.blues.io/guides-and-tutorials/notecard-guides/asset-tracking/"&gt;Blues developer site&lt;/a&gt;, in less than 30 minutes, I'd configured a &lt;a href="https://blues.io/products/notecarrier/notecarrier-af/"&gt;Notecarrier-AF&lt;/a&gt;, equipped it with a &lt;a href="https://blues.io/products/notecard/"&gt;Notecard&lt;/a&gt; to record its GPS coordinates, temperature, and motion data at an interval of once an hour, and pumped that data, in an easy-to-understand JSON data object, to a project I'd set up in the Blues &lt;a href="https://notehub.io/"&gt;Notehub&lt;/a&gt; cloud.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--S3cp1Pfp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nghg4l6c3g6kqwhcj5wu.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--S3cp1Pfp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nghg4l6c3g6kqwhcj5wu.jpeg" alt="Notecarrier GPS asset tracker and battery" width="733" height="555"&gt;&lt;/a&gt; &lt;em&gt;Behold, my asset tracker in all its glory: a Notecard, Notecarrier and LiPo battery&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bVAGKOkX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3szj3l8f176cfb8nic3v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bVAGKOkX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3szj3l8f176cfb8nic3v.png" alt="Notehub.io project dashboard" width="800" height="398"&gt;&lt;/a&gt; &lt;em&gt;This is the Notehub dashboard. The "Asset Tracker" project in the center is where all my sensor data was sent to.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In another 30 minutes, I had my &lt;a href="https://dev.blues.io/guides-and-tutorials/routing-data-to-cloud/datacake/"&gt;data in Notehub being sent to the Datacake platform&lt;/a&gt;, still in readable JSON format, and being interpreted to display the relevant GPS coordinates on a map.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EN98UWvn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x0t3bgv61x17t3cqj7z9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EN98UWvn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x0t3bgv61x17t3cqj7z9.png" alt="Datacake low code dashboard" width="800" height="507"&gt;&lt;/a&gt; &lt;em&gt;These are two of the widgets I put on my Datacake dashboard: the map plotting my course over time, and the temperature tracker.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Q0Wpl6WX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hq9a7gtuakclqzxrezz4.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Q0Wpl6WX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hq9a7gtuakclqzxrezz4.jpeg" alt="Fields you can add to Datacake dashboard" width="740" height="177"&gt;&lt;/a&gt; &lt;em&gt;Some of the fields I was setting from the JSON being delivered to Datacake via Notehub.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hVQTbM2t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/73vung1r73rohsguxutc.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hVQTbM2t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/73vung1r73rohsguxutc.jpeg" alt="Datacake decoder that takes in the data and formats it for the fields" width="740" height="338"&gt;&lt;/a&gt; &lt;em&gt;The Datacake HTTP payload decoder which took in the raw JSON from Notehub and assigned the correct pieces of info to the fields I'd defined in the image above.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--J6UA5jpq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u1amn4yhaq9hkw1vlvci.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--J6UA5jpq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u1amn4yhaq9hkw1vlvci.jpeg" alt="How to set Datacake route in Notehub to send data over" width="599" height="555"&gt;&lt;/a&gt; &lt;em&gt;The single URL link I added to my Notehub project so it knew which data files to send to my Datacake dashboard.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The best part about all this? &lt;strong&gt;Notehub doesn't just work with Datacake.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It works with (and has documentation to connect to) all the most popular cloud platforms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.blues.io/guides-and-tutorials/routing-data-to-cloud/aws-iot-analytics/"&gt;AWS IoT Analytics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.blues.io/guides-and-tutorials/routing-data-to-cloud/google-cloud-platform/"&gt;Google Cloud Platform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.blues.io/guides-and-tutorials/routing-data-to-cloud/azure/"&gt;Azure&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plus lots of low-code IoT platforms too:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.blues.io/guides-and-tutorials/routing-data-to-cloud/ubidots/"&gt;Ubidots&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.blues.io/guides-and-tutorials/routing-data-to-cloud/thingspeak/"&gt;ThingSpeak&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.blues.io/guides-and-tutorials/routing-data-to-cloud/initialstate/"&gt;InitialState&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once that sensor data is in Notehub, it's simple to send it anywhere else. And once it's anywhere else, just hook into that platform like you would with any other third-party API service providing data. Easy peasy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qc4-jlPD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fkkdo8uusifpn40s98gq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qc4-jlPD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fkkdo8uusifpn40s98gq.png" alt="The many data routing tutorials Blues Wireless has documentation for" width="800" height="340"&gt;&lt;/a&gt; &lt;em&gt;Just look at all the possible cloud providers Blues Wireless has provided routing tutorials for to get the data out of Notehub and to your app.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How does this relate to React, Again?
&lt;/h2&gt;

&lt;p&gt;I'll admit, this post is much more about the IoT side of things than in depth look at web development, but I'm operating under the assumption that if you're reading this, you're already fairly familiar with JavaScript, React and the web development world.&lt;/p&gt;

&lt;p&gt;What I'm trying to do is connect the dots: &lt;strong&gt;React makes web development easier, and Blues Wireless makes IoT development easier.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Could I have set up this neat little asset tracker myself without Blues Wireless? Yes, probably.&lt;/p&gt;

&lt;p&gt;Would it have taken me an exponentially longer time, and probably turned into such a frustrating, landmine-strewn obstacle course that I very well might have quit before achieving my goal? Also yes, quite likely.&lt;/p&gt;

&lt;p&gt;Just like with JavaScript, I could spend a lot of time on the unfun, nitty gritty details that JS frameworks take care of for me, but why would I? Why spend time and energy on things already solved for in these frameworks, when I could spend that same time building cool, useful stuff instead?&lt;/p&gt;

&lt;p&gt;And the same can now be said of IoT prototyping and development. Spend time on the low-level details, or choose Blues Wireless solutions and get to building the "cool, useful stuff" a whole lot sooner.&lt;/p&gt;

&lt;p&gt;I'm sold. Are you?&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to go from Here
&lt;/h2&gt;

&lt;p&gt;If you're ready to get started with your own IoT project, I recommend you check out our &lt;a href="https://dev.blues.io/quickstart/"&gt;Getting Started documentation&lt;/a&gt; on our developer experience site. It's what I used to get up and running with my project in no time at all.&lt;/p&gt;

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

&lt;p&gt;Stay tuned for the next installment in this series: I plan to show you how to send data from Notehub to a cloud provider and then display it in your own React app with D3. We are developers after all, building cool data visualizations is fun.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>iot</category>
      <category>cloud</category>
    </item>
  </channel>
</rss>
