tag:blogger.com,1999:blog-32519203743295579692024-03-28T05:54:21.632+00:00RAREblogRaspberry Pi, Arduino, Robotics and ElectronicsRomilly Cockinghttp://www.blogger.com/profile/04944907795639823332noreply@blogger.comBlogger315125tag:blogger.com,1999:blog-3251920374329557969.post-23406851092116817182023-10-12T16:29:00.000+01:002023-10-12T16:29:16.405+01:00Get rid of Magic Numbers from your code<p>Last November I got really perplexed.</p>
<p>I was looking at some code for the Pimoroni Explorer Base. I love that hardware. It comes with lots of useful peripherals and plenty of sample code to show you how to control them.</p>
<p>One of the examples is called noise. It plays a simple tune on the Explorer Base Piezo buzzer, and shows the rise and fall of the notes on the built-in display.</p>
<p>You tell the program what notes to play by creating a list like this:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb1-1"><a aria-hidden="true" href="#cb1-1"></a>tones <span class="op">=</span> [<span class="st">"AS6"</span>, <span class="st">"A6"</span>, <span class="st">"AS6"</span>, <span class="st">"P"</span>, <span class="st">"AS5"</span>, <span class="st">"P"</span>, <span class="st">"AS5"</span>, <span class="st">"P"</span>, <span class="st">"F6"</span>, <span class="st">"DS6"</span>, <span class="st">"D6"</span>, <span class="st">"F6"</span>, <span class="st">"AS6"</span>,]</span></code></pre></div>
<p>The code is easy to follow, but one thing really puzzled me.</p>
<p>The program turns each note into a frequency by looking up the name of the note in <code>tones</code>, a massive dictionary with names as keys and integer frequencies as values.</p>
<p>I could read the code, but I was still puzzled.</p>
<h2 id="where-did-all-those-frequencies-come-from">Where did all those frequencies come from?</h2>
<p>Each frequency is a <em>magic number</em>: a value that a program uses without explaining its origin. Magic numbers bother me, and in this case they make up most of the code!</p>
<p>I had a dim memory of the <a href="https://en.wikipedia.org/wiki/Chromatic_scale">chromatic scale</a> from music lessons long ago. After a little research I started to understand what was going on.</p>
<h3 id="the-chromatic-scale">The chromatic scale</h3>
<p>The names of the notes are based on the chromatic scale. That’s a series of twelve named tones that make up an octave. Since the tune in the code extends beyond a single octave, each note needs to specify the note name and the number of the octave it’s part of.</p>
<p>For example ‘A4’ is the note ‘A’ in octave number 4.</p>
<h3 id="how-do-you-convert-a-note-to-a-frequency">How do you convert a note to a frequency?</h3>
<p>In the chromatic scale the frequency of each tone has a fixed relation to the semitones above and below it.</p>
<p>An octave represents a doubling in frequency, so the ratio between semitones is the twelfth root of 2. That’s approximately 1.0595.</p>
<p>Now you know the <em>relationships</em> between notes, but what’s the anchor - the frequency to which the other notes relate?</p>
<h3 id="a440-pitch">A440 pitch</h3>
<p>The notes in the code are based on the <a href="https://en.wikipedia.org/wiki/A440_(pitch_standard)">A440 pitch standard</a>. That standard specifies that the frequency of the note A4 is 440 Hertz (cycles/second).</p>
<p>Now I had enough information to work out the relationship between note names and their frequencies. I wrote a Python module to do that, and I also fixed a couple of other minor niggles.</p>
<h3 id="take-a-look-at-the-new-code">Take a look at the new code</h3>
<p>You can see the code <a href="https://gist.github.com/romilly/e3bb09d0b965fc5c9183f6f696b59425">here</a>. Comments in the code explain where all the numbers come from.</p>
<h2 id="get-rid-of-magic-numbers">Get rid of Magic Numbers!</h2>
<p>The time you spend in getting rid of magic numbers will be <em>time well spent</em>.</p>
<p>The code will express intent more clearly, and you may even learn something useful!</p>
Romilly Cockinghttp://www.blogger.com/profile/04944907795639823332noreply@blogger.com0tag:blogger.com,1999:blog-3251920374329557969.post-78481377262739763782023-06-07T15:43:00.001+01:002023-06-07T15:43:54.482+01:00Draw pictures on your Pico!<p>Would you like to draw pictures on your Pico? </p><p>A while ago I experimented with <a href="https://blog.rareschool.com/2021/01/pi-pico-emulating-etch-sketch.html" target="_blank">sketcher</a> - a program for the Rasperry Pi Pico that connected to a Raspberry Pi. The Pico had two potentiometers that you could use to move a virtual pen in the Pi; the Pi ran the turtle graphics package which it used to draw the pictures.</p><p>The project was fun, and I wondered how easy it would be to run a similar program that would use a display with the Pico.</p><p>That potential project sat in my pending tray for ages, but I recently got an incentive to re-visit it.</p><h4 style="text-align: left;">A new resource</h4><p>Pimoroni have asked me to write a booklet for their <a href="https://shop.pimoroni.com/products/pico-explorer-base?variant=32369514315859" target="_blank">Pico Explorer Base</a>. I've been a fan of the product since it first came out, and I'm really enjoying a chance to revisit its awesome capabilities.</p><p>I decided I'd write a simple sketcher program that ran on the Pico. It uses two potentiometers to move a virtual pen up/down and left/right. The pen draws on the Explorer base display.</p><p>I've now got a simple version running, and I intend to enhance it over the next day or so.</p><h4 style="text-align: left;">Here's the first sample output.</h4><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEj1bwmbWWldgh2KMFyGQris1m5gDLUmWVEF5IEz6fNVanhYfDkOHSz04sM3s-eiFv7CIyVQC7UK9Yd-ynsj5RfyPxUgJ7Cnqy67KwrQ9r8actj8Sx4Z__GFQtAPxpqIMgS4KLt3FcmYTNwDDSeaT4jQiWx54U8nmQcJgaXKYECbbt9m5u1m8CKogAVv" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img alt="" data-original-height="4856" data-original-width="4704" height="240" src="https://blogger.googleusercontent.com/img/a/AVvXsEj1bwmbWWldgh2KMFyGQris1m5gDLUmWVEF5IEz6fNVanhYfDkOHSz04sM3s-eiFv7CIyVQC7UK9Yd-ynsj5RfyPxUgJ7Cnqy67KwrQ9r8actj8Sx4Z__GFQtAPxpqIMgS4KLt3FcmYTNwDDSeaT4jQiWx54U8nmQcJgaXKYECbbt9m5u1m8CKogAVv" width="232" /></a></div><p></p><p>I'd like to claim that it's a carefully designed image of a dog, but it's actually the result of a completely random experiment. The software works, though, and users more dexterous than me should be able to create some more artistic images.</p><p>The booklet is still under development, and won't be available for a while, but when it is you'll be able to read about it on <a href="https://twitter.com/rareblog" target="_blank">Twitter</a>, on this blog, and on the Pimoroni website.</p><p><br /></p><p><br /></p>Romilly Cockinghttp://www.blogger.com/profile/04944907795639823332noreply@blogger.com0tag:blogger.com,1999:blog-3251920374329557969.post-59302913050018484102023-05-09T18:07:00.001+01:002023-10-03T08:52:07.086+01:00ChatGPT Meets de Bono: A Powerful Combo <p>I'm struggling to make a decision at the moment, so I've enlisted the help of ChatGPT.</p><p>I am experimenting a lot with some of the new AI tools, and it's a bit like playing whack-a-mole at the moment; as soon as I get to grips with one bit of software, another appears and demands my attention.</p><p>A lot of what I'm doing is based on OpenAI's ChatGPT. I'm concerned that I'll spend too much on paid services, so I am actively looking at Open Source alternatives that I could run locally.</p><p></p>Alas, many Large Language Models (LLMs) are too resource-hungry to run on my workstation, and I've been thinking of trying out a Jetson Xavier which I'm not using at the moment.<p></p><h4 style="text-align: left;">Worth the time?</h4><p></p><p>I know I'll need to spend a few days getting to the point where I know whether I can use it for the projects I have in mind. Should I invest the time?</p><p>I decide to take a Judo approach and use ChatGPT to help me make the decision.</p><p>I used a prompt that's worked well for me before now. It based on de Bono's Six Thinking Hats.</p><p>You've probably come across the Six Thinking Hats technique before, but if not you can read about them on <a href="https://en.wikipedia.org/wiki/Six_Thinking_Hats" target="_blank">Wikipedia</a>.</p><h4 style="text-align: left;"><span style="font-family: inherit;">The Prompt and Response</span></h4><p>Here's my question, along with ChatGPT's response. I've made minor tweaks to the format but the text is unchanged.</p><div style="background-color: white; color: #080808; font-family: "JetBrains Mono", monospace; font-size: 9.8pt; white-space: pre;">I'm thinking of running an LLM as an alternative to ChatGPT on my 16 GB Jetson Xavier
developer kit.
<br />Use Edward de Bono's Six Thinking Hats to help me evaluate that idea.<br /><br /><b>ChatGPT</b><br /><br />Edward de Bono's Six Thinking Hats is a powerful decision-making tool that encourages looking at a problem or idea from<br />multiple perspectives. In this case, we'll use it to evaluate running an LLM (Large Language Model) as an alternative to<br />ChatGPT on a 16 GB Jetson Xavier developer kit.<br /><br /><h4 style="text-align: left;">White Hat (Facts and Information):</h4><ol style="text-align: left;"><li><span style="font-size: 9.8pt;">The Jetson Xavier developer kit has 16 GB of RAM and a powerful GPU,
making it suitable for running AI models.</span></li><li><span style="font-size: 9.8pt;">LLMs typically require significant computational power and memory.</span></li><li><span style="font-size: 9.8pt;">ChatGPT is based on GPT-4, which is also an LLM.</span></li></ol><h4 style="text-align: left;">Red Hat (Emotions and Intuition):</h4><ol style="text-align: left;"><li>You might feel excited about running an LLM on your Jetson Xavier kit,
as it could be a challenge and an opportunity to learn.</li><li>Concerns may arise about whether the hardware is capable of handling
such a large model efficiently.</li></ol><h4 style="text-align: left;">Black Hat (Critical Judgement):</h4><ol style="text-align: left;"><li><span style="font-size: 9.8pt;">Running an LLM on a Jetson Xavier developer kit might be limited by the
16 GB of RAM, depending on the size of the </span><span style="font-size: 9.8pt;">model.</span></li><li><span style="font-size: 9.8pt;">Performance and latency might be issues due to the large size of the LLM,
affecting real-time applications.</span></li><li><span style="font-size: 9.8pt;">Power consumption and heat generation could be concerns when running a
large model continuously.</span></li></ol><h4 style="text-align: left;">Yellow Hat (Optimism and Benefits):</h4><ol style="text-align: left;"><li><span style="font-size: 9.8pt;">Implementing an LLM on the Jetson Xavier kit could lead to a deeper
understanding of AI models and their deployment on </span><span style="font-size: 9.8pt;">edge devices.</span></li><li><span style="font-size: 9.8pt;">It could potentially offer better performance compared to smaller models,
resulting in more accurate and contextually </span><span style="font-size: 9.8pt;">aware responses.</span></li><li><span style="font-size: 9.8pt;">Developing and optimising an LLM for the Jetson Xavier kit could
contribute to the growing AI community.</span></li></ol><h4 style="text-align: left;">Green Hat (Creativity and Alternatives):</h4><ol style="text-align: left;"><li>Consider using a smaller, more efficient model that is optimised
for edge devices, while still providing a satisfactory level of performance.</li><li>Explore the possibility of using model distillation or pruning techniques
to create a smaller model suitable for the Jetson Xavier kit.</li><li>Investigate other hardware solutions or cloud-based platforms
that may better accommodate the computational requirements of an LLM.</li></ol><h4 style="text-align: left;">Blue Hat (Process and Summary):</h4>In summary, running an LLM as an alternative to ChatGPT on a 16 GB Jetson Xavier developer kit has its pros and cons.<br />While it may offer better performance and accuracy, it might also present challenges related to hardware limitations,<br />power consumption, and heat generation. Consider exploring alternative models or hardware solutions, or optimizing the<br />LLM for edge devices. Ultimately, the decision will depend on the specific use case and the desired balance between<br />performance and efficiency.<br /></div><div style="background-color: white; color: #080808; font-family: "JetBrains Mono", monospace; font-size: 9.8pt; white-space: pre;"><br /></div>
<h4 style="text-align: left;">Not bad for an AI!</h4><p>I'm impressed by the response, as I have been with other uses of the prompt. I've also used two other de Bono frameworks: Six Value Medals, and Six Action Shoes. You can ask ChatGPT about them.</p><p><i>Pro tip:</i> Make sure you are using ChatGPT 4, as version 3.5 has confused Action Shoes with the Thinking Hats!<br /><br />The books are less well known but they can form the basis of useful prompts.</p><p>You'll also see from the Wikipedia article about Six Thinking Hats that there are recommended sequences of Hat use for tackling particular types of problem. These could form the basis of further useful prompts for ChatGPT.</p><div class="post-body entry-content" id="post-body-3133517635497932729" itemprop="articleBody" style="background-color: white; color: #222222; font-size: 17.6px; line-height: 1.4; position: relative; width: 780px;"><div style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: #374151; font-size: 16px; margin: 1.25em 0px 0px; text-align: left; white-space: pre-wrap;"><span style="font-family: inherit;">I'll share more useful prompts as I discover them. You can keep track of them by following me on Twitter at <a href="https://twitter.com/rareblog" style="color: #2288bb; text-decoration-line: none;" target="_blank">@rareblog</a>.</span></div></div><p><br /></p>Romilly Cockinghttp://www.blogger.com/profile/04944907795639823332noreply@blogger.com0tag:blogger.com,1999:blog-3251920374329557969.post-31335176354979327292023-05-08T11:22:00.000+01:002023-05-08T11:22:32.800+01:00Finding the Best Solution with ChatGPT: A Personal Experience<p style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: #374151; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; margin: 0px 0px 1.25em; white-space: pre-wrap;">When you're developing software for technically savvy users, you'll often encounter an annoying problem: users provide their preferred solution instead of their requirements.</p><p style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: #374151; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; margin: 0px 0px 1.25em; white-space: pre-wrap;">This issue is even more pronounced when you are both developer and user.</p><p style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: #374151; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; margin: 0px 0px 1.25em; white-space: pre-wrap;">In such situations, you're likely to choose the first solution that seems to meet your needs, even though there might be better alternatives.</p><p style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: #374151; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; margin: 1.25em 0px; white-space: pre-wrap;">Pair programming can help, as your partner can spot what's going on and suggest other ideas. But what if you're programming on your own?</p><p style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: #374151; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; margin: 1.25em 0px; white-space: pre-wrap;">That's where ChatGPT comes in handy.</p><h2 style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: var(--tw-prose-headings); font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; line-height: 1.33333; margin: 2rem 0px 1rem; white-space: pre-wrap;">ChatGPT to the Rescue</h2><p style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: #374151; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; margin: 0px 0px 1.25em; white-space: pre-wrap;">I've recently been working on an application that downloads interesting <a href="https://arxiv.org/">arXiv </a>PDFs, and I wanted to access them remotely. I could think of a couple of ways to do that, but I decided to ask ChatGPT for advice. I used a prompt that had been helpful before:</p><p style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: #374151; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; margin: 1.25em 0px; white-space: pre-wrap;">"<span style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: var(--tw-prose-bold); font-weight: 600;">Suggest three ways I could accomplish that, with the pros, cons, and interesting features of each approach.</span>"</p><p style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: #374151; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; margin: 1.25em 0px; white-space: pre-wrap;">I presented my problem: I have a directory containing PDFs on a Raspberry Pi computer, and I want to be able to read the PDF files from Python on another Linux computer.</p><p style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: #374151; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; margin: 1.25em 0px; white-space: pre-wrap;">ChatGPT suggested three approaches, complete with pros and cons for each:</p><ol style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: #374151; counter-reset: item 0; display: flex; flex-direction: column; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; list-style-image: initial; list-style-position: initial; margin: 1.25em 0px; padding: 0px 0px 0px 1rem; white-space: pre-wrap;"><li style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; margin: 0px; padding-left: 0.375em;"><span style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: var(--tw-prose-bold); font-weight: 600; margin-bottom: 1.25em; margin-top: 1.25em;">File sharing with Samba</span></li><li style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; margin: 0px; padding-left: 0.375em;"><span style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: var(--tw-prose-bold); font-weight: 600; margin-bottom: 1.25em; margin-top: 1.25em;">Remote access with SSHFS</span></li><li style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; margin: 0px; padding-left: 0.375em;"><span style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: var(--tw-prose-bold); font-weight: 600; margin-bottom: 1.25em; margin-top: 1.25em;">Web server with a file browser</span></li></ol><p style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: #374151; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; margin: 1.25em 0px; white-space: pre-wrap;">I had considered using Samba shares or a web server, but I had forgotten about <a href="https://en.wikipedia.org/wiki/SSHFS">SSHFS</a>. Since I already had password-less SSH access to the Raspberry Pi, SSHFS was incredibly easy to set up, and it has been working very well.</p><h2 style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: var(--tw-prose-headings); font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; line-height: 1.33333; margin: 2rem 0px 1rem; white-space: pre-wrap;">Reusable Prompt for Problem-Solving</h2><p style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: #374151; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; margin: 0px 0px 1.25em; white-space: pre-wrap;">Here's the reusable prompt I mentioned earlier:</p><p style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: #374151; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; margin: 1.25em 0px; white-space: pre-wrap;">"<span style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: var(--tw-prose-bold); font-weight: 600;">Suggest three ways I could accomplish that, with the pros, cons, and interesting features of each approach.</span>"</p><p style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: #374151; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; margin: 1.25em 0px; white-space: pre-wrap;">I now use this prompt whenever I'm trying to find a solution to a problem, and ChatGPT consistently delivers valuable insights.</p><p style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: #374151; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; margin: 1.25em 0px 0px; white-space: pre-wrap;">I'll share more useful prompts as I discover them. You can keep track of them by following me on Twitter at <a href="https://twitter.com/rareblog" target="_blank">@rareblog</a>.</p>Romilly Cockinghttp://www.blogger.com/profile/04944907795639823332noreply@blogger.com0tag:blogger.com,1999:blog-3251920374329557969.post-25091844063921570042023-04-28T16:19:00.005+01:002023-04-28T16:24:06.464+01:00Pair Programming with ChatGPT<p> </p>
<p>Earlier today I started work on a small admin project to help me organise my excessive collection of repositories on GitHub.</p>
<p>I wanted a list of my repositories, showing the name, url, description, stars and forks.</p>
<p>I decided to get ChatGPT to write the code for me.</p>
<p>In the conversation that follows, you can imagine an embodied ChatBot sitting next to me as we pair program. I'm the customer, ChatGPT is the programmer. If I'm not happy with the results, I ask my pair to change the code.</p><p>My part is in italics. Everything else came from ChatGPT.</p>
<p>As you'll see, there are a couple of wrinkles I didn't anticipate, but ChatGPT fixed them.</p>
<p>This took less time than I'd have taken to write the code, and it was much more fun. I recommend it.</p>
<h4 style="text-align: left;">Here's the session.</h4>
<p><em>Write python to find the names of all my repositories on GitHub, My username is romilly.</em></p>
<p>You can use the GitHub API to retrieve the names of all your repositories. Here is the Python code to achieve that:</p>
<pre><code>import requests
# Replace <username> with your GitHub username
username = "romilly"
# Make a GET request to the GitHub API to retrieve your repositories
response = requests.get(f"https://api.github.com/users/{username}/repos")
# Check if the response was successful
if response.status_code == 200:
# Loop through the list of repositories and print their names
for repo in response.json():
print(repo["name"])
else:
print("Failed to retrieve repositories")</code></pre>
<p>This code uses the requests library to make a GET request to the GitHub API endpoint that retrieves your repositories. It then loops through the list of repositories in the response JSON and prints their names.</p>
<p><em>Modify that to output the name, url, description, stars and forks</em></p>
<p>Sure! Here's the modified code that retrieves and prints the name, URL, description, stars, and forks count of all your repositories:</p>
<pre><code>import requests
# Replace <username> with your GitHub username
username = "romilly"
# Make a GET request to the GitHub API to retrieve your repositories
response = requests.get(f"https://api.github.com/users/{username}/repos")
# Check if the response was successful
if response.status_code == 200:
# Loop through the list of repositories and print their details
for repo in response.json():
print(f"Name: {repo['name']}")
print(f"URL: {repo['html_url']}")
print(f"Description: {repo['description'] or 'No description available'}")
print(f"Stars: {repo['stargazers_count']}")
print(f"Forks: {repo['forks_count']}")
print("="*50)
else:
print("Failed to retrieve repositories")</code></pre>
<p>This code uses the same requests library to make a GET request to the GitHub API endpoint that retrieves your repositories. It then loops through the list of repositories in the response JSON and prints their name, URL, description, stars count, and forks count. If the repository has no description, the code prints “No description available” instead. Finally, the code prints a line of equal signs (=) to separate each repository's details.</p>
<p><em>That only prints the fist few repositories. Modify the code to paginate though the full set.</em></p>
<p>You're right! By default, the GitHub API only returns the first 30 repositories, so we need to paginate through the full set to retrieve all your repositories. Here's the modified code that uses pagination:</p>
<pre><code>import requests
# Replace <username> with your GitHub username
username = "romilly"
# Set the base URL for the GitHub API endpoint that retrieves your repositories
base_url = f"https://api.github.com/users/{username}/repos"
# Initialize the page number to 1
page = 1
# Loop through the pages of repositories
while True:
# Make a GET request to the current page of the GitHub API endpoint
response = requests.get(f"{base_url}?per_page=100&page={page}")
# Check if the response was successful
if response.status_code == 200:
# If the response is empty, we have reached the last page of repositories
if len(response.json()) == 0:
break
# Loop through the list of repositories on the current page and print their details
for repo in response.json():
print(f"Name: {repo['name']}")
print(f"URL: {repo['html_url']}")
print(f"Description: {repo['description'] or 'No description available'}")
print(f"Stars: {repo['stargazers_count']}")
print(f"Forks: {repo['forks_count']}")
print("="*50)
# Move to the next page
page += 1
else:
print(f"Failed to retrieve repositories from page {page}")
break</code></pre>
<p>This code initialises the page number to 1 and loops through the pages of repositories using a while loop. It makes a GET request to the current page of the GitHub API endpoint with a per_page parameter of 100, which is the maximum number of repositories that the API allows to be returned per page. If the response is successful and not empty, the code loops through the list of repositories on the current page and prints their details. Finally, the code moves to the next page and repeats the process until there are no more repositories to retrieve. If the response is unsuccessful, the code prints an error message and breaks out of the loop.</p>
<p><em>What's the rate limit for GitHub API requests?</em></p>
<p>The rate limit for unauthenticated requests to the GitHub API is 60 requests per hour, while the rate limit for authenticated requests varies based on your authentication status and whether you are using a personal access token.</p>
<p>Here are the rate limits for authenticated requests:</p>
<pre><code>Personal access token: 5,000 requests per hour
OAuth token: 5,000 requests per hour
OAuth token with public_repo or repo scope: 5,000 requests per hour
GitHub App installation token: 5,000 requests per hour
GitHub Actions token: 1,000 requests per hour</code></pre>
<p>It's important to note that the rate limit is calculated on a per-IP address basis, so if multiple people are using the same IP address to make API requests, their combined rate limit will be shared.</p>
<p>To check your rate limit status, you can make a GET request to the following endpoint:</p>
<pre><code>https://api.github.com/rate_limit</code></pre>
<p>This endpoint returns a JSON object that contains information about your current rate limit status, including the number of remaining requests and the time at which the limit resets.</p>
<p><em>Modify the code to check the rate limit before each request. If the limit has been reached, delay the request until the limit resets.</em></p>
<p>Good idea! Here's the modified code that checks the rate limit before each request and delays the request until the limit resets if it has been reached:</p>
<pre><code>import requests
import time
# Replace <username> with your GitHub username
username = "romilly"
# Set the base URL for the GitHub API endpoint that retrieves your repositories
base_url = f"https://api.github.com/users/{username}/repos"
# Initialize the page number to 1
page = 1
while True:
# Check the rate limit status before making the request
response = requests.get("https://api.github.com/rate_limit")
if response.status_code != 200:
print("Failed to retrieve rate limit status")
break
remaining_requests = response.json()["resources"]["core"]["remaining"]
reset_timestamp = response.json()["resources"]["core"]["reset"]
if remaining_requests == 0:
# If we have reached the rate limit, delay the request until the limit resets
delay_seconds = reset_timestamp - time.time() + 1
print(f"Rate limit reached. Waiting for {delay_seconds:.2f} seconds...")
time.sleep(delay_seconds)
# Make a GET request to the current page of the GitHub API endpoint
response = requests.get(f"{base_url}?per_page=100&page={page}")
# Check if the response was successful
if response.status_code == 200:
# If the response is empty, we have reached the last page of repositories
if len(response.json()) == 0:
break
# Loop through the list of repositories on the current page and print their details
for repo in response.json():
print(f"Name: {repo['name']}")
print(f"URL: {repo['html_url']}")
print(f"Description: {repo['description'] or 'No description available'}")
print(f"Stars: {repo['stargazers_count']}")
print(f"Forks: {repo['forks_count']}")
print("="*50)
# Move to the next page
page += 1
else:
print(f"Failed to retrieve repositories from page {page}")
break</code></pre>
<p>This code first makes a GET request to the rate limit endpoint to retrieve the number of remaining requests and the time at which the limit resets. If we have reached the rate limit, the code calculates the number of seconds until the limit resets and waits for that amount of time using the time.sleep() function. After the delay, the code makes the GET request to the current page of the GitHub API endpoint as before. If the response is successful, the code loops through the list of repositories on the current page and prints their details. Finally, the code moves to the next page and repeats the process until there are no more repositories to retrieve. If the response is unsuccessful, the code prints an error message and breaks out of the loop.</p><h4 style="text-align: left;">What next?</h4><div>I'm pretty happy with what ChatGPT did, but just like a real user <b>I still want some changes </b>- a database and a GUI to query it. I'll see if ChatGPT has time to do that and let you know.</div><div><br /></div><div>I'll be posting on twitter as <a href="https://twitter.com/rareblog" target="_blank">RAREblog</a>.</div><div><br /></div><div>If you found this interesting, please let others know: </div><div><br /><a href="https://twitter.com/intent/tweet?url=https://blog.rareschool.com/2023/04/pair-programming-with-chatgpt.html&text=Pair%20Programming%20with%20ChatGPT%20is%20fun!" target="_blank">Share on Twitter</a>
</div><div><br /></div>
Romilly Cockinghttp://www.blogger.com/profile/04944907795639823332noreply@blogger.com0tag:blogger.com,1999:blog-3251920374329557969.post-53730634769834015642023-04-22T14:22:00.002+01:002023-04-22T14:22:49.709+01:00Can ChatGPT code like a pro?<p>I've been experimenting with the code generation capabilities of ChatGPT, and I'm blown away.</p><p>As a test of its capabilities I decided to ask it to write a generic version of some code I've used, over and over, in slightly different forms, in real applications.</p><h4 style="text-align: left;">The Problem</h4><p>The code implements a Throttler - a class that wraps something that makes requests and makes sure that requests are not made too often.</p><p>Lots of APIs apply rate limits to requests, and this code makes it easy to enforce those limits within your application.</p><h4 style="text-align: left;">A coding session with ChatGPT</h4><p>Here'es the session I had with ChatGPT. My instructions are <i>in italics</i>. Everything else - code, comments, and explanations - was written by ChatGPT in response.</p>
<p><em>Write Python to create an Abstract Class called Requester. It should have a single abstract method called request that takes an argument called command. Add docstring comments.</em></p>
<p>Here is the Python code to create an Abstract Class called Requester with a single abstract method called request that takes an argument called command:</p>
<pre><code>from abc import ABC, abstractmethod
class Requester(ABC):
"""
Abstract class for requesting commands.
"""
@abstractmethod
def request(self, command):
"""
Abstract method for requesting a command.
Args:
command (str): The command to request.
Returns:
None
"""
pass</code></pre>
<p>In this code, we import the ABC (Abstract Base Class) module from the abc package. Then, we define the Requester class as an abstract class that inherits from ABC. We also define the single abstract method called request, which takes an argument called command. The docstring comments provide information about the class and the abstract method, including the arguments and return values.</p>
<p><em>Write code and docstring comments for a subclass called MockRequester which stores each request in a list. The list should be initialised in the constructor init method.</em></p>
<p>Here is the Python code to create a subclass called MockRequester which stores each request in a list:</p>
<pre><code>class MockRequester(Requester):
"""
Subclass of Requester that stores each request in a list.
"""
def __init__(self):
"""
Constructor for MockRequester that initializes an empty list to store the requests.
"""
self.requests = []
def request(self, command):
"""
Method to request a command and store it in the list of requests.
Args:
command (str): The command to request.
Returns:
None
"""
self.requests.append(command)</code></pre>
<p>In this code, we define the MockRequester class as a subclass of Requester. We also define the constructor method <strong>init</strong> which initializes an empty list to store the requests. The request method is also defined and it takes the command argument, which it stores in the list of requests using the append method. The docstring comments provide information about the class and methods, including the arguments and return values.</p>
<p><em>Now add a Throttler class subclass of Requester which takes a requester and a gap (a number of seconds) in its constructor. The Throttler should make sure that successive requests are separated by the sepcified number of seconds. As before, add docstring comments and add type hints.</em></p>
<p>Here is the Python code to create a Throttler class, a subclass of Requester, which takes a requester and a gap (a number of seconds) in its constructor:</p>
<pre><code>import time
class Throttler(Requester):
"""
Subclass of Requester that adds throttling functionality to limit the rate of requests.
Args:
requester (Requester): The requester object that Throttler will throttle.
gap (float): The number of seconds to wait between each request.
"""
def __init__(self, requester: Requester, gap: float):
"""
Constructor for Throttler that initializes the requester and the gap.
Args:
requester (Requester): The requester object that Throttler will throttle.
gap (float): The number of seconds to wait between each request.
"""
self.requester = requester
self.gap = gap
self.last_request_time = None
def request(self, command: str) -> None:
"""
Method to request a command with throttling between requests.
Args:
command (str): The command to request.
Returns:
None
"""
if self.last_request_time is not None:
time_since_last_request = time.time() - self.last_request_time
time_to_wait = self.gap - time_since_last_request
if time_to_wait > 0:
time.sleep(time_to_wait)
self.requester.request(command)
self.last_request_time = time.time()</code></pre>
<p>In this code, we define the Throttler class as a subclass of Requester. We also define the constructor method <strong>init</strong> which takes a requester object and a gap parameter. The request method is also defined which makes sure that the time gap between successive requests is at least the specified gap in seconds, before forwarding the request to the requester object. The docstring comments provide information about the class and methods, including the arguments and return values, as well as the types of the arguments.</p>
<p><em>Write a unit test using the unit test framework, a Throttler and the MockThrottler class to verify that the delay is working correctly. The test should take less than one tenth of a second to run. Add comments for the test.</em></p>
<p>Here is the Python code for a unit test that uses the Throttler and MockRequester classes to verify that the delay is working correctly:</p>
<pre><code>import unittest
class TestThrottler(unittest.TestCase):
"""
Unit test for Throttler class.
"""
def test_throttling(self):
"""
Test that the Throttler enforces the specified delay between requests.
"""
mock_requester = MockRequester()
throttler = Throttler(mock_requester, 0.1)
# Send 3 requests and verify that the delay between requests is at least 0.1 seconds
start_time = time.time()
throttler.request("command 1")
throttler.request("command 2")
throttler.request("command 3")
end_time = time.time()
# Verify that the requests were received in the correct order
self.assertEqual(mock_requester.requests, ["command 1", "command 2", "command 3"])
# Verify that the total time taken is at least 0.2 seconds (2 x 0.1 seconds)
self.assertGreaterEqual(end_time - start_time, 0.2)</code></pre>
<p>In this code, we define a unit test case TestThrottler that tests the Throttler class. The test method test_throttling creates a MockRequester object and a Throttler object with a delay of 0.1 seconds. It then sends 3 requests using the Throttler and verifies that the requests were received in the correct order. Finally, it checks that the total time taken is at least 0.2 seconds (2 x 0.1 seconds) to verify that the delay is working correctly. The comments provide information about the purpose of the test and what it is testing.</p>
<h4 style="text-align: left;">Conclusion</h4><p>I can't believe the level of skill that ChatGPT is showing. </p><p>Not only does it do a great job at coding, but it correctly worked out what I really wanted when I mistakenly asked it to use a MockThrottler instead of a MockRequester in the test!</p><p>I've been using it for coding tasks for the last three days and I've created documented, tested code about five times faster than I would on my own.</p><p><a href="https://chat.openai.com/" target="_blank">Try it yourself</a>.</p>Romilly Cockinghttp://www.blogger.com/profile/04944907795639823332noreply@blogger.com0tag:blogger.com,1999:blog-3251920374329557969.post-88235134095470644662023-04-20T17:31:00.003+01:002023-04-20T18:13:14.950+01:00Will ChatGPT save your job - or take it over?<p>Wondering whether ChatGPT will make you more effective, or do your job so well that you are no longer needed?</p><p>ChatGPT is one of the new generation of AI tools. You can ask it for help and it can turn its hand to a huge range of tasks.</p><h4 style="text-align: left;">ChatGPT as a Python coder</h4><p>Today I used it to write a very simple but useful computer application. The task would have taken me half an hour or so. ChatGPT did the job in a few seconds, and the code it write was well-structured, readable and well commented.</p><p>So far so good - but what about other tasks?</p><p>I decided to make a short video about what I'd done. (Watch this space to see it when it's ready). </p><p>I knew that my first step would be to write a script.</p><p><i>Could I ask ChatGPT to help me do that?</i></p><p>Sure.</p><p>Here's what happened next.</p><h4 style="text-align: left;">ChatGPT as scriptwriter</h4><p>Here's what I typed:</p><blockquote><p><span face="Söhne, ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif, Helvetica Neue, Arial, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji" style="color: #343541;"><span style="white-space: pre-wrap;">Earlier today I got ChatGPT to write a simple application for me. Now I want to make a video showing the process and the result. I'd also like to explain why the automation of small coding tasks can have a huge impact on personal productivity in the medium term. Help me by writing a rough script for the video.</span></span></p></blockquote><p>Here's how ChatGPT responded:</p><blockquote><p style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: #374151; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; margin: 0px 0px 1.25em; white-space: pre-wrap;">Sure, I can help you with that. Here's a rough script for the video:</p><p style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: #374151; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; margin: 1.25em 0px; white-space: pre-wrap;">[Introduction]
Hello, in this video, we're going to show you how to automate small coding tasks and why it can have a huge impact on your personal productivity in the medium term.</p><p style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: #374151; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; margin: 1.25em 0px; white-space: pre-wrap;">[Explanation of the problem]
As a developer, you know that even small coding tasks can take up a lot of time and energy. For example, writing a simple application may require hours of coding, debugging, and testing.</p><p style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: #374151; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; margin: 1.25em 0px; white-space: pre-wrap;">[Solution: automation]
But what if there was a way to automate these tasks? That's where automation tools come in. By using automation tools, you can save time and focus on more important tasks that require your attention.</p><p style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: #374151; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; margin: 1.25em 0px; white-space: pre-wrap;">[Demo]
Let's demonstrate this with an example. Earlier today, I asked ChatGPT, a large language model trained by OpenAI, to write a simple application for me. Here's how it works:</p><p style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: #374151; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; margin: 1.25em 0px; white-space: pre-wrap;">[Show the code generated by ChatGPT]</p><p style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: #374151; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; margin: 1.25em 0px; white-space: pre-wrap;">As you can see, ChatGPT was able to generate the code in just a few seconds. This saved me a lot of time and effort that I would have otherwise spent writing the code myself.</p><p style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: #374151; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; margin: 1.25em 0px; white-space: pre-wrap;">[Benefits of automation]
Now, let's talk about the benefits of automation. By automating small coding tasks, you can:</p><ul style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: #374151; display: flex; flex-direction: column; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; list-style-image: initial; list-style-position: initial; margin: 1.25em 0px; padding: 0px 0px 0px 1rem; white-space: pre-wrap;"><li style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; display: block; margin: 0px; padding-left: 0.375em; position: relative;">Save time and increase productivity</li><li style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; display: block; margin: 0px; padding-left: 0.375em; position: relative;">Focus on more important tasks</li><li style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; display: block; margin: 0px; padding-left: 0.375em; position: relative;">Reduce errors and improve code quality</li><li style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; display: block; margin: 0px; padding-left: 0.375em; position: relative;">Learn new skills by exploring automation tools</li><li style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; display: block; margin: 0px; padding-left: 0.375em; position: relative;">Free up mental space for creative thinking and problem-solving</li></ul><p style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: #374151; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; margin: 1.25em 0px 0px; white-space: pre-wrap;">[Conclusion] </p><p><span face="Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"" style="background-color: #f7f7f8; color: #374151; font-size: 16px; white-space: pre-wrap;">In conclusion, automation can have a huge impact on your personal productivity in the medium term. By using automation tools, you can save time, reduce errors, and focus on more important tasks. So why not explore automation tools and see how they can help you become a more productive developer? Thank you for watching!</span> </p></blockquote><h4 style="text-align: left;">Want to play? </h4><p><span face="Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"" style="color: #343541; font-size: 16px; white-space: pre-wrap;">What do you need to do to use OpenAI's ChartGPT demo? I asked ChatGPT. Here's what it said.</span></p><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><p style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: #374151; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; margin: 0px 0px 1.25em; white-space: pre-wrap;">To use OpenAI's ChatGPT demo, follow these steps:</p></blockquote><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><ul style="text-align: left;"><li>Go to the OpenAI website (<a href="https://beta.openai.com/demo/" style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; text-underline-offset: 2px;" target="_new">https://chat.openai.com/</a>) and scroll down to find the "Chat" demo.</li><li>Click on the "Chat" demo button to open it.</li><li>Once the demo is open, you will see a text box where you can type in your message. Type in a message and hit the "Enter" key or click the "Send" button to send your message.</li><li>ChatGPT will respond to your message with a generated response. You can continue the conversation by typing in another message and hitting "Enter" or clicking the "Send" button again.</li><li>You can also adjust the settings of the demo by clicking on the gear icon in the top right corner. Here, you can change the model, the temperature, and the maximum length of the response.</li></ul></blockquote><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><p style="--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-ring-color: rgba(59,130,246,0.5); --tw-ring-offset-color: #fff; --tw-ring-offset-shadow: 0 0 transparent; --tw-ring-offset-width: 0px; --tw-ring-shadow: 0 0 transparent; --tw-rotate: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-scroll-snap-strictness: proximity; --tw-shadow-colored: 0 0 transparent; --tw-shadow: 0 0 transparent; --tw-skew-x: 0; --tw-skew-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; background-color: #f7f7f8; border: 0px solid rgb(217, 217, 227); box-sizing: border-box; color: #374151; font-family: Söhne, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Ubuntu, Cantarell, "Noto Sans", sans-serif, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; margin: 1.25em 0px 0px; white-space: pre-wrap;">Note that the demo is intended for demonstration purposes only and may not be suitable for all use cases. Also, keep in mind that ChatGPT is a language model and may generate responses that are inappropriate or offensive. It's important to use ChatGPT responsibly and ethically.</p></blockquote><p><b><a href="https://chat.openai.com/" target="_blank">Give it a spin</a></b>, and let us know how you got on! </p><p><br /></p><blockquote><p> </p></blockquote><p> </p>Romilly Cockinghttp://www.blogger.com/profile/04944907795639823332noreply@blogger.com0tag:blogger.com,1999:blog-3251920374329557969.post-87286637875802592862023-01-29T17:27:00.000+00:002023-01-29T17:27:25.381+00:00Complex Network Analysis<p> I've recently been working on two fascinating, challenging projects.</p><p>One is a guide to the Pimoroni Explorer Base. That's going to take a while, and I'll report on progress as i go.</p><p>The other is a tool to help me keep track of published papers on some of the AI areas the interest me.</p><p>Therese's so much published that it's a real challenge to keep up. I rely heavily on the wonderful service provided by Semantic Scholar; that helps me keep track of new papers that cite older papers that I've identified as interesting.</p><p>Here's the citation network for one of the papers that interest me:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWJSD_Hj4oc0nH_yxeFiWXSYxG2rP2_J5PpGIr8OMvL7yTnCO8wyeDh6ZpryfwtIhBY4GvLHK45odNTJXcMWXwCOPAX8xvkPv0px0Z1FJJwZVEtfuvBAkMgDTuJokQ23cGDriMGyg67i8RyZPKEee_gcOjdnQZj1kJmHf85mFh-LSJDVhBJVLnceAf/s743/bigger-citation-graph.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="653" data-original-width="743" height="351" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWJSD_Hj4oc0nH_yxeFiWXSYxG2rP2_J5PpGIr8OMvL7yTnCO8wyeDh6ZpryfwtIhBY4GvLHK45odNTJXcMWXwCOPAX8xvkPv0px0Z1FJJwZVEtfuvBAkMgDTuJokQ23cGDriMGyg67i8RyZPKEee_gcOjdnQZj1kJmHf85mFh-LSJDVhBJVLnceAf/w400-h351/bigger-citation-graph.png" width="400" /></a></div><br /><p>I've done quite a bit of work on networks with Python, but I've recently been reading <a href="https://www.amazon.co.uk/Complex-Network-Analysis-Python-Zinoviev/dp/1680502697/" target="_blank">Complex Network Analysis in Python</a> by Dmitry Zinoviev. It's well-written, broad in coverage, and has plenty of useful sample code.</p><p>I love it. </p>Romilly Cockinghttp://www.blogger.com/profile/04944907795639823332noreply@blogger.com0tag:blogger.com,1999:blog-3251920374329557969.post-79457680540525469802022-08-25T16:46:00.002+01:002022-08-25T16:50:47.121+01:00How to write reliable tests for Python MQTT applications<p>More and more IoT applications use MQTT. It's a simple and very useful messaging framework which runs on small boards like the Raspberry Pi Pico as well as systems running under Linux, MacOS and Windows.</p><p>I recently decided to add some extra functionality to Lazydoro using MQTT. The code seemed to work when run manually but I had a lot of trouble getting my automated tests working. It took quite a while to understand the problem, but the fix was simple.</p><h3 style="text-align: left;">Intermittently failing tests are bad</h3><p>In the end-to-end test that was causing the problem, the code simulated the start of a pomodoro session and then checked that the correct MQTT message had been sent. The test usually failed but sometimes passed. When I manually ran a separate client that subscribed to the message stream I could see that the right messages were being sent.</p><p>Intermittently failing (or passing) tests are a nuisance. They do nothing to build confidence that the application under test is working reliably, and they are no help when you're refactoring. You can never be sure if the tested fail because you made a mistake in the refactoring, or was it just having one of its hissy fits?</p><h4 style="text-align: left;">Solving timing problems</h4><p>Intermittent failures like this are often due to timing issues. It's tempting to solve them by adding delays to the testing code, but this is prone to problems. Too short a delay, and the tests still fail from time to time; too long a delay, and the tests become burdensome to run.</p><p>The solution is simple; write your test so that it polls to see if the expected condition is true, and set a timeout so that it will only expire if the test is going to fail.</p><p>Before the test checks that the correct message has been sent, it waits until there is a message to check.</p><p>Here's the code that waits:</p>
<pre><code>
def wait_for_message(self, tries = 100, interval = 0.01):
for i in range(tries):
if len(self.messages()) > 0:
return
sleep(interval)
raise ValueError('waiting for message - timed out')
</code></pre>
Now the tests run reliably.<div><br /></div><div>You can see the entire Test Client code <a href="https://github.com/romilly/tdd-lazydoro/blob/master/test/test_tdd_lazydoro/helpers/mqtt_test.py" target="_blank">here</a>.</div>Romilly Cockinghttp://www.blogger.com/profile/04944907795639823332noreply@blogger.com0tag:blogger.com,1999:blog-3251920374329557969.post-5709315482483534342022-08-11T18:27:00.000+01:002022-08-11T18:27:43.780+01:00How can you make your Pico/Pico W project portable?<div style="text-align: left;">In this article you'll learn how to solder the Pimoroni Lipo Shim for Raspberry Pi Pico/Pico W and use it to power
your project from a single LiPo battery. You'll also discover a problem, and a solution, if you want to check the
battery level remotely.</div>
<p>Some Pico W projects need to work anywhere. Wi-Fi takes care of the connectivity, but the projects need battery
power to make them truly portable.</p>
<p>If you're building a portable project you'll find the Pimoroni Lipo Shim for Pico is a great solution.
Here's how to attach and use it.</p>
<h2>Powering the Pico</h2>
<p>While you're writing the software for your Pico or Pico are you'll be using a USB cable to linking it to
your host. Once your hardware and software are working you may want to free the Pico from its umbilical
cord.</p>
<p>The Pico can be powered from a battery; it's very adaptable, working off an input voltage that can range from 1.8 to
5.5 volts.</p>
<p>The <a href="https://shop.pimoroni.com/products/pico-lipo-shim?variant=32369543086163" target="_blank">Pimoroni Lipo Shim for Pico</a> takes advantage of that. It lets you power your project from a compact LiPo battery,
and you can recharge the battery via USB when it needs it. You can even use the Pico while the battery is charging.</p>
<p>There are lots of suitable batteries available, from Pimoroni and others, but my current favourite is the <a href="https://shop.pimoroni.com/products/galleon-400mah-battery?variant=40061068673107" target="_blank">Pimoroni Galleon</a>; its
rugged case reduces the risk of crushing or puncturing the LiPo.</p>
<p>So how do you hook up the LiPo shim to the Pico it's powering?</p>
<p>Pimoroni suggest two options.</p>
<h2>Connecting the Pico</h2>
<p>Both options involve some soldering. One is simple; the other, Pimoroni suggest, is suitable for advanced solderers.
You can connect the Pico to other components via female header sockets or male header pins.</p>
<h2>The simple option</h2>
<p>You want to connect two layers of PCB, one for the Pico and one for the Shim.
You can use stacking female headers like this:</p>
<p><img alt="image" height="320" src="http://images.rareschool.com/img/950477f4-197e-11ed-b085-bd6601aa7128-stacking-headers.png" title="" width="320" /></p>
<p><em>Image courtesy of Pimoroni</em>.</p>
<p>You get a bonus (sockets on the top of the Pico, giving you extra connection options), but there are two disadvantages:
your project is now quite a bit bigger, and you may find it fiddly to press the <code>bootsel</code> button if you need to.</p>
<p>There's an easy fix to the <code>bootsel</code> issue; you can use mpremote's <code>bootsel</code> command, or run <code>machine.bootsel()</code> from a
REPL.</p>
<p>If space is a problem, you have another option.</p>
<h2>For advanced solderers</h2>
<p>From the Pimoroni site:</p>
<p>"Alternatively, if you're ambitious in the ways of experimental soldering, you can try soldering the Pico and the SHIM both to the short end of your header, back to back. This method makes for a much more slimline Pico/SHIM package which works nicely with Pico Display, but you'll need to make sure your solder joints make good contact with the pads of both boards and the header."</p>
<p>Here's how you do it, in words and pictures.</p>
<h4 style="text-align: left;">Step 1</h4><div style="text-align: left;">Push 2 rows of 20-pin male headers <strong>part way</strong> into a breadboard and place the shim on top. Make sure that the</div>
<p>button of the shim is located below the shim and below where the USB connector of the Pico will be.</p>
<p><img alt="image" height="174" src="http://images.rareschool.com/img/80b9915a-1981-11ed-b085-bd6601aa7128-01-shim-on-headers.jpg" title="" width="400" /></p>
<p><img alt="image" height="265" src="http://images.rareschool.com/img/f9e73bb8-1981-11ed-b085-bd6601aa7128-02-shim-on-headers.jpg" title="" width="400" /></p>
<h4 style="text-align: left;"><span style="font-weight: normal;">Step 2</span></h4><div style="text-align: left;"><span style="font-weight: normal;">Lay the Pico/PicoW on top of the shim</span></div>
<p><img alt="image" height="270" src="http://images.rareschool.com/img/6bb1e900-1982-11ed-b085-bd6601aa7128-03-picow-on-top-of-shim.jpg" title="" width="400" /></p>
<h4 style="text-align: left;">Step 3</h4><div style="text-align: left;">Solder the corners of the Pico to the headers and check that everything is correctly aligned.</div>
<p>If not, it's fairly easy to melt the solder on the relevant corner and fix the alignment.</p>
<p><img alt="image" height="190" src="http://images.rareschool.com/img/e51a0fd4-1982-11ed-b085-bd6601aa7128-04-solder-corners.jpg" title="" width="400" /></p>
<h4 style="text-align: left;">Step 4</h4><div style="text-align: left;">You may want to check the underside of the Pico as well as the top.</div>
<p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEhrYeu2BN-9YPKEQ4FlMCAPgH46mhfzBqkCyH7G7fXT5qBuMqON06omzw60BfERbX39hWFqW6i9vs8dJYH4gxFFMIIJk6vVouURQwhtGjtRmDFQNZjRUvrsTtqGlNtYOGCxBvJzW3spm2_Iabv5xiCN0N07h9f7DMKKIsXj_677_9v94y7tf1zV7yty" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="2088" data-original-width="4520" height="185" src="https://blogger.googleusercontent.com/img/a/AVvXsEhrYeu2BN-9YPKEQ4FlMCAPgH46mhfzBqkCyH7G7fXT5qBuMqON06omzw60BfERbX39hWFqW6i9vs8dJYH4gxFFMIIJk6vVouURQwhtGjtRmDFQNZjRUvrsTtqGlNtYOGCxBvJzW3spm2_Iabv5xiCN0N07h9f7DMKKIsXj_677_9v94y7tf1zV7yty=w400-h185" width="400" /></a></div><br /><br /><p></p>
<h4 style="text-align: left;"><br /></h4><h4 style="text-align: left;"><br /></h4><h4 style="text-align: left;"><br /></h4><h4 style="text-align: left;">Step 5</h4><div style="text-align: left;">Now solder more of the header pins, filling the castellations at the side of the Pico and shim with solder.</div>
<p><img alt="image" height="278" src="http://images.rareschool.com/img/30a0a624-1984-11ed-b085-bd6601aa7128-06-more-soldering.jpg" title="" width="400" /></p>
<h4 style="text-align: left;">Step 6</h4><div style="text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjDdIKzHNwbMBg9zXS6wlnbQcNFBCxpVQpEnT7XDPNRku_lTugM1GBO3RPrl52MT-QHob2sBDryXR8HkWiK-irHvRafa8A3O5VzzvZHybwOK5Hkcu8m46Sn_eCHIKsbgz49mTwWQF4GhTiqee996o2O-S7ui_kWG0KkR0hcpFNAzU41wKgz97ruemnv" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="3392" data-original-width="7288" height="186" src="https://blogger.googleusercontent.com/img/a/AVvXsEjDdIKzHNwbMBg9zXS6wlnbQcNFBCxpVQpEnT7XDPNRku_lTugM1GBO3RPrl52MT-QHob2sBDryXR8HkWiK-irHvRafa8A3O5VzzvZHybwOK5Hkcu8m46Sn_eCHIKsbgz49mTwWQF4GhTiqee996o2O-S7ui_kWG0KkR0hcpFNAzU41wKgz97ruemnv=w400-h186" width="400" /></a></div><br /></div>
<p><br /></p>
<p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>If all is well you should see a white LED, showing that the Pico is powered, and a red LED, showing that the
battery is charging.</p>
<p>NB: You may need to push the button on the end of the shim to turn the power on.</p>
<p>Well done! You are now an official <em>advanced solderer</em> :)</p>
<h2>Checking the battery voltage</h2>
<p>If you're using a Pico you can easily use an OLED display to show the state of the battery. There's a useful program
to do that on the Pimoroni website, but it won't work for the PicoW without a change. That's because it uses ADC29
(also known as ADC3) to monitor the Vsys voltage.</p>
<p>You can do that on the Pico W, but you need to pull P25 high which disables wireless. Since I wanted to use MQTT
over a wireless connection to monitor the battery, I had to find an alternative solution.</p>
<p>A couple of the projects I had in mind used two of the three normal ADC pins on the pico, but none used all three.,
If your project doesn't need three Analogue inputs, you can use one to monitor the battery voltage.</p>
<p>That needs a little care, as the GPIO pins on the Pico (including the analogue pins) should not be connected to more
than 3.3 volts. A fully charged Lipo might be outputting 4.2 volts, which would damage the analogue pin.</p>
<p>Fortunately there's an easy solution.</p>
<h3>Using a voltage divider.</h3>
<p>You can use a voltage divider to reduce Vsys to a safe voltage. I used two 10K ohm resistors, but 100K would be
better, as that would reduce battery drain. </p>
<p>Then you can safely read the adc output and convert it to a voltage.
Remember to multiply it two to compensate for the divider!</p>
<p>Here's the breadboard layout for the divider:</p>
<p><img alt="image" height="289" src="http://images.rareschool.com/img/d6ce66ce-1990-11ed-b085-bd6601aa7128-divider_bb.png" title="" width="400" /></p>
<p>and here's the schematic:</p>
<p><img alt="image" height="395" src="http://images.rareschool.com/img/61c88e3a-1991-11ed-b085-bd6601aa7128-divider_schem.png" title="" width="400" /></p>
<h2 style="text-align: left;">The code</h2>
<pre style="text-align: left;"><code># This example shows how to read the voltage from a LiPo battery
# connected to a Raspberry Pi Pico via the Pimoroni LiPo shim for Pico
# secrets.py should contain your network id and password in this format:
"""
SSID = 'your Wi-Fi network id'
PASSWORD = 'your Wi-Fi password'
MQTT_HOST='broker url'
"""
from machine import ADC, Pin
import time
import random
import network_connection
from umqtt.simple import MQTTClient
from secrets import SSID, PASSWORD, MQTT_HOST
# connect to wifi
# this may take several seconds
network_connection.connect(SSID, PASSWORD)
print('connected')
CLIENT_ID = 'pico-lipo-monitor-%d' % (1000 + random.randrange(999))
mc = MQTTClient(CLIENT_ID, MQTT_HOST, keepalive=3600)
mc.connect()
adc2 = ADC(28)
conversion_factor = 2 * 3.3 / 65535
while True:
voltage = adc2.read_u16() * conversion_factor
message = 'Lipo voltage: %f' % voltage
mc.publish('lipo', message)
time.sleep(60)</code></pre><h2 style="text-align: left;">Summary</h2><p>That's how you can connect the Pimoroni Lipo shim for Pico and the Pico W, and how you can then monitor the LiPo battery voltage.</p>
<p>I'll be publishing the hardware and software details of the weather station project in my forthcoming guide to the
Pico W. Read more details <a href="https://www.rareschool.com/projects.html">here</a>.</p>
Romilly Cockinghttp://www.blogger.com/profile/04944907795639823332noreply@blogger.com0tag:blogger.com,1999:blog-3251920374329557969.post-12642909312241974412022-08-09T15:14:00.111+01:002022-08-12T08:27:06.600+01:00Raspberry Pi PicoW projects on Tom's Hardware PiCast<p>A few days ago I was asked to present some of my Raspberry Pi Pico W projects on the PiCast from <a href="https://www.tomshardware.com/uk" target="_blank">Tom's Hardware</a>.</p>Editor Avram Piltch (<span style="background-color: white; color: #536471; font-family: TwitterChirp, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-size: 15px; white-space: nowrap;"><a href="https://twitter.com/geekinchief" target="_blank">@geekinchief</a></span>) was the super-friendly host as usual, and Les Pounder (<a href="https://twitter.com/biglesp">@biglesp</a>) told us about his Pico W-based webserver. Raspberry Pi expert <a href="http://twitter.com/@AshPuckettB2B" target="_blank">Ash Hill</a> added to the fun.<div><br /></div><div>Ash edits Tom's hardware's monthly <a href="https://www.tomshardware.com/features/best-raspberry-pi-projects" target="_blank">Best Raspberry Pi Projects</a> feature - always worth a read.</div><div><h4 style="text-align: left;">Pico W projects a-plenty</h4><p>I had to dig out an extra USB hub to drive all the projects I showed!</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2yx57lPOt7p6hx2SVdVxOD4dkohhyXkxBKFNBuvYLvi60_Cv5o43Vu1IDFRt8uXIC81D6BtvOZCbGQJJfFfA_g7vXpV9FXtxjhXaPw8OKlFYBWAMdhZMITiNVKgcWiC6NJQ2V07BZKf4uN-mhGOTDUgCOI9FCpWAAUtWOfo-p8SMSwHQBuDKV1vfO/s6760/toms-hw-props.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="4600" data-original-width="6760" height="218" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2yx57lPOt7p6hx2SVdVxOD4dkohhyXkxBKFNBuvYLvi60_Cv5o43Vu1IDFRt8uXIC81D6BtvOZCbGQJJfFfA_g7vXpV9FXtxjhXaPw8OKlFYBWAMdhZMITiNVKgcWiC6NJQ2V07BZKf4uN-mhGOTDUgCOI9FCpWAAUtWOfo-p8SMSwHQBuDKV1vfO/s320/toms-hw-props.jpg" width="320" /></a></div><br /><p><br /></p><h4 style="text-align: left;">Missed it? Don't worry!</h4><p>You can <a href="https://www.youtube.com/watch?v=RkgotMVeV8o" target="_blank">watch a recording on YouTube</a>:</p><div class="separator" style="clear: both; text-align: center;"><iframe allowfullscreen="" class="BLOG_video_class" height="266" src="https://www.youtube.com/embed/RkgotMVeV8o" width="320" youtube-src-id="RkgotMVeV8o"></iframe></div><br /><p><br /></p><p><br /></p><p><br /></p></div>Romilly Cockinghttp://www.blogger.com/profile/04944907795639823332noreply@blogger.com0tag:blogger.com,1999:blog-3251920374329557969.post-25671868642814044672022-08-03T18:34:00.005+01:002022-08-03T18:34:49.278+01:00Connect the Raspberry Pi Pico to an OLED using Grove breakouts<p>In this short article you'll learn how to make and use a compact, inexpensive adapter that will allow you to
connect a Raspberry Pi Pico/PicoW to Grove I2C peripherals. With a Grove to Stemma QT/Kwiic adapter cable, you can also
connect your Pico/PicoW to Adafruit and Sparkfun I2C devices.</p>
<p>You'll laso learn a useful hack that lets you connect the 2mm spaced Grove adapters to a 0.1" spaced (2.54mm) pcb.</p>
<h2>Grow with Grove</h2>
<p>Regular readers will know I love breakout boards.</p>
<p>One of the projects that I've been working on is a Raspberry Pi Locator. It reads updates from the
@rpilocator RSS feed, and it tells you when and where Raspberry Pi stock is available. The Pico W will sound a
buzzer when there's stock around, but it would be great if it could tell you which stores had the Pis. On OLED
display would keep things compact, and I thought I'd hook one up.</p>
<p>Seeed studios have a great range of Grove breakouts with an easy-to-use connection system, and I knew I had some
Grove-compatible I2C OLEDs in my parts stock.</p>
<p>Then I realized I'd need an adapter to connect the Pico to the OLED.</p>
<p>I have a few of Pimoroni's Pico proto boards, and I wondered if I use one as a base for a Grove connector.</p>
<p>After a few false starts I had a soldered connector. Soldering the board is
a little fiddly, but it's not too bad if you add the components in the right order.</p>
<p>Here's the parts list:</p>
<p>1 Pimoroni Pico proto board.
2 x 20 way 0.1" female headers
2 x 2.7K ohm resistors. Anything from 2.2k to 5.6K would work just as well
1 Grove connector
Tinned copper wire and white, yellow, red and black hookup wire.</p>
<p>Here's the schematic:</p>
<p><img alt="image" height="325" src="http://images.rareschool.com/img/17310c62-1351-11ed-9360-d1f4a154a7f8-pico-grove-adapter_schem-cropped.png" title="" width="400" /></p>
<p>Begin by soldering two lots of bridge wires between successive strips of the Pico proto.</p>
<p>Next, cut and solder wires from the proto inner edge of the proto to the relevant strips,</p>
<p>There are four of those connections.</p>
<p>The white wire caries SDA, the I2C data signal.
The yellow wire carries SCL, the I2C clock signal.
The red wire carries 3.3 volts.
The black is 0v (Ground).</p>
<p>After that, solder the Grove connector. Make sure you have it the right way around!
Since its legs are 2mm apart, you need to splay them out slightly to fit the 0.1" spacing of the Pico proto.</p>
<p>Finally, solder the female headers to the board. Make sure the board is the right way up!</p>
<p>The easiest way to solder female headers is to use a breadboard with two rows of male headers plugged in.
Then push the female headers onto the th male header pins and place the Pico proto board on top of the female headers.</p>
<p>Now it's easy to solder the board to the female headers.</p>
<p><img alt="image" height="185" src="http://images.rareschool.com/img/d4c2bd94-1350-11ed-9360-d1f4a154a7f8-pico-grove-adapter-mk2.jpg" title="" width="320" /></p>
<p>Time to test the board!</p>
<h2>Checking I2C</h2>
<p>Connect the OLED (or whatever I2C device you plan to use) using a standard Grove cable.</p>
<p>Next, run the following program on your Pico.</p>
<pre><code>
# We're using I2C0 with SDA on Pin 0, SCL on pin 1
import machine
i2c = machine.I2C(0, scl=machine.Pin(1), sda=machine.Pin(0))
print('Scanning')
devices = i2c.scan()
if len(devices) == 0:
print("No i2c devices found!")
else:
print('%d i2c device(s) found:'% len(devices))
for device in devices:
print("address: %d - 0x%X" % (device, device))</code></pre>
<p>It should list all the I2C devices it finds on the Pico's I2C bus 0.</p>
<p>I used Grove 128x128 pixel monochrome OLED based on the SH1107 display.
After a little searching I found a <a href="https://github.com/nemart69/sh1107-micropython">MicroPython driver</a> for it.</p>
<p>I installed the driver on the Pico and ran this test:</p>
<pre><code>
import sh1107
import machine
i2c = machine.I2C(0, scl=machine.Pin(1), sda=machine.Pin(0))
oled = sh1107.SH1107_I2C(128, 128, i2c)
oled.text('MicroPython', 10, 0)
oled.text('I2C + Pico', 10, 16)
oled.text('+ Grove', 10, 32)
oled.show()
</code></pre>
<p>And voila:</p>
<p><img alt="image" height="278" src="http://images.rareschool.com/img/3e7a3ece-1351-11ed-9360-d1f4a154a7f8-oled-128x128.jpg" title="" width="320" /></p>
Romilly Cockinghttp://www.blogger.com/profile/04944907795639823332noreply@blogger.com0tag:blogger.com,1999:blog-3251920374329557969.post-77641503869706306652022-08-01T07:11:00.000+01:002022-08-01T07:11:05.963+01:00Simple, repeatable deployments in a MicroPython environment<p>Have you ever suffered from "It works on my machine"?</p><p>Most of us have, as users (well, it doesn't work on mine!") or as developers ("what have I done wrong this time?". </p><p>The cause is almost always down to <i>something</i> that's on the developer's machine that they have forgotten about, but make use of. That <i>something</i> doesn't get included in the installation process, so users may not have it installed.</p><p>There's a great way to avoid that.</p><h4 style="text-align: left;">Testing an installation process if you have an OS</h4><p>If the software you're developing runs under an Operating System, run the installation in a freshly-created virtual machine. That will ensure that you start without <i>somethings</i> installed. You'll only have the software that's specified in your installation process.</p><p>If that works, you're in good shape.</p><p>What about installing MicroPython software on devices like the Raspberry Pi Pico?</p><h4 style="text-align: left;">Testing an installation process for MicroPython projects</h4><p>A comparable deployment process has three stages. You need to</p><p></p><ol style="text-align: left;"><li>Wipe the MicroPython file system completely</li><li>Install a known version of MicroPython</li><li>Install the application code</li></ol><div>You can find software to do stages 1 and 2 <a href="https://github.com/romilly/mp_installer" target="_blank">on GitHub</a>. </div><div>It's called <span style="font-family: courier;">mp-installer</span>.</div><div><br /></div><div>The README file has instructions for installation and use.</div><div><br /></div><div><div>At present it only works on Linux, including Raspberry Pi OS on the Pi or other hardware.</div><div>I'll get it working on Windows and/or Mac/OS if I can find a helpful collaborator!</div></div><h4 style="text-align: left;">Installing the application code</h4><div>If your application consists of one or two files you can install the code manually using Thonny.</div><div><br /></div><div>If you want an automated process, or have many files to install, there's a great alternative.</div><h4 style="text-align: left;">Use <span style="font-family: courier;">mpremote</span> to install MicroPython files</h4><div><span style="font-family: courier;">mpremote</span> is a really useful utility written by the MicroPython team.</div><div><br /></div><div>You call it from the command line. You can use it to move files to and from the Pico, run files, execute MicroPython commands. You can even force the Pico into boot-loader mode if you need to update the MicroPython interpreter.</div><div><br /></div><div>You'll find instructions for installing and using mpremote <a href="https://docs.micropython.org/en/latest/reference/mpremote.html" target="_blank">here</a>.</div><div><br /></div><div>Here's a script that installs a complete application:</div><pre><code>
#!/usr/bin/env bash
cd ../src
mpremote cp secrets.py :secrets.py
mpremote cp network_connection.py :network_connection.py
mpremote cp -r mp/ :
mpremote cp -r pi_finder/ :</code></pre>
<h4 style="text-align: left;">A repeatable, reliable process</h4><p>You'll find that running <span style="font-family: courier;">mpremote</span> in a script is a great way to install all the files your application needs.</p><p>If you first run <span style="font-family: courier;">mp-installer.py</span> with the nuke option, you'll have a repeatable automated installation process.</p><p>That way you'll be confident that your users will get all the software they need to run your application.</p>
Romilly Cockinghttp://www.blogger.com/profile/04944907795639823332noreply@blogger.com0tag:blogger.com,1999:blog-3251920374329557969.post-63075388236158736562022-07-30T07:42:00.000+01:002022-07-30T07:42:10.262+01:00Seven secrets of the Raspberry Pi Pico's success<p>The Pi Pico sold well; the Pico W has sold out several times since its launch.</p><p>With two million units planned for production this year, the Pico shortage will be temporary, but it's a sign that the new Pico W is going to repeat the amazing success of the original Raspberry Pi.</p><table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbbsl6vXejQCqy1qjCimSi70gJ3e482HJyafHCXXa41FlofFKlM8ftHTfUhcISYb-XBn8Yh44y7ku0wlxbLsERFuw4Mcv7l4xP-RxLnXc-aWdPo_omf496_pAPFaUQJBG_lpZrZvYjPIlRKbv5cctgIUNhJchh_jgpVRoInnwjo2NtWpsoWhVsOWIS/s1210/pico-w.jpg" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" data-original-height="506" data-original-width="1210" height="134" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbbsl6vXejQCqy1qjCimSi70gJ3e482HJyafHCXXa41FlofFKlM8ftHTfUhcISYb-XBn8Yh44y7ku0wlxbLsERFuw4Mcv7l4xP-RxLnXc-aWdPo_omf496_pAPFaUQJBG_lpZrZvYjPIlRKbv5cctgIUNhJchh_jgpVRoInnwjo2NtWpsoWhVsOWIS/s320/pico-w.jpg" width="320" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Pico W</td></tr></tbody></table><p>Why is it selling so well?</p><h4 style="text-align: left;">What's the secret?</h4><p>Yesterday I jointed a group of friendly and knowledgeable enthusiasts for an online meeting of the<a href="https://www.meetup.com/micropython-meetup/" target="_blank"> Melbourne MicroPython Meetup</a>.</p><p>It's not just a <i>user</i> group. Damien George gave a talk about the Rapsberry Pi Pico W. Damien is the creator of MicroPython, and he was responsible for the MicroPython port with Wifi support. The software was available on the day that the Pico W launched.</p><p>In the discussion that followed, several of us speculated about the secret sauce that made the Pico/Pico W so successful.</p><p>We concluded that there was no single factor, but that several features combined to make them so attractive.</p><h4 style="text-align: left;">Seven key features</h4><p>What helped to catapult the Pico and Pico W to instant success?</p><p><b>First</b>: the Raspberry Pi brand. With over 40 million Pis in the hands of Makers, Programmers, Learners, Teachers and Entrepreneurs, the Raspberry Pi name is widely known, respected and trusted.</p><p><b>Second</b>: Community. the Pico family is supported by a huge Raspberry Pi community of Makers, Hackers, Journalists and Vendors.</p><p>On the day of its launch companies like Pimoroni, Adafruit and the Pi Hut offered the Pico W for sale and announced their own add-on products. The Pico W sold quickly.</p><p>By the next day enthusiasts had started to <a href="https://www.youtube.com/watch?v=ZWTEO--0TWc" target="_blank">share their exploration</a> of the Pico W!</p><p>Tom's Hardware publicised the product from the day of its launch and was quick to share projects as they were announced.</p><p>The <a href="https://magpi.raspberrypi.com/issues/119" target="_blank">July 2022 issue of the MagPi magazine</a> has articles about Pico projects, and reviews of newly announced third party products.</p><p><b>Third</b>: Great documentation, available at launch. Raspberry Pi's documentation is written by Alasdair Allen. He's uniquely qualified. He's a been a physicist, a hardware hacker and a journalist. His documentation is readable, clear and accurate, and he's quick to respond to feedback.</p><p><b>Fourth</b>: Great software, available from the start.</p><p>With a full-featured, well-documented MicroPython port available from day one, the Pico W made it really easy for us to explore its potential.</p><p><table cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2ISr87lQvxfg7VyGbtdkcBudwV9-pMxgtwIKJFyEqyLQ6XP1ywzM5hxtYGE5nTp32kUiiF-gAEecq1m5w-4LG-E4YuXti6Z4dskNmgrW-l8ZvvLMkZkXsO7jEqfw1wrrcFdR5Hk30f0-7Pr3uLxjfNWL7TKJOWEfW5BAfEbCeVwOWvlzqUCpfDfwD/s2684/picowx4.jpg" imageanchor="1" style="clear: right; margin-bottom: 1em; margin-left: auto; margin-right: auto;"><img border="0" data-original-height="1853" data-original-width="2684" height="138" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2ISr87lQvxfg7VyGbtdkcBudwV9-pMxgtwIKJFyEqyLQ6XP1ywzM5hxtYGE5nTp32kUiiF-gAEecq1m5w-4LG-E4YuXti6Z4dskNmgrW-l8ZvvLMkZkXsO7jEqfw1wrrcFdR5Hk30f0-7Pr3uLxjfNWL7TKJOWEfW5BAfEbCeVwOWvlzqUCpfDfwD/w200-h138/picowx4.jpg" width="200" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Pico W strip</td></tr></tbody></table><b>Fifth</b>: Great for making new products. The castellations on the Pico and Pico W allow them to be soldered directly onto PCBs.</p><p>Picos are available in quantity, in strip packaging, as is the RP2040 chip on which they are based.</p><p><b>Sixth</b>: PIO (Programmable I/O) is an amazing feature of both the original Pico and the W.</p><p>PIO has enabled all sorts of capabilities; an HDMI interface, a VGA interface, and other specialist protocols, all <i>implemented in software</i>. </p><p><b>Seventh</b>: Availability. While the Pico W has sold out a couple of times, stocks have quickly been replenished. Global chip shortages have impacted the availability of many products, including the Raspberry Pi Model 4, but the Pico has been essay to find online.</p><h4 style="text-align: left;">What are the Pico's competitors?</h4><div>There are hundreds of similar products, but the main competitors of the Pico are</div><div><ol style="text-align: left;"><li>Boards from other manufacturers that use the same chip - the RP2040. Boards are available from dozens of vendors, including Adafruit, Pimoroni and Arduino. Many of these have great extra features, but most cost significantly more than the $4/$6 of the Pico and Pico W.</li><li>Boards from the Arduino family. The Arduino has a large and loyal following, but the original products lack the memory to support MicroPython, and more recent products are significantly more expensive.</li><li>Boards based on the ESP 8266/32. These have a large following, and they are available at a very competitive price. I'm sure they will continue to hold a significant share of the market, but their market is fragmented and the boards attract much less media coverage than the Pico.</li></ol><h4 style="text-align: left;">So what's the future?</h4></div><div>The range of Pico-based projects and products is growing fast. Eben Upton (CEO of the commercial arm of Raspberry Pi) says they expect to make 2 million units this year, ant it looks as if demand will match or exceed that. The Pico is affordable, powerful, fun and available. It's a great platform.</div><div><br /></div><div>I'm currently working on a free guide to the Pico W. Keep an eye on this blog, or follow <a href="https://twitter.com/rareblog">@RAREblog</a> in Twitter for more information.</div><p><br /></p><p><br /></p>Romilly Cockinghttp://www.blogger.com/profile/04944907795639823332noreply@blogger.com0tag:blogger.com,1999:blog-3251920374329557969.post-59256033424845245422022-07-18T18:18:00.001+01:002022-07-18T18:18:34.564+01:00Make your own strip-board breakout boards<h1>A useful trick for Makers</h1>
<p>In this article you'll see when you should build your own breakout boards, and you'll learn a useful trick to use when making them.</p>
<h2>Breakout boards rock</h2>
<p>Digital Makers can often make projects faster by using breakout boards.</p>
<p>A breakout board is a small but useful module that you can use to compose your project. There are hundreds of breakout boards available. Many are based on tiny SMD chips that are tricky to solder. You can save time and reduce the risk of mistakes by buying a ready-made breakout board.</p>
<p>Here's an Adafruit breakout board I used in the Raspberry Pi version of Lazydoro.</p>
<p><img alt="image" height="389" src="http://images.rareschool.com/img/d8d1b304-06ad-11ed-9ce6-d119118dc5a1-lazydoro.jpg" title="" width="640" /></p>
<p>Of course, you can only buy a ready-made board if you can find one that does what you need,</p>
<p>If there isn't, consider making one!</p>
<h2>Why bother with self-built breakouts?</h2>
<p>Why not just put everything you need on the main project board?</p>
<p>There are advantages to a design that uses pluggable modules. You can usually test the module on its own, and there's a good chance that you can re-use the model (or at least the design) in future projects.</p>
<p>Hardware and software developers have been doing this for decades. Well-designed hardware and software modules reduce coupling, increase cohesion, and increase quality.</p>
<p>Here's a concrete example from a current project.</p>
<h2>A buzzer module.</h2>
<p>One of my projects (lazydoro) needed a buzzer.</p>
<p>The project is based on the Raspberry Pi Pico W, so digital outputs are limited to 3.3 volts.
Some active buzzers will work at that voltage, but they sound a bit feeble. I needed to hear
the buzzer in the next room, so I wanted to drive it at 5 volts.</p>
<p>I decided to build a module.</p>
<h2>The module design</h2>
<p>The circuit is simple. It uses an inexpensive BC337 NPN transistor as a switch. A 3.3 volt control signal applied to the base of the transistor runs the switch on, reducing the output voltage to just over 0 volts. That results in a 5 volt potential difference across the buzzer, which then buzzes loudly.</p>
<p>Here's the schematic. </p>
<p><img alt="image" src="http://images.rareschool.com/img/6162a496-06ac-11ed-9ce6-d119118dc5a1-buzzer-module_schem.png" title="" /></p>
<p><strong>NB:</strong> In the schematic, power, ground and the signal come in <em>on the right</em>!</p>
<h2>Mark 1 - oh, dear</h2>
<p>I built the module on a small piece of strip-board. Since you can only solder strip-board on the copper side of the board, I soldered a 3-way female header on the module and a 3-way male header on the main board.</p>
<p><img alt="image" height="246" src="http://images.rareschool.com/img/8806e502-06b2-11ed-9ce6-d119118dc5a1-buzzer-mk1.png" title="" width="640" /></p>
<p>It worked, but it didn't look very pretty in place.</p>
<p><img alt="image" height="526" src="http://images.rareschool.com/img/ba6626d8-04dd-11ed-8d9e-a7ed5844bcc7-lazydoro-mk5-proto.jpg" title="" width="640" /></p>
<h2>Mark 2 - the trick</h2>
<p>Luckily I remembered an old trick which resulted in a much neater version.</p>
<p>If you solder header pins without modding them the pins are too short to plug into a female header.</p>
<p>Luckily, you can push the plastic to the top of the pins so they look like this:</p>
<p><img alt="image" height="259" src="http://images.rareschool.com/img/57e21e06-06b6-11ed-9ce6-d119118dc5a1-3-pins-squidged.jpg" title="" width="320" /></p>
<p>Now you can solder them to strip-board and the pins are long enough to work.</p>
<p>Here's the new module:</p>
<p><img alt="image" height="389" src="http://images.rareschool.com/img/0eea6ee6-05a4-11ed-b540-ed92c440c2c1-buzzer-mk2.jpg" title="" width="640" /></p>
<p>and here's the strip-board design. As you can see, the design fits on a standard 9-strip board, and you don't need to cut any of the strips.</p>
<p><img alt="image" height="291" src="http://images.rareschool.com/img/5ab2bb4a-06bb-11ed-9ce6-d119118dc5a1-buzzer-module_bb.png" title="" width="640" /></p>
<p>I added a female header to the main board and inserted the new module.
Here it is in place. It works perfectly and looks much better now the buzzer is on top!</p>
<p><img alt="image" height="446" src="http://images.rareschool.com/img/c3e5a3ec-06b7-11ed-9ce6-d119118dc5a1-lazydoro-mk5-with-buzzer-mk2.jpg" title="" width="640" /></p>
<h2>Summary</h2>
<p>Breakout boards are a great times-saver. If you can't find one that does what you want, it's often easy (and satisfying) to make your own.</p>
<p>If you're using strip-board you can easily adapt male headers to that you can solder them to the copper side of the strip-board.</p>
Romilly Cockinghttp://www.blogger.com/profile/04944907795639823332noreply@blogger.com0tag:blogger.com,1999:blog-3251920374329557969.post-26803121306737456682022-07-17T17:35:00.001+01:002022-07-17T17:35:40.551+01:00Ports and Adapters - Struggling back to Beginner's Mind<p>I have to admit it: I've been struggling over the last two days. </p><p>I'm working on a write-up of a <a href="https://www.techtarget.com/whatis/definition/walking-skeleton" target="_blank">Walking Skeleton</a> for Lazydoro, together with sample code for its <a href="https://en.wikipedia.org/wiki/Hexagonal_architecture_(software)" target="_blank">Ports and Adapters</a> architecture.</p><p>I often create a Walking Skeleton at the start of an application. Lazydoro is now in its fifth version, there's already a finished working version, and it's difficult to 'un-know' how it evolved. Trying to create what I <i>might</i> have written at the start has been quite a challenge</p><p>The process has been valuable, though. I think I understand <i>Ports and Adapters</i> better through working on my sample code and explanation, and it's had another benefit.</p><p>I've set up an automated deployment process which I'll be able to use for future MicroPython projects.</p><h4 style="text-align: left;">Automating deployment</h4><p>Some applications can be deployed as a single file, but small modules are easier to read and test. lazydoro now consists of 9 files in a three-directory tree, and deploying a new version manually has become a real pain and is prone to error.</p><p>The new process uses a <a href="https://gist.github.com/romilly/5a1ff86d1e4d87e084b76d5651f23a40" target="_blank">gist on GitHub</a> which deletes all the files from a MicroPython-based board. It's always possible that your code relies on a file that you deployed a while back but have now forgotten about - a common cause of 'It works on My Machine'. </p><p>Deployment to an empty file system is the safest way of avoiding that.</p><p>There's more to the deportment task than that, and I'm documenting the whole malarkey.</p><h4 style="text-align: left;">What's coming</h4><p>I'm not sure how soon I'll have all the sample code ready, but you'll get to see three things:</p><p></p><ol style="text-align: left;"><li>The automated deployment process</li><li>The <i>Walking Skeleton</i>, and what you risk without one</li><li><i>Ports and Adapters </i>in an Embedded Application, and how that architecture helps with unexpected change.</li></ol><div>Meanwhile I will be posting a short article about a simple technique for making stack-able breakout boards using strip-board. It's a very simple trick I forgot to apply to lazydoro until this morning; the buzzer break-out board now look much neater. </div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlBY2ySEeNZdAcfeYKni9JVMRWgYXWl_jNLhjvzZZNou9ZmCgsiner1ADT_2mj_37u5eWt9t0Lc01Yd-kPHUQDOnao7ZDm1PnDxR8tH23E9Jo-dBsGDq1z765spCHUb_HkPVRxDPV9cjgsvDUUjo6aOFnizQFhcuTZTR8GyAg5IAaMZ4YwmEr0qfO6/s1052/before-and-after.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="465" data-original-width="1052" height="283" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlBY2ySEeNZdAcfeYKni9JVMRWgYXWl_jNLhjvzZZNou9ZmCgsiner1ADT_2mj_37u5eWt9t0Lc01Yd-kPHUQDOnao7ZDm1PnDxR8tH23E9Jo-dBsGDq1z765spCHUb_HkPVRxDPV9cjgsvDUUjo6aOFnizQFhcuTZTR8GyAg5IAaMZ4YwmEr0qfO6/w640-h283/before-and-after.png" width="640" /></a></div><br /><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><br /><p></p>Romilly Cockinghttp://www.blogger.com/profile/04944907795639823332noreply@blogger.com0tag:blogger.com,1999:blog-3251920374329557969.post-55232438771631240262022-07-08T14:25:00.003+01:002022-07-08T14:25:59.063+01:00Raspberry Pi Pico W project plans<div class="separator"></div><h1>Please help me choose!</h1>
<p>The announcement of the Raspberry Pi Pico W has opened up a huge range of
fun, exciting projects.</p>
<p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><img alt="image" height="225" src="http://images.rareschool.com/img/6b7a6948-fdf4-11ec-8ac1-6117af0618ca-picow-cloud.jpg" style="margin-left: auto; margin-right: auto;" title="" width="320" /></td></tr><tr><td class="tr-caption" style="text-align: center;">Pico W in the cloud</td></tr></tbody></table>I've a long list of candidates, and I'm hoping for advice about which
ones have the widest appeal.</p>
<p>The first project is a shoo-in, not least because it's almost finished. I'll
tell you about that shortly. I have seven other projects pulling for my
attention, and I'll give you a quick introduction to each. Then I hope you
can help me chose which to do next, or even suggest an alternative.</p>
<p>I'll open up polls on Twitter and Facebook, but I'd also welcome feedback on
this blog.</p>
<h2>Project number One</h2>
<p>If you're a regular reader of the RAREblog you will already be familiar with
Lazydoro, my automated Pomodoro timer. The current version is now running well,
but it's based on a Raspberry Pi Zero. I'm migrating the project to the Pico
W for two reasons:</p>
<ol>
<li>I want to keep a daily log of my Pomodoros, so I need wifi access</li>
<li>A new Raspberry Pi is a rare thing at the moment, because of the chip
shortage.</li>
</ol>
<p>In contrast, the supply of Pico Ws is fine. If you want to build your own
Lazydoro you'll find it easy to get all the necessary hardware, and you'll
pay less for it!</p>
<p>I'll blog about the hardware and software as soon as I've finished the
migration - but after that I wonder what to build next.</p>
<h2>After that?</h2>
<p>These are the next projects I have in mind, in alphabetical order.</p>
<p>All of them are simple and inexpensive.</p>
<p>Which would appeal to you? And what other projects would you like me to build?</p>
<h3>Digital Callipers</h3>
<p>I've had a set of Digital Callipers for ages, and I love them.</p>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><img alt="image" height="114" src="http://images.rareschool.com/img/d4505794-feac-11ec-b258-1167eb2a3ab1-callipers.jpg" style="margin-left: auto; margin-right: auto;" title="" width="320" /></td></tr><tr><td class="tr-caption" style="text-align: center;">digital callipers</td></tr></tbody></table>
<p>They work well, but I find it
annoying having to manually record dimensions. I've used the callipers to
capture the sizes of new boards that I was adding to the library for
<a href="https://github.com/romilly/breadboarder">Breadboarder</a>.</p>
<p>What I'd like, ideally, is to be able to press a button on the callipers
that captures a measurement and simultaneously triggers a photo of what's being
measured.</p>
<p>There's an <a href="https://hackaday.com/2010/12/15/reading-a-digital-caliper-with-a-microcontroller/">article on Hackaday</a>
that shows how to capture measurements with a PIC micro. I'm sure I can do
it with the Pico.</p>
<h3>Logic Analyzer</h3>
<p>I have several items of test equipment that I use to track down issues with
I2C and Serial comms, but they take a while to set up and the
interface is never quite what I want.</p>
<p>I like the idea of a web-based interface that I can tailor to my needs. I
could use the GPIO pins on a Pico W to track digital signals, but if I used
an MCP23S17 I could monitor up to 16 signals in parallel at the speed of SPI.</p>
<p>I know I'd find that useful. Would you?</p>
<h3>The Microwriter</h3>
<p>Back in the early 1980s word processing was all the rage. If you wanted to
write text on the move your options were limited. One popular solution was a
chording keyboard device called the <a href="https://en.wikipedia.org/wiki/Microwriter">microwriter</a></p>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><img alt="The original microwriter" height="320" src="https://upload.wikimedia.org/wikipedia/commons/e/ec/Microwriter.png" style="margin-left: auto; margin-right: auto;" title="" width="240" /></td></tr><tr><td class="tr-caption" style="text-align: center;">micowriter</td></tr></tbody></table>
<p>Rumour has it that the microwriter was particularly popular with James Bond's
colleagues. In the 1980e chaps like James couldn't type but apparently Q
convinced them that the microwriter was cool to use - a sort of Typist's
Aston Martin.</p>
<p>I'd love to create an updated microwriter. You could display the text you
type on a cheap monochrome OLED, store the text in flash and use wifi to upload
it to your laptop or workstation.</p>
<h3>The Intelligent Breadboard</h3>
<p>This is another project I've had on hold for ages.</p>
<p>The idea is simple: connect the strips of a breadboard to something which
can montor (and perhaps set) the voltage of each strip.</p>
<p>It's a simple form of Automatic Test Equipment (ATE).</p>
<p>The first version of TIB (The Intelligent Breadboard) would have two
MCP23S17s connected to the strips of a mini breadboard with 170 tie points.</p>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><img alt="image" height="320" src="http://images.rareschool.com/img/742903c6-feb5-11ec-b258-1167eb2a3ab1-bb-yellow.png" style="margin-left: auto; margin-right: auto;" title="" width="320" /></td></tr><tr><td class="tr-caption" style="text-align: center;">mini breadboard</td></tr></tbody></table>
<p>Later versions could add analogue voltage measurement and connect to a
larger board. You could even program TIB to check your wiring step-by-steo
when prototyping a new project.</p>
<h3>A Web-based Oscilloscope</h3>
<p>Years ago, when the first mbed board came out, I wrote a project that created a
web-page showing a varying voltage. I used svg to generate the graphics. It
worked surprisingly well. Time to update that for the Pico W?</p>
<p>The first version would be for audio frequency signals only, but it would be
interesting to see how rapidly one could capture analogue signals. I'd want to
wrap some high-performance ome C code as an .mpy file. I've not tried that, and
it would be a useful skill to learn.</p>
<h3>SLAMbot</h3>
<p>SLAMbor is a mobile robot. Not one that slams into things, but one that does SLAM:
<a href="https://en.wikipedia.org/wiki/Simultaneous_localization_and_mapping">Simultaneous Location and Mapping</a>.</p>
<p>You'd control the robot from your phone, and it would use a set of 8 VL53L0X
ToF sensors to build a map of its environment as you drove the bot around a
room.</p>
<h3>Weather Station</h3>
<p>I'd love to build a web-connected weather station around the BME280. I'd also like
an anemometer to measure wind speed. I've recently come across a couple of
promising designs that could be built without a 3D printer.</p>
<h2>Over to you</h2>
<p>I'm looking forward to a lot of Pico W fun over the next few weeks!</p>
<p>I'll post a list of the projects on Twitter and ask for your vote.
It will have to be a pair of polls as I think Twitter limits us to 4 choices.</p>
<p>Alternatively, you can let me know what you'd like to see as a comment
on this blog.</p>
Romilly Cockinghttp://www.blogger.com/profile/04944907795639823332noreply@blogger.com0tag:blogger.com,1999:blog-3251920374329557969.post-67636690189669409852022-07-05T15:40:00.008+01:002022-07-05T16:07:34.763+01:00Lazydoro is working again!<p><a href="https://blog.rareschool.com/2022/07/lazydoro-mk-3-lots-of-automated-tests.html">Yesterday</a> things looked tricky.</p><p>I realised this morning that I could easily fix the problem I've been having with lazydoro and my new chair.</p><p>Doh! All it took was an adjustment to the distance threshold.</p><p>Lazydoro is now sitting by my keyboard and keeping a watchful eye on me.</p><p><br /></p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJwQnW_izlY3h4wAh9VavG-ltlqIdlFIDcim2-CRNKJTP9j55bPFm8EJhogKNBfAFscSJhd9tuufFbq8V2ZeLLJeuayuz6LGOf4GCumt0qlYM96zENPaeL8mmpXHVMbYlXM3LFjXWiHg14dEAPM9fLqR13reakxQCPL24H0ZKAdWQ-uY-t4y589WZf/s988/lazy-back-cropped.jpg" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="556" data-original-width="988" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjJwQnW_izlY3h4wAh9VavG-ltlqIdlFIDcim2-CRNKJTP9j55bPFm8EJhogKNBfAFscSJhd9tuufFbq8V2ZeLLJeuayuz6LGOf4GCumt0qlYM96zENPaeL8mmpXHVMbYlXM3LFjXWiHg14dEAPM9fLqR13reakxQCPL24H0ZKAdWQ-uY-t4y589WZf/w400-h225/lazy-back-cropped.jpg" width="400" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">lazydoro back at work</td></tr></tbody></table><br /><p>Next steps: keep a log of Pomodoros completed/broken.</p><p><br /></p><p><br /></p>Romilly Cockinghttp://www.blogger.com/profile/04944907795639823332noreply@blogger.com0tag:blogger.com,1999:blog-3251920374329557969.post-28609723132623991922022-07-04T15:49:00.007+01:002022-07-04T15:49:50.829+01:00Lazydoro Mk 3 - lots of automated tests for a simple design<div>At the end of <a href="https://blog.rareschool.com/2022/07/lazydoro-migrates-to-pi.html">yesterday's post</a> lazydoro was running on a Pi zero still not working reliably.</div><div><br /></div>In November 2021 I worked on <a href="https://nesslabs.com/">Ness Labs</a>' Write a book in 30 days challenge.
<p>I had to rely on a web-based Pomorodo timer, and thought I'd have another
try at the lazydoro project.</p>
<p>I decided to rewrite lazydoro from scratch.</p>
<p>Lazydoro needs to do four things.</p>
<ol>
<li>It needs to know when I arrive at or leave my desk</li>
<li>It needs to keep track of passing time</li>
<li>It needs to know where I am in a Pomodoro cycle</li>
<li>It needs to provide feedback to keep me on track.</li>
</ol>
<p>To make lazydoro easy to test I used a variant of the <em>Ports and Adapters</em>
architecture. (Ports and Adapters is sometimes called Hexagonal Architecture).</p>
<p>I first came across it in an <a href="https://alistair.cockburn.us/hexagonal-architecture/">article by Alistair Cockburn</a>, and it made a lot
of sense. It's also featured in the GOOS book: <i>Growing Object Oriented Software Guided by Tests</i>.</p><h3 style="text-align: left;">Simple architecture</h3>
<p>Here's the architecture for lazydoro:</p>
<p><img alt="image" src="http://images.rareschool.com/img/62ec9844-fba3-11ec-a043-fde8798d331b-objects.png" title="" /></p>
<p>At the centre is the <i>application model</i>: the Pomodoro object. That contains
code that keeps track of where I am in the Pomodoro Cycle. It's a state machine,
and it changes state based on my presence or absence and the passage of time.</p>
<p>The Pomodoro model gets inputs from a ClockWatcher.</p>
<p>The ClockWatcher gets a tick that tells it time has passed. It then asks the
rangefinder how close the nearest object is, and from that it works out
whether I've just arrived at or left my desk and sends a message to the Pomodoro
object as necessary .</p>
<p>As the Pomodoro tracks the various states (waiting to start, in a Pomorodo,
waiting for me to begin a break, on a break, or waiting for me to return). it
updates the Display, turning LEDs on or off and sounding the buzzer as
appropriate.</p>
<p>The great advantage of that approach is that I can easily test the
application. I can send a message pretending that a
second has passed as often as I like. That's really helpful because it
allows me to run a test much faster than real time. A real Pomodoro cycle
takes 30 minutes: 25 minutes of work and a 5-minute break. I can simulate
a full cycle in under a second if my fake clock ticks fast enough.</p><h3 style="text-align: left;">Mock Objects</h3>
<p>The automated tests use Mock Objects to represent the RangeFinder and
Display.</p>
<p>As a result, the tests are simple and expressive. Here's the test code
for a full Pomodoro cycle:</p>
<p><code></code></p><pre><code>def test_tracks_full_pomodoro(self):
# main success scenario
self.person_absent()
self.wait(seconds=1)
self.check_leds_are_off()
self.person_present()
self.check_leds_are_off()
self.wait(seconds=10)
assert_that(self.display, shows_only(BLUE))
self.wait(minutes=24)
assert_that(self.display, shows_all(BLUE))
self.wait(minutes=5)
assert_that(self.display, shows_all(RED))
self.person_absent()
self.wait(seconds=20)
assert_that(self.display, shows_only(GREEN))
self.wait(minutes=1)
assert_that(self.display, shows_only(GREEN, GREEN))
self.wait(minutes=1)
assert_that(self.display, shows_only(GREEN, GREEN, GREEN))
self.wait(minutes=1)
assert_that(self.display, shows_only(GREEN, GREEN, GREEN, GREEN))
self.wait(minutes=1)
assert_that(self.display, shows_only(GREEN, GREEN, GREEN, GREEN, GREEN))
self.wait(minutes=1)
self.check_leds_are_off()
self.person_present()
self.wait(seconds=20)
assert_that(self.display, shows_only(BLUE))
</code></pre><code>
</code><p></p>
<h2>Success at last?</h2>
<p>It worked well for weeks, and I used lazydoro every day.</p>
<p>And then <strong>it stopped working</strong>.</p>
<h2>What went wrong?</h2>
<p>The code was fine. The problem was physical.</p>
<p>I got a new chair for my study.</p>
<p>It had much better support for my back, but
lazydoro's ToF sensor sometimes thought the chair was me! It picked up a
reflection from the back of the chair, and it sometimes thought I was at my
desk doing a Pomodoro even when the chair was empty.</p>
<h2>A new beginning</h2>
<p>I needed a new approach.</p>
<p>I decided to ask for help in one of the Facebook groups I belong to, and got
lots of interesting suggestions.</p>
<p>Tomorrow I'll reveal what happened next.</p>
Romilly Cockinghttp://www.blogger.com/profile/04944907795639823332noreply@blogger.com0tag:blogger.com,1999:blog-3251920374329557969.post-5260304509770737572022-07-03T11:35:00.002+01:002022-07-03T11:35:17.034+01:00Lazydoro migrates to the Pi<div class="separator"></div><div class="separator"></div><p><a href="https://blog.rareschool.
com/2022/07/the-lazydoro-story-part-1.html">In the previous blog post</a> I described <em>cushadoro</em> and its
successor, lazydoro Mk 1.</p>
<p>That was the first version of lazydoro, implementedin CircuitPython and based on
the Adafruit Trinket M0. Today I'll describe the next stage of the project,
a Raspberry Pi-based version.</p>
<p>I made real progress but, as you'll see, lazydoro was still not quite good enough.</p>
<h2>Lazydoro needed unit tests</h2>
<p>At the point were we left the project the Python code had become a little
complicated. I thought I ought to do more automated testing.</p>
<p>It's possible to do that on CircuitMpython devices like the Trinket M0, but
it's a lot easier if you canuse the standard Python libraries,
including Python's unittest and mock frameworks.</p>
<p>I decided to migrate the project to a Raspberry Pi.</p>
<h2>Moving to the Raspberry Pi</h2>
<p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"><tbody><tr><td style="text-align: center;"><img alt="image" height="150" src="http://images.rareschool.com/img/1520474c-faad-11ec-b8f9-75ae25bb68dc-lazydoro-mk2.jpg" style="margin-left: auto; margin-right: auto;" title="" width="200" /></td></tr><tr><td class="tr-caption" style="text-align: center;">Explorer HAT prototype</td></tr></tbody></table>At the time I migrated the project I had just fallen in love with
Pimoroni's brilliant <a href="https://shop.pimoroni.
com/products/explorer-hat?variant=1074827129">Explorer HAT pro</a>. The HAT has loads of
useful peripherals, and needs no soldering, so it's
great for learning and for rapid hardware protoyping.</p>
<p>I liked the Explorer HAT so much that I wrote a
<a href="https://leanpub.com/explorerhattricks">book</a> about it!</p>
<p>The first version of Lazydoro Mark 2 used an Explorer HAT, a VL53L0X sensor
and a buzzer for feedback.</p>
<h2>Migrating the code with Adafruit Blinka</h2>
<p>Adafruit's VL53L0X library is written for CircuitPython, but the clever folk
at Adafruit have come up with a library for the Pi called
<a href="https://learn.adafruit.com/circuitpython-on-raspberrypi-linux/installing-circuitpython-on-raspberry-pi">adafruit-blinka</a>.</p>
<p>Blinka allows you to <strong>use Adafruit's CircuitPython's device libraries on a
Raspberry Pi</strong> or a Jetson Nano. I found I could move the sensor code from
the Trinket to the Pi without change.</p>
<p>I did make one change, though, taking an opportunity to enhance the hardware.</p>
<h2>Adding the Pimoroni Blinkt!</h2>
<p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"><tbody><tr><td style="text-align: center;"><img alt="image" height="180" src="http://images.rareschool.com/img/0bfee478-fab4-11ec-b8f9-75ae25bb68dc-lazy-zero-running-cropped.jpg" style="margin-left: auto; margin-right: auto;" title="" width="320" /></td></tr><tr><td class="tr-caption" style="text-align: center;">Pi zero version with Blinkt!</td></tr></tbody></table>I replaced the Trinket's single NeoPixel with a Pimoroni Blinkt! display.</p>
<p>The Blinkt! has a bank of 8 multi-colour NeoPixels. There's a Pimoroni
library that can set the colour and brightness of each display.</p>
<p>That allowed me to show how much time had passed during a Pomodoro or a
break.</p>
<p>The project doesn't need a lot of computing power, so I decided to run it on an
inexpensive Raspberry Pi Zero.</p>
<p>Once I had a working prototype I transferred the design to an <a href="https://www.adafruit.com/product/3203">Adafruit perma-proto bonnet</a>.</p>
<p>That was good enough to demonstrate.</p>
<h2>Featured on Hackaday!</h2>
<p>I showed it off at a Raspberry Pint Meetup, and posted the project on
<a href="https://hackaday.io/project/169609-lazydoro">Hackaday.io</a>.
To my delight it got featured, and quite a few people
showed interest.</p>
<p>I might have stopped there, but I didn't.</p>
<p>The Mark 2 version was good, but not quite good enough.</p>
<h2>Lazydoro - usable but not ideal</h2>
<p>I used Mark 2 intermittently, but it wasn't perfect.</p>
<p>The ToF sensor still returned occasional spurious readings. Some were easy to fix.</p><p>The sensor sometimes returns a distance of 0 instead of the 8191 reading that means
'out of range'.That looked like an off-by-one error in the library or the
hardware! I wrote a simple fix for that problem, but I found I still got occasional spurious readings.</p><p>I found the necessary change was harder that I'd hoped. I made a few attempts, but I used lazydoro
less and less, and I eventually accepted the frustrations of using a web-based timer.</p>
<p>In November last year I started work on a new book: <a href="https://leanpub.com/techtalks">Give Memorable
Technical Talks</a>.</p><p>I did a lot of
Pomodoro-based writing, and I longed for a better Pomodoro timer.</p>
<h2>Next - Lazydoro Mark 3</h2>
<p>Want to know how the search for a timer went? Look out for more revelations in
the next thrilling episode!</p>
Romilly Cockinghttp://www.blogger.com/profile/04944907795639823332noreply@blogger.com0tag:blogger.com,1999:blog-3251920374329557969.post-54599602905300636832022-07-02T17:18:00.000+01:002022-07-02T17:18:35.460+01:00The Lazydoro story - Part 1<div class="separator"></div><div class="separator"></div><h1>The Lazydoro</h1>Lazydoro is potentially the most useful project I've built.
<p>I use the Pomodoro method when I am writing or coding. It keeps me focused,
and makes it easy for me to maintain progress. The Pomodoro technique
involves working for 25 minutes without interruption, followed by a 5-minute
break away from your desk. It helps with productivity, and it's good for your
health.</p>
<p>There's just one problem. You need to remember to start a Pomodoro timer!</p>
<h1>Using a timer</h1>
<p>When I first started using the technique I tried using a web-based app to keep
track of time.</p>
<p>Sometimes I remembered to use it, but sometimes I forgot. If I was
deeply absorbed in what I was doing I lost track of time and failed to take
my break. After a few days of that my mood and my knees suffered!</p>
<p>I tried building some hardware to make the Pomodoro technique easier to use.</p><p></p>
<h2>Cushadoro<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"><tbody><tr><td style="text-align: center;"><img alt="image" height="196" src="http://images.rareschool.com/img/c832450a-fa1d-11ec-bdad-9505c373e2e1-cushadoro-v0.1.jpg" style="margin-left: auto; margin-right: auto;" title="" width="200" /></td></tr><tr><td class="tr-caption" style="text-align: center;">Cushadoro</td></tr></tbody></table></h2>
<p>I started the project back in 2015. I called that version <em>cushardoro</em> - an Adafruit
trinket attached to a resistive pressure sensor located in a cushion on my
study seat!</p>
<p>It worked after a fashion, but it had several practical drawbacks. It
required me to put some rather uncomfortable hardware in the cushion on my
chair, and it was not very reliable. Sometimes it failed to notice me when I sat
down, I had to make sure the battery was fresh, and the buzzer was the only
form of feedback.</p>
<p>I also found it quite a challenge to program using the Arduino C-based
environment. I'm not very comfortable programming in C, and I miss having a
REPL for rapid feedback as I code.</p>
<p>I archived the project and forgot about it.</p>
<h2><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="float: right; margin-left: 1em; text-align: right;"><tbody><tr><td style="text-align: center;"><img alt="image" height="111" src="http://images.rareschool.com/img/f5ee91b8-fa1f-11ec-bdad-9505c373e2e1-trinket-m0-cropped-for-twitter.jpg" style="margin-left: auto; margin-right: auto;" title="" width="200" /></td></tr><tr><td class="tr-caption" style="text-align: center;">Lazydoro Mk 1</td></tr></tbody></table>Lazydoro is born</h2>
<p>Back in 2018 <a href="https://www.facebook.com/groups/1224765904203592/search/?q=Time%20of%20Flight">Richard Kirby posted</a> in the Raspberry Pint forum about his experiments with a ToF (Time of Flight) sensor.</p>
<p>I wondered if the sensor would work for my Pomodoro timer. Could I use a ToF
sensor to check if I was in my chair ? I bought a VL53L0X sensor and tried it out.</p>
<p>It looked promising, and in February 2019 I started working on a prototype.
It used the Adafruit Trinket M0, which I could program in CircuitPython.</p>
<p>The prototype had one input device (the distance sensor) and two output
devices: the on-board NeoPixel display and a buzzer.</p>
<p>The application just checked the distance from lazydoro to the area in front
of my keyboard.</p>
<p>When I was at my desk I was about 30 cm away; when I was taking a break the
distance to the wall behind me was over 1 metre.</p>
<p>The application checked the distance and used that and the CircuitPython
<code>time.sleep()</code> method to work out how the Pomodoro was going. It provided
visual feedback using the on-board NeoPixel. When I was on a break it drove a
buzzer to tell me when it was time to return.</p>
<p>The VL53L0X ToF distance sensor worked fairly well but there were occasional
glitches when the sensor seemed to misread this distance. I asked Richard if he'd seen similar behaviour, and he had.</p>
<p>I tried to track the problem down but eventually gave up. I switched to
another project and left the hardware in its project box for another year.</p>
<p>In January 2020 I decided to have another go.</p>
<p>Find out what happened in tomorrow's exciting episode!</p>
Romilly Cockinghttp://www.blogger.com/profile/04944907795639823332noreply@blogger.com0tag:blogger.com,1999:blog-3251920374329557969.post-36041271354039363902022-06-23T11:04:00.003+01:002022-06-23T11:05:40.109+01:00Technologists: one thing you must know if you use logseq with GitHub<div class="separator"><p style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img alt="image" height="199" src="http://images.rareschool.com/img/5d7b108e-f2d4-11ec-8700-ddd99869d0da-my-second-brain.png" title="" width="200" /></p></div><p>Like many technologists, I
use Personal Management Tools to manage information overload.</p>
<p>I've been using technology to help me keep track of complex technical
material since the 1980s. These days, my favourite tool is
<a href="https://logseq.com/">logseq</a>. You can use logseq to capture, connect and
create technical information. Over the last couple of years I've built up a large,
heavily linked knowledge graph - a
<a href="https://www.amazon.co.uk/Building-Second-Brain-Organise-Potential-ebook/dp/B09MDNDYYF/">second brain</a>
filled with information and ideas.</p>
<p>It's worked well for me but this week <i>I hit a pitfall.</i></p>
<h2>Using logseq with GitHub</h2>
<p>If you use logseq as your PKM system you may be using GitHub to back up and
version your knowledge graphs.</p><p>Logseq has great GitHub integration. Because of that, many users have adopted
GitHub as a way of making sure their <em>second brain</em> is secure and easy to access
from anywhere they want.</p>
<p>If you are using logseq with GitHub, beware - there's a potential pitfall
lurking!</p>
<h2>A trap to avoid</h2>
<p>Logseq has great support for PDFs and mp4 videos.</p>
<p>You can embed your mp4 video files using drag and drop.
You can also drag a PDF into the logseq window, open it and highlight
items of interest in the PDF. You can even copy your highlights into your
notes.</p>
<p>But when logseq tries to commit your changes, GitHub may object!</p>
<h2>Watch out for large files</h2>
<p>If you use logseq to capture large files you're likely to
encounter GitHub's file size limits.</p>
<p><em>Git</em> will allow you to commit large files locally but <em>GitHub</em> won't allow
you to push them to the central repository. GitHub <em>warns</em> you if you try to
push files larger than 50MB, and <strong>it will refuse</strong> to push
<a href="https://docs.github.com/en/repositories/working-with-files/managing-large-files/about-large-files-on-github">files larger than 100 MB</a>.</p>
<p>If you try, you'll see an error message refusing your push.</p><p> <code>error: GH001: Large files detected</code></p>
<p>Fortunately there's an easy solution: <strong>GitHub LFS</strong>.</p>
<h2>LFS to the rescue</h2>
<p>GitHub LFS (Large File Storage) allows you to version and push files which are
larger than GitHub's normal 100MB limit.</p>
<p>It's easy to add LFS to an existing repository if there are no large
files. You'll find detailed instructions <a href="https://git-lfs.github.com/">here</a>.</p>
<p>What if you've added large files to your graph and GitHub has refused to let
you push it?</p>
<p>There's good news about that too.</p>
<h2>LFS migration</h2>
<p>There's a <a href="https://github.com/git-lfs/git-lfs/wiki/Tutorial#migrating-existing-repository-data-to-lfs">tutorial on GitHub</a>
which tells you how to migrate large files that have been committed locally
and need to be moved to LFS. After I'd enabled LFS, all I had to do to
migrate my existing mp4 files and PDFs was to run</p>
<p><code><span style="font-family: courier;">git lfs migrate import --include="*.mp4"</span></code></p><p><span style="font-family: courier;"><code>git </code>lfs migrate import --include="*.pdf"</span></p>
<p>and I could then commit and push in the usual way.</p>
<h2>LFS costs</h2>
<p>LFS won't break your budget. Every account gets a free 1 GB storage allowance,
and you can pay just $5/month to add a 50gb datapack. There's also a bandwidth
limit, but you're less likely to be constrained by that.</p>
<h2>Summary</h2>
<p>GitHub and logseq make a great partnership, but if you're going to store
videos or large PDFs you'll want to add LFS support to your GitHub repository.</p>
Romilly Cockinghttp://www.blogger.com/profile/04944907795639823332noreply@blogger.com0tag:blogger.com,1999:blog-3251920374329557969.post-78918064957537087422022-06-10T09:19:00.000+01:002022-06-10T09:19:21.754+01:00Three strategies to manage Technology Overload<p style="text-align: left;">If you're reading this you're probably a <i>knowledge worker</i>.</p><p>Your value lies in the knowledge at your disposal and your ability to apply it. There are daily advances in every field
of technology, and you are subjected to a flood of new knowledge competing for your attention. It's easy to feel
overwhelmed.</p>
<p>In this article, you'll </p>
<ul>
<li><p>read a brief introduction to the flood of information overload and its causes.</p></li>
<li><p>see three strategies you can use to cope with that flood.</p></li>
<li><p>see how to avoid getting overwhelmed by the range of tools - otherwise the cure will be worse than the disease!</p></li>
</ul>
<p>But first - what's the problem?</p>
<h2>What's the problem?</h2>
<h3>The Explosion of Information</h3>
<p><a href="https://en.wikipedia.org/wiki/Edholm%27s_law">Edholm's Law</a> states that Internet traffic now follows the same pace of growth as Moore's Law.</p>
<p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEixszjQH9PguWWQuEsSjpLr-T4uILibjFaBHNeXq8dhV22DtRugq90xbGUbZEGejQIaEH6xoXG39KVCIgXUQWeSwMvvf-yKI-Y9nyiveGBylNd-njBBjO1Q4UItwidlPuicMAlEB62A3CtmdOxbMdedRMWgs6je03NmWAPFUYymNA5NRd9TkQaoCZTU" style="margin-left: 1em; margin-right: 1em;"><br /><img alt="" data-original-height="272" data-original-width="485" height="179" src="https://blogger.googleusercontent.com/img/a/AVvXsEixszjQH9PguWWQuEsSjpLr-T4uILibjFaBHNeXq8dhV22DtRugq90xbGUbZEGejQIaEH6xoXG39KVCIgXUQWeSwMvvf-yKI-Y9nyiveGBylNd-njBBjO1Q4UItwidlPuicMAlEB62A3CtmdOxbMdedRMWgs6je03NmWAPFUYymNA5NRd9TkQaoCZTU" width="320" /></a></div><br /><p></p>
<p>That poses a huge problem for knowledge workers.</p>
<p>Here's a picture of the problem:</p>
<p></p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjkoCA7MpAPyM5cptAY8V9DhXHd0BXju_N55Ope-SYQGY44QHLzjJiOj0xf6Um73Mcc64zwW6WfkFNIPuzMjEVRlkk5UPKh6LoexF_-nsCj6UvFv2tgjjjmkw3yudtOKqrR9gBUrt4D7JfWoA4U0gVHltUATRh1UZ2gJJECbWt7UOpoxyEMnrk2KrUk" style="margin-left: auto; margin-right: auto;"><img alt="" data-original-height="2239" data-original-width="3723" height="384" src="https://blogger.googleusercontent.com/img/a/AVvXsEjkoCA7MpAPyM5cptAY8V9DhXHd0BXju_N55Ope-SYQGY44QHLzjJiOj0xf6Um73Mcc64zwW6WfkFNIPuzMjEVRlkk5UPKh6LoexF_-nsCj6UvFv2tgjjjmkw3yudtOKqrR9gBUrt4D7JfWoA4U0gVHltUATRh1UZ2gJJECbWt7UOpoxyEMnrk2KrUk=w640-h384" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">The knowledge gap</td></tr></tbody></table><p></p><p></p>
<p>If that worries you, you're not alone.</p>
<h3>Professor Eddie Obeng explores the Knowledge Explosion</h3>
<p>In his delightful and provocative
<a href="https://www.ted.com/talks/eddie_obeng_smart_failure_for_a_fast_changing_world/">TED video</a>,
Eddie Obeng warns us about what has happened to us in the 21st century.</p>
<p>"Somebody or something has changed the rules about how our world works....</p>
<p>I think what's happened, perhaps, is that we've not noticed that change...</p>
<p>What we do know is that the world has accelerated."</p>
<p>He goes on to confirm that the rate at which knowledge is generated has grown faster
than our ability to absorb it.</p>
<p>This has profound implications for leadership, companies, organizations and countries
which Eddie explores in his writing and his work
at <a href="https://www.pentaclethevbs.com/">Pentangle</a>.</p>
<h3>Azeem Azhar agrees</h3>
<p>In his book <a href="https://www.amazon.co.uk/gp/product/1847942903/">Exponential</a> Azeem Azhar points out that </p>
as technology accelerates, the human mind struggles to keep up<br /> - and our companies, workplaces, and democracies get left behind.<br />
This is the exponential gap.<br />
<p>If you're interested in finding more about Azeem Azhar's perspective, you can subscribe to his
<a href="https://www.exponentialview.co/">Exponential View</a></p>
<p>So <strong>how can you cope</strong>?</p>
<h2>Three proven strategies</h2>
<p>These three strategies can dramatically improve your ability to cope with the flood of new
information:</p>
<ol>
<li>PKM Tools
<ol>
<li>Mind Mapping</li>
<li>Clipping</li>
<li>Note-taking</li>
</ol></li>
<li>Power Learning
<ol>
<li>Learning How to Learn</li>
<li>Learn Like a Pro</li>
<li>Ultralearning</li>
</ol></li>
<li>Harnessing Collective Intelligence
<ol>
<li>Focussed Internet communities</li>
<li>Collaborative Software</li>
<li>Using AI as part of collective intelligence</li>
</ol></li>
</ol>
<h3>PKM tools</h3>
<p><a href="https://en.wikipedia.org/wiki/Personal_knowledge_management">According to Wikipedia</a>,
PKM (Personal Knowledge Management) is a process of collecting information that a person
uses to
* gather
* classify
* store
* search
* retrieve and
* share knowledge.</p>
<p>I'd add the ability to <em>connect and enhance</em> the items of knowledge in your store.</p>
<p>You may already be using PKM tools to increase your productivity, but it might be time to update your toolbox. This
is a fast-developing field!</p>
<h4>Mind Mapping</h4>
<p>Mind Maps aren't a modern invention. They have been used by Knowledge workers for hundreds of years. Here's a modern
version of the Porphyrian tree, which was used by medieval educators and was adapted by
<a href="https://en.wikipedia.org/wiki/Carl_Linnaeus">Linnaeus</a> in the 18th century to illustrate the relationships between
species. </p>
<p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEiwlNojkye-2MskcY486ZxJQXRJrv_Tx-VJOjp0UAEOzCzmY6CENDvUfuA-VAOBXdIADtETGx42zzx0YbopKc8UfiA6XUuUsTtmaurJoPTrvgKwm8c_h1NxsD-ghPuj0ttwTgXoiVO23lc-XXYzuCMTdUerABnlHC7tGwDldlddU1DBJGfPQAjjLU44" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="456" data-original-width="379" height="400" src="https://blogger.googleusercontent.com/img/a/AVvXsEiwlNojkye-2MskcY486ZxJQXRJrv_Tx-VJOjp0UAEOzCzmY6CENDvUfuA-VAOBXdIADtETGx42zzx0YbopKc8UfiA6XUuUsTtmaurJoPTrvgKwm8c_h1NxsD-ghPuj0ttwTgXoiVO23lc-XXYzuCMTdUerABnlHC7tGwDldlddU1DBJGfPQAjjLU44=w332-h400" width="332" /></a></div><p></p>
<p>Widespread adoption started when <a href="https://www.coursera.org/learn/algorithms-part2">Tony Buzan</a> introduced the
term <em>Mind Map</em> in 1974.</p>
<p>Buzan described hand-drawn maps, and they are still very useful. A hand-drawn map is an intensely individual creation,
and can be a thing of beauty.</p>
<p>The main disadvantages of hand-drawn maps is that they require photography
to back them up, and they are difficult to share and search.</p>
<p>Many Mind Mappers now use software to create and publish their maps.
Here's an example: a MindMap of my new book on <a href="https://leanpub.com/techtalks">Giving Memorable Technical Talks</a>.</p>
<p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjfAKZM_Q0r_4etecC-JGt4S0rI2dJpT_NpO9uC4Uv2_FNKg7BqeESdrLPkG6bB5FnyWJrmDjdNcLYhPSYSvz7E0-D1Y7cedSv2jqsI4-iu5M7OV308rokd8_pvGOFIuOe76W873CnfY8Je3cFZN-wP7rHgfV9By0a-avxW1s1e9tRXXiWUsAQ-ODmA" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="2197" data-original-width="5166" height="272" src="https://blogger.googleusercontent.com/img/a/AVvXsEjfAKZM_Q0r_4etecC-JGt4S0rI2dJpT_NpO9uC4Uv2_FNKg7BqeESdrLPkG6bB5FnyWJrmDjdNcLYhPSYSvz7E0-D1Y7cedSv2jqsI4-iu5M7OV308rokd8_pvGOFIuOe76W873CnfY8Je3cFZN-wP7rHgfV9By0a-avxW1s1e9tRXXiWUsAQ-ODmA=w640-h272" width="640" /></a></div><br /><br /><p></p>
<p>Mind mapping software started to appear in the 1980s. These days there are dozens, if not hundreds,
of books about mind mapping and dozens of software tools.</p>
<p>You'll find great lists/reviews of Mind Mapping tools, and lots of advice, on Chuck Frey's
<a href="https://mindmappingsoftwareblog.com/">Mind Mapping Software Blog</a>.</p>
<p>I've used a number of MindMapping tools, but for the last few years I have relied on
<a href="https://www.freeplane.org/wiki/index.php/Home">Freeplane</a>. It's free, open
source, well documented, and it's supported by an active user community.</p>
<p>Freeplane stores maps in XML which is easy to transform into and out of other formats.
If you're into Python coding, you might find a use for <a href="https://github.com/romilly/fm2md">fm2md</a>, a library that can
convert a Freemind or Freeplane Mind Map into a set of <code>markdown</code> documents ready for publication on Leanpub.</p>
<p>Freeplane works very well, but it's not designed for collaboration.
I'll mention some alternatives in the section on collaborative software.</p>
<p>Mind Maps provide a rich visual experience, but they suffer from one major limitation;
each map <em>represents knowledge as a single tree</em> rooted in a single root node.
While it's possible to make connections between branches, these can
rapidly get confusing.</p>
<p>You'll find information about Note-taking tools that support <em>networks</em> of connections below.</p>
<h4>Clipping</h4>
<p>Clipping tools allow you to save a URL, an entire page or selected highlights.
The two I use are Evernote and Pocket.</p>
<h5>Evernote</h5>
<p><a href="https://evernote.com/">Evernote</a> offers all three possibilities, and has a wealth of additional capabilities including
audio note-taking and Optical Character Recognition (OCR).</p>
<h5>Pocket</h5>
<p><a href="https://getpocket.com">Pocket</a> was originally called 'Read it Later', and that explains just what Pocket lets you do.</p>
<p>You can save content to read later. Pocket will supplement the content with links to other articles that are likely to
be of interest. You can tag links as you save them, and the paid version of Pocket will suggest tags for you to use.</p>
<h5>Kindle</h5>
<p>But surely Amazon's Kindle is a eBook reader?</p>
<p>It is, but it also allows you to highlight passages in, and add notes
to, the Kindle books you own.</p>
<p>You can <a href="https://read.amazon.com/notebook">read your Kindle highlights and notes</a> online, and applications like
<a href="https://readwise.io/">Readwise</a>
can collect them for you. Some can even import them into Note-taking apps, as you'll see in the next section.</p>
<p>The tools below allow you to create your own notes, and more recent tools help you
to build linked <em>networks</em> of notes.</p>
<p>It's possible to create collections of notes without links, but the connections between ideas are often as valuable
as the ideas themselves. For that reason, this article will focus on note-taking apps with linking capabilities.</p>
<h4>Linked Note-taking apps</h4>
<p><a href="https://tiddlywiki.com/">TiddlyWiki</a> is the grandfather of personal note-taking apps. Derived from Ward Cunningham's wiki concept, TiddlyWiki
offers a serverless, self-contained wiki stored in a single html file.</p>
<p>I first started using TiddlyWiki in 2005 and continued to use it for over a decade, along with Freeplane for Mind Maps.</p>
<p>TiddlyWiki has an engaged and helpful community along with a rich ecosystem of plug-ins. Its main weakness is that
it relies on the ability to save the html file from within a browser, and that has become harder and harder as
browsers have tightened their security.</p><p><b>A worry</b></p><p>It's still possible to save files locally, but my worry is that one day a browser update will
prevent me from accessing a PLM tool that I would normally rely on many times each day.</p>
<p>There are workarounds, but they rely on third party plugins which need to be updated if there are significant
changes to the supporting browser.</p>
<p>Many TiddlyWiki users have migrated to more recent software.</p>
<p>Roam, a linked note-taking app from <a href="https://roamresearch.com/">Roam Research</a>, has taken off dramatically over the last couple of years.
In 2017 it was a prototype with a single user; by 2021 it had over 60,000.</p>
<p>Roam supports collaboration, and it has an attractive and ergonomic user interface.</p>
<p>I started to use Roam daily in late 2019, and my graph (network) now links over 1800 pages.</p>
<p>Some users, including me, find Roam's $15/month price tag onerous, and dislike the fact that
Roam keeps all your data in the cloud. You can download backups, but there are three different backup formats and each has
limitations.</p>
<p>It's a remarkable product which continues to develop, but it has at least two serious competitors.</p>
<p><a href="https://obsidian.md/">Obsidian</a> implements a similar concept but with a different interface.
It's free, and it stores your data in your local filesystem.
Like Roam, it is closed source, but it has an open plug-in API.</p>
<p><a href="https://logseq.com/">logseq</a> has most of Roam's features and adds some of its own. It's free, it's open
source and it stores the text and assets in your notes as local files. It's beta software, but it's easy to back up.
It is not designed for collaboration; if that's a major requirement Roam might be a better alternative.</p><p>Concerns about cost, privacy ownership led me to migrate to logseq. I've been using it for a couple of weeks and I am happy with the switch.</p>
<p>With <a href="https://readwise.io/">Readwise</a> it's easy to automate the import of your Kindle highlights into both Roam and Obsidian. That's not
yet directly supported in logseq, but there is a workaround. Install Obsidian alongside logseq!</p><p>Readwise will create or update markdown notes for Obsidian, and logseq will see them and incorporate them into your
logseq graph.</p>
<p>There's great advice on selecting a note-taking app in
<a href="https://nesslabs.com/how-to-choose-the-right-note-taking-app">how to choose the right note-taking app</a> on the Ness
Labs website, and in
<a href="https://fortelabs.co/blog/the-4-notetaking-styles-how-to-choose-a-digital-notes-app-as-your-second-brain/">overview of note-taking styles</a>
on Forte Labs.</p>
<p>You can make good use of PKM tools to support <i>power learning</i>.</p>
<h3>Power Learning</h3>
<p>These days you can enjoy a dramatic improvement in your ability to learn and recall information.</p>
<p>PKM tools can help tremendously, as can recent research in Psychology and Neuroscience.
You can <em>learn how to learn</em> from inexpensive books and free MOOCs (Massive Open On-line Courses).</p>
<p>Here are some favourites that will help you learn much more effectively:</p>
<h4>MOOCs and Books</h4>
<h5>Learning How to Learn</h5>
<p>Over three million students have taken <a href="https://www.coursera.org/learn/learning-how-to-learn">Learning How to Learn</a>
by Barbara Oakley and Terry Sejnowski. It's a great course, and very thorough.</p>
<p>The authors have written a <a href="https://www.amazon.co.uk/Learning-How-Learn-Spending-Studying-ebook/dp/B077CRLW9">book</a>
based on the course that's targeted at kids and teens.</p>
<h5>Learn like a Pro</h5>
<p>Shorter, and recently updated, <a href="https://www.edx.org/course/llap">Learn like a Pro</a> covers similar ground at a faster
pace. There's also a
<a href="https://www.amazon.co.uk/Learn-like-Pro-Science-Based-Anything/dp/B08L42RFHY">book</a> version of that for adults. </p>
<h5>Ultralearning</h5>
<p>I like Scott Young's
<a href="https://www.amazon.co. uk/Ultralearning-Accelerate-Career-Outsmart-Competition/dp/B07T23CZ2W">Ultralearning</a>.</p>
<p>From the book's blurb:</p>
Faced with tumultuous economic times and rapid technological change, staying ahead in your career depends on continual learning - a lifelong mastery of new ideas, subjects and skills. If you want to accomplish more and stand apart from everyone else, you need to become an ultralearner
<p>Online, Scott tells <a href="https://www.scotthyoung.com/blog/myprojects/mit-challenge-2/">the story of an experiment</a>
in which he mastered the MIT’s 4-year undergraduate
computer science curriculum in 12 months, without taking any classes.</p>
<p>Scott's experiment is an example of <a href="https://medium.
com/my-learning-journal/why-you-should-learn-in-public-4fd3a6239549">Learning in Public</a>. It's a great way to add value to your learning
efforts for yourself and others.</p>
<p>What else can you use to cope with tech overwhelm?</p>
<h2>Collective Intelligence</h2>
<p>The third strategy is to use the power of <em>collective intelligence</em>.</p>
<p>In <em>The Wisdom of Crowds</em>, James Surowiecki suggests that groups can often make better decisions than could have been
made by any single member of the group. That's not always true, of course, as political history demonstrates, but
there's another way in which groups can surpass their individual members.</p>
<p>They can combine their knowledge and collectively make connections that no individual could see.</p>
<h3>Common-Interest Communities</h3>
<p>Usenet brought together groups of people with shared interests from the very earliest days of the Internet. In the
1990s many of us graduated to Google Groups and Yahoo groups. Today Social media offer multiple ways to discover
people with relevant interests and knowledge, to ask them questions, and to share opinions and resources.</p>
<p>Often, though, you'll want to work together with others on <em>creating shared resources</em>.</p>
<h3>Collaborative software tools</h3>
<p>COVID forced many of us to work from home. One consequence has been an explosion of web-based and
desktop software tools to help remote workers to collaborate.</p>
<p>Google Docs and Google Slides have been around for a while and they both offer excellent support for collaborative
development. </p>
<p>Slack and Zoom, miro and gotomeeting have all become household names.</p>
<p>There's a fast-growing group of integrated collaboration tools that combine calendar management, contact management,
document management, project management and task management. An online search for team collaboration tools will
throw up lots of articles comparing current offerings; the market is changing so rapidly that you'll need to update
your search results regularly if you want to keep up.</p><h4 style="text-align: left;">Knowledge Management Systems</h4><p>Earlier you read about Personal Knowledge Management. Within a Community or Organisation, you may need to widen your scope to address a communal KMS (Knowledge management System).</p><p><br /><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEj8HefXfQ_cm-4aDDl9FsMFnMqF9Nk9ZDNAZywfHdLTDQwlgVnX-vWr-B_Louss3AN6te97B242p5a-8Ep2UU4hbJtgFj8c2JTOZygPhdrDjURyOOiH8wrgVoWRO9om9erbdlbJydZTdSnppmqWdjut7PO0NEgrh1mT26UFS8Vsar4nmFGcR3iskZOr" style="margin-left: auto; margin-right: auto;"><img alt="" data-original-height="477" data-original-width="796" height="384" src="https://blogger.googleusercontent.com/img/a/AVvXsEj8HefXfQ_cm-4aDDl9FsMFnMqF9Nk9ZDNAZywfHdLTDQwlgVnX-vWr-B_Louss3AN6te97B242p5a-8Ep2UU4hbJtgFj8c2JTOZygPhdrDjURyOOiH8wrgVoWRO9om9erbdlbJydZTdSnppmqWdjut7PO0NEgrh1mT26UFS8Vsar4nmFGcR3iskZOr=w640-h384" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">From Design Knowledge Management System</td></tr></tbody></table><br /><br /></p><p>This is a huge topic in its own right. There is an International Standard (ISO 30401) that addresses the subject of KMS. You'll find a good introduction to the Standard and its implementation in <i>Design Knowledge Management System</i> by Santosh Shekar.</p>
<h3>AI and collective intelligence</h3>
<p>The very technologies that cause the knowledge explosion have given us <em>tools to mitigate the explosion</em>.</p>
<p>You can <strong>harness AI as a partner</strong> in collective intelligence communities.</p>
<p>The MIT Collective Intelligence Design Lab is a trailblazer in that area. It's working on a methodology called
supermind design. You can read an overview in their free
<a href="https://cci.mit.edu/wp-content/uploads/2021/08/Supermind-Design-Primer-v2.pdf">Supermind Design Primer</a>.</p>
<h2>Conclusion</h2>
<p>It's tempting to experiment with every new tool and technique, but that will dilute your focus and worsen the very
problem you're trying to solve.</p><p>These days I restrict myself to trying a single tool at a time, and I allow myself
enough time to reach a level of competence that allows me to make an informed judgement about adopting it. That's
typically somewhere between a week and a month, though I may decide to drop an unsatisfactory tool immediately.</p>
<p>The exponential explosion of technology poses a challenge for knowledge workers, but it's also provided us with an
amazing range of tools and techniques to help us cope.</p><h4 style="text-align: left;">How about you?</h4>
<p>Do you have a favourite tool or technique? If so, do share it in the comments.</p>
Romilly Cockinghttp://www.blogger.com/profile/04944907795639823332noreply@blogger.com0tag:blogger.com,1999:blog-3251920374329557969.post-63447057321510252522022-05-06T10:38:00.028+01:002022-05-13T20:30:37.965+01:00APL and Python go head-to-head<h2>Markdown is great, but...</h2>
<p>I've encountered a problem.</p>
<p>I use <em>Markdown</em> a lot. Since it's pure text-based markup, it's handled well by Git and GitHub. That helps me keep
track of versions, and it simplifies text merges if I'm collaborating on a writing project.</p>
<p>A lot of my writing features Python code, and I like to work on the code and the article in the same editor.</p>
<p>Fortunately there's great support for editing and displaying Markdown in both <a href="https://www.jetbrains.
com/pycharm/">PyCharm</a> and <a href="https://code.visualstudio.com/">VS Code</a>.</p>
<p>Markdown is supported by lots of FOSS tools which make it easy to convert between Markdown and other formats.</p>
<p>I regularly use <code>pandoc</code> to turn Markdown in to pdfs and to publish on <a href="https://code.visualstudio.com/">Leanpub</a>,
and I use the <code>markdown</code> package to convert Markdown files into HTML which I can paste into Blogger.</p>
<p>(Pandoc can create HTML but the output is not directly usable in Blogger.)</p>
<p>So I have a problem.</p>
<p>Much of markdown is standardised but the <code>pandoc</code> and <code>markdown</code> programs handle code blocks differently.</p>
<p>In <code>pandoc</code>, Markdown code blocks are enclosed in triple backticks, with an optional description of the code language.</p>
<p>The <code>markdown</code> program expects code blocks to be indented by four spaces with no surrounding backticks.</p>
<p>I often want to take a Markdown document and create both HTML for the blog and a pdf for people to download, but that
requires two different formats for the source document.</p>
<p>I could make the changes manually but that is tedious and error-prone. I decided to write some code to convert between
the two formats.</p>
<p>I'd normally write that sort of code in Python, but I also use APL. I wondered how the two approaches would compare.</p>
<p>I first met APL in (cough) 1967 or 1968, and the version I learned then lacks many of the modern features in <a href="https://www.dyalog.com/">Dyalog
APL</a>.</p>
<p>Luckily there are some very competent and helpful young developers in the
<a href="https://chat.stackexchange.com/rooms/52405/the-apl-orchard">APL Orchard</a> community.
If you post a problem there you'll often get an immediate solution,
so I can easily improve on my dinosaur-like approach to APL problems.</p>
<p>Today I am going to try to find the best solution I can in APL and compare it with a Python version. I'm not worried
about performance, since I know each approach is more than capable of converting my documents faster than the eye can
see.</p>
<p>I'm more interested in the different approaches. APL is a functional array-oriented language; Python supports functional
programming, but most of us use a mixture of procedural and Object-oriented code.</p>
<p>I created a Python solution fairly quickly.</p>
<pre><code>from typing import List
class Gulper:
def __init__(self):
self.is_reading_markdown = True
self.result = None
def gulp(self,line: str):
if self.is_reading_markdown:
self.read_markdown(line)
else: self.read_code(line)
def read_markdown(self, line):
if line.startswith('```'):
self.is_reading_markdown = False
return
self.result.append(line)
def read_code(self, line):
if line.startswith('```'):
self.is_reading_markdown = True
return
self.result.append(' %s' % line)
def convert(self, lines: List[str]):
self.result = []
for line in lines:
self.gulp(line)
return self.result
</code></pre>
<p>It's pretty straightforward; it's essentially a state machine which switches between reading text and reading code
whenever it encounters a line with three back-ticks.</p>
<p>Here's the APL:</p>
<pre><code>conv←{t←⊣/'```'⍷3↑⍤1⊢⍵ ⋄ n←2|+\t ⋄ (~t)⌿(¯4×t<n)⌽⍵,⍤1⊢4⍴' '}
</code></pre>
<p>I've broken the function down into simpler parts and explained each line by line <a href="https://www.rareschool.com/assets/html/convert-with-apl.html" target="_blank">here</a>.</p>
Romilly Cockinghttp://www.blogger.com/profile/04944907795639823332noreply@blogger.com0tag:blogger.com,1999:blog-3251920374329557969.post-67068605064536897482022-04-28T16:37:00.001+01:002022-04-28T16:37:57.098+01:00Let the computer test your Python GUI application<h1 id="let-the-computer-test-your-python-gui-application">Let the computer test your Python GUI application</h1>
<p>In this article you’ll see how easy it is to write automated tests for Graphical User Interfaces (GUIs) written using the brilliant <code>guizero</code> Python library.</p>
<p>I’ll start with a horror story which explains why I’m so keen on automated GUI tests.</p>
<p>Next I’ll describe an application that I’m using as an example. The code for the app and the test are available on GitHub; the link is in the resources section at the end of this post.</p>
<p>After that, I’ll show how the tests are built up and describe how they enabled me to find and fix a bug.</p>
<h2 id="a-personal-horror-story">A personal horror story</h2>
<p>A couple of years ago I presented a Quiz Runner application to an audience of Digital Makers.</p>
<p>The Quiz Runner application used a workstation to manage the Quiz.</p>
<p>Quiz Contestants used micro:bits to ‘buzz’ in when they thought they knew an answer.</p>
<p>The micro:bits communicated via radio using the micro:bit’s built-in radio capability, and everything (workstation and micro:bits) was programmed in Python.</p>
<p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrOYUOAjfuhv8_1oeHkdjr1J01q2uXJXiGVDqIQTZv_2-CqX7NSAtpAJ-vrjGvKnoed3ybYCzOoEu5K3QJfujhxAiG9-cG2kdDscZo2sa5Ec2lPCFbtrAyvnX_VtgUSVoWvdUcqTbHiYSXWMXtE-GmSP7Bgbu5vH131pM11AW4zHNnc_JvlDnTJIu8/s793/quiz-runner.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="561" data-original-width="793" height="226" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrOYUOAjfuhv8_1oeHkdjr1J01q2uXJXiGVDqIQTZv_2-CqX7NSAtpAJ-vrjGvKnoed3ybYCzOoEu5K3QJfujhxAiG9-cG2kdDscZo2sa5Ec2lPCFbtrAyvnX_VtgUSVoWvdUcqTbHiYSXWMXtE-GmSP7Bgbu5vH131pM11AW4zHNnc_JvlDnTJIu8/s320/quiz-runner.png" width="320" /></a></div>The Quiz Runner application had a simple Graphical User Interface (GUI) to control the quiz and keep scores, and a micro:bit connected via its serial interface to interact with the contestants’ micro:bits.<p></p>
<p>The demo started really well. Then something went wrong with the GUI and I had to abandon the demo. I was annoyed and embarrassed.</p>
<h3 id="software-craftsmanship">Software craftsmanship</h3>
<p>My grandfather was a carpenter, as were his father and Grandfather. They were craftsmen in wood. I like to think of myself as a craftsman in software, but I felt I’d just made a door that would not open.</p>
<p>When I had time to explore the problem I found it very hard to reproduce. I needed to hop between the QuizRunner App and the four team micro:bits, clicking and pressing the right things at the right time for dozens of permutations of behaviour.</p>
<p>I gave up.</p>
<p>The first time that you manually test a GUI application, it feels like fun.</p>
<p>The tenth time? Not so much.</p>
<h3 id="the-downsides-of-manual-testing">The downsides of manual testing</h3>
<p>Manual testing has its place, but it can be boring and error-prone. Worse still, there’s no automatic record of what was tested, or what worked.</p>
<p>Because it’s boring, many developers avoid it as far as possible. That can mean that edge cases get tested in QA or production rather than in development. That’s expensive - the later a bug is detected, the greater the cost of fixing it.</p>
<p>So how can you create automated tests for gui-based applications?</p>
<h2 id="how-can-you-use-automated-tests-with-guis">How can you use automated tests with GUIs?</h2>
<p>There are gui-testing libraries available, but the commercial products are expensive and most of the open-source tools I’ve found are cumbersome.</p>
<p><strong>There is good news</strong>, though, if you use Python’s excellent <a href="https://lawsie.github.io/guizero/about/">guizero</a> library.</p>
<p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoT-vY6PdITg-l4xkFVgneFldtPP-6W3J8B4dAr1nwnyCSKYNWPpC44kEhcVXllhDmsbkGrPbML1O0dEqnTr3nMe0QaTx2b93YNdlS8-ckzu5bpNYBTJOkk069BgXfJ7C3x-HqUQFhsVT9WI6FNJ1mpT8J3d00EBRoHzZ__zOTjfbxpP59k6fhTt5I/s683/cover.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="683" data-original-width="460" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoT-vY6PdITg-l4xkFVgneFldtPP-6W3J8B4dAr1nwnyCSKYNWPpC44kEhcVXllhDmsbkGrPbML1O0dEqnTr3nMe0QaTx2b93YNdlS8-ckzu5bpNYBTJOkk069BgXfJ7C3x-HqUQFhsVT9WI6FNJ1mpT8J3d00EBRoHzZ__zOTjfbxpP59k6fhTt5I/w135-h200/cover.png" width="135" /></a></div><code>guizero</code> was written by Laura Sach and Martin O’Hanlon.<p></p>
<p>They are experienced educators, and they work for the Raspberry Pi Foundation.</p>
<p><code>guizero</code> is easy to use, it has great documentation and there’s a super Book of Examples!</p>
<p>The book is called <em>Create Graphical User Interfaces with Python</em>, and it’s available from <a href="https://magpi.raspberrypi.com/books/create-guis">The MagPi website</a>.</p>
<p>I’m a big fan of <code>guizero</code>. It ticks all the boxes in my <a href="https://blog.rareschool.com/2022/04/choosing-python-library.html">Python library checklist</a>, and I use it a lot. The library has lots of automated tests, but the book is aimed at beginners, so it recommends manual testing.</p>
<p>To keep the code simple, the book also makes use of global variables. I’m happy with that in a book for beginners, but experienced software developers try to avoid globals in their code.</p>
<p>I wondered how easy it would be for me to <em>refactor</em> to eliminate the globals, and to remove some code duplication.</p>
<h2 id="refactoring-the-original-code">Refactoring the original code</h2>
<p>Refactoring is a technique that you can use to improve the design of existing code without its external behaviour.</p>
<p>You may have come across Martin Fowler’s book on the subject. It’s a classic, and I refer to it a lot.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfBw2mPTctGmZPPS2DHXKoorPODu25MOZdw4mJw_2_R9dYvQuPfR7qpPmCG9MfW79C8OOROCNuxQ-eA0k43yw6PNGKh4F2to0pJikFMlNqQh9epW0vHUuzqjpZJb5CQUyiSmdA35H_9w0hBP6w4INFgq6g0Q8Q9g8hfhH3iPG-pQCDSQNX3vUhKRkx/s3024/refactoring.jpg" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"><img border="0" data-original-height="3024" data-original-width="2384" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfBw2mPTctGmZPPS2DHXKoorPODu25MOZdw4mJw_2_R9dYvQuPfR7qpPmCG9MfW79C8OOROCNuxQ-eA0k43yw6PNGKh4F2to0pJikFMlNqQh9epW0vHUuzqjpZJb5CQUyiSmdA35H_9w0hBP6w4INFgq6g0Q8Q9g8hfhH3iPG-pQCDSQNX3vUhKRkx/w158-h200/refactoring.jpg" width="158" /></a></div><figure><figcaption aria-hidden="true">In order to refactor safely, you need automated tests. Refactoring is best done in baby steps unless the code is very simple, or you are very brave. You run the automated tests after each baby step and use git to commit the code if the test passes. That way you always have a working version as you progress towards the version that you’re aiming for.</figcaption></figure>
<p>I refactored one of my favourite examples from <em>Create Graphical User Interfaces with Python</em>. It’s a game of Noughts and Crosses (or Tic-tac-toe if you’re reading this in North America).</p>
<p>I ended up with code that had no globals and tests that exercised the system thoroughly.</p>
<h2 id="how-do-the-tests-work">How do the tests work?</h2>
<h3 id="add-the-magic-code-that-pushes-buttons">Add the magic code that pushes buttons</h3>
<p>The most important code is this short fragment:</p>
<pre><code>from guizero import PushButton
def push(button: PushButton) -> None:
button.tk.invoke()</code></pre>
<div style="text-align: left;">It allows you to write code in your test that has the same effect ass a user pressing a button in the GUI.</div><div style="text-align: left;"><br /></div><div style="text-align: left;">I found it buried in the unit tests for the <span style="font-family: courier;">guizero</span> library.</div><h3 id="set-up-the-test-fixtures">Set up the test fixtures</h3>
<p>You create unit tests by writing Test Cases.</p>
<p>You set up the environment for your tests by creating test fixtures.</p>
<p>Opening a GUI application takes time, so you want to do it once per Test Case.</p>
<p>You do that by writing a <em>class method</em> called <code>setUpClass</code>.</p>
<pre><code>import unittest
from tictactoe import TicTacToeApp
class TicTacToeTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls.app = TicTacToeApp()</code></pre>
<p>You write individual tests by creating Test Case methods whose names start with <code>test</code>.</p>
<p>The TestCase will run these in random order, so you need to make sure that your tests don’t interfere with each other.</p>
<p>You do that by writing a <code>setUp</code> method which will reset the game before each test method is run.</p>
<pre><code>def setUp(self) -> None:
self.app.reset_board()</code></pre>
<p>This calls the <code>reset_board</code> method in the application:</p>
<pre><code>def reset_board(self):
for x in range(3):
for y in range(3):
self.square(x, y).text = " "
self.square(x, y).enable()
self.winner = None
self.current_player = 'X'
self.message.value = 'It is your turn, X'</code></pre>
<h3 id="write-the-tests">Write the tests</h3>
<p>Next you write tests to check that the game is working correctly.</p>
<p>Each test simulates a player making a move by clicking on a free cell on the board.</p>
<p>The tests also check whose turn it is before making the move.</p>
<p>The tests use a couple of helper methods to make the tests more readable.</p>
<p>There’s an excellent discussion of test readability in Clean Code. (See the resources at the end of the article.)</p>
<h3 id="use-helper-methods-to-make-tests-more-readable">Use helper methods to make tests more readable</h3>
<p>Here are the helper methods:</p>
<pre><code>def message_value(self):
return self.app.message.value
def play(self, x, y, player):
self.assertEqual(self.message_value(), 'It is your turn, %s' % player)
self.push(x, y)</code></pre>
<p>The <code>message_value</code> method is just a concise way of finding the text of the last message sent by the game.</p>
<p>The <code>play</code> method checks that the last message tells the current player it’s their turn to play, and then clicks on the button that is specified by the x and y coordinates.</p>
<h3 id="write-the-first-test">Write the first test</h3>
<p>The first test just checks that the player changes after a move.</p>
<pre><code>def test_turn_changes_after_player_moves(self):
self.play(0, 0, 'X')
self.assertEqual(self.message_value(), 'It is your turn, O')</code></pre>
<p>That test passes. That’s good news. It tells you that the refactoring hasn’t broken that behaviour.</p>
<h3 id="test-a-game-that-x-wins">Test a game that X wins</h3>
<p>Next write a test to check that the game knows when X has won.</p>
<pre><code>def test_knows_if_x_has_won(self):
self.play(0, 0, 'X')
self.play(0, 1, 'O')
self.play(1, 0, 'X')
self.play(0, 2, 'O')
self.play(2, 0, 'X')
self.assertEqual(self.message_value(), 'X wins!')</code></pre>
<p>That passes. You’re on a roll!</p>
<h3 id="test-a-win-for-o">Test a win for O</h3>
<p>Here’s a game that O wins.</p>
<pre><code>def test_knows_if_o_has_won(self):
self.play(0, 0, 'X')
self.play(0, 1, 'O')
self.play(1, 0, 'X')
self.play(1, 1, 'O')
self.play(1, 2, 'X')
self.play(2, 1, 'O')
self.assertEqual(self.message_value(), 'O wins!')</code></pre>
<h3 id="check-for-a-drawn-game">Check for a drawn game</h3>
<p>If the last square is filled without either player winning, the game is drawn.</p>
<p>Here’s a test for that:</p>
<pre><code>def test_recognises_draw(self):
self.play(0, 0, 'X')
self.play(1, 1, 'O')
self.play(2, 2, 'X')
self.play(0, 1, 'O')
self.play(2, 1, 'X')
self.play(2, 0, 'O')
self.play(0, 2, 'X')
self.play(1, 2, 'O')
self.play(1, 0, 'X')
self.assertEqual("It's a draw", self.message_value())</code></pre>
<p>So far so good. But…</p>
<h3 id="finding-and-fixing-a-bug">Finding and fixing a bug</h3>
<p>When I was writing one of the tests I saw some strange behaviour. When I played the original version of the game I confirmed that it has a bug.</p>
<p>You can carry on making moves after the game has been won!</p>
<p>When you find a bug, you need to do four things.</p>
<ol type="1">
<li>Write a test that demonstrates the bug by failing.</li>
<li>Fix the bug</li>
<li>Verify that the test now passes</li>
<li>Check in your code!</li>
</ol>
<h4 id="verify-the-bug">Verify the bug</h4>
<p>Here’s the test that demonstrates the bug. When you run it on an unfixed application it fails.</p>
<pre><code>def test_game_stops_when_someone_wins(self):
self.play(0, 0, 'X')
self.play(0, 1, 'O')
self.play(1, 0, 'X')
self.play(1, 1, 'O')
self.play(1, 2, 'X')
self.play(2, 1, 'O')
# O wins!
self.push(0, 2) # should be ignored
self.push(2, 0) # should be ignored
self.push(2, 2) # should be ignored
self.assertEqual(self.message_value(), 'O wins!')</code></pre>
<h4 id="fix-the-bug">Fix the bug</h4>
<p>Here’s the application code that fixes the bug:</p>
<pre><code>def disable_all_squares(self):
for i in range(3):
for j in range(3):
self.square(i, j).disable()</code></pre>
<p>The application needs to invoke that method when a game has been won.</p>
<h4 id="verify-the-bug-is-fixed">Verify the bug is fixed</h4>
<p>If you now run the tests they all pass, so it’s safe to check in your changes.</p>
<p>Success! You now have a working, tested application.</p>
<h2 id="resources">Resources</h2><div>The code for this article is <a href="https://github.com/romilly/testing-guizero-apps">on GitHub</a>.</div>
<p>guizero is available on <a href="https://lawsie.github.io/guizero/about/">GitHub</a>.</p>
<p>You can install it via pip.</p>
<p><code>pip3 install guizero</code></p>
<p>Documentation is available <a href="https://lawsie.github.io/guizero/start/">here</a>.</p>
<p>The book ‘Create Graphical User Interfaces with Python’ is available from the <a href="https://magpi.raspberrypi.com/books/create-guis">MagPi website</a>.</p>
<p>I mentioned two other books:</p>
<p><a href="https://amazon.co.uk/Refactoring-Improving-Existing-Addison-Wesley-Technology/dp/0134757599">Refactoring</a> by Martin Fowler, and <a href="https://amazon.co.uk/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882">Clean Code</a> by Robert C. Martin.</p>
<p><b>Questions</b>? Ask in a comment, or tweet me at @RAREblog.</p>
<p>Image credits:</p>
<p>Micro:bit images courtesy of https://microbit.org Radio beacon: https://en.wikipedia.org/wiki/File:Wireless_tower.svg</p>
Romilly Cockinghttp://www.blogger.com/profile/04944907795639823332noreply@blogger.com0