<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/rss-style.xsl" type="text/xsl"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	    xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	     xmlns:dc="http://purl.org/dc/elements/1.1/"
	   xmlns:atom="http://www.w3.org/2005/Atom"
	     xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	  xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>
<channel>
	<title>tildagon &#8211; Terence Eden’s Blog</title>
	<atom:link href="https://shkspr.mobi/blog/tag/tildagon/feed/" rel="self" type="application/rss+xml" />
	<link>https://shkspr.mobi/blog</link>
	<description>Regular nonsense about tech and its effects 🙃</description>
	<lastBuildDate>Thu, 28 Aug 2025 19:22:42 +0000</lastBuildDate>
	<language>en-GB</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://shkspr.mobi/blog/wp-content/uploads/2023/07/cropped-avatar-32x32.jpeg</url>
	<title>tildagon &#8211; Terence Eden’s Blog</title>
	<link>https://shkspr.mobi/blog</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title><![CDATA[Drawing PPM images on the Tildagon in MicroPython]]></title>
		<link>https://shkspr.mobi/blog/2024/06/drawing-ppm-images-on-the-tildagon-in-micropython/</link>
					<comments>https://shkspr.mobi/blog/2024/06/drawing-ppm-images-on-the-tildagon-in-micropython/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Wed, 19 Jun 2024 11:34:15 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[emfcamp]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[tildagon]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=50872</guid>

					<description><![CDATA[The Tildagon has 2MB of RAM. That&#039;s not enough to do... well, most things you&#039;d want to do with a computer!  There&#039;s not much processing power, so running complex image decoding algorithms might be a bit beyond it.  Is there a simple image format which can be parsed and displayed? Yes! The ancient Portable PixMap (PPM) format.    The standard is beautiful in its simplicity.  Here&#039;s the header: …]]></description>
										<content:encoded><![CDATA[<p>The Tildagon has 2MB of RAM. That's not enough to do... well, most things you'd want to do with a computer!  There's not much processing power, so running complex image decoding algorithms might be a bit beyond it.</p>

<p>Is there a simple image format which can be parsed and displayed? Yes! The ancient <a href="https://en.wikipedia.org/wiki/Netpbm">Portable PixMap</a> (PPM) format.</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2024/06/tildagonimages.jpg" alt="Various circuit boards showing images." width="512" height="517" class="aligncenter size-full wp-image-50873">

<p>The standard is beautiful in its simplicity.  Here's the header:</p>

<pre><code class="language-ppm">P6
# Created by GIMP version 2.10.38 PNM plug-in
120 120
255
���t�{...
</code></pre>

<p>The <code>P6</code> identifies it as a PPM file. The <code>#</code> is a comment. <code>120 120</code> says that the image's dimensions are 120 pixels horizontal, and 120 vertical. <code>255</code> is the maximum value for each colour.</p>

<p>Then comes a big blob of binary data. Each byte is a value from 0 to 255.</p>

<p>To find the Red, Green, and Blue values of the first pixel, read the first 3 bytes. The next 3 bytes are the RGB of the next pixel. And so on.</p>

<p>There's no compression. Just pure pixel values.</p>

<p>Because of the low memory limits of the Tildagon, I found it impossible to load the entire file into memory and then paint it on the screen.  Instead, I read it in chunks.</p>

<p>First, load the file as a read-only binary. Then skip the header and get straight to the pixel data.</p>

<pre><code class="language-python">#   Open a 120px x 120px Raw / Binary PPM file
with open('/apps/ppm/chrome120.ppm', 'rb') as ppm_file:
    print("Skipping Header")
    # Skip the header
    header = b''
    while True:
        line = ppm_file.readline()
        header += line
        if header.endswith(b'\n255\n'):
            break
</code></pre>

<p>Images on the Tildagon are drawn from the top left, which has co-ordinates -120,-120</p>

<pre><code class="language-python">    #   Start at the top left
    x, y = -120, -120
</code></pre>

<p>Next, read in a line of pixels. The image is 120px wide, each pixel has 3 values, so that's 360 bytes. Grab the pixel values and draw them to screen:</p>

<pre><code class="language-python">    while True:
        #   Read in 1 line at a time (3 bytes * 120px)
        chunk = ppm_file.read(360)
        if not chunk:
            break  # End of file
        #   Read the RGB, convert to float
        for i in range(0, len(chunk), 3):
            r = chunk[i]    /255
            g = chunk[i + 1]/255
            b = chunk[i + 2]/255
            #   Draw the pixel in a 2x2 square
            ctx.rgb(r, g, b).rectangle(x, y, 2, 2).fill()
</code></pre>

<p>The screen's resolution is 240x240, so each pixel from the 120x120 image needs to be drawn as 2x2 rectangle.</p>

<p>Once that's done, move to the next square to be drawn. Once a full line has been drawn, move down to drawing the next line.</p>

<pre><code class="language-python">            #   Move the the next square
            x += 2
            #   If a complete line has been drawn
            #   Move down a line (2px) and reset the x coordinate
            if x &gt;= 120:
                x = -120
                y += 2
        #   Clear the chunk from memory
        del chunk
        #   Perform garbage collection
        gc.collect()
</code></pre>

<p>A bit of manual garbage collection doesn't hurt! And then a bit more for good luck!</p>

<pre><code class="language-python">del(ppm_file)
#   Final collection
print("Collecting")
gc.collect()
</code></pre>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=50872&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2024/06/drawing-ppm-images-on-the-tildagon-in-micropython/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Setting the time on the Tildagon]]></title>
		<link>https://shkspr.mobi/blog/2024/06/setting-the-time-on-the-tildagon/</link>
					<comments>https://shkspr.mobi/blog/2024/06/setting-the-time-on-the-tildagon/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Mon, 17 Jun 2024 11:34:22 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[emf]]></category>
		<category><![CDATA[emfcamp]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[tildagon]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=50843</guid>

					<description><![CDATA[I&#039;m beginning my adventures in MicroPython in the hope that I&#039;ll have something interesting working on the Tildagon Badge for EMF2026.  Here&#039;s a basic implementation of a clockface.    Here&#039;s how to set the time on the badge. There&#039;s a hardware clock which should keep time between reboots.   Install mpremote on your computer. Connect the Tildagon to your computer using a USB-C data cable On your…]]></description>
										<content:encoded><![CDATA[<p>I'm beginning my adventures in MicroPython in the hope that I'll have something interesting working on the Tildagon Badge for EMF2026.  Here's a basic implementation of a clockface.</p>

<p><video width="854" height="480" src="https://shkspr.mobi/blog/wp-content/uploads/2024/06/Watch-Timer.mp4" autoplay="" loop="" muted=""></video></p>

<p>Here's how to set the time on the badge. There's a hardware clock which <em>should</em> keep time between reboots.</p>

<ol>
<li>Install <a href="https://docs.micropython.org/en/latest/reference/mpremote.html">mpremote</a> on your computer.</li>
<li>Connect the Tildagon to your computer using a USB-C data cable</li>
<li>On your computer's command line, run <code>mpremote</code>. You should see:
&gt; <code>Connected to MicroPython at /dev/ttyACM0</code>
&gt; <code>Use Ctrl-] or Ctrl-x to exit this shell</code></li>
<li>Hold down the <kbd>ctrl</kbd> key on your computer. While holding it down, press the <kbd>C</kbd> key on your computer. This will open up a shell for you to enter commands.</li>
<li>Enter the following commands one at a time, followed by enter</li>
</ol>

<pre><code class="language-python">from machine import RTC
rtc = RTC()
rtc.datetime()
</code></pre>

<p>That will display the time that the badge currently thinks it is.</p>

<p>For example: <code>(2000, 1, 1, 5, 0, 1, 47, 984022)</code></p>

<p>This is a slightly unusual format. It is: year, month, day, weekday, hours, minutes, seconds, subseconds.</p>

<p>The "weekday" is 0 for Monday, 1 for Tuesday etc.</p>

<p>This is an array. So, to access individual elements of the time, you can say:</p>

<pre><code class="language-python">year = rtc.datetime()[0]
</code></pre>

<p>Alternatively, you can do:</p>

<pre><code class="language-python">import time
time.localtime()
</code></pre>

<p>That will return something like: <code>(2024, 6, 11, 15, 11, 39, 1, 163)</code></p>

<p>Which, <a href="https://docs.micropython.org/en/latest/library/time.html#time.gmtime">according to the documentation</a>, is "year, month, mday, hour, minute, second, weekday, yearday".</p>

<h2 id="setting-the-clock"><a href="https://shkspr.mobi/blog/2024/06/setting-the-time-on-the-tildagon/#setting-the-clock">Setting the clock</a></h2>

<p>To manually set the date and time, run:</p>

<pre><code class="language-python">rtc.datetime((2023, 6, 16, 1, 15, 36, 0, 0))
</code></pre>

<h2 id="ntp"><a href="https://shkspr.mobi/blog/2024/06/setting-the-time-on-the-tildagon/#ntp">NTP</a></h2>

<p>If you want to use NTP to synchronise the time with an Internet-based atomic clock, here's what you need to do.</p>

<ol>
<li><a href="https://tildagon.badge.emfcamp.org/using-the-badge/connect-to-wifi/">Connect your badge to WiFi</a>.</li>
<li>As above, connect with USB and run <code>mpremote</code>, then obtain a shell.</li>
<li>Enter the following commands one at a time, followed by enter:</li>
</ol>

<pre><code class="language-python">import ntptime
ntptime.settime()
rtc.datetime()
</code></pre>

<p>That will now show you the synchronised time.  Note, it will be set to <strong>UTC</strong>.  There's no way to set the timezone - you'll have to deal with that in your code elsewhere.</p>

<p>You can also <a href="https://docs.micropython.org/en/latest/esp8266/quickref.html#real-time-clock-rtc">read the reference documentation about the Real Time Clock</a>.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=50843&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2024/06/setting-the-time-on-the-tildagon/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Displaying a QR code in MicroPython on the Tildagon Badge]]></title>
		<link>https://shkspr.mobi/blog/2024/06/displaying-a-qr-code-in-micropython-on-the-tildagon-badge/</link>
					<comments>https://shkspr.mobi/blog/2024/06/displaying-a-qr-code-in-micropython-on-the-tildagon-badge/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sat, 15 Jun 2024 11:34:16 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[emf]]></category>
		<category><![CDATA[emfcamp]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[QR Codes]]></category>
		<category><![CDATA[tildagon]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=50822</guid>

					<description><![CDATA[This was a bit of a labour of love - and something I wanted to get running during EMF Camp. I&#039;m documenting in the hope it&#039;ll be useful for EMF 2026!  Here&#039;s the end result:    Background  I&#039;m going to assume that you have updated your badge to the latest firmware version.  You will also need to install mpremote on your development machine.  You should also have successfully run the basic Hello,…]]></description>
										<content:encoded><![CDATA[<p>This was a bit of a labour of love - and something I wanted to get running during EMF Camp. I'm documenting in the hope it'll be useful for EMF 2026!</p>

<p>Here's the end result:</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2024/06/Hex-Badge-QR.jpg" alt="A hexagonal circuit board with a circular screen. The screen displays a monochrome QR code." width="1024" height="771" class="aligncenter size-full wp-image-50828">

<h2 id="background"><a href="https://shkspr.mobi/blog/2024/06/displaying-a-qr-code-in-micropython-on-the-tildagon-badge/#background">Background</a></h2>

<p>I'm going to assume that you have updated your badge to the latest firmware version.</p>

<p>You will also need to <a href="https://docs.micropython.org/en/latest/reference/mpremote.html">install <code>mpremote</code></a> on your development machine.</p>

<p>You should also have successfully run the basic <a href="https://tildagon.badge.emfcamp.org/tildagon-apps/development/">Hello, World!</a> app.</p>

<h2 id="drawing-surface"><a href="https://shkspr.mobi/blog/2024/06/displaying-a-qr-code-in-micropython-on-the-tildagon-badge/#drawing-surface">Drawing surface</a></h2>

<p>The Tildagon screen is 240x240 pixels. However, it is also a circle.  This gives an internal square of 170x170 pixels.  The drawing co-ordinates have 0,0 in the centre. Which means the target area is the red square as shown here:</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2024/06/target-size.png" alt="A red square with co-ordinates displayed over it." width="240" height="240" class="aligncenter size-full wp-image-50823" style="border-radius: unset !important; border: none !important;">

<h2 id="generate-a-qr-code"><a href="https://shkspr.mobi/blog/2024/06/displaying-a-qr-code-in-micropython-on-the-tildagon-badge/#generate-a-qr-code">Generate a QR code</a></h2>

<p>As you can see, there isn't much space here. A <a href="https://www.qrcode.com/en/about/version.html">Version 1 QR Code</a> is a mere 21x21 pixels. When set to "Low" error correction, it can contain up to 25 characters.  A URl should start with https:// - which is 8 characters.  That leaves 17 characters for the domain and path.</p>

<p>Use your favourite QR generator to make the tiniest QR code you can.  Make sure there's no border. It should be 21x21 pixels. Here's mine:</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2024/06/21tinyqr.png" alt="A very small QR code." width="21" height="21" class="aligncenter size-full wp-image-50824" style="border-radius: unset !important; border: none !important;" image-rendering:="" pixelated;="">

<p>See? Tiny!</p>

<h2 id="prepare-the-qr-code"><a href="https://shkspr.mobi/blog/2024/06/displaying-a-qr-code-in-micropython-on-the-tildagon-badge/#prepare-the-qr-code">Prepare the QR code</a></h2>

<p>Next, we need to turn the QR code into a binary matrix. There may be easier ways to do this, but I used a scrap of Python:</p>

<pre><code class="language-python">from PIL import Image
import numpy as np

#    Load the image
image = Image.open("qr.png")

#    Convert the image to grayscale
gray_image = image.convert("L")

#    Threshold the image to get binary black and white image
threshold = 128
bw_image = gray_image.point(lambda x: 0 if x &gt; threshold else 1, '1')

#    Convert the image to a NumPy array
pixel_array = np.array(bw_image, dtype=int)

#    Convert the array to a string with commas between the elements
array_str = np.array2string(pixel_array, separator=',', formatter={'int':lambda x: str(x)})

print(array_str)
</code></pre>

<p>Copy the output - we'll need it later!</p>

<h2 id="calculate-size"><a href="https://shkspr.mobi/blog/2024/06/displaying-a-qr-code-in-micropython-on-the-tildagon-badge/#calculate-size">Calculate size</a></h2>

<p>We have a canvas of 170 pixels and a QR code of 21 pixels.  170 / 21 = 8.1 pixels. Ah. Drawing fractional pixels isn't fun. Luckily, QR codes benefit from having a safe area around them.  If we make each QR pixel 7 screen pixels, that gives us (21 x 7) = 147 pixels. Which gives us enough space for a small white border.</p>

<p>If the QR code is to be centred, the top left corner will be in position (147 / 2) = 74.  That means it will need to be drawn at position -74,-74.  The top left corner is -120,-120.</p>

<p>So the offset used to calculate the location is (-120 + 74) = 46.</p>

<p>(You might be able to get away with 8 pixels and an offset of 36 pixel. Try it!)</p>

<p>Remember those numbers!</p>

<h2 id="write-the-app"><a href="https://shkspr.mobi/blog/2024/06/displaying-a-qr-code-in-micropython-on-the-tildagon-badge/#write-the-app">Write the app</a></h2>

<p>This reuses a lot of the Hello World code.</p>

<pre><code class="language-python">import app
from app_components import TextDialog, clear_background
from events.input import Buttons, BUTTON_TYPES

class QrApp(app.App):
    #   Define the colours
    black = (  0,   0,   0)
    white = (255, 255, 255)

    def __init__(self):
        self.button_states = Buttons(self)

    def update(self, delta):
        if self.button_states.get(BUTTON_TYPES["CANCEL"]):
            self.button_states.clear()
            self.minimise()

    def draw(self, ctx):
        clear_background(ctx)

        #   QR code data (21x21 matrix)
        qr_code =[[1,1,1,1,1,1,1,0,0,1,1,0,0,0,1,1,1,1,1,1,1],
                  [1,0,0,0,0,0,1,0,1,0,1,0,0,0,1,0,0,0,0,0,1],
                  [1,0,1,1,1,0,1,0,1,1,1,1,1,0,1,0,1,1,1,0,1],
                  [1,0,1,1,1,0,1,0,0,1,1,0,0,0,1,0,1,1,1,0,1],
                  [1,0,1,1,1,0,1,0,1,1,1,1,0,0,1,0,1,1,1,0,1],
                  [1,0,0,0,0,0,1,0,1,1,0,1,0,0,1,0,0,0,0,0,1],
                  [1,1,1,1,1,1,1,0,1,0,1,0,1,0,1,1,1,1,1,1,1],
                  [0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0],
                  [1,1,0,1,0,0,1,1,0,0,1,0,0,0,1,1,1,0,1,1,0],
                  [1,0,1,1,1,1,0,1,1,0,0,1,1,0,1,1,1,0,0,0,1],
                  [1,0,1,0,0,1,1,1,0,1,1,0,1,0,0,0,0,0,1,0,1],
                  [1,1,1,1,0,1,0,1,0,0,0,0,0,0,1,0,1,1,0,1,1],
                  [1,0,1,0,0,1,1,1,0,0,1,1,1,0,0,1,0,1,0,0,0],
                  [0,0,0,0,0,0,0,0,1,0,0,0,1,1,0,1,0,0,0,0,1],
                  [1,1,1,1,1,1,1,0,1,0,1,0,1,1,0,0,1,1,1,1,0],
                  [1,0,0,0,0,0,1,0,0,0,0,1,1,0,0,1,1,0,0,0,0],
                  [1,0,1,1,1,0,1,0,0,0,1,1,0,1,0,0,1,1,0,1,1],
                  [1,0,1,1,1,0,1,0,1,0,0,0,1,0,1,0,1,0,0,0,1],
                  [1,0,1,1,1,0,1,0,0,0,1,1,1,0,1,0,1,0,1,0,1],
                  [1,0,0,0,0,0,1,0,1,1,1,0,0,1,0,0,0,0,0,0,0],
                  [1,1,1,1,1,1,1,0,1,0,1,0,1,0,1,0,1,0,0,1,0]]

        #   Draw background
        ctx.rgb(*self.white).rectangle(-120, -120, 240, 240).fill()

        #   Size of each QR code pixel on the canvas
        pixel_size = 7

        #   Offset size in pixels
        offset_size = 46

        #   Calculate the offset to start drawing the QR code (centre it within the available space)
        offset = -120 + offset_size

        #   Loop through the array
        for row in range(21):
            for col in range(21):
                if qr_code[row][col] == 1:
                    x = (col * pixel_size) + offset
                    y = (row * pixel_size) + offset
                    ctx.rgb(*self.black).rectangle(x, y, pixel_size, pixel_size).fill()

__app_export__ = QrApp
</code></pre>

<h2 id="installation"><a href="https://shkspr.mobi/blog/2024/06/displaying-a-qr-code-in-micropython-on-the-tildagon-badge/#installation">Installation</a></h2>

<ul>
<li><a href="https://tildagon.badge.emfcamp.org/tildagon-apps/run-on-badge/">Follow the instructions</a></li>
<li>Run <code>mpremote cp ~/Documents/badge/* :/apps/qr/</code></li>
<li>Restart the badge</li>
<li>Scroll down the app list and launch the QR app</li>
</ul>

<h2 id="the-non-stupid-way"><a href="https://shkspr.mobi/blog/2024/06/displaying-a-qr-code-in-micropython-on-the-tildagon-badge/#the-non-stupid-way">The non-stupid way!</a></h2>

<p>OK, that was the hard way - here's the easy way.</p>

<p>Use the MicroPython QR Generation library <a href="https://github.com/JASchilz/uQR/blob/master/uQR.py">uQR</a>.</p>

<p>If you pop that file in your project directory, and upload it to the badge, then you can import it with:</p>

<pre><code class="language-python">from .uQR import QRCode
</code></pre>

<p>The QR code has its own white margin and is a 2D array of True &amp; Falses.</p>

<pre><code class="language-python"># QR code data (29x29 matrix)
qr = QRCode()
qr.add_data("https://edent.tel")
qr_code = qr.get_matrix()
qr_size = len( qr_code )

#   Draw background
ctx.rgb(*self.white).rectangle(-120, -120, 240, 240).fill()

#   Size of each QR code pixel on the canvas
pixel_size = int( 170 / qr_size ) + 1

#   Border size in pixels
border_size = ( 240 - (pixel_size*qr_size) ) / 2

#   Calculate the offset to start drawing the QR code (centre it within the available space)
offset = -120 + border_size

#   Loop through the array
for row in range( len(qr_code) ):
    for col in range( len(qr_code) ):
        if qr_code[row][col] == True:
            x = (col * pixel_size) + offset
            y = (row * pixel_size) + offset
            ctx.rgb(*self.black).rectangle(x, y, pixel_size, pixel_size).fill()
</code></pre>

<h2 id="next-steps"><a href="https://shkspr.mobi/blog/2024/06/displaying-a-qr-code-in-micropython-on-the-tildagon-badge/#next-steps">Next steps</a></h2>

<ul>
<li>This is hardcoded for a single QR code - mine! Perhaps it should be configurable?</li>
<li>Add some text to the screen?</li>
<li>Animations? Colour? Flashing LEDs?</li>
</ul>

<p>Got any thoughts? Stick them in the box!</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=50822&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2024/06/displaying-a-qr-code-in-micropython-on-the-tildagon-badge/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
	</channel>
</rss>
