<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>day62 &amp;mdash; Gerrit Niezen</title>
    <link>https://gerritniezen.com/tag:day62</link>
    <description>Maker of open-source software and hardware.</description>
    <pubDate>Fri, 24 Apr 2026 06:02:01 +0000</pubDate>
    <image>
      <url>https://i.snap.as/aMPXpIot.png</url>
      <title>day62 &amp;mdash; Gerrit Niezen</title>
      <link>https://gerritniezen.com/tag:day62</link>
    </image>
    <item>
      <title>Building a Bluetooth LE temperature sensor</title>
      <link>https://gerritniezen.com/building-a-bluetooth-le-temperature-sensor?pk_campaign=rss-feed</link>
      <description>&lt;![CDATA[For my hydroponics project, I would like to measure the ambient temperature. I&#39;ve done this in the past as a WiFi temperature sensor, but this time round I wanted to use my Pixl.js board with Bluetooth LE connectivity.&#xA;&#xA;Here is the code to read data from a DS18B20 temperature sensor, display a temperature graph on the Pixl.js screen, and also make it available via Bluetooth LE as an Environmental Sensing Service.&#xA;&#xA;const PINSENSOR = A1;  // Pin temperature sensor is connected to&#xA;&#xA;let ow, sensor, history;&#xA;let avrSum = 0, avrNum = 0;&#xA;let lastTemp = 0;&#xA;&#xA;// Draw the temperature and graph to the screem&#xA;function draw() {&#xA;  g.clear();&#xA;  g.setFontVector(30);&#xA;  g.setFontAlign(0,-1);&#xA;  g.drawString(lastTemp.toFixed(1), 64,5);&#xA;  g.setFontBitmap();&#xA;  g.drawString(&#34;Temperature&#34;, 64, 0);&#xA;  require(&#34;graph&#34;).drawLine(g, history, {&#xA;    axes : true,&#xA;    gridy : 5,&#xA;    x:0, y:37,&#xA;    width:128,&#xA;    height:27&#xA;  });&#xA;  g.flip();&#xA;}&#xA;&#xA;// Called when we get a temperature reading&#xA;function onTemp(temp) {&#xA;  if (temp === null) return; // bad reading&#xA;  avrSum += temp;&#xA;  avrNum++;&#xA;  lastTemp = temp;&#xA;&#xA;  // send on BLE&#xA;  const th = Math.round(lastTemp * 100);&#xA;  NRF.updateServices({&#xA;    0x181A : { // environmentalsensing&#xA;      0x2A6E : { // temperature&#xA;        value : [th&amp;255,th    8],&#xA;        readable: true,&#xA;        notify: true&#xA;      }&#xA;    }&#xA;  });&#xA;&#xA;  draw();&#xA;}&#xA;&#xA;// take temp reading and update graph&#xA;var readingInterval = setInterval(function() {&#xA;  if (sensor) sensor.getTemp(onTemp);&#xA;}, 10000);&#xA;&#xA;// save average to history&#xA;var histInterval = setInterval(function() {&#xA;  history.set(new Float32Array(history.buffer,4));&#xA;  history[history.length-1] = avrSum / avrNum;&#xA;  avrSum = 0;&#xA;  avrNum = 0;&#xA;}, 60000); // 1 minute&#xA;&#xA;// At startup, set up OneWire&#xA;function onInit() {&#xA;  try {&#xA;    ow = new OneWire(PINSENSOR);&#xA;    sensor = require(&#34;DS18B20&#34;).connect(ow);&#xA;    history = new Float32Array(128);&#xA;    avrSum=0;&#xA;    avrNum=0;&#xA;&#xA;    NRF.setServices({&#xA;      0x181A: { // environmentalsensing&#xA;        0x2A6E: { // temperature&#xA;          notify: true,&#xA;          readable: true,&#xA;          value : [0x00, 0x00, 0x7F, 0xFF, 0xFF],&#xA;        }&#xA;      }&#xA;    }, { advertise: [ &#39;181A&#39; ] });&#xA;  } catch (e) {&#xA;    console.log(&#39;Error:&#39;, e);&#xA;  }&#xA;&#xA;This code is based on the lovely Pixl.js Freezer Alarm example.&#xA;&#xA;I then wanted to be able to connect to the sensor via Web Bluetooth in the browser, which led to this web app:&#xA;&#xA;document.addEventListener(&#39;DOMContentLoaded&#39;, event =  {&#xA;  const button = document.getElementById(&#39;connectButton&#39;)&#xA;  let device&#xA;&#xA;  button.addEventListener(&#39;click&#39;, async (e) =  {&#xA;    try {&#xA;      if (button.innerHTML === &#39;Disconnect&#39;) {&#xA;        device.gatt.disconnect()&#xA;        button.innerHTML = &#39;Connect&#39;&#xA;        return&#xA;      }&#xA;      document.getElementById(&#39;p1&#39;).innerHTML = &#39;Connecting&#39;&#xA;      console.log(&#39;Requesting Bluetooth Device...&#39;)&#xA;      device = await navigator.bluetooth.requestDevice({&#xA;        filters: [{&#xA;          namePrefix: &#39;Pixl.js&#39;&#xA;        }],&#xA;        optionalServices: [&#39;environmentalsensing&#39;]&#xA;      })&#xA;&#xA;      device.addEventListener(&#39;gattserverdisconnected&#39;, onDisconnected)&#xA;&#xA;      console.log(&#39;Connecting to GATT Server...&#39;)&#xA;      const server = await device.gatt.connect()&#xA;      button.innerHTML = &#39;Disconnect&#39;&#xA;&#xA;      console.log(&#39;Getting Environmental Sensing Service...&#39;)&#xA;      const service = await server.getPrimaryService(&#39;environmentalsensing&#39;)&#xA;&#xA;      console.log(&#39;Getting Temperature Characteristic...&#39;)&#xA;      const characteristic = await service.getCharacteristic(&#39;temperature&#39;)&#xA;&#xA;      console.log(&#39;Reading temperature...&#39;)&#xA;      const value = await characteristic.readValue()&#xA;      const temp = value.getUint16(0, true) / 100&#xA;&#xA;      console.log(&#39;Temperature is &#39; + temp)&#xA;      document.getElementById(&#39;p1&#39;).innerHTML = temp&#xA;&#xA;      await characteristic.startNotifications()&#xA;&#xA;      console.log(&#39;Notifications started&#39;)&#xA;      characteristic.addEventListener(&#39;characteristicvaluechanged&#39;, handleNotifications)&#xA;    } catch (error) {&#xA;      console.log(&#39;Argh! &#39; + error)&#xA;      document.getElementById(&#39;p1&#39;).innerHTML = error&#xA;      button.innerHTML = &#39;Connect&#39;&#xA;    }&#xA;  })&#xA;&#xA;  function handleNotifications (event) {&#xA;    const value = event.target.value&#xA;    const temp = value.getUint16(0, true) / 100&#xA;&#xA;    console.log(&#39;Temperature is now &#39; + temp)&#xA;    document.getElementById(&#39;p1&#39;).innerHTML = temp&#xA;  }&#xA;&#xA;  function onDisconnected () {&#xA;    console.log(&#39;Disconnected.&#39;)&#xA;    button.innerHTML = &#39;Connect&#39;&#xA;    document.getElementById(&#39;p1&#39;).innerHTML = &#39;Disconnected&#39;&#xA;  }&#xA;})&#xA;&#xA;The index.html file looks like this:&#xA;&#xA;html&#xA;  head&#xA;    titleHydroloop/title&#xA;    meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1&#34;&#xA;    script src=&#34;main.js&#34;/script&#xA;    link rel=&#34;stylesheet&#34; type=&#34;text/css&#34; href=&#34;main.css&#34;&#xA;  /head&#xA;  body&#xA;    button id=&#34;connectButton&#34; class=&#34;buttons&#34;Connect/button&#xA;    p id=&#34;p1&#34; class=&#34;text&#34;/p&#xA;  /body&#xA;/html&#xA;&#xA;One Web Bluetooth discrepancy that I discovered between desktop Chrome and Chrome on Android, is that desktop Chrome will still read a value from a service, even if the characteristic is not set to be readable. Chrome on Android, on the other hand, will throw an error. That&#39;s why you see both readable: true and notify: true in the code above.&#xA;&#xA;This works great! Temperature readings in the browser are automatically updated, and it even shows you when you get disconnected from the sensor.&#xA;&#xA;---&#xA;I’m publishing this as part of 100 Days To Offload. You can join in yourself by visiting https://100daystooffload.com.&#xA;&#xA;#100DaysToOffload #day62 #hydroponics&#xA;&#xA;iComment on this post/i&#xD;&#xA;div id=&#34;cusdis_thread&#34;/div]]&gt;</description>
      <content:encoded><![CDATA[<p>For my hydroponics project, I would like to measure the ambient temperature. I&#39;ve <a href="https://gerritniezen.com/adding-timestamps-to-a-wireless-temperature-sensor">done this in the past</a> as a WiFi temperature sensor, but this time round I wanted to use my <a href="https://www.espruino.com/Pixl.js">Pixl.js</a> board with Bluetooth LE connectivity.</p>

<p>Here is the code to read data from a DS18B20 temperature sensor, display a temperature graph on the Pixl.js screen, and also make it available via Bluetooth LE as an <a href="https://www.bluetooth.com/specifications/assigned-numbers/environmental-sensing-service-characteristics/">Environmental Sensing Service</a>.</p>

<pre><code class="language-js">const PIN_SENSOR = A1;  // Pin temperature sensor is connected to

let ow, sensor, history;
let avrSum = 0, avrNum = 0;
let lastTemp = 0;

// Draw the temperature and graph to the screem
function draw() {
  g.clear();
  g.setFontVector(30);
  g.setFontAlign(0,-1);
  g.drawString(lastTemp.toFixed(1), 64,5);
  g.setFontBitmap();
  g.drawString(&#34;Temperature&#34;, 64, 0);
  require(&#34;graph&#34;).drawLine(g, history, {
    axes : true,
    gridy : 5,
    x:0, y:37,
    width:128,
    height:27
  });
  g.flip();
}

// Called when we get a temperature reading
function onTemp(temp) {
  if (temp === null) return; // bad reading
  avrSum += temp;
  avrNum++;
  lastTemp = temp;

  // send on BLE
  const th = Math.round(lastTemp * 100);
  NRF.updateServices({
    0x181A : { // environmental_sensing
      0x2A6E : { // temperature
        value : [th&amp;255,th&gt;&gt;8],
        readable: true,
        notify: true
      }
    }
  });

  draw();
}

// take temp reading and update graph
var readingInterval = setInterval(function() {
  if (sensor) sensor.getTemp(onTemp);
}, 10000);

// save average to history
var histInterval = setInterval(function() {
  history.set(new Float32Array(history.buffer,4));
  history[history.length-1] = avrSum / avrNum;
  avrSum = 0;
  avrNum = 0;
}, 60000); // 1 minute

// At startup, set up OneWire
function onInit() {
  try {
    ow = new OneWire(PIN_SENSOR);
    sensor = require(&#34;DS18B20&#34;).connect(ow);
    history = new Float32Array(128);
    avrSum=0;
    avrNum=0;

    NRF.setServices({
      0x181A: { // environmental_sensing
        0x2A6E: { // temperature
          notify: true,
          readable: true,
          value : [0x00, 0x00, 0x7F, 0xFF, 0xFF],
        }
      }
    }, { advertise: [ &#39;181A&#39; ] });
  } catch (e) {
    console.log(&#39;Error:&#39;, e);
  }
</code></pre>

<p>This code is based on the lovely Pixl.js <a href="https://www.espruino.com/Freezer+Alarm">Freezer Alarm</a> example.</p>

<p>I then wanted to be able to connect to the sensor via Web Bluetooth in the browser, which led to this web app:</p>

<pre><code class="language-js">document.addEventListener(&#39;DOMContentLoaded&#39;, event =&gt; {
  const button = document.getElementById(&#39;connectButton&#39;)
  let device

  button.addEventListener(&#39;click&#39;, async (e) =&gt; {
    try {
      if (button.innerHTML === &#39;Disconnect&#39;) {
        device.gatt.disconnect()
        button.innerHTML = &#39;Connect&#39;
        return
      }
      document.getElementById(&#39;p1&#39;).innerHTML = &#39;Connecting&#39;
      console.log(&#39;Requesting Bluetooth Device...&#39;)
      device = await navigator.bluetooth.requestDevice({
        filters: [{
          namePrefix: &#39;Pixl.js&#39;
        }],
        optionalServices: [&#39;environmental_sensing&#39;]
      })

      device.addEventListener(&#39;gattserverdisconnected&#39;, onDisconnected)

      console.log(&#39;Connecting to GATT Server...&#39;)
      const server = await device.gatt.connect()
      button.innerHTML = &#39;Disconnect&#39;

      console.log(&#39;Getting Environmental Sensing Service...&#39;)
      const service = await server.getPrimaryService(&#39;environmental_sensing&#39;)

      console.log(&#39;Getting Temperature Characteristic...&#39;)
      const characteristic = await service.getCharacteristic(&#39;temperature&#39;)

      console.log(&#39;Reading temperature...&#39;)
      const value = await characteristic.readValue()
      const temp = value.getUint16(0, true) / 100

      console.log(&#39;Temperature is &#39; + temp)
      document.getElementById(&#39;p1&#39;).innerHTML = temp

      await characteristic.startNotifications()

      console.log(&#39;Notifications started&#39;)
      characteristic.addEventListener(&#39;characteristicvaluechanged&#39;, handleNotifications)
    } catch (error) {
      console.log(&#39;Argh! &#39; + error)
      document.getElementById(&#39;p1&#39;).innerHTML = error
      button.innerHTML = &#39;Connect&#39;
    }
  })

  function handleNotifications (event) {
    const value = event.target.value
    const temp = value.getUint16(0, true) / 100

    console.log(&#39;Temperature is now &#39; + temp)
    document.getElementById(&#39;p1&#39;).innerHTML = temp
  }

  function onDisconnected () {
    console.log(&#39;Disconnected.&#39;)
    button.innerHTML = &#39;Connect&#39;
    document.getElementById(&#39;p1&#39;).innerHTML = &#39;Disconnected&#39;
  }
})
</code></pre>

<p>The <code>index.html</code> file looks like this:</p>

<pre><code class="language-html">&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;Hydroloop&lt;/title&gt;
    &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1&#34;&gt;
    &lt;script src=&#34;main.js&#34;&gt;&lt;/script&gt;
    &lt;link rel=&#34;stylesheet&#34; type=&#34;text/css&#34; href=&#34;main.css&#34;&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;button id=&#34;connectButton&#34; class=&#34;buttons&#34;&gt;Connect&lt;/button&gt;
    &lt;p id=&#34;p1&#34; class=&#34;text&#34;&gt;&lt;/p&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>

<p>One Web Bluetooth discrepancy that I discovered between desktop Chrome and Chrome on Android, is that desktop Chrome will still read a value from a service, even if the characteristic is not set to be readable. Chrome on Android, on the other hand, will throw an error. That&#39;s why you see both <code>readable: true</code> and <code>notify: true</code> in the code above.</p>

<p>This works great! Temperature readings in the browser are automatically updated, and it even shows you when you get disconnected from the sensor.</p>

<hr/>

<p>I’m publishing this as part of 100 Days To Offload. You can join in yourself by visiting <a href="https://100daystooffload.com">https://100daystooffload.com</a>.</p>

<p><a href="https://gerritniezen.com/tag:100DaysToOffload" class="hashtag"><span>#</span><span class="p-category">100DaysToOffload</span></a> <a href="https://gerritniezen.com/tag:day62" class="hashtag"><span>#</span><span class="p-category">day62</span></a> <a href="https://gerritniezen.com/tag:hydroponics" class="hashtag"><span>#</span><span class="p-category">hydroponics</span></a></p>

<p><i>Comment on this post</i>
<div id="cusdis_thread" id="cusdis_thread"></div></p>
]]></content:encoded>
      <guid>https://gerritniezen.com/building-a-bluetooth-le-temperature-sensor</guid>
      <pubDate>Thu, 17 Sep 2020 12:20:37 +0000</pubDate>
    </item>
  </channel>
</rss>